FeatureStore: Add adminGetFeatures and adminSetFeatures to NVMeMi

Implements the low-level NVMe-MI admin commands for getting and setting
features.

Design-Doc: go/bmc-redfish-nvme-get-set-features
Google-Bug-Id: 405609473
Change-Id: I280a58835c9250d03b883ef1a5ddd8a5ce9aa24b
Signed-off-by: Guangzong Chen <guangzong@google.com>
diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
index 48b55f5..772ab4c 100644
--- a/src/NVMeIntf.hpp
+++ b/src/NVMeIntf.hpp
@@ -182,6 +182,32 @@
         nvme_mi_ctrl_t ctrl, nvme_identify_cns cns, uint32_t nsid,
         uint16_t cntid,
         std::function<void(nvme_ex_ptr, std::span<uint8_t>)>&& cb) = 0;
+    struct GetFeaturesRequest
+    {
+        uint8_t fid;
+        uint8_t sel;
+        uint32_t cdw11;
+        uint32_t nsid;
+        size_t data_len;
+    };
+
+    virtual void
+        adminGetFeatures(nvme_mi_ctrl_t ctrl, const GetFeaturesRequest& req,
+                         std::function<void(nvme_ex_ptr, uint32_t response,
+                                            std::span<uint8_t>)>&& cb) = 0;
+
+    struct SetFeaturesRequest
+    {
+        uint8_t fid;
+        uint32_t cdw11;
+        uint32_t nsid;
+        bool save;
+        std::span<const uint8_t> data;
+    };
+
+    virtual void adminSetFeatures(
+        nvme_mi_ctrl_t ctrl, const SetFeaturesRequest& req,
+        std::function<void(nvme_ex_ptr, uint32_t response)>&& cb) = 0;
     virtual void adminGetLogPage(
         nvme_mi_ctrl_t ctrl, nvme_cmd_get_log_lid lid, uint32_t nsid,
         uint8_t lsp, uint16_t lsi,
diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
index a58239c..195ef1c 100644
--- a/src/NVMeMi.cpp
+++ b/src/NVMeMi.cpp
@@ -843,6 +843,146 @@
     }
 }
 
