NVMed: Add NVMe-MI patches for dbus-sensors
Tested: Manually tested the NVMed functionality
Google-Bug-Id: 283027276
Change-Id: I6d11bddd194655ce8a7c7e925e27c8dbbc971712
Signed-off-by: Muhammad Usama <muhammadusama@google.com>
(cherry picked from commit 8e03b2bb94a409424a6f3bb0267bfea36ea50670)
diff --git a/recipes-phosphor/sensors/dbus-sensors/0001-nvme-sensor-refactor-the-code.patch b/recipes-phosphor/sensors/dbus-sensors/0001-nvme-sensor-refactor-the-code.patch
new file mode 100644
index 0000000..c31605e
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0001-nvme-sensor-refactor-the-code.patch
@@ -0,0 +1,1407 @@
+From 2d40f5d5454621f313e07ee25243c4ba961bd831 Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Mon, 29 Aug 2022 17:21:06 +0000
+Subject: [PATCH 01/34] nvme sensor: refactor the code
+
+Refactor the code in order to adapt to the hierachy of the NVMe
+subsystem exposed by NVMe-MI spec. The new design is documented at:
+https://github.com/openbmc/docs/blob/628472ab74a0803ed7d726e6a95b00e0767790b9/designs/nvmed.md
+
+The refactor follows the design pattern of MVC (Model-View-Controller).
+
+NVMe Interface work as the "Model", who provides the async access to the
+data from the NVMe device controller via the OOB protocol of either NVMe
+MI Basic or NVMe MI.
+
+The DBus interface class (such as NVMeSensor), serves as "View", who
+creates the interaction to the DBus.
+
+The controllers (e.g. NVMeSubsys), glues the above two components
+together. It schedules the data retrieve from Model and update the
+corresponding View in the heirachy.
+
+Undefine `BOOST_ASIO_NO_DEPRECATED` temporarily until all nvme mi
+patches are merged. An all-in-one fix will be applied.
+
+Tested:
+
+Nuvoton with Samsung PM1733V5TLC
+
+E-M config:
+```
+{
+ "Exposes": [
+ {
+ "Address": "$address",
+ "Bus": "$bus",
+ "Name": "FRU",
+ "Type": "EEPROM_24C02"
+ },
+ {
+ "Type": "NVME1000",
+ "Name": "ExampleNVMe",
+ "Bus": "$bus",
+ "Address": "0x6a",
+ "Thresholds": [
+ {
+ "Direction": "greater than",
+ "Name": "upper critical",
+ "Severity": 1,
+ "Value": 115
+ },
+ {
+ "Direction": "greater than",
+ "Name": "upper non critical",
+ "Severity": 0,
+ "Value": 110
+ },
+ {
+ "Direction": "less than",
+ "Name": "lower non critical",
+ "Severity": 0,
+ "Value": 5
+ },
+ {
+ "Direction": "less than",
+ "Name": "lower critical",
+ "Severity": 1,
+ "Value": 0
+ }
+ ]
+ }
+ ],
+ "Name": "NVMe_${index}",
+ "Probe": "xyz.openbmc_project.FruDevice({'PRODUCT_PRODUCT_NAME': 'PM1733V5TLC'})",
+ "Type": "Board",
+ "xyz.openbmc_project.Inventory.Decorator.Asset": {
+ "Manufacturer": "$PRODUCT_MANUFACTURER",
+ "Model": "$PRODUCT_PRODUCT_NAME",
+ "PartNumber": "$PRODUCT_PART_NUMBER",
+ "SerialNumber": "$PRODUCT_SERIAL_NUMBER"
+ }
+}
+```
+
+Sensor:
+
+```
+busctl introspect xyz.openbmc_project.NVMe /xyz/openbmc_project/sensors/temperature/ExampleNVMe
+NAME TYPE SIGNATURE RESULT/VALUE FLAGS
+org.freedesktop.DBus.Introspectable interface - - -
+.Introspect method - s -
+org.freedesktop.DBus.Peer interface - - -
+.GetMachineId method - s -
+.Ping method - - -
+org.freedesktop.DBus.Properties interface - - -
+.Get method ss v -
+.GetAll method s a{sv} -
+.Set method ssv - -
+.PropertiesChanged signal sa{sv}as - -
+xyz.openbmc_project.Association.Definitions interface - - -
+.Associations property a(sss) 1 "chassis" "all_sensors" "/xyz/openb... emits-change
+xyz.openbmc_project.Sensor.Threshold.Critical interface - - -
+.CriticalAlarmHigh property b false emits-change
+.CriticalAlarmLow property b false emits-change
+.CriticalHigh property d 115 emits-change writable
+.CriticalLow property d 0 emits-change writable
+xyz.openbmc_project.Sensor.Threshold.Warning interface - - -
+.WarningAlarmHigh property b false emits-change
+.WarningAlarmLow property b false emits-change
+.WarningHigh property d 110 emits-change writable
+.WarningLow property d 5 emits-change writable
+xyz.openbmc_project.Sensor.Value interface - - -
+.MaxValue property d 127 emits-change
+.MinValue property d 0 emits-change
+.Unit property s "xyz.openbmc_project.Sensor.Value.Uni... emits-change
+.Value property d 31 emits-change writable
+xyz.openbmc_project.State.Decorator.Availability interface - - -
+.Available property b true emits-change writable
+xyz.openbmc_project.State.Decorator.OperationalStatus interface - - -
+.Functional property b true emits-change
+```
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: Ib99f3c1ab6d65533cb80804abd119f0d4a9f0f8a
+---
+ src/{NVMeBasicContext.cpp => NVMeBasic.cpp} | 193 ++++++++---------
+ src/NVMeBasic.hpp | 50 +++++
+ src/NVMeBasicContext.hpp | 48 -----
+ src/NVMeContext.hpp | 109 ----------
+ src/NVMeIntf.hpp | 43 ++++
+ src/NVMeSensor.cpp | 9 +-
+ src/NVMeSensor.hpp | 4 +-
+ src/NVMeSensorMain.cpp | 219 +++++++++-----------
+ src/NVMeSubsys.cpp | 215 +++++++++++++++++++
+ src/NVMeSubsys.hpp | 70 +++++++
+ src/meson.build | 11 +-
+ 11 files changed, 569 insertions(+), 402 deletions(-)
+ rename src/{NVMeBasicContext.cpp => NVMeBasic.cpp} (71%)
+ create mode 100644 src/NVMeBasic.hpp
+ delete mode 100644 src/NVMeBasicContext.hpp
+ delete mode 100644 src/NVMeContext.hpp
+ create mode 100644 src/NVMeIntf.hpp
+ create mode 100644 src/NVMeSubsys.cpp
+ create mode 100644 src/NVMeSubsys.hpp
+
+diff --git a/src/NVMeBasicContext.cpp b/src/NVMeBasic.cpp
+similarity index 71%
+rename from src/NVMeBasicContext.cpp
+rename to src/NVMeBasic.cpp
+index 0bc2252..b2cb44e 100644
+--- a/src/NVMeBasicContext.cpp
++++ b/src/NVMeBasic.cpp
+@@ -1,20 +1,15 @@
+-#include "NVMeBasicContext.hpp"
++#include "NVMeBasic.hpp"
+
+ #include <endian.h>
+-#include <sys/ioctl.h>
+-#include <unistd.h>
+
+-#include <FileHandle.hpp>
+ #include <boost/asio/read.hpp>
+ #include <boost/asio/streambuf.hpp>
+ #include <boost/asio/write.hpp>
+
+-#include <cassert>
+-#include <cerrno>
+-#include <cinttypes>
+-#include <cstdio>
+-#include <cstring>
+-#include <system_error>
++#include <iostream>
++#include <map>
++#include <memory>
++#include <optional>
+
+ extern "C"
+ {
+@@ -22,6 +17,9 @@ extern "C"
+ #include <linux/i2c-dev.h>
+ }
+
++// a map from root bus number to the NVMeBasicIO
++static std::map<int, std::shared_ptr<NVMeBasicIO>> basicIOMap;
++
+ /*
+ * NVMe-MI Basic Management Command
+ *
+@@ -183,10 +181,11 @@ static ssize_t processBasicQueryStream(FileHandle& in, FileHandle& out)
+ return rc;
+ }
+
+-/* Throws std::error_code on failure */
+-/* FIXME: Probably shouldn't do fallible stuff in a constructor */
+-NVMeBasicContext::NVMeBasicContext(boost::asio::io_context& io, int rootBus) :
+- NVMeContext::NVMeContext(io, rootBus), io(io), reqStream(io), respStream(io)
++NVMeBasicIO::NVMeBasicIO(
++ boost::asio::io_context& io,
++ std::function<ssize_t(FileHandle& in, FileHandle& out)>&& procFunc) :
++ reqStream(io),
++ respStream(io)
+ {
+ std::array<int, 2> responsePipe{};
+ std::array<int, 2> requestPipe{};
+@@ -224,11 +223,12 @@ NVMeBasicContext::NVMeBasicContext(boost::asio::io_context& io, int rootBus) :
+ FileHandle streamOut(responsePipe[1]);
+ respStream.assign(responsePipe[0]);
+
+- thread = std::jthread([streamIn{std::move(streamIn)},
+- streamOut{std::move(streamOut)}]() mutable {
++ thread = std::jthread(
++ [streamIn{std::move(streamIn)}, streamOut{std::move(streamOut)},
++ procFunc(std::move(procFunc))]() mutable {
+ ssize_t rc = 0;
+
+- if ((rc = processBasicQueryStream(streamIn, streamOut)) < 0)
++ if ((rc = procFunc(streamIn, streamOut)) < 0)
+ {
+ std::cerr << "Failure while processing query stream: "
+ << strerror(static_cast<int>(-rc)) << "\n";
+@@ -238,36 +238,63 @@ NVMeBasicContext::NVMeBasicContext(boost::asio::io_context& io, int rootBus) :
+ });
+ }
+
+-void NVMeBasicContext::readAndProcessNVMeSensor()
++static std::filesystem::path deriveRootBusPath(int busNumber)
++{
++ return "/sys/bus/i2c/devices/i2c-" + std::to_string(busNumber) +
++ "/mux_device";
++}
++
++static std::optional<int> deriveRootBus(std::optional<int> busNumber)
+ {
+- if (pollCursor == sensors.end())
++ if (!busNumber)
+ {
+- this->pollNVMeDevices();
+- return;
++ return std::nullopt;
+ }
+
+- std::shared_ptr<NVMeSensor> sensor = *pollCursor++;
++ std::filesystem::path muxPath = deriveRootBusPath(*busNumber);
++
++ if (!std::filesystem::is_symlink(muxPath))
++ {
++ return *busNumber;
++ }
+
+- if (!sensor->readingStateGood())
++ std::string rootName = std::filesystem::read_symlink(muxPath).filename();
++ size_t dash = rootName.find('-');
++ if (dash == std::string::npos)
+ {
+- sensor->markAvailable(false);
+- sensor->updateValue(std::numeric_limits<double>::quiet_NaN());
+- readAndProcessNVMeSensor();
+- return;
++ std::cerr << "Error finding root bus for " << rootName << "\n";
++ return std::nullopt;
+ }
+
+- /* Potentially defer sampling the sensor sensor if it is in error */
+- if (!sensor->sample())
++ return std::stoi(rootName.substr(0, dash));
++}
++
++NVMeBasic::NVMeBasic(boost::asio::io_context& io, int bus, int addr) :
++ io(io), bus(bus), addr(addr)
++{
++ auto root = deriveRootBus(bus);
++
++ if (!root || *root < 0)
+ {
+- readAndProcessNVMeSensor();
+- return;
++ throw std::runtime_error("invalid root bus number");
+ }
+
+- auto command = encodeBasicQuery(sensor->bus, 0x6a, 0x00);
++ if (!basicIOMap[*root])
++ {
++ basicIOMap[*root].reset(new NVMeBasicIO(io, processBasicQueryStream));
++ }
++ basicIO = basicIOMap[*root];
++}
++
++void NVMeBasic::getStatus(
++ std::function<void(const std::error_code&, DriveStatus*)>&& cb)
++{
++ auto command = encodeBasicQuery(bus, addr, 0x00);
+
+ /* Issue the request */
+ boost::asio::async_write(
+- reqStream, boost::asio::buffer(command->data(), command->size()),
++ basicIO->reqStream,
++ boost::asio::buffer(command->data(), command->size()),
+ [command](boost::system::error_code ec, std::size_t) {
+ if (ec)
+ {
+@@ -280,7 +307,7 @@ void NVMeBasicContext::readAndProcessNVMeSensor()
+
+ /* Gather the response and dispatch for parsing */
+ boost::asio::async_read(
+- respStream, *response,
++ basicIO->respStream, *response,
+ [response](const boost::system::error_code& ec, std::size_t n) {
+ if (ec)
+ {
+@@ -317,11 +344,14 @@ void NVMeBasicContext::readAndProcessNVMeSensor()
+ response->prepare(len);
+ return len;
+ },
+- [weakSelf{weak_from_this()}, sensor, response](
++ [weakSelf{weak_from_this()}, response, cb = std::move(cb)](
+ const boost::system::error_code& ec, std::size_t length) mutable {
+ if (ec)
+ {
+ std::cerr << "Got error reading basic query: " << ec << "\n";
++ boost::asio::post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(std::errc::io_error), nullptr);
++ });
+ return;
+ }
+
+@@ -336,81 +366,30 @@ void NVMeBasicContext::readAndProcessNVMeSensor()
+ /* Deserialise the response */
+ response->consume(1); /* Drop the length byte */
+ std::istream is(response.get());
+- std::vector<char> data(response->size());
+- is.read(data.data(), response->size());
+-
+- /* Update the sensor */
+- self->processResponse(sensor, data.data(), data.size());
+-
+- /* Enqueue processing of the next sensor */
+- self->readAndProcessNVMeSensor();
+- }
+- });
+-}
++ auto data = std::make_shared<std::vector<char>>(response->size());
++ is.read(data->data(), response->size());
+
+-void NVMeBasicContext::pollNVMeDevices()
+-{
+- pollCursor = sensors.begin();
+-
+- scanTimer.expires_after(std::chrono::seconds(1));
+- scanTimer.async_wait([weakSelf{weak_from_this()}](
+- const boost::system::error_code errorCode) {
+- if (errorCode == boost::asio::error::operation_aborted)
+- {
+- return;
+- }
++ /* Process the callback */
++ self->io.post([data, cb{std::move(cb)}]() {
++ if (data->size() <
++ sizeof(DriveStatus) + 1) // The first byte is status flags
++ {
++ cb(std::make_error_code(std::errc::message_size), nullptr);
++ return;
++ }
+
+- if (errorCode)
+- {
+- std::cerr << errorCode.message() << "\n";
+- return;
+- }
++ uint8_t flags = static_cast<uint8_t>(data->front());
++ if (((flags & NVME_MI_BASIC_SFLGS_DRIVE_NOT_READY) != 0) ||
++ ((flags & NVME_MI_BASIC_SFLGS_DRIVE_FUNCTIONAL) == 0))
++ {
++ cb(std::make_error_code(std::errc::no_such_device),
++ nullptr);
++ return;
++ }
+
+- if (auto self = weakSelf.lock())
+- {
+- self->readAndProcessNVMeSensor();
++ data->erase(data->begin());
++ cb({}, reinterpret_cast<DriveStatus*>(data->data()));
++ });
+ }
+ });
+ }
+-
+-static double getTemperatureReading(int8_t reading)
+-{
+- if (reading == static_cast<int8_t>(0x80) ||
+- reading == static_cast<int8_t>(0x81))
+- {
+- // 0x80 = No temperature data or temperature data is more the 5 s
+- // old 0x81 = Temperature sensor failure
+- return std::numeric_limits<double>::quiet_NaN();
+- }
+-
+- return reading;
+-}
+-
+-void NVMeBasicContext::processResponse(std::shared_ptr<NVMeSensor>& sensor,
+- void* msg, size_t len)
+-{
+- if (msg == nullptr || len < 6)
+- {
+- sensor->incrementError();
+- return;
+- }
+-
+- uint8_t* messageData = static_cast<uint8_t*>(msg);
+-
+- uint8_t status = messageData[0];
+- if (((status & NVME_MI_BASIC_SFLGS_DRIVE_NOT_READY) != 0) ||
+- ((status & NVME_MI_BASIC_SFLGS_DRIVE_FUNCTIONAL) == 0))
+- {
+- sensor->markFunctional(false);
+- return;
+- }
+-
+- double value = getTemperatureReading(messageData[2]);
+- if (!std::isfinite(value))
+- {
+- sensor->incrementError();
+- return;
+- }
+-
+- sensor->updateValue(value);
+-}
+diff --git a/src/NVMeBasic.hpp b/src/NVMeBasic.hpp
+new file mode 100644
+index 0000000..0a54a55
+--- /dev/null
++++ b/src/NVMeBasic.hpp
+@@ -0,0 +1,50 @@
++#pragma once
++
++#include "FileHandle.hpp"
++#include "NVMeIntf.hpp"
++
++#include <boost/asio.hpp>
++#include <boost/asio/posix/stream_descriptor.hpp>
++
++#include <thread>
++
++class NVMeBasicIO
++{
++ public:
++ NVMeBasicIO(
++ boost::asio::io_context& io,
++ std::function<ssize_t(FileHandle& in, FileHandle& out)>&& procFunc);
++ boost::asio::posix::stream_descriptor reqStream;
++ boost::asio::posix::stream_descriptor respStream;
++
++ private:
++ std::jthread thread;
++};
++
++// NVMe Basic Management Command
++// NVMe MI Spec Appendix A.
++class NVMeBasic :
++ public NVMeBasicIntf,
++ public std::enable_shared_from_this<NVMeBasic>
++{
++ public:
++ NVMeBasic(boost::asio::io_context& io, int bus, int addr);
++
++ int getBus() const override
++ {
++ return bus;
++ }
++ int getAddr() const override
++ {
++ return addr;
++ }
++ void getStatus(std::function<void(const std::error_code&, DriveStatus*)>&&
++ cb) override;
++ ~NVMeBasic() override = default;
++
++ private:
++ boost::asio::io_context& io;
++ int bus;
++ int addr;
++ std::shared_ptr<NVMeBasicIO> basicIO;
++};
+diff --git a/src/NVMeBasicContext.hpp b/src/NVMeBasicContext.hpp
+deleted file mode 100644
+index 52b6a09..0000000
+--- a/src/NVMeBasicContext.hpp
++++ /dev/null
+@@ -1,48 +0,0 @@
+-#pragma once
+-
+-#include "NVMeContext.hpp"
+-
+-#include <boost/asio/io_context.hpp>
+-#include <boost/asio/posix/stream_descriptor.hpp>
+-
+-#include <thread>
+-
+-class NVMeBasicContext : public NVMeContext
+-{
+- public:
+- NVMeBasicContext(boost::asio::io_context& io, int rootBus);
+- ~NVMeBasicContext() override = default;
+- void pollNVMeDevices() override;
+- void readAndProcessNVMeSensor() override;
+- void processResponse(std::shared_ptr<NVMeSensor>& sensor, void* msg,
+- size_t len) override;
+-
+- private:
+- NVMeBasicContext(boost::asio::io_context& io, int rootBus, int cmdOut,
+- int streamIn, int streamOut, int cmdIn);
+- boost::asio::io_context& io;
+-
+- // The IO thread must be destructed after the stream descriptors, so
+- // initialise it first. http://eel.is/c++draft/class.base.init#note-6
+- //
+- // Providing a stop-source to the thread execution function isn't
+- // particularly useful as it will spend most of its time blocked in a system
+- // call - ioctl() for the actual device communication, or read() and write()
+- // on the pipes associated with reqStream and respStream. Rather than trying
+- // to force a stop, rely on read()/write() failures from closed pipes to
+- // coerce it to exit and thus allow completion of the join().
+- std::jthread thread;
+-
+- // Destruction of the stream descriptors has the effect of issuing cancel(),
+- // destroying the closure of the callback where we might be carrying
+- // weak_ptrs to `this`.
+- // https://www.boost.org/doc/libs/1_79_0/doc/html/boost_asio/reference/posix__basic_descriptor/_basic_descriptor.html
+- boost::asio::posix::stream_descriptor reqStream;
+- boost::asio::posix::stream_descriptor respStream;
+-
+- enum
+- {
+- NVME_MI_BASIC_SFLGS_DRIVE_NOT_READY = 0x40,
+- NVME_MI_BASIC_SFLGS_DRIVE_FUNCTIONAL = 0x20,
+- };
+-};
+diff --git a/src/NVMeContext.hpp b/src/NVMeContext.hpp
+deleted file mode 100644
+index 14e38a1..0000000
+--- a/src/NVMeContext.hpp
++++ /dev/null
+@@ -1,109 +0,0 @@
+-#pragma once
+-
+-#include "NVMeSensor.hpp"
+-
+-#include <boost/asio/io_context.hpp>
+-#include <boost/asio/steady_timer.hpp>
+-
+-#include <memory>
+-#include <stdexcept>
+-
+-class NVMeContext : public std::enable_shared_from_this<NVMeContext>
+-{
+- public:
+- NVMeContext(boost::asio::io_context& io, int rootBus) :
+- scanTimer(io), rootBus(rootBus), pollCursor(sensors.end())
+- {
+- if (rootBus < 0)
+- {
+- throw std::invalid_argument(
+- "Invalid root bus: Bus ID must not be negative");
+- }
+- }
+-
+- virtual ~NVMeContext()
+- {
+- scanTimer.cancel();
+- }
+-
+- void addSensor(const std::shared_ptr<NVMeSensor>& sensor)
+- {
+- sensors.emplace_back(sensor);
+- }
+-
+- std::optional<std::shared_ptr<NVMeSensor>>
+- getSensorAtPath(const std::string& path)
+- {
+- for (auto& sensor : sensors)
+- {
+- if (sensor->configurationPath == path)
+- {
+- return sensor;
+- }
+- }
+-
+- return std::nullopt;
+- }
+-
+- // Post-condition: The sensor list does not contain the provided sensor
+- // Post-condition: pollCursor is a valid iterator for the sensor list
+- void removeSensor(const std::shared_ptr<NVMeSensor>& sensor)
+- {
+- // Locate the sensor that we're removing in the sensor list
+- auto found = std::find(sensors.begin(), sensors.end(), sensor);
+-
+- // If we failed to find the sensor in the list the post-condition is
+- // already satisfied
+- if (found == sensors.end())
+- {
+- return;
+- }
+-
+- // We've found the sensor in the list
+-
+- // If we're not actively polling the sensor list, then remove the sensor
+- if (pollCursor == sensors.end())
+- {
+- sensors.erase(found);
+- return;
+- }
+-
+- // We're actively polling the sensor list
+-
+- // If we're not polling the specific sensor that has been removed, then
+- // remove the sensor
+- if (*pollCursor != *found)
+- {
+- sensors.erase(found);
+- return;
+- }
+-
+- // We're polling the sensor that is being removed
+-
+- // Remove the sensor and update the poll cursor so the cursor remains
+- // valid
+- pollCursor = sensors.erase(found);
+- }
+-
+- virtual void close()
+- {
+- scanTimer.cancel();
+- }
+-
+- virtual void pollNVMeDevices() = 0;
+-
+- virtual void readAndProcessNVMeSensor() = 0;
+-
+- virtual void processResponse(std::shared_ptr<NVMeSensor>& sensor, void* msg,
+- size_t len) = 0;
+-
+- protected:
+- boost::asio::steady_timer scanTimer;
+- int rootBus; // Root bus for this drive
+- std::list<std::shared_ptr<NVMeSensor>> sensors;
+- std::list<std::shared_ptr<NVMeSensor>>::iterator pollCursor;
+-};
+-
+-using NVMEMap = boost::container::flat_map<int, std::shared_ptr<NVMeContext>>;
+-
+-NVMEMap& getNVMEMap(void);
+diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
+new file mode 100644
+index 0000000..1531eb6
+--- /dev/null
++++ b/src/NVMeIntf.hpp
+@@ -0,0 +1,43 @@
++#pragma once
++#include <functional>
++#include <memory>
++
++class NVMeIntf
++{
++ public:
++ NVMeIntf() = default;
++ virtual ~NVMeIntf() = default;
++};
++
++// Interface to get information via NVMe MI Basic CMD protocol.
++class NVMeBasicIntf : public NVMeIntf
++{
++ public:
++ struct DriveStatus
++ {
++ uint8_t SmartWarnings;
++ uint8_t Temp;
++ uint8_t DriveLifeUsed;
++ uint8_t WarningTemp;
++ uint8_t PowerState;
++ };
++
++ enum StatusFlags : uint8_t
++ {
++ NVME_MI_BASIC_SFLGS_DRIVE_NOT_READY = 0x40,
++ NVME_MI_BASIC_SFLGS_DRIVE_FUNCTIONAL = 0x20,
++ };
++
++ NVMeBasicIntf() = default;
++
++ // The i2c bus number
++ virtual int getBus() const = 0;
++ // The i2c address for NVMe Basic
++ virtual int getAddr() const = 0;
++
++ // Get NVMe drive status, data address is from 00h~07h
++ virtual void getStatus(
++ std::function<void(const std::error_code&, DriveStatus*)>&& cb) = 0;
++
++ ~NVMeBasicIntf() override = default;
++};
+diff --git a/src/NVMeSensor.cpp b/src/NVMeSensor.cpp
+index e11fae7..3bd743f 100644
+--- a/src/NVMeSensor.cpp
++++ b/src/NVMeSensor.cpp
+@@ -26,17 +26,12 @@ NVMeSensor::NVMeSensor(sdbusplus::asio::object_server& objectServer,
+ std::shared_ptr<sdbusplus::asio::connection>& conn,
+ const std::string& sensorName,
+ std::vector<thresholds::Threshold>&& thresholdsIn,
+- const std::string& sensorConfiguration,
+- const int busNumber) :
++ const std::string& sensorConfiguration) :
+ Sensor(escapeName(sensorName), std::move(thresholdsIn), sensorConfiguration,
+ NVMeSensor::sensorType, false, false, maxReading, minReading, conn,
+ PowerState::on),
+- bus(busNumber), objServer(objectServer)
++ objServer(objectServer)
+ {
+- if (bus < 0)
+- {
+- throw std::invalid_argument("Invalid bus: Bus ID must not be negative");
+- }
+
+ sensorInterface = objectServer.add_interface(
+ "/xyz/openbmc_project/sensors/temperature/" + name,
+diff --git a/src/NVMeSensor.hpp b/src/NVMeSensor.hpp
+index dfed215..52fa852 100644
+--- a/src/NVMeSensor.hpp
++++ b/src/NVMeSensor.hpp
+@@ -13,15 +13,13 @@ class NVMeSensor : public Sensor
+ std::shared_ptr<sdbusplus::asio::connection>& conn,
+ const std::string& sensorName,
+ std::vector<thresholds::Threshold>&& thresholds,
+- const std::string& sensorConfiguration, int busNumber);
++ const std::string& sensorConfiguration);
+ ~NVMeSensor() override;
+
+ NVMeSensor& operator=(const NVMeSensor& other) = delete;
+
+ bool sample();
+
+- int bus;
+-
+ private:
+ const unsigned int scanDelayTicks = 5 * 60;
+ sdbusplus::asio::object_server& objServer;
+diff --git a/src/NVMeSensorMain.cpp b/src/NVMeSensorMain.cpp
+index ef1abbe..0a72a7a 100644
+--- a/src/NVMeSensorMain.cpp
++++ b/src/NVMeSensorMain.cpp
+@@ -14,21 +14,16 @@
+ // limitations under the License.
+ */
+
+-#include "NVMeBasicContext.hpp"
+-#include "NVMeContext.hpp"
+-#include "NVMeSensor.hpp"
+-
++#include "NVMeBasic.hpp"
++#include "NVMeSubsys.hpp"
+ #include <boost/asio/steady_timer.hpp>
+
+ #include <optional>
+ #include <regex>
+
+-static NVMEMap nvmeDeviceMap;
+-
+-NVMEMap& getNVMEMap()
+-{
+- return nvmeDeviceMap;
+-}
++// a map with key value of {path, NVMeSubsystem}
++using NVMEMap = std::map<std::string, std::shared_ptr<NVMeSubsystem>>;
++static NVMEMap nvmeSubsysMap;
+
+ static std::optional<int>
+ extractBusNumber(const std::string& path,
+@@ -44,91 +39,67 @@ static std::optional<int>
+ return std::visit(VariantToIntVisitor(), findBus->second);
+ }
+
+-static std::optional<std::string>
+- extractSensorName(const std::string& path,
+- const SensorBaseConfigMap& properties)
++static std::optional<int> extractAddress(const std::string& path,
++ const SensorBaseConfigMap& properties)
+ {
+- auto findSensorName = properties.find("Name");
+- if (findSensorName == properties.end())
++ auto findAddr = properties.find("Address");
++ if (findAddr == properties.end())
+ {
+- std::cerr << "could not determine configuration name for " << path
+- << "\n";
++ std::cerr << "could not determine address for " << path << "\n";
+ return std::nullopt;
+ }
+
+- return std::get<std::string>(findSensorName->second);
++ return std::visit(VariantToIntVisitor(), findAddr->second);
+ }
+
+-static std::filesystem::path deriveRootBusPath(int busNumber)
+-{
+- return "/sys/bus/i2c/devices/i2c-" + std::to_string(busNumber) +
+- "/mux_device";
+-}
+-
+-static std::optional<int> deriveRootBus(std::optional<int> busNumber)
++static std::optional<std::string>
++ extractName(const std::string& path, const SensorBaseConfigMap& properties)
+ {
+- if (!busNumber)
++ auto findName = properties.find("Name");
++ if (findName == properties.end())
+ {
++ std::cerr << "could not determine configuration name for " << path
++ << "\n";
+ return std::nullopt;
+ }
+
+- std::filesystem::path muxPath = deriveRootBusPath(*busNumber);
+-
+- if (!std::filesystem::is_symlink(muxPath))
+- {
+- return *busNumber;
+- }
+-
+- std::string rootName = std::filesystem::read_symlink(muxPath).filename();
+- size_t dash = rootName.find('-');
+- if (dash == std::string::npos)
+- {
+- std::cerr << "Error finding root bus for " << rootName << "\n";
+- return std::nullopt;
+- }
+-
+- return std::stoi(rootName.substr(0, dash));
++ return std::get<std::string>(findName->second);
+ }
+
+-static std::shared_ptr<NVMeContext>
+- provideRootBusContext(boost::asio::io_context& io, NVMEMap& map,
+- int rootBus)
++static std::optional<std::string>
++ extractProtocol(const std::string& path,
++ const SensorBaseConfigMap& properties)
+ {
+- auto findRoot = map.find(rootBus);
+- if (findRoot != map.end())
++ auto findProtocol = properties.find("Protocol");
++ if (findProtocol == properties.end())
+ {
+- return findRoot->second;
++ std::cerr << "could not determine nvme protocl for " << path << "\n";
++ return std::nullopt;
+ }
+-
+- std::shared_ptr<NVMeContext> context =
+- std::make_shared<NVMeBasicContext>(io, rootBus);
+- map[rootBus] = context;
+-
+- return context;
++ return std::get<std::string>(findProtocol->second);
+ }
+
+-static void handleSensorConfigurations(
+- boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
++static void handleConfigurations(
++ boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer,
+ std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
+- const ManagedObjectType& sensorConfigurations)
++ const ManagedObjectType& nvmeConfigurations)
+ {
+ // todo: it'd be better to only update the ones we care about
+- for (const auto& [_, nvmeContextPtr] : nvmeDeviceMap)
++ for (const auto& [_, nvmeSubsys] : nvmeSubsysMap)
+ {
+- if (nvmeContextPtr)
++ if (nvmeSubsys)
+ {
+- nvmeContextPtr->close();
++ nvmeSubsys->stop();
+ }
+ }
+- nvmeDeviceMap.clear();
++ nvmeSubsysMap.clear();
+
+ // iterate through all found configurations
+- for (const auto& [interfacePath, sensorData] : sensorConfigurations)
++ for (const auto& [interfacePath, configData] : nvmeConfigurations)
+ {
+ // find base configuration
+- auto sensorBase =
+- sensorData.find(configInterfaceName(NVMeSensor::sensorType));
+- if (sensorBase == sensorData.end())
++ auto sensorBase = configData.find(configInterfaceName(NVMeSubsystem::sensorType));
++ if (sensorBase == configData.end())
+ {
+ continue;
+ }
+@@ -136,65 +107,68 @@ static void handleSensorConfigurations(
+ const SensorBaseConfigMap& sensorConfig = sensorBase->second;
+ std::optional<int> busNumber =
+ extractBusNumber(interfacePath, sensorConfig);
++ std::optional<int> address =
++ extractAddress(interfacePath, sensorConfig);
+ std::optional<std::string> sensorName =
+- extractSensorName(interfacePath, sensorConfig);
+- std::optional<int> rootBus = deriveRootBus(busNumber);
++ extractName(interfacePath, sensorConfig);
++ std::optional<std::string> nvmeProtocol =
++ extractProtocol(interfacePath, sensorConfig);
+
+- if (!(busNumber && sensorName && rootBus))
++ if (!(busNumber && sensorName))
+ {
+ continue;
+ }
+
+- std::vector<thresholds::Threshold> sensorThresholds;
+- if (!parseThresholdsFromConfig(sensorData, sensorThresholds))
++ // the default protocol is mi_basic
++ if (!nvmeProtocol)
+ {
+- std::cerr << "error populating thresholds for " << *sensorName
+- << "\n";
++ nvmeProtocol.emplace("mi_basic");
+ }
+-
+- try
+- {
+- // May throw for an invalid rootBus
+- std::shared_ptr<NVMeContext> context =
+- provideRootBusContext(io, nvmeDeviceMap, *rootBus);
+-
+- // Construct the sensor after grabbing the context so we don't
+- // glitch D-Bus May throw for an invalid busNumber
+- std::shared_ptr<NVMeSensor> sensorPtr =
+- std::make_shared<NVMeSensor>(
+- objectServer, io, dbusConnection, *sensorName,
+- std::move(sensorThresholds), interfacePath, *busNumber);
+-
+- context->addSensor(sensorPtr);
+- }
+- catch (const std::invalid_argument& ex)
++ if (*nvmeProtocol == "mi_basic")
+ {
+- std::cerr << "Failed to add sensor for "
+- << std::string(interfacePath) << ": " << ex.what()
+- << "\n";
++ // defualt i2c basic port is 0x6a
++ if (!address)
++ {
++ address.emplace(0x6a);
++ }
++ try
++ {
++ std::shared_ptr<NVMeIntf> nvmeBasic{
++ new NVMeBasic(io, *busNumber, *address)};
++
++ auto nvmeSubsys = std::make_shared<NVMeSubsystem>(
++ io, objectServer, dbusConnection, interfacePath,
++ *sensorName, configData, nvmeBasic);
++ nvmeSubsysMap.emplace(interfacePath, nvmeSubsys);
++ nvmeSubsys->start();
++ }
++ catch (std::exception& ex)
++ {
++ std::cerr << "Failed to add subsystem for "
++ << std::string(interfacePath) << ": " << ex.what()
++ << "\n";
++ continue;
++ }
+ }
+ }
+- for (const auto& [_, context] : nvmeDeviceMap)
+- {
+- context->pollNVMeDevices();
+- }
+ }
+
+-void createSensors(boost::asio::io_context& io,
+- sdbusplus::asio::object_server& objectServer,
+- std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
++void createNVMeSubsystems(
++ boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer,
++ std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
+ {
+
+ auto getter = std::make_shared<GetSensorConfiguration>(
+ dbusConnection, [&io, &objectServer, &dbusConnection](
+- const ManagedObjectType& sensorConfigurations) {
+- handleSensorConfigurations(io, objectServer, dbusConnection,
+- sensorConfigurations);
++ const ManagedObjectType& nvmeConfigurations) {
++ handleConfigurations(io, objectServer, dbusConnection,
++ nvmeConfigurations);
+ });
+- getter->getConfiguration(std::vector<std::string>{NVMeSensor::sensorType});
++ getter->getConfiguration(
++ std::vector<std::string>{NVMeSubsystem::sensorType});
+ }
+
+-static void interfaceRemoved(sdbusplus::message_t& message, NVMEMap& contexts)
++static void interfaceRemoved(sdbusplus::message_t& message, NVMEMap& subsystems)
+ {
+ if (message.is_method_error())
+ {
+@@ -207,36 +181,33 @@ static void interfaceRemoved(sdbusplus::message_t& message, NVMEMap& contexts)
+
+ message.read(path, interfaces);
+
+- for (auto& [_, context] : contexts)
++ auto interface = std::find(interfaces.begin(), interfaces.end(),
++ configInterfaceName(NVMeSubsystem::sensorType));
++ if (interface == interfaces.end())
+ {
+- std::optional<std::shared_ptr<NVMeSensor>> sensor =
+- context->getSensorAtPath(path);
+- if (!sensor)
+- {
+- continue;
+- }
+-
+- auto interface = std::find(interfaces.begin(), interfaces.end(),
+- (*sensor)->objectType);
+- if (interface == interfaces.end())
+- {
+- continue;
+- }
++ return;
++ }
+
+- context->removeSensor(sensor.value());
++ auto subsys = subsystems.find(path);
++ if (subsys == subsystems.end())
++ {
++ return;
+ }
++
++ subsys->second->stop();
++ nvmeSubsysMap.erase(subsys);
+ }
+
+ int main()
+ {
+ boost::asio::io_context io;
+ auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
+- systemBus->request_name("xyz.openbmc_project.NVMeSensor");
++ systemBus->request_name("xyz.openbmc_project.NVMe");
+ sdbusplus::asio::object_server objectServer(systemBus, true);
+ objectServer.add_manager("/xyz/openbmc_project/sensors");
++ objectServer.add_manager("/xyz/openbmc_project/sensors");
+
+- boost::asio::post(io,
+- [&]() { createSensors(io, objectServer, systemBus); });
++ io.post([&]() { createNVMeSubsystems(io, objectServer, systemBus); });
+
+ boost::asio::steady_timer filterTimer(io);
+ std::function<void(sdbusplus::message_t&)> eventHandler =
+@@ -256,7 +227,7 @@ int main()
+ return;
+ }
+
+- createSensors(io, objectServer, systemBus);
++ createNVMeSubsystems(io, objectServer, systemBus);
+ });
+ };
+
+@@ -272,7 +243,7 @@ int main()
+ "type='signal',member='InterfacesRemoved',arg0path='" +
+ std::string(inventoryPath) + "/'",
+ [](sdbusplus::message_t& msg) {
+- interfaceRemoved(msg, nvmeDeviceMap);
++ interfaceRemoved(msg, nvmeSubsysMap);
+ });
+
+ setupManufacturingModeMatch(*systemBus);
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+new file mode 100644
+index 0000000..0f02d16
+--- /dev/null
++++ b/src/NVMeSubsys.cpp
+@@ -0,0 +1,215 @@
++#include "NVMeSubsys.hpp"
++
++#include "Thresholds.hpp"
++
++std::optional<std::string>
++ extractOneFromTail(std::string::const_reverse_iterator& rbegin,
++ const std::string::const_reverse_iterator& rend)
++{
++ std::string name;
++ auto curr = rbegin;
++ // remove the ending '/'s
++ while (rbegin != rend && *rbegin == '/')
++ {
++ rbegin++;
++ }
++ if (rbegin == rend)
++ {
++ return std::nullopt;
++ }
++ curr = rbegin++;
++
++ // extract word
++ while (rbegin != rend && *rbegin != '/')
++ {
++ rbegin++;
++ }
++ if (rbegin == rend)
++ {
++ return std::nullopt;
++ }
++ name.append(rbegin.base(), curr.base());
++ return {name};
++}
++
++// a path of "/xyz/openbmc_project/inventory/system/board/{prod}/{nvme}" will
++// generates a sensor name {prod}_{nvme}
++std::optional<std::string> createSensorNameFromPath(const std::string& path)
++{
++ if (path.empty())
++ {
++ return std::nullopt;
++ }
++ auto rbegin = path.crbegin();
++
++ auto nvme = extractOneFromTail(rbegin, path.crend());
++ auto prod = extractOneFromTail(rbegin, path.crend());
++ auto board = extractOneFromTail(rbegin, path.crend());
++
++ if (!nvme || !prod || !board || board != "board")
++ {
++ return std::nullopt;
++ }
++ std::string name{std::move(*prod)};
++ name.append("_");
++ name.append(*nvme);
++ return name;
++}
++
++// get temporature from a NVMe Basic reading.
++static double getTemperatureReading(int8_t reading)
++{
++ if (reading == static_cast<int8_t>(0x80) ||
++ reading == static_cast<int8_t>(0x81))
++ {
++ // 0x80 = No temperature data or temperature data is more the 5 s
++ // old 0x81 = Temperature sensor failure
++ return std::numeric_limits<double>::quiet_NaN();
++ }
++
++ return reading;
++}
++
++NVMeSubsystem::NVMeSubsystem(boost::asio::io_context& io,
++ sdbusplus::asio::object_server& objServer,
++ std::shared_ptr<sdbusplus::asio::connection> conn,
++ const std::string& path, const std::string& name,
++ const SensorData& configData,
++ const std::shared_ptr<NVMeIntf>& intf) :
++ io(io),
++ objServer(objServer), conn(conn), path(path), name(name), nvmeIntf(intf),
++ ctempTimer(io)
++{
++ if (!intf)
++ {
++ throw std::runtime_error("NVMe interface is null");
++ }
++ if (dynamic_cast<NVMeBasicIntf*>(nvmeIntf.get()) != nullptr)
++ {
++ std::optional<std::string> sensorName = createSensorNameFromPath(path);
++ if (!sensorName)
++ {
++ // fail to parse sensor name from path, using name instead.
++ sensorName.emplace(name);
++ }
++
++ std::vector<thresholds::Threshold> sensorThresholds;
++ if (!parseThresholdsFromConfig(configData, sensorThresholds))
++ {
++ std::cerr << "error populating thresholds for " << *sensorName
++ << "\n";
++ throw std::runtime_error("error populating thresholds for " +
++ *sensorName);
++ }
++
++ ctemp.emplace(objServer, io, conn, *sensorName,
++ std::move(sensorThresholds), path);
++ }
++ else
++ {
++ throw std::runtime_error("Unsupported NVMe interface");
++ }
++}
++
++void NVMeSubsystem::start()
++{
++ // start to poll value for CTEMP sensor.
++ if (auto intf = std::dynamic_pointer_cast<NVMeBasicIntf>(nvmeIntf))
++ {
++ ctemp_fetcher_t<NVMeBasicIntf::DriveStatus*> dataFether =
++ [intf](std::function<void(const std::error_code&,
++ NVMeBasicIntf::DriveStatus*)>&& cb) {
++ intf->getStatus(std::move(cb));
++ };
++ ctemp_parser_t<NVMeBasicIntf::DriveStatus*> dataParser =
++ [](NVMeBasicIntf::DriveStatus* status) -> std::optional<double> {
++ if (status == nullptr)
++ {
++ return std::nullopt;
++ }
++ return {getTemperatureReading(status->Temp)};
++ };
++ pollCtemp(dataFether, dataParser);
++ }
++}
++
++template <class T>
++void NVMeSubsystem::pollCtemp(
++ const std::function<void(std::function<void(const std::error_code&, T)>&&)>&
++ dataFetcher,
++ const std::function<std::optional<double>(T data)>& dataParser)
++{
++ ctempTimer.expires_from_now(std::chrono::seconds(1));
++ ctempTimer.async_wait(std::bind_front(NVMeSubsystem::Detail::pollCtemp<T>,
++ shared_from_this(), dataFetcher,
++ dataParser));
++}
++
++template <class T>
++void NVMeSubsystem::Detail::pollCtemp(std::shared_ptr<NVMeSubsystem> self,
++ ctemp_fetcher_t<T> dataFetcher,
++ ctemp_parser_t<T> dataParser,
++ const boost::system::error_code errorCode)
++{
++
++ if (errorCode == boost::asio::error::operation_aborted)
++ {
++ return;
++ }
++ if (errorCode)
++ {
++ std::cerr << errorCode.message() << "\n";
++ self->pollCtemp(dataFetcher, dataParser);
++ return;
++ }
++
++ if (!self->ctemp)
++ {
++ self->pollCtemp(dataFetcher, dataParser);
++ return;
++ }
++
++ if (!self->ctemp->readingStateGood())
++ {
++ self->ctemp->markAvailable(false);
++ self->ctemp->updateValue(std::numeric_limits<double>::quiet_NaN());
++ self->pollCtemp(dataFetcher, dataParser);
++ return;
++ }
++
++ /* Potentially defer sampling the sensor sensor if it is in error */
++ if (!self->ctemp->sample())
++ {
++ self->pollCtemp(dataFetcher, dataParser);
++ return;
++ }
++
++ dataFetcher(
++ std::bind_front(Detail::updateCtemp<T>, self, dataParser, dataFetcher));
++}
++
++template <class T>
++void NVMeSubsystem::Detail::updateCtemp(
++ const std::shared_ptr<NVMeSubsystem>& self, ctemp_parser_t<T> dataParser,
++ ctemp_fetcher_t<T> dataFetcher, const boost::system::error_code error,
++ T data)
++{
++ if (error)
++ {
++ std::cerr << "error reading ctemp from subsystem: " << self->name
++ << ", reason:" << error.message() << "\n";
++ self->ctemp->markFunctional(false);
++ self->pollCtemp(dataFetcher, dataParser);
++ return;
++ }
++ auto value = dataParser(data);
++ if (!value)
++ {
++ self->ctemp->incrementError();
++ self->pollCtemp(dataFetcher, dataParser);
++ return;
++ }
++
++ self->ctemp->updateValue(*value);
++ self->pollCtemp(dataFetcher, dataParser);
++}
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+new file mode 100644
+index 0000000..fa741b7
+--- /dev/null
++++ b/src/NVMeSubsys.hpp
+@@ -0,0 +1,70 @@
++#include "NVMeBasic.hpp"
++#include "NVMeSensor.hpp"
++#include "Utils.hpp"
++
++class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
++{
++ public:
++ static constexpr const char* sensorType = "NVME1000";
++
++ NVMeSubsystem(boost::asio::io_context& io,
++ sdbusplus::asio::object_server& objServer,
++ std::shared_ptr<sdbusplus::asio::connection> conn,
++ const std::string& path, const std::string& name,
++ const SensorData& configData,
++ const std::shared_ptr<NVMeIntf>& intf);
++
++ void start();
++
++ void stop()
++ {
++ ctempTimer.cancel();
++ }
++
++ private:
++ boost::asio::io_context& io;
++ sdbusplus::asio::object_server& objServer;
++ std::shared_ptr<sdbusplus::asio::connection> conn;
++ std::string path;
++ std::string name;
++
++ std::shared_ptr<NVMeIntf> nvmeIntf;
++
++ /* thermal sensor for the subsystem */
++ std::optional<NVMeSensor> ctemp;
++ boost::asio::steady_timer ctempTimer;
++
++ // Function type for fetching ctemp which incaplucated in a structure of T.
++ // The fetcher function take a callback as input to process the result.
++ template <class T>
++ using ctemp_fetcher_t =
++ std::function<void(std::function<void(const std::error_code&, T)>&&)>;
++
++ // Function type for parsing ctemp out the structure of type T.
++ // The parser function will return the value of ctemp or nullopt on failure.
++ template <class T>
++ using ctemp_parser_t = std::function<std::optional<double>(T data)>;
++
++ template <class T>
++ void pollCtemp(const ctemp_fetcher_t<T>& dataFetcher,
++ const ctemp_parser_t<T>& dataParser);
++
++ // implemetation details for the class.
++ // It should contain only static function and using binding to the
++ // NVMeSubsystem instances. The detail is defined to claim the accessibility
++ // to the parent private field.
++ class Detail
++ {
++ public:
++ template <class T>
++ static void pollCtemp(std::shared_ptr<NVMeSubsystem> self,
++ ctemp_fetcher_t<T> dataFetcher,
++ ctemp_parser_t<T> dataParser,
++ boost::system::error_code errorCode);
++ template <class T>
++ static void updateCtemp(const std::shared_ptr<NVMeSubsystem>& self,
++ ctemp_parser_t<T> dataParser,
++ ctemp_fetcher_t<T> dataFetcher,
++ boost::system::error_code errorCode, T data);
++ };
++};
+diff --git a/src/meson.build b/src/meson.build
+index 665517a..90a3c6f 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -186,16 +186,19 @@ if get_option('mcu').enabled()
+ endif
+
+ if get_option('nvme').enabled()
+- nvme_srcs = files('NVMeSensorMain.cpp', 'NVMeSensor.cpp')
+- nvme_srcs += files('NVMeBasicContext.cpp')
+-
++ nvme_srcs = [
++ 'NVMeSensorMain.cpp',
++ 'NVMeSensor.cpp',
++ 'NVMeBasic.cpp',
++ 'NVMeSubsys.cpp'
++ ]
+ nvme_deps = [ default_deps, i2c, thresholds_dep, utils_dep, threads ]
+
+ executable(
+ 'nvmesensor',
+ sources: nvme_srcs,
+ dependencies: nvme_deps,
+- cpp_args: uring_args,
++ cpp_args: [uring_args, '-frtti', '-UBOOST_ASIO_NO_DEPRECATED'],
+ install: true,
+ )
+ endif
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0002-NVMe-sensor-add-Storage-and-Drive-interface.patch b/recipes-phosphor/sensors/dbus-sensors/0002-NVMe-sensor-add-Storage-and-Drive-interface.patch
new file mode 100644
index 0000000..ba713ca
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0002-NVMe-sensor-add-Storage-and-Drive-interface.patch
@@ -0,0 +1,214 @@
+From 97215d05ee8506cc2f4dc85ae701307bfb887ed1 Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Thu, 8 Sep 2022 16:43:59 -0700
+Subject: [PATCH 02/34] NVMe sensor: add Storage and Drive interface
+
+Add xyz.openbmc_project.Inventory.Item.Drive and
+xyz.openbmc_project.Inventory.Item.Storage interfaces to the nvme
+subsystem. Make association between the Storage and Chassis.
+
+The interfaces are used by Redfish server (BMCWeb) to construct the
+Storage resources.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: Iaa2615f519f5a95dd0cdf3678ca1774b5285f469
+---
+ src/NVMeDrive.hpp | 16 ++++++++++++++++
+ src/NVMeSensorMain.cpp | 2 +-
+ src/NVMeStorage.hpp | 17 +++++++++++++++++
+ src/NVMeSubsys.cpp | 37 ++++++++++++++++++++++++++++++++++++-
+ src/NVMeSubsys.hpp | 12 ++++++++++++
+ src/meson.build | 7 ++++++-
+ 6 files changed, 88 insertions(+), 3 deletions(-)
+ create mode 100644 src/NVMeDrive.hpp
+ create mode 100644 src/NVMeStorage.hpp
+
+diff --git a/src/NVMeDrive.hpp b/src/NVMeDrive.hpp
+new file mode 100644
+index 0000000..c7a9ba8
+--- /dev/null
++++ b/src/NVMeDrive.hpp
+@@ -0,0 +1,16 @@
++#include <xyz/openbmc_project/Inventory/Item/Drive/server.hpp>
++
++using DriveBase =
++ sdbusplus::xyz::openbmc_project::Inventory::Item::server::Drive;
++class NVMeDrive : public DriveBase
++{
++ public:
++ NVMeDrive(sdbusplus::bus_t& bus, const char* path) : DriveBase(bus, path)
++ {
++ emit_added();
++ }
++ ~NVMeDrive() override
++ {
++ emit_removed();
++ }
++};
+diff --git a/src/NVMeSensorMain.cpp b/src/NVMeSensorMain.cpp
+index 0a72a7a..4e5cfc9 100644
+--- a/src/NVMeSensorMain.cpp
++++ b/src/NVMeSensorMain.cpp
+@@ -205,7 +205,7 @@ int main()
+ systemBus->request_name("xyz.openbmc_project.NVMe");
+ sdbusplus::asio::object_server objectServer(systemBus, true);
+ objectServer.add_manager("/xyz/openbmc_project/sensors");
+- objectServer.add_manager("/xyz/openbmc_project/sensors");
++ objectServer.add_manager("/xyz/openbmc_project/inventory");
+
+ io.post([&]() { createNVMeSubsystems(io, objectServer, systemBus); });
+
+diff --git a/src/NVMeStorage.hpp b/src/NVMeStorage.hpp
+new file mode 100644
+index 0000000..c72661c
+--- /dev/null
++++ b/src/NVMeStorage.hpp
+@@ -0,0 +1,17 @@
++#include <xyz/openbmc_project/Inventory/Item/Storage/server.hpp>
++
++using StorageBase =
++ sdbusplus::xyz::openbmc_project::Inventory::Item::server::Storage;
++class NVMeStorage : public StorageBase
++{
++ public:
++ NVMeStorage(sdbusplus::bus_t& bus, const char* path) :
++ StorageBase(bus, path)
++ {
++ emit_added();
++ }
++ ~NVMeStorage() override
++ {
++ emit_removed();
++ }
++};
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index 0f02d16..d0e41a5 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -2,6 +2,8 @@
+
+ #include "Thresholds.hpp"
+
++#include <filesystem>
++
+ std::optional<std::string>
+ extractOneFromTail(std::string::const_reverse_iterator& rbegin,
+ const std::string::const_reverse_iterator& rend)
+@@ -56,6 +58,22 @@ std::optional<std::string> createSensorNameFromPath(const std::string& path)
+ return name;
+ }
+
++void createStorageAssociation(
++ std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
++ const std::string& path)
++{
++ if (association)
++ {
++ std::filesystem::path p(path);
++
++ std::vector<Association> associations;
++ associations.emplace_back("chassis", "storage",
++ p.parent_path().string());
++ association->register_property("Associations", associations);
++ association->initialize();
++ }
++}
++
+ // get temporature from a NVMe Basic reading.
+ static double getTemperatureReading(int8_t reading)
+ {
+@@ -78,12 +96,16 @@ NVMeSubsystem::NVMeSubsystem(boost::asio::io_context& io,
+ const std::shared_ptr<NVMeIntf>& intf) :
+ io(io),
+ objServer(objServer), conn(conn), path(path), name(name), nvmeIntf(intf),
+- ctempTimer(io)
++ ctempTimer(io),
++ storage(*dynamic_cast<sdbusplus::bus_t*>(conn.get()), path.c_str()),
++ drive(*dynamic_cast<sdbusplus::bus_t*>(conn.get()), path.c_str())
+ {
+ if (!intf)
+ {
+ throw std::runtime_error("NVMe interface is null");
+ }
++
++ // initiate the common interfaces (thermal sensor, Drive and Storage)
+ if (dynamic_cast<NVMeBasicIntf*>(nvmeIntf.get()) != nullptr)
+ {
+ std::optional<std::string> sensorName = createSensorNameFromPath(path);
+@@ -104,6 +126,17 @@ NVMeSubsystem::NVMeSubsystem(boost::asio::io_context& io,
+
+ ctemp.emplace(objServer, io, conn, *sensorName,
+ std::move(sensorThresholds), path);
++
++ /* xyz.openbmc_project.Inventory.Item.Drive */
++ drive.protocol(NVMeDrive::DriveProtocol::NVMe);
++ drive.type(NVMeDrive::DriveType::SSD);
++ // TODO: update capacity
++
++ /* xyz.openbmc_project.Inventory.Item.Storage */
++ // make association to chassis
++ auto storageAssociation =
++ objServer.add_interface(path, association::interface);
++ createStorageAssociation(storageAssociation, path);
+ }
+ else
+ {
+@@ -131,6 +164,8 @@ void NVMeSubsystem::start()
+ };
+ pollCtemp(dataFether, dataParser);
+ }
++
++ // TODO: start to poll Drive status.
+ }
+
+ template <class T>
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index fa741b7..f4b2cc2 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -1,5 +1,7 @@
+ #include "NVMeBasic.hpp"
++#include "NVMeDrive.hpp"
+ #include "NVMeSensor.hpp"
++#include "NVMeStorage.hpp"
+ #include "Utils.hpp"
+
+ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+@@ -49,6 +51,16 @@ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+ void pollCtemp(const ctemp_fetcher_t<T>& dataFetcher,
+ const ctemp_parser_t<T>& dataParser);
+
++ /*
++ Storage interface: xyz.openbmc_project.Inventory.Item.Storage
++ */
++ NVMeStorage storage;
++
++ /*
++ Drive interface: xyz.openbmc_project.Inventory.Item.Drive
++ */
++ NVMeDrive drive;
++
+ // implemetation details for the class.
+ // It should contain only static function and using binding to the
+ // NVMeSubsystem instances. The detail is defined to claim the accessibility
+diff --git a/src/meson.build b/src/meson.build
+index 90a3c6f..788cc6b 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -192,7 +192,12 @@ if get_option('nvme').enabled()
+ 'NVMeBasic.cpp',
+ 'NVMeSubsys.cpp'
+ ]
+- nvme_deps = [ default_deps, i2c, thresholds_dep, utils_dep, threads ]
++
++ pdi_deps = dependency ('phosphor-dbus-interfaces')
++ nvme_deps = [
++ default_deps, i2c, thresholds_dep,
++ utils_dep, threads, pdi_deps
++ ]
+
+ executable(
+ 'nvmesensor',
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0003-nvmesensor-Add-NVMe-MI-protocol-and-controller.patch b/recipes-phosphor/sensors/dbus-sensors/0003-nvmesensor-Add-NVMe-MI-protocol-and-controller.patch
new file mode 100644
index 0000000..aca0cd7
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0003-nvmesensor-Add-NVMe-MI-protocol-and-controller.patch
@@ -0,0 +1,564 @@
+From 4dd8224171dcde2f8efe912236eab9b54ac2f2af Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Fri, 16 Sep 2022 18:52:55 -0700
+Subject: [PATCH 03/34] nvmesensor: Add NVMe-MI protocol and controller
+
+NVMeMiIntf will expose NVMe-MI and NVMe-MI admin interface in the async
+style. NVMeMi class is a implementation of the NVMeMiIntf. It is based
+on libnvme(mi) and tranferring the MI messages via MCTP channel.
+
+NVMeMi first initiates the MCTP endpoint via mctpd DBus interface. It
+also opens the mctp ep for the remote NVMe OOB controller. Each NVMeMi
+instance maintains its own thread for physical transportation, given the
+transportation delay of NVMe-MI message over I2C.
+
+The change alse expose all controllers with the NVMe subsystem via the
+NVMe MI protocol. It creates
+xyz.openbmc_project.Inventory.Item.StorageController interface.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: I2b8d172ed826ca0798b472a8c0bb95c8677e60bb
+---
+ src/NVMeController.hpp | 47 +++++++++
+ src/NVMeIntf.hpp | 16 +++
+ src/NVMeMi.cpp | 212 +++++++++++++++++++++++++++++++++++++++
+ src/NVMeMi.hpp | 48 +++++++++
+ src/NVMeSensorMain.cpp | 29 ++++++
+ src/NVMeSubsys.cpp | 38 ++++++-
+ src/NVMeSubsys.hpp | 4 +
+ src/meson.build | 20 +++-
+ subprojects/libnvme.wrap | 6 ++
+ 9 files changed, 414 insertions(+), 6 deletions(-)
+ create mode 100644 src/NVMeController.hpp
+ create mode 100644 src/NVMeMi.cpp
+ create mode 100644 src/NVMeMi.hpp
+ create mode 100644 subprojects/libnvme.wrap
+
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+new file mode 100644
+index 0000000..67554f6
+--- /dev/null
++++ b/src/NVMeController.hpp
+@@ -0,0 +1,47 @@
++
++#pragma once
++
++#include "NVMeIntf.hpp"
++
++#include <boost/asio/io_context.hpp>
++#include <sdbusplus/asio/connection.hpp>
++#include <sdbusplus/asio/object_server.hpp>
++#include <xyz/openbmc_project/Inventory/Item/StorageController/server.hpp>
++
++#include <utility>
++
++using ControllerBase =
++ sdbusplus::xyz::openbmc_project::Inventory::Item::server::StorageController;
++class NVMeController :
++ private ControllerBase,
++ public std::enable_shared_from_this<NVMeController>
++
++{
++ public:
++ NVMeController(boost::asio::io_context& io,
++ sdbusplus::asio::object_server& objServer,
++ std::shared_ptr<sdbusplus::asio::connection> conn,
++ std::string path, std::shared_ptr<NVMeMiIntf> nvmeIntf,
++ nvme_mi_ctrl_t ctrl) :
++ ControllerBase(dynamic_cast<sdbusplus::bus_t&>(*conn), path.c_str()),
++ io(io), objServer(objServer), conn(conn), path(path),
++ nvmeIntf(nvmeIntf), nvmeCtrl(ctrl)
++ {
++ emit_added();
++ }
++ void start(){};
++
++ ~NVMeController() override
++ {
++ emit_removed();
++ }
++
++ private:
++ boost::asio::io_context& io;
++ sdbusplus::asio::object_server& objServer;
++ std::shared_ptr<sdbusplus::asio::connection> conn;
++ std::string path;
++
++ std::shared_ptr<NVMeMiIntf> nvmeIntf;
++ nvme_mi_ctrl_t nvmeCtrl;
++};
+diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
+index 1531eb6..0fed5fd 100644
+--- a/src/NVMeIntf.hpp
++++ b/src/NVMeIntf.hpp
+@@ -1,4 +1,6 @@
+ #pragma once
++#include <libnvme-mi.h>
++
+ #include <functional>
+ #include <memory>
+
+@@ -41,3 +43,17 @@ class NVMeBasicIntf : public NVMeIntf
+
+ ~NVMeBasicIntf() override = default;
+ };
++
++class NVMeMiIntf : public NVMeIntf
++{
++ public:
++ virtual int getNID() const = 0;
++ virtual int getEID() const = 0;
++ virtual void miSubsystemHealthStatusPoll(
++ std::function<void(const std::error_code&,
++ nvme_mi_nvm_ss_health_status*)>&& cb) = 0;
++ virtual void
++ miScanCtrl(std::function<void(const std::error_code&,
++ const std::vector<nvme_mi_ctrl_t>&)>
++ cb) = 0;
++};
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+new file mode 100644
+index 0000000..3d01a21
+--- /dev/null
++++ b/src/NVMeMi.cpp
+@@ -0,0 +1,212 @@
++#include "NVMeMi.hpp"
++
++#include <cerrno>
++#include <iostream>
++
++nvme_root_t NVMeMi::nvmeRoot = nvme_mi_create_root(stderr, DEFAULT_LOGLEVEL);
++
++NVMeMi::NVMeMi(boost::asio::io_context& io, sdbusplus::bus_t& dbus, int bus,
++ int addr) :
++ io(io),
++ dbus(dbus)
++{
++ if (!nvmeRoot)
++ {
++ throw std::runtime_error("invalid NVMe root");
++ }
++
++ // init mctp ep via mctpd
++ int i = 0;
++ for (;; i++)
++ {
++ try
++ {
++ auto msg = dbus.new_method_call(
++ "xyz.openbmc_project.MCTP", "/xyz/openbmc_project/mctp",
++ "au.com.CodeConstruct.MCTP", "SetupEndpoint");
++
++ msg.append("mctpi2c" + std::to_string(bus));
++ msg.append(std::vector<uint8_t>{static_cast<uint8_t>(addr)});
++ auto reply = msg.call(); // throw SdBusError
++
++ reply.read(eid);
++ reply.read(nid);
++ reply.read(mctpPath);
++ break;
++ }
++ catch (const std::exception& e)
++ {
++ if (i < 5)
++ {
++ std::cerr << "retry to SetupEndpoint: " << e.what()
++ << std::endl;
++ }
++ else
++ {
++ throw std::runtime_error(e.what());
++ }
++ }
++ }
++
++ // open mctp endpoint
++ nvmeEP = nvme_mi_open_mctp(nvmeRoot, nid, eid);
++ if (!nvmeEP)
++ {
++ throw std::runtime_error("can't open MCTP endpoint " +
++ std::to_string(nid) + ":" +
++ std::to_string(eid));
++ }
++
++ // start worker thread
++ workerStop = false;
++ thread = std::thread([&io = workerIO, &stop = workerStop, &mtx = workerMtx,
++ &cv = workerCv]() {
++ // With BOOST_ASIO_DISABLE_THREADS, boost::asio::executor_work_guard
++ // issues null_event across the thread, which caused invalid invokation.
++ // We implement a simple invoke machenism based std::condition_variable.
++ while (1)
++ {
++ io.run();
++ io.restart();
++ {
++ std::unique_lock<std::mutex> lock(mtx);
++ cv.wait(lock);
++ if (stop)
++ {
++ // exhaust all tasks and exit
++ io.run();
++ break;
++ }
++ }
++
++ }
++ });
++}
++
++NVMeMi::~NVMeMi()
++{
++ // close worker
++ workerStop = true;
++ {
++ std::unique_lock<std::mutex> lock(workerMtx);
++ workerCv.notify_all();
++ }
++ thread.join();
++
++ // close EP
++ if (nvmeEP)
++ {
++ nvme_mi_close(nvmeEP);
++ }
++
++ // TODO: delete mctp ep from mctpd
++}
++
++void NVMeMi::post(std::function<void(void)>&& func)
++{
++ if (!workerStop)
++ {
++ std::unique_lock<std::mutex> lock(workerMtx);
++ if (!workerStop)
++ {
++ workerIO.post(std::move(func));
++ workerCv.notify_all();
++ return;
++ }
++ }
++ throw std::runtime_error("NVMeMi has been stopped");
++}
++
++void NVMeMi::miSubsystemHealthStatusPoll(
++ std::function<void(const std::error_code&, nvme_mi_nvm_ss_health_status*)>&&
++ cb)
++{
++ if (!nvmeEP)
++ {
++ std::cerr << "nvme endpoint is invalid" << std::endl;
++
++ io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(std::errc::no_such_device), nullptr);
++ });
++ return;
++ }
++
++ try
++ {
++ post([self{shared_from_this()}, cb{std::move(cb)}]() {
++ nvme_mi_nvm_ss_health_status ss_health;
++ auto rc = nvme_mi_mi_subsystem_health_status_poll(self->nvmeEP,
++ true, &ss_health);
++ if (rc)
++ {
++
++ std::cerr << "fail to subsystem_health_status_poll: "
++ << std::strerror(errno) << std::endl;
++ self->io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(static_cast<std::errc>(errno)),
++ nullptr);
++ });
++ return;
++ }
++ self->io.post(
++ [cb{std::move(cb)}, ss_health{std::move(ss_health)}]() mutable {
++ cb({}, &ss_health);
++ });
++ });
++ }
++ catch (const std::runtime_error& e)
++ {
++ std::cerr << e.what() << std::endl;
++ io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(std::errc::no_such_device), {});
++ });
++ return;
++ }
++}
++
++void NVMeMi::miScanCtrl(std::function<void(const std::error_code&,
++ const std::vector<nvme_mi_ctrl_t>&)>
++ cb)
++{
++ if (!nvmeEP)
++ {
++ std::cerr << "nvme endpoint is invalid" << std::endl;
++
++ io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(std::errc::no_such_device), {});
++ });
++ return;
++ }
++
++ try
++ {
++ post([self{shared_from_this()}, cb{std::move(cb)}]() {
++ int rc = nvme_mi_scan_ep(self->nvmeEP, true);
++ if (rc)
++ {
++ std::cerr << "fail to scan controllers" << std::endl;
++ self->io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(std::errc::bad_message), {});
++ });
++ return;
++ }
++
++ std::vector<nvme_mi_ctrl_t> list;
++ nvme_mi_ctrl_t c;
++ nvme_mi_for_each_ctrl(self->nvmeEP, c)
++ {
++ list.push_back(c);
++ }
++ self->io.post(
++ [cb{std::move(cb)}, list{std::move(list)}]() { cb({}, list); });
++ });
++ }
++ catch (const std::runtime_error& e)
++ {
++ std::cerr << e.what() << std::endl;
++ io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(std::errc::no_such_device), {});
++ });
++ return;
++ }
++}
+diff --git a/src/NVMeMi.hpp b/src/NVMeMi.hpp
+new file mode 100644
+index 0000000..520ce1d
+--- /dev/null
++++ b/src/NVMeMi.hpp
+@@ -0,0 +1,48 @@
++#include "NVMeIntf.hpp"
++
++#include <boost/asio.hpp>
++#include <sdbusplus/bus.hpp>
++
++#include <thread>
++
++class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
++{
++ public:
++ NVMeMi(boost::asio::io_context& io, sdbusplus::bus_t& dbus, int bus,
++ int addr);
++ ~NVMeMi() override;
++
++ int getNID() const override
++ {
++ return nid;
++ }
++ int getEID() const override
++ {
++ return eid;
++ }
++ void miSubsystemHealthStatusPoll(
++ std::function<void(const std::error_code&,
++ nvme_mi_nvm_ss_health_status*)>&& cb) override;
++ void miScanCtrl(std::function<void(const std::error_code&,
++ const std::vector<nvme_mi_ctrl_t>&)>
++ cb) override;
++
++ private:
++ static nvme_root_t nvmeRoot;
++
++ boost::asio::io_context& io;
++ sdbusplus::bus_t& dbus;
++ nvme_mi_ep_t nvmeEP;
++
++ int nid;
++ uint8_t eid;
++ std::string mctpPath;
++
++ bool workerStop;
++ std::mutex workerMtx;
++ std::condition_variable workerCv;
++ boost::asio::io_context workerIO;
++ std::thread thread;
++
++ void post(std::function<void(void)>&& func);
++};
+diff --git a/src/NVMeSensorMain.cpp b/src/NVMeSensorMain.cpp
+index 4e5cfc9..d85dac7 100644
+--- a/src/NVMeSensorMain.cpp
++++ b/src/NVMeSensorMain.cpp
+@@ -15,7 +15,9 @@
+ */
+
+ #include "NVMeBasic.hpp"
++#include "NVMeMi.hpp"
+ #include "NVMeSubsys.hpp"
++
+ #include <boost/asio/steady_timer.hpp>
+
+ #include <optional>
+@@ -150,6 +152,33 @@ static void handleConfigurations(
+ continue;
+ }
+ }
++ else if (*nvmeProtocol == "mi_i2c")
++ {
++ // defualt i2c nvme-mi port is 0x1d
++ if (!address)
++ {
++ address.emplace(0x1d);
++ }
++ try
++ {
++ std::shared_ptr<NVMeIntf> nvmeMi{new NVMeMi(
++ io, dynamic_cast<sdbusplus::bus_t&>(*dbusConnection),
++ *busNumber, *address)};
++
++ auto nvmeSubsys = std::make_shared<NVMeSubsystem>(
++ io, objectServer, dbusConnection, interfacePath,
++ *sensorName, configData, nvmeMi);
++ nvmeSubsysMap.emplace(interfacePath, nvmeSubsys);
++ nvmeSubsys->start();
++ }
++ catch (std::exception& ex)
++ {
++ std::cerr << "Failed to add subsystem for "
++ << std::string(interfacePath) << ": " << ex.what()
++ << "\n";
++ continue;
++ }
++ }
+ }
+ }
+
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index d0e41a5..7900295 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -104,9 +104,10 @@ NVMeSubsystem::NVMeSubsystem(boost::asio::io_context& io,
+ {
+ throw std::runtime_error("NVMe interface is null");
+ }
+-
++
+ // initiate the common interfaces (thermal sensor, Drive and Storage)
+- if (dynamic_cast<NVMeBasicIntf*>(nvmeIntf.get()) != nullptr)
++ if (dynamic_cast<NVMeBasicIntf*>(nvmeIntf.get()) != nullptr ||
++ dynamic_cast<NVMeMiIntf*>(nvmeIntf.get()) != nullptr)
+ {
+ std::optional<std::string> sensorName = createSensorNameFromPath(path);
+ if (!sensorName)
+@@ -146,6 +147,39 @@ NVMeSubsystem::NVMeSubsystem(boost::asio::io_context& io,
+
+ void NVMeSubsystem::start()
+ {
++ // add controllers for the subsystem
++ if (auto nvme = std::dynamic_pointer_cast<NVMeMiIntf>(nvmeIntf))
++ {
++ nvme->miScanCtrl(
++ [self{shared_from_this()},
++ nvme](const std::error_code& ec,
++ const std::vector<nvme_mi_ctrl_t>& ctrlList) mutable {
++ if (ec || ctrlList.size() == 0)
++ {
++ // TODO: mark the subsystem invalid and reschedule refresh
++ std::cerr << "fail to scan controllers for the nvme subsystem"
++ << (ec ? ": " + ec.message() : "") << std::endl;
++ return;
++ }
++
++ // TODO: use ctrlid instead of index
++ uint16_t index = 0;
++ for (auto c : ctrlList)
++ {
++ std::filesystem::path path = std::filesystem::path(self->path) /
++ "controllers" /
++ std::to_string(index);
++ auto [ctrl, _] = self->controllers.insert(
++ {index, std::make_shared<NVMeController>(
++ self->io, self->objServer, self->conn,
++ path.string(), nvme, c)});
++ ctrl->second->start();
++
++ index++;
++ }
++ });
++ }
++
+ // start to poll value for CTEMP sensor.
+ if (auto intf = std::dynamic_pointer_cast<NVMeBasicIntf>(nvmeIntf))
+ {
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index f4b2cc2..1d51cec 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -1,4 +1,5 @@
+ #include "NVMeBasic.hpp"
++#include "NVMeController.hpp"
+ #include "NVMeDrive.hpp"
+ #include "NVMeSensor.hpp"
+ #include "NVMeStorage.hpp"
+@@ -61,6 +62,9 @@ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+ */
+ NVMeDrive drive;
+
++ // map from cntrlid to controller instances
++ std::map<uint16_t, std::shared_ptr<NVMeController>> controllers{};
++
+ // implemetation details for the class.
+ // It should contain only static function and using binding to the
+ // NVMeSubsystem instances. The detail is defined to claim the accessibility
+diff --git a/src/meson.build b/src/meson.build
+index 788cc6b..9f37585 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -190,13 +190,25 @@ if get_option('nvme').enabled()
+ 'NVMeSensorMain.cpp',
+ 'NVMeSensor.cpp',
+ 'NVMeBasic.cpp',
+- 'NVMeSubsys.cpp'
++ 'NVMeSubsys.cpp',
++ 'NVMeMi.cpp'
+ ]
+
+- pdi_deps = dependency ('phosphor-dbus-interfaces')
++ pdi_dep = dependency ('phosphor-dbus-interfaces')
++ libnvme = dependency('libnvme',
++ required : true,
++ fallback : ['libnvme', 'libnvme_dep'],
++ include_type: 'system'
++ )
++ libnvme_mi = dependency('libnvme-mi',
++ required : true,
++ fallback : ['libnvme', 'libnvme_mi_dep'],
++ include_type: 'system'
++ )
++
+ nvme_deps = [
+- default_deps, i2c, thresholds_dep,
+- utils_dep, threads, pdi_deps
++ default_deps, i2c, thresholds_dep, utils_dep,
++ threads, pdi_dep, libnvme, libnvme_mi
+ ]
+
+ executable(
+diff --git a/subprojects/libnvme.wrap b/subprojects/libnvme.wrap
+new file mode 100644
+index 0000000..5d0b7d4
+--- /dev/null
++++ b/subprojects/libnvme.wrap
+@@ -0,0 +1,6 @@
++[wrap-git]
++url = https://github.com/linux-nvme/libnvme.git
++revision = HEAD
++
++[provide]
++dependency_names = libnvme_dep, libnvme_mi_dep
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0004-Enable-ctemp-sensor-for-nvme-mi.patch b/recipes-phosphor/sensors/dbus-sensors/0004-Enable-ctemp-sensor-for-nvme-mi.patch
new file mode 100644
index 0000000..50a4656
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0004-Enable-ctemp-sensor-for-nvme-mi.patch
@@ -0,0 +1,49 @@
+From 80a8495e87cba923ffddb19ffe370c07c11c79ab Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Wed, 21 Sep 2022 05:49:20 +0000
+Subject: [PATCH 04/34] Enable ctemp sensor for nvme mi
+
+polling ctemp from miSubsystemHealthStatusPoll and update the sensor
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: I03601646db8a66d4b7ff687185a4c7fb7d4c6f8e
+---
+ src/NVMeSubsys.cpp | 22 ++++++++++++++++++++++
+ 1 file changed, 22 insertions(+)
+
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index 7900295..57668fa 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -198,6 +198,28 @@ void NVMeSubsystem::start()
+ };
+ pollCtemp(dataFether, dataParser);
+ }
++ else if (auto intf = std::dynamic_pointer_cast<NVMeMiIntf>(nvmeIntf))
++ {
++ ctemp_fetcher_t<nvme_mi_nvm_ss_health_status*>
++ dataFether =
++ [intf](
++ std::function<void(const std::error_code&,
++ nvme_mi_nvm_ss_health_status*)>&& cb) {
++ intf->miSubsystemHealthStatusPoll(std::move(cb));
++ };
++ ctemp_parser_t<nvme_mi_nvm_ss_health_status*>
++ dataParser = [](nvme_mi_nvm_ss_health_status* status)
++ -> std::optional<double> {
++ // Drive Functional
++ bool df = status->nss & 0x20;
++ if (!df)
++ {
++ return std::nullopt;
++ }
++ return {getTemperatureReading(status->ctemp)};
++ };
++ pollCtemp(dataFether, dataParser);
++ }
+
+ // TODO: start to poll Drive status.
+ }
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0005-nvmesensor-Add-Identify-and-cntrl-association.patch b/recipes-phosphor/sensors/dbus-sensors/0005-nvmesensor-Add-Identify-and-cntrl-association.patch
new file mode 100644
index 0000000..061cbae
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0005-nvmesensor-Add-Identify-and-cntrl-association.patch
@@ -0,0 +1,349 @@
+From fe3d552e865877e326b03fd02bc1cda26f0cd8b4 Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Fri, 16 Sep 2022 19:26:38 -0700
+Subject: [PATCH 05/34] nvmesensor: Add Identify and cntrl association
+
+Add Identify to NVMeMi. The NVMeSubsys will identify the primary
+controllers and their corresponding secondary controllers. It will
+create the association between the primary and secondary controllers.
+
+Change-Id: I4776b68ba4dc15b69f517dc0930de9363f31421a
+Signed-off-by: Hao Jiang <jianghao@google.com>
+---
+ src/NVMeController.cpp | 29 ++++++++++++++
+ src/NVMeController.hpp | 14 +++++++
+ src/NVMeIntf.hpp | 6 +++
+ src/NVMeMi.cpp | 86 ++++++++++++++++++++++++++++++++++++++++++
+ src/NVMeMi.hpp | 4 ++
+ src/NVMeSubsys.cpp | 85 ++++++++++++++++++++++++++++++++++++++---
+ src/meson.build | 9 +++--
+ 7 files changed, 223 insertions(+), 10 deletions(-)
+ create mode 100644 src/NVMeController.cpp
+
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+new file mode 100644
+index 0000000..5abfcdc
+--- /dev/null
++++ b/src/NVMeController.cpp
+@@ -0,0 +1,29 @@
++#include "NVMeController.hpp"
++
++void NVMeController::setSecAssoc(
++ const std::vector<std::shared_ptr<NVMeController>> secCntrls)
++{
++
++ if (secAssoc)
++ {
++ objServer.remove_interface(secAssoc);
++ secAssoc.reset();
++ }
++
++ if (secCntrls.empty())
++ {
++ return;
++ }
++
++ using Association = std::tuple<std::string, std::string, std::string>;
++ secAssoc = objServer.add_interface(
++ path, "xyz.openbmc_project.Association.Definitions");
++ std::vector<Association> associations;
++
++ for (auto& cntrl : secCntrls)
++ {
++ associations.emplace_back("secondary", "primary", cntrl->path);
++ }
++ secAssoc->register_property("Associations", associations);
++ secAssoc->initialize();
++}
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+index 67554f6..6e090c9 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -31,6 +31,16 @@ class NVMeController :
+ }
+ void start(){};
+
++ // setup association to the secondary controllers. Clear the Association if
++ // empty.
++ void setSecAssoc(
++ const std::vector<std::shared_ptr<NVMeController>> secCntrls);
++
++ inline void setSecAssoc()
++ {
++ setSecAssoc({});
++ }
++
+ ~NVMeController() override
+ {
+ emit_removed();
+@@ -44,4 +54,8 @@ class NVMeController :
+
+ std::shared_ptr<NVMeMiIntf> nvmeIntf;
+ nvme_mi_ctrl_t nvmeCtrl;
++
++ // The Association interface to secondary controllers from a primary
++ // controller
++ std::shared_ptr<sdbusplus::asio::dbus_interface> secAssoc;
+ };
+diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
+index 0fed5fd..389289b 100644
+--- a/src/NVMeIntf.hpp
++++ b/src/NVMeIntf.hpp
+@@ -3,6 +3,7 @@
+
+ #include <functional>
+ #include <memory>
++#include <span>
+
+ class NVMeIntf
+ {
+@@ -56,4 +57,9 @@ class NVMeMiIntf : public NVMeIntf
+ miScanCtrl(std::function<void(const std::error_code&,
+ const std::vector<nvme_mi_ctrl_t>&)>
+ cb) = 0;
++ virtual void adminIdentify(
++ nvme_mi_ctrl_t ctrl, nvme_identify_cns cns, uint32_t nsid,
++ uint16_t cntid,
++ std::function<void(const std::error_code&, std::span<uint8_t>)>&&
++ cb) = 0;
+ };
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index 3d01a21..3958500 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -210,3 +210,89 @@ void NVMeMi::miScanCtrl(std::function<void(const std::error_code&,
+ return;
+ }
+ }
++
++void NVMeMi::adminIdentify(
++ nvme_mi_ctrl_t ctrl, nvme_identify_cns cns, uint32_t nsid, uint16_t cntid,
++ std::function<void(const std::error_code&, std::span<uint8_t>)>&& cb)
++{
++ if (!nvmeEP)
++ {
++
++ std::cerr << "nvme endpoint is invalid" << std::endl;
++ io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(std::errc::no_such_device), {});
++ });
++ return;
++ }
++ try
++ {
++ post([ctrl, cns, nsid, cntid, self{shared_from_this()},
++ cb{std::move(cb)}]() {
++ int rc = 0;
++ std::vector<uint8_t> data;
++ switch (cns)
++ {
++ case NVME_IDENTIFY_CNS_SECONDARY_CTRL_LIST:
++ {
++ data.resize(sizeof(nvme_secondary_ctrl_list));
++ nvme_identify_args args{};
++ memset(&args, 0, sizeof(args));
++ args.result = nullptr;
++ args.data = data.data();
++ args.args_size = sizeof(args);
++ args.cns = cns;
++ args.csi = NVME_CSI_NVM;
++ args.nsid = nsid;
++ args.cntid = cntid;
++ args.cns_specific_id = NVME_CNSSPECID_NONE;
++ args.uuidx = NVME_UUID_NONE,
++
++ rc = nvme_mi_admin_identify_partial(ctrl, &args, 0,
++ data.size());
++
++ break;
++ }
++
++ default:
++ {
++ data.resize(NVME_IDENTIFY_DATA_SIZE);
++ nvme_identify_args args{};
++ memset(&args, 0, sizeof(args));
++ args.result = nullptr;
++ args.data = data.data();
++ args.args_size = sizeof(args);
++ args.cns = cns;
++ args.csi = NVME_CSI_NVM;
++ args.nsid = nsid;
++ args.cntid = cntid;
++ args.cns_specific_id = NVME_CNSSPECID_NONE;
++ args.uuidx = NVME_UUID_NONE,
++
++ rc = nvme_mi_admin_identify(ctrl, &args);
++ }
++ }
++ if (rc)
++ {
++ std::cerr << "fail to do nvme identify: "
++ << std::strerror(errno) << std::endl;
++ self->io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(static_cast<std::errc>(errno)), {});
++ });
++ return;
++ }
++
++ self->io.post([cb{std::move(cb)}, data{std::move(data)}]() mutable {
++ std::span<uint8_t> span{data.data(), data.size()};
++ cb({}, span);
++ });
++ });
++ }
++ catch (const std::runtime_error& e)
++ {
++ std::cerr << e.what() << std::endl;
++ io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(std::errc::no_such_device), {});
++ });
++ return;
++ }
++}
+diff --git a/src/NVMeMi.hpp b/src/NVMeMi.hpp
+index 520ce1d..5d0d4e2 100644
+--- a/src/NVMeMi.hpp
++++ b/src/NVMeMi.hpp
+@@ -26,6 +26,10 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+ void miScanCtrl(std::function<void(const std::error_code&,
+ const std::vector<nvme_mi_ctrl_t>&)>
+ cb) override;
++ void adminIdentify(nvme_mi_ctrl_t ctrl, nvme_identify_cns cns,
++ uint32_t nsid, uint16_t cntid,
++ std::function<void(const std::error_code&,
++ std::span<uint8_t>)>&& cb) override;
+
+ private:
+ static nvme_root_t nvmeRoot;
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index 57668fa..b49743f 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -162,21 +162,94 @@ void NVMeSubsystem::start()
+ return;
+ }
+
+- // TODO: use ctrlid instead of index
+- uint16_t index = 0;
++ // TODO: manually open nvme_mi_ctrl_t from cntrl id, instead hacking
++ // into structure of nvme_mi_ctrl
+ for (auto c : ctrlList)
+ {
++ /* calucate the cntrl id from nvme_mi_ctrl:
++ struct nvme_mi_ctrl
++ {
++ struct nvme_mi_ep* ep;
++ __u16 id;
++ struct list_node ep_entry;
++ };
++ */
++ uint16_t* index = reinterpret_cast<uint16_t*>(
++ (reinterpret_cast<uint8_t*>(c) +
++ std::max(sizeof(uint16_t), sizeof(void*))));
+ std::filesystem::path path = std::filesystem::path(self->path) /
+ "controllers" /
+- std::to_string(index);
++ std::to_string(*index);
+ auto [ctrl, _] = self->controllers.insert(
+- {index, std::make_shared<NVMeController>(
+- self->io, self->objServer, self->conn,
+- path.string(), nvme, c)});
++ {*index, std::make_shared<NVMeController>(
++ self->io, self->objServer, self->conn,
++ path.string(), nvme, c)});
+ ctrl->second->start();
+
+ index++;
+ }
++
++ /*
++ find primary controller and make association
++ The controller is SR-IOV, meaning all controllers (within a
++ subsystem) are pointing to a single primary controller. So we
++ only need to do identify on an arbatary controller.
++ */
++ auto ctrl = ctrlList.back();
++ nvme->adminIdentify(
++ ctrl, nvme_identify_cns::NVME_IDENTIFY_CNS_SECONDARY_CTRL_LIST,
++ 0, 0,
++ [self{self->shared_from_this()}](const std::error_code& ec,
++ std::span<uint8_t> data) {
++ if (ec || data.size() < sizeof(nvme_secondary_ctrl_list))
++ {
++ std::cerr << "fail to identify secondary controller list"
++ << std::endl;
++ return;
++ }
++ nvme_secondary_ctrl_list& listHdr =
++ *reinterpret_cast<nvme_secondary_ctrl_list*>(data.data());
++
++ // Remove all associations
++ for (const auto& [_, cntrl] : self->controllers)
++ {
++ cntrl->setSecAssoc();
++ }
++
++ if (listHdr.num == 0)
++ {
++ std::cerr << "empty identify secondary controller list"
++ << std::endl;
++ return;
++ }
++
++ // all sc_entry pointing to a single pcid, so we only check
++ // the first entry.
++ auto findPrimary =
++ self->controllers.find(listHdr.sc_entry[0].pcid);
++ if (findPrimary == self->controllers.end())
++ {
++ std::cerr << "fail to match primary controller from "
++ "identify sencondary cntrl list"
++ << std::endl;
++ return;
++ }
++ std::vector<std::shared_ptr<NVMeController>> secCntrls;
++ for (int i = 0; i < listHdr.num; i++)
++ {
++ auto findSecondary =
++ self->controllers.find(listHdr.sc_entry[i].scid);
++ if (findSecondary == self->controllers.end())
++ {
++ std::cerr << "fail to match secondary controller from "
++ "identify sencondary cntrl list"
++ << std::endl;
++ break;
++ }
++ secCntrls.push_back(findSecondary->second);
++ }
++ findPrimary->second->setSecAssoc(secCntrls);
++ });
+ });
+ }
+
+diff --git a/src/meson.build b/src/meson.build
+index 9f37585..740fdd5 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -186,13 +186,14 @@ if get_option('mcu').enabled()
+ endif
+
+ if get_option('nvme').enabled()
+- nvme_srcs = [
++ nvme_srcs = files(
+ 'NVMeSensorMain.cpp',
+ 'NVMeSensor.cpp',
+ 'NVMeBasic.cpp',
+- 'NVMeSubsys.cpp',
+- 'NVMeMi.cpp'
+- ]
++ 'NVMeSubsys.cpp',
++ 'NVMeMi.cpp',
++ 'NVMeController.cpp'
++ )
+
+ pdi_dep = dependency ('phosphor-dbus-interfaces')
+ libnvme = dependency('libnvme',
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0006-Add-NVMeAdmin-intf-with-GetLogPage.patch b/recipes-phosphor/sensors/dbus-sensors/0006-Add-NVMeAdmin-intf-with-GetLogPage.patch
new file mode 100644
index 0000000..e681fdb
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0006-Add-NVMeAdmin-intf-with-GetLogPage.patch
@@ -0,0 +1,310 @@
+From 9df967c6ea11a48371336aa78ac967b6eae03ea2 Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Tue, 20 Sep 2022 18:36:16 +0000
+Subject: [PATCH 06/34] Add NVMeAdmin intf with GetLogPage
+
+xyz.openbmc_project.Nvme.NVMeAdmin interface is used to NVMe spec ADMIN
+API to the NVMe devices.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: Ia432ede188f2c198fa8a9f1b8e454e465df3572b
+---
+ src/NVMeController.cpp | 63 +++++++++++++++++++++
+ src/NVMeController.hpp | 22 ++++----
+ src/NVMeIntf.hpp | 5 ++
+ src/NVMeMi.cpp | 121 ++++++++++++++++++++++++++++++++++++++++-
+ src/NVMeMi.hpp | 4 ++
+ 5 files changed, 202 insertions(+), 13 deletions(-)
+
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index 5abfcdc..0479723 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -1,5 +1,68 @@
+ #include "NVMeController.hpp"
+
++#include <sdbusplus/message/native_types.hpp>
++#include <xyz/openbmc_project/Common/File/error.hpp>
++
++#include <cstdio>
++#include <filesystem>
++#include <iostream>
++
++NVMeController::NVMeController(
++ boost::asio::io_context& io, sdbusplus::asio::object_server& objServer,
++ std::shared_ptr<sdbusplus::asio::connection> conn, std::string path,
++ std::shared_ptr<NVMeMiIntf> nvmeIntf, nvme_mi_ctrl_t ctrl) :
++ ControllerBase(dynamic_cast<sdbusplus::bus_t&>(*conn), path.c_str()),
++ io(io), objServer(objServer), conn(conn), path(path), nvmeIntf(nvmeIntf),
++ nvmeCtrl(ctrl)
++{
++ emit_added();
++
++ /* Admin interface */
++ adminIntf = objServer.add_interface(path, adminIntfName);
++ adminIntf->register_method("GetLogPage", [nvmeIntf, nvmeCtrl{ctrl}](
++ unsigned lid, uint32_t nsid,
++ uint8_t lsp, uint16_t lsi) {
++ std::array<int, 2> pipe;
++ if (::pipe(pipe.data()) < 0)
++ {
++ std::cerr << "GetLogPage fails to open pipe: "
++ << std::strerror(errno) << std::endl;
++ throw sdbusplus::xyz::openbmc_project::Common::File::Error::Open();
++ }
++
++ nvmeIntf->adminGetLogPage(
++ nvmeCtrl, static_cast<nvme_cmd_get_log_lid>(lid), nsid, lsp, lsi,
++ [pipe](const std::error_code& ec, std::span<uint8_t> data) {
++ ::close(pipe[0]);
++ int fd = pipe[1];
++ if (ec)
++ {
++ std::cerr << "fail to GetLogPage: " << ec.message()
++ << std::endl;
++ close(fd);
++ return;
++ }
++
++ // TODO: evaluate the impact of client not reading fast enough on
++ // large trunk of data
++ if (::write(fd, data.data(), data.size()) < 0)
++ {
++ std::cerr << "GetLogPage fails to write fd: "
++ << std::strerror(errno) << std::endl;
++ };
++ close(fd);
++ });
++ return sdbusplus::message::unix_fd{pipe[0]};
++ });
++ adminIntf->initialize();
++}
++
++NVMeController::~NVMeController()
++{
++ objServer.remove_interface(adminIntf);
++ emit_removed();
++}
++
+ void NVMeController::setSecAssoc(
+ const std::vector<std::shared_ptr<NVMeController>> secCntrls)
+ {
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+index 6e090c9..18c3389 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -22,13 +22,10 @@ class NVMeController :
+ sdbusplus::asio::object_server& objServer,
+ std::shared_ptr<sdbusplus::asio::connection> conn,
+ std::string path, std::shared_ptr<NVMeMiIntf> nvmeIntf,
+- nvme_mi_ctrl_t ctrl) :
+- ControllerBase(dynamic_cast<sdbusplus::bus_t&>(*conn), path.c_str()),
+- io(io), objServer(objServer), conn(conn), path(path),
+- nvmeIntf(nvmeIntf), nvmeCtrl(ctrl)
+- {
+- emit_added();
+- }
++ nvme_mi_ctrl_t ctrl);
++
++ ~NVMeController() override;
++
+ void start(){};
+
+ // setup association to the secondary controllers. Clear the Association if
+@@ -41,11 +38,6 @@ class NVMeController :
+ setSecAssoc({});
+ }
+
+- ~NVMeController() override
+- {
+- emit_removed();
+- }
+-
+ private:
+ boost::asio::io_context& io;
+ sdbusplus::asio::object_server& objServer;
+@@ -58,4 +50,10 @@ class NVMeController :
+ // The Association interface to secondary controllers from a primary
+ // controller
+ std::shared_ptr<sdbusplus::asio::dbus_interface> secAssoc;
++
++ // NVMe Admin interface: xyz.openbmc_project.Nvme.NVMeAdmin
++ static constexpr char adminIntfName[] =
++ "xyz.openbmc_project.Nvme.NVMeAdmin";
++ static constexpr char adminDataPath[] = "/run/initramfs";
++ std::shared_ptr<sdbusplus::asio::dbus_interface> adminIntf;
+ };
+diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
+index 389289b..0080b3b 100644
+--- a/src/NVMeIntf.hpp
++++ b/src/NVMeIntf.hpp
+@@ -62,4 +62,9 @@ class NVMeMiIntf : public NVMeIntf
+ uint16_t cntid,
+ std::function<void(const std::error_code&, std::span<uint8_t>)>&&
+ 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,
++ std::function<void(const std::error_code&, std::span<uint8_t>)>&&
++ cb) = 0;
+ };
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index 3958500..222a97e 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -1,5 +1,7 @@
+ #include "NVMeMi.hpp"
+
++#include <boost/endian.hpp>
++
+ #include <cerrno>
+ #include <iostream>
+
+@@ -217,7 +219,6 @@ void NVMeMi::adminIdentify(
+ {
+ if (!nvmeEP)
+ {
+-
+ std::cerr << "nvme endpoint is invalid" << std::endl;
+ io.post([cb{std::move(cb)}]() {
+ cb(std::make_error_code(std::errc::no_such_device), {});
+@@ -296,3 +297,121 @@ void NVMeMi::adminIdentify(
+ return;
+ }
+ }
++
++// Get Temetery Log header and return the size for hdr + data area (Area 1, 2,
++// 3, or maybe 4)
++void getTelemetryLogHost(nvme_mi_ctrl_t ctrl, bool create,
++ std::vector<uint8_t>& data)
++{
++ int rc = 0;
++ data.resize(sizeof(nvme_telemetry_log));
++ nvme_telemetry_log& log =
++ *reinterpret_cast<nvme_telemetry_log*>(data.data());
++ if (create)
++ {
++ rc = nvme_mi_admin_get_log_create_telemetry_host(ctrl, &log);
++ if (rc)
++ {
++ std::cerr << "failed to create telemetry host log" << std::endl;
++ throw std::system_error(errno, std::generic_category());
++ }
++ return;
++ }
++ else
++ {
++ rc = nvme_mi_admin_get_log_telemetry_host(ctrl, 0, sizeof(log), &log);
++ }
++ if (rc)
++ {
++ std::cerr << "failed to retain telemetry host log" << std::endl;
++ throw std::system_error(errno, std::generic_category());
++ }
++
++ long size =
++ static_cast<long>((boost::endian::little_to_native(log.dalb3) + 1)) *
++ NVME_LOG_TELEM_BLOCK_SIZE;
++
++ data.resize(size);
++ rc =
++ nvme_mi_admin_get_log_telemetry_host(ctrl, 0, data.size(), data.data());
++ if (rc)
++ {
++ std::cerr << "failed to get full telemetry host log" << std::endl;
++ throw std::system_error(errno, std::generic_category());
++ }
++}
++
++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)
++{
++ if (!nvmeEP)
++ {
++ std::cerr << "nvme endpoint is invalid" << std::endl;
++ io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(std::errc::no_such_device), {});
++ });
++ return;
++ }
++
++ try
++ {
++ post([ctrl, nsid, lid, lsp, lsi, self{shared_from_this()},
++ cb{std::move(cb)}]() {
++ std::vector<uint8_t> data;
++ try
++ {
++ switch (lid)
++ {
++ case NVME_LOG_LID_TELEMETRY_HOST:
++ {
++ bool create;
++ if (lsp == NVME_LOG_TELEM_HOST_LSP_CREATE)
++ {
++ create = true;
++ }
++ else if (lsp == NVME_LOG_TELEM_HOST_LSP_RETAIN)
++ {
++ create = false;
++ }
++ else
++ {
++ std::cerr << "invalid lsp for telemetry host log"
++ << std::endl;
++ throw std::system_error(std::make_error_code(
++ std::errc::invalid_argument));
++ }
++ getTelemetryLogHost(ctrl, create, data);
++ break;
++ default:
++ {
++ std::cerr << "unknown lid for GetLogPage"
++ << std::endl;
++ throw std::system_error(std::make_error_code(
++ std::errc::invalid_argument));
++ }
++ }
++ }
++ }
++ catch (const std::system_error& e)
++ {
++ self->io.post(
++ [cb{std::move(cb)}, ec = e.code()]() { cb(ec, {}); });
++ return;
++ }
++ self->io.post([cb{std::move(cb)}, data{std::move(data)}]() mutable {
++ std::span<uint8_t> span{data.data(), data.size()};
++ cb({}, span);
++ });
++ });
++ }
++ catch (const std::runtime_error& e)
++ {
++ std::cerr << "NVMeMi adminGetLogPage throws: " << e.what() << std::endl;
++ io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(std::errc::no_such_device), {});
++ });
++ return;
++ }
++}
+diff --git a/src/NVMeMi.hpp b/src/NVMeMi.hpp
+index 5d0d4e2..78ebec9 100644
+--- a/src/NVMeMi.hpp
++++ b/src/NVMeMi.hpp
+@@ -30,6 +30,10 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+ uint32_t nsid, uint16_t cntid,
+ 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) override;
+
+ private:
+ static nvme_root_t nvmeRoot;
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0007-Add-more-logs-to-NVMe-daemon.patch b/recipes-phosphor/sensors/dbus-sensors/0007-Add-more-logs-to-NVMe-daemon.patch
new file mode 100644
index 0000000..f2837dc
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0007-Add-more-logs-to-NVMe-daemon.patch
@@ -0,0 +1,306 @@
+From 68db54ddb4a01d87c1a70d959c260f7e990f633b Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Mon, 26 Sep 2022 06:07:46 +0000
+Subject: [PATCH 07/34] Add more logs to NVMe daemon
+
+The following logs are now supported via GetLogPage:
+* NVME_LOG_LID_ERROR
+* NVME_LOG_LID_SMART
+* NVME_LOG_LID_FW_SLOT
+* NVME_LOG_LID_CMD_EFFECTS
+* NVME_LOG_LID_DEVICE_SELF_TEST
+* NVME_LOG_LID_CHANGED_NS
+* NVME_LOG_LID_TELEMETRY_CTRL
+* NVME_LOG_LID_RESERVATION
+* NVME_LOG_LID_SANITIZE
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: If038c5c6cbbc419f81f0f8e7417d59550606f986
+---
+ src/NVMeMi.cpp | 204 ++++++++++++++++++++++++++++++++++++++++++++-----
+ src/NVMeMi.hpp | 4 +
+ 2 files changed, 189 insertions(+), 19 deletions(-)
+
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index 222a97e..8797d8e 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -298,16 +298,27 @@ void NVMeMi::adminIdentify(
+ }
+ }
+
++static int nvme_mi_admin_get_log_telemetry_host_rae(nvme_mi_ctrl_t ctrl,
++ bool /*rae*/, __u64 offset,
++ __u32 len, void* log)
++{
++ return nvme_mi_admin_get_log_telemetry_host(ctrl, offset, len, log);
++}
++
+ // Get Temetery Log header and return the size for hdr + data area (Area 1, 2,
+ // 3, or maybe 4)
+-void getTelemetryLogHost(nvme_mi_ctrl_t ctrl, bool create,
+- std::vector<uint8_t>& data)
++void getTelemetryLog(nvme_mi_ctrl_t ctrl, bool host, bool create,
++ std::vector<uint8_t>& data)
+ {
+ int rc = 0;
+ data.resize(sizeof(nvme_telemetry_log));
+ nvme_telemetry_log& log =
+ *reinterpret_cast<nvme_telemetry_log*>(data.data());
+- if (create)
++ auto func = host ? nvme_mi_admin_get_log_telemetry_host_rae
++ : nvme_mi_admin_get_log_telemetry_ctrl;
++
++ // Only host telemetry log requires create.
++ if (host && create)
+ {
+ rc = nvme_mi_admin_get_log_create_telemetry_host(ctrl, &log);
+ if (rc)
+@@ -319,11 +330,12 @@ void getTelemetryLogHost(nvme_mi_ctrl_t ctrl, bool create,
+ }
+ else
+ {
+- rc = nvme_mi_admin_get_log_telemetry_host(ctrl, 0, sizeof(log), &log);
++ rc = func(ctrl, false, 0, sizeof(log), &log);
+ }
+ if (rc)
+ {
+- std::cerr << "failed to retain telemetry host log" << std::endl;
++ std::cerr << "failed to retain telemetry log for "
++ << (host ? "host" : "ctrl") << std::endl;
+ throw std::system_error(errno, std::generic_category());
+ }
+
+@@ -332,11 +344,11 @@ void getTelemetryLogHost(nvme_mi_ctrl_t ctrl, bool create,
+ NVME_LOG_TELEM_BLOCK_SIZE;
+
+ data.resize(size);
+- rc =
+- nvme_mi_admin_get_log_telemetry_host(ctrl, 0, data.size(), data.data());
++ rc = func(ctrl, false, 0, data.size(), data.data());
+ if (rc)
+ {
+- std::cerr << "failed to get full telemetry host log" << std::endl;
++ std::cerr << "failed to get full telemetry log for "
++ << (host ? "host" : "ctrl") << std::endl;
+ throw std::system_error(errno, std::generic_category());
+ }
+ }
+@@ -364,34 +376,188 @@ void NVMeMi::adminGetLogPage(
+ {
+ switch (lid)
+ {
+- case NVME_LOG_LID_TELEMETRY_HOST:
++ case NVME_LOG_LID_ERROR:
+ {
+- bool create;
+- if (lsp == NVME_LOG_TELEM_HOST_LSP_CREATE)
++ data.resize(nvme_mi_xfer_size);
++ // The number of entries for most recent error logs.
++ // Currently we only do one nvme mi transfer for the
++ // error log to avoid blocking other tasks
++ static constexpr int num =
++ nvme_mi_xfer_size / sizeof(nvme_error_log_page);
++ nvme_error_log_page* log =
++ reinterpret_cast<nvme_error_log_page*>(data.data());
++
++ int rc =
++ nvme_mi_admin_get_log_error(ctrl, num, false, log);
++ if (rc)
+ {
+- create = true;
++ std::cerr << "fail to get error log" << std::endl;
++ throw std::system_error(std::make_error_code(
++ std::errc::invalid_argument));
+ }
+- else if (lsp == NVME_LOG_TELEM_HOST_LSP_RETAIN)
++ }
++ break;
++ case NVME_LOG_LID_SMART:
++ {
++ data.resize(sizeof(nvme_smart_log));
++ nvme_smart_log* log =
++ reinterpret_cast<nvme_smart_log*>(data.data());
++ int rc =
++ nvme_mi_admin_get_log_smart(ctrl, nsid, false, log);
++ if (rc)
+ {
+- create = false;
++ std::cerr << "fail to get smart log" << std::endl;
++ throw std::system_error(std::make_error_code(
++ std::errc::invalid_argument));
++ }
++ }
++ break;
++ case NVME_LOG_LID_FW_SLOT:
++ {
++ data.resize(sizeof(nvme_firmware_slot));
++ nvme_firmware_slot* log =
++ reinterpret_cast<nvme_firmware_slot*>(data.data());
++ int rc =
++ nvme_mi_admin_get_log_fw_slot(ctrl, false, log);
++ if (rc)
++ {
++ std::cerr << "fail to get firmware slot"
++ << std::endl;
++ throw std::system_error(std::make_error_code(
++ std::errc::invalid_argument));
++ }
++ }
++ break;
++ case NVME_LOG_LID_CMD_EFFECTS:
++ {
++ data.resize(sizeof(nvme_cmd_effects_log));
++ nvme_cmd_effects_log* log =
++ reinterpret_cast<nvme_cmd_effects_log*>(
++ data.data());
++
++ // nvme rev 1.3 doesn't support csi,
++ // set to default csi = NVME_CSI_NVM
++ int rc = nvme_mi_admin_get_log_cmd_effects(
++ ctrl, NVME_CSI_NVM, log);
++ if (rc)
++ {
++ std::cerr
++ << "fail to get cmd supported and effects log"
++ << std::endl;
++ throw std::system_error(std::make_error_code(
++ std::errc::invalid_argument));
++ }
++ }
++ break;
++ case NVME_LOG_LID_DEVICE_SELF_TEST:
++ {
++ data.resize(sizeof(nvme_self_test_log));
++ nvme_self_test_log* log =
++ reinterpret_cast<nvme_self_test_log*>(data.data());
++ int rc =
++ nvme_mi_admin_get_log_device_self_test(ctrl, log);
++ if (rc)
++ {
++ std::cerr << "fail to get device self test log"
++ << std::endl;
++ throw std::system_error(std::make_error_code(
++ std::errc::invalid_argument));
++ }
++ }
++ break;
++ case NVME_LOG_LID_CHANGED_NS:
++ {
++ data.resize(sizeof(nvme_ns_list));
++ nvme_ns_list* log =
++ reinterpret_cast<nvme_ns_list*>(data.data());
++ int rc = nvme_mi_admin_get_log_changed_ns_list(
++ ctrl, false, log);
++ if (rc)
++ {
++ std::cerr << "fail to get changed namespace list"
++ << std::endl;
++ throw std::system_error(std::make_error_code(
++ std::errc::invalid_argument));
++ }
++ }
++ break;
++ case NVME_LOG_LID_TELEMETRY_HOST:
++ // fall through to NVME_LOG_LID_TELEMETRY_CTRL
++ case NVME_LOG_LID_TELEMETRY_CTRL:
++ {
++ bool host = false;
++ bool create = false;
++ if (lid == NVME_LOG_LID_TELEMETRY_HOST)
++ {
++ host = true;
++ if (lsp == NVME_LOG_TELEM_HOST_LSP_CREATE)
++ {
++ create = true;
++ }
++ else if (lsp == NVME_LOG_TELEM_HOST_LSP_RETAIN)
++ {
++ create = false;
++ }
++ else
++ {
++ std::cerr
++ << "invalid lsp for telemetry host log"
++ << std::endl;
++ throw std::system_error(std::make_error_code(
++ std::errc::invalid_argument));
++ }
+ }
+ else
+ {
+- std::cerr << "invalid lsp for telemetry host log"
++ host = false;
++ }
++
++ getTelemetryLog(ctrl, host, create, data);
++ }
++ break;
++ case NVME_LOG_LID_RESERVATION:
++ {
++ data.resize(sizeof(nvme_resv_notification_log));
++ nvme_resv_notification_log* log =
++ reinterpret_cast<nvme_resv_notification_log*>(
++ data.data());
++
++ int rc =
++ nvme_mi_admin_get_log_reservation(ctrl, false, log);
++ if (rc)
++ {
++ std::cerr << "fail to get reservation "
++ "notification log"
+ << std::endl;
+ throw std::system_error(std::make_error_code(
+ std::errc::invalid_argument));
+ }
+- getTelemetryLogHost(ctrl, create, data);
+- break;
+- default:
++ }
++ break;
++ case NVME_LOG_LID_SANITIZE:
++ {
++ data.resize(sizeof(nvme_sanitize_log_page));
++ nvme_sanitize_log_page* log =
++ reinterpret_cast<nvme_sanitize_log_page*>(
++ data.data());
++
++ int rc =
++ nvme_mi_admin_get_log_sanitize(ctrl, false, log);
++ if (rc)
+ {
+- std::cerr << "unknown lid for GetLogPage"
++ std::cerr << "fail to get sanitize status log"
+ << std::endl;
+ throw std::system_error(std::make_error_code(
+ std::errc::invalid_argument));
+ }
+ }
++ break;
++ default:
++ {
++ std::cerr << "unknown lid for GetLogPage" << std::endl;
++ throw std::system_error(
++ std::make_error_code(std::errc::invalid_argument));
++ }
+ }
+ }
+ catch (const std::system_error& e)
+diff --git a/src/NVMeMi.hpp b/src/NVMeMi.hpp
+index 78ebec9..7608f85 100644
+--- a/src/NVMeMi.hpp
++++ b/src/NVMeMi.hpp
+@@ -36,6 +36,10 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+ std::span<uint8_t>)>&& cb) override;
+
+ private:
++ // the transfer size for nvme mi messages.
++ // define in github.com/linux-nvme/libnvme/blob/master/src/nvme/mi.c
++ static constexpr int nvme_mi_xfer_size = 4096;
++
+ static nvme_root_t nvmeRoot;
+
+ boost::asio::io_context& io;
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0008-Add-Identify-method-to-NVMeAdmin-interface.patch b/recipes-phosphor/sensors/dbus-sensors/0008-Add-Identify-method-to-NVMeAdmin-interface.patch
new file mode 100644
index 0000000..bfa3b85
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0008-Add-Identify-method-to-NVMeAdmin-interface.patch
@@ -0,0 +1,62 @@
+From 3c9556e7a6c93af2665cb3face5e4bf01a6c7ff8 Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Thu, 29 Sep 2022 18:47:09 +0000
+Subject: [PATCH 08/34] Add Identify method to NVMeAdmin interface
+
+This method is used to send NVMe admin command `Identify` to the target
+controller. It will return a fd to read the raw data field of the
+NVMe-MI admin response message.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: I5109af51d6e50e53bda2498bf34656fad4dbe557
+---
+ src/NVMeController.cpp | 33 +++++++++++++++++++++++++++++++++
+ 1 file changed, 33 insertions(+)
+
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index 0479723..dc88cf6 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -54,6 +54,39 @@ NVMeController::NVMeController(
+ });
+ return sdbusplus::message::unix_fd{pipe[0]};
+ });
++
++ adminIntf->register_method(
++ "Identify",
++ [nvmeIntf, nvmeCtrl{ctrl}](uint8_t cns, uint16_t cntid, uint32_t nsid) {
++ std::array<int, 2> pipe;
++ if (::pipe(pipe.data()) < 0)
++ {
++ std::cerr << "Identify fails to open pipe: " << std::strerror(errno)
++ << std::endl;
++ throw sdbusplus::xyz::openbmc_project::Common::File::Error::Open();
++ }
++
++ nvmeIntf->adminIdentify(
++ nvmeCtrl, static_cast<nvme_identify_cns>(cns), nsid, cntid,
++ [pipe](const std::error_code& ec, std::span<uint8_t> data) {
++ ::close(pipe[0]);
++ int fd = pipe[1];
++ if (ec)
++ {
++ std::cerr << "fail to Identify: " << ec.message() << std::endl;
++ close(fd);
++ return;
++ }
++ if (write(fd, data.data(), data.size()) < 0)
++ {
++ std::cerr << "Identify fails to write fd: "
++ << std::strerror(errno) << std::endl;
++ };
++ close(fd);
++ });
++ return sdbusplus::message::unix_fd{pipe[0]};
++ });
++
+ adminIntf->initialize();
+ }
+
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0009-nvmesensor-add-adminXfer-for-MI-interface.patch b/recipes-phosphor/sensors/dbus-sensors/0009-nvmesensor-add-adminXfer-for-MI-interface.patch
new file mode 100644
index 0000000..9bf521e
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0009-nvmesensor-add-adminXfer-for-MI-interface.patch
@@ -0,0 +1,160 @@
+From 83360adff6ffe99e383f514a3b890898549f344e Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Tue, 27 Sep 2022 14:09:24 -0700
+Subject: [PATCH 09/34] nvmesensor: add adminXfer for MI interface
+
+The adminXfer is used to transfer arbitrary NVMe-MI admin commands. It
+can be used for any vendor specify commands.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: Ib8222946600b0031db9c36ef4ca820f4ab0dc5c7
+---
+ src/NVMeIntf.hpp | 35 +++++++++++++++++++++++
+ src/NVMeMi.cpp | 73 ++++++++++++++++++++++++++++++++++++++++++++++++
+ src/NVMeMi.hpp | 5 ++++
+ 3 files changed, 113 insertions(+)
+
+diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
+index 0080b3b..9e7b3a4 100644
+--- a/src/NVMeIntf.hpp
++++ b/src/NVMeIntf.hpp
+@@ -67,4 +67,39 @@ class NVMeMiIntf : public NVMeIntf
+ uint8_t lsp, uint16_t lsi,
+ std::function<void(const std::error_code&, std::span<uint8_t>)>&&
+ cb) = 0;
++
++ /**
++ * adminXfer() - Raw admin transfer interface.
++ * @ctrl: controller to send the admin command to
++ * @admin_req: request header
++ * @data: request data payload
++ * @resp_data_offset: offset into request data to retrieve from controller
++ * @cb: callback function after the response received.
++ * @ec: error code
++ * @admin_resp: response header
++ * @resp_data: response data payload
++ *
++ * Performs an arbitrary NVMe Admin command, using the provided request
++ * header, in @admin_req. The requested data is attached by @data, if any.
++ *
++ * On success, @cb will be called and response header and data are stored in
++ * @admin_resp and @resp_data, which has an optional appended payload
++ * buffer. The response data does not include the Admin request header, so 0
++ * represents no payload.
++ *
++ * As with all Admin commands, we can request partial data from the Admin
++ * Response payload, offset by @resp_data_offset. In case of resp_data
++ * contains only partial data of the caller's requirement, a follow-up call
++ * to adminXfer with offset is required.
++ *
++ * See: &struct nvme_mi_admin_req_hdr and &struct nvme_mi_admin_resp_hdr.
++ *
++ * @ec will be returned on failure.
++ */
++ virtual void
++ adminXfer(nvme_mi_ctrl_t ctrl, const nvme_mi_admin_req_hdr& admin_req,
++ std::span<uint8_t> data,
++ std::function<void(const std::error_code& ec,
++ const nvme_mi_admin_resp_hdr& admin_resp,
++ std::span<uint8_t> resp_data)>&& cb) = 0;
+ };
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index 8797d8e..f66ec57 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -581,3 +581,76 @@ void NVMeMi::adminGetLogPage(
+ return;
+ }
+ }
++
++void NVMeMi::adminXfer(
++ nvme_mi_ctrl_t ctrl, const nvme_mi_admin_req_hdr& admin_req,
++ std::span<uint8_t> data,
++ std::function<void(const std::error_code&, const nvme_mi_admin_resp_hdr&,
++ std::span<uint8_t>)>&& cb)
++{
++ if (!nvmeEP)
++ {
++ std::cerr << "nvme endpoint is invalid" << std::endl;
++ io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(std::errc::no_such_device), {}, {});
++ });
++ return;
++ }
++ try
++ {
++ std::vector<uint8_t> req(sizeof(nvme_mi_admin_req_hdr) + data.size());
++ memcpy(req.data(), &admin_req, sizeof(nvme_mi_admin_req_hdr));
++ memcpy(req.data() + sizeof(nvme_mi_admin_req_hdr), data.data(),
++ data.size());
++ post([ctrl, req{std::move(req)}, self{shared_from_this()},
++ cb{std::move(cb)}]() mutable {
++ int rc = 0;
++
++ nvme_mi_admin_req_hdr* reqHeader =
++ reinterpret_cast<nvme_mi_admin_req_hdr*>(req.data());
++
++ size_t respDataSize =
++ boost::endian::little_to_native<size_t>(reqHeader->dlen);
++ off_t respDataOffset =
++ boost::endian::little_to_native<off_t>(reqHeader->doff);
++ size_t bufSize = sizeof(nvme_mi_admin_resp_hdr) + respDataSize;
++ std::vector<uint8_t> buf(bufSize);
++ nvme_mi_admin_resp_hdr* respHeader =
++ reinterpret_cast<nvme_mi_admin_resp_hdr*>(buf.data());
++
++ rc = nvme_mi_admin_xfer(ctrl, reqHeader,
++ req.size() - sizeof(nvme_mi_admin_req_hdr),
++ respHeader, respDataOffset, &respDataSize);
++
++ if (rc)
++ {
++ std::cerr << "failed to nvme_mi_admin_xfer" << std::endl;
++ self->io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(static_cast<std::errc>(errno)), {},
++ {});
++ });
++ return;
++ }
++ // the MI interface will only consume protocol/io errors
++ // The client will take the reponsibility to deal with nvme-mi
++ // status flag and nvme status field(cwd3). cmd specific return
++ // value (cdw0) is also client's job.
++
++ buf.resize(sizeof(nvme_mi_admin_resp_hdr) + respDataSize);
++ self->io.post([cb{std::move(cb)}, data{std::move(buf)}]() mutable {
++ std::span<uint8_t> span(
++ data.begin() + sizeof(nvme_mi_admin_resp_hdr), data.end());
++ cb({}, *reinterpret_cast<nvme_mi_admin_resp_hdr*>(data.data()),
++ span);
++ });
++ });
++ }
++ catch (const std::runtime_error& e)
++ {
++ std::cerr << e.what() << std::endl;
++ io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(std::errc::no_such_device), {}, {});
++ });
++ return;
++ }
++}
+diff --git a/src/NVMeMi.hpp b/src/NVMeMi.hpp
+index 7608f85..694de7a 100644
+--- a/src/NVMeMi.hpp
++++ b/src/NVMeMi.hpp
+@@ -34,6 +34,11 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+ uint32_t nsid, uint8_t lsp, uint16_t lsi,
+ std::function<void(const std::error_code&,
+ std::span<uint8_t>)>&& cb) override;
++ void adminXfer(nvme_mi_ctrl_t ctrl, const nvme_mi_admin_req_hdr& admin_req,
++ std::span<uint8_t> data,
++ std::function<void(const std::error_code&,
++ const nvme_mi_admin_resp_hdr&,
++ std::span<uint8_t>)>&& cb) override;
+
+ private:
+ // the transfer size for nvme mi messages.
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0010-nvmesensor-handle-libnvme-mi-status-return-code.patch b/recipes-phosphor/sensors/dbus-sensors/0010-nvmesensor-handle-libnvme-mi-status-return-code.patch
new file mode 100644
index 0000000..660af01
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0010-nvmesensor-handle-libnvme-mi-status-return-code.patch
@@ -0,0 +1,585 @@
+From 41c8ceecc86d720635b18ecf03af60a9294caabb Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Wed, 28 Sep 2022 18:23:29 +0000
+Subject: [PATCH 10/34] nvmesensor: handle libnvme-mi status return code
+
+libnvme-mi return status by returning a positive rc. Previously it only
+handles the negitive rc with errno.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: I7d8f56751a4b8594ebadbef364309d5e9b8ff52f
+---
+ src/NVMeIntf.hpp | 48 ++++++
+ src/NVMeMi.cpp | 387 +++++++++++++++++++++++++----------------------
+ 2 files changed, 257 insertions(+), 178 deletions(-)
+
+diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
+index 9e7b3a4..d34ac3e 100644
+--- a/src/NVMeIntf.hpp
++++ b/src/NVMeIntf.hpp
+@@ -48,6 +48,54 @@ class NVMeBasicIntf : public NVMeIntf
+ class NVMeMiIntf : public NVMeIntf
+ {
+ public:
++ constexpr static std::string_view statusToString(nvme_mi_resp_status status)
++ {
++ switch (status)
++ {
++ case NVME_MI_RESP_SUCCESS:
++ return "success";
++ case NVME_MI_RESP_MPR:
++ return "More Processing Required";
++ case NVME_MI_RESP_INTERNAL_ERR:
++ return "Internal Error";
++ case NVME_MI_RESP_INVALID_OPCODE:
++ return "Invalid command opcode";
++ case NVME_MI_RESP_INVALID_PARAM:
++ return "Invalid command parameter";
++ case NVME_MI_RESP_INVALID_CMD_SIZE:
++ return "Invalid command size";
++ case NVME_MI_RESP_INVALID_INPUT_SIZE:
++ return "Invalid command input data size";
++ case NVME_MI_RESP_ACCESS_DENIED:
++ return "Access Denied";
++ case NVME_MI_RESP_VPD_UPDATES_EXCEEDED:
++ return "More VPD updates than allowed";
++ case NVME_MI_RESP_PCIE_INACCESSIBLE:
++ return "PCIe functionality currently unavailable";
++ case NVME_MI_RESP_MEB_SANITIZED:
++ return "MEB has been cleared due to sanitize";
++ case NVME_MI_RESP_ENC_SERV_FAILURE:
++ return "Enclosure services process failed";
++ case NVME_MI_RESP_ENC_SERV_XFER_FAILURE:
++ return "Transfer with enclosure services failed";
++ case NVME_MI_RESP_ENC_FAILURE:
++ return "Unreoverable enclosure failure";
++ case NVME_MI_RESP_ENC_XFER_REFUSED:
++ return "Enclosure services transfer refused";
++ case NVME_MI_RESP_ENC_FUNC_UNSUP:
++ return "Unsupported enclosure services function";
++ case NVME_MI_RESP_ENC_SERV_UNAVAIL:
++ return "Enclosure services unavailable";
++ case NVME_MI_RESP_ENC_DEGRADED:
++ return "Noncritical failure detected by enc. services";
++ case NVME_MI_RESP_SANITIZE_IN_PROGRESS:
++ return "Command prohibited during sanitize";
++ default:
++ return "";
++ }
++ return "";
++ }
++
+ virtual int getNID() const = 0;
+ virtual int getEID() const = 0;
+ virtual void miSubsystemHealthStatusPoll(
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index f66ec57..f597d8c 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -80,7 +80,6 @@ NVMeMi::NVMeMi(boost::asio::io_context& io, sdbusplus::bus_t& dbus, int bus,
+ break;
+ }
+ }
+-
+ }
+ });
+ }
+@@ -139,7 +138,7 @@ void NVMeMi::miSubsystemHealthStatusPoll(
+ nvme_mi_nvm_ss_health_status ss_health;
+ auto rc = nvme_mi_mi_subsystem_health_status_poll(self->nvmeEP,
+ true, &ss_health);
+- if (rc)
++ if (rc < 0)
+ {
+
+ std::cerr << "fail to subsystem_health_status_poll: "
+@@ -150,6 +149,18 @@ void NVMeMi::miSubsystemHealthStatusPoll(
+ });
+ return;
+ }
++ else if (rc > 0)
++ {
++ std::string_view errMsg =
++ statusToString(static_cast<nvme_mi_resp_status>(rc));
++ std::cerr << "fail to subsystem_health_status_poll: " << errMsg
++ << std::endl;
++ self->io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(std::errc::bad_message), nullptr);
++ });
++ return;
++ }
++
+ self->io.post(
+ [cb{std::move(cb)}, ss_health{std::move(ss_health)}]() mutable {
+ cb({}, &ss_health);
+@@ -184,9 +195,21 @@ void NVMeMi::miScanCtrl(std::function<void(const std::error_code&,
+ {
+ post([self{shared_from_this()}, cb{std::move(cb)}]() {
+ int rc = nvme_mi_scan_ep(self->nvmeEP, true);
+- if (rc)
++ if (rc < 0)
++ {
++ std::cerr << "fail to scan controllers: "
++ << std::strerror(errno) << std::endl;
++ self->io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(static_cast<std::errc>(errno)), {});
++ });
++ return;
++ }
++ else if (rc > 0)
+ {
+- std::cerr << "fail to scan controllers" << std::endl;
++ std::string_view errMsg =
++ statusToString(static_cast<nvme_mi_resp_status>(rc));
++ std::cerr << "fail to scan controllers: " << errMsg
++ << std::endl;
+ self->io.post([cb{std::move(cb)}]() {
+ cb(std::make_error_code(std::errc::bad_message), {});
+ });
+@@ -272,7 +295,7 @@ void NVMeMi::adminIdentify(
+ rc = nvme_mi_admin_identify(ctrl, &args);
+ }
+ }
+- if (rc)
++ if (rc < 0)
+ {
+ std::cerr << "fail to do nvme identify: "
+ << std::strerror(errno) << std::endl;
+@@ -281,6 +304,17 @@ void NVMeMi::adminIdentify(
+ });
+ return;
+ }
++ else if (rc > 0)
++ {
++ std::string_view errMsg =
++ statusToString(static_cast<nvme_mi_resp_status>(rc));
++ std::cerr << "fail to do nvme identify: " << errMsg
++ << std::endl;
++ self->io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(std::errc::bad_message), {});
++ });
++ return;
++ }
+
+ self->io.post([cb{std::move(cb)}, data{std::move(data)}]() mutable {
+ std::span<uint8_t> span{data.data(), data.size()};
+@@ -307,8 +341,8 @@ static int nvme_mi_admin_get_log_telemetry_host_rae(nvme_mi_ctrl_t ctrl,
+
+ // Get Temetery Log header and return the size for hdr + data area (Area 1, 2,
+ // 3, or maybe 4)
+-void getTelemetryLog(nvme_mi_ctrl_t ctrl, bool host, bool create,
+- std::vector<uint8_t>& data)
++int getTelemetryLog(nvme_mi_ctrl_t ctrl, bool host, bool create,
++ std::vector<uint8_t>& data)
+ {
+ int rc = 0;
+ data.resize(sizeof(nvme_telemetry_log));
+@@ -324,19 +358,18 @@ void getTelemetryLog(nvme_mi_ctrl_t ctrl, bool host, bool create,
+ if (rc)
+ {
+ std::cerr << "failed to create telemetry host log" << std::endl;
+- throw std::system_error(errno, std::generic_category());
++ return rc;
+ }
+- return;
+- }
+- else
+- {
+- rc = func(ctrl, false, 0, sizeof(log), &log);
++ return 0;
+ }
++
++ rc = func(ctrl, false, 0, sizeof(log), &log);
++
+ if (rc)
+ {
+ std::cerr << "failed to retain telemetry log for "
+ << (host ? "host" : "ctrl") << std::endl;
+- throw std::system_error(errno, std::generic_category());
++ return rc;
+ }
+
+ long size =
+@@ -349,8 +382,9 @@ void getTelemetryLog(nvme_mi_ctrl_t ctrl, bool host, bool create,
+ {
+ std::cerr << "failed to get full telemetry log for "
+ << (host ? "host" : "ctrl") << std::endl;
+- throw std::system_error(errno, std::generic_category());
++ return rc;
+ }
++ return 0;
+ }
+
+ void NVMeMi::adminGetLogPage(
+@@ -372,200 +406,197 @@ void NVMeMi::adminGetLogPage(
+ post([ctrl, nsid, lid, lsp, lsi, self{shared_from_this()},
+ cb{std::move(cb)}]() {
+ std::vector<uint8_t> data;
+- try
++
++ int rc = 0;
++ switch (lid)
+ {
+- switch (lid)
++ case NVME_LOG_LID_ERROR:
+ {
+- case NVME_LOG_LID_ERROR:
++ data.resize(nvme_mi_xfer_size);
++ // The number of entries for most recent error logs.
++ // Currently we only do one nvme mi transfer for the
++ // error log to avoid blocking other tasks
++ static constexpr int num =
++ nvme_mi_xfer_size / sizeof(nvme_error_log_page);
++ nvme_error_log_page* log =
++ reinterpret_cast<nvme_error_log_page*>(data.data());
++
++ rc = nvme_mi_admin_get_log_error(ctrl, num, false, log);
++ if (rc)
+ {
+- data.resize(nvme_mi_xfer_size);
+- // The number of entries for most recent error logs.
+- // Currently we only do one nvme mi transfer for the
+- // error log to avoid blocking other tasks
+- static constexpr int num =
+- nvme_mi_xfer_size / sizeof(nvme_error_log_page);
+- nvme_error_log_page* log =
+- reinterpret_cast<nvme_error_log_page*>(data.data());
+-
+- int rc =
+- nvme_mi_admin_get_log_error(ctrl, num, false, log);
+- if (rc)
+- {
+- std::cerr << "fail to get error log" << std::endl;
+- throw std::system_error(std::make_error_code(
+- std::errc::invalid_argument));
+- }
++ std::cerr << "fail to get error log" << std::endl;
++ break;
+ }
+- break;
+- case NVME_LOG_LID_SMART:
++ }
++ break;
++ case NVME_LOG_LID_SMART:
++ {
++ data.resize(sizeof(nvme_smart_log));
++ nvme_smart_log* log =
++ reinterpret_cast<nvme_smart_log*>(data.data());
++ rc = nvme_mi_admin_get_log_smart(ctrl, nsid, false, log);
++ if (rc)
+ {
+- data.resize(sizeof(nvme_smart_log));
+- nvme_smart_log* log =
+- reinterpret_cast<nvme_smart_log*>(data.data());
+- int rc =
+- nvme_mi_admin_get_log_smart(ctrl, nsid, false, log);
+- if (rc)
+- {
+- std::cerr << "fail to get smart log" << std::endl;
+- throw std::system_error(std::make_error_code(
+- std::errc::invalid_argument));
+- }
++ std::cerr << "fail to get smart log" << std::endl;
++ break;
+ }
+- break;
+- case NVME_LOG_LID_FW_SLOT:
++ }
++ break;
++ case NVME_LOG_LID_FW_SLOT:
++ {
++ data.resize(sizeof(nvme_firmware_slot));
++ nvme_firmware_slot* log =
++ reinterpret_cast<nvme_firmware_slot*>(data.data());
++ rc = nvme_mi_admin_get_log_fw_slot(ctrl, false, log);
++ if (rc)
+ {
+- data.resize(sizeof(nvme_firmware_slot));
+- nvme_firmware_slot* log =
+- reinterpret_cast<nvme_firmware_slot*>(data.data());
+- int rc =
+- nvme_mi_admin_get_log_fw_slot(ctrl, false, log);
+- if (rc)
+- {
+- std::cerr << "fail to get firmware slot"
+- << std::endl;
+- throw std::system_error(std::make_error_code(
+- std::errc::invalid_argument));
+- }
++ std::cerr << "fail to get firmware slot" << std::endl;
++ break;
+ }
+- break;
+- case NVME_LOG_LID_CMD_EFFECTS:
++ }
++ break;
++ case NVME_LOG_LID_CMD_EFFECTS:
++ {
++ data.resize(sizeof(nvme_cmd_effects_log));
++ nvme_cmd_effects_log* log =
++ reinterpret_cast<nvme_cmd_effects_log*>(data.data());
++
++ // nvme rev 1.3 doesn't support csi,
++ // set to default csi = NVME_CSI_NVM
++ rc = nvme_mi_admin_get_log_cmd_effects(ctrl, NVME_CSI_NVM,
++ log);
++ if (rc)
+ {
+- data.resize(sizeof(nvme_cmd_effects_log));
+- nvme_cmd_effects_log* log =
+- reinterpret_cast<nvme_cmd_effects_log*>(
+- data.data());
+-
+- // nvme rev 1.3 doesn't support csi,
+- // set to default csi = NVME_CSI_NVM
+- int rc = nvme_mi_admin_get_log_cmd_effects(
+- ctrl, NVME_CSI_NVM, log);
+- if (rc)
+- {
+- std::cerr
+- << "fail to get cmd supported and effects log"
+- << std::endl;
+- throw std::system_error(std::make_error_code(
+- std::errc::invalid_argument));
+- }
++ std::cerr << "fail to get cmd supported and effects log"
++ << std::endl;
++ break;
+ }
+- break;
+- case NVME_LOG_LID_DEVICE_SELF_TEST:
++ }
++ break;
++ case NVME_LOG_LID_DEVICE_SELF_TEST:
++ {
++ data.resize(sizeof(nvme_self_test_log));
++ nvme_self_test_log* log =
++ reinterpret_cast<nvme_self_test_log*>(data.data());
++ rc = nvme_mi_admin_get_log_device_self_test(ctrl, log);
++ if (rc)
+ {
+- data.resize(sizeof(nvme_self_test_log));
+- nvme_self_test_log* log =
+- reinterpret_cast<nvme_self_test_log*>(data.data());
+- int rc =
+- nvme_mi_admin_get_log_device_self_test(ctrl, log);
+- if (rc)
+- {
+- std::cerr << "fail to get device self test log"
+- << std::endl;
+- throw std::system_error(std::make_error_code(
+- std::errc::invalid_argument));
+- }
++ std::cerr << "fail to get device self test log"
++ << std::endl;
++ break;
+ }
+- break;
+- case NVME_LOG_LID_CHANGED_NS:
++ }
++ break;
++ case NVME_LOG_LID_CHANGED_NS:
++ {
++ data.resize(sizeof(nvme_ns_list));
++ nvme_ns_list* log =
++ reinterpret_cast<nvme_ns_list*>(data.data());
++ rc =
++ nvme_mi_admin_get_log_changed_ns_list(ctrl, false, log);
++ if (rc)
+ {
+- data.resize(sizeof(nvme_ns_list));
+- nvme_ns_list* log =
+- reinterpret_cast<nvme_ns_list*>(data.data());
+- int rc = nvme_mi_admin_get_log_changed_ns_list(
+- ctrl, false, log);
+- if (rc)
+- {
+- std::cerr << "fail to get changed namespace list"
+- << std::endl;
+- throw std::system_error(std::make_error_code(
+- std::errc::invalid_argument));
+- }
++ std::cerr << "fail to get changed namespace list"
++ << std::endl;
++ break;
+ }
+- break;
+- case NVME_LOG_LID_TELEMETRY_HOST:
+- // fall through to NVME_LOG_LID_TELEMETRY_CTRL
+- case NVME_LOG_LID_TELEMETRY_CTRL:
++ }
++ break;
++ case NVME_LOG_LID_TELEMETRY_HOST:
++ // fall through to NVME_LOG_LID_TELEMETRY_CTRL
++ case NVME_LOG_LID_TELEMETRY_CTRL:
++ {
++ bool host = false;
++ bool create = false;
++ if (lid == NVME_LOG_LID_TELEMETRY_HOST)
+ {
+- bool host = false;
+- bool create = false;
+- if (lid == NVME_LOG_LID_TELEMETRY_HOST)
++ host = true;
++ if (lsp == NVME_LOG_TELEM_HOST_LSP_CREATE)
+ {
+- host = true;
+- if (lsp == NVME_LOG_TELEM_HOST_LSP_CREATE)
+- {
+- create = true;
+- }
+- else if (lsp == NVME_LOG_TELEM_HOST_LSP_RETAIN)
+- {
+- create = false;
+- }
+- else
+- {
+- std::cerr
+- << "invalid lsp for telemetry host log"
+- << std::endl;
+- throw std::system_error(std::make_error_code(
+- std::errc::invalid_argument));
+- }
++ create = true;
+ }
+- else
++ else if (lsp == NVME_LOG_TELEM_HOST_LSP_RETAIN)
+ {
+- host = false;
++ create = false;
+ }
+-
+- getTelemetryLog(ctrl, host, create, data);
+- }
+- break;
+- case NVME_LOG_LID_RESERVATION:
+- {
+- data.resize(sizeof(nvme_resv_notification_log));
+- nvme_resv_notification_log* log =
+- reinterpret_cast<nvme_resv_notification_log*>(
+- data.data());
+-
+- int rc =
+- nvme_mi_admin_get_log_reservation(ctrl, false, log);
+- if (rc)
++ else
+ {
+- std::cerr << "fail to get reservation "
+- "notification log"
++ std::cerr << "invalid lsp for telemetry host log"
+ << std::endl;
+- throw std::system_error(std::make_error_code(
+- std::errc::invalid_argument));
++ rc = -1;
++ errno = EINVAL;
++ break;
+ }
+ }
+- break;
+- case NVME_LOG_LID_SANITIZE:
++ else
+ {
+- data.resize(sizeof(nvme_sanitize_log_page));
+- nvme_sanitize_log_page* log =
+- reinterpret_cast<nvme_sanitize_log_page*>(
+- data.data());
+-
+- int rc =
+- nvme_mi_admin_get_log_sanitize(ctrl, false, log);
+- if (rc)
+- {
+- std::cerr << "fail to get sanitize status log"
+- << std::endl;
+- throw std::system_error(std::make_error_code(
+- std::errc::invalid_argument));
+- }
++ host = false;
+ }
+- break;
+- default:
++
++ rc = getTelemetryLog(ctrl, host, create, data);
++ }
++ break;
++ case NVME_LOG_LID_RESERVATION:
++ {
++ data.resize(sizeof(nvme_resv_notification_log));
++ nvme_resv_notification_log* log =
++ reinterpret_cast<nvme_resv_notification_log*>(
++ data.data());
++
++ int rc =
++ nvme_mi_admin_get_log_reservation(ctrl, false, log);
++ if (rc)
++ {
++ std::cerr << "fail to get reservation "
++ "notification log"
++ << std::endl;
++ break;
++ }
++ }
++ break;
++ case NVME_LOG_LID_SANITIZE:
++ {
++ data.resize(sizeof(nvme_sanitize_log_page));
++ nvme_sanitize_log_page* log =
++ reinterpret_cast<nvme_sanitize_log_page*>(data.data());
++
++ int rc = nvme_mi_admin_get_log_sanitize(ctrl, false, log);
++ if (rc)
+ {
+- std::cerr << "unknown lid for GetLogPage" << std::endl;
+- throw std::system_error(
+- std::make_error_code(std::errc::invalid_argument));
++ std::cerr << "fail to get sanitize status log"
++ << std::endl;
++ break;
+ }
+ }
++ break;
++ default:
++ {
++ std::cerr << "unknown lid for GetLogPage" << std::endl;
++ rc = -1;
++ errno = EINVAL;
++ }
+ }
+- catch (const std::system_error& e)
++
++ if (rc < 0)
+ {
+- self->io.post(
+- [cb{std::move(cb)}, ec = e.code()]() { cb(ec, {}); });
++ std::cerr << "fail to get log page: " << std::strerror(errno)
++ << std::endl;
++ self->io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(static_cast<std::errc>(errno)), {});
++ });
+ return;
+ }
++ else if (rc > 0)
++ {
++ std::string_view errMsg =
++ statusToString(static_cast<nvme_mi_resp_status>(rc));
++ std::cerr << "fail to get log pag: " << errMsg << std::endl;
++ self->io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(std::errc::bad_message), {});
++ return;
++ });
++ }
++
+ self->io.post([cb{std::move(cb)}, data{std::move(data)}]() mutable {
+ std::span<uint8_t> span{data.data(), data.size()};
+ cb({}, span);
+@@ -622,7 +653,7 @@ void NVMeMi::adminXfer(
+ req.size() - sizeof(nvme_mi_admin_req_hdr),
+ respHeader, respDataOffset, &respDataSize);
+
+- if (rc)
++ if (rc < 0)
+ {
+ std::cerr << "failed to nvme_mi_admin_xfer" << std::endl;
+ self->io.post([cb{std::move(cb)}]() {
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0011-nvmesensor-handle-the-broken-pipe-signal.patch b/recipes-phosphor/sensors/dbus-sensors/0011-nvmesensor-handle-the-broken-pipe-signal.patch
new file mode 100644
index 0000000..290bf37
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0011-nvmesensor-handle-the-broken-pipe-signal.patch
@@ -0,0 +1,36 @@
+From bd0fc8a47f7ab381d4368d9ea68ab1bfc45bd43e Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Mon, 28 Nov 2022 23:28:16 +0000
+Subject: [PATCH 11/34] nvmesensor: handle the broken pipe signal
+
+The NVMe controller uses pipe to transfer raw data. The pipe could
+be closed by the client. It should not be considered as an error.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: Ia0e2538674ff3d4caa1e13662a6187eb45ed682f
+---
+ src/NVMeSensorMain.cpp | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+diff --git a/src/NVMeSensorMain.cpp b/src/NVMeSensorMain.cpp
+index d85dac7..d04d3e5 100644
+--- a/src/NVMeSensorMain.cpp
++++ b/src/NVMeSensorMain.cpp
+@@ -276,5 +276,14 @@ int main()
+ });
+
+ setupManufacturingModeMatch(*systemBus);
++
++ // The NVMe controller used pipe to transfer raw data. The pipe could be
++ // closed by the client. It should not be considered as an error.
++ boost::asio::signal_set signals(io, SIGPIPE);
++ signals.async_wait(
++ [](const boost::system::error_code& error, int signal_number) {
++ std::cerr << "signal: " << strsignal(signal_number) << ", "
++ << error.message() << std::endl;
++ });
+ io.run();
+ }
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0012-nvmesensor-Add-FirmwareCommit-to-nvme-daemon.patch b/recipes-phosphor/sensors/dbus-sensors/0012-nvmesensor-Add-FirmwareCommit-to-nvme-daemon.patch
new file mode 100644
index 0000000..47c1dd4
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0012-nvmesensor-Add-FirmwareCommit-to-nvme-daemon.patch
@@ -0,0 +1,278 @@
+From 9695c31c9d0c9261cdaafaad0027c3c5a80cd95e Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Fri, 21 Oct 2022 23:25:15 +0000
+Subject: [PATCH 12/34] nvmesensor: Add FirmwareCommit to nvme daemon
+
+The FirmwareCommit is to send nvme_mi_admin_fw_commit cmd to nvme
+device. The attribute "FirmwareCommitStatus" show the progress stauts of
+the commit cmd.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: I6fbb2f65144ef8ab6827fd26cc1d546038b2d851
+---
+ src/NVMeController.cpp | 93 ++++++++++++++++++++++++++++++++++++++++++
+ src/NVMeController.hpp | 12 ++++++
+ src/NVMeIntf.hpp | 4 ++
+ src/NVMeMi.cpp | 73 +++++++++++++++++++++++++++++++++
+ src/NVMeMi.hpp | 6 +++
+ 5 files changed, 188 insertions(+)
+
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index dc88cf6..219b83d 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -2,6 +2,7 @@
+
+ #include <sdbusplus/message/native_types.hpp>
+ #include <xyz/openbmc_project/Common/File/error.hpp>
++#include <xyz/openbmc_project/Common/error.hpp>
+
+ #include <cstdio>
+ #include <filesystem>
+@@ -87,6 +88,50 @@ NVMeController::NVMeController(
+ return sdbusplus::message::unix_fd{pipe[0]};
+ });
+
++ auto& commitStatus = this->commitStatus;
++ commitStatus = FwCommitStatus::Ready;
++ adminIntf->register_property_r("FirmwareCommitStatus", commitStatus,
++ sdbusplus::vtable::property_::emits_change,
++ [&commitStatus](const auto&) {
++ if (commitStatus != FwCommitStatus::Ready &&
++ commitStatus != FwCommitStatus::InProgress)
++ {
++ auto tmp = commitStatus;
++ commitStatus = FwCommitStatus::Ready;
++ return tmp;
++ }
++ return commitStatus;
++ });
++
++ adminIntf->register_method(
++ "FirmwareCommitAsync",
++ [nvmeIntf, nvmeCtrl{ctrl},
++ &commitStatus](uint8_t commitAction, uint8_t firmwareSlot, bool bpid) {
++ if (commitStatus == FwCommitStatus::InProgress)
++ {
++ throw sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed();
++ }
++ commitStatus = FwCommitStatus::InProgress;
++ nvmeIntf->adminFwCommit(
++ nvmeCtrl, static_cast<nvme_fw_commit_ca>(commitAction & 0b111),
++ firmwareSlot, bpid,
++ [&commitStatus](const std::error_code& ec,
++ nvme_status_field status) {
++ if (ec)
++ {
++ commitStatus = FwCommitStatus::Failed;
++ return;
++ }
++ if (status != NVME_SC_SUCCESS)
++ {
++ commitStatus = FwCommitStatus::RequireReset;
++ return;
++ }
++
++ commitStatus = FwCommitStatus::Success;
++ });
++ });
++
+ adminIntf->initialize();
+ }
+
+@@ -123,3 +168,51 @@ void NVMeController::setSecAssoc(
+ secAssoc->register_property("Associations", associations);
+ secAssoc->initialize();
+ }
++
++namespace sdbusplus::message::details
++{
++const static std::unordered_map<NVMeController::FwCommitStatus, std::string>
++ mapCommitStatusString{
++ {NVMeController::FwCommitStatus::Ready, "Ready"},
++ {NVMeController::FwCommitStatus::InProgress, "In Progress"},
++ {NVMeController::FwCommitStatus::Failed, "Failed"},
++ {NVMeController::FwCommitStatus::RequireReset, "Request Reset"},
++ {NVMeController::FwCommitStatus::Success, "Success"}};
++const static std::unordered_map<std::string, NVMeController::FwCommitStatus>
++ mapStringCommitStatus{
++ {"Ready", NVMeController::FwCommitStatus::Ready},
++ {"In Progress", NVMeController::FwCommitStatus::InProgress},
++ {"Failed", NVMeController::FwCommitStatus::Failed},
++ {"Request Reset", NVMeController::FwCommitStatus::RequireReset},
++ {"Success", NVMeController::FwCommitStatus::Success}};
++
++template <>
++struct convert_from_string<NVMeController::FwCommitStatus>
++{
++ static std::optional<NVMeController::FwCommitStatus>
++ op(const std::string& string) noexcept
++ {
++ auto find = mapStringCommitStatus.find(string);
++ if (find == mapStringCommitStatus.end())
++ {
++ return {};
++ }
++ return {find->second};
++ }
++};
++
++template <>
++struct convert_to_string<NVMeController::FwCommitStatus>
++{
++ static std::string op(NVMeController::FwCommitStatus status)
++ {
++ auto find = mapCommitStatusString.find(status);
++ if (find == mapCommitStatusString.end())
++ {
++ return {};
++ }
++ return find->second;
++ }
++};
++
++} // namespace sdbusplus::message::details
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+index 18c3389..db2e404 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -18,6 +18,15 @@ class NVMeController :
+
+ {
+ public:
++ enum class FwCommitStatus : uint8_t
++ {
++ Ready,
++ InProgress,
++ Failed,
++ RequireReset,
++ Success,
++ };
++
+ NVMeController(boost::asio::io_context& io,
+ sdbusplus::asio::object_server& objServer,
+ std::shared_ptr<sdbusplus::asio::connection> conn,
+@@ -56,4 +65,7 @@ class NVMeController :
+ "xyz.openbmc_project.Nvme.NVMeAdmin";
+ static constexpr char adminDataPath[] = "/run/initramfs";
+ std::shared_ptr<sdbusplus::asio::dbus_interface> adminIntf;
++
++ // FW update status
++ FwCommitStatus commitStatus;
+ };
+diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
+index d34ac3e..e9060ab 100644
+--- a/src/NVMeIntf.hpp
++++ b/src/NVMeIntf.hpp
+@@ -115,6 +115,10 @@ class NVMeMiIntf : public NVMeIntf
+ uint8_t lsp, uint16_t lsi,
+ std::function<void(const std::error_code&, std::span<uint8_t>)>&&
+ 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&,
++ nvme_status_field)>&& cb) = 0;
+
+ /**
+ * adminXfer() - Raw admin transfer interface.
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index f597d8c..0cd9ebc 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -685,3 +685,76 @@ void NVMeMi::adminXfer(
+ return;
+ }
+ }
++
++void NVMeMi::adminFwCommit(
++ nvme_mi_ctrl_t ctrl, nvme_fw_commit_ca action, uint8_t slot, bool bpid,
++ std::function<void(const std::error_code&, nvme_status_field)>&& cb)
++{
++ if (!nvmeEP)
++ {
++ std::cerr << "nvme endpoint is invalid" << std::endl;
++ io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(std::errc::no_such_device),
++ nvme_status_field::NVME_SC_MASK);
++ });
++ return;
++ }
++ try
++ {
++ nvme_fw_commit_args args;
++ memset(&args, 0, sizeof(args));
++ args.args_size = sizeof(args);
++ args.action = action;
++ args.slot = slot;
++ args.bpid = bpid;
++ io.post([ctrl, args, cb{std::move(cb)},
++ self{shared_from_this()}]() mutable {
++ int rc = nvme_mi_admin_fw_commit(ctrl, &args);
++ if (rc < 0)
++ {
++
++ std::cerr << "fail to nvme_mi_admin_fw_commit: "
++ << std::strerror(errno) << std::endl;
++ self->io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(static_cast<std::errc>(errno)),
++ nvme_status_field::NVME_SC_MASK);
++ });
++ return;
++ }
++ else if (rc >= 0)
++ {
++ switch (rc & 0x7ff)
++ {
++ case NVME_SC_SUCCESS:
++ case NVME_SC_FW_NEEDS_CONV_RESET:
++ case NVME_SC_FW_NEEDS_SUBSYS_RESET:
++ case NVME_SC_FW_NEEDS_RESET:
++ self->io.post([rc, cb{std::move(cb)}]() {
++ cb({}, static_cast<nvme_status_field>(rc));
++ });
++ break;
++ default:
++ std::string_view errMsg = statusToString(
++ static_cast<nvme_mi_resp_status>(rc));
++ std::cerr
++ << "fail to nvme_mi_admin_fw_commit: " << errMsg
++ << std::endl;
++ self->io.post([rc, cb{std::move(cb)}]() {
++ cb(std::make_error_code(std::errc::bad_message),
++ static_cast<nvme_status_field>(rc));
++ });
++ }
++ return;
++ }
++ });
++ }
++ catch (const std::runtime_error& e)
++ {
++ std::cerr << e.what() << std::endl;
++ io.post([cb{std::move(cb)}]() {
++ cb(std::make_error_code(std::errc::no_such_device),
++ nvme_status_field::NVME_SC_MASK);
++ });
++ return;
++ }
++}
+diff --git a/src/NVMeMi.hpp b/src/NVMeMi.hpp
+index 694de7a..64f2fc6 100644
+--- a/src/NVMeMi.hpp
++++ b/src/NVMeMi.hpp
+@@ -34,6 +34,12 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+ uint32_t nsid, uint8_t lsp, uint16_t lsi,
+ std::function<void(const std::error_code&,
+ std::span<uint8_t>)>&& cb) override;
++
++ void adminFwCommit(
++ nvme_mi_ctrl_t ctrl, nvme_fw_commit_ca action, uint8_t slot, bool bpid,
++ std::function<void(const std::error_code&, nvme_status_field)>&& cb)
++ override;
++
+ void adminXfer(nvme_mi_ctrl_t ctrl, const nvme_mi_admin_req_hdr& admin_req,
+ std::span<uint8_t> data,
+ std::function<void(const std::error_code&,
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0013-nvmesensor-Using-generated-controller-server.patch b/recipes-phosphor/sensors/dbus-sensors/0013-nvmesensor-Using-generated-controller-server.patch
new file mode 100644
index 0000000..9bfb26b
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0013-nvmesensor-Using-generated-controller-server.patch
@@ -0,0 +1,457 @@
+From 4ed719b22cf47f677412e2ca40cc10e69dc9dba3 Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Tue, 29 Nov 2022 01:48:52 +0000
+Subject: [PATCH 13/34] nvmesensor: Using generated controller server
+
+libnvme-vu include the yaml file for the definition of
+xyz.openbmc_project.NVMe.NVMeAdmin. The server.hpp and server.cpp
+will be gerenated from the yaml. Move the functionility from dynamic
+insertion of sdbusplus::asio to the inheretence of the generated
+classes.
+
+The goals are:
+* a more clear structure by distributing the functions into the
+ overloading instead of a bit chunck of constructor.
+* DBus interface sharing across the server and the client.
+* Get prepared for the future plug-in style functions by
+ splitting the Plug-in class out of NVMeController.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: I13d6f6b639f892faf27ee1f3170a21c8793be3a6
+---
+ src/NVMeController.cpp | 241 ++++++++++++++++--------------------
+ src/NVMeController.hpp | 68 +++++++---
+ src/meson.build | 9 +-
+ subprojects/libnvme-vu.wrap | 6 +
+ 4 files changed, 166 insertions(+), 158 deletions(-)
+ create mode 100644 subprojects/libnvme-vu.wrap
+
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index 219b83d..59e7b88 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -8,29 +8,43 @@
+ #include <filesystem>
+ #include <iostream>
+
++using sdbusplus::xyz::openbmc_project::Inventory::Item::server::
++ StorageController;
++using sdbusplus::xyz::openbmc_project::NVMe::server::NVMeAdmin;
++
+ NVMeController::NVMeController(
+ boost::asio::io_context& io, sdbusplus::asio::object_server& objServer,
+ std::shared_ptr<sdbusplus::asio::connection> conn, std::string path,
+ std::shared_ptr<NVMeMiIntf> nvmeIntf, nvme_mi_ctrl_t ctrl) :
+- ControllerBase(dynamic_cast<sdbusplus::bus_t&>(*conn), path.c_str()),
++ StorageController(dynamic_cast<sdbusplus::bus_t&>(*conn), path.c_str()),
++ NVMeAdmin(*conn, path.c_str(),
++ {{"FirmwareCommitStatus", {FwCommitStatus::Ready}}}),
+ io(io), objServer(objServer), conn(conn), path(path), nvmeIntf(nvmeIntf),
+ nvmeCtrl(ctrl)
+ {
+- emit_added();
+-
+- /* Admin interface */
+- adminIntf = objServer.add_interface(path, adminIntfName);
+- adminIntf->register_method("GetLogPage", [nvmeIntf, nvmeCtrl{ctrl}](
+- unsigned lid, uint32_t nsid,
+- uint8_t lsp, uint16_t lsi) {
+- std::array<int, 2> pipe;
+- if (::pipe(pipe.data()) < 0)
+- {
+- std::cerr << "GetLogPage fails to open pipe: "
+- << std::strerror(errno) << std::endl;
+- throw sdbusplus::xyz::openbmc_project::Common::File::Error::Open();
+- }
++ StorageController::emit_added();
++ NVMeAdmin::emit_added();
++}
++
++void NVMeController::start()
++{}
++
++sdbusplus::message::unix_fd NVMeController::getLogPage(uint8_t lid,
++ uint32_t nsid,
++ uint8_t lsp,
++ uint16_t lsi)
++{
++ std::array<int, 2> pipe;
++ if (::pipe(pipe.data()) < 0)
++ {
++ std::cerr << "GetLogPage fails to open pipe: " << std::strerror(errno)
++ << std::endl;
++ throw sdbusplus::xyz::openbmc_project::Common::File::Error::Open();
++ }
+
++ // standard NVMe Log IDs
++ if (lid < uint8_t{0xC0})
++ {
+ nvmeIntf->adminGetLogPage(
+ nvmeCtrl, static_cast<nvme_cmd_get_log_lid>(lid), nsid, lsp, lsi,
+ [pipe](const std::error_code& ec, std::span<uint8_t> data) {
+@@ -44,8 +58,8 @@ NVMeController::NVMeController(
+ return;
+ }
+
+- // TODO: evaluate the impact of client not reading fast enough on
+- // large trunk of data
++ // TODO: evaluate the impact of client not reading fast enough
++ // on large trunk of data
+ if (::write(fd, data.data(), data.size()) < 0)
+ {
+ std::cerr << "GetLogPage fails to write fd: "
+@@ -53,92 +67,93 @@ NVMeController::NVMeController(
+ };
+ close(fd);
+ });
+- return sdbusplus::message::unix_fd{pipe[0]};
+- });
+-
+- adminIntf->register_method(
+- "Identify",
+- [nvmeIntf, nvmeCtrl{ctrl}](uint8_t cns, uint16_t cntid, uint32_t nsid) {
+- std::array<int, 2> pipe;
+- if (::pipe(pipe.data()) < 0)
+- {
+- std::cerr << "Identify fails to open pipe: " << std::strerror(errno)
+- << std::endl;
+- throw sdbusplus::xyz::openbmc_project::Common::File::Error::Open();
+- }
++ }
++ // vendor Log IDs
++ else
++ {}
++ return sdbusplus::message::unix_fd{pipe[0]};
++}
+
+- nvmeIntf->adminIdentify(
+- nvmeCtrl, static_cast<nvme_identify_cns>(cns), nsid, cntid,
+- [pipe](const std::error_code& ec, std::span<uint8_t> data) {
+- ::close(pipe[0]);
+- int fd = pipe[1];
+- if (ec)
+- {
+- std::cerr << "fail to Identify: " << ec.message() << std::endl;
+- close(fd);
+- return;
+- }
+- if (write(fd, data.data(), data.size()) < 0)
+- {
+- std::cerr << "Identify fails to write fd: "
+- << std::strerror(errno) << std::endl;
+- };
++sdbusplus::message::unix_fd NVMeController::identify(uint8_t cns, uint32_t nsid,
++ uint16_t cntid)
++{
++ std::array<int, 2> pipe;
++ if (::pipe(pipe.data()) < 0)
++ {
++ std::cerr << "Identify fails to open pipe: " << std::strerror(errno)
++ << std::endl;
++ throw sdbusplus::xyz::openbmc_project::Common::File::Error::Open();
++ }
++
++ nvmeIntf->adminIdentify(
++ nvmeCtrl, static_cast<nvme_identify_cns>(cns), nsid, cntid,
++ [self{shared_from_this()}, pipe](const std::error_code& ec,
++ std::span<uint8_t> data) {
++ ::close(pipe[0]);
++ int fd = pipe[1];
++ if (ec)
++ {
++ std::cerr << "fail to Identify: " << ec.message() << std::endl;
+ close(fd);
+- });
+- return sdbusplus::message::unix_fd{pipe[0]};
++ return;
++ }
++ if (write(fd, data.data(), data.size()) < 0)
++ {
++ std::cerr << "Identify fails to write fd: " << std::strerror(errno)
++ << std::endl;
++ };
++ close(fd);
+ });
++ return sdbusplus::message::unix_fd{pipe[0]};
++}
+
+- auto& commitStatus = this->commitStatus;
+- commitStatus = FwCommitStatus::Ready;
+- adminIntf->register_property_r("FirmwareCommitStatus", commitStatus,
+- sdbusplus::vtable::property_::emits_change,
+- [&commitStatus](const auto&) {
+- if (commitStatus != FwCommitStatus::Ready &&
+- commitStatus != FwCommitStatus::InProgress)
++NVMeAdmin::FwCommitStatus
++ NVMeController::firmwareCommitStatus(NVMeAdmin::FwCommitStatus status)
++{
++ auto commitStatus = this->NVMeAdmin::firmwareCommitStatus();
++ // The function is only allowed to reset the status back to ready
++ if (status != FwCommitStatus::Ready ||
++ commitStatus == FwCommitStatus::Ready ||
++ commitStatus == FwCommitStatus::InProgress)
++ {
++ throw sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed{};
++ }
++ return this->NVMeAdmin::firmwareCommitStatus(status);
++}
++
++void NVMeController::firmwareCommitAsync(uint8_t commitAction,
++ uint8_t firmwareSlot, bool bpid)
++{
++ auto commitStatus = this->NVMeAdmin::firmwareCommitStatus();
++ if (commitStatus != FwCommitStatus::Ready)
++ {
++ throw sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed();
++ }
++ this->NVMeAdmin::firmwareCommitStatus(FwCommitStatus::InProgress);
++ nvmeIntf->adminFwCommit(
++ nvmeCtrl, static_cast<nvme_fw_commit_ca>(commitAction & 0b111),
++ firmwareSlot, bpid,
++ [self{shared_from_this()}](const std::error_code& ec,
++ nvme_status_field status) {
++ if (ec)
+ {
+- auto tmp = commitStatus;
+- commitStatus = FwCommitStatus::Ready;
+- return tmp;
++ self->NVMeAdmin::firmwareCommitStatus(FwCommitStatus::Failed);
++ return;
+ }
+- return commitStatus;
+- });
+-
+- adminIntf->register_method(
+- "FirmwareCommitAsync",
+- [nvmeIntf, nvmeCtrl{ctrl},
+- &commitStatus](uint8_t commitAction, uint8_t firmwareSlot, bool bpid) {
+- if (commitStatus == FwCommitStatus::InProgress)
++ if (status != NVME_SC_SUCCESS)
+ {
+- throw sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed();
++ self->NVMeAdmin::firmwareCommitStatus(FwCommitStatus::RequireReset);
++ return;
+ }
+- commitStatus = FwCommitStatus::InProgress;
+- nvmeIntf->adminFwCommit(
+- nvmeCtrl, static_cast<nvme_fw_commit_ca>(commitAction & 0b111),
+- firmwareSlot, bpid,
+- [&commitStatus](const std::error_code& ec,
+- nvme_status_field status) {
+- if (ec)
+- {
+- commitStatus = FwCommitStatus::Failed;
+- return;
+- }
+- if (status != NVME_SC_SUCCESS)
+- {
+- commitStatus = FwCommitStatus::RequireReset;
+- return;
+- }
+
+- commitStatus = FwCommitStatus::Success;
+- });
++ self->NVMeAdmin::firmwareCommitStatus(FwCommitStatus::Success);
+ });
+-
+- adminIntf->initialize();
+ }
+
+ NVMeController::~NVMeController()
+ {
+- objServer.remove_interface(adminIntf);
+- emit_removed();
++ NVMeAdmin::emit_removed();
++ StorageController::emit_removed();
+ }
+
+ void NVMeController::setSecAssoc(
+@@ -168,51 +183,3 @@ void NVMeController::setSecAssoc(
+ secAssoc->register_property("Associations", associations);
+ secAssoc->initialize();
+ }
+-
+-namespace sdbusplus::message::details
+-{
+-const static std::unordered_map<NVMeController::FwCommitStatus, std::string>
+- mapCommitStatusString{
+- {NVMeController::FwCommitStatus::Ready, "Ready"},
+- {NVMeController::FwCommitStatus::InProgress, "In Progress"},
+- {NVMeController::FwCommitStatus::Failed, "Failed"},
+- {NVMeController::FwCommitStatus::RequireReset, "Request Reset"},
+- {NVMeController::FwCommitStatus::Success, "Success"}};
+-const static std::unordered_map<std::string, NVMeController::FwCommitStatus>
+- mapStringCommitStatus{
+- {"Ready", NVMeController::FwCommitStatus::Ready},
+- {"In Progress", NVMeController::FwCommitStatus::InProgress},
+- {"Failed", NVMeController::FwCommitStatus::Failed},
+- {"Request Reset", NVMeController::FwCommitStatus::RequireReset},
+- {"Success", NVMeController::FwCommitStatus::Success}};
+-
+-template <>
+-struct convert_from_string<NVMeController::FwCommitStatus>
+-{
+- static std::optional<NVMeController::FwCommitStatus>
+- op(const std::string& string) noexcept
+- {
+- auto find = mapStringCommitStatus.find(string);
+- if (find == mapStringCommitStatus.end())
+- {
+- return {};
+- }
+- return {find->second};
+- }
+-};
+-
+-template <>
+-struct convert_to_string<NVMeController::FwCommitStatus>
+-{
+- static std::string op(NVMeController::FwCommitStatus status)
+- {
+- auto find = mapCommitStatusString.find(status);
+- if (find == mapCommitStatusString.end())
+- {
+- return {};
+- }
+- return find->second;
+- }
+-};
+-
+-} // namespace sdbusplus::message::details
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+index db2e404..2ce5d39 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -7,26 +7,18 @@
+ #include <sdbusplus/asio/connection.hpp>
+ #include <sdbusplus/asio/object_server.hpp>
+ #include <xyz/openbmc_project/Inventory/Item/StorageController/server.hpp>
++#include <xyz/openbmc_project/NVMe/NVMeAdmin/server.hpp>
+
+ #include <utility>
+
+-using ControllerBase =
+- sdbusplus::xyz::openbmc_project::Inventory::Item::server::StorageController;
+ class NVMeController :
+- private ControllerBase,
++ private sdbusplus::xyz::openbmc_project::Inventory::Item::server::
++ StorageController,
++ private sdbusplus::xyz::openbmc_project::NVMe::server::NVMeAdmin,
+ public std::enable_shared_from_this<NVMeController>
+
+ {
+ public:
+- enum class FwCommitStatus : uint8_t
+- {
+- Ready,
+- InProgress,
+- Failed,
+- RequireReset,
+- Success,
+- };
+-
+ NVMeController(boost::asio::io_context& io,
+ sdbusplus::asio::object_server& objServer,
+ std::shared_ptr<sdbusplus::asio::connection> conn,
+@@ -35,7 +27,7 @@ class NVMeController :
+
+ ~NVMeController() override;
+
+- void start(){};
++ void start();
+
+ // setup association to the secondary controllers. Clear the Association if
+ // empty.
+@@ -60,12 +52,48 @@ class NVMeController :
+ // controller
+ std::shared_ptr<sdbusplus::asio::dbus_interface> secAssoc;
+
+- // NVMe Admin interface: xyz.openbmc_project.Nvme.NVMeAdmin
+- static constexpr char adminIntfName[] =
+- "xyz.openbmc_project.Nvme.NVMeAdmin";
+- static constexpr char adminDataPath[] = "/run/initramfs";
+- std::shared_ptr<sdbusplus::asio::dbus_interface> adminIntf;
+
+- // FW update status
+- FwCommitStatus commitStatus;
++ /* NVMeAdmin method overload */
++
++ /** @brief Implementation for GetLogPage
++ * Send GetLogPage command to NVMe device
++ *
++ * @param[in] lid - Log Page Identifier
++ * @param[in] nsid - Namespace Identifier
++ * @param[in] lsp - Log Specific Field
++ * @param[in] lsi - Log Specific Identifier
++ *
++ * @return log[sdbusplus::message::unix_fd] - Returned Log Page
++ */
++ sdbusplus::message::unix_fd getLogPage(uint8_t lid, uint32_t nsid,
++ uint8_t lsp, uint16_t lsi) override;
++
++ /** @brief Implementation for Identify
++ * Send Identify command to NVMe device
++ *
++ * @param[in] cns - Controller or Namespace Structure
++ * @param[in] nsid - Namespace Identifier
++ * @param[in] cntid - Controller Identifier
++ *
++ * @return data[sdbusplus::message::unix_fd] - Identify Data
++ */
++ sdbusplus::message::unix_fd identify(uint8_t cns, uint32_t nsid,
++ uint16_t cntid) override;
++ /** Set value of FirmwareCommitStatus
++ * Used to reset the the status back to ready if the commit is not in
++ * process.
++ */
++ NVMeAdmin::FwCommitStatus
++ firmwareCommitStatus(NVMeAdmin::FwCommitStatus) override;
++
++ /** @brief Implementation for FirmwareCommitAsync
++ * Send Firmware Commit command to NVMe device
++ *
++ * @param[in] commitAction - Commit Action defined by NVMe base spec
++ * (Figure 175 of rev 1.4)
++ * @param[in] firmwareSlot - Firmware Slot
++ * @param[in] bpid - Boot Partition ID
++ */
++ void firmwareCommitAsync(uint8_t commitAction, uint8_t firmwareSlot,
++ bool bpid) override;
+ };
+diff --git a/src/meson.build b/src/meson.build
+index 740fdd5..5214ebd 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -207,9 +207,16 @@ if get_option('nvme').enabled()
+ include_type: 'system'
+ )
+
++ libnvme_vu = dependency('libnvme-interface',
++ required : true,
++ fallback : ['libnvme-vu', 'nvme_intf_dep'],
++ include_type: 'system'
++ )
++
+ nvme_deps = [
+ default_deps, i2c, thresholds_dep, utils_dep,
+- threads, pdi_dep, libnvme, libnvme_mi
++ threads, pdi_dep, libnvme, libnvme_mi,
++ libnvme_vu
+ ]
+
+ executable(
+diff --git a/subprojects/libnvme-vu.wrap b/subprojects/libnvme-vu.wrap
+new file mode 100644
+index 0000000..99ef558
+--- /dev/null
++++ b/subprojects/libnvme-vu.wrap
+@@ -0,0 +1,6 @@
++[wrap-git]
++url = https://github.com/drakedog2008/libnvme-vu
++revision = 6951ed340c8eb76528f8575433dbec1e2b1641f5
++
++[provide]
++dependency_names = nvme_intf_dep
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0014-nvmesensor-Add-NVMePlugin.patch b/recipes-phosphor/sensors/dbus-sensors/0014-nvmesensor-Add-NVMePlugin.patch
new file mode 100644
index 0000000..9872f30
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0014-nvmesensor-Add-NVMePlugin.patch
@@ -0,0 +1,282 @@
+From 9e3da3bb6496d7745caac537a8d117210667d549 Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Mon, 28 Nov 2022 23:54:58 +0000
+Subject: [PATCH 14/34] nvmesensor: Add NVMePlugin
+
+The NVmePlugin is used to define vendor unique commands or fields(such
+as VU LogPage ID).
+
+Also enable the VU logpage handler in NVMeController.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: Ib18b7b58dbefcb96c48596d44001d1f2d67fbd4b
+---
+ src/NVMeController.cpp | 28 ++++++++++++--
+ src/NVMeController.hpp | 7 +++-
+ src/NVMePlugin.hpp | 83 ++++++++++++++++++++++++++++++++++++++++++
+ src/NVMeSubsys.cpp | 20 +++++-----
+ src/NVMeSubsys.hpp | 7 +++-
+ 5 files changed, 129 insertions(+), 16 deletions(-)
+ create mode 100644 src/NVMePlugin.hpp
+
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index 59e7b88..20973b7 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -1,5 +1,7 @@
+ #include "NVMeController.hpp"
+
++#include "NVMePlugin.hpp"
++
+ #include <sdbusplus/message/native_types.hpp>
+ #include <xyz/openbmc_project/Common/File/error.hpp>
+ #include <xyz/openbmc_project/Common/error.hpp>
+@@ -26,8 +28,10 @@ NVMeController::NVMeController(
+ NVMeAdmin::emit_added();
+ }
+
+-void NVMeController::start()
+-{}
++void NVMeController::start(std::shared_ptr<NVMePlugin> nvmePlugin)
++{
++ plugin = nvmePlugin;
++}
+
+ sdbusplus::message::unix_fd NVMeController::getLogPage(uint8_t lid,
+ uint32_t nsid,
+@@ -69,8 +73,24 @@ sdbusplus::message::unix_fd NVMeController::getLogPage(uint8_t lid,
+ });
+ }
+ // vendor Log IDs
+- else
+- {}
++ else if (!plugin.expired())
++ {
++ auto nvmePlugin = plugin.lock();
++ auto handler = nvmePlugin->getGetLogPageHandler();
++ if (handler)
++ {
++ handler(pipe, lid, nsid, lsp, lsi);
++ }
++ else // No VU LogPage handler
++ {
++ throw sdbusplus::xyz::openbmc_project::Common::Error::
++ InvalidArgument();
++ }
++ }
++ else // No VU plugin
++ {
++ throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
++ }
+ return sdbusplus::message::unix_fd{pipe[0]};
+ }
+
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+index 2ce5d39..92c7142 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -11,6 +11,7 @@
+
+ #include <utility>
+
++class NVMePlugin;
+ class NVMeController :
+ private sdbusplus::xyz::openbmc_project::Inventory::Item::server::
+ StorageController,
+@@ -27,7 +28,7 @@ class NVMeController :
+
+ ~NVMeController() override;
+
+- void start();
++ void start(std::shared_ptr<NVMePlugin> nvmePlugin);
+
+ // setup association to the secondary controllers. Clear the Association if
+ // empty.
+@@ -40,6 +41,8 @@ class NVMeController :
+ }
+
+ private:
++ friend class NVMePlugin;
++
+ boost::asio::io_context& io;
+ sdbusplus::asio::object_server& objServer;
+ std::shared_ptr<sdbusplus::asio::connection> conn;
+@@ -52,6 +55,8 @@ class NVMeController :
+ // controller
+ std::shared_ptr<sdbusplus::asio::dbus_interface> secAssoc;
+
++ // NVMe Plug-in for vendor defined command/field
++ std::weak_ptr<NVMePlugin> plugin;
+
+ /* NVMeAdmin method overload */
+
+diff --git a/src/NVMePlugin.hpp b/src/NVMePlugin.hpp
+new file mode 100644
+index 0000000..97922c5
+--- /dev/null
++++ b/src/NVMePlugin.hpp
+@@ -0,0 +1,83 @@
++#pragma once
++#include <NVMeController.hpp>
++class NVMePlugin
++{
++ public:
++ using getlogpage_t =
++ std::function<void(std::array<int, 2> pipe, uint8_t lid, uint32_t nsid,
++ uint8_t lsp, uint16_t lsi)>;
++ NVMePlugin(std::shared_ptr<NVMeController> cntl) : nvmeController(cntl)
++ {}
++ virtual ~NVMePlugin()
++ {}
++ virtual getlogpage_t getGetLogPageHandler()
++ {
++ return {};
++ }
++
++ protected:
++ const std::string& getPath() const
++ {
++ return nvmeController->path;
++ }
++ sdbusplus::asio::connection& getDbusConnection()
++ {
++ return *nvmeController->conn;
++ }
++
++ boost::asio::io_context& getIOContext()
++ {
++ return nvmeController->io;
++ }
++
++ /**
++ * adminXfer() - transfer Raw admin cmd to the binded conntroller
++ * @admin_req: request header
++ * @data: request data payload
++ * @resp_data_offset: offset into request data to retrieve from controller
++ * @cb: callback function after the response received.
++ * @ec: error code
++ * @admin_resp: response header
++ * @resp_data: response data payload
++ *
++ * Performs an arbitrary NVMe Admin command, using the provided request
++ * header, in @admin_req. The requested data is attached by @data, if any.
++ *
++ * On success, @cb will be called and response header and data are stored in
++ * @admin_resp and @resp_data, which has an optional appended payload
++ * buffer. The response data does not include the Admin request header, so 0
++ * represents no payload.
++ *
++ * As with all Admin commands, we can request partial data from the Admin
++ * Response payload, offset by @resp_data_offset. In case of resp_data
++ * contains only partial data of the caller's requirement, a follow-up call
++ * to adminXfer with offset is required.
++ *
++ * See: &struct nvme_mi_admin_req_hdr and &struct nvme_mi_admin_resp_hdr.
++ *
++ * @ec will be returned on failure.
++ */
++ void adminXfer(const nvme_mi_admin_req_hdr& admin_req,
++ std::span<uint8_t> data,
++ std::function<void(const std::error_code& ec,
++ const nvme_mi_admin_resp_hdr& admin_resp,
++ std::span<uint8_t> resp_data)>&& cb)
++ {
++ nvmeController->nvmeIntf->adminXfer(nvmeController->nvmeCtrl, admin_req,
++ data, std::move(cb));
++ }
++ /**
++ * @brief Get cntrl_id for the binded NVMe controller
++ *
++ * @return cntrl_id
++ */
++ uint16_t getCntrlId()
++ {
++ return *reinterpret_cast<uint16_t*>(
++ (reinterpret_cast<uint8_t*>(nvmeController->nvmeCtrl) +
++ std::max(sizeof(uint16_t), sizeof(void*))));
++ }
++
++ private:
++ std::shared_ptr<NVMeController> nvmeController;
++};
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index b49743f..7b86fb6 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -1,5 +1,6 @@
+ #include "NVMeSubsys.hpp"
+
++#include "NVMePlugin.hpp"
+ #include "Thresholds.hpp"
+
+ #include <filesystem>
+@@ -180,11 +181,12 @@ void NVMeSubsystem::start()
+ std::filesystem::path path = std::filesystem::path(self->path) /
+ "controllers" /
+ std::to_string(*index);
+- auto [ctrl, _] = self->controllers.insert(
+- {*index, std::make_shared<NVMeController>(
+- self->io, self->objServer, self->conn,
+- path.string(), nvme, c)});
+- ctrl->second->start();
++ auto nvmeController = std::make_shared<NVMeController>(
++ self->io, self->objServer, self->conn, path.string(), nvme,
++ c);
++ std::shared_ptr<NVMePlugin> plugin = {};
++ self->controllers.insert({*index, {nvmeController, plugin}});
++ nvmeController->start(plugin);
+
+ index++;
+ }
+@@ -211,9 +213,9 @@ void NVMeSubsystem::start()
+ *reinterpret_cast<nvme_secondary_ctrl_list*>(data.data());
+
+ // Remove all associations
+- for (const auto& [_, cntrl] : self->controllers)
++ for (const auto& [_, pair] : self->controllers)
+ {
+- cntrl->setSecAssoc();
++ pair.first->setSecAssoc();
+ }
+
+ if (listHdr.num == 0)
+@@ -246,9 +248,9 @@ void NVMeSubsystem::start()
+ << std::endl;
+ break;
+ }
+- secCntrls.push_back(findSecondary->second);
++ secCntrls.push_back(findSecondary->second.first);
+ }
+- findPrimary->second->setSecAssoc(secCntrls);
++ findPrimary->second.first->setSecAssoc(secCntrls);
+ });
+ });
+ }
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index 1d51cec..b0c7efa 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -1,6 +1,7 @@
+ #include "NVMeBasic.hpp"
+ #include "NVMeController.hpp"
+ #include "NVMeDrive.hpp"
++#include "NVMePlugin.hpp"
+ #include "NVMeSensor.hpp"
+ #include "NVMeStorage.hpp"
+ #include "Utils.hpp"
+@@ -62,8 +63,10 @@ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+ */
+ NVMeDrive drive;
+
+- // map from cntrlid to controller instances
+- std::map<uint16_t, std::shared_ptr<NVMeController>> controllers{};
++ // map from cntrlid to a pair of {controller, controller_plugin}
++ std::map<uint16_t, std::pair<std::shared_ptr<NVMeController>,
++ std::shared_ptr<NVMePlugin>>>
++ controllers{};
+
+ // implemetation details for the class.
+ // It should contain only static function and using binding to the
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0015-nvmesensor-add-associations.patch b/recipes-phosphor/sensors/dbus-sensors/0015-nvmesensor-add-associations.patch
new file mode 100644
index 0000000..940a773
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0015-nvmesensor-add-associations.patch
@@ -0,0 +1,191 @@
+From 38977ae061e05d1b68da2e313519810c026d53c2 Mon Sep 17 00:00:00 2001
+From: Willy Tu <wltu@google.com>
+Date: Tue, 6 Dec 2022 14:16:32 -0800
+Subject: [PATCH 15/34] nvmesensor: add associations
+
+The BMCWeb uses the associations between
+Chassic/Storage/Drive/StorageController to create the link between
+resources.
+
+Change-Id: I4f5b3da8fc81e4ee0615410e3ad0ccc79b90e0d9
+Signed-off-by: Willy Tu <wltu@google.com>
+---
+ src/NVMeSubsys.cpp | 112 +++++++++++++++++++++++----------------------
+ src/NVMeSubsys.hpp | 3 ++
+ 2 files changed, 61 insertions(+), 54 deletions(-)
+
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index 7b86fb6..0d5af8b 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -59,20 +59,23 @@ std::optional<std::string> createSensorNameFromPath(const std::string& path)
+ return name;
+ }
+
+-void createStorageAssociation(
+- std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
+- const std::string& path)
++void NVMeSubsystem::createStorageAssociation()
+ {
+- if (association)
+- {
+- std::filesystem::path p(path);
+-
+- std::vector<Association> associations;
+- associations.emplace_back("chassis", "storage",
+- p.parent_path().string());
+- association->register_property("Associations", associations);
+- association->initialize();
+- }
++ auto storageAssociation =
++ objServer.add_interface(path, association::interface);
++
++ storageAssociation->register_property(
++ "Associations", associations,
++ // custom set
++ [&](const std::vector<Association>&,
++ std::vector<Association>& propertyValue) {
++ propertyValue = associations;
++ return false;
++ },
++ // custom get
++ [&](const std::vector<Association>&) { return associations; });
++
++ storageAssociation->initialize();
+ }
+
+ // get temporature from a NVMe Basic reading.
+@@ -107,43 +110,41 @@ NVMeSubsystem::NVMeSubsystem(boost::asio::io_context& io,
+ }
+
+ // initiate the common interfaces (thermal sensor, Drive and Storage)
+- if (dynamic_cast<NVMeBasicIntf*>(nvmeIntf.get()) != nullptr ||
+- dynamic_cast<NVMeMiIntf*>(nvmeIntf.get()) != nullptr)
++ if (dynamic_cast<NVMeBasicIntf*>(nvmeIntf.get()) == nullptr &&
++ dynamic_cast<NVMeMiIntf*>(nvmeIntf.get()) == nullptr)
+ {
+- std::optional<std::string> sensorName = createSensorNameFromPath(path);
+- if (!sensorName)
+- {
+- // fail to parse sensor name from path, using name instead.
+- sensorName.emplace(name);
+- }
+-
+- std::vector<thresholds::Threshold> sensorThresholds;
+- if (!parseThresholdsFromConfig(configData, sensorThresholds))
+- {
+- std::cerr << "error populating thresholds for " << *sensorName
+- << "\n";
+- throw std::runtime_error("error populating thresholds for " +
+- *sensorName);
+- }
+-
+- ctemp.emplace(objServer, io, conn, *sensorName,
+- std::move(sensorThresholds), path);
+-
+- /* xyz.openbmc_project.Inventory.Item.Drive */
+- drive.protocol(NVMeDrive::DriveProtocol::NVMe);
+- drive.type(NVMeDrive::DriveType::SSD);
+- // TODO: update capacity
+-
+- /* xyz.openbmc_project.Inventory.Item.Storage */
+- // make association to chassis
+- auto storageAssociation =
+- objServer.add_interface(path, association::interface);
+- createStorageAssociation(storageAssociation, path);
++ throw std::runtime_error("Unsupported NVMe interface");
+ }
+- else
++
++ std::optional<std::string> sensorName = createSensorNameFromPath(path);
++ if (!sensorName)
+ {
+- throw std::runtime_error("Unsupported NVMe interface");
++ // fail to parse sensor name from path, using name instead.
++ sensorName.emplace(name);
++ }
++
++ std::vector<thresholds::Threshold> sensorThresholds;
++ if (!parseThresholdsFromConfig(configData, sensorThresholds))
++ {
++ std::cerr << "error populating thresholds for " << *sensorName << "\n";
++ throw std::runtime_error("error populating thresholds for " +
++ *sensorName);
+ }
++
++ ctemp.emplace(objServer, io, conn, *sensorName, std::move(sensorThresholds),
++ path);
++
++ /* xyz.openbmc_project.Inventory.Item.Drive */
++ drive.protocol(NVMeDrive::DriveProtocol::NVMe);
++ drive.type(NVMeDrive::DriveType::SSD);
++ // TODO: update capacity
++
++ /* xyz.openbmc_project.Inventory.Item.Storage */
++ // make association for Drive/Storage/Chassis
++ std::filesystem::path p(path);
++ associations.emplace_back("chassis", "storage", p.parent_path().string());
++ associations.emplace_back("chassis", "drive", p.parent_path().string());
++ associations.emplace_back("drive", "storage", path);
+ }
+
+ void NVMeSubsystem::start()
+@@ -160,6 +161,7 @@ void NVMeSubsystem::start()
+ // TODO: mark the subsystem invalid and reschedule refresh
+ std::cerr << "fail to scan controllers for the nvme subsystem"
+ << (ec ? ": " + ec.message() : "") << std::endl;
++ self->createStorageAssociation();
+ return;
+ }
+
+@@ -188,8 +190,13 @@ void NVMeSubsystem::start()
+ self->controllers.insert({*index, {nvmeController, plugin}});
+ nvmeController->start(plugin);
+
++ // set StorageController Association
++ self->associations.emplace_back("storage_controller", "storage",
++ path);
++
+ index++;
+ }
++ self->createStorageAssociation();
+
+ /*
+ find primary controller and make association
+@@ -275,16 +282,13 @@ void NVMeSubsystem::start()
+ }
+ else if (auto intf = std::dynamic_pointer_cast<NVMeMiIntf>(nvmeIntf))
+ {
+- ctemp_fetcher_t<nvme_mi_nvm_ss_health_status*>
+- dataFether =
+- [intf](
+- std::function<void(const std::error_code&,
+- nvme_mi_nvm_ss_health_status*)>&& cb) {
++ ctemp_fetcher_t<nvme_mi_nvm_ss_health_status*> dataFether =
++ [intf](std::function<void(const std::error_code&,
++ nvme_mi_nvm_ss_health_status*)>&& cb) {
+ intf->miSubsystemHealthStatusPoll(std::move(cb));
+ };
+- ctemp_parser_t<nvme_mi_nvm_ss_health_status*>
+- dataParser = [](nvme_mi_nvm_ss_health_status* status)
+- -> std::optional<double> {
++ ctemp_parser_t<nvme_mi_nvm_ss_health_status*> dataParser =
++ [](nvme_mi_nvm_ss_health_status* status) -> std::optional<double> {
+ // Drive Functional
+ bool df = status->nss & 0x20;
+ if (!df)
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index b0c7efa..3b654fc 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -86,4 +86,7 @@ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+ ctemp_fetcher_t<T> dataFetcher,
+ boost::system::error_code errorCode, T data);
+ };
++
++ std::vector<Association> associations;
++ void createStorageAssociation();
+ };
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0016-nvmesensor-Create-thermal-sensor-in-start.patch b/recipes-phosphor/sensors/dbus-sensors/0016-nvmesensor-Create-thermal-sensor-in-start.patch
new file mode 100644
index 0000000..da1aaa4
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0016-nvmesensor-Create-thermal-sensor-in-start.patch
@@ -0,0 +1,136 @@
+From 68c6f26bd4df63fcfdc5fd142dad21535a05eb1b Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Tue, 20 Dec 2022 01:58:11 +0000
+Subject: [PATCH 16/34] nvmesensor: Create thermal sensor in start()
+
+Move the thermal sensor creation from NVMeSubsys constructor to start().
+This is to align with the other sublayer construction, e.g. Controllers.
+And all sublayer components will get access to the configuration.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: Iccc45fc0d99777763a67995fb6ca6ce3d0e52f7c
+---
+ src/NVMeSensorMain.cpp | 8 ++++----
+ src/NVMeSubsys.cpp | 40 ++++++++++++++++++++--------------------
+ src/NVMeSubsys.hpp | 3 +--
+ 3 files changed, 25 insertions(+), 26 deletions(-)
+
+diff --git a/src/NVMeSensorMain.cpp b/src/NVMeSensorMain.cpp
+index d04d3e5..0656d2c 100644
+--- a/src/NVMeSensorMain.cpp
++++ b/src/NVMeSensorMain.cpp
+@@ -140,9 +140,9 @@ static void handleConfigurations(
+
+ auto nvmeSubsys = std::make_shared<NVMeSubsystem>(
+ io, objectServer, dbusConnection, interfacePath,
+- *sensorName, configData, nvmeBasic);
++ *sensorName, nvmeBasic);
+ nvmeSubsysMap.emplace(interfacePath, nvmeSubsys);
+- nvmeSubsys->start();
++ nvmeSubsys->start(configData);
+ }
+ catch (std::exception& ex)
+ {
+@@ -167,9 +167,9 @@ static void handleConfigurations(
+
+ auto nvmeSubsys = std::make_shared<NVMeSubsystem>(
+ io, objectServer, dbusConnection, interfacePath,
+- *sensorName, configData, nvmeMi);
++ *sensorName, nvmeMi);
+ nvmeSubsysMap.emplace(interfacePath, nvmeSubsys);
+- nvmeSubsys->start();
++ nvmeSubsys->start(configData);
+ }
+ catch (std::exception& ex)
+ {
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index 0d5af8b..f718681 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -96,7 +96,6 @@ NVMeSubsystem::NVMeSubsystem(boost::asio::io_context& io,
+ sdbusplus::asio::object_server& objServer,
+ std::shared_ptr<sdbusplus::asio::connection> conn,
+ const std::string& path, const std::string& name,
+- const SensorData& configData,
+ const std::shared_ptr<NVMeIntf>& intf) :
+ io(io),
+ objServer(objServer), conn(conn), path(path), name(name), nvmeIntf(intf),
+@@ -116,24 +115,6 @@ NVMeSubsystem::NVMeSubsystem(boost::asio::io_context& io,
+ throw std::runtime_error("Unsupported NVMe interface");
+ }
+
+- std::optional<std::string> sensorName = createSensorNameFromPath(path);
+- if (!sensorName)
+- {
+- // fail to parse sensor name from path, using name instead.
+- sensorName.emplace(name);
+- }
+-
+- std::vector<thresholds::Threshold> sensorThresholds;
+- if (!parseThresholdsFromConfig(configData, sensorThresholds))
+- {
+- std::cerr << "error populating thresholds for " << *sensorName << "\n";
+- throw std::runtime_error("error populating thresholds for " +
+- *sensorName);
+- }
+-
+- ctemp.emplace(objServer, io, conn, *sensorName, std::move(sensorThresholds),
+- path);
+-
+ /* xyz.openbmc_project.Inventory.Item.Drive */
+ drive.protocol(NVMeDrive::DriveProtocol::NVMe);
+ drive.type(NVMeDrive::DriveType::SSD);
+@@ -147,7 +128,7 @@ NVMeSubsystem::NVMeSubsystem(boost::asio::io_context& io,
+ associations.emplace_back("drive", "storage", path);
+ }
+
+-void NVMeSubsystem::start()
++void NVMeSubsystem::start(const SensorData& configData)
+ {
+ // add controllers for the subsystem
+ if (auto nvme = std::dynamic_pointer_cast<NVMeMiIntf>(nvmeIntf))
+@@ -262,6 +243,25 @@ void NVMeSubsystem::start()
+ });
+ }
+
++ // add thermal sensor for the subsystem
++ std::optional<std::string> sensorName = createSensorNameFromPath(path);
++ if (!sensorName)
++ {
++ // fail to parse sensor name from path, using name instead.
++ sensorName.emplace(name);
++ }
++
++ std::vector<thresholds::Threshold> sensorThresholds;
++ if (!parseThresholdsFromConfig(configData, sensorThresholds))
++ {
++ std::cerr << "error populating thresholds for " << *sensorName << "\n";
++ throw std::runtime_error("error populating thresholds for " +
++ *sensorName);
++ }
++
++ ctemp.emplace(objServer, io, conn, *sensorName, std::move(sensorThresholds),
++ path);
++
+ // start to poll value for CTEMP sensor.
+ if (auto intf = std::dynamic_pointer_cast<NVMeBasicIntf>(nvmeIntf))
+ {
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index 3b654fc..eb10b47 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -15,10 +15,9 @@ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+ sdbusplus::asio::object_server& objServer,
+ std::shared_ptr<sdbusplus::asio::connection> conn,
+ const std::string& path, const std::string& name,
+- const SensorData& configData,
+ const std::shared_ptr<NVMeIntf>& intf);
+
+- void start();
++ void start(const SensorData& configData);
+
+ void stop()
+ {
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0017-nvmesensor-Move-temp-sensor-function-to-NVMeUtil.patch b/recipes-phosphor/sensors/dbus-sensors/0017-nvmesensor-Move-temp-sensor-function-to-NVMeUtil.patch
new file mode 100644
index 0000000..8eb320a
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0017-nvmesensor-Move-temp-sensor-function-to-NVMeUtil.patch
@@ -0,0 +1,513 @@
+From 5e59fcefac0a370b7e0e1a4660f9ee2a1260980b Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Thu, 22 Dec 2022 00:48:57 +0000
+Subject: [PATCH 17/34] nvmesensor: Move temp sensor function to NVMeUtil
+
+The temp sensor can not only be used for NVMe subsystem. E.g. Each NVMe
+controller can have its individual cTEMP reporting from controller level
+SMART log. Abstract the function into NVMeUtil so the other NVMe
+components can resue the functionility.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: I9e17f5665d8cf7b756c274f30ae84dc8c80d25c8
+---
+ src/NVMeSubsys.cpp | 146 ++----------------------------
+ src/NVMeSubsys.hpp | 43 ++-------
+ src/NVMeUtil.hpp | 219 +++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 231 insertions(+), 177 deletions(-)
+ create mode 100644 src/NVMeUtil.hpp
+
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index f718681..1b0aef5 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -3,61 +3,8 @@
+ #include "NVMePlugin.hpp"
+ #include "Thresholds.hpp"
+
+-#include <filesystem>
+-
+-std::optional<std::string>
+- extractOneFromTail(std::string::const_reverse_iterator& rbegin,
+- const std::string::const_reverse_iterator& rend)
+-{
+- std::string name;
+- auto curr = rbegin;
+- // remove the ending '/'s
+- while (rbegin != rend && *rbegin == '/')
+- {
+- rbegin++;
+- }
+- if (rbegin == rend)
+- {
+- return std::nullopt;
+- }
+- curr = rbegin++;
+-
+- // extract word
+- while (rbegin != rend && *rbegin != '/')
+- {
+- rbegin++;
+- }
+- if (rbegin == rend)
+- {
+- return std::nullopt;
+- }
+- name.append(rbegin.base(), curr.base());
+- return {name};
+-}
+
+-// a path of "/xyz/openbmc_project/inventory/system/board/{prod}/{nvme}" will
+-// generates a sensor name {prod}_{nvme}
+-std::optional<std::string> createSensorNameFromPath(const std::string& path)
+-{
+- if (path.empty())
+- {
+- return std::nullopt;
+- }
+- auto rbegin = path.crbegin();
+-
+- auto nvme = extractOneFromTail(rbegin, path.crend());
+- auto prod = extractOneFromTail(rbegin, path.crend());
+- auto board = extractOneFromTail(rbegin, path.crend());
+-
+- if (!nvme || !prod || !board || board != "board")
+- {
+- return std::nullopt;
+- }
+- std::string name{std::move(*prod)};
+- name.append("_");
+- name.append(*nvme);
+- return name;
+-}
++#include <filesystem>
+
+ void NVMeSubsystem::createStorageAssociation()
+ {
+@@ -99,7 +46,6 @@ NVMeSubsystem::NVMeSubsystem(boost::asio::io_context& io,
+ const std::shared_ptr<NVMeIntf>& intf) :
+ io(io),
+ objServer(objServer), conn(conn), path(path), name(name), nvmeIntf(intf),
+- ctempTimer(io),
+ storage(*dynamic_cast<sdbusplus::bus_t*>(conn.get()), path.c_str()),
+ drive(*dynamic_cast<sdbusplus::bus_t*>(conn.get()), path.c_str())
+ {
+@@ -259,8 +205,9 @@ void NVMeSubsystem::start(const SensorData& configData)
+ *sensorName);
+ }
+
+- ctemp.emplace(objServer, io, conn, *sensorName, std::move(sensorThresholds),
+- path);
++ ctemp = std::make_shared<NVMeSensor>(objServer, io, conn, *sensorName,
++ std::move(sensorThresholds), path);
++ ctempTimer = std::make_shared<boost::asio::steady_timer>(io);
+
+ // start to poll value for CTEMP sensor.
+ if (auto intf = std::dynamic_pointer_cast<NVMeBasicIntf>(nvmeIntf))
+@@ -278,7 +225,7 @@ void NVMeSubsystem::start(const SensorData& configData)
+ }
+ return {getTemperatureReading(status->Temp)};
+ };
+- pollCtemp(dataFether, dataParser);
++ pollCtemp(this->ctempTimer, this->ctemp, dataFether, dataParser);
+ }
+ else if (auto intf = std::dynamic_pointer_cast<NVMeMiIntf>(nvmeIntf))
+ {
+@@ -297,89 +244,8 @@ void NVMeSubsystem::start(const SensorData& configData)
+ }
+ return {getTemperatureReading(status->ctemp)};
+ };
+- pollCtemp(dataFether, dataParser);
++ pollCtemp(ctempTimer, ctemp, dataFether, dataParser);
+ }
+
+ // TODO: start to poll Drive status.
+ }
+-
+-template <class T>
+-void NVMeSubsystem::pollCtemp(
+- const std::function<void(std::function<void(const std::error_code&, T)>&&)>&
+- dataFetcher,
+- const std::function<std::optional<double>(T data)>& dataParser)
+-{
+- ctempTimer.expires_from_now(std::chrono::seconds(1));
+- ctempTimer.async_wait(std::bind_front(NVMeSubsystem::Detail::pollCtemp<T>,
+- shared_from_this(), dataFetcher,
+- dataParser));
+-}
+-
+-template <class T>
+-void NVMeSubsystem::Detail::pollCtemp(std::shared_ptr<NVMeSubsystem> self,
+- ctemp_fetcher_t<T> dataFetcher,
+- ctemp_parser_t<T> dataParser,
+- const boost::system::error_code errorCode)
+-{
+-
+- if (errorCode == boost::asio::error::operation_aborted)
+- {
+- return;
+- }
+- if (errorCode)
+- {
+- std::cerr << errorCode.message() << "\n";
+- self->pollCtemp(dataFetcher, dataParser);
+- return;
+- }
+-
+- if (!self->ctemp)
+- {
+- self->pollCtemp(dataFetcher, dataParser);
+- return;
+- }
+-
+- if (!self->ctemp->readingStateGood())
+- {
+- self->ctemp->markAvailable(false);
+- self->ctemp->updateValue(std::numeric_limits<double>::quiet_NaN());
+- self->pollCtemp(dataFetcher, dataParser);
+- return;
+- }
+-
+- /* Potentially defer sampling the sensor sensor if it is in error */
+- if (!self->ctemp->sample())
+- {
+- self->pollCtemp(dataFetcher, dataParser);
+- return;
+- }
+-
+- dataFetcher(
+- std::bind_front(Detail::updateCtemp<T>, self, dataParser, dataFetcher));
+-}
+-
+-template <class T>
+-void NVMeSubsystem::Detail::updateCtemp(
+- const std::shared_ptr<NVMeSubsystem>& self, ctemp_parser_t<T> dataParser,
+- ctemp_fetcher_t<T> dataFetcher, const boost::system::error_code error,
+- T data)
+-{
+- if (error)
+- {
+- std::cerr << "error reading ctemp from subsystem: " << self->name
+- << ", reason:" << error.message() << "\n";
+- self->ctemp->markFunctional(false);
+- self->pollCtemp(dataFetcher, dataParser);
+- return;
+- }
+- auto value = dataParser(data);
+- if (!value)
+- {
+- self->ctemp->incrementError();
+- self->pollCtemp(dataFetcher, dataParser);
+- return;
+- }
+-
+- self->ctemp->updateValue(*value);
+- self->pollCtemp(dataFetcher, dataParser);
+-}
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index eb10b47..db9f228 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -1,9 +1,11 @@
++#pragma once
+ #include "NVMeBasic.hpp"
+ #include "NVMeController.hpp"
+ #include "NVMeDrive.hpp"
+ #include "NVMePlugin.hpp"
+ #include "NVMeSensor.hpp"
+ #include "NVMeStorage.hpp"
++#include "NVMeUtil.hpp"
+ #include "Utils.hpp"
+
+ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+@@ -21,7 +23,7 @@ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+
+ void stop()
+ {
+- ctempTimer.cancel();
++ ctempTimer->cancel();
+ }
+
+ private:
+@@ -33,24 +35,10 @@ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+
+ std::shared_ptr<NVMeIntf> nvmeIntf;
+
+- /* thermal sensor for the subsystem */
+- std::optional<NVMeSensor> ctemp;
+- boost::asio::steady_timer ctempTimer;
+-
+- // Function type for fetching ctemp which incaplucated in a structure of T.
+- // The fetcher function take a callback as input to process the result.
+- template <class T>
+- using ctemp_fetcher_t =
+- std::function<void(std::function<void(const std::error_code&, T)>&&)>;
+-
+- // Function type for parsing ctemp out the structure of type T.
+- // The parser function will return the value of ctemp or nullopt on failure.
+- template <class T>
+- using ctemp_parser_t = std::function<std::optional<double>(T data)>;
+
+- template <class T>
+- void pollCtemp(const ctemp_fetcher_t<T>& dataFetcher,
+- const ctemp_parser_t<T>& dataParser);
++ /* thermal sensor for the subsystem */
++ std::shared_ptr<NVMeSensor> ctemp;
++ std::shared_ptr<boost::asio::steady_timer> ctempTimer;
+
+ /*
+ Storage interface: xyz.openbmc_project.Inventory.Item.Storage
+@@ -67,25 +55,6 @@ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+ std::shared_ptr<NVMePlugin>>>
+ controllers{};
+
+- // implemetation details for the class.
+- // It should contain only static function and using binding to the
+- // NVMeSubsystem instances. The detail is defined to claim the accessibility
+- // to the parent private field.
+- class Detail
+- {
+- public:
+- template <class T>
+- static void pollCtemp(std::shared_ptr<NVMeSubsystem> self,
+- ctemp_fetcher_t<T> dataFetcher,
+- ctemp_parser_t<T> dataParser,
+- boost::system::error_code errorCode);
+- template <class T>
+- static void updateCtemp(const std::shared_ptr<NVMeSubsystem>& self,
+- ctemp_parser_t<T> dataParser,
+- ctemp_fetcher_t<T> dataFetcher,
+- boost::system::error_code errorCode, T data);
+- };
+-
+ std::vector<Association> associations;
+ void createStorageAssociation();
+ };
+diff --git a/src/NVMeUtil.hpp b/src/NVMeUtil.hpp
+new file mode 100644
+index 0000000..3b6e73a
+--- /dev/null
++++ b/src/NVMeUtil.hpp
+@@ -0,0 +1,219 @@
++#pragma once
++#include "NVMeSensor.hpp"
++
++#include <boost/algorithm/string/join.hpp>
++#include <boost/asio.hpp>
++
++#include <filesystem>
++#include <iostream>
++#include <optional>
++
++inline std::filesystem::path deriveRootBusPath(int busNumber)
++{
++ return "/sys/bus/i2c/devices/i2c-" + std::to_string(busNumber) +
++ "/mux_device";
++}
++
++inline std::optional<int> deriveRootBus(std::optional<int> busNumber)
++{
++ if (!busNumber)
++ {
++ return std::nullopt;
++ }
++
++ std::filesystem::path muxPath = deriveRootBusPath(*busNumber);
++
++ if (!std::filesystem::is_symlink(muxPath))
++ {
++ return *busNumber;
++ }
++
++ std::string rootName = std::filesystem::read_symlink(muxPath).filename();
++ size_t dash = rootName.find('-');
++ if (dash == std::string::npos)
++ {
++ std::cerr << "Error finding root bus for " << rootName << "\n";
++ return std::nullopt;
++ }
++
++ return std::stoi(rootName.substr(0, dash));
++}
++
++inline std::optional<std::string>
++ extractOneFromTail(std::string::const_reverse_iterator& rbegin,
++ const std::string::const_reverse_iterator& rend)
++{
++ std::string name;
++ auto curr = rbegin;
++ // remove the ending '/'s
++ while (rbegin != rend && *rbegin == '/')
++ {
++ rbegin++;
++ }
++ if (rbegin == rend)
++ {
++ return std::nullopt;
++ }
++ curr = rbegin++;
++
++ // extract word
++ while (rbegin != rend && *rbegin != '/')
++ {
++ rbegin++;
++ }
++ if (rbegin == rend)
++ {
++ return std::nullopt;
++ }
++ name.append(rbegin.base(), curr.base());
++ return {name};
++}
++
++// a path of
++// "/xyz/openbmc_project/inventory/system/board/{prod}/{nvme}/{substruct}..."
++// will generates a sensor name {prod}_{nvme}_{substruct}...
++inline std::optional<std::string>
++ createSensorNameFromPath(const std::string& path)
++{
++ if (path.empty())
++ {
++ return std::nullopt;
++ }
++ auto rbegin = path.crbegin();
++ std::vector<std::string> names;
++ do
++ {
++ auto name = extractOneFromTail(rbegin, path.crend());
++ if (!name)
++ {
++ return std::nullopt;
++ }
++ else if (*name == "board")
++ {
++ break;
++ }
++ names.insert(names.begin(), *name);
++ } while (rbegin != path.rend());
++
++ std::string name = boost::algorithm::join(names, "_");
++ return name;
++}
++
++// Function to update NVMe temp sensor
++
++// Function type for fetching ctemp which incaplucated in a structure of T.
++// The fetcher function take a callback as input to process the result.
++template <class T>
++using ctemp_fetcher_t =
++ std::function<void(std::function<void(const std::error_code&, T)>&&)>;
++
++// Function type for parsing ctemp out the structure of type T.
++// The parser function will return the value of ctemp or nullopt on failure.
++template <class T>
++using ctemp_parser_t = std::function<std::optional<double>(T data)>;
++
++template <class T>
++void pollCtemp(
++ std::shared_ptr<boost::asio::steady_timer> timer,
++ std::shared_ptr<NVMeSensor> sensor,
++ const std::function<void(std::function<void(const std::error_code&, T)>&&)>&
++ dataFetcher,
++ const std::function<std::optional<double>(T data)>& dataParser);
++
++namespace detail
++{
++
++template <class T>
++void updateCtemp(std::shared_ptr<boost::asio::steady_timer> timer,
++ std::shared_ptr<NVMeSensor> sensor,
++ ctemp_parser_t<T> dataParser, ctemp_fetcher_t<T> dataFetcher,
++ const boost::system::error_code error, T data)
++{
++ if (error)
++ {
++ std::cerr << "error reading ctemp from subsystem"
++ << ", reason:" << error.message() << "\n";
++ sensor->markFunctional(false);
++ ::pollCtemp(std::move(timer), std::move(sensor), dataFetcher,
++ dataParser);
++ return;
++ }
++ auto value = dataParser(data);
++ if (!value)
++ {
++ sensor->incrementError();
++ ::pollCtemp(std::move(timer), std::move(sensor), dataFetcher,
++ dataParser);
++ return;
++ }
++
++ sensor->updateValue(*value);
++ ::pollCtemp(std::move(timer), std::move(sensor), dataFetcher, dataParser);
++}
++
++template <class T>
++void pollCtemp(std::shared_ptr<boost::asio::steady_timer> timer,
++ std::shared_ptr<NVMeSensor> sensor,
++ ctemp_fetcher_t<T> dataFetcher, ctemp_parser_t<T> dataParser,
++ const boost::system::error_code errorCode)
++{
++
++ if (errorCode == boost::asio::error::operation_aborted)
++ {
++ return;
++ }
++ if (errorCode)
++ {
++ std::cerr << errorCode.message() << "\n";
++ ::pollCtemp(std::move(timer), std::move(sensor), dataFetcher,
++ dataParser);
++ return;
++ }
++
++ if (!sensor)
++ {
++ ::pollCtemp(std::move(timer), std::move(sensor), dataFetcher,
++ dataParser);
++ return;
++ }
++
++ if (!sensor->readingStateGood())
++ {
++ sensor->markAvailable(false);
++ sensor->updateValue(std::numeric_limits<double>::quiet_NaN());
++ ::pollCtemp(std::move(timer), std::move(sensor), dataFetcher,
++ dataParser);
++ return;
++ }
++
++ /* Potentially defer sampling the sensor sensor if it is in error */
++ if (!sensor->sample())
++ {
++ ::pollCtemp(std::move(timer), std::move(sensor), dataFetcher,
++ dataParser);
++ return;
++ }
++
++ dataFetcher(std::bind_front(detail::updateCtemp<T>, std::move(timer),
++ std::move(sensor), dataParser, dataFetcher));
++}
++
++} // namespace detail
++
++template <class T>
++void pollCtemp(
++ std::shared_ptr<boost::asio::steady_timer> timer,
++ std::shared_ptr<NVMeSensor> sensor,
++ const std::function<void(std::function<void(const std::error_code&, T)>&&)>&
++ dataFetcher,
++ const std::function<std::optional<double>(T data)>& dataParser)
++{
++ if (!timer && !sensor)
++ {
++ return;
++ }
++ timer->expires_from_now(std::chrono::seconds(1));
++ timer->async_wait(std::bind_front(detail::pollCtemp<T>, std::move(timer),
++ std::move(sensor), dataFetcher,
++ dataParser));
++}
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0018-nvmesensor-Change-the-plugin-log-handler.patch b/recipes-phosphor/sensors/dbus-sensors/0018-nvmesensor-Change-the-plugin-log-handler.patch
new file mode 100644
index 0000000..bec85b0
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0018-nvmesensor-Change-the-plugin-log-handler.patch
@@ -0,0 +1,74 @@
+From 2c76fcabed53855b515d33cf80afb8eb82cd9ebd Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Thu, 22 Dec 2022 19:35:32 +0000
+Subject: [PATCH 18/34] nvmesensor: Change the plugin log handler
+
+Align the logpage handler type definition to the standard NVMe-MI
+logpage function(A.K.A adminGetLogPage). This is because the plugin
+should only provide the data fetching methodology from NVMe vendor
+protocol. The DBus data transfering over pipe should be part of
+NVMeController DBus interface, not the plugin.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: I67fd13950fdc93a1c5d570fc3e963c1f73916684
+---
+ src/NVMeController.cpp | 23 ++++++++++++++++++++++-
+ src/NVMePlugin.hpp | 7 +++----
+ 2 files changed, 25 insertions(+), 5 deletions(-)
+
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index 20973b7..46f84cb 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -79,7 +79,28 @@ sdbusplus::message::unix_fd NVMeController::getLogPage(uint8_t lid,
+ auto handler = nvmePlugin->getGetLogPageHandler();
+ if (handler)
+ {
+- handler(pipe, lid, nsid, lsp, lsi);
++ std::function<void(const std::error_code&, std::span<uint8_t>)> cb =
++ [pipe](std::error_code ec, std::span<uint8_t> data) {
++ ::close(pipe[0]);
++ int fd = pipe[1];
++ if (ec)
++ {
++ std::cerr << "fail to GetLogPage: " << ec.message()
++ << std::endl;
++ close(fd);
++ return;
++ }
++
++ // TODO: evaluate the impact of client not reading fast enough
++ // on large trunk of data
++ if (::write(fd, data.data(), data.size()) < 0)
++ {
++ std::cerr << "GetLogPage fails to write fd: "
++ << std::strerror(errno) << std::endl;
++ };
++ close(fd);
++ };
++ handler(lid, nsid, lsp, lsi, std::move(cb));
+ }
+ else // No VU LogPage handler
+ {
+diff --git a/src/NVMePlugin.hpp b/src/NVMePlugin.hpp
+index 97922c5..cccb2b5 100644
+--- a/src/NVMePlugin.hpp
++++ b/src/NVMePlugin.hpp
+@@ -1,11 +1,10 @@
+ #pragma once
+-#include <NVMeController.hpp>
+ class NVMePlugin
+ {
+ public:
+- using getlogpage_t =
+- std::function<void(std::array<int, 2> pipe, uint8_t lid, uint32_t nsid,
+- uint8_t lsp, uint16_t lsi)>;
++ using getlogpage_t = std::function<void(
++ uint8_t lid, uint32_t nsid, uint8_t lsp, uint16_t lsi,
++ std::function<void(const std::error_code&, std::span<uint8_t>)>&& cb)>;
+ NVMePlugin(std::shared_ptr<NVMeController> cntl) : nvmeController(cntl)
+ {}
+ virtual ~NVMePlugin()
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0019-nvmesensor-Add-nvme-controller-plugin.patch b/recipes-phosphor/sensors/dbus-sensors/0019-nvmesensor-Add-nvme-controller-plugin.patch
new file mode 100644
index 0000000..3c2f1fd
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0019-nvmesensor-Add-nvme-controller-plugin.patch
@@ -0,0 +1,404 @@
+From f90edf75d2bf6a9d3375fadad5e6ec2e5b3f9913 Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Tue, 3 Jan 2023 19:45:03 +0000
+Subject: [PATCH 19/34] nvmesensor: Add nvme/controller plugin
+
+Rename the NVMePlugin to NVMeControllerPlugin. And add NVMePlugin for
+NVMe subsystem.
+
+The controller plugin is used to send NVMe Admin VU command to the
+controllers. The VU admin command is defined by vendor and following the
+NVMe base spec (Figure 139, rev 1.4).
+
+The new NVMe plugin is used to attached to the whole NVMe subsystem and
+create the controller plugin respectively. It is also used to send
+NVMe-MI VU command to the MI controller, according the the NVMe-MI
+spec(figure 57, rev 1.2).
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: I7c9b1b738f84a53817299df4cc6b4a538a2eb804
+---
+ src/NVMeController.cpp | 2 +-
+ src/NVMeController.hpp | 20 +++++--
+ src/NVMePlugin.hpp | 122 ++++++++++++++++++++++++++++++++++++-----
+ src/NVMeSubsys.cpp | 58 +++++++++++++++-----
+ src/NVMeSubsys.hpp | 14 +++--
+ 5 files changed, 178 insertions(+), 38 deletions(-)
+
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index 46f84cb..c5333e1 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -28,7 +28,7 @@ NVMeController::NVMeController(
+ NVMeAdmin::emit_added();
+ }
+
+-void NVMeController::start(std::shared_ptr<NVMePlugin> nvmePlugin)
++void NVMeController::start(std::shared_ptr<NVMeControllerPlugin> nvmePlugin)
+ {
+ plugin = nvmePlugin;
+ }
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+index 92c7142..592c3de 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -11,7 +11,7 @@
+
+ #include <utility>
+
+-class NVMePlugin;
++class NVMeControllerPlugin;
+ class NVMeController :
+ private sdbusplus::xyz::openbmc_project::Inventory::Item::server::
+ StorageController,
+@@ -28,7 +28,7 @@ class NVMeController :
+
+ ~NVMeController() override;
+
+- void start(std::shared_ptr<NVMePlugin> nvmePlugin);
++ void start(std::shared_ptr<NVMeControllerPlugin> nvmePlugin);
+
+ // setup association to the secondary controllers. Clear the Association if
+ // empty.
+@@ -40,8 +40,20 @@ class NVMeController :
+ setSecAssoc({});
+ }
+
++ /**
++ * @brief Get cntrl_id for the binded NVMe controller
++ *
++ * @return cntrl_id
++ */
++ uint16_t getCntrlId() const
++ {
++ return *reinterpret_cast<uint16_t*>(
++ (reinterpret_cast<uint8_t*>(nvmeCtrl) +
++ std::max(sizeof(uint16_t), sizeof(void*))));
++ }
++
+ private:
+- friend class NVMePlugin;
++ friend class NVMeControllerPlugin;
+
+ boost::asio::io_context& io;
+ sdbusplus::asio::object_server& objServer;
+@@ -56,7 +68,7 @@ class NVMeController :
+ std::shared_ptr<sdbusplus::asio::dbus_interface> secAssoc;
+
+ // NVMe Plug-in for vendor defined command/field
+- std::weak_ptr<NVMePlugin> plugin;
++ std::weak_ptr<NVMeControllerPlugin> plugin;
+
+ /* NVMeAdmin method overload */
+
+diff --git a/src/NVMePlugin.hpp b/src/NVMePlugin.hpp
+index cccb2b5..122bc5a 100644
+--- a/src/NVMePlugin.hpp
++++ b/src/NVMePlugin.hpp
+@@ -1,13 +1,24 @@
+ #pragma once
+-class NVMePlugin
++#include "NVMeController.hpp"
++#include "NVMeSubsys.hpp"
++#include "Utils.hpp"
++
++class NVMePlugin;
++
++class NVMeControllerPlugin
+ {
+ public:
+ using getlogpage_t = std::function<void(
+ uint8_t lid, uint32_t nsid, uint8_t lsp, uint16_t lsi,
+ std::function<void(const std::error_code&, std::span<uint8_t>)>&& cb)>;
+- NVMePlugin(std::shared_ptr<NVMeController> cntl) : nvmeController(cntl)
++
++ // The controller plugin can only be created from NVMePlugin
++ NVMeControllerPlugin(std::shared_ptr<NVMeController> cntl,
++ const SensorData&) :
++ nvmeController(cntl)
+ {}
+- virtual ~NVMePlugin()
++
++ virtual ~NVMeControllerPlugin()
+ {}
+ virtual getlogpage_t getGetLogPageHandler()
+ {
+@@ -19,9 +30,13 @@ class NVMePlugin
+ {
+ return nvmeController->path;
+ }
+- sdbusplus::asio::connection& getDbusConnection()
++ sdbusplus::asio::object_server& getDbusServer()
+ {
+- return *nvmeController->conn;
++ return nvmeController->objServer;
++ }
++ std::shared_ptr<sdbusplus::asio::connection> getDbusConnection()
++ {
++ return nvmeController->conn;
+ }
+
+ boost::asio::io_context& getIOContext()
+@@ -42,15 +57,16 @@ class NVMePlugin
+ * Performs an arbitrary NVMe Admin command, using the provided request
+ * header, in @admin_req. The requested data is attached by @data, if any.
+ *
+- * On success, @cb will be called and response header and data are stored in
++ * On success, @cb will be called and response header and data are stored
++ * in
+ * @admin_resp and @resp_data, which has an optional appended payload
+- * buffer. The response data does not include the Admin request header, so 0
+- * represents no payload.
++ * buffer. The response data does not include the Admin request header, so
++ * 0 represents no payload.
+ *
+ * As with all Admin commands, we can request partial data from the Admin
+ * Response payload, offset by @resp_data_offset. In case of resp_data
+- * contains only partial data of the caller's requirement, a follow-up call
+- * to adminXfer with offset is required.
++ * contains only partial data of the caller's requirement, a follow-up
++ * call to adminXfer with offset is required.
+ *
+ * See: &struct nvme_mi_admin_req_hdr and &struct nvme_mi_admin_resp_hdr.
+ *
+@@ -70,13 +86,91 @@ class NVMePlugin
+ *
+ * @return cntrl_id
+ */
+- uint16_t getCntrlId()
++ uint16_t getCntrlId() const
+ {
+- return *reinterpret_cast<uint16_t*>(
+- (reinterpret_cast<uint8_t*>(nvmeController->nvmeCtrl) +
+- std::max(sizeof(uint16_t), sizeof(void*))));
++ return nvmeController->getCntrlId();
+ }
+
+ private:
+ std::shared_ptr<NVMeController> nvmeController;
+ };
++
++class NVMePlugin
++{
++ public:
++ NVMePlugin(std::shared_ptr<NVMeSubsystem> subsys,
++ const SensorData& /*config*/) :
++ subsystem(std::move(subsys)){};
++
++ virtual ~NVMePlugin()
++ {}
++
++ std::shared_ptr<NVMeControllerPlugin>
++ createControllerPlugin(const NVMeController& controller,
++ const SensorData& config)
++ {
++ // searching for the target controller in NVMe subsystem
++ auto res = subsystem->controllers.find(controller.getCntrlId());
++ if (res == subsystem->controllers.end() ||
++ &controller != res->second.first.get())
++ {
++ throw std::runtime_error("Failed to create controller plugin: "
++ "cannot find the controller");
++ }
++
++ // insert the plugin
++ res->second.second = makeController(res->second.first, config);
++ return res->second.second;
++ }
++
++ // the NVMe subsystem will start the plugin after NVMesubsystem finished
++ // intialization and started.
++ virtual void start()
++ {
++ return;
++ }
++
++ // the NVMe subsystem will stop the plugin before NVMe subsystem stop
++ // itself.
++ virtual void stop()
++ {
++ return;
++ }
++
++ protected:
++ const std::string& getPath() const
++ {
++ return subsystem->path;
++ }
++ const std::string& getName() const
++ {
++ return subsystem->name;
++ }
++ boost::asio::io_context& getIOContext()
++ {
++ return subsystem->io;
++ }
++ sdbusplus::asio::object_server& getDbusServer()
++ {
++ return subsystem->objServer;
++ }
++ std::shared_ptr<sdbusplus::asio::connection> getDbusConnection()
++ {
++ return subsystem->conn;
++ }
++
++ const std::map<uint16_t, std::pair<std::shared_ptr<NVMeController>,
++ std::shared_ptr<NVMeControllerPlugin>>>&
++ getControllers()
++ {
++ return subsystem->controllers;
++ }
++ // The nvme plugin implemenation need to overload the function to create a
++ // derived controller plugin.
++ virtual std::shared_ptr<NVMeControllerPlugin>
++ makeController(std::shared_ptr<NVMeController> cntl,
++ const SensorData&) = 0;
++
++ private:
++ std::shared_ptr<NVMeSubsystem> subsystem;
++};
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index 1b0aef5..90bad81 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -3,7 +3,6 @@
+ #include "NVMePlugin.hpp"
+ #include "Thresholds.hpp"
+
+-
+ #include <filesystem>
+
+ void NVMeSubsystem::createStorageAssociation()
+@@ -80,9 +79,9 @@ void NVMeSubsystem::start(const SensorData& configData)
+ if (auto nvme = std::dynamic_pointer_cast<NVMeMiIntf>(nvmeIntf))
+ {
+ nvme->miScanCtrl(
+- [self{shared_from_this()},
+- nvme](const std::error_code& ec,
+- const std::vector<nvme_mi_ctrl_t>& ctrlList) mutable {
++ [self{shared_from_this()}, nvme,
++ configData](const std::error_code& ec,
++ const std::vector<nvme_mi_ctrl_t>& ctrlList) mutable {
+ if (ec || ctrlList.size() == 0)
+ {
+ // TODO: mark the subsystem invalid and reschedule refresh
+@@ -110,16 +109,36 @@ void NVMeSubsystem::start(const SensorData& configData)
+ std::filesystem::path path = std::filesystem::path(self->path) /
+ "controllers" /
+ std::to_string(*index);
+- auto nvmeController = std::make_shared<NVMeController>(
+- self->io, self->objServer, self->conn, path.string(), nvme,
+- c);
+- std::shared_ptr<NVMePlugin> plugin = {};
+- self->controllers.insert({*index, {nvmeController, plugin}});
+- nvmeController->start(plugin);
++
++ try
++ {
++ auto nvmeController = std::make_shared<NVMeController>(
++ self->io, self->objServer, self->conn, path.string(),
++ nvme, c);
++
++ // insert the controllers with empty plugin
++ auto [iter, _] = self->controllers.insert(
++ {*index, {nvmeController, {}}});
++ auto& ctrlPlugin = iter->second.second;
++
++ // creat controller plugin
++ if (self->plugin)
++ {
++ ctrlPlugin = self->plugin->createControllerPlugin(
++ *nvmeController, configData);
++ }
++ nvmeController->start(ctrlPlugin);
+
+- // set StorageController Association
+- self->associations.emplace_back("storage_controller", "storage",
+- path);
++ // set StorageController Association
++ self->associations.emplace_back("storage_controller",
++ "storage", path);
++ }
++ catch (const std::exception& e)
++ {
++ std::cerr << "failed to create controller: "
++ << std::to_string(*index)
++ << ", reason: " << e.what() << std::endl;
++ }
+
+ index++;
+ }
+@@ -248,4 +267,17 @@ void NVMeSubsystem::start(const SensorData& configData)
+ }
+
+ // TODO: start to poll Drive status.
++
++ if (plugin)
++ {
++ plugin->start();
++ }
++}
++void NVMeSubsystem::stop()
++{
++ if (plugin)
++ {
++ plugin->stop();
++ }
++ ctempTimer->cancel();
+ }
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index db9f228..8af2497 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -2,12 +2,14 @@
+ #include "NVMeBasic.hpp"
+ #include "NVMeController.hpp"
+ #include "NVMeDrive.hpp"
+-#include "NVMePlugin.hpp"
+ #include "NVMeSensor.hpp"
+ #include "NVMeStorage.hpp"
+ #include "NVMeUtil.hpp"
+ #include "Utils.hpp"
+
++class NVMeControllerPlugin;
++class NVMePlugin;
++
+ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+ {
+ public:
+@@ -21,12 +23,10 @@ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+
+ void start(const SensorData& configData);
+
+- void stop()
+- {
+- ctempTimer->cancel();
+- }
++ void stop();
+
+ private:
++ friend class NVMePlugin;
+ boost::asio::io_context& io;
+ sdbusplus::asio::object_server& objServer;
+ std::shared_ptr<sdbusplus::asio::connection> conn;
+@@ -35,6 +35,8 @@ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+
+ std::shared_ptr<NVMeIntf> nvmeIntf;
+
++ // plugin
++ std::shared_ptr<NVMePlugin> plugin;
+
+ /* thermal sensor for the subsystem */
+ std::shared_ptr<NVMeSensor> ctemp;
+@@ -52,7 +54,7 @@ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+
+ // map from cntrlid to a pair of {controller, controller_plugin}
+ std::map<uint16_t, std::pair<std::shared_ptr<NVMeController>,
+- std::shared_ptr<NVMePlugin>>>
++ std::shared_ptr<NVMeControllerPlugin>>>
+ controllers{};
+
+ std::vector<Association> associations;
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0020-nvmesensor-add-timeout-for-xfer.patch b/recipes-phosphor/sensors/dbus-sensors/0020-nvmesensor-add-timeout-for-xfer.patch
new file mode 100644
index 0000000..ced5672
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0020-nvmesensor-add-timeout-for-xfer.patch
@@ -0,0 +1,122 @@
+From d4b11f261a5a8da134e8a354e31d103de8ffe817 Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Tue, 10 Jan 2023 01:24:00 +0000
+Subject: [PATCH 20/34] nvmesensor: add timeout for xfer
+
+The xfer which is used by plugins or others may share a different timeout
+setting, since the VU command could take time for processing. So added
+the timeout as parameter so the client can modify according to the use
+case.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: I02d8c8527d56914ef15faf502cb2db98ea705228
+---
+ src/NVMeIntf.hpp | 3 ++-
+ src/NVMeMi.cpp | 10 ++++++++--
+ src/NVMeMi.hpp | 2 +-
+ src/NVMePlugin.hpp | 5 +++--
+ 4 files changed, 14 insertions(+), 6 deletions(-)
+
+diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
+index e9060ab..e7ba3cf 100644
+--- a/src/NVMeIntf.hpp
++++ b/src/NVMeIntf.hpp
+@@ -125,6 +125,7 @@ class NVMeMiIntf : public NVMeIntf
+ * @ctrl: controller to send the admin command to
+ * @admin_req: request header
+ * @data: request data payload
++ * @timeout_ms: timeout in ms
+ * @resp_data_offset: offset into request data to retrieve from controller
+ * @cb: callback function after the response received.
+ * @ec: error code
+@@ -150,7 +151,7 @@ class NVMeMiIntf : public NVMeIntf
+ */
+ virtual void
+ adminXfer(nvme_mi_ctrl_t ctrl, const nvme_mi_admin_req_hdr& admin_req,
+- std::span<uint8_t> data,
++ std::span<uint8_t> data, unsigned int timeout_ms,
+ std::function<void(const std::error_code& ec,
+ const nvme_mi_admin_resp_hdr& admin_resp,
+ std::span<uint8_t> resp_data)>&& cb) = 0;
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index 0cd9ebc..c8579a7 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -615,7 +615,7 @@ void NVMeMi::adminGetLogPage(
+
+ void NVMeMi::adminXfer(
+ nvme_mi_ctrl_t ctrl, const nvme_mi_admin_req_hdr& admin_req,
+- std::span<uint8_t> data,
++ std::span<uint8_t> data, unsigned int timeout_ms,
+ std::function<void(const std::error_code&, const nvme_mi_admin_resp_hdr&,
+ std::span<uint8_t>)>&& cb)
+ {
+@@ -633,7 +633,7 @@ void NVMeMi::adminXfer(
+ memcpy(req.data(), &admin_req, sizeof(nvme_mi_admin_req_hdr));
+ memcpy(req.data() + sizeof(nvme_mi_admin_req_hdr), data.data(),
+ data.size());
+- post([ctrl, req{std::move(req)}, self{shared_from_this()},
++ post([ctrl, req{std::move(req)}, self{shared_from_this()}, timeout_ms,
+ cb{std::move(cb)}]() mutable {
+ int rc = 0;
+
+@@ -649,9 +649,15 @@ void NVMeMi::adminXfer(
+ nvme_mi_admin_resp_hdr* respHeader =
+ reinterpret_cast<nvme_mi_admin_resp_hdr*>(buf.data());
+
++ // set timeout
++ unsigned timeout = nvme_mi_ep_get_timeout(self->nvmeEP);
++ nvme_mi_ep_set_timeout(self->nvmeEP, timeout_ms);
++
+ rc = nvme_mi_admin_xfer(ctrl, reqHeader,
+ req.size() - sizeof(nvme_mi_admin_req_hdr),
+ respHeader, respDataOffset, &respDataSize);
++ // revert to previous timeout
++ nvme_mi_ep_set_timeout(self->nvmeEP, timeout);
+
+ if (rc < 0)
+ {
+diff --git a/src/NVMeMi.hpp b/src/NVMeMi.hpp
+index 64f2fc6..0d9fe2c 100644
+--- a/src/NVMeMi.hpp
++++ b/src/NVMeMi.hpp
+@@ -41,7 +41,7 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+ override;
+
+ void adminXfer(nvme_mi_ctrl_t ctrl, const nvme_mi_admin_req_hdr& admin_req,
+- std::span<uint8_t> data,
++ std::span<uint8_t> data, unsigned int timeout_ms,
+ std::function<void(const std::error_code&,
+ const nvme_mi_admin_resp_hdr&,
+ std::span<uint8_t>)>&& cb) override;
+diff --git a/src/NVMePlugin.hpp b/src/NVMePlugin.hpp
+index 122bc5a..0283e9b 100644
+--- a/src/NVMePlugin.hpp
++++ b/src/NVMePlugin.hpp
+@@ -48,6 +48,7 @@ class NVMeControllerPlugin
+ * adminXfer() - transfer Raw admin cmd to the binded conntroller
+ * @admin_req: request header
+ * @data: request data payload
++ * @timeout_ms: timeout in ms
+ * @resp_data_offset: offset into request data to retrieve from controller
+ * @cb: callback function after the response received.
+ * @ec: error code
+@@ -73,13 +74,13 @@ class NVMeControllerPlugin
+ * @ec will be returned on failure.
+ */
+ void adminXfer(const nvme_mi_admin_req_hdr& admin_req,
+- std::span<uint8_t> data,
++ std::span<uint8_t> data, unsigned int timeout_ms,
+ std::function<void(const std::error_code& ec,
+ const nvme_mi_admin_resp_hdr& admin_resp,
+ std::span<uint8_t> resp_data)>&& cb)
+ {
+ nvmeController->nvmeIntf->adminXfer(nvmeController->nvmeCtrl, admin_req,
+- data, std::move(cb));
++ data, timeout_ms, std::move(cb));
+ }
+ /**
+ * @brief Get cntrl_id for the binded NVMe controller
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0021-nvmesensor-segmentation-fault-workaround-and-fix.patch b/recipes-phosphor/sensors/dbus-sensors/0021-nvmesensor-segmentation-fault-workaround-and-fix.patch
new file mode 100644
index 0000000..6b3c8fc
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0021-nvmesensor-segmentation-fault-workaround-and-fix.patch
@@ -0,0 +1,105 @@
+From d02dacecc061ae16457943969885ac677034f294 Mon Sep 17 00:00:00 2001
+From: Jinliang Wang <jinliangw@google.com>
+Date: Fri, 20 Jan 2023 11:53:54 -0800
+Subject: [PATCH 21/34] nvmesensor: segmentation fault workaround and fix
+
+1) undefine BOOST_ASIO_DISABLE_THREADS and BOOST_ASIO_HAS_IO_URING
+ to work around segmentation fault during boost asio mutithreading
+2) fix (per thread) errno passing issue to prevent segmentation fault
+ during error handling
+3) fix srcid print issue
+
+Signed-off-by: Jinliang Wang <jinliangw@google.com>
+Change-Id: I25695cb0c2ac0e76b493ed48235370e041287b96
+---
+ src/NVMeMi.cpp | 24 ++++++++++++------------
+ src/meson.build | 3 ++-
+ 2 files changed, 14 insertions(+), 13 deletions(-)
+
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index c8579a7..dd083b2 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -143,8 +143,8 @@ void NVMeMi::miSubsystemHealthStatusPoll(
+
+ std::cerr << "fail to subsystem_health_status_poll: "
+ << std::strerror(errno) << std::endl;
+- self->io.post([cb{std::move(cb)}]() {
+- cb(std::make_error_code(static_cast<std::errc>(errno)),
++ self->io.post([cb{std::move(cb)}, last_errno{errno}]() {
++ cb(std::make_error_code(static_cast<std::errc>(last_errno)),
+ nullptr);
+ });
+ return;
+@@ -199,8 +199,8 @@ void NVMeMi::miScanCtrl(std::function<void(const std::error_code&,
+ {
+ std::cerr << "fail to scan controllers: "
+ << std::strerror(errno) << std::endl;
+- self->io.post([cb{std::move(cb)}]() {
+- cb(std::make_error_code(static_cast<std::errc>(errno)), {});
++ self->io.post([cb{std::move(cb)}, last_errno{errno}]() {
++ cb(std::make_error_code(static_cast<std::errc>(last_errno)), {});
+ });
+ return;
+ }
+@@ -299,8 +299,8 @@ void NVMeMi::adminIdentify(
+ {
+ std::cerr << "fail to do nvme identify: "
+ << std::strerror(errno) << std::endl;
+- self->io.post([cb{std::move(cb)}]() {
+- cb(std::make_error_code(static_cast<std::errc>(errno)), {});
++ self->io.post([cb{std::move(cb)}, last_errno{errno}]() {
++ cb(std::make_error_code(static_cast<std::errc>(last_errno)), {});
+ });
+ return;
+ }
+@@ -581,8 +581,8 @@ void NVMeMi::adminGetLogPage(
+ {
+ std::cerr << "fail to get log page: " << std::strerror(errno)
+ << std::endl;
+- self->io.post([cb{std::move(cb)}]() {
+- cb(std::make_error_code(static_cast<std::errc>(errno)), {});
++ self->io.post([cb{std::move(cb)}, last_errno{errno}]() {
++ cb(std::make_error_code(static_cast<std::errc>(last_errno)), {});
+ });
+ return;
+ }
+@@ -662,8 +662,8 @@ void NVMeMi::adminXfer(
+ if (rc < 0)
+ {
+ std::cerr << "failed to nvme_mi_admin_xfer" << std::endl;
+- self->io.post([cb{std::move(cb)}]() {
+- cb(std::make_error_code(static_cast<std::errc>(errno)), {},
++ self->io.post([cb{std::move(cb)}, last_errno{errno}]() {
++ cb(std::make_error_code(static_cast<std::errc>(last_errno)), {},
+ {});
+ });
+ return;
+@@ -721,8 +721,8 @@ void NVMeMi::adminFwCommit(
+
+ std::cerr << "fail to nvme_mi_admin_fw_commit: "
+ << std::strerror(errno) << std::endl;
+- self->io.post([cb{std::move(cb)}]() {
+- cb(std::make_error_code(static_cast<std::errc>(errno)),
++ self->io.post([cb{std::move(cb)}, last_errno{errno}]() {
++ cb(std::make_error_code(static_cast<std::errc>(last_errno)),
+ nvme_status_field::NVME_SC_MASK);
+ });
+ return;
+diff --git a/src/meson.build b/src/meson.build
+index 5214ebd..64200f3 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -223,7 +223,8 @@ if get_option('nvme').enabled()
+ 'nvmesensor',
+ sources: nvme_srcs,
+ dependencies: nvme_deps,
+- cpp_args: [uring_args, '-frtti', '-UBOOST_ASIO_NO_DEPRECATED'],
++ cpp_args: [uring_args, '-frtti', '-UBOOST_ASIO_NO_DEPRECATED',
++ '-UBOOST_ASIO_DISABLE_THREADS', '-UBOOST_ASIO_HAS_IO_URING'],
+ install: true,
+ )
+ endif
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0022-nvmesensor-spin-out-the-worker-for-NVMeMi.patch b/recipes-phosphor/sensors/dbus-sensors/0022-nvmesensor-spin-out-the-worker-for-NVMeMi.patch
new file mode 100644
index 0000000..4820b49
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0022-nvmesensor-spin-out-the-worker-for-NVMeMi.patch
@@ -0,0 +1,207 @@
+From 5e01aaf1ac207acd3c18020d256d690e18bb18ba Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Mon, 12 Dec 2022 22:51:29 +0000
+Subject: [PATCH 22/34] nvmesensor: spin out the worker for NVMeMi
+
+Created the worker class for NVMeMi. The MCTP endpoint under the same
+i2c bus will share the same worker thread, similar to NVMeBasic.
+There is no real physical concurrency among the i2c/mctp devices on
+the same bus. Though mctp kernel drive can schedule and sequencialize
+the transactions but assigning individual worker thread to each EP
+makes no sense.
+
+Also move the helper function into NVMeUtil to share across NVMeMi and
+NVMeBasic.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: I5e2ee30a0f5066deaef56dc7b4b891efbaf9d250
+---
+ src/NVMeBasic.cpp | 33 ++-------------------------------
+ src/NVMeMi.cpp | 36 ++++++++++++++++++++++++++++++++----
+ src/NVMeMi.hpp | 35 +++++++++++++++++++++++++++++------
+ 3 files changed, 63 insertions(+), 41 deletions(-)
+
+diff --git a/src/NVMeBasic.cpp b/src/NVMeBasic.cpp
+index b2cb44e..2211088 100644
+--- a/src/NVMeBasic.cpp
++++ b/src/NVMeBasic.cpp
+@@ -1,5 +1,7 @@
+ #include "NVMeBasic.hpp"
+
++#include "NVMeUtil.hpp"
++
+ #include <endian.h>
+
+ #include <boost/asio/read.hpp>
+@@ -238,37 +240,6 @@ NVMeBasicIO::NVMeBasicIO(
+ });
+ }
+
+-static std::filesystem::path deriveRootBusPath(int busNumber)
+-{
+- return "/sys/bus/i2c/devices/i2c-" + std::to_string(busNumber) +
+- "/mux_device";
+-}
+-
+-static std::optional<int> deriveRootBus(std::optional<int> busNumber)
+-{
+- if (!busNumber)
+- {
+- return std::nullopt;
+- }
+-
+- std::filesystem::path muxPath = deriveRootBusPath(*busNumber);
+-
+- if (!std::filesystem::is_symlink(muxPath))
+- {
+- return *busNumber;
+- }
+-
+- std::string rootName = std::filesystem::read_symlink(muxPath).filename();
+- size_t dash = rootName.find('-');
+- if (dash == std::string::npos)
+- {
+- std::cerr << "Error finding root bus for " << rootName << "\n";
+- return std::nullopt;
+- }
+-
+- return std::stoi(rootName.substr(0, dash));
+-}
+-
+ NVMeBasic::NVMeBasic(boost::asio::io_context& io, int bus, int addr) :
+ io(io), bus(bus), addr(addr)
+ {
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index dd083b2..59ba3a5 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -1,10 +1,15 @@
+ #include "NVMeMi.hpp"
+
++#include "NVMeUtil.hpp"
++
+ #include <boost/endian.hpp>
+
+ #include <cerrno>
+ #include <iostream>
+
++std::map<int, std::weak_ptr<NVMeMi::Worker>> NVMeMi::workerMap{};
++
++// libnvme-mi root service
+ nvme_root_t NVMeMi::nvmeRoot = nvme_mi_create_root(stderr, DEFAULT_LOGLEVEL);
+
+ NVMeMi::NVMeMi(boost::asio::io_context& io, sdbusplus::bus_t& dbus, int bus,
+@@ -17,6 +22,13 @@ NVMeMi::NVMeMi(boost::asio::io_context& io, sdbusplus::bus_t& dbus, int bus,
+ throw std::runtime_error("invalid NVMe root");
+ }
+
++ auto root = deriveRootBus(bus);
++
++ if (!root || *root < 0)
++ {
++ throw std::runtime_error("invalid root bus number");
++ }
++
+ // init mctp ep via mctpd
+ int i = 0;
+ for (;; i++)
+@@ -59,7 +71,21 @@ NVMeMi::NVMeMi(boost::asio::io_context& io, sdbusplus::bus_t& dbus, int bus,
+ std::to_string(eid));
+ }
+
+- // start worker thread
++ auto res = workerMap.find(*root);
++
++ if (res == workerMap.end() || res->second.expired())
++ {
++ worker = std::make_shared<Worker>();
++ workerMap[*root] = worker;
++ }
++ else
++ {
++ worker = res->second.lock();
++ }
++}
++
++NVMeMi::Worker::Worker()
++{ // start worker thread
+ workerStop = false;
+ thread = std::thread([&io = workerIO, &stop = workerStop, &mtx = workerMtx,
+ &cv = workerCv]() {
+@@ -84,7 +110,7 @@ NVMeMi::NVMeMi(boost::asio::io_context& io, sdbusplus::bus_t& dbus, int bus,
+ });
+ }
+
+-NVMeMi::~NVMeMi()
++NVMeMi::Worker::~Worker()
+ {
+ // close worker
+ workerStop = true;
+@@ -93,7 +119,9 @@ NVMeMi::~NVMeMi()
+ workerCv.notify_all();
+ }
+ thread.join();
+-
++}
++NVMeMi::~NVMeMi()
++{
+ // close EP
+ if (nvmeEP)
+ {
+@@ -103,7 +131,7 @@ NVMeMi::~NVMeMi()
+ // TODO: delete mctp ep from mctpd
+ }
+
+-void NVMeMi::post(std::function<void(void)>&& func)
++void NVMeMi::Worker::post(std::function<void(void)>&& func)
+ {
+ if (!workerStop)
+ {
+diff --git a/src/NVMeMi.hpp b/src/NVMeMi.hpp
+index 0d9fe2c..3071596 100644
+--- a/src/NVMeMi.hpp
++++ b/src/NVMeMi.hpp
+@@ -61,11 +61,34 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+ uint8_t eid;
+ std::string mctpPath;
+
+- bool workerStop;
+- std::mutex workerMtx;
+- std::condition_variable workerCv;
+- boost::asio::io_context workerIO;
+- std::thread thread;
++ // A worker thread for calling NVMeMI cmd.
++ class Worker
++ {
++ private:
++ bool workerStop;
++ std::mutex workerMtx;
++ std::condition_variable workerCv;
++ boost::asio::io_context workerIO;
++ std::thread thread;
++
++ public:
++ Worker();
++ Worker(const Worker&) = delete;
++ ~Worker();
++ void post(std::function<void(void)>&& func);
++ };
++
++ // A map from root bus number to the Worker
++ // This map means to reuse the same worker for all NVMe EP under the same
++ // I2C root bus. There is no real physical concurrency among the i2c/mctp
++ // devices on the same bus. Though mctp kernel drive can schedule and
++ // sequencialize the transactions but assigning individual worker thread to
++ // each EP makes no sense.
++ static std::map<int, std::weak_ptr<Worker>> workerMap;
+
+- void post(std::function<void(void)>&& func);
++ std::shared_ptr<Worker> worker;
++ void post(std::function<void(void)>&& func)
++ {
++ worker->post(std::move(func));
++ }
+ };
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0023-nvmesensor-delay-subsystem-creation.patch b/recipes-phosphor/sensors/dbus-sensors/0023-nvmesensor-delay-subsystem-creation.patch
new file mode 100644
index 0000000..a0632dd
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0023-nvmesensor-delay-subsystem-creation.patch
@@ -0,0 +1,118 @@
+From 8fc4305b3d5188170615c7fbbf05704e021b8d17 Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Thu, 23 Feb 2023 00:55:46 +0000
+Subject: [PATCH 23/34] nvmesensor: delay subsystem creation
+
+Put the subsystem creation into a second loop. So all NVMeIntf should be
+initiated ahead of the subsystem. It is due to the timing requirement
+from mctpd for NVMeMiIntf.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: Ia210c143722259486bbd6d23be6eec63438060b4
+---
+ src/NVMeSensorMain.cpp | 63 ++++++++++++++++++++++++++++++++----------
+ 1 file changed, 48 insertions(+), 15 deletions(-)
+
+diff --git a/src/NVMeSensorMain.cpp b/src/NVMeSensorMain.cpp
+index 0656d2c..3f8797f 100644
+--- a/src/NVMeSensorMain.cpp
++++ b/src/NVMeSensorMain.cpp
+@@ -96,7 +96,15 @@ static void handleConfigurations(
+ }
+ nvmeSubsysMap.clear();
+
+- // iterate through all found configurations
++ /* We perform two iterations for configurations here. The first iteration is
++ * to set up NVMeIntf. The second iter is to setup NVMe subsystem.
++ *
++ * The reason to seperate these two processes is NVMeIntf initialization of
++ * NVMeMI is via MCTPd, from which the mctp control msg should be relatively
++ * short and should not be delayed by NVMe-MI protocol msg from NVMe
++ * subsystem.
++ */
++ std::map<std::string, std::shared_ptr<NVMeIntf>> nvmeInterfaces;
+ for (const auto& [interfacePath, configData] : nvmeConfigurations)
+ {
+ // find base configuration
+@@ -137,16 +145,11 @@ static void handleConfigurations(
+ {
+ std::shared_ptr<NVMeIntf> nvmeBasic{
+ new NVMeBasic(io, *busNumber, *address)};
+-
+- auto nvmeSubsys = std::make_shared<NVMeSubsystem>(
+- io, objectServer, dbusConnection, interfacePath,
+- *sensorName, nvmeBasic);
+- nvmeSubsysMap.emplace(interfacePath, nvmeSubsys);
+- nvmeSubsys->start(configData);
++ nvmeInterfaces.emplace(interfacePath, nvmeBasic);
+ }
+ catch (std::exception& ex)
+ {
+- std::cerr << "Failed to add subsystem for "
++ std::cerr << "Failed to add nvme basic interface for "
+ << std::string(interfacePath) << ": " << ex.what()
+ << "\n";
+ continue;
+@@ -164,22 +167,52 @@ static void handleConfigurations(
+ std::shared_ptr<NVMeIntf> nvmeMi{new NVMeMi(
+ io, dynamic_cast<sdbusplus::bus_t&>(*dbusConnection),
+ *busNumber, *address)};
+-
+- auto nvmeSubsys = std::make_shared<NVMeSubsystem>(
+- io, objectServer, dbusConnection, interfacePath,
+- *sensorName, nvmeMi);
+- nvmeSubsysMap.emplace(interfacePath, nvmeSubsys);
+- nvmeSubsys->start(configData);
++ nvmeInterfaces.emplace(interfacePath, nvmeMi);
+ }
+ catch (std::exception& ex)
+ {
+- std::cerr << "Failed to add subsystem for "
++ std::cerr << "Failed to add nvme mi interface for "
+ << std::string(interfacePath) << ": " << ex.what()
+ << "\n";
+ continue;
+ }
+ }
+ }
++
++ for (const auto& [interfacePath, configData] : nvmeConfigurations)
++ {
++ // find base configuration
++ auto sensorBase =
++ configData.find(configInterfaceName(NVMeSubsystem::sensorType));
++ if (sensorBase == configData.end())
++ {
++ continue;
++ }
++
++ const SensorBaseConfigMap& sensorConfig = sensorBase->second;
++
++ std::optional<std::string> sensorName =
++ extractName(interfacePath, sensorConfig);
++
++ auto find = nvmeInterfaces.find(interfacePath);
++ if (find == nvmeInterfaces.end())
++ continue;
++ try
++ {
++ auto nvmeSubsys = std::make_shared<NVMeSubsystem>(
++ io, objectServer, dbusConnection, interfacePath, *sensorName,
++ std::move(find->second));
++ nvmeSubsysMap.emplace(interfacePath, nvmeSubsys);
++ nvmeSubsys->start(configData);
++ }
++ catch (std::exception& ex)
++ {
++ std::cerr << "Failed to add nvme subsystem for "
++ << std::string(interfacePath) << ": " << ex.what()
++ << "\n";
++ continue;
++ }
++ }
+ }
+
+ void createNVMeSubsystems(
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0024-nvmesensor-clean-up-the-association.patch b/recipes-phosphor/sensors/dbus-sensors/0024-nvmesensor-clean-up-the-association.patch
new file mode 100644
index 0000000..599e1b6
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0024-nvmesensor-clean-up-the-association.patch
@@ -0,0 +1,256 @@
+From c6460b06d61e06229061d85041424b7f280594a8 Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Fri, 31 Mar 2023 17:32:34 +0000
+Subject: [PATCH 24/34] nvmesensor: clean up the association
+
+The association now belonges to the child component:
+* subsystem: association to chassis and between storage/drive
+* controller: association to subsystem and betwen primary/secondary
+
+This is to bind the lifetime of the association to the corresponding
+component.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: I845f49de65a17ec5d3d1bee747851b34a4557a05
+---
+ src/NVMeController.cpp | 50 ++++++++++++++++++++++++++++++++----------
+ src/NVMeController.hpp | 17 +++++++++++---
+ src/NVMeSubsys.cpp | 44 ++++++++++++++++++-------------------
+ src/NVMeSubsys.hpp | 4 +++-
+ 4 files changed, 76 insertions(+), 39 deletions(-)
+
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index c5333e1..7e26643 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -26,6 +26,12 @@ NVMeController::NVMeController(
+ {
+ StorageController::emit_added();
+ NVMeAdmin::emit_added();
++ assocIntf = objServer.add_interface(
++ path, "xyz.openbmc_project.Association.Definitions");
++
++ // regiester a property with empty association
++ assocIntf->register_property("Associations", std::vector<Association>{});
++ assocIntf->initialize();
+ }
+
+ void NVMeController::start(std::shared_ptr<NVMeControllerPlugin> nvmePlugin)
+@@ -193,6 +199,7 @@ void NVMeController::firmwareCommitAsync(uint8_t commitAction,
+
+ NVMeController::~NVMeController()
+ {
++ objServer.remove_interface(assocIntf);
+ NVMeAdmin::emit_removed();
+ StorageController::emit_removed();
+ }
+@@ -200,27 +207,46 @@ NVMeController::~NVMeController()
+ void NVMeController::setSecAssoc(
+ const std::vector<std::shared_ptr<NVMeController>> secCntrls)
+ {
++ secondaryControllers.clear();
+
+- if (secAssoc)
++ if (secCntrls.empty())
+ {
+- objServer.remove_interface(secAssoc);
+- secAssoc.reset();
++ return;
+ }
+
+- if (secCntrls.empty())
++ for (const auto& cntrl : secCntrls)
+ {
+- return;
++ secondaryControllers.push_back(cntrl->path);
+ }
+
+- using Association = std::tuple<std::string, std::string, std::string>;
+- secAssoc = objServer.add_interface(
+- path, "xyz.openbmc_project.Association.Definitions");
+ std::vector<Association> associations;
++ for (const auto& subsys : subsystems)
++ {
++ associations.emplace_back("storage", "storage_controller", subsys);
++ }
+
+- for (auto& cntrl : secCntrls)
++ for (const auto& cntrl : secondaryControllers)
+ {
+- associations.emplace_back("secondary", "primary", cntrl->path);
++ associations.emplace_back("secondary", "primary", cntrl);
+ }
+- secAssoc->register_property("Associations", associations);
+- secAssoc->initialize();
++
++ assocIntf->set_property("Associations", associations);
++}
++
++void NVMeController::addSubsystemAssociation(const std::string& subsysPath)
++{
++ subsystems.push_back(subsysPath);
++
++ std::vector<Association> associations;
++ for (const auto& subsys : subsystems)
++ {
++ associations.emplace_back("storage", "storage_controller", subsys);
++ }
++
++ for (const auto& cntrl : secondaryControllers)
++ {
++ associations.emplace_back("secondary", "primary", cntrl);
++ }
++
++ assocIntf->set_property("Associations", associations);
+ }
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+index 592c3de..c964b87 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -52,6 +52,14 @@ class NVMeController :
+ std::max(sizeof(uint16_t), sizeof(void*))));
+ }
+
++ /**
++ * @brief Register the NVMe subsystem to the controller. The function can be
++ * called mutiple times to associate multi-subsys to a single controller.
++ *
++ * @param subsysPath Path to the subsystem
++ */
++ void addSubsystemAssociation(const std::string& subsysPath);
++
+ private:
+ friend class NVMeControllerPlugin;
+
+@@ -63,9 +71,12 @@ class NVMeController :
+ std::shared_ptr<NVMeMiIntf> nvmeIntf;
+ nvme_mi_ctrl_t nvmeCtrl;
+
+- // The Association interface to secondary controllers from a primary
+- // controller
+- std::shared_ptr<sdbusplus::asio::dbus_interface> secAssoc;
++ std::shared_ptr<sdbusplus::asio::dbus_interface> assocIntf;
++ // The association to subsystems
++ std::vector<std::string> subsystems;
++
++ // The association to secondary controllers from a primary controller
++ std::vector<std::string> secondaryControllers;
+
+ // NVMe Plug-in for vendor defined command/field
+ std::weak_ptr<NVMeControllerPlugin> plugin;
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index 90bad81..6fd78b0 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -7,21 +7,18 @@
+
+ void NVMeSubsystem::createStorageAssociation()
+ {
+- auto storageAssociation =
+- objServer.add_interface(path, association::interface);
++ std::vector<Association> associations;
++ std::filesystem::path p(path);
++
++ associations.emplace_back("chassis", "storage", p.parent_path().string());
++ associations.emplace_back("chassis", "drive", p.parent_path().string());
++ associations.emplace_back("drive", "storage", path);
+
+- storageAssociation->register_property(
+- "Associations", associations,
+- // custom set
+- [&](const std::vector<Association>&,
+- std::vector<Association>& propertyValue) {
+- propertyValue = associations;
+- return false;
+- },
+- // custom get
+- [&](const std::vector<Association>&) { return associations; });
++ assocIntf = objServer.add_interface(path, association::interface);
+
+- storageAssociation->initialize();
++ assocIntf->register_property("Associations", associations);
++
++ assocIntf->initialize();
+ }
+
+ // get temporature from a NVMe Basic reading.
+@@ -67,10 +64,12 @@ NVMeSubsystem::NVMeSubsystem(boost::asio::io_context& io,
+
+ /* xyz.openbmc_project.Inventory.Item.Storage */
+ // make association for Drive/Storage/Chassis
+- std::filesystem::path p(path);
+- associations.emplace_back("chassis", "storage", p.parent_path().string());
+- associations.emplace_back("chassis", "drive", p.parent_path().string());
+- associations.emplace_back("drive", "storage", path);
++ createStorageAssociation();
++}
++
++NVMeSubsystem::~NVMeSubsystem()
++{
++ objServer.remove_interface(assocIntf);
+ }
+
+ void NVMeSubsystem::start(const SensorData& configData)
+@@ -87,7 +86,7 @@ void NVMeSubsystem::start(const SensorData& configData)
+ // TODO: mark the subsystem invalid and reschedule refresh
+ std::cerr << "fail to scan controllers for the nvme subsystem"
+ << (ec ? ": " + ec.message() : "") << std::endl;
+- self->createStorageAssociation();
++ // self->createStorageAssociation();
+ return;
+ }
+
+@@ -121,6 +120,9 @@ void NVMeSubsystem::start(const SensorData& configData)
+ {*index, {nvmeController, {}}});
+ auto& ctrlPlugin = iter->second.second;
+
++ // set StorageController Association
++ nvmeController->addSubsystemAssociation(self->path);
++
+ // creat controller plugin
+ if (self->plugin)
+ {
+@@ -128,10 +130,6 @@ void NVMeSubsystem::start(const SensorData& configData)
+ *nvmeController, configData);
+ }
+ nvmeController->start(ctrlPlugin);
+-
+- // set StorageController Association
+- self->associations.emplace_back("storage_controller",
+- "storage", path);
+ }
+ catch (const std::exception& e)
+ {
+@@ -142,7 +140,7 @@ void NVMeSubsystem::start(const SensorData& configData)
+
+ index++;
+ }
+- self->createStorageAssociation();
++ // self->createStorageAssociation();
+
+ /*
+ find primary controller and make association
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index 8af2497..0c95318 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -21,6 +21,8 @@ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+ const std::string& path, const std::string& name,
+ const std::shared_ptr<NVMeIntf>& intf);
+
++ ~NVMeSubsystem();
++
+ void start(const SensorData& configData);
+
+ void stop();
+@@ -57,6 +59,6 @@ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+ std::shared_ptr<NVMeControllerPlugin>>>
+ controllers{};
+
+- std::vector<Association> associations;
++ std::shared_ptr<sdbusplus::asio::dbus_interface> assocIntf;
+ void createStorageAssociation();
+ };
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0025-nvmesensor-change-the-controller-init-sequence.patch b/recipes-phosphor/sensors/dbus-sensors/0025-nvmesensor-change-the-controller-init-sequence.patch
new file mode 100644
index 0000000..c98b4f9
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0025-nvmesensor-change-the-controller-init-sequence.patch
@@ -0,0 +1,67 @@
+From b0cdd9da9ad7ecc2379c4c79aeb66dac07030e14 Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Thu, 6 Apr 2023 23:49:17 +0000
+Subject: [PATCH 25/34] nvmesensor: change the controller init sequence
+
+The start should be the last step for controller initialization process.
+Since the start may depend on the info from intialization.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: Ie3f3c53c98c5410076533600448646d996526957
+---
+ src/NVMeSubsys.cpp | 19 ++++++++++++-------
+ 1 file changed, 12 insertions(+), 7 deletions(-)
+
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index 6fd78b0..42d9717 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -108,7 +108,7 @@ void NVMeSubsystem::start(const SensorData& configData)
+ std::filesystem::path path = std::filesystem::path(self->path) /
+ "controllers" /
+ std::to_string(*index);
+-
++
+ try
+ {
+ auto nvmeController = std::make_shared<NVMeController>(
+@@ -118,18 +118,17 @@ void NVMeSubsystem::start(const SensorData& configData)
+ // insert the controllers with empty plugin
+ auto [iter, _] = self->controllers.insert(
+ {*index, {nvmeController, {}}});
+- auto& ctrlPlugin = iter->second.second;
+
+- // set StorageController Association
+- nvmeController->addSubsystemAssociation(self->path);
+-
+- // creat controller plugin
++ // create controller plugin
+ if (self->plugin)
+ {
++ auto& ctrlPlugin = iter->second.second;
+ ctrlPlugin = self->plugin->createControllerPlugin(
+ *nvmeController, configData);
+ }
+- nvmeController->start(ctrlPlugin);
++
++ // set StorageController Association
++ nvmeController->addSubsystemAssociation(self->path);
+ }
+ catch (const std::exception& e)
+ {
+@@ -202,6 +201,12 @@ void NVMeSubsystem::start(const SensorData& configData)
+ secCntrls.push_back(findSecondary->second.first);
+ }
+ findPrimary->second.first->setSecAssoc(secCntrls);
++
++ // start controller
++ for (auto& [_, pair] : self->controllers)
++ {
++ pair.first->start(pair.second);
++ }
+ });
+ });
+ }
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0026-nvmesensor-Check-SCS-for-secondary-controllers.patch b/recipes-phosphor/sensors/dbus-sensors/0026-nvmesensor-Check-SCS-for-secondary-controllers.patch
new file mode 100644
index 0000000..1736075
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0026-nvmesensor-Check-SCS-for-secondary-controllers.patch
@@ -0,0 +1,327 @@
+From 0d458922aa59bed85f2dbfe10db4f4d0d380ae57 Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Fri, 7 Apr 2023 23:51:14 +0000
+Subject: [PATCH 26/34] nvmesensor: Check SCS for secondary controllers
+
+The Secondary Controller State indicates if the secondary controller has
+been enabled by CC.EN. For disabled controllers, the StorageController
+and NVMeAdmin interface should not be created.
+
+It is because the ctrl_list will list all controllers in the system
+regardless of the enable status. The number of supported controllers can
+be large compare to the active ones in the system. In our example
+system, the total controllers in one SSD can live up to 128 but we
+only enble one or two of them. The unused controller can seriously slow
+down the performance of dbus if we chose to expose them.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: I6fdbb503550968d013e16822f9d11e9962bb93a4
+---
+ src/NVMeController.cpp | 100 ++++++++++++++++++++++++++++++++---------
+ src/NVMeController.hpp | 54 ++++++++++++++++++----
+ src/NVMeSubsys.cpp | 19 +++++++-
+ 3 files changed, 141 insertions(+), 32 deletions(-)
+
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index 7e26643..e40da17 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -14,35 +14,67 @@ using sdbusplus::xyz::openbmc_project::Inventory::Item::server::
+ StorageController;
+ using sdbusplus::xyz::openbmc_project::NVMe::server::NVMeAdmin;
+
+-NVMeController::NVMeController(
++NVMeControllerEnabled::NVMeControllerEnabled(
+ boost::asio::io_context& io, sdbusplus::asio::object_server& objServer,
+ std::shared_ptr<sdbusplus::asio::connection> conn, std::string path,
+ std::shared_ptr<NVMeMiIntf> nvmeIntf, nvme_mi_ctrl_t ctrl) :
++ NVMeController(io, objServer, conn, path, nvmeIntf, ctrl),
+ StorageController(dynamic_cast<sdbusplus::bus_t&>(*conn), path.c_str()),
+ NVMeAdmin(*conn, path.c_str(),
+- {{"FirmwareCommitStatus", {FwCommitStatus::Ready}}}),
+- io(io), objServer(objServer), conn(conn), path(path), nvmeIntf(nvmeIntf),
+- nvmeCtrl(ctrl)
++ {{"FirmwareCommitStatus", {FwCommitStatus::Ready}}})
+ {
+- StorageController::emit_added();
+- NVMeAdmin::emit_added();
+ assocIntf = objServer.add_interface(
+ path, "xyz.openbmc_project.Association.Definitions");
+
+ // regiester a property with empty association
+ assocIntf->register_property("Associations", std::vector<Association>{});
+ assocIntf->initialize();
++
++ StorageController::emit_added();
++ NVMeAdmin::emit_added();
+ }
+
+-void NVMeController::start(std::shared_ptr<NVMeControllerPlugin> nvmePlugin)
++NVMeControllerEnabled::NVMeControllerEnabled(NVMeController&& nvmeController) :
++ NVMeController(std::move(nvmeController)),
++ StorageController(
++ dynamic_cast<sdbusplus::bus_t&>(*this->NVMeController::conn),
++ this->NVMeController::path.c_str()),
++ NVMeAdmin(*this->NVMeController::conn, this->NVMeController::path.c_str(),
++ {{"FirmwareCommitStatus", {FwCommitStatus::Ready}}})
+ {
+- plugin = nvmePlugin;
++ assocIntf = objServer.add_interface(
++ path, "xyz.openbmc_project.Association.Definitions");
++
++ // the association could have be set via NVMeController, set the association
++ // dbus property accordingly
++ std::vector<Association> associations;
++ for (const auto& subsys : subsystems)
++ {
++ associations.emplace_back("storage", "storage_controller", subsys);
++ }
++
++ for (const auto& cntrl : secondaryControllers)
++ {
++ associations.emplace_back("secondary", "primary", cntrl);
++ }
++
++ assocIntf->register_property("Associations", associations);
++ assocIntf->initialize();
++
++ StorageController::emit_added();
++ NVMeAdmin::emit_added();
++}
++
++void NVMeControllerEnabled::start(
++ std::shared_ptr<NVMeControllerPlugin> nvmePlugin)
++{
++ this->NVMeController::start(std::move(nvmePlugin));
+ }
+
+-sdbusplus::message::unix_fd NVMeController::getLogPage(uint8_t lid,
+- uint32_t nsid,
+- uint8_t lsp,
+- uint16_t lsi)
++sdbusplus::message::unix_fd NVMeControllerEnabled::getLogPage(uint8_t lid,
++ uint32_t nsid,
++ uint8_t lsp,
++ uint16_t lsi)
+ {
+ std::array<int, 2> pipe;
+ if (::pipe(pipe.data()) < 0)
+@@ -121,8 +153,8 @@ sdbusplus::message::unix_fd NVMeController::getLogPage(uint8_t lid,
+ return sdbusplus::message::unix_fd{pipe[0]};
+ }
+
+-sdbusplus::message::unix_fd NVMeController::identify(uint8_t cns, uint32_t nsid,
+- uint16_t cntid)
++sdbusplus::message::unix_fd
++ NVMeControllerEnabled::identify(uint8_t cns, uint32_t nsid, uint16_t cntid)
+ {
+ std::array<int, 2> pipe;
+ if (::pipe(pipe.data()) < 0)
+@@ -154,8 +186,8 @@ sdbusplus::message::unix_fd NVMeController::identify(uint8_t cns, uint32_t nsid,
+ return sdbusplus::message::unix_fd{pipe[0]};
+ }
+
+-NVMeAdmin::FwCommitStatus
+- NVMeController::firmwareCommitStatus(NVMeAdmin::FwCommitStatus status)
++NVMeAdmin::FwCommitStatus NVMeControllerEnabled::firmwareCommitStatus(
++ NVMeAdmin::FwCommitStatus status)
+ {
+ auto commitStatus = this->NVMeAdmin::firmwareCommitStatus();
+ // The function is only allowed to reset the status back to ready
+@@ -168,8 +200,8 @@ NVMeAdmin::FwCommitStatus
+ return this->NVMeAdmin::firmwareCommitStatus(status);
+ }
+
+-void NVMeController::firmwareCommitAsync(uint8_t commitAction,
+- uint8_t firmwareSlot, bool bpid)
++void NVMeControllerEnabled::firmwareCommitAsync(uint8_t commitAction,
++ uint8_t firmwareSlot, bool bpid)
+ {
+ auto commitStatus = this->NVMeAdmin::firmwareCommitStatus();
+ if (commitStatus != FwCommitStatus::Ready)
+@@ -197,13 +229,31 @@ void NVMeController::firmwareCommitAsync(uint8_t commitAction,
+ });
+ }
+
+-NVMeController::~NVMeController()
++NVMeControllerEnabled::~NVMeControllerEnabled()
+ {
+- objServer.remove_interface(assocIntf);
+ NVMeAdmin::emit_removed();
+ StorageController::emit_removed();
+ }
+
++NVMeController::NVMeController(
++ boost::asio::io_context& io, sdbusplus::asio::object_server& objServer,
++ std::shared_ptr<sdbusplus::asio::connection> conn, std::string path,
++ std::shared_ptr<NVMeMiIntf> nvmeIntf, nvme_mi_ctrl_t ctrl) :
++ io(io),
++ objServer(objServer), conn(conn), path(path), nvmeIntf(nvmeIntf),
++ nvmeCtrl(ctrl)
++{}
++
++NVMeController::~NVMeController()
++{
++ objServer.remove_interface(assocIntf);
++}
++
++void NVMeController::start(std::shared_ptr<NVMeControllerPlugin> nvmePlugin)
++{
++ plugin = nvmePlugin;
++}
++
+ void NVMeController::setSecAssoc(
+ const std::vector<std::shared_ptr<NVMeController>> secCntrls)
+ {
+@@ -230,7 +280,10 @@ void NVMeController::setSecAssoc(
+ associations.emplace_back("secondary", "primary", cntrl);
+ }
+
+- assocIntf->set_property("Associations", associations);
++ if (assocIntf)
++ {
++ assocIntf->set_property("Associations", associations);
++ }
+ }
+
+ void NVMeController::addSubsystemAssociation(const std::string& subsysPath)
+@@ -248,5 +301,8 @@ void NVMeController::addSubsystemAssociation(const std::string& subsysPath)
+ associations.emplace_back("secondary", "primary", cntrl);
+ }
+
+- assocIntf->set_property("Associations", associations);
++ if (assocIntf)
++ {
++ assocIntf->set_property("Associations", associations);
++ }
+ }
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+index c964b87..5a0f36f 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -12,12 +12,18 @@
+ #include <utility>
+
+ class NVMeControllerPlugin;
+-class NVMeController :
+- private sdbusplus::xyz::openbmc_project::Inventory::Item::server::
+- StorageController,
+- private sdbusplus::xyz::openbmc_project::NVMe::server::NVMeAdmin,
+- public std::enable_shared_from_this<NVMeController>
+
++/**
++ * @brief A class to represent the NVMeController has not been enabled (CC.EN =
++ * 0)
++ *
++ * The disabled controllers still have cntrl_id and are listed in the
++ * cntrl_list. However the functionility has been disabled so neither
++ * StorageController nor NVMeAdmin interface should be exposed for the disabled
++ * controllers.
++ *
++ */
++class NVMeController
+ {
+ public:
+ NVMeController(boost::asio::io_context& io,
+@@ -26,9 +32,9 @@ class NVMeController :
+ std::string path, std::shared_ptr<NVMeMiIntf> nvmeIntf,
+ nvme_mi_ctrl_t ctrl);
+
+- ~NVMeController() override;
++ virtual ~NVMeController();
+
+- void start(std::shared_ptr<NVMeControllerPlugin> nvmePlugin);
++ virtual void start(std::shared_ptr<NVMeControllerPlugin> nvmePlugin);
+
+ // setup association to the secondary controllers. Clear the Association if
+ // empty.
+@@ -60,7 +66,7 @@ class NVMeController :
+ */
+ void addSubsystemAssociation(const std::string& subsysPath);
+
+- private:
++ protected:
+ friend class NVMeControllerPlugin;
+
+ boost::asio::io_context& io;
+@@ -80,7 +86,39 @@ class NVMeController :
+
+ // NVMe Plug-in for vendor defined command/field
+ std::weak_ptr<NVMeControllerPlugin> plugin;
++};
++
++/**
++ * @brief A class for the NVMe controller that has been enabled (CC.EN = 1)
++ *
++ * The premitted NVMe Admin cmds should be anable to processed via the enabled
++ * controller (e.g reading the temletries or other admin tasks). Thus the
++ * NVMeAmin and StorageController Dbus interface will be exposed via this class.
++ *
++ */
++class NVMeControllerEnabled :
++ public NVMeController,
++ private sdbusplus::xyz::openbmc_project::Inventory::Item::server::
++ StorageController,
++ private sdbusplus::xyz::openbmc_project::NVMe::server::NVMeAdmin,
++ public std::enable_shared_from_this<NVMeControllerEnabled>
++
++{
++ public:
++ NVMeControllerEnabled(boost::asio::io_context& io,
++ sdbusplus::asio::object_server& objServer,
++ std::shared_ptr<sdbusplus::asio::connection> conn,
++ std::string path,
++ std::shared_ptr<NVMeMiIntf> nvmeIntf,
++ nvme_mi_ctrl_t ctrl);
+
++ NVMeControllerEnabled(NVMeController&& nvmeController);
++
++ ~NVMeControllerEnabled() override;
++
++ void start(std::shared_ptr<NVMeControllerPlugin> nvmePlugin) override;
++
++ private:
+ /* NVMeAdmin method overload */
+
+ /** @brief Implementation for GetLogPage
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index 42d9717..e9ad699 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -186,6 +186,12 @@ void NVMeSubsystem::start(const SensorData& configData)
+ << std::endl;
+ return;
+ }
++
++ // Enable primary controller since they are required to work
++ auto& primaryController = findPrimary->second.first;
++ primaryController.reset(new NVMeControllerEnabled(
++ std::move(*primaryController.get())));
++
+ std::vector<std::shared_ptr<NVMeController>> secCntrls;
+ for (int i = 0; i < listHdr.num; i++)
+ {
+@@ -198,9 +204,18 @@ void NVMeSubsystem::start(const SensorData& configData)
+ << std::endl;
+ break;
+ }
+- secCntrls.push_back(findSecondary->second.first);
++
++ auto& secondaryController = findSecondary->second.first;
++
++ // Check Secondary Controller State
++ if (listHdr.sc_entry[i].scs != 0)
++ {
++ secondaryController.reset(new NVMeControllerEnabled(
++ std::move(*secondaryController.get())));
++ }
++ secCntrls.push_back(secondaryController);
+ }
+- findPrimary->second.first->setSecAssoc(secCntrls);
++ primaryController->setSecAssoc(secCntrls);
+
+ // start controller
+ for (auto& [_, pair] : self->controllers)
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0027-Enable-asio-threads-and-boost-coroutine.patch b/recipes-phosphor/sensors/dbus-sensors/0027-Enable-asio-threads-and-boost-coroutine.patch
new file mode 100644
index 0000000..6d18ad3
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0027-Enable-asio-threads-and-boost-coroutine.patch
@@ -0,0 +1,43 @@
+From af7572953ba58aa5adcb7f77c69594cfdd6453ec Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Wed, 18 Jan 2023 15:53:54 +0800
+Subject: [PATCH 27/34] Enable asio threads and boost coroutine
+
+ASIO thread support is required to use a worker thread pool,
+coroutines will be used for asynchronous dbus method handlers.
+
+Stripped binary size difference is negligible, for
+nvmesensor 723632 vs 723696 bytes.
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: I9930601e0be158fd388aed3f1e61722cafe45b23
+---
+ meson.build | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/meson.build b/meson.build
+index fdbecfd..bdcbb93 100644
+--- a/meson.build
++++ b/meson.build
+@@ -30,8 +30,8 @@ add_project_arguments(
+ '-DBOOST_NO_RTTI',
+ '-DBOOST_NO_TYPEID',
+ '-DBOOST_ALL_NO_LIB',
+- '-DBOOST_ASIO_DISABLE_THREADS',
+ '-DBOOST_ALLOW_DEPRECATED_HEADERS',
++ '-DBOOST_ASIO_HAS_THREADS',
+ language: 'cpp',
+ )
+
+@@ -66,7 +66,7 @@ systemd_system_unit_dir = systemd.get_variable(
+ pkgconfig_define: ['prefix', get_option('prefix')])
+ threads = dependency('threads')
+
+-boost = dependency('boost',version : '>=1.79.0', required : false, include_type: 'system')
++boost = dependency('boost',version : '>=1.79.0', required : false, include_type: 'system', modules: ['coroutine'])
+ if not boost.found()
+ subproject('boost', required: false)
+ boost_inc = include_directories('subprojects/boost_1_79_0/', is_system:true)
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0028-nvmesensor-Split-constructor-for-shared_from_this.patch b/recipes-phosphor/sensors/dbus-sensors/0028-nvmesensor-Split-constructor-for-shared_from_this.patch
new file mode 100644
index 0000000..97121ff
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0028-nvmesensor-Split-constructor-for-shared_from_this.patch
@@ -0,0 +1,152 @@
+From abcfdf873fce129504fb009b84615e91abeeabe2 Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Wed, 18 Jan 2023 16:06:41 +0800
+Subject: [PATCH 28/34] nvmesensor: Split constructor for shared_from_this
+
+This allows using shared_from_this during initialisation
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: I25ba721d4de2eaaa5cd70a2509a87041f9789a78
+---
+ src/NVMeController.cpp | 37 ++++++++++++++++++++++++++-----------
+ src/NVMeController.hpp | 20 +++++++++++++++-----
+ src/NVMeSubsys.cpp | 8 ++++----
+ 3 files changed, 45 insertions(+), 20 deletions(-)
+
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index e40da17..cec6664 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -14,6 +14,28 @@ using sdbusplus::xyz::openbmc_project::Inventory::Item::server::
+ StorageController;
+ using sdbusplus::xyz::openbmc_project::NVMe::server::NVMeAdmin;
+
++std::shared_ptr<NVMeControllerEnabled> NVMeControllerEnabled::create(
++ boost::asio::io_context& io, sdbusplus::asio::object_server& objServer,
++ std::shared_ptr<sdbusplus::asio::connection> conn, std::string path,
++ std::shared_ptr<NVMeMiIntf> nvmeIntf, nvme_mi_ctrl_t ctrl)
++{
++
++ auto self = std::shared_ptr<NVMeControllerEnabled>(
++ new NVMeControllerEnabled(io, objServer, conn, path, nvmeIntf, ctrl));
++ self->init();
++ return self;
++}
++
++std::shared_ptr<NVMeControllerEnabled>
++ NVMeControllerEnabled::create(NVMeController&& nvmeController)
++{
++
++ auto self = std::shared_ptr<NVMeControllerEnabled>(
++ new NVMeControllerEnabled(std::move(nvmeController)));
++ self->init();
++ return self;
++}
++
+ NVMeControllerEnabled::NVMeControllerEnabled(
+ boost::asio::io_context& io, sdbusplus::asio::object_server& objServer,
+ std::shared_ptr<sdbusplus::asio::connection> conn, std::string path,
+@@ -22,17 +44,7 @@ NVMeControllerEnabled::NVMeControllerEnabled(
+ StorageController(dynamic_cast<sdbusplus::bus_t&>(*conn), path.c_str()),
+ NVMeAdmin(*conn, path.c_str(),
+ {{"FirmwareCommitStatus", {FwCommitStatus::Ready}}})
+-{
+- assocIntf = objServer.add_interface(
+- path, "xyz.openbmc_project.Association.Definitions");
+-
+- // regiester a property with empty association
+- assocIntf->register_property("Associations", std::vector<Association>{});
+- assocIntf->initialize();
+-
+- StorageController::emit_added();
+- NVMeAdmin::emit_added();
+-}
++{}
+
+ NVMeControllerEnabled::NVMeControllerEnabled(NVMeController&& nvmeController) :
+ NVMeController(std::move(nvmeController)),
+@@ -41,6 +53,9 @@ NVMeControllerEnabled::NVMeControllerEnabled(NVMeController&& nvmeController) :
+ this->NVMeController::path.c_str()),
+ NVMeAdmin(*this->NVMeController::conn, this->NVMeController::path.c_str(),
+ {{"FirmwareCommitStatus", {FwCommitStatus::Ready}}})
++{}
++
++void NVMeControllerEnabled::init()
+ {
+ assocIntf = objServer.add_interface(
+ path, "xyz.openbmc_project.Association.Definitions");
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+index 5a0f36f..0c567f9 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -3,7 +3,7 @@
+
+ #include "NVMeIntf.hpp"
+
+-#include <boost/asio/io_context.hpp>
++#include <boost/asio.hpp>
+ #include <sdbusplus/asio/connection.hpp>
+ #include <sdbusplus/asio/object_server.hpp>
+ #include <xyz/openbmc_project/Inventory/Item/StorageController/server.hpp>
+@@ -105,6 +105,19 @@ class NVMeControllerEnabled :
+
+ {
+ public:
++ static std::shared_ptr<NVMeControllerEnabled> create(
++ boost::asio::io_context& io, sdbusplus::asio::object_server& objServer,
++ std::shared_ptr<sdbusplus::asio::connection> conn, std::string path,
++ std::shared_ptr<NVMeMiIntf> nvmeIntf, nvme_mi_ctrl_t ctrl);
++
++ static std::shared_ptr<NVMeControllerEnabled>
++ create(NVMeController&& nvmeController);
++
++ ~NVMeControllerEnabled() override;
++
++ void start(std::shared_ptr<NVMeControllerPlugin> nvmePlugin) override;
++
++ private:
+ NVMeControllerEnabled(boost::asio::io_context& io,
+ sdbusplus::asio::object_server& objServer,
+ std::shared_ptr<sdbusplus::asio::connection> conn,
+@@ -114,11 +127,8 @@ class NVMeControllerEnabled :
+
+ NVMeControllerEnabled(NVMeController&& nvmeController);
+
+- ~NVMeControllerEnabled() override;
++ void init();
+
+- void start(std::shared_ptr<NVMeControllerPlugin> nvmePlugin) override;
+-
+- private:
+ /* NVMeAdmin method overload */
+
+ /** @brief Implementation for GetLogPage
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index e9ad699..97e879e 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -189,8 +189,8 @@ void NVMeSubsystem::start(const SensorData& configData)
+
+ // Enable primary controller since they are required to work
+ auto& primaryController = findPrimary->second.first;
+- primaryController.reset(new NVMeControllerEnabled(
+- std::move(*primaryController.get())));
++ primaryController = NVMeControllerEnabled::create(
++ std::move(*primaryController.get()));
+
+ std::vector<std::shared_ptr<NVMeController>> secCntrls;
+ for (int i = 0; i < listHdr.num; i++)
+@@ -210,8 +210,8 @@ void NVMeSubsystem::start(const SensorData& configData)
+ // Check Secondary Controller State
+ if (listHdr.sc_entry[i].scs != 0)
+ {
+- secondaryController.reset(new NVMeControllerEnabled(
+- std::move(*secondaryController.get())));
++ secondaryController = NVMeControllerEnabled::create(
++ std::move(*secondaryController.get()));
+ }
+ secCntrls.push_back(secondaryController);
+ }
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0029-nvmesensor-Add-SecuritySend-and-SecurityReceive.patch b/recipes-phosphor/sensors/dbus-sensors/0029-nvmesensor-Add-SecuritySend-and-SecurityReceive.patch
new file mode 100644
index 0000000..b151659
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0029-nvmesensor-Add-SecuritySend-and-SecurityReceive.patch
@@ -0,0 +1,448 @@
+From d902af64fba515d7250ba5ec74924d0e41900d87 Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Wed, 18 Jan 2023 16:33:24 +0800
+Subject: [PATCH 29/34] nvmesensor: Add SecuritySend and SecurityReceive
+
+The dbus interface is manually implemented to allow
+an asynchronous method handler.
+
+This has been tested with SecurityReceive GET_COMID and
+SecuritySend VERIFY_COMID_VALID against a PM1733 drive.
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: Ic5acd45b6ade2651927b0abbfde35551e139d996
+---
+ src/AsioHelper.hpp | 40 ++++++++++++
+ src/NVMeController.cpp | 134 +++++++++++++++++++++++++++++++++++++++++
+ src/NVMeController.hpp | 14 +++++
+ src/NVMeIntf.hpp | 11 ++++
+ src/NVMeMi.cpp | 102 +++++++++++++++++++++++++++++++
+ src/NVMeMi.hpp | 13 ++++
+ 6 files changed, 314 insertions(+)
+ create mode 100644 src/AsioHelper.hpp
+
+diff --git a/src/AsioHelper.hpp b/src/AsioHelper.hpp
+new file mode 100644
+index 0000000..678b296
+--- /dev/null
++++ b/src/AsioHelper.hpp
+@@ -0,0 +1,40 @@
++#pragma once
++
++#include <memory>
++#include <stdexcept>
++#include <utility>
++
++namespace asio_helper
++{
++
++// Wraps a non-copyable callable and makes it copyable (moving into
++// a shared_ptr). It throws a runtime_error if called twice.
++//
++// Can be used to capture a non-copyable callable in a lambda, where
++// the lambda itself needs to be copyable so it can be used with std::function.
++//
++// This is intended to use a boost::asio::basic_yield_context completion token
++// as type F type. It will be called with a single argument.
++template <typename F>
++struct CopyableCallback
++{
++ public:
++ CopyableCallback(F&& cb) : cb(std::make_shared<F>(std::move(cb)))
++ {}
++
++ template <typename R>
++ void operator()(R&& r)
++ {
++ if (!cb)
++ {
++ throw std::runtime_error("CopyableCallback was called twice");
++ }
++ (*cb)(std::move(r));
++ cb.reset();
++ }
++
++ private:
++ std::shared_ptr<F> cb;
++};
++
++} // namespace asio_helper
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index cec6664..1c34a58 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -1,7 +1,9 @@
+ #include "NVMeController.hpp"
+
++#include "AsioHelper.hpp"
+ #include "NVMePlugin.hpp"
+
++#include <sdbusplus/exception.hpp>
+ #include <sdbusplus/message/native_types.hpp>
+ #include <xyz/openbmc_project/Common/File/error.hpp>
+ #include <xyz/openbmc_project/Common/error.hpp>
+@@ -76,6 +78,27 @@ void NVMeControllerEnabled::init()
+ assocIntf->register_property("Associations", associations);
+ assocIntf->initialize();
+
++
++ securityInterface = objServer.add_interface(
++ path, "xyz.openbmc_project.Inventory.Item.StorageControllerSecurity");
++ securityInterface->register_method(
++ "SecuritySend",
++ [self{shared_from_this()}](boost::asio::yield_context yield,
++ uint8_t proto, uint16_t proto_specific,
++ std::vector<uint8_t> data) {
++ return self->securitySendMethod(yield, proto, proto_specific, data);
++ });
++ securityInterface->register_method(
++ "SecurityReceive",
++ [self{shared_from_this()}](boost::asio::yield_context yield,
++ uint8_t proto, uint16_t proto_specific,
++ uint32_t transfer_length) {
++ return self->securityReceiveMethod(yield, proto, proto_specific,
++ transfer_length);
++ });
++
++ securityInterface->initialize();
++
+ StorageController::emit_added();
+ NVMeAdmin::emit_added();
+ }
+@@ -246,6 +269,7 @@ void NVMeControllerEnabled::firmwareCommitAsync(uint8_t commitAction,
+
+ NVMeControllerEnabled::~NVMeControllerEnabled()
+ {
++ objServer.remove_interface(securityInterface);
+ NVMeAdmin::emit_removed();
+ StorageController::emit_removed();
+ }
+@@ -321,3 +345,113 @@ void NVMeController::addSubsystemAssociation(const std::string& subsysPath)
+ assocIntf->set_property("Associations", associations);
+ }
+ }
++
++void NVMeControllerEnabled::securitySendMethod(boost::asio::yield_context yield,
++ uint8_t proto,
++ uint16_t proto_specific,
++ std::span<uint8_t> data)
++{
++ using callback_t = void(std::tuple<std::error_code, int>);
++ auto [err, nvme_status] =
++ boost::asio::async_initiate<boost::asio::yield_context, callback_t>(
++ [intf{nvmeIntf}, ctrl{nvmeCtrl}, proto, proto_specific,
++ &data](auto&& handler) {
++ auto h = asio_helper::CopyableCallback(std::move(handler));
++
++ intf->adminSecuritySend(
++ ctrl, proto, proto_specific, data,
++ [h](const std::error_code& err, int nvme_status) mutable {
++ h(std::make_tuple(err, nvme_status));
++ });
++ },
++ yield);
++
++ // exception must be thrown outside of the async block
++ checkLibNVMeError(err, nvme_status, "SecuritySend");
++}
++
++std::vector<uint8_t> NVMeControllerEnabled::securityReceiveMethod(
++ boost::asio::yield_context yield, uint8_t proto, uint16_t proto_specific,
++ uint32_t transfer_length)
++{
++ using callback_t =
++ void(std::tuple<std::error_code, int, std::vector<uint8_t>>);
++ auto [err, nvme_status, data] =
++ boost::asio::async_initiate<boost::asio::yield_context, callback_t>(
++ [intf{nvmeIntf}, ctrl{nvmeCtrl}, proto, proto_specific,
++ transfer_length](auto&& handler) {
++ auto h = asio_helper::CopyableCallback(std::move(handler));
++
++ intf->adminSecurityReceive(ctrl, proto, proto_specific, transfer_length,
++ [h](const std::error_code& err,
++ int nvme_status,
++ std::span<uint8_t> data) mutable {
++ std::vector<uint8_t> d(data.begin(), data.end());
++ h(std::make_tuple(err, nvme_status, d));
++ });
++ },
++ yield);
++
++ // exception must be thrown outside of the async block
++ checkLibNVMeError(err, nvme_status, "SecurityReceive");
++ return data;
++}
++
++class NVMeSdBusPlusError : public sdbusplus::exception::exception
++{
++
++ public:
++ NVMeSdBusPlusError(const std::string_view& desc) : desc(desc)
++ {}
++
++ const char* name() const noexcept override
++ {
++ return "xyz.openbmc_project.NVMe.NVMeError";
++ }
++ const char* description() const noexcept override
++ {
++ return desc.c_str();
++ }
++ int get_errno() const noexcept override
++ {
++ // arbitrary, sdbusplus method return ignores this errno
++ return EIO;
++ }
++
++ private:
++ const std::string desc;
++};
++
++/* Throws an appropriate error type for the given status from libnvme,
++ * or returns normally if nvme_status == 0 */
++void NVMeControllerEnabled::checkLibNVMeError(const std::error_code& err,
++ int nvme_status,
++ const char* method_name)
++{
++ if (nvme_status < 0)
++ {
++ throw sdbusplus::exception::SdBusError(err.value(), method_name);
++ }
++ else if (nvme_status > 0)
++ {
++ int val = nvme_status_get_value(nvme_status);
++ int ty = nvme_status_get_type(nvme_status);
++ std::string desc;
++
++ switch (ty)
++ {
++ case NVME_STATUS_TYPE_NVME:
++ desc =
++ std::string("NVMe: ") + nvme_status_to_string(val, false);
++ break;
++ case NVME_STATUS_TYPE_MI:
++ desc = std::string("NVMe MI: ") + nvme_mi_status_to_string(val);
++ break;
++ default:
++ std::cerr << "Unknown libnvme error status " << nvme_status
++ << std::endl;
++ desc = "Unknown libnvme error";
++ }
++ throw NVMeSdBusPlusError(desc);
++ }
++}
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+index 0c567f9..538d9de 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -74,6 +74,8 @@ class NVMeController
+ std::shared_ptr<sdbusplus::asio::connection> conn;
+ std::string path;
+
++ std::shared_ptr<sdbusplus::asio::dbus_interface> securityInterface;
++
+ std::shared_ptr<NVMeMiIntf> nvmeIntf;
+ nvme_mi_ctrl_t nvmeCtrl;
+
+@@ -129,6 +131,9 @@ class NVMeControllerEnabled :
+
+ void init();
+
++
++ static void checkLibNVMeError(const std::error_code& err, int nvme_status,
++ const char* method_name);
+ /* NVMeAdmin method overload */
+
+ /** @brief Implementation for GetLogPage
+@@ -172,4 +177,13 @@ class NVMeControllerEnabled :
+ */
+ void firmwareCommitAsync(uint8_t commitAction, uint8_t firmwareSlot,
+ bool bpid) override;
++
++ void securitySendMethod(boost::asio::yield_context yield, uint8_t proto,
++ uint16_t proto_specific, std::span<uint8_t> data);
++
++ std::vector<uint8_t> securityReceiveMethod(boost::asio::yield_context yield,
++ uint8_t proto,
++ uint16_t proto_specific,
++ uint32_t transfer_length);
++
+ };
+diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
+index e7ba3cf..fc8776d 100644
+--- a/src/NVMeIntf.hpp
++++ b/src/NVMeIntf.hpp
+@@ -120,6 +120,17 @@ class NVMeMiIntf : public NVMeIntf
+ std::function<void(const std::error_code&,
+ nvme_status_field)>&& cb) = 0;
+
++ virtual void adminSecuritySend(
++ nvme_mi_ctrl_t ctrl, uint8_t proto, uint16_t proto_specific,
++ std::span<uint8_t> data,
++ std::function<void(const std::error_code&, int nvme_status)>&& cb) = 0;
++
++ virtual void adminSecurityReceive(
++ nvme_mi_ctrl_t ctrl, uint8_t proto, uint16_t proto_specific,
++ uint32_t transfer_length,
++ std::function<void(const std::error_code&, int nvme_status,
++ const std::span<uint8_t> data)>&& cb) = 0;
++
+ /**
+ * adminXfer() - Raw admin transfer interface.
+ * @ctrl: controller to send the admin command to
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index 59ba3a5..fabc0b5 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -12,6 +12,8 @@ std::map<int, std::weak_ptr<NVMeMi::Worker>> NVMeMi::workerMap{};
+ // libnvme-mi root service
+ nvme_root_t NVMeMi::nvmeRoot = nvme_mi_create_root(stderr, DEFAULT_LOGLEVEL);
+
++constexpr size_t maxNVMeMILength = 4096;
++
+ NVMeMi::NVMeMi(boost::asio::io_context& io, sdbusplus::bus_t& dbus, int bus,
+ int addr) :
+ io(io),
+@@ -146,6 +148,21 @@ void NVMeMi::Worker::post(std::function<void(void)>&& func)
+ throw std::runtime_error("NVMeMi has been stopped");
+ }
+
++// Calls .post(), catching runtime_error and returning an error code on failure.
++std::error_code NVMeMi::try_post(std::function<void(void)>&& func)
++{
++ try
++ {
++ post([self{shared_from_this()}, func{std::move(func)}]() { func(); });
++ }
++ catch (const std::runtime_error& e)
++ {
++ std::cerr << e.what() << std::endl;
++ return std::make_error_code(std::errc::no_such_device);
++ }
++ return std::error_code();
++}
++
+ void NVMeMi::miSubsystemHealthStatusPoll(
+ std::function<void(const std::error_code&, nvme_mi_nvm_ss_health_status*)>&&
+ cb)
+@@ -792,3 +809,88 @@ void NVMeMi::adminFwCommit(
+ return;
+ }
+ }
++
++void NVMeMi::adminSecuritySend(
++ nvme_mi_ctrl_t ctrl, uint8_t proto, uint16_t proto_specific,
++ std::span<uint8_t> data,
++ std::function<void(const std::error_code&, int nvme_status)>&& cb)
++{
++ std::error_code post_err = try_post(
++ [self{shared_from_this()}, ctrl, proto, proto_specific, data,
++ cb{std::move(cb)}]() {
++ struct nvme_security_send_args args;
++ memset(&args, 0x0, sizeof(args));
++ args.secp = proto;
++ args.spsp0 = proto_specific & 0xff;
++ args.spsp1 = proto_specific >> 8;
++ args.nssf = 0;
++ args.data = data.data();
++ args.data_len = data.size_bytes();
++ args.args_size = sizeof(struct nvme_security_send_args);
++
++ int status = nvme_mi_admin_security_send(ctrl, &args);
++ self->io.post([cb{std::move(cb)}, nvme_errno{errno}, status]() {
++ auto err = std::make_error_code(static_cast<std::errc>(nvme_errno));
++ cb(err, status);
++ });
++ });
++ if (post_err)
++ {
++ std::cerr << "adminSecuritySend post failed: " << post_err << std::endl;
++ io.post([cb{std::move(cb)}, post_err]() { cb(post_err, -1); });
++ }
++}
++
++void NVMeMi::adminSecurityReceive(
++ nvme_mi_ctrl_t ctrl, uint8_t proto, uint16_t proto_specific,
++ uint32_t transfer_length,
++ std::function<void(const std::error_code&, int nvme_status,
++ std::span<uint8_t> data)>&& cb)
++{
++ if (transfer_length > maxNVMeMILength)
++ {
++ cb(std::make_error_code(std::errc::invalid_argument), -1, {});
++ return;
++ }
++
++ std::error_code post_err = try_post(
++ [self{shared_from_this()}, ctrl, proto, proto_specific, transfer_length,
++ cb{std::move(cb)}]() {
++ std::vector<uint8_t> data(transfer_length);
++
++ struct nvme_security_receive_args args;
++ memset(&args, 0x0, sizeof(args));
++ args.secp = proto;
++ args.spsp0 = proto_specific & 0xff;
++ args.spsp1 = proto_specific >> 8;
++ args.nssf = 0;
++ args.data = data.data();
++ args.data_len = data.size();
++ args.args_size = sizeof(struct nvme_security_receive_args);
++
++ int status = nvme_mi_admin_security_recv(ctrl, &args);
++ if (args.data_len > maxNVMeMILength)
++ {
++ std::cerr << "nvme_mi_admin_security_send returned excess data, "
++ << args.data_len << std::endl;
++ self->io.post([cb]() {
++ cb(std::make_error_code(std::errc::protocol_error), -1, {});
++ });
++ return;
++ }
++
++ data.resize(args.data_len);
++ self->io.post(
++ [cb{std::move(cb)}, nvme_errno{errno}, status, data]() mutable {
++ std::span<uint8_t> span{data.data(), data.size()};
++ auto err = std::make_error_code(static_cast<std::errc>(nvme_errno));
++ cb(err, status, span);
++ });
++ });
++ if (post_err)
++ {
++ std::cerr << "adminSecurityReceive post failed: " << post_err
++ << std::endl;
++ io.post([cb{std::move(cb)}, post_err]() { cb(post_err, -1, {}); });
++ }
++}
+diff --git a/src/NVMeMi.hpp b/src/NVMeMi.hpp
+index 3071596..6a57b6e 100644
+--- a/src/NVMeMi.hpp
++++ b/src/NVMeMi.hpp
+@@ -46,6 +46,17 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+ const nvme_mi_admin_resp_hdr&,
+ std::span<uint8_t>)>&& cb) override;
+
++ void adminSecuritySend(nvme_mi_ctrl_t ctrl, uint8_t proto,
++ uint16_t proto_specific, std::span<uint8_t> data,
++ std::function<void(const std::error_code&,
++ int nvme_status)>&& cb) override;
++
++ void adminSecurityReceive(
++ nvme_mi_ctrl_t ctrl, uint8_t proto, uint16_t proto_specific,
++ uint32_t transfer_length,
++ std::function<void(const std::error_code&, int nvme_status,
++ std::span<uint8_t> data)>&& cb) override;
++
+ private:
+ // the transfer size for nvme mi messages.
+ // define in github.com/linux-nvme/libnvme/blob/master/src/nvme/mi.c
+@@ -91,4 +102,6 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+ {
+ worker->post(std::move(func));
+ }
++
++ std::error_code try_post(std::function<void(void)>&& func);
+ };
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0030-nvmesensor-always-create-host-telemetry-log-page.patch b/recipes-phosphor/sensors/dbus-sensors/0030-nvmesensor-always-create-host-telemetry-log-page.patch
new file mode 100644
index 0000000..3340496
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0030-nvmesensor-always-create-host-telemetry-log-page.patch
@@ -0,0 +1,93 @@
+From 7a53e6ef38c992a8ebf5c38ad969430eb5aa2506 Mon Sep 17 00:00:00 2001
+From: Jinliang Wang <jinliangw@google.com>
+Date: Mon, 17 Apr 2023 13:49:16 -0700
+Subject: [PATCH 30/34] nvmesensor: always create host telemetry log page
+
+According to our use case, it's easier for client to get fresh
+host telemetry log page on every request. So we are going to deprecate
+the LSP paremeter in future, and always create host telemetry log.
+
+Change-Id: Ibeafe5f8f511363435b7c9947baaf2153ca12da4
+Signed-off-by: Jinliang Wang <jinliangw@google.com>
+---
+ src/NVMeMi.cpp | 48 ++++++++++++------------------------------------
+ 1 file changed, 12 insertions(+), 36 deletions(-)
+
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index fabc0b5..8f73aed 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -386,7 +386,7 @@ static int nvme_mi_admin_get_log_telemetry_host_rae(nvme_mi_ctrl_t ctrl,
+
+ // Get Temetery Log header and return the size for hdr + data area (Area 1, 2,
+ // 3, or maybe 4)
+-int getTelemetryLog(nvme_mi_ctrl_t ctrl, bool host, bool create,
++static int getTelemetryLog(nvme_mi_ctrl_t ctrl, bool host, bool create,
+ std::vector<uint8_t>& data)
+ {
+ int rc = 0;
+@@ -405,16 +405,16 @@ int getTelemetryLog(nvme_mi_ctrl_t ctrl, bool host, bool create,
+ std::cerr << "failed to create telemetry host log" << std::endl;
+ return rc;
+ }
+- return 0;
+ }
+-
+- rc = func(ctrl, false, 0, sizeof(log), &log);
+-
+- if (rc)
++ else
+ {
+- std::cerr << "failed to retain telemetry log for "
+- << (host ? "host" : "ctrl") << std::endl;
+- return rc;
++ rc = func(ctrl, false, 0, sizeof(log), &log);
++ if (rc)
++ {
++ std::cerr << "failed to retain telemetry log header for "
++ << (host ? "host" : "ctrl") << std::endl;
++ return rc;
++ }
+ }
+
+ long size =
+@@ -551,33 +551,9 @@ void NVMeMi::adminGetLogPage(
+ // fall through to NVME_LOG_LID_TELEMETRY_CTRL
+ case NVME_LOG_LID_TELEMETRY_CTRL:
+ {
+- bool host = false;
+- bool create = false;
+- if (lid == NVME_LOG_LID_TELEMETRY_HOST)
+- {
+- host = true;
+- if (lsp == NVME_LOG_TELEM_HOST_LSP_CREATE)
+- {
+- create = true;
+- }
+- else if (lsp == NVME_LOG_TELEM_HOST_LSP_RETAIN)
+- {
+- create = false;
+- }
+- else
+- {
+- std::cerr << "invalid lsp for telemetry host log"
+- << std::endl;
+- rc = -1;
+- errno = EINVAL;
+- break;
+- }
+- }
+- else
+- {
+- host = false;
+- }
+-
++ bool host = (lid == NVME_LOG_LID_TELEMETRY_HOST);
++ // We always create host telemetry regardless LSP
++ bool create = host && true;
+ rc = getTelemetryLog(ctrl, host, create, data);
+ }
+ break;
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0031-nvmesensor-set-default-timeout-to-20-seconds-for-Sec.patch b/recipes-phosphor/sensors/dbus-sensors/0031-nvmesensor-set-default-timeout-to-20-seconds-for-Sec.patch
new file mode 100644
index 0000000..df23f7a
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0031-nvmesensor-set-default-timeout-to-20-seconds-for-Sec.patch
@@ -0,0 +1,55 @@
+From 46da5fcb52c7751d0ec8bde97e8f8295af0bdddb Mon Sep 17 00:00:00 2001
+From: Jinliang Wang <jinliangw@google.com>
+Date: Tue, 18 Apr 2023 16:18:37 -0700
+Subject: [PATCH 31/34] nvmesensor: set default timeout to 20 seconds for
+ Security Send and Receive
+
+TCG commands (through Security Send and Receive) may take quite long to
+complete. The default timeout 5 seconds in libnvme is not sufficent.
+Here we increase the default timeout to 20 seconds.
+
+Change-Id: Ie995ddc2ad05fb8371d036b1b70d9d49706a644f
+Signed-off-by: Jinliang Wang <jinliangw@google.com>
+---
+ src/NVMeMi.cpp | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index 8f73aed..106413d 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -13,6 +13,7 @@ std::map<int, std::weak_ptr<NVMeMi::Worker>> NVMeMi::workerMap{};
+ nvme_root_t NVMeMi::nvmeRoot = nvme_mi_create_root(stderr, DEFAULT_LOGLEVEL);
+
+ constexpr size_t maxNVMeMILength = 4096;
++constexpr int tcgDefaultTimeoutMS = 20*1000;
+
+ NVMeMi::NVMeMi(boost::asio::io_context& io, sdbusplus::bus_t& dbus, int bus,
+ int addr) :
+@@ -804,7 +805,11 @@ void NVMeMi::adminSecuritySend(
+ args.data_len = data.size_bytes();
+ args.args_size = sizeof(struct nvme_security_send_args);
+
++ unsigned timeout = nvme_mi_ep_get_timeout(self->nvmeEP);
++ nvme_mi_ep_set_timeout(self->nvmeEP, tcgDefaultTimeoutMS);
+ int status = nvme_mi_admin_security_send(ctrl, &args);
++ nvme_mi_ep_set_timeout(self->nvmeEP, timeout);
++
+ self->io.post([cb{std::move(cb)}, nvme_errno{errno}, status]() {
+ auto err = std::make_error_code(static_cast<std::errc>(nvme_errno));
+ cb(err, status);
+@@ -844,7 +849,11 @@ void NVMeMi::adminSecurityReceive(
+ args.data_len = data.size();
+ args.args_size = sizeof(struct nvme_security_receive_args);
+
++ unsigned timeout = nvme_mi_ep_get_timeout(self->nvmeEP);
++ nvme_mi_ep_set_timeout(self->nvmeEP, tcgDefaultTimeoutMS);
+ int status = nvme_mi_admin_security_recv(ctrl, &args);
++ nvme_mi_ep_set_timeout(self->nvmeEP, timeout);
++
+ if (args.data_len > maxNVMeMILength)
+ {
+ std::cerr << "nvme_mi_admin_security_send returned excess data, "
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0032-nvmesensor-close-pipe-file-descriptor-before-excepti.patch b/recipes-phosphor/sensors/dbus-sensors/0032-nvmesensor-close-pipe-file-descriptor-before-excepti.patch
new file mode 100644
index 0000000..fd36056
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0032-nvmesensor-close-pipe-file-descriptor-before-excepti.patch
@@ -0,0 +1,38 @@
+From 0636c36ff896efde95a0f17fcf39b034e0dadfbd Mon Sep 17 00:00:00 2001
+From: Jinliang Wang <jinliangw@google.com>
+Date: Tue, 18 Apr 2023 16:24:38 -0700
+Subject: [PATCH 32/34] nvmesensor: close pipe file descriptor before exception
+
+Close pipe file descriptors before throwing exception to prevent
+the potential leak of file desctriptor.
+
+Change-Id: Iffcb0e043307ea5774c9b51743ff8accf3400c82
+Signed-off-by: Jinliang Wang <jinliangw@google.com>
+---
+ src/NVMeController.cpp | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index 1c34a58..d534532 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -180,12 +180,16 @@ sdbusplus::message::unix_fd NVMeControllerEnabled::getLogPage(uint8_t lid,
+ }
+ else // No VU LogPage handler
+ {
++ ::close(pipe[0]);
++ ::close(pipe[1]);
+ throw sdbusplus::xyz::openbmc_project::Common::Error::
+ InvalidArgument();
+ }
+ }
+ else // No VU plugin
+ {
++ ::close(pipe[0]);
++ ::close(pipe[1]);
+ throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
+ }
+ return sdbusplus::message::unix_fd{pipe[0]};
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0033-nvmesensor-Add-toggle-for-single-thread-mode.patch b/recipes-phosphor/sensors/dbus-sensors/0033-nvmesensor-Add-toggle-for-single-thread-mode.patch
new file mode 100644
index 0000000..4b5a16d
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0033-nvmesensor-Add-toggle-for-single-thread-mode.patch
@@ -0,0 +1,103 @@
+From b2254dc06d0c96b3783d4620dd6c14a3e6dad973 Mon Sep 17 00:00:00 2001
+From: Hao Jiang <jianghao@google.com>
+Date: Wed, 19 Apr 2023 17:26:09 +0000
+Subject: [PATCH 33/34] nvmesensor: Add toggle for single thread mode
+
+Add a flag to NVMeMi class to turn off the single thread mode. The
+single thread mode is to aggregate the communicate to the devices under
+the same i2c bus. So the MCTP request/response packet will always
+transmitting in the expected order.
+
+If the I2C drive support concurrent master/slave mode, the single thread
+mode is not required since the MCTP layer can handle the concurrent MCTP
+packets to/from different endpoints. Then the toggle can be turned off.
+
+Signed-off-by: Hao Jiang <jianghao@google.com>
+Change-Id: Ib17650c9d6465167f046dee44e2d526255343f1b
+---
+ src/NVMeMi.cpp | 39 +++++++++++++++++++++++----------------
+ src/NVMeMi.hpp | 2 +-
+ 2 files changed, 24 insertions(+), 17 deletions(-)
+
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index 106413d..f0a2dcf 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -16,7 +16,7 @@ constexpr size_t maxNVMeMILength = 4096;
+ constexpr int tcgDefaultTimeoutMS = 20*1000;
+
+ NVMeMi::NVMeMi(boost::asio::io_context& io, sdbusplus::bus_t& dbus, int bus,
+- int addr) :
++ int addr, bool singleThreadMode) :
+ io(io),
+ dbus(dbus)
+ {
+@@ -25,11 +25,30 @@ NVMeMi::NVMeMi(boost::asio::io_context& io, sdbusplus::bus_t& dbus, int bus,
+ throw std::runtime_error("invalid NVMe root");
+ }
+
+- auto root = deriveRootBus(bus);
++ if (singleThreadMode)
++ {
++
++ auto root = deriveRootBus(bus);
++
++ if (!root || *root < 0)
++ {
++ throw std::runtime_error("invalid root bus number");
++ }
++ auto res = workerMap.find(*root);
+
+- if (!root || *root < 0)
++ if (res == workerMap.end() || res->second.expired())
++ {
++ worker = std::make_shared<Worker>();
++ workerMap[*root] = worker;
++ }
++ else
++ {
++ worker = res->second.lock();
++ }
++ }
++ else
+ {
+- throw std::runtime_error("invalid root bus number");
++ worker = std::make_shared<Worker>();
+ }
+
+ // init mctp ep via mctpd
+@@ -73,18 +92,6 @@ NVMeMi::NVMeMi(boost::asio::io_context& io, sdbusplus::bus_t& dbus, int bus,
+ std::to_string(nid) + ":" +
+ std::to_string(eid));
+ }
+-
+- auto res = workerMap.find(*root);
+-
+- if (res == workerMap.end() || res->second.expired())
+- {
+- worker = std::make_shared<Worker>();
+- workerMap[*root] = worker;
+- }
+- else
+- {
+- worker = res->second.lock();
+- }
+ }
+
+ NVMeMi::Worker::Worker()
+diff --git a/src/NVMeMi.hpp b/src/NVMeMi.hpp
+index 6a57b6e..2aa6636 100644
+--- a/src/NVMeMi.hpp
++++ b/src/NVMeMi.hpp
+@@ -9,7 +9,7 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+ {
+ public:
+ NVMeMi(boost::asio::io_context& io, sdbusplus::bus_t& dbus, int bus,
+- int addr);
++ int addr, bool singleThreadMode = false);
+ ~NVMeMi() override;
+
+ int getNID() const override
+--
+2.34.1
+
diff --git a/recipes-phosphor/sensors/dbus-sensors_%.bbappend b/recipes-phosphor/sensors/dbus-sensors_%.bbappend
index 025bee3..676001d 100644
--- a/recipes-phosphor/sensors/dbus-sensors_%.bbappend
+++ b/recipes-phosphor/sensors/dbus-sensors_%.bbappend
@@ -11,6 +11,52 @@
file://0103-RedfishSensor-Intentionally-drop-reported-stale.patch \
"
+# gBMC NVMe-MI support begins
+nvmesensor_patches = " \
+ file://0001-nvme-sensor-refactor-the-code.patch \
+ file://0002-NVMe-sensor-add-Storage-and-Drive-interface.patch \
+ file://0003-nvmesensor-Add-NVMe-MI-protocol-and-controller.patch \
+ file://0004-Enable-ctemp-sensor-for-nvme-mi.patch \
+ file://0005-nvmesensor-Add-Identify-and-cntrl-association.patch \
+ file://0006-Add-NVMeAdmin-intf-with-GetLogPage.patch \
+ file://0007-Add-more-logs-to-NVMe-daemon.patch \
+ file://0008-Add-Identify-method-to-NVMeAdmin-interface.patch \
+ file://0009-nvmesensor-add-adminXfer-for-MI-interface.patch \
+ file://0010-nvmesensor-handle-libnvme-mi-status-return-code.patch \
+ file://0011-nvmesensor-handle-the-broken-pipe-signal.patch \
+ file://0012-nvmesensor-Add-FirmwareCommit-to-nvme-daemon.patch \
+ file://0013-nvmesensor-Using-generated-controller-server.patch \
+ file://0014-nvmesensor-Add-NVMePlugin.patch \
+ file://0015-nvmesensor-add-associations.patch \
+ file://0016-nvmesensor-Create-thermal-sensor-in-start.patch \
+ file://0017-nvmesensor-Move-temp-sensor-function-to-NVMeUtil.patch \
+ file://0018-nvmesensor-Change-the-plugin-log-handler.patch \
+ file://0019-nvmesensor-Add-nvme-controller-plugin.patch \
+ file://0020-nvmesensor-add-timeout-for-xfer.patch \
+ file://0021-nvmesensor-segmentation-fault-workaround-and-fix.patch \
+ file://0022-nvmesensor-spin-out-the-worker-for-NVMeMi.patch \
+ file://0023-nvmesensor-delay-subsystem-creation.patch \
+ file://0024-nvmesensor-clean-up-the-association.patch \
+ file://0025-nvmesensor-change-the-controller-init-sequence.patch \
+ file://0026-nvmesensor-Check-SCS-for-secondary-controllers.patch \
+ file://0027-Enable-asio-threads-and-boost-coroutine.patch \
+ file://0028-nvmesensor-Split-constructor-for-shared_from_this.patch \
+ file://0029-nvmesensor-Add-SecuritySend-and-SecurityReceive.patch \
+ file://0030-nvmesensor-always-create-host-telemetry-log-page.patch \
+ file://0031-nvmesensor-set-default-timeout-to-20-seconds-for-Sec.patch \
+ file://0032-nvmesensor-close-pipe-file-descriptor-before-excepti.patch \
+ file://0033-nvmesensor-Add-toggle-for-single-thread-mode.patch \
+"
+PACKAGECONFIG[nvmesensor] = "-Dnvme=enabled, -Dnvme=disabled, libnvme libnvme-vu"
+SYSTEMD_SERVICE:${PN} += "${@bb.utils.contains('PACKAGECONFIG', 'nvmesensor', \
+ 'xyz.openbmc_project.nvmesensor.service', \
+ '', d)}"
+SRC_URI:append:gbmc = " \
+ ${@bb.utils.contains('PACKAGECONFIG', 'nvmesensor', '${nvmesensor_patches}', '', d)} \
+"
+# gBMC NVMe-MI support ends
+
+
# TODO(linchuyuan@google.com): remove the following section once the upstream has the change
# b/191874662
PACKAGECONFIG:append:gbmc = " gpiosensor"