nvmed: Implement separate SSD Telemetry Data Areas

Enhance nvmed to be able to support configurable SSD data areas
for host initiated telemetry while maintaining backwards compatibility
for standard aggregated host initiated telemetry queries.

Tested: https://paste.googleplex.com/5986425462980608
Google-Bug-Id: 516921479
Change-Id: Ie3479c186702d451882b92aa4c8e6c8f4d7cdc4c
Signed-off-by: Agrim Bharat <agrimbharat@google.com>
diff --git a/src/NVMeCacheImpl.hpp b/src/NVMeCacheImpl.hpp
index 78c47ff..956affe 100644
--- a/src/NVMeCacheImpl.hpp
+++ b/src/NVMeCacheImpl.hpp
@@ -11,7 +11,7 @@
  * Metric implementation for LogPage
  */
 
-template <nvme_cmd_get_log_lid LID, class ClockType>
+template <nvme_cmd_get_log_lid LID, class ClockType, uint8_t DA>
 class ControllerLogPageMetric : public MetricBase<ClockType>
 {
   public:
@@ -43,7 +43,18 @@
         }
         else if constexpr (LID == NVME_LOG_LID_TELEMETRY_HOST)
         {
-            return "HostInitiatedMetric";
+            if constexpr (DA >= 1 && DA <= 4)
+            {
+                constexpr std::array<std::string_view, 5> daStrings = {
+                    "", // DA=0 is not used in this range
+                    "DriveHostInitiated_DA1", "DriveHostInitiated_DA2",
+                    "DriveHostInitiated_DA3", "DriveHostInitiated_DA4"};
+                return daStrings[DA];
+            }
+            else
+            {
+                return "HostInitiatedMetric";
+            }
         }
         else if constexpr (LID == NVME_LOG_LID_CMD_AND_FEAT_LOCKDOWN)
         {
@@ -101,7 +112,7 @@
              path{cntrlPtr->path}, id{getIdentifier()}, cb{std::move(cb)}](
                 const std::error_code& ec, std::span<uint8_t> data) {
             auto self = std::dynamic_pointer_cast<
-                ControllerLogPageMetric<LID, ClockType>>(weakSelf.lock());
+                ControllerLogPageMetric<LID, ClockType, DA>>(weakSelf.lock());
             if (!self)
             {
                 lg2::info("metric released before callback", "PATH", path, "ID",
@@ -110,10 +121,11 @@
                 cb({}, 4096, true);
                 return;
             }
+
             if (ec)
             {
                 if (self->errorCount <
-                    ControllerLogPageMetric<LID, ClockType>::errorCountMax)
+                    ControllerLogPageMetric<LID, ClockType, DA>::errorCountMax)
                 {
                     lg2::warning("fail to fresh log page at retry({RETRY})",
                                  "PATH", path, "ID", id, "RETRY",
@@ -143,7 +155,8 @@
             cacheData.insert(cacheData.begin(), data.begin(), data.end());
 
             cb({}, data.size(), true);
-        });
+        },
+            DA);
     }
     /* member variables */
     std::weak_ptr<NVMeControllerEnabled> controller;
diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
index f3aeeef..7917aa6 100644
--- a/src/NVMeController.cpp
+++ b/src/NVMeController.cpp
@@ -34,6 +34,8 @@
 constexpr int kDiscoverLogsMaxRetries =
     6; // exponentially backoff ~6.3s (100ms to 3.2s)
 constexpr int kDiscoverLogsBaseBackoffMs = 100;
+constexpr uint64_t kDefaultDA1IntervalSeconds = 60;
+constexpr uint64_t kDefaultDAIntervalSeconds = 86400;
 
 inline std::optional<std::string> findPluginName(const SensorData& config)
 {
@@ -52,6 +54,58 @@
     return std::get<std::string>(findPlugin->second);
 }
 