+void NVMeMi::adminGetFeatures(
+    nvme_mi_ctrl_t ctrl, const GetFeaturesRequest& req,
+    std::function<void(nvme_ex_ptr, uint32_t, std::span<uint8_t>)>&& cb)
+{
+    if (auto degraded = isEndpointDegraded())
+    {
+        lg2::error("MCTP connection is not established", "DEVICE",
+                   device->describe());
+        boost::asio::post(io, [cb{std::move(cb)}]() {
+            cb(makeLibNVMeError("nvme endpoint is degraded"), 0, {});
+        });
+        return;
+    }
+    try
+    {
+        post([ctrl, fid{req.fid}, sel{req.sel}, cdw11{req.cdw11},
+              nsid{req.nsid}, dataLen{req.data_len}, self{shared_from_this()},
+              ep{endpoint}, cb{std::move(cb)}]() mutable {
+            nvme_get_features_args getArgs{};
+            getArgs.args_size = sizeof(getArgs);
+            getArgs.fid = fid;
+            getArgs.sel = static_cast<nvme_get_features_sel>(sel);
+            getArgs.cdw11 = cdw11;
+            getArgs.nsid = nsid;
+
+            std::vector<uint8_t> data;
+            if (dataLen > 0)
+            {
+                data.resize(dataLen);
+                getArgs.data = data.data();
+                getArgs.data_len = dataLen;
+            }
+
+            uint32_t response = 0;
+            getArgs.result = &response;
+
+            int rc = nvme_mi_admin_get_features(ctrl, &getArgs);
+
+            if (rc < 0)
+            {
+                lg2::error("fail to do nvme get features: {ERROR}", "ENDPOINT",
+                           ep->describe(), "ERROR", strerror(errno));
+            }
+            else if (rc > 0)
+            {
+                std::string_view errMsg =
+                    statusToString(static_cast<nvme_mi_resp_status>(rc));
+                lg2::error("fail to do nvme get features: {ERROR}", "ENDPOINT",
+                           ep->describe(), "ERROR", errMsg.data());
+            }
+
+            auto ex = makeLibNVMeError(errno, rc, "adminGetFeatures");
+
+            boost::asio::post(self->io, [cb{cb}, ex, response,
+                                         data{std::move(data)}]() mutable {
+                std::span<uint8_t> span{data.data(), data.size()};
+                cb(ex, response, span);
+            });
+        });
+    }
+    catch (const std::runtime_error& e)
+    {
+        lg2::error("{ERROR}", "ENDPOINT", endpoint->describe(), "ERROR",
+                   e.what());
+        auto msg = std::string("Runtime error: ") + e.what();
+        boost::asio::post(io, [cb{std::move(cb)}, msg]() {
+            cb(makeLibNVMeError(msg), 0, {});
+        });
+        return;
+    }
+}
+
+void NVMeMi::adminSetFeatures(nvme_mi_ctrl_t ctrl,
+                              const SetFeaturesRequest& req,
+                              std::function<void(nvme_ex_ptr, uint32_t)>&& cb)
+{
+    if (auto degraded = isEndpointDegraded())
+    {
+        lg2::error("MCTP connection is not established", "DEVICE",
+                   device->describe());
+        boost::asio::post(io, [cb{std::move(cb)}]() {
+            cb(makeLibNVMeError("nvme endpoint is degraded"), 0);
+        });
+        return;
+    }
+    try
+    {
+        std::vector<uint8_t> data(req.data.begin(), req.data.end());
+
+        post([ctrl, fid{req.fid}, cdw11{req.cdw11}, nsid{req.nsid},
+              save{req.save}, data{std::move(data)}, self{shared_from_this()},
+              ep{endpoint}, cb{std::move(cb)}]() mutable {
+            nvme_set_features_args setArgs{};
+            setArgs.args_size = sizeof(setArgs);
+            setArgs.fid = fid;
+            setArgs.cdw11 = cdw11;
+            setArgs.nsid = nsid;
+            setArgs.save = save;
+
+            if (!data.empty())
+            {
+                setArgs.data = data.data();
+                setArgs.data_len = data.size();
+            }
+
+            uint32_t cdw0 = 0;
+            setArgs.result = &cdw0;
+
+            int rc = nvme_mi_admin_set_features(ctrl, &setArgs);
+
+            if (rc < 0)
+            {
+                lg2::error("fail to do nvme set features: {ERROR}", "ENDPOINT",
+                           ep->describe(), "ERROR", strerror(errno));
+            }
+            else if (rc > 0)
+            {
+                std::string_view errMsg =
+                    statusToString(static_cast<nvme_mi_resp_status>(rc));
+                lg2::error("fail to do nvme set features: {ERROR}", "ENDPOINT",
+                           ep->describe(), "ERROR", errMsg.data());
+            }
+
+            auto ex = makeLibNVMeError(errno, rc, "adminSetFeatures");
+
+            boost::asio::post(self->io,
+                              [cb{cb}, ex, cdw0]() mutable { cb(ex, cdw0); });
+        });
+    }
+    catch (const std::runtime_error& e)
+    {
+        lg2::error("{ERROR}", "ENDPOINT", endpoint->describe(), "ERROR",
+                   e.what());
+        auto msg = std::string("Runtime error: ") + e.what();
+        boost::asio::post(
+            io, [cb{std::move(cb)}, msg]() { cb(makeLibNVMeError(msg), 0); });
+        return;
+    }
+}
+
 static int nvmeMiAdminGetLogTelemetryHostRae(nvme_mi_ctrl_t ctrl, bool /*rae*/,
                                              __u64 offset, __u32 len, void* log)
 {
diff --git a/src/NVMeMi.hpp b/src/NVMeMi.hpp
index e048c2f..4efe698 100644
--- a/src/NVMeMi.hpp
+++ b/src/NVMeMi.hpp
@@ -1,3 +1,5 @@
+#pragma once
+
 #include "NVMeIntf.hpp"
 #include "Utils.hpp"
 
@@ -75,6 +77,13 @@
         nvme_mi_ctrl_t ctrl, nvme_identify_cns cns, uint32_t nsid,
         uint16_t cntid,
         std::function<void(nvme_ex_ptr, std::span<uint8_t>)>&& cb) override;
