FeatureStore: Integrate into NVMe objects

Instantiates and attaches FeatureStore to Controller, Subsystem, and Volume
objects.

Design-Doc: go/bmc-redfish-nvme-get-set-features
Google-Bug-Id: 405609473
Change-Id: I88312cdc5061f3e3b8c3a022773921f9f5deef69
Signed-off-by: Guangzong Chen <guangzong@google.com>
diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
index 5f2496e..f3aeeef 100644
--- a/src/NVMeController.cpp
+++ b/src/NVMeController.cpp
@@ -410,6 +410,25 @@
     });
 }
 
+void NVMeControllerEnabled::initializeFeatureStore()
+{
+    if (featureStore)
+    {
+        return;
+    }
+
+    this->featureStore =
+        std::make_shared<FeatureStore>(io, objServer, conn, path, nvmeIntf,
+                                       nvmeCtrl, 0, FeatureScope::Controller);
+
+    this->featureStore->init([](const std::error_code& ec) {
+        if (ec)
+        {
+            lg2::error("Failed to initialize feature store: {ERROR}", "ERROR",
+                       ec.message());
+        }
+    });
+}
 void NVMeControllerEnabled::start(
     const std::shared_ptr<NVMeControllerPlugin>& nvmePlugin)
 {
@@ -420,6 +439,7 @@
     if (isPrimary)
     {
         this->initializeMetricStore();
+        this->initializeFeatureStore();
     }
 }
 
@@ -436,6 +456,7 @@
     {
         discoverLogsRetryTimer->cancel();
     }
+    this->featureStore.reset();
 }
 
 void NVMeController::createAssociation()
diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
index 1e3ead4..9867411 100644
--- a/src/NVMeController.hpp
+++ b/src/NVMeController.hpp
@@ -1,6 +1,7 @@
 
 #pragma once
 
+#include "NVMeFeatureStore.hpp"
 #include "NVMeIntf.hpp"
 #include "NVMeMetricStore.hpp"
 #include "NVMePlugin.hpp"
@@ -90,6 +91,10 @@
 
     void setNvmeVersion(uint32_t version);
 
+    std::shared_ptr<NVMeMiIntf> getNvmeIntf() const
+    {
+        return nvmeIntf;
+    }
     /**
      * @brief Update association interface.
      *
@@ -113,6 +118,7 @@
     std::shared_ptr<sdbusplus::asio::dbus_interface> securityInterface;
     std::shared_ptr<sdbusplus::asio::dbus_interface> passthruInterface;
     std::shared_ptr<sdbusplus::asio::dbus_interface> lockdownInterface;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> featureStoreIntf;
 
     std::shared_ptr<NVMeMiIntf> nvmeIntf;
     nvme_mi_ctrl_t nvmeCtrl;
@@ -202,11 +208,16 @@
     Status status = Status::Disabled;
 
     std::optional<MetricStore> metricStore;
+    std::shared_ptr<FeatureStore> featureStore;
 
     /** Create Metric store and load all the metric implementations
      */
     void initializeMetricStore();
 
+    /** Create Feature store and load all the feature implementations
+     */
+    void initializeFeatureStore();
+
     explicit NVMeControllerEnabled(NVMeController&& nvmeController);
 
     void init();
diff --git a/src/NVMeFeatureStore.hpp b/src/NVMeFeatureStore.hpp
index 49832c7..37f59a7 100644
--- a/src/NVMeFeatureStore.hpp
+++ b/src/NVMeFeatureStore.hpp
@@ -28,7 +28,13 @@
                  std::shared_ptr<NVMeMiIntf> nvmeIntf, nvme_mi_ctrl_t ctrl,
                  uint32_t nsid, FeatureScope scope);
 
-    virtual ~FeatureStore() = default;
+    virtual ~FeatureStore()
+    {
+        if (dbusIntf)
+        {
+            objServer.remove_interface(dbusIntf);
+        }
+    }
 
     // Initialize the feature collection by querying the device
     void init(std::function<void(const std::error_code&)>&& cb);
diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
index 9c5b6cb..41715d7 100644
--- a/src/NVMeSubsys.cpp
+++ b/src/NVMeSubsys.cpp
@@ -162,12 +162,47 @@
     drive = std::make_shared<NVMeDrive>(io, conn, path, weak_from_this());
     drive->protocol(NVMeDrive::DriveProtocol::NVMe);
     drive->type(NVMeDrive::DriveType::SSD);
-    // TODO: update capacity
-
-    // make association for Drive/Storage/Chassis
     createAssociation();
 }
 
+void NVMeSubsystem::initializeFeatureStore()
+{
+    if (featureStore)
+    {
+        return;
+    }
+
+    std::shared_ptr<NVMeControllerEnabled> ctrl;
+    try
+    {
+        ctrl = getPrimaryController();
+    }
+    catch (const std::exception& e)
+    {
+        // Suppress error if no controller is available yet (e.g. during initial
+        // scan)
+        return;
+    }
+
+    if (!ctrl)
+    {
+        return;
+    }
+
+    this->featureStore = std::make_shared<FeatureStore>(
+        io, objServer, conn, path, ctrl->getNvmeIntf(), ctrl->getMiCtrl(), 0,
+        FeatureScope::Subsystem);
+
+    this->featureStore->init([](const std::error_code& ec) {
+        if (ec)
+        {
+            lg2::error(
+                "Failed to initialize feature store for subsystem: {ERROR}",
+                "ERROR", ec.message());
+        }
+    });
+}
+
 NVMeSubsystem::~NVMeSubsystem()
 {
     objServer.remove_interface(assocIntf);
@@ -224,6 +259,7 @@
         secCntrls.push_back(secondaryController);
     }
     primaryController->setPrimary(secCntrls);
