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"