+inline std::vector<std::string> findHostInitiatedLog(const SensorData& config)
+{
+    auto sensorBase = config.find(configInterfaceName(nvme::sensorType));
+    if (sensorBase == config.end())
+    {
+        return {};
+    }
+    const SensorBaseConfigMap& sensorConfig = sensorBase->second;
+    auto findHostInitLog = sensorConfig.find("HostInitiatedLog");
+    if (findHostInitLog == sensorConfig.end())
+    {
+        return {};
+    }
+    if (std::holds_alternative<std::vector<std::string>>(
+            findHostInitLog->second))
+    {
+        return std::get<std::vector<std::string>>(findHostInitLog->second);
+    }
+    return {};
+}
+
+inline std::optional<uint64_t> findDAPollingRate(const SensorData& config,
+                                                 const std::string& key)
+{
+    auto sensorBase = config.find(configInterfaceName(nvme::sensorType));
+    if (sensorBase == config.end())
+    {
+        return {};
+    }
+    const SensorBaseConfigMap& sensorConfig = sensorBase->second;
+    auto findRate = sensorConfig.find(key);
+    if (findRate == sensorConfig.end())
+    {
+        return {};
+    }
+
+    if (std::holds_alternative<uint64_t>(findRate->second))
+    {
+        return std::get<uint64_t>(findRate->second);
+    }
+    if (std::holds_alternative<double>(findRate->second))
+    {
+        return static_cast<uint64_t>(std::get<double>(findRate->second));
+    }
+    if (std::holds_alternative<uint32_t>(findRate->second))
+    {
+        return std::get<uint32_t>(findRate->second);
+    }
+
+    return {};
+}
+
 std::shared_ptr<NVMeControllerEnabled>
     NVMeControllerEnabled::create(NVMeController&& nvmeController)
 {
@@ -275,6 +329,18 @@
     using HostTelemetryMetric =
         ControllerLogPageMetric<NVME_LOG_LID_TELEMETRY_HOST,
                                 SchedulerClockType>;
+    using HostTelemetryMetricDA1 =
+        ControllerLogPageMetric<NVME_LOG_LID_TELEMETRY_HOST, SchedulerClockType,
+                                1>;
+    using HostTelemetryMetricDA2 =
+        ControllerLogPageMetric<NVME_LOG_LID_TELEMETRY_HOST, SchedulerClockType,
+                                2>;
+    using HostTelemetryMetricDA3 =
+        ControllerLogPageMetric<NVME_LOG_LID_TELEMETRY_HOST, SchedulerClockType,
+                                3>;
+    using HostTelemetryMetricDA4 =
+        ControllerLogPageMetric<NVME_LOG_LID_TELEMETRY_HOST, SchedulerClockType,
+                                4>;
     using LockdownMetric =
         ControllerLogPageMetric<NVME_LOG_LID_CMD_AND_FEAT_LOCKDOWN,
                                 SchedulerClockType>;
@@ -302,7 +368,10 @@
         this->weak_from_this(), scheduler, defaultLogpageRefreshDuration));
 
     auto pluginName = findPluginName(config);
-    if (!pluginName || pluginName != "OCP")
+    bool isOcp = pluginName == "OCP";
+    auto hostInitLog = findHostInitiatedLog(config);
+
+    if (!isOcp)
     {
         metrics.push_back(std::make_shared<HostTelemetryMetric>(
             this->weak_from_this(), scheduler,
@@ -310,6 +379,55 @@
                 std::chrono::seconds(900))));
     }
 