+    void
+        adminGetFeatures(nvme_mi_ctrl_t ctrl, const GetFeaturesRequest& req,
+                         std::function<void(nvme_ex_ptr, uint32_t response,
+                                            std::span<uint8_t>)>&& cb) override;
+    void adminSetFeatures(
+        nvme_mi_ctrl_t ctrl, const SetFeaturesRequest& req,
+        std::function<void(nvme_ex_ptr, uint32_t response)>&& cb) override;
     void adminGetLogPage(nvme_mi_ctrl_t ctrl, nvme_cmd_get_log_lid lid,
                          uint32_t nsid, uint8_t lsp, uint16_t lsi,
                          std::function<void(const std::error_code&,
diff --git a/src/NVMeMiFake.hpp b/src/NVMeMiFake.hpp
index 0871aa2..6b29101 100644
--- a/src/NVMeMiFake.hpp
+++ b/src/NVMeMiFake.hpp
@@ -189,6 +189,26 @@
         });
     }
 
+    void adminGetFeatures([[maybe_unused]] nvme_mi_ctrl_t ctrl,
+                          [[maybe_unused]] const GetFeaturesRequest& req,
+                          std::function<void(nvme_ex_ptr, uint32_t,
+                                             std::span<uint8_t>)>&& cb) override
+    {
+        boost::asio::post(io, [cb = std::move(cb)]() mutable {
+            cb(makeLibNVMeError("not supported"), 0, {});
+        });
+    }
+
+    void adminSetFeatures(
+        [[maybe_unused]] nvme_mi_ctrl_t ctrl,
+        [[maybe_unused]] const SetFeaturesRequest& req,
+        std::function<void(nvme_ex_ptr, uint32_t)>&& cb) override
+    {
+        boost::asio::post(io, [cb = std::move(cb)]() mutable {
+            cb(makeLibNVMeError("not supported"), 0);
+        });
+    }
+
     void adminGetLogPage(nvme_mi_ctrl_t /*ctrl*/, nvme_cmd_get_log_lid lid,
                          uint32_t /*nsid*/, uint8_t lsp, uint16_t /*lsi*/,
                          std::function<void(const std::error_code&,
diff --git a/tests/test_nvme_mi.cpp b/tests/test_nvme_mi.cpp
index 77e4927..5f1f02b 100644
--- a/tests/test_nvme_mi.cpp
+++ b/tests/test_nvme_mi.cpp
@@ -108,6 +108,15 @@
                  uint16_t cntid,
                  std::function<void(nvme_ex_ptr, std::span<uint8_t>)>&& cb),
                 (override));
+    MOCK_METHOD(void, adminGetFeatures,
+                (nvme_mi_ctrl_t ctrl, const GetFeaturesRequest& req,
+                 std::function<void(nvme_ex_ptr, uint32_t response,
+                                    std::span<uint8_t>)>&& cb),
+                (override));
+    MOCK_METHOD(void, adminSetFeatures,
+                (nvme_mi_ctrl_t ctrl, const SetFeaturesRequest& req,
+                 std::function<void(nvme_ex_ptr, uint32_t response)>&& cb),
+                (override));
     MOCK_METHOD(
         void, adminGetLogPage,
         (nvme_mi_ctrl_t ctrl, nvme_cmd_get_log_lid lid, uint32_t nsid,