+    initializeFeatureStore();
 
     (void)boost::asio::spawn(io, [self{shared_from_this()}](
                                      const boost::asio::yield_context& yield) {
@@ -852,6 +888,11 @@
     {
         plugin.reset();
     }
+
+    if (featureStore)
+    {
+        featureStore.reset();
+    }
 }
 
 sdbusplus::message::object_path
diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
index 877a587..7ca9c47 100644
--- a/src/NVMeSubsys.hpp
+++ b/src/NVMeSubsys.hpp
@@ -2,6 +2,7 @@
 #include "NVMeBasic.hpp"
 #include "NVMeController.hpp"
 #include "NVMeDrive.hpp"
+#include "NVMeFeatureStore.hpp"
 #include "NVMePlugin.hpp"
 #include "NVMeProgress.hpp"
 #include "NVMeSensor.hpp"
@@ -96,6 +97,9 @@
     friend class NVMeTest;
 #endif
 
+    // may throw NVMeError if no controller is available
+    std::shared_ptr<NVMeControllerEnabled> getPrimaryController() const;
+
   private:
     friend class NVMePlugin;
 
@@ -184,6 +188,10 @@
 
     std::shared_ptr<sdbusplus::asio::dbus_interface> assocIntf;
 
+    std::shared_ptr<FeatureStore> featureStore;
+
+    void initializeFeatureStore();
+
     void createAssociation();
     void updateAssociation();
     std::vector<Association> makeAssociation() const;
@@ -195,9 +203,6 @@
     // mark the availability of the Storage device.
     void markAvailable(bool toggle);
 
-    // may throw NVMeError if no controller is available
-    std::shared_ptr<NVMeControllerEnabled> getPrimaryController() const;
-
     sdbusplus::message::object_path
         createVolume(boost::asio::yield_context yield, uint64_t size,
                      size_t lbaFormat, bool metadataAtEnd) override;
diff --git a/src/NVMeVolume.cpp b/src/NVMeVolume.cpp
index 82b2f20..4920fa2 100644
--- a/src/NVMeVolume.cpp
+++ b/src/NVMeVolume.cpp
@@ -75,6 +75,7 @@
     VolumeBase::emit_added();
     NvmeVolumeBase::emit_added();
     initializeMetricStore();
+    initializeFeatureStore();
 }
 
 std::shared_ptr<NVMeVolume>
@@ -128,3 +129,47 @@
 {
     throw std::runtime_error("Method Not Supported");
 }
+
+void NVMeVolume::initializeFeatureStore()
+{
+    if (featureStore)
+    {
+        return;
+    }
+
+    auto subsysPtr = subsys.lock();
+    if (!subsysPtr)
+    {
+        return;
+    }
+
+    std::shared_ptr<NVMeControllerEnabled> ctrl;
+    try
+    {
+        ctrl = subsysPtr->getPrimaryController();
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error(
+            "Failed to get primary controller for feature store: {ERROR}",
+            "ERROR", e.what());
+        return;
+    }
+
+    if (!ctrl)
+    {
+        return;
+    }
+
+    this->featureStore = std::make_shared<FeatureStore>(
+        conn->get_io_context(), objServer, conn, path, ctrl->getNvmeIntf(),
+        ctrl->getMiCtrl(), namespaceId(), FeatureScope::Namespace);
+
+    this->featureStore->init([](const std::error_code& ec) {
+        if (ec)
+        {
+            lg2::error("Failed to initialize feature store for volume: {ERROR}",
+                       "ERROR", ec.message());
+        }
+    });
+}
diff --git a/src/NVMeVolume.hpp b/src/NVMeVolume.hpp
index 8922add..fbdafca 100644
--- a/src/NVMeVolume.hpp
+++ b/src/NVMeVolume.hpp
@@ -69,7 +69,13 @@
 
     std::optional<MetricStore> metricStore;
 
+    std::shared_ptr<FeatureStore> featureStore;
+
     /** Create Metric store and load all the metric implementations
      */
     void initializeMetricStore();
+
+    /** Create Feature store and load all the feature implementations
+     */
+    void initializeFeatureStore();
 };
diff --git a/tests/meson.build b/tests/meson.build
index 266db3d..8e89d85 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -81,6 +81,7 @@
             '../src/NVMeSensor.cpp',
             '../src/NVMeSubsys.cpp',
             '../src/NVMeController.cpp',
+            '../src/NVMeFeatureStore.cpp',
             '../src/NVMeError.cpp',
             '../src/NVMeProgress.cpp',
             '../src/NVMeStorage.cpp',
diff --git a/tests/test_nvme_mi.cpp b/tests/test_nvme_mi.cpp
index 240b4cf..6025d4b 100644
--- a/tests/test_nvme_mi.cpp
+++ b/tests/test_nvme_mi.cpp
@@ -223,6 +223,14 @@
         systemBus->request_name("xyz.openbmc_project.NVMeTest");
         subsys->unavailableMaxCount = 1;
         subsys->pollingInterval = subsysPollTime;
+
+        ON_CALL(mock, adminGetFeatures)
+            .WillByDefault([](nvme_mi_ctrl_t,
+                              const NVMeMiIntf::GetFeaturesRequest&,
+                              std::function<void(nvme_ex_ptr, uint32_t,
+                                                 std::span<uint8_t>)>&& cb) {
+            cb(nullptr, 0, {});
+        });
     }
 
     static void SetUpTestSuite()