+    for (const auto& dataArea : hostInitLog)
+    {
+        std::string configKey = "HostInitiatedLog" + dataArea + "Interval";
+        auto rateOpt = findDAPollingRate(config, configKey);
+        uint64_t durationSec = rateOpt.value_or(0);
+        if (durationSec == 0)
+        {
+            durationSec = (dataArea == "DA1") ? kDefaultDA1IntervalSeconds
+                                              : kDefaultDAIntervalSeconds;
+        }
+
+        if (dataArea == "DA1")
+        {
+            lg2::debug("{PATH}: applied polling interval {DURATION}s for DA1",
+                       "PATH", path, "DURATION", durationSec);
+            metrics.push_back(std::make_shared<HostTelemetryMetricDA1>(
+                this->weak_from_this(), scheduler,
+                std::chrono::duration_cast<SchedulerClockType::duration>(
+                    std::chrono::seconds(durationSec))));
+        }
+        else if (dataArea == "DA2")
+        {
+            lg2::debug("{PATH}: applied polling interval {DURATION}s for DA2",
+                       "PATH", path, "DURATION", durationSec);
+            metrics.push_back(std::make_shared<HostTelemetryMetricDA2>(
+                this->weak_from_this(), scheduler,
+                std::chrono::duration_cast<SchedulerClockType::duration>(
+                    std::chrono::seconds(durationSec))));
+        }
+        else if (dataArea == "DA3")
+        {
+            lg2::debug("{PATH}: applied polling interval {DURATION}s for DA3",
+                       "PATH", path, "DURATION", durationSec);
+            metrics.push_back(std::make_shared<HostTelemetryMetricDA3>(
+                this->weak_from_this(), scheduler,
+                std::chrono::duration_cast<SchedulerClockType::duration>(
+                    std::chrono::seconds(durationSec))));
+        }
+        else if (dataArea == "DA4")
+        {
+            lg2::debug("{PATH}: applied polling interval {DURATION}s for DA4",
+                       "PATH", path, "DURATION", durationSec);
+            metrics.push_back(std::make_shared<HostTelemetryMetricDA4>(
+                this->weak_from_this(), scheduler,
+                std::chrono::duration_cast<SchedulerClockType::duration>(
+                    std::chrono::seconds(durationSec))));
+        }
+    }
+
     // Command Lockdown (Log Page 14h) is dynamically registered only on
     // NVMe 2.0+ controllers that explicitly report support via Supported Log
     // Pages (00h) check.
diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
index 9867411..fbb3adf 100644
--- a/src/NVMeController.hpp
+++ b/src/NVMeController.hpp
@@ -153,7 +153,8 @@
 /**
  * Forward declaration for metrics
  */
-template <nvme_cmd_get_log_lid LID, class ClockType = std::chrono::steady_clock>
+template <nvme_cmd_get_log_lid LID, class ClockType = std::chrono::steady_clock,
+          uint8_t DA = 0>
 class ControllerLogPageMetric;
 template <nvme_identify_cns CNS, class ClockType>
 class IdentifyMetric;
@@ -189,7 +190,7 @@
     void stop() override;
 
   private:
-    template <nvme_cmd_get_log_lid LID, class ClockType>
+    template <nvme_cmd_get_log_lid LID, class ClockType, uint8_t DA>
     friend class ControllerLogPageMetric;
     template <nvme_identify_cns CNS, class ClockType>
     friend class IdentifyMetric;
diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
index 772ab4c..e3b6288 100644
--- a/src/NVMeIntf.hpp
+++ b/src/NVMeIntf.hpp
@@ -11,6 +11,15 @@
 #include <span>
 #include <variant>
 
+enum class TelemetryDataArea : uint8_t
+{
+    Aggregated = 0,
+    DA1 = 1,
+    DA2 = 2,
+    DA3 = 3,
+    DA4 = 4
+};
+
 class NVMeBasicIntf;
 class NVMeMiIntf;
 struct NVMeNSIdentify;
@@ -211,8 +220,16 @@
     virtual 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&, std::span<uint8_t>)>&&
