blob: 1de3b85683027f6be1bba038f637fd4b6bd78aaf [file]
#include "NVMeFeatureStore.hpp"
#include "nvme_features.pb.h"
#include <endian.h>
#include <boost/asio/async_result.hpp>
#include <boost/asio/post.hpp>
#include <phosphor-logging/lg2.hpp>
#include <algorithm>
#include <cstring>
#include <iomanip>
#include <map>
#include <sstream>
#include <tuple>
#include <utility>
#include <vector>
static constexpr uint32_t nvmeGetFeaturesCapSaveable = 0x1;
static constexpr uint32_t nvmeGetFeaturesCapChangeable = 0x4;
struct FeatureMeta
{
uint8_t fid; ///< NVMe Feature Identifier
std::string protoClass; ///< Fully qualified Protobuf class name
FeatureScope scope; ///< Applicability scope
};
/**
* @brief Map of P0 features defined in the design document.
*/
static const std::map<std::string, FeatureMeta> supportedFeatures = {
{"Arbitration",
{0x01, "google.gbmc.nvme.base.Arbitration", FeatureScope::Controller}},
{"PowerManagement",
{0x02, "google.gbmc.nvme.base.PowerManagement", FeatureScope::Controller}},
{"TemperatureThreshold",
{0x04, "google.gbmc.nvme.base.TemperatureThreshold",
FeatureScope::Controller}},
{"ErrorRecovery",
{0x05, "google.gbmc.nvme.base.ErrorRecovery", FeatureScope::Namespace}},
{"VolatileWriteCache",
{0x06, "google.gbmc.nvme.base.VolatileWriteCache",
FeatureScope::Controller}},
{"NumberOfQueues",
{0x07, "google.gbmc.nvme.base.NumberOfQueues", FeatureScope::Controller}},
{"InterruptCoalescing",
{0x08, "google.gbmc.nvme.base.InterruptCoalescing",
FeatureScope::Controller}},
{"InterruptVectorConfiguration",
{0x09, "google.gbmc.nvme.base.InterruptVectorConfiguration",
FeatureScope::Controller}},
{"WriteAtomicityNormal",
{0x0A, "google.gbmc.nvme.base.WriteAtomicityNormal",
FeatureScope::Controller}},
{"AsynchronousEventConfiguration",
{0x0B, "google.gbmc.nvme.base.AsynchronousEventConfiguration",
FeatureScope::Controller}},
};
template <typename T>
static T parseProto(const std::vector<uint8_t>& data)
{
T msg;
if (!msg.ParseFromArray(data.data(), static_cast<int>(data.size())))
{
throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
}
return msg;
}
// Helper for async_initiate
static std::tuple<nvme_ex_ptr, uint32_t, std::vector<uint8_t>>
asyncAdminGetFeatures(boost::asio::io_context& io,
const std::shared_ptr<NVMeMiIntf>& nvmeIntf,
nvme_mi_ctrl_t ctrl,
const NVMeMiIntf::GetFeaturesRequest& req,
boost::asio::yield_context yield)
{
return boost::asio::async_initiate<boost::asio::yield_context,
void(nvme_ex_ptr, uint32_t,
std::vector<uint8_t>)>(
[&io](auto&& callback, const std::shared_ptr<NVMeMiIntf>& nvmeIntf,
nvme_mi_ctrl_t ctrl, const NVMeMiIntf::GetFeaturesRequest& req) {
auto sharedCb = std::make_shared<std::decay_t<decltype(callback)>>(
std::forward<decltype(callback)>(callback));
nvmeIntf->adminGetFeatures(
ctrl, req,
[sharedCb, &io](nvme_ex_ptr ex, uint32_t resp,
std::span<uint8_t> data) mutable {
std::vector<uint8_t> dataCopy(data.begin(), data.end());
boost::asio::post(io, [sharedCb = std::move(sharedCb), ex, resp,
dataCopy = std::move(dataCopy)]() mutable {
(*sharedCb)(ex, resp, dataCopy);
});
});
},
yield, nvmeIntf, ctrl, req);
}
static std::tuple<nvme_ex_ptr, uint32_t> asyncAdminSetFeatures(
boost::asio::io_context& io, const std::shared_ptr<NVMeMiIntf>& nvmeIntf,
nvme_mi_ctrl_t ctrl, const NVMeMiIntf::SetFeaturesRequest& req,
boost::asio::yield_context yield)
{
return boost::asio::async_initiate<boost::asio::yield_context,
void(nvme_ex_ptr, uint32_t)>(
[&io](auto&& callback, const std::shared_ptr<NVMeMiIntf>& nvmeIntf,
nvme_mi_ctrl_t ctrl, const NVMeMiIntf::SetFeaturesRequest& req) {
auto sharedCb = std::make_shared<std::decay_t<decltype(callback)>>(
std::forward<decltype(callback)>(callback));
nvmeIntf->adminSetFeatures(
ctrl, req, [sharedCb, &io](nvme_ex_ptr ex, uint32_t resp) mutable {
boost::asio::post(io, [sharedCb = std::move(sharedCb), ex,
resp]() mutable { (*sharedCb)(ex, resp); });
});
},
yield, nvmeIntf, ctrl, req);
}
static std::tuple<nvme_ex_ptr, std::vector<uint8_t>> asyncAdminIdentify(
boost::asio::io_context& io, const std::shared_ptr<NVMeMiIntf>& nvmeIntf,
nvme_mi_ctrl_t ctrl, nvme_identify_cns cns, uint32_t nsid, uint16_t cntid,
boost::asio::yield_context yield)
{
return boost::asio::async_initiate<boost::asio::yield_context,
void(nvme_ex_ptr, std::vector<uint8_t>)>(
[&io](auto&& callback, const std::shared_ptr<NVMeMiIntf>& nvmeIntf,
nvme_mi_ctrl_t ctrl, nvme_identify_cns cns, uint32_t nsid,
uint16_t cntid) {
auto sharedCb = std::make_shared<std::decay_t<decltype(callback)>>(
std::forward<decltype(callback)>(callback));
nvmeIntf->adminIdentify(
ctrl, cns, nsid, cntid,
[sharedCb, &io](nvme_ex_ptr ex, std::span<uint8_t> data) mutable {
std::vector<uint8_t> dataCopy(data.begin(), data.end());
boost::asio::post(io, [sharedCb = std::move(sharedCb), ex,
dataCopy = std::move(dataCopy)]() mutable {
(*sharedCb)(ex, dataCopy);
});
});
},
yield, nvmeIntf, ctrl, cns, nsid, cntid);
}
static std::tuple<std::error_code, std::vector<uint8_t>> asyncAdminGetLogPage(
boost::asio::io_context& io, const std::shared_ptr<NVMeMiIntf>& nvmeIntf,
nvme_mi_ctrl_t ctrl, nvme_cmd_get_log_lid lid, uint32_t nsid, uint8_t lsp,
uint16_t lsi, boost::asio::yield_context yield)
{
return boost::asio::async_initiate<boost::asio::yield_context,
void(std::error_code,
std::vector<uint8_t>)>(
[&io](auto&& callback, const std::shared_ptr<NVMeMiIntf>& nvmeIntf,
nvme_mi_ctrl_t ctrl, nvme_cmd_get_log_lid lid, uint32_t nsid,
uint8_t lsp, uint16_t lsi) {
auto sharedCb = std::make_shared<std::decay_t<decltype(callback)>>(
std::forward<decltype(callback)>(callback));
nvmeIntf->adminGetLogPage(
ctrl, lid, nsid, lsp, lsi,
[sharedCb, &io](const std::error_code& ec,
std::span<uint8_t> data) mutable {
std::vector<uint8_t> dataCopy(data.begin(), data.end());
boost::asio::post(io, [sharedCb = std::move(sharedCb), ec,
dataCopy = std::move(dataCopy)]() mutable {
(*sharedCb)(ec, dataCopy);
});
});
},
yield, nvmeIntf, ctrl, lid, nsid, lsp, lsi);
}
FeatureStore::FeatureStore(
boost::asio::io_context& io, sdbusplus::asio::object_server& objServer,
const std::shared_ptr<sdbusplus::asio::connection>& conn,
const std::string& objectPath, std::shared_ptr<NVMeMiIntf> nvmeIntf,
nvme_mi_ctrl_t ctrl, uint32_t nsid, FeatureScope scope) :
io(io), objServer(objServer), conn(conn), objectPath(objectPath),
nvmeIntf(std::move(nvmeIntf)), ctrl(ctrl), nsid(nsid), scope(scope)
{}
void FeatureStore::registerDbusInterface()
{
dbusIntf = objServer.add_interface(objectPath,
"xyz.openbmc_project.NVMe.FeatureStore");
dbusIntf->register_property("ProtoLibraryVersion", std::string("2.0"),
sdbusplus::asio::PropertyPermission::readOnly);
dbusIntf->register_property("FeatureCollection", featureCollection,
sdbusplus::asio::PropertyPermission::readOnly);
dbusIntf->register_method(
"GetFeature",
[weakSelf = weak_from_this()](const boost::asio::yield_context& yield,
const std::string& featureName) {
auto self = weakSelf.lock();
if (!self)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
}
return self->getFeature(yield, featureName);
});
dbusIntf->register_method(
"SetFeature",
[weakSelf = weak_from_this()](const boost::asio::yield_context& yield,
const std::string& featureName,
const std::vector<uint8_t>& data) {
auto self = weakSelf.lock();
if (!self)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
}
self->setFeature(yield, featureName, data);
});
dbusIntf->initialize();
}
void FeatureStore::init(std::function<void(const std::error_code&)>&& cb)
{
(void)boost::asio::spawn(
io, [self{shared_from_this()}, cb{std::move(cb)}](
const boost::asio::yield_context& yield) mutable {
self->fetchIdentifyData(yield);
self->registerDbusInterface();
cb({});
});
}
void FeatureStore::fetchIdentifyData(const boost::asio::yield_context& yield)
{
auto [ex, data] = asyncAdminIdentify(
io, nvmeIntf, ctrl, NVME_IDENTIFY_CNS_CTRL, NVME_NSID_NONE, 0, yield);
if (ex)
{
lg2::error(
"Identify Controller failed during FeatureStore init: {ERROR}",
"ERROR", ex->what());
discoverFeatures(yield);
return;
}
if (data.size() < sizeof(nvme_id_ctrl))
{
lg2::error("Identify Controller returned insufficient data");
discoverFeatures(yield);
return;
}
nvme_id_ctrl idCtrl = {};
std::memcpy(&idCtrl, data.data(), sizeof(nvme_id_ctrl));
vwcSupported = (idCtrl.vwc & NVME_CTRL_VWC_PRESENT) != 0;
oncs = le16toh(idCtrl.oncs);
if (!vwcSupported)
{
lg2::info("Volatile Write Cache (FID 0x06) not supported by hardware");
}
discoverFeatures(yield);
}
void FeatureStore::discoverFeatures(const boost::asio::yield_context& yield)
{
bool oncsBit4 = (oncs & (1 << 4)) != 0;
featureCollection.clear();
std::vector<std::pair<std::string, FeatureMeta>> featuresToCheck;
for (const auto& [name, meta] : supportedFeatures)
{
if (meta.scope == scope)
{
if (name == "VolatileWriteCache" && !vwcSupported)
{
continue;
}
featuresToCheck.emplace_back(name, meta);
}
}
// Fetch Log Page 12h (Feature Identifiers Supported and Effects)
std::vector<uint8_t> fidSupportedEffects;
auto [ec, logData] = asyncAdminGetLogPage(
io, nvmeIntf, ctrl, NVME_LOG_LID_FID_SUPPORTED_EFFECTS, NVME_NSID_NONE,
0, 0, yield);
if (!ec)
{
if (logData.size() == sizeof(struct nvme_fid_supported_effects_log))
{
fidSupportedEffects = std::move(logData);
}
else
{
lg2::error(
"Log Page 12h size mismatch: expected {EXPECTED}, got {GOT}",
"EXPECTED", sizeof(struct nvme_fid_supported_effects_log),
"GOT", logData.size());
}
}
else
{
lg2::error("Failed to fetch Log Page 12h: {ERROR}", "ERROR",
ec.message());
}
for (const auto& [name, meta] : featuresToCheck)
{
if (!fidSupportedEffects.empty())
{
size_t offset = static_cast<size_t>(meta.fid) * 4;
if (offset + sizeof(uint32_t) <= fidSupportedEffects.size())
{
uint32_t entry = 0;
std::memcpy(&entry, fidSupportedEffects.data() + offset,
sizeof(entry));
entry = le32toh(entry);
if ((entry & 0x1) == 0) // FSUPP bit
{
lg2::info(
"Feature {NAME} (FID {FID}) not supported per Log Page 12h",
"NAME", name, "FID", meta.fid);
continue;
}
}
else
{
lg2::error("Log Page 12h data too short for FID {FID}", "FID",
meta.fid);
continue;
}
}
std::stringstream ss;
ss << "0x" << std::setfill('0') << std::setw(2) << std::hex
<< static_cast<int>(meta.fid);
std::string fidStr = ss.str();
if (!oncsBit4)
{
featureCollection.emplace_back(name + "_Current", fidStr,
meta.protoClass);
continue;
}
NVMeMiIntf::GetFeaturesRequest args{};
args.fid = meta.fid;
args.sel = NVME_GET_FEATURES_SEL_SUPPORTED; // 011b
args.nsid = (scope == FeatureScope::Namespace) ? nsid : NVME_NSID_NONE;
args.cdw11 = 0;
args.data_len = 0;
auto [ex, cdw0, data] = asyncAdminGetFeatures(io, nvmeIntf, ctrl, args,
yield);
if (ex)
{
lg2::error("Failed to query capabilities for FID {FID}: {ERROR}",
"FID", meta.fid, "ERROR", ex->what());
featureCollection.emplace_back(name + "_Current", fidStr,
meta.protoClass);
}
else
{
featureCaps[meta.fid] = cdw0;
featureCollection.emplace_back(name + "_Current", fidStr,
meta.protoClass);
if ((cdw0 & nvmeGetFeaturesCapChangeable) != 0)
{
featureCollection.emplace_back(name + "_Default", fidStr,
meta.protoClass);
}
if ((cdw0 & nvmeGetFeaturesCapSaveable) != 0)
{
featureCollection.emplace_back(name + "_Saved", fidStr,
meta.protoClass);
}
}
}
}
bool FeatureStore::isFeatureSupported(const std::string& featureName) const
{
return std::any_of(featureCollection.begin(), featureCollection.end(),
[&featureName](const auto& item) {
return std::get<0>(item) == featureName;
});
}
std::vector<uint8_t>
FeatureStore::getFeature(const boost::asio::yield_context& yield,
const std::string& featureName)
{
if (!isFeatureSupported(featureName))
{
throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
}
size_t lastUnderscore = featureName.find_last_of('_');
if (lastUnderscore == std::string::npos)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
}
std::string baseName = featureName.substr(0, lastUnderscore);
std::string selectStr = featureName.substr(lastUnderscore + 1);
auto it = supportedFeatures.find(baseName);
if (it == supportedFeatures.end() || it->second.scope != scope)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
}
uint8_t fid = it->second.fid;
uint8_t sel = 0;
if (selectStr == "Current")
{
sel = 0;
}
else if (selectStr == "Default")
{
sel = 1;
}
else if (selectStr == "Saved")
{
sel = 2;
}
else
{
throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
}
NVMeMiIntf::GetFeaturesRequest args{};
args.fid = fid;
args.sel = sel;
args.nsid = (scope == FeatureScope::Namespace) ? nsid : NVME_NSID_NONE;
args.cdw11 = 0;
args.data_len = 0;
auto [ex, cdw0, data] = asyncAdminGetFeatures(io, nvmeIntf, ctrl, args,
yield);
if (ex)
{
lg2::error("NVMe Get Features (FID {FID}) failed: {ERROR}", "FID", fid,
"ERROR", ex->what());
throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
}
std::vector<uint8_t> protoData;
switch (fid)
{
case 0x01:
{
google::gbmc::nvme::base::Arbitration msg;
msg.set_arbitration_burst(cdw0 & 0xFF);
msg.set_low_priority_weight((cdw0 >> 8) & 0xFF);
msg.set_medium_priority_weight((cdw0 >> 16) & 0xFF);
msg.set_high_priority_weight((cdw0 >> 24) & 0xFF);
protoData.resize(msg.ByteSizeLong());
if (!msg.SerializeToArray(protoData.data(),
static_cast<int>(protoData.size())))
{
lg2::error("Failed to serialize Arbitration");
}
break;
}
case 0x02:
{
google::gbmc::nvme::base::PowerManagement msg;
msg.set_power_state(cdw0 & 0x1F);
msg.set_workload_hint((cdw0 >> 8) & 0x7);
protoData.resize(msg.ByteSizeLong());
if (!msg.SerializeToArray(protoData.data(),
static_cast<int>(protoData.size())))
{
lg2::error("Failed to serialize PowerManagement");
}
break;
}
case 0x04:
{
google::gbmc::nvme::base::TemperatureThreshold msg;
msg.set_temperature_threshold(cdw0 & 0xFFFF);
msg.set_threshold_temperature_select((cdw0 >> 16) & 0xF);
msg.set_threshold_type_select(
static_cast<google::gbmc::nvme::base::
TemperatureThreshold_ThresholdType>(
(cdw0 >> 20) & 0x3));
protoData.resize(msg.ByteSizeLong());
if (!msg.SerializeToArray(protoData.data(),
static_cast<int>(protoData.size())))
{
lg2::error("Failed to serialize TemperatureThreshold");
}
break;
}
case 0x05:
{
google::gbmc::nvme::base::ErrorRecovery msg;
msg.set_tler(cdw0 & 0xFFFF);
msg.set_dulbe_enable(
static_cast<google::gbmc::nvme::base::ErrorRecovery_State>(
(cdw0 >> 16) & 0x1));
protoData.resize(msg.ByteSizeLong());
if (!msg.SerializeToArray(protoData.data(),
static_cast<int>(protoData.size())))
{
lg2::error("Failed to serialize ErrorRecovery");
}
break;
}
case 0x06:
{
google::gbmc::nvme::base::VolatileWriteCache msg;
msg.set_write_cache_enable(
static_cast<google::gbmc::nvme::base::VolatileWriteCache_State>(
cdw0 & 0x1));
protoData.resize(msg.ByteSizeLong());
if (!msg.SerializeToArray(protoData.data(),
static_cast<int>(protoData.size())))
{
lg2::error("Failed to serialize VolatileWriteCache");
}
break;
}
case 0x07:
{
google::gbmc::nvme::base::NumberOfQueues msg;
msg.set_number_of_submission_queues(cdw0 & 0xFFFF);
msg.set_number_of_completion_queues((cdw0 >> 16) & 0xFFFF);
protoData.resize(msg.ByteSizeLong());
if (!msg.SerializeToArray(protoData.data(),
static_cast<int>(protoData.size())))
{
lg2::error("Failed to serialize NumberOfQueues");
}
break;
}
case 0x08:
{
google::gbmc::nvme::base::InterruptCoalescing msg;
msg.set_aggregation_threshold(cdw0 & 0xFF);
msg.set_aggregation_time((cdw0 >> 8) & 0xFF);
protoData.resize(msg.ByteSizeLong());
if (!msg.SerializeToArray(protoData.data(),
static_cast<int>(protoData.size())))
{
lg2::error("Failed to serialize Interrupt Coalescing");
}
break;
}
case 0x09:
{
google::gbmc::nvme::base::InterruptVectorConfiguration msg;
msg.set_interrupt_vector(cdw0 & 0xFFFF);
msg.set_coalescing_disable(
static_cast<google::gbmc::nvme::base::
InterruptVectorConfiguration_CoalescingState>(
(cdw0 >> 16) & 0x1));
protoData.resize(msg.ByteSizeLong());
if (!msg.SerializeToArray(protoData.data(),
static_cast<int>(protoData.size())))
{
lg2::error("Failed to serialize InterruptVectorConfiguration");
}
break;
}
case 0x0A:
{
google::gbmc::nvme::base::WriteAtomicityNormal msg;
msg.set_disable_normal(
static_cast<google::gbmc::nvme::base::
WriteAtomicityNormal_NormalWriteAtomicityState>(
cdw0 & 0x1));
protoData.resize(msg.ByteSizeLong());
if (!msg.SerializeToArray(protoData.data(),
static_cast<int>(protoData.size())))
{
lg2::error("Failed to serialize WriteAtomicityNormal");
}
break;
}
case 0x0B:
{
google::gbmc::nvme::base::AsynchronousEventConfiguration msg;
msg.set_smart_critical_warning_config(cdw0 & 0xFF);
protoData.resize(msg.ByteSizeLong());
if (!msg.SerializeToArray(protoData.data(),
static_cast<int>(protoData.size())))
{
lg2::error(
"Failed to serialize AsynchronousEventConfiguration");
}
break;
}
default:
break;
}
return protoData;
}
void FeatureStore::setFeature(const boost::asio::yield_context& yield,
const std::string& featureName,
const std::vector<uint8_t>& data)
{
if (!isFeatureSupported(featureName))
{
throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
}
size_t lastUnderscore = featureName.find_last_of('_');
if (lastUnderscore == std::string::npos)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
}
std::string baseName = featureName.substr(0, lastUnderscore);
std::string selectStr = featureName.substr(lastUnderscore + 1);
auto it = supportedFeatures.find(baseName);
if (it == supportedFeatures.end() || it->second.scope != scope)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
}
if (selectStr == "Default")
{
// Default endpoint is read-only
throw sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed();
}
if (selectStr != "Current" && selectStr != "Saved")
{
throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
}
uint8_t fid = it->second.fid;
bool save = (selectStr == "Saved");
auto capIt = featureCaps.find(fid);
if (capIt != featureCaps.end())
{
uint32_t caps = capIt->second;
bool changeable = (caps & nvmeGetFeaturesCapChangeable) != 0;
bool saveable = (caps & nvmeGetFeaturesCapSaveable) != 0;
if (save && !saveable)
{
lg2::error("SetFeature: FID {FID} is not saveable per SEL=011b",
"FID", lg2::hex, fid);
throw sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed();
}
if (!save && !changeable)
{
lg2::error("SetFeature: FID {FID} is not changeable per SEL=011b",
"FID", lg2::hex, fid);
throw sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed();
}
}
uint32_t cdw11 = 0;
switch (fid)
{
case 0x01:
{
auto msg = parseProto<google::gbmc::nvme::base::Arbitration>(data);
if (msg.arbitration_burst() > 0xFF ||
msg.low_priority_weight() > 0xFF ||
msg.medium_priority_weight() > 0xFF ||
msg.high_priority_weight() > 0xFF)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::
InvalidArgument();
}
cdw11 = (msg.arbitration_burst() & 0xFF) |
((msg.low_priority_weight() & 0xFF) << 8) |
((msg.medium_priority_weight() & 0xFF) << 16) |
((msg.high_priority_weight() & 0xFF) << 24);
break;
}
case 0x02:
{
auto msg =
parseProto<google::gbmc::nvme::base::PowerManagement>(data);
if (msg.power_state() > 0x1F || msg.workload_hint() > 0x7)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::
InvalidArgument();
}
cdw11 = (msg.power_state() & 0x1F) |
((msg.workload_hint() & 0x7) << 8);
break;
}
case 0x04:
{
auto msg =
parseProto<google::gbmc::nvme::base::TemperatureThreshold>(
data);
if (msg.temperature_threshold() > 0xFFFF ||
msg.threshold_temperature_select() > 0xF)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::
InvalidArgument();
}
cdw11 = (msg.temperature_threshold() & 0xFFFF) |
((msg.threshold_temperature_select() & 0xF) << 16) |
((static_cast<uint32_t>(msg.threshold_type_select()) & 0x3)
<< 20);
break;
}
case 0x05:
{
auto msg =
parseProto<google::gbmc::nvme::base::ErrorRecovery>(data);
if (msg.tler() > 0xFFFF)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::
InvalidArgument();
}
cdw11 = (msg.tler() & 0xFFFF) |
((static_cast<uint32_t>(msg.dulbe_enable()) & 0x1) << 16);
break;
}
case 0x06:
{
auto msg =
parseProto<google::gbmc::nvme::base::VolatileWriteCache>(data);
cdw11 = static_cast<uint32_t>(msg.write_cache_enable()) & 0x1;
break;
}
case 0x07:
{
auto msg =
parseProto<google::gbmc::nvme::base::NumberOfQueues>(data);
if (msg.number_of_submission_queues() > 0xFFFF ||
msg.number_of_completion_queues() > 0xFFFF)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::
InvalidArgument();
}
cdw11 = (msg.number_of_submission_queues() & 0xFFFF) |
((msg.number_of_completion_queues() & 0xFFFF) << 16);
break;
}
case 0x08:
{
auto msg =
parseProto<google::gbmc::nvme::base::InterruptCoalescing>(data);
if (msg.aggregation_threshold() > 0xFF ||
msg.aggregation_time() > 0xFF)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::
InvalidArgument();
}
cdw11 = (msg.aggregation_threshold() & 0xFF) |
((msg.aggregation_time() & 0xFF) << 8);
break;
}
case 0x09:
{
auto msg = parseProto<
google::gbmc::nvme::base::InterruptVectorConfiguration>(data);
if (msg.interrupt_vector() > 0xFFFF)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::
InvalidArgument();
}
cdw11 = (msg.interrupt_vector() & 0xFFFF) |
((static_cast<uint32_t>(msg.coalescing_disable()) & 0x1)
<< 16);
break;
}
case 0x0A:
{
auto msg =
parseProto<google::gbmc::nvme::base::WriteAtomicityNormal>(
data);
cdw11 = static_cast<uint32_t>(msg.disable_normal()) & 0x1;
break;
}
case 0x0B:
{
auto msg = parseProto<
google::gbmc::nvme::base::AsynchronousEventConfiguration>(data);
if (msg.smart_critical_warning_config() > 0xFF)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::
InvalidArgument();
}
cdw11 = msg.smart_critical_warning_config() & 0xFF;
break;
}
default:
break;
}
NVMeMiIntf::SetFeaturesRequest args{};
args.fid = fid;
args.save = save;
args.cdw11 = cdw11;
args.nsid = (scope == FeatureScope::Namespace) ? nsid : NVME_NSID_NONE;
args.data = {};
auto [ex, resp] = asyncAdminSetFeatures(io, nvmeIntf, ctrl, args, yield);
if (ex)
{
lg2::error("NVMe Set Features (FID {FID}) failed: {ERROR}", "FID", fid,
"ERROR", ex->what());
throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
}
}