| #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(); |
| } |
| } |