-            cb) = 0;
+        std::function<void(const std::error_code&, std::span<uint8_t>)>&& cb,
+        uint8_t dataArea) = 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&, std::span<uint8_t>)>&& cb)
+    {
+        adminGetLogPage(ctrl, lid, nsid, lsp, lsi, std::move(cb), 0);
+    }
     virtual void adminFwCommit(nvme_mi_ctrl_t ctrl, nvme_fw_commit_ca action,
                                uint8_t slot, bool bpid,
                                std::function<void(const std::error_code&,
diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
index 41d8c86..5c2f83e 100644
--- a/src/NVMeMi.cpp
+++ b/src/NVMeMi.cpp
@@ -991,7 +991,8 @@
 
 // Get Temetery Log header and return the size for hdr + data area (Area 1, 2,
 // 3, or maybe 4)
-int getTelemetryLogSize(nvme_mi_ctrl_t ctrl, bool host, uint32_t& size)
+int getTelemetryLogSize(nvme_mi_ctrl_t ctrl, bool host, uint32_t& size,
+                        uint8_t dataArea = 0)
 {
     int rc = 0;
     nvme_telemetry_log log = {};
@@ -1018,13 +1019,38 @@
         return rc;
     }
 
-    // Restrict the telemetry log to Data Area 1. DA1 and DA2 contain the same
-    // essential telemetry data, as DA2 is a superset of DA1 and no additional
-    // unique information is populated in DA2. Limiting to DA1 is sufficient and
-    // reduces OOB data transfer.
-    size = static_cast<uint32_t>(
-               (boost::endian::little_to_native(log.dalb1) + 1)) *
-           NVME_LOG_TELEM_BLOCK_SIZE;
+    // Dynamically calculate the telemetry log size based on the requested Data
+    // Area. The NVMe specification defines boundaries (dalb1, dalb2, etc.) in
+    // the Block 0 header. We cast to our enum and calculate the block boundary
+    // to prevent over-fetching. If a drive's firmware does not support a
+    // requested area (dalbX == 0), the calculation evaluates to 1 block
+    // (512 bytes), which safely returns just the telemetry header.
+    auto area = static_cast<TelemetryDataArea>(dataArea);
+    if (area == TelemetryDataArea::DA2)
+    {
+        size = static_cast<uint32_t>(
+                   (boost::endian::little_to_native(log.dalb2) + 1)) *
+               NVME_LOG_TELEM_BLOCK_SIZE;
+    }
+    else if (area == TelemetryDataArea::DA3)
+    {
+        size = static_cast<uint32_t>(
+                   (boost::endian::little_to_native(log.dalb3) + 1)) *
+               NVME_LOG_TELEM_BLOCK_SIZE;
+    }
+    else if (area == TelemetryDataArea::DA4)
+    {
+        size = static_cast<uint32_t>(
+                   (boost::endian::little_to_native(log.dalb4) + 1)) *
+               NVME_LOG_TELEM_BLOCK_SIZE;
+    }
+    else
+    {
+        // Default to DA1 or Aggregated
+        size = static_cast<uint32_t>(
+                   (boost::endian::little_to_native(log.dalb1) + 1)) *
+               NVME_LOG_TELEM_BLOCK_SIZE;
+    }
     return rc;
 }
 
@@ -1214,7 +1240,8 @@
 void NVMeMi::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&, std::span<uint8_t>)>&& cb)
+    std::function<void(const std::error_code&, std::span<uint8_t>)>&& cb,
+    uint8_t dataArea)
 {
     if (auto degraded = isEndpointDegraded())
     {
@@ -1228,8 +1255,8 @@
 
     try
     {
-        post([ctrl, nsid, lid, lsp, self{shared_from_this()}, ep{endpoint},
-              cb{std::move(cb)}]() {
+        post([ctrl, nsid, lid, lsp, dataArea, self{shared_from_this()},
+              ep{endpoint}, cb{std::move(cb)}]() {
             std::vector<uint8_t> data;
             std::function<void(void)> logHandler;
             int rc = 0;
@@ -1391,7 +1418,7 @@
                     bool host = lid == NVME_LOG_LID_TELEMETRY_HOST;
 
                     uint32_t size = 0;
-                    rc = getTelemetryLogSize(ctrl, host, size);
+                    rc = getTelemetryLogSize(ctrl, host, size, dataArea);
                     if (rc == 0)
                     {
                         data.resize(size);
diff --git a/src/NVMeMi.hpp b/src/NVMeMi.hpp
index 4efe698..04dab5b 100644
--- a/src/NVMeMi.hpp
+++ b/src/NVMeMi.hpp
@@ -84,10 +84,11 @@
     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&,
-                                            std::span<uint8_t>)>&& 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&, std::span<uint8_t>)>&& cb,
+        uint8_t dataArea) override;
 
     void adminFwCommit(
         nvme_mi_ctrl_t ctrl, nvme_fw_commit_ca action, uint8_t slot, bool bpid,
diff --git a/src/NVMeMiFake.hpp b/src/NVMeMiFake.hpp
index 2723cc4..d91cabb 100644
--- a/src/NVMeMiFake.hpp
+++ b/src/NVMeMiFake.hpp
@@ -189,7 +189,6 @@
             });
         });
     }
-
     void adminGetFeatures([[maybe_unused]] nvme_mi_ctrl_t ctrl,
                           [[maybe_unused]] const GetFeaturesRequest& req,
                           std::function<void(nvme_ex_ptr, uint32_t,
@@ -210,10 +209,11 @@
         });
     }
 
-    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&,
-                                            std::span<uint8_t>)>&& 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&, std::span<uint8_t>)>&& cb,
+        uint8_t /*dataArea*/) override
     {
         try
         {
@@ -248,6 +248,7 @@
                                 log.dalb1 = 512;
                                 log.dalb2 = 512;
                                 log.dalb3 = 512;
+                                log.dalb4 = 512;
                                 data.resize(sizeof(nvme_telemetry_log) + 512);
                                 std::string str = "hello world";
                                 for (std::size_t i = 0; i < str.size(); i++)
diff --git a/tests/test_nvme_feature_store.cpp b/tests/test_nvme_feature_store.cpp
index 3e1bd5c..439a100 100644
--- a/tests/test_nvme_feature_store.cpp
+++ b/tests/test_nvme_feature_store.cpp
@@ -48,7 +48,8 @@
         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&, std::span<uint8_t>)>&& cb),
+         std::function<void(const std::error_code&, std::span<uint8_t>)>&& cb,
+         uint8_t dataArea),
         (override));
     MOCK_METHOD(
         void, adminFwCommit,
diff --git a/tests/test_nvme_mi.cpp b/tests/test_nvme_mi.cpp
index 363116d..2fd8572 100644
--- a/tests/test_nvme_mi.cpp
+++ b/tests/test_nvme_mi.cpp
@@ -50,13 +50,13 @@
             return fake->adminIdentify(ctrl, cns, nsid, cntid, std::move(cb));
         });
         ON_CALL(*this, adminGetLogPage)
-            .WillByDefault(
-                [this](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&,
-                                          std::span<uint8_t>)>&& cb) {
+            .WillByDefault([this](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&,
+                                                     std::span<uint8_t>)>&& cb,
+                                  uint8_t dataArea) {
             return fake->adminGetLogPage(ctrl, lid, nsid, lsp, lsi,
-                                         std::move(cb));
+                                         std::move(cb), dataArea);
         });
         ON_CALL(*this, adminFwCommit)
             .WillByDefault([this](nvme_mi_ctrl_t ctrl, nvme_fw_commit_ca action,
@@ -121,7 +121,8 @@
         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&, std::span<uint8_t>)>&& cb),
+         std::function<void(const std::error_code&, std::span<uint8_t>)>&& cb,
+         uint8_t dataArea),
         (override));
     MOCK_METHOD(
         void, adminFwCommit,