nvmesensor: add volume management and sanitize patches

Those patches come from https://github.com/CodeConstruct/dbus-sensors/commits/dev/nvme-redfish

Tested:
DBus interface:
https://paste.googleplex.com/5995823744679936

lightkeeper_tool_full_cleaning:
https://paste.googleplex.com/6595177559883776

Change-Id: I2f19544ee1f6656b098374e6d2dd853d31bda013
Signed-off-by: Jinliang Wang <jinliangw@google.com>
diff --git a/recipes-phosphor/sensors/dbus-sensors/0001-NVMe-Add-NVMeError-as-a-common-error-type.patch b/recipes-phosphor/sensors/dbus-sensors/0001-NVMe-Add-NVMeError-as-a-common-error-type.patch
new file mode 100644
index 0000000..28b354f
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0001-NVMe-Add-NVMeError-as-a-common-error-type.patch
@@ -0,0 +1,456 @@
+From 5abe6c23f0733564ca751ab30571dcdce85a3acf Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Thu, 20 Apr 2023 21:25:57 +0800
+Subject: [PATCH 01/16] NVMe: Add NVMeError as a common error type.
+
+The name and description of a specific exception can be returned over
+dbus, proxied through by NVMeSdBusPlusError.
+
+Some known libnvme status codes are translated to specific D-Bus error
+names from phosphor-dbus-interfaces.
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: I4ad0cf840fcfc4a42b85330a0fad27ddfefc13aa
+(cherry picked from commit 0c0bc1e7d7fe4922092e383e85dbcb885c028f79)
+---
+ include/NVMeError.hpp  |  54 +++++++++
+ src/NVMeController.cpp |  60 +---------
+ src/NVMeController.hpp |   3 -
+ src/NVMeError.cpp      | 248 +++++++++++++++++++++++++++++++++++++++++
+ src/NVMeSubsys.cpp     |   1 +
+ src/meson.build        |   3 +-
+ 6 files changed, 306 insertions(+), 63 deletions(-)
+ create mode 100644 include/NVMeError.hpp
+ create mode 100644 src/NVMeError.cpp
+
+diff --git a/include/NVMeError.hpp b/include/NVMeError.hpp
+new file mode 100644
+index 0000000..7dfb727
+--- /dev/null
++++ b/include/NVMeError.hpp
+@@ -0,0 +1,54 @@
++#pragma once
++
++#include <sdbusplus/exception.hpp>
++#include <xyz/openbmc_project/Common/error.hpp>
++
++#include <iostream>
++#include <memory>
++#include <system_error>
++
++namespace CommonErr = sdbusplus::xyz::openbmc_project::Common::Error;
++
++class NVMeSdBusPlusError : public sdbusplus::exception_t
++{
++  public:
++    // In general makeLibNVMeError() should be used rather than raw
++    // constructors.
++    NVMeSdBusPlusError(std::string_view desc);
++    NVMeSdBusPlusError(std::shared_ptr<sdbusplus::exception_t> specific);
++    NVMeSdBusPlusError(std::string_view desc,
++                       std::shared_ptr<sdbusplus::exception_t> specific);
++
++    const char* what() const noexcept override;
++    const char* name() const noexcept override;
++    const char* description() const noexcept override;
++    int get_errno() const noexcept override;
++    void print(std::ostream& o) const;
++
++  private:
++    void init();
++    const std::string desc;
++    std::shared_ptr<sdbusplus::exception_t> specific;
++    std::string whatMsg;
++};
++
++using nvme_ex_ptr = std::shared_ptr<NVMeSdBusPlusError>;
++
++/* Translates an error from libnvme */
++nvme_ex_ptr makeLibNVMeError(const std::error_code& err, int nvme_status,
++                             const char* method_name);
++nvme_ex_ptr makeLibNVMeError(int nvme_errno, int nvme_status,
++                             const char* method_name);
++
++/* Creates an internal error */
++nvme_ex_ptr makeLibNVMeError(std::string_view msg);
++/* Creates an error based on a known exception type */
++nvme_ex_ptr makeLibNVMeError(std::string_view desc,
++                             std::shared_ptr<sdbusplus::exception_t> specific);
++
++/* Throws an appropriate error type for the given status from libnvme,
++ * or returns normally if nvme_status == 0 */
++void checkLibNVMeError(const std::error_code& err, int nvme_status,
++                       const char* method_name);
++
++std::ostream& operator<<(std::ostream& o, nvme_ex_ptr ex);
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index f96dcce..765844d 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -1,6 +1,7 @@
+ #include "NVMeController.hpp"
+ 
+ #include "AsioHelper.hpp"
++#include "NVMeError.hpp"
+ #include "NVMePlugin.hpp"
+ 
+ #include <sdbusplus/exception.hpp>
+@@ -481,62 +482,3 @@ std::tuple<uint32_t, uint32_t, uint32_t>
+     }
+     return {mi_status, admin_status, completion_dw0};
+ }
+-
+-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 8c64883..e33d932 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -132,9 +132,6 @@ 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
+diff --git a/src/NVMeError.cpp b/src/NVMeError.cpp
+new file mode 100644
+index 0000000..5e0ca78
+--- /dev/null
++++ b/src/NVMeError.cpp
+@@ -0,0 +1,248 @@
++#include "NVMeError.hpp"
++
++#include <libnvme-mi.h>
++
++#include <xyz/openbmc_project/Common/Device/error.hpp>
++#include <xyz/openbmc_project/Common/error.hpp>
++
++NVMeSdBusPlusError::NVMeSdBusPlusError(std::string_view desc) : desc(desc)
++{
++    init();
++}
++
++NVMeSdBusPlusError::NVMeSdBusPlusError(
++    std::shared_ptr<sdbusplus::exception_t> specific) :
++    specific(specific)
++{
++    init();
++}
++
++NVMeSdBusPlusError::NVMeSdBusPlusError(
++    std::string_view desc, std::shared_ptr<sdbusplus::exception_t> specific) :
++    desc(desc),
++    specific(specific)
++{
++    init();
++}
++
++void NVMeSdBusPlusError::init()
++{
++    whatMsg = std::string(name()) + ": " + description();
++}
++
++const char* NVMeSdBusPlusError::name() const noexcept
++{
++    if (specific)
++    {
++        return specific->name();
++    }
++    return CommonErr::InternalFailure().name();
++}
++
++const char* NVMeSdBusPlusError::description() const noexcept
++{
++    if (!desc.empty())
++    {
++        return desc.c_str();
++    }
++    if (specific)
++    {
++        return specific->description();
++    }
++    return "nvmesensor internal error";
++}
++
++const char* NVMeSdBusPlusError::what() const noexcept
++{
++    return whatMsg.c_str();
++}
++
++int NVMeSdBusPlusError::get_errno() const noexcept
++{
++    if (specific)
++    {
++        return specific->get_errno();
++    }
++    // arbitrary, sdbusplus method return ignores this errno
++    return EIO;
++}
++
++void NVMeSdBusPlusError::print(std::ostream& o) const
++{
++    o << description();
++}
++
++/* Converts a subset of known status codes to dbus enums */
++static void translateLibNVMe(int val, std::string& desc,
++                             std::shared_ptr<sdbusplus::exception_t>& specific)
++{
++    uint16_t sc = nvme_status_code(val);
++    uint16_t sct = nvme_status_code_type(val);
++
++    switch (sct)
++    {
++        case NVME_SCT_GENERIC:
++            switch (sc)
++            {
++                case NVME_SC_INVALID_FIELD:
++                    specific = std::make_shared<CommonErr::InvalidArgument>();
++                    break;
++
++                case NVME_SC_CAP_EXCEEDED:
++                    specific = std::make_shared<CommonErr::TooManyResources>();
++                    break;
++
++                case NVME_SC_SANITIZE_IN_PROGRESS:
++                    specific = std::make_shared<CommonErr::Unavailable>();
++                    break;
++
++                default:
++                    specific =
++                        std::make_shared<CommonErr::DeviceOperationFailed>();
++            }
++            break;
++        case NVME_SCT_CMD_SPECIFIC:
++            switch (sc)
++            {
++                case NVME_SC_INVALID_FORMAT:
++                    specific = std::make_shared<CommonErr::InvalidArgument>();
++                    break;
++
++                case NVME_SC_INSUFFICIENT_CAP:
++                case NVME_SC_NS_INSUFFICIENT_CAP:
++                case NVME_SC_NS_ID_UNAVAILABLE:
++                case NVME_SC_NS_ATTACHMENT_LIMIT_EXCEEDED:
++                    specific = std::make_shared<CommonErr::TooManyResources>();
++                    break;
++
++                case NVME_SC_FW_NEEDS_SUBSYS_RESET:
++                case NVME_SC_FW_NEEDS_RESET:
++                    specific = std::make_shared<CommonErr::Unavailable>();
++                    break;
++
++                default:
++                    specific =
++                        std::make_shared<CommonErr::DeviceOperationFailed>();
++            }
++            break;
++        default:
++            specific = std::make_shared<CommonErr::DeviceOperationFailed>();
++    }
++
++    // always return the description from libnvme
++    std::ostringstream s;
++    s << "NVMe: " << nvme_status_to_string(val, false) << " (SCT " << sct
++      << " SC 0x" << std::hex << sc << ")";
++    desc = s.str();
++}
++
++static void
++    translateLibNVMeMI(int val, std::string& desc,
++                       std::shared_ptr<sdbusplus::exception_t>& specific)
++{
++    switch (val)
++    {
++        case NVME_MI_RESP_INVALID_PARAM:
++            specific = std::make_shared<CommonErr::InvalidArgument>();
++            break;
++
++        case NVME_MI_RESP_SANITIZE_IN_PROGRESS:
++            specific = std::make_shared<CommonErr::Unavailable>();
++            break;
++
++        // INVALID_CMD_SIZE is returned by some drives
++        case NVME_MI_RESP_INVALID_OPCODE:
++        case NVME_MI_RESP_INVALID_CMD_SIZE:
++            specific = std::make_shared<CommonErr::UnsupportedRequest>();
++            break;
++
++        default:
++            specific = std::make_shared<CommonErr::DeviceOperationFailed>();
++    }
++
++    // always return the description from libnvme
++    std::ostringstream s;
++    s << "NVMe MI: " << nvme_mi_status_to_string(val) << " (MI status 0x"
++      << std::hex << val << ")";
++    desc = s.str();
++}
++
++nvme_ex_ptr makeLibNVMeError(const std::error_code& err, int nvme_status,
++                             const char* method_name)
++{
++    // TODO: possibly remove method_name argument
++    (void)method_name;
++
++    if (nvme_status < 0)
++    {
++        auto desc = std::string("libnvme error: ") + err.message();
++        return std::make_shared<NVMeSdBusPlusError>(desc);
++    }
++    else if (nvme_status > 0)
++    {
++        int val = nvme_status_get_value(nvme_status);
++        int ty = nvme_status_get_type(nvme_status);
++        std::string desc;
++        std::shared_ptr<sdbusplus::exception_t> specific;
++
++        switch (ty)
++        {
++            case NVME_STATUS_TYPE_NVME:
++                translateLibNVMe(val, desc, specific);
++                break;
++            case NVME_STATUS_TYPE_MI:
++                translateLibNVMeMI(val, desc, specific);
++                break;
++            default:
++                std::cerr << "Unknown libnvme error status " << nvme_status
++                          << std::endl;
++                desc = "Unknown libnvme error";
++        }
++        return std::make_shared<NVMeSdBusPlusError>(desc, specific);
++    }
++    // No Error
++    return nullptr;
++}
++
++nvme_ex_ptr makeLibNVMeError(int nvme_errno, int nvme_status,
++                             const char* method_name)
++{
++    auto err = std::make_error_code(static_cast<std::errc>(nvme_errno));
++    return makeLibNVMeError(err, nvme_status, method_name);
++}
++
++nvme_ex_ptr makeLibNVMeError(std::string_view msg)
++{
++    return std::make_shared<NVMeSdBusPlusError>(msg);
++}
++
++nvme_ex_ptr makeLibNVMeError(std::string_view desc,
++                             std::shared_ptr<sdbusplus::exception_t> specific)
++{
++    return std::make_shared<NVMeSdBusPlusError>(desc, specific);
++}
++
++/* Throws an appropriate error type for the given status from libnvme,
++ * or returns normally if nvme_status == 0 */
++void checkLibNVMeError(const std::error_code& err, int nvme_status,
++                       const char* method_name)
++{
++    auto e = makeLibNVMeError(err, nvme_status, method_name);
++    if (e)
++    {
++        throw *e;
++    }
++}
++
++std::ostream& operator<<(std::ostream& o, nvme_ex_ptr ex)
++{
++    if (ex)
++    {
++        ex->print(o);
++    }
++    else
++    {
++        o << "(null nvme_ex_ptr)";
++    }
++    return o;
++}
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index 54f0da8..4fe1bff 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -1,5 +1,6 @@
+ #include "NVMeSubsys.hpp"
+ 
++#include "NVMeError.hpp"
+ #include "NVMePlugin.hpp"
+ #include "NVMeUtil.hpp"
+ #include "Thresholds.hpp"
+diff --git a/src/meson.build b/src/meson.build
+index 0ba65d0..d291081 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -192,7 +192,8 @@ if get_option('nvme').enabled()
+         'NVMeBasic.cpp',
+         'NVMeSubsys.cpp',
+         'NVMeMi.cpp',
+-        'NVMeController.cpp'
++        'NVMeController.cpp',
++        'NVMeError.cpp',
+     )
+ 
+     pdi_dep = dependency ('phosphor-dbus-interfaces')
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0002-nvmesensor-Manually-implement-StorageController.patch b/recipes-phosphor/sensors/dbus-sensors/0002-nvmesensor-Manually-implement-StorageController.patch
new file mode 100644
index 0000000..6ad60e2
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0002-nvmesensor-Manually-implement-StorageController.patch
@@ -0,0 +1,96 @@
+From cc906069a6e78d5c2106ac3318c998a5547067bc Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Thu, 20 Apr 2023 16:13:50 +0800
+Subject: [PATCH 02/16] nvmesensor: Manually implement StorageController
+
+To allow using async methods
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: Idf32130353305c23177b6183000509841e3d7db6
+(cherry picked from commit ee8f227955ea376c633b3da83242ce650e4a1cba)
+---
+ src/NVMeController.cpp | 20 ++++++++++++++------
+ src/NVMeController.hpp |  7 +++++--
+ 2 files changed, 19 insertions(+), 8 deletions(-)
+
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index 765844d..09e0bd7 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -44,16 +44,16 @@ NVMeControllerEnabled::NVMeControllerEnabled(
+     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()),
++    // StorageController(dynamic_cast<sdbusplus::bus_t&>(*conn), path.c_str()),
+     NVMeAdmin(*conn, path.c_str(),
+               {{"FirmwareCommitStatus", {FwCommitStatus::Ready}}})
+ {}
+ 
+ NVMeControllerEnabled::NVMeControllerEnabled(NVMeController&& nvmeController) :
+     NVMeController(std::move(nvmeController)),
+-    StorageController(
+-        dynamic_cast<sdbusplus::bus_t&>(*this->NVMeController::conn),
+-        this->NVMeController::path.c_str()),
++    // 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}}})
+ {}
+@@ -131,9 +131,16 @@ void NVMeControllerEnabled::init()
+             yield, proto, proto_specific, transfer_length);
+         });
+ 
++    // StorageController interface is implemented manually to allow
++    // async methods
++    ctrlInterface = objServer.add_interface(
++        path, "xyz.openbmc_project.Inventory.Item.StorageController");
++
++    ctrlInterface->initialize();
++
+     securityInterface->initialize();
++    // StorageController::emit_added();
+ 
+-    StorageController::emit_added();
+     NVMeAdmin::emit_added();
+ }
+ 
+@@ -309,7 +316,8 @@ NVMeControllerEnabled::~NVMeControllerEnabled()
+ {
+     objServer.remove_interface(securityInterface);
+     NVMeAdmin::emit_removed();
+-    StorageController::emit_removed();
++    // StorageController::emit_removed();
++    objServer.remove_interface(ctrlInterface);
+ }
+ 
+ NVMeController::NVMeController(
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+index e33d932..eb9c180 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -74,6 +74,7 @@ class NVMeController
+     std::shared_ptr<sdbusplus::asio::connection> conn;
+     std::string path;
+ 
++    std::shared_ptr<sdbusplus::asio::dbus_interface> ctrlInterface;
+     std::shared_ptr<sdbusplus::asio::dbus_interface> securityInterface;
+     std::shared_ptr<sdbusplus::asio::dbus_interface> passthruInterface;
+ 
+@@ -101,8 +102,10 @@ class NVMeController
+  */
+ class NVMeControllerEnabled :
+     public NVMeController,
+-    private sdbusplus::xyz::openbmc_project::Inventory::Item::server::
+-        StorageController,
++    //  StorageController interface will be used from PDI once coroutine
++    //  sdbusplus methods are added. In the interim it is implemented manually.
++    // private sdbusplus::xyz::openbmc_project::Inventory::Item::server::
++    //     StorageController,
+     private sdbusplus::xyz::openbmc_project::NVMe::server::NVMeAdmin,
+     public std::enable_shared_from_this<NVMeControllerEnabled>
+ 
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0003-nvme-Simplify-Worker-post.patch b/recipes-phosphor/sensors/dbus-sensors/0003-nvme-Simplify-Worker-post.patch
new file mode 100644
index 0000000..45b6403
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0003-nvme-Simplify-Worker-post.patch
@@ -0,0 +1,47 @@
+From a7910aa05fa8ba6e551a4566e187a71d994ebcd2 Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Thu, 27 Apr 2023 14:25:54 +0800
+Subject: [PATCH 03/16] nvme: Simplify Worker::post
+
+Add comment for future timeout
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: Ifc05082e737a6d108bc94da5634fc6e95402a232
+(cherry picked from commit 393a3e58fcfb8fd5643b8ec42d5d6e37853bdd6b)
+---
+ src/NVMeMi.cpp | 17 ++++++++---------
+ 1 file changed, 8 insertions(+), 9 deletions(-)
+
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index 695bec8..17825ac 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -229,17 +229,16 @@ NVMeMi::~NVMeMi()
+ 
+ void NVMeMi::Worker::post(std::function<void(void)>&& func)
+ {
+-    if (!workerStop)
++    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");
+     }
+-    throw std::runtime_error("NVMeMi has been stopped");
++
++    // TODO: need a timeout on this lock so we can return
++    // a useful "busy" error message when the NVMe-MI endpoint is in-use
++    std::unique_lock<std::mutex> lock(workerMtx);
++    workerIO.post(std::move(func));
++    workerCv.notify_all();
+ }
+ 
+ void NVMeMi::post(std::function<void(void)>&& func)
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0004-utils-add-getRandomId.patch b/recipes-phosphor/sensors/dbus-sensors/0004-utils-add-getRandomId.patch
new file mode 100644
index 0000000..ad52231
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0004-utils-add-getRandomId.patch
@@ -0,0 +1,88 @@
+From b9e2a997ff2c6232cbd03988ce483c9fbe5ad882 Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Fri, 28 Apr 2023 17:24:55 +0800
+Subject: [PATCH 04/16] utils: add getRandomId()
+
+Provides a GUID with an extra counter element to aid debugging.
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: Idc48aa73c003f987b2b26d813bd8cb9f29b11d28
+(cherry picked from commit 91fda2d540064b33e83786ae8ecd6d7228d9fc8d)
+---
+ src/Utils.cpp   | 18 ++++++++++++++++++
+ src/Utils.hpp   |  2 ++
+ src/meson.build |  2 +-
+ 3 files changed, 21 insertions(+), 1 deletion(-)
+
+diff --git a/src/Utils.cpp b/src/Utils.cpp
+index 866f05b..39e6c65 100644
+--- a/src/Utils.cpp
++++ b/src/Utils.cpp
+@@ -18,6 +18,9 @@
+ 
+ #include "dbus-sensor_config.h"
+ 
++#include <sys/random.h>
++#include <systemd/sd-id128.h>
++
+ #include "DeviceMgmt.hpp"
+ 
+ #include <boost/container/flat_map.hpp>
+@@ -25,6 +28,7 @@
+ #include <sdbusplus/asio/object_server.hpp>
+ #include <sdbusplus/bus/match.hpp>
+ 
++#include <atomic>
+ #include <filesystem>
+ #include <fstream>
+ #include <memory>
+@@ -402,6 +406,20 @@ static void
+         post::interface, post::property);
+ }
+ 
++/* Returns a random string suitable as a D-Bus object path element */
++std::string getRandomId()
++{
++    static std::atomic_size_t counter;
++    sd_id128_t id;
++    char s[SD_ID128_STRING_MAX];
++    sd_id128_randomize(&id);
++    sd_id128_to_string(id, s);
++    std::string r(s);
++    r += '_';
++    r += std::to_string(++counter);
++    return r;
++}
++
+ static void
+     getChassisStatus(const std::shared_ptr<sdbusplus::asio::connection>& conn,
+                      size_t retries = 2)
+diff --git a/src/Utils.hpp b/src/Utils.hpp
+index 65e6018..bbd9de7 100644
+--- a/src/Utils.hpp
++++ b/src/Utils.hpp
+@@ -261,6 +261,8 @@ inline float getPollRate(const SensorBaseConfigMap& cfg, float dflt)
+     return pollRate;
+ }
+ 
++std::string getRandomId();
++
+ inline void setLed(const std::shared_ptr<sdbusplus::asio::connection>& conn,
+                    const std::string& name, bool on)
+ {
+diff --git a/src/meson.build b/src/meson.build
+index d291081..27ab8ea 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -28,7 +28,7 @@ utils_a = static_library(
+ 
+ utils_dep = declare_dependency(
+     link_with: [ utils_a ],
+-    dependencies: [ sdbusplus ],
++    dependencies: [ sdbusplus, systemd ],
+ )
+ 
+ devicemgmt_a = static_library(
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0005-nvme-Hack-workaround-for-drives-without-secondary.patch b/recipes-phosphor/sensors/dbus-sensors/0005-nvme-Hack-workaround-for-drives-without-secondary.patch
new file mode 100644
index 0000000..b42fbe8
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0005-nvme-Hack-workaround-for-drives-without-secondary.patch
@@ -0,0 +1,88 @@
+From 7786f9c504d94a1e8654b822226faf174a0e3716 Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Wed, 3 May 2023 10:07:26 +0800
+Subject: [PATCH 05/16] nvme: Hack workaround for drives without secondary
+
+Some drives won't return any secondary controller list,
+so just choose the single arbitrary controller as the primary.
+
+Required for Samsung PM9A2 or Micron 7450 M.2 drives
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: I180326d882e7094ff0293827afba7cb6c84416b7
+(cherry picked from commit c6a4016162c845ca7c3beaf3d7bbdeed2d57579a)
+---
+ src/NVMeSubsys.cpp | 37 ++++++++++++++++++++++++++++++++-----
+ src/NVMeSubsys.hpp |  2 ++
+ 2 files changed, 34 insertions(+), 5 deletions(-)
+
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index 4fe1bff..7b13467 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -204,13 +204,24 @@ void NVMeSubsystem::markFunctional(bool toggle)
+                                                  std::span<uint8_t> data) {
+                 if (ec || data.size() < sizeof(nvme_secondary_ctrl_list))
+                 {
+-                    std::cerr << "fail to identify secondary controller list"
+-                              << std::endl;
+-                    self->status = Status::Stop;
+-                    self->markFunctional(false);
+-                    self->markAvailable(false);
++                    // std::cerr << "fail to identify secondary controller list"
++                    //           << std::endl;
++                    // self->status = Status::Stop;
++                    // self->markFunctional(false);
++                    // self->markAvailable(false);
++                    // TODO Some drives don't support identify secondary, so use
++                    // fallback. This may need refiniing.
++                    std::cerr << "Failed to identify secondary controller "
++                                 "list. error "
++                              << ec << " data size " << data.size()
++                              << " expected size "
++                              << sizeof(nvme_secondary_ctrl_list)
++                              << ". Fallback, using arbitrary controller as "
++                                 "primary.\n";
++                    self->fallbackNoSecondary();
+                     return;
+                 }
++
+                 nvme_secondary_ctrl_list& listHdr =
+                     *reinterpret_cast<nvme_secondary_ctrl_list*>(data.data());
+ 
+@@ -545,3 +556,19 @@ void NVMeSubsystem::stop()
+         plugin.reset();
+     }
+ }
++
++void NVMeSubsystem::fallbackNoSecondary()
++{
++    // choose an arbitrary one
++    auto& pc = controllers.begin()->second.first;
++    primaryController = NVMeControllerEnabled::create(std::move(*pc));
++    // replace with the new controller object
++    pc = primaryController;
++
++    // start controller
++    for (auto& [_, pair] : controllers)
++    {
++        pair.first->start(pair.second);
++    }
++    status = Status::Start;
++}
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index dd9dbfa..3308cd6 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -78,6 +78,8 @@ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+     // mark the availability of the Storage device.
+     void markAvailable(bool toggle);
+ 
++    void fallbackNoSecondary();
++
+     // a counter to skip health poll when NVMe subsystem becomes Unavailable
+     unsigned UnavailableCount = 0;
+     static constexpr unsigned UnavailableMaxCount = 60;
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0006-nvme-Split-constructor-for-NVMeSubsystem.patch b/recipes-phosphor/sensors/dbus-sensors/0006-nvme-Split-constructor-for-NVMeSubsystem.patch
new file mode 100644
index 0000000..f70e0f5
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0006-nvme-Split-constructor-for-NVMeSubsystem.patch
@@ -0,0 +1,312 @@
+From 97e8ee18ab8a21c442083dc6043a36596d0dbc01 Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Wed, 3 May 2023 16:56:22 +0800
+Subject: [PATCH 06/16] nvme: Split constructor for NVMeSubsystem
+
+A create() method now returns a shared pointer, and allows
+the shared_from_this() to be used during object initialisation.
+
+We also make NVMeSubsystem implement NVMeStorage to simplify future dbus
+method handlers.
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: I17d735fb0661e331c4fb60ea9ba974c5b6b454cc
+(cherry picked from commit 42aa53fb0dd83c3ce2e7d1bfa88bb0af4f51b392)
+---
+ src/NVMeController.cpp | 22 ---------------------
+ src/NVMeController.hpp | 12 ------------
+ src/NVMeSensorMain.cpp |  2 +-
+ src/NVMeStorage.hpp    | 27 +++++++++++++++++++++-----
+ src/NVMeSubsys.cpp     | 43 +++++++++++++++++++++++++++++++-----------
+ src/NVMeSubsys.hpp     | 31 +++++++++++++++++++-----------
+ 6 files changed, 75 insertions(+), 62 deletions(-)
+
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index 09e0bd7..5fbe488 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -17,18 +17,6 @@ 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)
+ {
+@@ -39,16 +27,6 @@ std::shared_ptr<NVMeControllerEnabled>
+     return self;
+ }
+ 
+-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}}})
+-{}
+-
+ NVMeControllerEnabled::NVMeControllerEnabled(NVMeController&& nvmeController) :
+     NVMeController(std::move(nvmeController)),
+     // StorageController(
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+index eb9c180..ef32a7e 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -111,11 +111,6 @@ 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);
+ 
+@@ -124,13 +119,6 @@ class NVMeControllerEnabled :
+     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,
+-                          std::string path,
+-                          std::shared_ptr<NVMeMiIntf> nvmeIntf,
+-                          nvme_mi_ctrl_t ctrl);
+-
+     NVMeControllerEnabled(NVMeController&& nvmeController);
+ 
+     void init();
+diff --git a/src/NVMeSensorMain.cpp b/src/NVMeSensorMain.cpp
+index c380c7a..1682c21 100644
+--- a/src/NVMeSensorMain.cpp
++++ b/src/NVMeSensorMain.cpp
+@@ -235,7 +235,7 @@ static void handleConfigurations(
+             continue;
+         try
+         {
+-            auto nvmeSubsys = std::make_shared<NVMeSubsystem>(
++            auto nvmeSubsys = NVMeSubsystem::create(
+                 io, objectServer, dbusConnection, interfacePath, *sensorName,
+                 configData, std::move(find->second));
+             nvmeSubsysMap.emplace(interfacePath, nvmeSubsys);
+diff --git a/src/NVMeStorage.hpp b/src/NVMeStorage.hpp
+index c72661c..6913eee 100644
+--- a/src/NVMeStorage.hpp
++++ b/src/NVMeStorage.hpp
+@@ -1,17 +1,34 @@
++#pragma once
++
+ #include <xyz/openbmc_project/Inventory/Item/Storage/server.hpp>
+ 
++#include <memory>
++
+ 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(sdbusplus::asio::object_server& objServer,
++                sdbusplus::bus_t& bus, const char* path) :
++        StorageBase(bus, path),
++        objServer(objServer), path(path)
++    {}
++
+     ~NVMeStorage() override
+     {
+         emit_removed();
+     }
++
++  protected:
++    // Called by parent class for setup after shared_ptr has been initialised
++    static void init(std::shared_ptr<NVMeStorage> self)
++    {
++        self->emit_added();
++    }
++
++  private:
++    sdbusplus::asio::object_server& objServer;
++
++    const std::string path;
+ };
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index 7b13467..4738628 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -1,6 +1,6 @@
+ #include "NVMeSubsys.hpp"
+ 
+-#include "NVMeError.hpp"
++#include "AsioHelper.hpp"
+ #include "NVMePlugin.hpp"
+ #include "NVMeUtil.hpp"
+ #include "Thresholds.hpp"
+@@ -37,16 +37,31 @@ static double getTemperatureReading(int8_t reading)
+     return reading;
+ }
+ 
+-NVMeSubsystem::NVMeSubsystem(boost::asio::io_context& asio,
+-                             sdbusplus::asio::object_server& server,
++std::shared_ptr<NVMeSubsystem> NVMeSubsystem::create(
++    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, NVMeIntf intf)
++{
++    auto self = std::shared_ptr<NVMeSubsystem>(
++        new NVMeSubsystem(io, objServer, conn, path, name, configData, intf));
++    self->init();
++    return self;
++}
++
++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, NVMeIntf intf) :
+-    io(asio),
+-    objServer(server), conn(conn), path(path), name(name), config(configData),
+-    nvmeIntf(std::move(intf)), status(Status::Stop),
+-    storage(*dynamic_cast<sdbusplus::bus_t*>(conn.get()), path.c_str()),
++    NVMeStorage(objServer, *dynamic_cast<sdbusplus::bus_t*>(conn.get()),
++                path.c_str()),
++    io(io), objServer(objServer), conn(conn), path(path), name(name),
++    config(configData), nvmeIntf(intf), status(Status::Stop),
+     drive(*dynamic_cast<sdbusplus::bus_t*>(conn.get()), path.c_str())
++{}
++
++// Performs initialisation after shared_from_this() has been set up.
++void NVMeSubsystem::init()
+ {
+     NVMeIntf::Protocol protocol{NVMeIntf::Protocol::NVMeBasic};
+     try
+@@ -65,6 +80,9 @@ NVMeSubsystem::NVMeSubsystem(boost::asio::io_context& asio,
+         throw std::runtime_error("Unsupported NVMe interface");
+     }
+ 
++    NVMeStorage::init(
++        std::static_pointer_cast<NVMeStorage>(shared_from_this()));
++
+     /* xyz.openbmc_project.Inventory.Item.Drive */
+     drive.protocol(NVMeDrive::DriveProtocol::NVMe);
+     drive.type(NVMeDrive::DriveType::SSD);
+@@ -257,9 +275,11 @@ void NVMeSubsystem::markFunctional(bool toggle)
+                 }
+ 
+                 // Enable primary controller since they are required to work
+-                auto& primaryController = findPrimary->second.first;
+-                primaryController = NVMeControllerEnabled::create(
+-                    std::move(*primaryController.get()));
++                auto& pc = findPrimary->second.first;
++                self->primaryController =
++                    NVMeControllerEnabled::create(std::move(*pc));
++                // replace with the new controller object
++                pc = self->primaryController;
+ 
+                 std::vector<std::shared_ptr<NVMeController>> secCntrls;
+                 for (int i = 0; i < listHdr.num; i++)
+@@ -284,7 +304,7 @@ void NVMeSubsystem::markFunctional(bool toggle)
+                     }
+                     secCntrls.push_back(secondaryController);
+                 }
+-                primaryController->setSecAssoc(secCntrls);
++                self->primaryController->setSecAssoc(secCntrls);
+ 
+                 // start controller
+                 for (auto& [_, pair] : self->controllers)
+@@ -523,6 +543,7 @@ void NVMeSubsystem::start()
+                   dataProcessor);
+     }
+ }
++
+ void NVMeSubsystem::stop()
+ {
+     if (ctempTimer)
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index 3308cd6..c29d271 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -9,16 +9,19 @@
+ class NVMeControllerPlugin;
+ class NVMePlugin;
+ 
+-class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
++class NVMeSubsystem :
++    public std::enable_shared_from_this<NVMeSubsystem>,
++    public NVMeStorage
+ {
+   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, NVMeIntf intf);
++    static std::shared_ptr<NVMeSubsystem>
++        create(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, NVMeIntf intf);
+ 
+     ~NVMeSubsystem();
+ 
+@@ -27,6 +30,13 @@ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+     void stop();
+ 
+   private:
++    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, NVMeIntf intf);
++    void init();
++
+     friend class NVMePlugin;
+     boost::asio::io_context& io;
+     sdbusplus::asio::object_server& objServer;
+@@ -53,11 +63,6 @@ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+     std::shared_ptr<NVMeSensor> ctemp;
+     std::shared_ptr<boost::asio::steady_timer> ctempTimer;
+ 
+-    /*
+-    Storage interface: xyz.openbmc_project.Inventory.Item.Storage
+-    */
+-    NVMeStorage storage;
+-
+     /*
+     Drive interface: xyz.openbmc_project.Inventory.Item.Drive
+     */
+@@ -68,6 +73,10 @@ class NVMeSubsystem : public std::enable_shared_from_this<NVMeSubsystem>
+                                  std::shared_ptr<NVMeControllerPlugin>>>
+         controllers{};
+ 
++    // controller to use for NVMe operations. Is a member of the controllers
++    // map.
++    std::shared_ptr<NVMeControllerEnabled> primaryController;
++
+     std::shared_ptr<sdbusplus::asio::dbus_interface> assocIntf;
+     void createStorageAssociation();
+ 
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0007-NVMe-Add-createVolume.patch b/recipes-phosphor/sensors/dbus-sensors/0007-NVMe-Add-createVolume.patch
new file mode 100644
index 0000000..6bf33a5
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0007-NVMe-Add-createVolume.patch
@@ -0,0 +1,796 @@
+From 55b10899182910b521b0d1680fea6b9380f0a058 Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Thu, 18 May 2023 15:08:24 +0800
+Subject: [PATCH 07/16] NVMe: Add createVolume
+
+Adds a new NVMeVolume object, and NVMeProgress object to track progress.
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: I397ab4f6fa5e2a554727186a1819cf6a0104fb2a
+(cherry picked from commit 92ec46692152250aa91435a531d1695517fd4b31)
+---
+ include/NVMeProgress.hpp |  58 ++++++++++++++++++
+ include/NVMeVolume.hpp   |  60 ++++++++++++++++++
+ src/NVMeController.hpp   |   8 +++
+ src/NVMeIntf.hpp         |  10 +++
+ src/NVMeMi.cpp           | 128 +++++++++++++++++++++++++++++++++++++++
+ src/NVMeMi.hpp           |   7 +++
+ src/NVMeProgress.cpp     |  86 ++++++++++++++++++++++++++
+ src/NVMeStorage.hpp      |  27 +++++++++
+ src/NVMeSubsys.cpp       | 113 ++++++++++++++++++++++++++++++++++
+ src/NVMeSubsys.hpp       |  31 ++++++++++
+ src/NVMeVolume.cpp       |  66 ++++++++++++++++++++
+ src/meson.build          |   2 +
+ 12 files changed, 596 insertions(+)
+ create mode 100644 include/NVMeProgress.hpp
+ create mode 100644 include/NVMeVolume.hpp
+ create mode 100644 src/NVMeProgress.cpp
+ create mode 100644 src/NVMeVolume.cpp
+
+diff --git a/include/NVMeProgress.hpp b/include/NVMeProgress.hpp
+new file mode 100644
+index 0000000..c5bf6ec
+--- /dev/null
++++ b/include/NVMeProgress.hpp
+@@ -0,0 +1,58 @@
++#pragma once
++
++#include "NVMeError.hpp"
++#include "NVMeVolume.hpp"
++
++#include <boost/asio.hpp>
++#include <sdbusplus/asio/connection.hpp>
++#include <sdbusplus/asio/object_server.hpp>
++#include <xyz/openbmc_project/Common/Progress/server.hpp>
++#include <xyz/openbmc_project/Nvme/CreateVolumeProgressFailure/server.hpp>
++#include <xyz/openbmc_project/Nvme/CreateVolumeProgressSuccess/server.hpp>
++
++#include <memory>
++
++class NVMeVolume;
++
++using OperationStatus =
++    sdbusplus::xyz::openbmc_project::Common::server::Progress::OperationStatus;
++using CreateVolumeProgressSuccess =
++    sdbusplus::xyz::openbmc_project::Nvme::server::CreateVolumeProgressSuccess;
++using CreateVolumeProgressFailure =
++    sdbusplus::xyz::openbmc_project::Nvme::server::CreateVolumeProgressFailure;
++
++class NVMeProgress :
++    protected sdbusplus::xyz::openbmc_project::Common::server::Progress
++{
++  public:
++    NVMeProgress(std::shared_ptr<sdbusplus::asio::connection> conn,
++                 const std::string& path);
++
++    ~NVMeProgress() override;
++
++    void complete();
++    void fail();
++};
++
++class NVMeCreateVolumeProgress : public NVMeProgress
++{
++  public:
++    NVMeCreateVolumeProgress(std::shared_ptr<sdbusplus::asio::connection> conn,
++                             const std::string& path);
++
++    ~NVMeCreateVolumeProgress() override;
++
++    void createSuccess(std::shared_ptr<NVMeVolume> volume);
++    void createFailure(nvme_ex_ptr e);
++
++    /* Returns the volume path if successful, or empty otherwise */
++    std::string volumePath() const;
++
++  private:
++    std::shared_ptr<sdbusplus::asio::connection> conn;
++    std::string path;
++
++    // interfaces are added only once the state is set to success/failure
++    std::shared_ptr<CreateVolumeProgressSuccess> success;
++    std::shared_ptr<CreateVolumeProgressFailure> failure;
++};
+diff --git a/include/NVMeVolume.hpp b/include/NVMeVolume.hpp
+new file mode 100644
+index 0000000..1b0e408
+--- /dev/null
++++ b/include/NVMeVolume.hpp
+@@ -0,0 +1,60 @@
++#pragma once
++
++#include "NVMeSubsys.hpp"
++
++#include <boost/asio.hpp>
++#include <sdbusplus/asio/connection.hpp>
++#include <sdbusplus/asio/object_server.hpp>
++#include <xyz/openbmc_project/Inventory/Item/Volume/server.hpp>
++#include <xyz/openbmc_project/Nvme/Volume/server.hpp>
++
++#include <memory>
++
++using VolumeBase =
++    sdbusplus::xyz::openbmc_project::Inventory::Item::server::Volume;
++using NvmeVolumeBase = sdbusplus::xyz::openbmc_project::Nvme::server::Volume;
++
++class NVMeSubsystem;
++
++// Object.Delete implemented manually at present, to allow async method call
++// for .Delete
++
++// using DeleteBase =
++//     sdbusplus::xyz::openbmc_project::Object::server::Delete;
++
++class NVMeVolume : public VolumeBase, public NvmeVolumeBase
++{
++  public:
++    static std::shared_ptr<NVMeVolume>
++        create(sdbusplus::asio::object_server& objServer,
++               std::shared_ptr<sdbusplus::asio::connection> conn,
++               std::shared_ptr<NVMeSubsystem> subsys, uint32_t nsid);
++    ~NVMeVolume() override;
++
++    const std::string path;
++
++  private:
++    NVMeVolume(sdbusplus::asio::object_server& objServer,
++               std::shared_ptr<sdbusplus::asio::connection> conn,
++               std::shared_ptr<NVMeSubsystem> subsys, uint32_t nsid);
++    void init();
++
++    void formatLuks(std::vector<uint8_t> password,
++                    VolumeBase::FilesystemType type) override;
++
++    void erase(VolumeBase::EraseMethod eraseType) override;
++
++    void lock() override;
++
++    void unlock(std::vector<uint8_t> password) override;
++
++    void changePassword(std::vector<uint8_t> oldPassword,
++                        std::vector<uint8_t> newPassword) override;
++
++    // TODO, for Object.Delete
++    // std::shared_ptr<sdbusplus::asio::dbus_interface> deleteInterface;
++
++    sdbusplus::asio::object_server& objServer;
++
++    std::weak_ptr<NVMeSubsystem> subsys;
++};
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+index ef32a7e..4349ebb 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -58,6 +58,14 @@ class NVMeController
+              std::max(sizeof(uint16_t), sizeof(void*))));
+     }
+ 
++    /**
++     * @brief Get the NVMe controller handle
++     */
++    nvme_mi_ctrl_t getMiCtrl() const
++    {
++        return nvmeCtrl;
++    }
++
+     /**
+      * @brief Register the NVMe subsystem to the controller. The function can be
+      * called mutiple times to associate multi-subsys to a single controller.
+diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
+index 7751fac..e4769e8 100644
+--- a/src/NVMeIntf.hpp
++++ b/src/NVMeIntf.hpp
+@@ -1,4 +1,7 @@
+ #pragma once
++
++#include "NVMeError.hpp"
++
+ #include <libnvme-mi.h>
+ 
+ #include <functional>
+@@ -201,6 +204,13 @@ class NVMeMiIntf
+         uint32_t cdw13, uint32_t cdw14, uint32_t cdw15,
+         std::function<void(const std::error_code&, int nvme_status,
+                            uint32_t comption_dw0)>&& cb) = 0;
++
++    virtual void createNamespace(
++        nvme_mi_ctrl_t ctrl, uint64_t size, size_t lba_format,
++        bool metadata_at_end,
++        std::function<void(nvme_ex_ptr ex)>&& submitted_cb,
++        std::function<void(nvme_ex_ptr ex, uint32_t new_ns)>&& finished_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 17825ac..f8578ba 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -1,7 +1,10 @@
+ #include "NVMeMi.hpp"
+ 
++#include "NVMeError.hpp"
+ #include "NVMeUtil.hpp"
+ 
++#include <endian.h>
++
+ #include <boost/endian.hpp>
+ 
+ #include <cerrno>
+@@ -1167,3 +1170,128 @@ void NVMeMi::adminNonDataCmd(
+         io.post([cb{std::move(cb)}, post_err]() { cb(post_err, -1, 0); });
+     }
+ }
++
++/* throws a nvme_ex_ptr on failure */
++size_t NVMeMi::getBlockSize(nvme_mi_ctrl_t ctrl, size_t lba_format)
++{
++    struct nvme_id_ns id;
++    printf("getblocksize\n");
++    int status = nvme_mi_admin_identify_ns(ctrl, NVME_NSID_ALL, &id);
++    auto e = makeLibNVMeError(errno, status, "getBlockSize");
++    if (e)
++    {
++        throw e;
++    }
++
++    printf("nlbaf %d, lbaf %d\n", (int)id.nlbaf, lba_format);
++
++    // Sanity check for the value from the drive
++    size_t max_lbaf = std::min(63, (int)id.nlbaf);
++
++    // NLBAF is the maximum allowed index (not a count)
++    if (lba_format > max_lbaf)
++    {
++        throw makeLibNVMeError("LBA format out of range, maximum is " +
++                                   std::to_string(max_lbaf),
++                               std::make_shared<CommonErr::InvalidArgument>());
++    }
++
++    return 1 << id.lbaf[lba_format].ds;
++}
++
++/*
++ finished_cb will not be called if submitted_cb is called with a failure.
++ */
++void NVMeMi::createNamespace(
++    nvme_mi_ctrl_t ctrl, uint64_t size, size_t lba_format, bool metadata_at_end,
++    std::function<void(nvme_ex_ptr ex)>&& submitted_cb,
++    std::function<void(nvme_ex_ptr ex, uint32_t new_ns)>&& finished_cb)
++{
++    printf("createns %d\n", (int)gettid());
++    std::error_code post_err =
++        try_post([self{shared_from_this()}, ctrl, size, lba_format,
++                  metadata_at_end, submitted_cb{std::move(submitted_cb)},
++                  finished_cb{std::move(finished_cb)}]() {
++            size_t block_size;
++
++            try
++            {
++                block_size = self->getBlockSize(ctrl, lba_format);
++            }
++            catch (nvme_ex_ptr e)
++            {
++                submitted_cb(e);
++                return;
++            }
++
++            if (size % block_size != 0)
++            {
++                auto msg =
++                    std::string("Size must be a multiple of the block size ") +
++                    std::to_string(block_size);
++                submitted_cb(std::make_shared<NVMeSdBusPlusError>(msg));
++                return;
++            }
++
++            uint64_t blocks = size / block_size;
++
++            // TODO: this will become nvme_ns_mgmt_host_sw_specified in a newer
++            // libnvme.
++            struct nvme_id_ns data;
++            uint32_t new_ns = 0;
++
++            uint8_t flbas = 0;
++            if (metadata_at_end)
++            {
++                flbas |= (1 << 4);
++            }
++            // low 4 bits at 0:3
++            flbas |= (lba_format & 0xf);
++            // high 2 bits at 5:6
++            flbas |= ((lba_format & 0x30) << 1);
++
++            memset(&data, 0x0, sizeof(data));
++            data.nsze = ::htole64(blocks);
++            data.ncap = ::htole64(blocks);
++            data.flbas = flbas;
++
++            printf("verified %d\n", (int)gettid());
++
++            // submission has been verified.
++            submitted_cb(nvme_ex_ptr());
++            printf("after submitted_cb %d\n", (int)gettid());
++
++            int status = nvme_mi_admin_ns_mgmt_create(ctrl, &data, 0, &new_ns);
++            nvme_ex_ptr e = makeLibNVMeError(errno, status, "createVolume");
++
++            self->io.post([finished_cb{std::move(finished_cb)}, e, new_ns]() {
++                finished_cb(e, new_ns);
++            });
++
++#if 0
++        // TODO testing purposes
++        static uint32_t counter = 20;
++
++        printf("createNamespace top, sleeping 5 seconds\n");
++        sleep(5);
++
++        uint32_t new_ns = counter++;
++
++        printf("create complete. ns %d\n", new_ns);
++
++        auto err = std::make_error_code(static_cast<std::errc>(0));
++        cb(err, 0, new_ns);
++#endif
++        });
++
++    printf("submitted cb %d\n", (int)gettid());
++
++    if (post_err)
++    {
++        std::cerr << "adminAttachDetachNamespace post failed: " << post_err
++                  << std::endl;
++        auto e = makeLibNVMeError(post_err, -1, "createVolume");
++        io.post(
++            [submitted_cb{std::move(submitted_cb)}, e]() { submitted_cb(e); });
++    }
++}
+diff --git a/src/NVMeMi.hpp b/src/NVMeMi.hpp
+index b8b6c45..3a70a9e 100644
+--- a/src/NVMeMi.hpp
++++ b/src/NVMeMi.hpp
+@@ -65,6 +65,11 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+         uint32_t cdw13, uint32_t cdw14, uint32_t cdw15,
+         std::function<void(const std::error_code&, int nvme_status,
+                            uint32_t comption_dw0)>&& cb);
++    void createNamespace(nvme_mi_ctrl_t ctrl, uint64_t size, size_t lba_format,
++                         bool metadata_at_end,
++                         std::function<void(nvme_ex_ptr ex)>&& submitted_cb,
++                         std::function<void(nvme_ex_ptr ex, uint32_t new_ns)>&&
++                             finished_cb) override;
+ 
+   private:
+     // the transfer size for nvme mi messages.
+@@ -139,4 +144,6 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+         nvme_mi_ctrl_t ctrl, bool host, uint64_t offset,
+         std::vector<uint8_t>&& data,
+         std::function<void(const std::error_code&, std::span<uint8_t>)>&& cb);
++
++    size_t getBlockSize(nvme_mi_ctrl_t ctrl, size_t lba_format);
+ };
+diff --git a/src/NVMeProgress.cpp b/src/NVMeProgress.cpp
+new file mode 100644
+index 0000000..ebc649d
+--- /dev/null
++++ b/src/NVMeProgress.cpp
+@@ -0,0 +1,86 @@
++#include "NVMeProgress.hpp"
++
++using sdbusplus::xyz::openbmc_project::Common::server::Progress;
++
++NVMeProgress::NVMeProgress(std::shared_ptr<sdbusplus::asio::connection> conn,
++                           const std::string& path) :
++    Progress(dynamic_cast<sdbusplus::bus_t&>(*conn), path.c_str())
++{
++    uint64_t usec = std::chrono::duration_cast<std::chrono::microseconds>(
++                        std::chrono::system_clock::now().time_since_epoch())
++                        .count();
++    startTime(usec, true);
++
++    Progress::emit_added();
++}
++
++NVMeProgress::~NVMeProgress()
++{
++    Progress::emit_removed();
++}
++
++void NVMeProgress::complete()
++{
++    uint64_t usec = std::chrono::duration_cast<std::chrono::microseconds>(
++                        std::chrono::system_clock::now().time_since_epoch())
++                        .count();
++    completedTime(usec);
++    status(OperationStatus::Completed);
++}
++
++void NVMeProgress::fail()
++{
++    // TODO: perhaps errorName could be a general NVMeProgress property.
++    status(OperationStatus::Failed);
++}
++
++NVMeCreateVolumeProgress::NVMeCreateVolumeProgress(
++    std::shared_ptr<sdbusplus::asio::connection> conn,
++    const std::string& path) :
++    NVMeProgress(conn, path),
++    conn(conn), path(path)
++{}
++
++NVMeCreateVolumeProgress::~NVMeCreateVolumeProgress()
++{
++    // TODO
++    if (success)
++    {
++        success->emit_removed();
++    }
++    if (failure)
++    {
++        failure->emit_removed();
++    }
++}
++
++void NVMeCreateVolumeProgress::createSuccess(std::shared_ptr<NVMeVolume> volume)
++{
++    success = std::make_shared<CreateVolumeProgressSuccess>(
++        dynamic_cast<sdbusplus::bus_t&>(*conn), path.c_str());
++    success->volumePath(volume->path);
++    success->emit_added();
++    complete();
++}
++
++void NVMeCreateVolumeProgress::createFailure(nvme_ex_ptr e)
++{
++    failure = std::make_shared<CreateVolumeProgressFailure>(
++        dynamic_cast<sdbusplus::bus_t&>(*conn), path.c_str());
++    failure->errorName(e->name());
++    failure->errorDescription(e->description());
++    failure->emit_added();
++    fail();
++}
++
++std::string NVMeCreateVolumeProgress::volumePath() const
++{
++    if (success)
++    {
++        return success->volumePath();
++    }
++    else
++    {
++        return "";
++    }
++}
+diff --git a/src/NVMeStorage.hpp b/src/NVMeStorage.hpp
+index 6913eee..b8be4dc 100644
+--- a/src/NVMeStorage.hpp
++++ b/src/NVMeStorage.hpp
+@@ -1,5 +1,7 @@
+ #pragma once
+ 
++#include "NVMeError.hpp"
++
+ #include <xyz/openbmc_project/Inventory/Item/Storage/server.hpp>
+ 
+ #include <memory>
+@@ -17,17 +19,42 @@ class NVMeStorage : public StorageBase
+ 
+     ~NVMeStorage() override
+     {
++        objServer.remove_interface(nvmeStorageInterface);
+         emit_removed();
+     }
+ 
++    virtual sdbusplus::message::object_path
++        createVolume(boost::asio::yield_context yield, uint64_t size,
++                     size_t lbaFormat, bool metadataAtEnd) = 0;
++
+   protected:
+     // Called by parent class for setup after shared_ptr has been initialised
+     static void init(std::shared_ptr<NVMeStorage> self)
+     {
++        self->nvmeStorageInterface = self->objServer.add_interface(
++            self->path, "xyz.openbmc_project.Nvme.Storage");
++        self->nvmeStorageInterface->register_method(
++            "CreateVolume", [weak{std::weak_ptr<NVMeStorage>(self)}](
++                                boost::asio::yield_context yield, uint64_t size,
++                                size_t lbaFormat, bool metadataAtEnd) {
++                if (auto self = weak.lock())
++                {
++                    return self->createVolume(yield, size, lbaFormat,
++                                              metadataAtEnd);
++                }
++                throw *makeLibNVMeError("storage removed");
++            });
++        self->nvmeStorageInterface->initialize();
++
+         self->emit_added();
+     }
+ 
+   private:
++    // NVMe-specific interface.
++    // implemented manually for async, will eventually come from
++    // sdbusplus::xyz::openbmc_project::Nvme::Storage
++    std::shared_ptr<sdbusplus::asio::dbus_interface> nvmeStorageInterface;
++
+     sdbusplus::asio::object_server& objServer;
+ 
+     const std::string path;
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index 4738628..5de6721 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -593,3 +593,116 @@ void NVMeSubsystem::fallbackNoSecondary()
+     }
+     status = Status::Start;
+ }
++
++sdbusplus::message::object_path
++    NVMeSubsystem::createVolume(boost::asio::yield_context yield, uint64_t size,
++                                size_t lbaFormat, bool metadataAtEnd)
++{
++    // #0 (sequence of runtime/callbacks)
++    auto prog_id = getRandomId();
++
++    nvme_mi_ctrl_t ctrl = primaryController->getMiCtrl();
++    auto intf = std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
++
++    using submit_callback_t = void(std::tuple<nvme_ex_ptr>);
++    auto [ex] = boost::asio::async_initiate<boost::asio::yield_context,
++                                            submit_callback_t>(
++        [weak{weak_from_this()}, prog_id, intf, ctrl, size, lbaFormat,
++         metadataAtEnd](auto&& handler) {
++        auto h = asio_helper::CopyableCallback(std::move(handler));
++
++        // #1
++        intf->createNamespace(
++            ctrl, size, lbaFormat, metadataAtEnd,
++
++            // submitted_cb
++            [h](nvme_ex_ptr ex) mutable {
++            // #2
++
++            // Async completion of the createNamespace call.
++            // The actual nvme_mi_admin_ns_mgmt_create() call is still running
++            // in a separate thread. Pass the error status back out.
++            h(std::make_tuple(ex));
++            },
++
++            // finished_cb
++            [weak, prog_id](nvme_ex_ptr ex, uint32_t new_ns) mutable {
++            // #5. This will only be called once #4 completes.
++            // It will not be called if the submit failed.
++            auto self = weak.lock();
++            if (!self)
++            {
++                std::cerr << "createNamespace completed while nvmesensor was "
++                             "exiting\n";
++                return;
++            }
++            // The NS create has completed (either successfully or not)
++            self->createVolumeFinished(prog_id, ex, new_ns);
++        });
++        },
++        yield);
++
++    // #3
++
++    // Exception must be thrown outside of the async block
++    if (ex)
++    {
++        throw *ex;
++    }
++
++    // Progress endpoint for clients to poll, if the submit was successful.
++    std::string prog_path = path + "/CreateProgress/" + prog_id;
++
++    auto prog = std::make_shared<NVMeCreateVolumeProgress>(conn, prog_path);
++    if (!createProgress.insert({prog_id, prog}).second)
++    {
++        throw std::logic_error("duplicate progress id");
++    }
++
++    // #4
++    return prog_path;
++}
++
++void NVMeSubsystem::createVolumeFinished(std::string prog_id, nvme_ex_ptr ex,
++                                         uint32_t new_ns)
++{
++    try
++    {
++        auto p = createProgress.find(prog_id);
++        if (p == createProgress.end())
++        {
++            throw std::logic_error("Missing progress entry");
++        }
++        auto prog = p->second;
++
++        if (ex)
++        {
++            prog->createFailure(ex);
++            return;
++        }
++
++        if (volumes.contains(new_ns))
++        {
++            std::string err_msg = std::string("Internal error, NSID exists " +
++                                              std::to_string(new_ns));
++            std::cerr << err_msg << "\n";
++            prog->createFailure(makeLibNVMeError(err_msg));
++            return;
++        }
++
++        auto vol =
++            NVMeVolume::create(objServer, conn, shared_from_this(), new_ns);
++        volumes.insert({new_ns, vol});
++        prog->createSuccess(vol);
++    }
++    catch (const std::exception& e)
++    {
++        std::cerr << "Unhandled error in createVolumeFinished: " << e.what()
++                  << "\n";
++    }
++}
++
++std::string NVMeSubsystem::volumePath(uint32_t nsid) const
++{
++    return path + "/volumes/" + std::to_string(nsid);
++}
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index c29d271..bc2f4d6 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -2,12 +2,16 @@
+ #include "NVMeBasic.hpp"
+ #include "NVMeController.hpp"
+ #include "NVMeDrive.hpp"
++#include "NVMeProgress.hpp"
+ #include "NVMeSensor.hpp"
+ #include "NVMeStorage.hpp"
++#include "NVMeUtil.hpp"
+ #include "Utils.hpp"
+ 
+ class NVMeControllerPlugin;
+ class NVMePlugin;
++class NVMeVolume;
++class NVMeCreateVolumeProgress;
+ 
+ class NVMeSubsystem :
+     public std::enable_shared_from_this<NVMeSubsystem>,
+@@ -29,6 +33,14 @@ class NVMeSubsystem :
+ 
+     void stop();
+ 
++    /** @brief Returns the dbus path for a given volume.
++     *
++     *  @param[in] nsid - The NSID of the volume
++     *
++     *  @return path[std::string] - The dbus path for the volume.
++     */
++    std::string volumePath(uint32_t nsid) const;
++
+   private:
+     NVMeSubsystem(boost::asio::io_context& io,
+                   sdbusplus::asio::object_server& objServer,
+@@ -73,6 +85,17 @@ class NVMeSubsystem :
+                                  std::shared_ptr<NVMeControllerPlugin>>>
+         controllers{};
+ 
++    /*
++    map of nsid to volumes
++    */
++    std::map<uint32_t, std::shared_ptr<NVMeVolume>> volumes;
++
++    /*
++    In-progress or completed create operations
++    */
++    std::unordered_map<std::string, std::shared_ptr<NVMeCreateVolumeProgress>>
++        createProgress;
++
+     // controller to use for NVMe operations. Is a member of the controllers
+     // map.
+     std::shared_ptr<NVMeControllerEnabled> primaryController;
+@@ -89,6 +112,14 @@ class NVMeSubsystem :
+ 
+     void fallbackNoSecondary();
+ 
++    sdbusplus::message::object_path
++        createVolume(boost::asio::yield_context yield, uint64_t size,
++                     size_t lbaFormat, bool metadataAtEnd);
++
++    // callback when drive completes. not called in dbus method context.
++    void createVolumeFinished(std::string prog_id, nvme_ex_ptr ex,
++                              uint32_t new_ns);
++
+     // a counter to skip health poll when NVMe subsystem becomes Unavailable
+     unsigned UnavailableCount = 0;
+     static constexpr unsigned UnavailableMaxCount = 60;
+diff --git a/src/NVMeVolume.cpp b/src/NVMeVolume.cpp
+new file mode 100644
+index 0000000..f3eea64
+--- /dev/null
++++ b/src/NVMeVolume.cpp
+@@ -0,0 +1,66 @@
++#include "NVMeVolume.hpp"
++
++NVMeVolume::NVMeVolume(sdbusplus::asio::object_server& objServer,
++                       std::shared_ptr<sdbusplus::asio::connection> conn,
++                       std::shared_ptr<NVMeSubsystem> subsys, uint32_t nsid) :
++    VolumeBase(dynamic_cast<sdbusplus::bus_t&>(*conn),
++               subsys->volumePath(nsid).c_str()),
++    NvmeVolumeBase(dynamic_cast<sdbusplus::bus_t&>(*conn),
++                   subsys->volumePath(nsid).c_str()),
++    path(subsys->volumePath(nsid)), objServer(objServer), subsys(subsys)
++{
++    namespaceId(nsid, false);
++    // see init()
++}
++
++void NVMeVolume::init()
++{
++    VolumeBase::emit_added();
++    NvmeVolumeBase::emit_added();
++}
++
++std::shared_ptr<NVMeVolume>
++    NVMeVolume::create(sdbusplus::asio::object_server& objServer,
++                       std::shared_ptr<sdbusplus::asio::connection> conn,
++                       std::shared_ptr<NVMeSubsystem> subsys, uint32_t nsid)
++{
++    auto self = std::shared_ptr<NVMeVolume>(
++        new NVMeVolume(objServer, conn, subsys, nsid));
++    self->init();
++    return self;
++}
++
++NVMeVolume::~NVMeVolume()
++{
++    NvmeVolumeBase::emit_removed();
++    VolumeBase::emit_removed();
++}
++
++void NVMeVolume::erase(VolumeBase::EraseMethod eraseType)
++{
++    (void)eraseType;
++    // TODO: will need dbus async method handling.
++    throw std::runtime_error("volume erase not yet implemented");
++}
++
++// Additional methods on Volume that are not relevant.
++
++void NVMeVolume::formatLuks(std::vector<uint8_t>, VolumeBase::FilesystemType)
++{
++    throw std::runtime_error("Method Not Supported");
++}
++
++void NVMeVolume::lock()
++{
++    throw std::runtime_error("Method Not Supported");
++}
++
++void NVMeVolume::unlock(std::vector<uint8_t>)
++{
++    throw std::runtime_error("Method Not Supported");
++}
++
++void NVMeVolume::changePassword(std::vector<uint8_t>, std::vector<uint8_t>)
++{
++    throw std::runtime_error("Method Not Supported");
++}
+diff --git a/src/meson.build b/src/meson.build
+index 27ab8ea..f630802 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -193,6 +193,8 @@ if get_option('nvme').enabled()
+         'NVMeSubsys.cpp',
+         'NVMeMi.cpp',
+         'NVMeController.cpp',
++        'NVMeProgress.cpp',
++        'NVMeVolume.cpp',
+         'NVMeError.cpp',
+     )
+ 
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0008-nvme-add-Delete-method-to-volume.patch b/recipes-phosphor/sensors/dbus-sensors/0008-nvme-add-Delete-method-to-volume.patch
new file mode 100644
index 0000000..8e49d5f
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0008-nvme-add-Delete-method-to-volume.patch
@@ -0,0 +1,206 @@
+From 51bbc59d50b302a96a789a58f56462e950910f77 Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Fri, 28 Apr 2023 19:21:42 +0800
+Subject: [PATCH 08/16] nvme: add Delete method to volume
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: I0516143b63d0234abb10ef0b09555f365fd81f16
+(cherry picked from commit c99d4432ebf7d82f6b4aed31b7d15fb1a9f0ad4b)
+---
+ include/NVMeVolume.hpp |  8 +++++---
+ src/NVMeIntf.hpp       |  4 ++++
+ src/NVMeMi.cpp         | 21 +++++++++++++++++++++
+ src/NVMeMi.hpp         |  5 +++++
+ src/NVMeSubsys.cpp     | 40 ++++++++++++++++++++++++++++++++++++++++
+ src/NVMeSubsys.hpp     |  3 +++
+ src/NVMeVolume.cpp     | 19 +++++++++++++++++++
+ 7 files changed, 97 insertions(+), 3 deletions(-)
+
+diff --git a/include/NVMeVolume.hpp b/include/NVMeVolume.hpp
+index 1b0e408..09b04d1 100644
+--- a/include/NVMeVolume.hpp
++++ b/include/NVMeVolume.hpp
+@@ -22,7 +22,10 @@ class NVMeSubsystem;
+ // using DeleteBase =
+ //     sdbusplus::xyz::openbmc_project::Object::server::Delete;
+ 
+-class NVMeVolume : public VolumeBase, public NvmeVolumeBase
++class NVMeVolume :
++    public VolumeBase,
++    public NvmeVolumeBase,
++    public std::enable_shared_from_this<NVMeVolume>
+ {
+   public:
+     static std::shared_ptr<NVMeVolume>
+@@ -51,8 +54,7 @@ class NVMeVolume : public VolumeBase, public NvmeVolumeBase
+     void changePassword(std::vector<uint8_t> oldPassword,
+                         std::vector<uint8_t> newPassword) override;
+ 
+-    // TODO, for Object.Delete
+-    // std::shared_ptr<sdbusplus::asio::dbus_interface> deleteInterface;
++    std::shared_ptr<sdbusplus::asio::dbus_interface> deleteInterface;
+ 
+     sdbusplus::asio::object_server& objServer;
+ 
+diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
+index e4769e8..fc7f78e 100644
+--- a/src/NVMeIntf.hpp
++++ b/src/NVMeIntf.hpp
+@@ -211,6 +211,10 @@ class NVMeMiIntf
+         std::function<void(nvme_ex_ptr ex)>&& submitted_cb,
+         std::function<void(nvme_ex_ptr ex, uint32_t new_ns)>&& finished_cb) = 0;
+ 
++    virtual void adminDeleteNamespace(
++        nvme_mi_ctrl_t ctrl, uint32_t nsid,
++        std::function<void(const std::error_code&, int nvme_status)>&& 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 f8578ba..10f3213 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -1295,3 +1295,24 @@ void NVMeMi::createNamespace(
+             [submitted_cb{std::move(submitted_cb)}, e]() { submitted_cb(e); });
+     }
+ }
++
++// Deletes a namespace
++void NVMeMi::adminDeleteNamespace(
++    nvme_mi_ctrl_t ctrl, uint32_t nsid,
++    std::function<void(const std::error_code&, int nvme_status)>&& cb)
++{
++    std::error_code post_err = try_post(
++        [self{shared_from_this()}, ctrl, nsid, cb{std::move(cb)}]() {
++        int status = nvme_mi_admin_ns_mgmt_delete(ctrl, nsid);
++
++        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 << "deleteNamespace 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 3a70a9e..08ea4f9 100644
+--- a/src/NVMeMi.hpp
++++ b/src/NVMeMi.hpp
+@@ -71,6 +71,11 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+                          std::function<void(nvme_ex_ptr ex, uint32_t new_ns)>&&
+                              finished_cb) override;
+ 
++    void adminDeleteNamespace(
++        nvme_mi_ctrl_t ctrl, uint32_t nsid,
++        std::function<void(const std::error_code&, int nvme_status)>&& cb)
++        override;
++
+   private:
+     // the transfer size for nvme mi messages.
+     // define in github.com/linux-nvme/libnvme/blob/master/src/nvme/mi.c
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index 5de6721..ca126e7 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -706,3 +706,43 @@ std::string NVMeSubsystem::volumePath(uint32_t nsid) const
+ {
+     return path + "/volumes/" + std::to_string(nsid);
+ }
++
++void NVMeSubsystem::deleteVolume(boost::asio::yield_context yield,
++                                 std::shared_ptr<NVMeVolume> volume)
++{
++    nvme_mi_ctrl_t ctrl = primaryController->getMiCtrl();
++    auto intf = std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
++
++    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, ctrl, nsid{volume->namespaceId()}](auto&& handler) {
++        auto h = asio_helper::CopyableCallback(std::move(handler));
++
++        intf->adminDeleteNamespace(
++            ctrl, nsid,
++            [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, "Delete");
++
++    // remove any progress references
++    for (const auto& [prog_id, prog] : createProgress)
++    {
++        std::string s = prog->volumePath();
++        if (prog->volumePath() == volume->path)
++        {
++            createProgress.erase(prog_id);
++            break;
++        }
++    }
++
++    if (volumes.erase(volume->namespaceId()) != 1)
++    {
++        throw std::runtime_error("volume disappeared unexpectedly");
++    }
++}
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index bc2f4d6..b223eb5 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -41,6 +41,9 @@ class NVMeSubsystem :
+      */
+     std::string volumePath(uint32_t nsid) const;
+ 
++    void deleteVolume(boost::asio::yield_context yield,
++                      std::shared_ptr<NVMeVolume> volume);
++
+   private:
+     NVMeSubsystem(boost::asio::io_context& io,
+                   sdbusplus::asio::object_server& objServer,
+diff --git a/src/NVMeVolume.cpp b/src/NVMeVolume.cpp
+index f3eea64..2e262a5 100644
+--- a/src/NVMeVolume.cpp
++++ b/src/NVMeVolume.cpp
+@@ -15,6 +15,24 @@ NVMeVolume::NVMeVolume(sdbusplus::asio::object_server& objServer,
+ 
+ void NVMeVolume::init()
+ {
++    deleteInterface =
++        objServer.add_interface(path, "xyz.openbmc_project.Object.Delete");
++    deleteInterface->register_method(
++        "Delete", [weak{weak_from_this()}](boost::asio::yield_context yield) {
++            auto self = weak.lock();
++            if (!self)
++            {
++                throw std::runtime_error("volume delete called twice?");
++            }
++            auto subsys = self->subsys.lock();
++            if (!subsys)
++            {
++                throw std::runtime_error("nvmesensor is shutting down");
++            }
++            subsys->deleteVolume(yield, self);
++        });
++    deleteInterface->initialize();
++
+     VolumeBase::emit_added();
+     NvmeVolumeBase::emit_added();
+ }
+@@ -34,6 +52,7 @@ NVMeVolume::~NVMeVolume()
+ {
+     NvmeVolumeBase::emit_removed();
+     VolumeBase::emit_removed();
++    objServer.remove_interface(deleteInterface);
+ }
+ 
+ void NVMeVolume::erase(VolumeBase::EraseMethod eraseType)
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0009-nvme-updateVolumes-to-populate-volumes.patch b/recipes-phosphor/sensors/dbus-sensors/0009-nvme-updateVolumes-to-populate-volumes.patch
new file mode 100644
index 0000000..595735e
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0009-nvme-updateVolumes-to-populate-volumes.patch
@@ -0,0 +1,273 @@
+From c50f4c8d1cc920553fc0362529b4eea5ebed7bb1 Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Thu, 4 May 2023 14:34:02 +0800
+Subject: [PATCH 09/16] nvme: updateVolumes() to populate volumes
+
+This lists namespaces from the drive and adds/removes Volume objects as
+required.
+
+Currently called once at startup, could be made to run on a timer or
+triggered from a dbus method.
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: I2af3f1f3a4a4d218eca668d8ab0ae443d0871453
+(cherry picked from commit 0bd3368b1a466115bcec7e18d2880e01e199bb26)
+---
+ src/NVMeIntf.hpp   |  11 +++++
+ src/NVMeMi.cpp     |  53 ++++++++++++++++++++++
+ src/NVMeMi.hpp     |   5 +++
+ src/NVMeSubsys.cpp | 109 +++++++++++++++++++++++++++++++++++++++++++++
+ src/NVMeSubsys.hpp |   4 ++
+ 5 files changed, 182 insertions(+)
+
+diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
+index fc7f78e..894590f 100644
+--- a/src/NVMeIntf.hpp
++++ b/src/NVMeIntf.hpp
+@@ -215,6 +215,17 @@ class NVMeMiIntf
+         nvme_mi_ctrl_t ctrl, uint32_t nsid,
+         std::function<void(const std::error_code&, int nvme_status)>&& cb) = 0;
+ 
++    /**
++     * listNamespaces() - return list of NSIDs
++     *
++     * @cb will be called on success or failure. On success @ns will
++     * contain the list of NSIDs in sequential order, including inactive
++     * namespaces.
++     */
++    virtual void adminListNamespaces(
++        nvme_mi_ctrl_t ctrl,
++        std::function<void(nvme_ex_ptr ex, std::vector<uint32_t> ns)>&& 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 10f3213..56dcfbd 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -1316,3 +1316,56 @@ void NVMeMi::adminDeleteNamespace(
+         io.post([cb{std::move(cb)}, post_err]() { cb(post_err, -1); });
+     }
+ }
++
++void NVMeMi::adminListNamespaces(
++    nvme_mi_ctrl_t ctrl,
++    std::function<void(nvme_ex_ptr, std::vector<uint32_t> ns)>&& cb)
++{
++    std::error_code post_err = try_post(
++        [self{shared_from_this()}, ctrl, cb{std::move(cb)}]() {
++        int status, nvme_errno;
++        std::vector<uint32_t> ns;
++        // sanity in case of bad drives, allows for >1million NSes
++        const int MAX_ITER = 1000;
++
++        for (int i = 0; i < MAX_ITER; i++)
++        {
++            struct nvme_ns_list list;
++            uint32_t start = NVME_NSID_NONE;
++            if (!ns.empty())
++            {
++                start = ns.back() + 1;
++            }
++            status =
++                nvme_mi_admin_identify_allocated_ns_list(ctrl, start, &list);
++            nvme_errno = errno;
++            if (status != 0)
++            {
++                ns.clear();
++                break;
++            }
++
++            for (size_t i = 0; i < NVME_ID_NS_LIST_MAX; i++)
++            {
++                if (!list.ns[i])
++                {
++                    break;
++                }
++                ns.push_back(list.ns[i]);
++            }
++            if (list.ns[NVME_ID_NS_LIST_MAX - 1] == 0)
++            {
++                // all entries read
++                break;
++            }
++        }
++
++        auto ex = makeLibNVMeError(nvme_errno, status, "adminListNamespaces");
++        self->io.post([cb{std::move(cb)}, ex, ns]() { cb(ex, ns); });
++    });
++    if (post_err)
++    {
++        auto ex = makeLibNVMeError("post failed");
++        io.post([cb{std::move(cb)}, ex]() { cb(ex, std::vector<uint32_t>()); });
++    }
++}
+diff --git a/src/NVMeMi.hpp b/src/NVMeMi.hpp
+index 08ea4f9..13d5bec 100644
+--- a/src/NVMeMi.hpp
++++ b/src/NVMeMi.hpp
+@@ -76,6 +76,11 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+         std::function<void(const std::error_code&, int nvme_status)>&& cb)
+         override;
+ 
++    void adminListNamespaces(
++        nvme_mi_ctrl_t ctrl,
++        std::function<void(nvme_ex_ptr ex, std::vector<uint32_t> ns)>&& cb)
++        override;
++
+   private:
+     // the transfer size for nvme mi messages.
+     // define in github.com/linux-nvme/libnvme/blob/master/src/nvme/mi.c
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index ca126e7..d32392a 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -319,6 +319,9 @@ void NVMeSubsystem::markFunctional(bool toggle)
+                 }
+ 
+                 self->status = Status::Start;
++
++                // TODO: may need to wait for this to complete?
++                self->updateVolumes();
+                 });
+         });
+     }
+@@ -592,6 +595,9 @@ void NVMeSubsystem::fallbackNoSecondary()
+         pair.first->start(pair.second);
+     }
+     status = Status::Start;
++
++    // TODO: may need to wait for this to complete?
++    updateVolumes();
+ }
+ 
+ sdbusplus::message::object_path
+@@ -707,6 +713,109 @@ std::string NVMeSubsystem::volumePath(uint32_t nsid) const
+     return path + "/volumes/" + std::to_string(nsid);
+ }
+ 
++void NVMeSubsystem::addIdentifyNamespace(uint32_t nsid)
++{
++    nvme_mi_ctrl_t ctrl = primaryController->getMiCtrl();
++    auto intf = std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
++    intf->adminIdentify(ctrl, nvme_identify_cns::NVME_IDENTIFY_CNS_ALLOCATED_NS,
++                        nsid, NVME_CNTLID_NONE,
++                        [self{shared_from_this()}, intf, ctrl,
++                         nsid](nvme_ex_ptr ex, std::span<uint8_t> data) {
++        if (ex)
++        {
++            std::cerr << "Error adding volume for nsid " << nsid << ": " << ex
++                      << "\n";
++            return;
++        }
++
++        nvme_id_ns& id = *reinterpret_cast<nvme_id_ns*>(data.data());
++
++        // msb 6:5 and lsb 3:0
++        size_t lbaf_index = ((id.flbas >> 1) & 0x30) | (id.flbas & 0x0f);
++        size_t blockSize = 1ul << id.lbaf[lbaf_index].ds;
++        bool metadataAtEnd = id.flbas & (1 << 4);
++
++        NVMeNSIdentify ns = {
++            .namespaceId = nsid,
++            .size = ::le64toh(id.nsze * blockSize),
++            .capacity = ::le64toh(id.ncap * blockSize),
++            .blockSize = blockSize,
++            .lbaFormat = lbaf_index,
++            .metadataAtEnd = metadataAtEnd,
++        };
++
++        self->addVolume(ns);
++
++        // determine attached controllers
++        intf->adminIdentify(
++            ctrl, nvme_identify_cns::NVME_IDENTIFY_CNS_NS_CTRL_LIST, nsid,
++            NVME_CNTLID_NONE,
++            [self, nsid](nvme_ex_ptr ex, std::span<uint8_t> data) {
++            if (ex)
++            {
++                std::cerr << "Error fetching attached controller list for nsid "
++                          << nsid << ": " << ex << "\n";
++                return;
++            }
++
++            nvme_ctrl_list& list =
++                *reinterpret_cast<nvme_ctrl_list*>(data.data());
++            uint16_t num = ::le16toh(list.num);
++            if (num == NVME_ID_CTRL_LIST_MAX)
++            {
++                std::cerr << "Warning: full ctrl list returned\n";
++            }
++
++            for (auto i = 0; i < num; i++)
++            {
++                uint16_t c = ::le16toh(list.identifier[i]);
++                self->attachCtrlVolume(c, nsid);
++            }
++            });
++    });
++}
++
++void NVMeSubsystem::updateVolumes()
++{
++    nvme_mi_ctrl_t ctrl = primaryController->getMiCtrl();
++    auto intf = std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
++    intf->adminListNamespaces(
++        ctrl, [ctrl, intf, self{shared_from_this()}](
++                  nvme_ex_ptr ex, std::vector<uint32_t> ns) mutable {
++            if (ex)
++            {
++                std::cerr << "list namespaces failed: " << ex << "\n";
++                return;
++            }
++
++            std::vector<uint32_t> existing;
++            for (auto& [n, _] : self->volumes)
++            {
++                existing.push_back(n);
++            }
++
++            std::vector<uint32_t> additions;
++            std::vector<uint32_t> deletions;
++
++            // namespace lists are ordered
++            std::set_difference(ns.begin(), ns.end(), existing.begin(),
++                                existing.end(), std::back_inserter(additions));
++
++            std::set_difference(existing.begin(), existing.end(), ns.begin(),
++                                ns.end(), std::back_inserter(deletions));
++
++            for (auto n : deletions)
++            {
++                self->forgetVolume(self->volumes.find(n)->second);
++            }
++
++            for (auto n : additions)
++            {
++                self->addIdentifyNamespace(n);
++            }
++        });
++}
++
+ void NVMeSubsystem::deleteVolume(boost::asio::yield_context yield,
+                                  std::shared_ptr<NVMeVolume> volume)
+ {
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index b223eb5..470d78d 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -123,6 +123,10 @@ class NVMeSubsystem :
+     void createVolumeFinished(std::string prog_id, nvme_ex_ptr ex,
+                               uint32_t new_ns);
+ 
++    void addIdentifyNamespace(uint32_t nsid);
++
++    void updateVolumes();
++
+     // a counter to skip health poll when NVMe subsystem becomes Unavailable
+     unsigned UnavailableCount = 0;
+     static constexpr unsigned UnavailableMaxCount = 60;
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0010-nvme-Add-storage-associations.patch b/recipes-phosphor/sensors/dbus-sensors/0010-nvme-Add-storage-associations.patch
new file mode 100644
index 0000000..95eb0a8
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0010-nvme-Add-storage-associations.patch
@@ -0,0 +1,535 @@
+From 6bfe3ccdf7430b035d65f27b69589145c8a0e14d Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Wed, 3 May 2023 18:42:58 +0800
+Subject: [PATCH 10/16] nvme: Add storage associations
+
+Add "containing" for Volumes in Storage objects, and "attaching" for
+Volumes in Controllers.
+
+The list of attached namespaces is retrieved as part of updateVolumes().
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: I0bf9ecd71fb265f4c8a47734372e7495549576c0
+(cherry picked from commit 8bbdcfb765f83375fdaefb164d58a644ed381d9e)
+---
+ include/NVMeProgress.hpp |   3 +-
+ src/NVMeController.cpp   | 107 +++++++++++++++++--------------------
+ src/NVMeController.hpp   |  20 ++++---
+ src/NVMeMi.cpp           |   8 +++
+ src/NVMeProgress.cpp     |   2 +-
+ src/NVMeSubsys.cpp       | 112 +++++++++++++++++++++++++++++++++++----
+ src/NVMeSubsys.hpp       |  17 +++++-
+ 7 files changed, 189 insertions(+), 80 deletions(-)
+
+diff --git a/include/NVMeProgress.hpp b/include/NVMeProgress.hpp
+index c5bf6ec..f66c0eb 100644
+--- a/include/NVMeProgress.hpp
++++ b/include/NVMeProgress.hpp
+@@ -48,9 +48,10 @@ class NVMeCreateVolumeProgress : public NVMeProgress
+     /* Returns the volume path if successful, or empty otherwise */
+     std::string volumePath() const;
+ 
++    const std::string path;
++
+   private:
+     std::shared_ptr<sdbusplus::asio::connection> conn;
+-    std::string path;
+ 
+     // interfaces are added only once the state is set to success/failure
+     std::shared_ptr<CreateVolumeProgressSuccess> success;
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index 5fbe488..3ee0f40 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -3,6 +3,7 @@
+ #include "AsioHelper.hpp"
+ #include "NVMeError.hpp"
+ #include "NVMePlugin.hpp"
++#include "NVMeSubsys.hpp"
+ 
+ #include <sdbusplus/exception.hpp>
+ #include <sdbusplus/message/native_types.hpp>
+@@ -20,7 +21,6 @@ using sdbusplus::xyz::openbmc_project::NVMe::server::NVMeAdmin;
+ std::shared_ptr<NVMeControllerEnabled>
+     NVMeControllerEnabled::create(NVMeController&& nvmeController)
+ {
+-
+     auto self = std::shared_ptr<NVMeControllerEnabled>(
+         new NVMeControllerEnabled(std::move(nvmeController)));
+     self->init();
+@@ -38,24 +38,7 @@ NVMeControllerEnabled::NVMeControllerEnabled(NVMeController&& nvmeController) :
+ 
+ void NVMeControllerEnabled::init()
+ {
+-    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();
++    createAssociation();
+ 
+     passthruInterface =
+         objServer.add_interface(path, "xyz.openbmc_project.NVMe.Passthru");
+@@ -128,6 +111,49 @@ void NVMeControllerEnabled::start(
+     this->NVMeController::start(std::move(nvmePlugin));
+ }
+ 
++void NVMeController::createAssociation()
++{
++    assocIntf = objServer.add_interface(path, association::interface);
++    assocIntf->register_property("Associations", makeAssociation());
++    assocIntf->initialize();
++}
++
++void NVMeController::updateAssociation()
++{
++    if (assocIntf)
++    {
++        assocIntf->set_property("Associations", makeAssociation());
++    }
++}
++
++std::vector<Association> NVMeController::makeAssociation() const
++{
++    std::vector<Association> associations;
++    std::filesystem::path p(path);
++
++    auto s = subsys.lock();
++    if (!s)
++    {
++        std::cerr << "makeAssociation() after shutdown\n";
++        return associations;
++    }
++
++    associations.emplace_back("storage", "storage_controller", s->path);
++
++    for (const auto& cntrl : secondaryControllers)
++    {
++        associations.emplace_back("secondary", "primary", cntrl);
++    }
++
++    for (uint32_t nsid : s->attachedVolumes(getCntrlId()))
++    {
++        auto p = s->volumePath(nsid);
++        associations.emplace_back("attaching", "attached", p);
++    }
++
++    return associations;
++}
++
+ sdbusplus::message::unix_fd NVMeControllerEnabled::getLogPage(uint8_t lid,
+                                                               uint32_t nsid,
+                                                               uint8_t lsp,
+@@ -301,10 +327,11 @@ NVMeControllerEnabled::~NVMeControllerEnabled()
+ 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) :
++    std::shared_ptr<NVMeMiIntf> nvmeIntf, nvme_mi_ctrl_t ctrl,
++    std::weak_ptr<NVMeSubsystem> subsys) :
+     io(io),
+     objServer(objServer), conn(conn), path(path), nvmeIntf(nvmeIntf),
+-    nvmeCtrl(ctrl)
++    nvmeCtrl(ctrl), subsys(subsys)
+ {}
+ 
+ NVMeController::~NVMeController()
+@@ -331,43 +358,7 @@ void NVMeController::setSecAssoc(
+     {
+         secondaryControllers.push_back(cntrl->path);
+     }
+-
+-    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);
+-    }
+-
+-    if (assocIntf)
+-    {
+-        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);
+-    }
+-
+-    if (assocIntf)
+-    {
+-        assocIntf->set_property("Associations", associations);
+-    }
++    updateAssociation();
+ }
+ 
+ void NVMeControllerEnabled::securitySendMethod(boost::asio::yield_context yield,
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+index 4349ebb..6e83b86 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -2,6 +2,7 @@
+ #pragma once
+ 
+ #include "NVMeIntf.hpp"
++#include "Utils.hpp"
+ 
+ #include <boost/asio.hpp>
+ #include <sdbusplus/asio/connection.hpp>
+@@ -12,6 +13,7 @@
+ #include <utility>
+ 
+ class NVMeControllerPlugin;
++class NVMeSubsystem;
+ 
+ /**
+  * @brief A class to represent the NVMeController has not been enabled (CC.EN =
+@@ -30,7 +32,7 @@ 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);
++                   nvme_mi_ctrl_t ctrl, std::weak_ptr<NVMeSubsystem> subsys);
+ 
+     virtual ~NVMeController();
+ 
+@@ -67,12 +69,11 @@ class NVMeController
+     }
+ 
+     /**
+-     * @brief Register the NVMe subsystem to the controller. The function can be
+-     * called mutiple times to associate multi-subsys to a single controller.
++     * @brief Update association interface.
+      *
+-     * @param subsysPath Path to the subsystem
+-     */
+-    void addSubsystemAssociation(const std::string& subsysPath);
++     * May be called externally when attached volumes change.
++     **/
++    void updateAssociation();
+ 
+   protected:
+     friend class NVMeControllerPlugin;
+@@ -90,12 +91,15 @@ class NVMeController
+     nvme_mi_ctrl_t nvmeCtrl;
+ 
+     std::shared_ptr<sdbusplus::asio::dbus_interface> assocIntf;
+-    // The association to subsystems
+-    std::vector<std::string> subsystems;
++    void createAssociation();
++    std::vector<Association> makeAssociation() const;
+ 
+     // The association to secondary controllers from a primary controller
+     std::vector<std::string> secondaryControllers;
+ 
++    // The parent subsystem
++    std::weak_ptr<NVMeSubsystem> subsys;
++
+     // NVMe Plug-in for vendor defined command/field
+     std::weak_ptr<NVMeControllerPlugin> plugin;
+ };
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index 56dcfbd..67c40f3 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -461,6 +461,7 @@ void NVMeMi::adminIdentify(
+                     rc = nvme_mi_admin_identify(ctrl, &args);
+                 }
+             }
++
+             if (rc < 0)
+             {
+                 std::cerr << "[bus: " << self->bus << ", addr: " << self->addr
+@@ -485,6 +486,13 @@ void NVMeMi::adminIdentify(
+                 });
+                 return;
+             }
++            
++            auto ex = makeLibNVMeError(errno, rc, "adminIdentify");
++            if (ex)
++            {
++                fprintf(stderr, "fail to do nvme identify cns 0x%x: %s\n", cns,
++                        ex->description());
++            }
+ 
+             self->io.post([cb{std::move(cb)}, data{std::move(data)}]() mutable {
+                 std::span<uint8_t> span{data.data(), data.size()};
+diff --git a/src/NVMeProgress.cpp b/src/NVMeProgress.cpp
+index ebc649d..076cc93 100644
+--- a/src/NVMeProgress.cpp
++++ b/src/NVMeProgress.cpp
+@@ -38,7 +38,7 @@ NVMeCreateVolumeProgress::NVMeCreateVolumeProgress(
+     std::shared_ptr<sdbusplus::asio::connection> conn,
+     const std::string& path) :
+     NVMeProgress(conn, path),
+-    conn(conn), path(path)
++    path(path), conn(conn)
+ {}
+ 
+ NVMeCreateVolumeProgress::~NVMeCreateVolumeProgress()
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index d32392a..15cd269 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -7,7 +7,19 @@
+ 
+ #include <filesystem>
+ 
+-void NVMeSubsystem::createStorageAssociation()
++void NVMeSubsystem::createAssociation()
++{
++    assocIntf = objServer.add_interface(path, association::interface);
++    assocIntf->register_property("Associations", makeAssociation());
++    assocIntf->initialize();
++}
++
++void NVMeSubsystem::updateAssociation()
++{
++    assocIntf->set_property("Associations", makeAssociation());
++}
++
++std::vector<Association> NVMeSubsystem::makeAssociation() const
+ {
+     std::vector<Association> associations;
+     std::filesystem::path p(path);
+@@ -16,11 +28,17 @@ void NVMeSubsystem::createStorageAssociation()
+     associations.emplace_back("chassis", "drive", p.parent_path().string());
+     associations.emplace_back("drive", "storage", path);
+ 
+-    assocIntf = objServer.add_interface(path, association::interface);
++    for (auto& [_, prog] : createProgress)
++    {
++        associations.emplace_back("awaiting", "awaited", prog->path);
++    }
+ 
+-    assocIntf->register_property("Associations", associations);
++    for (auto& [_, vol] : volumes)
++    {
++        associations.emplace_back("containing", "contained", vol->path);
++    }
+ 
+-    assocIntf->initialize();
++    return associations;
+ }
+ 
+ // get temporature from a NVMe Basic reading.
+@@ -55,7 +73,7 @@ NVMeSubsystem::NVMeSubsystem(boost::asio::io_context& io,
+                              const SensorData& configData, NVMeIntf intf) :
+     NVMeStorage(objServer, *dynamic_cast<sdbusplus::bus_t*>(conn.get()),
+                 path.c_str()),
+-    io(io), objServer(objServer), conn(conn), path(path), name(name),
++    path(path), io(io), objServer(objServer), conn(conn), name(name),
+     config(configData), nvmeIntf(intf), status(Status::Stop),
+     drive(*dynamic_cast<sdbusplus::bus_t*>(conn.get()), path.c_str())
+ {}
+@@ -90,7 +108,7 @@ void NVMeSubsystem::init()
+ 
+     /* xyz.openbmc_project.Inventory.Item.Storage */
+     // make association for Drive/Storage/Chassis
+-    createStorageAssociation();
++    createAssociation();
+ }
+ 
+ NVMeSubsystem::~NVMeSubsystem()
+@@ -180,7 +198,7 @@ void NVMeSubsystem::markFunctional(bool toggle)
+                 {
+                     auto nvmeController = std::make_shared<NVMeController>(
+                         self->io, self->objServer, self->conn, path.string(),
+-                        nvme, c);
++                        nvme, c, self->weak_from_this());
+ 
+                     // insert the controllers with empty plugin
+                     auto [iter, _] = self->controllers.insert(
+@@ -193,9 +211,6 @@ void NVMeSubsystem::markFunctional(bool toggle)
+                         ctrlPlugin = self->plugin->createControllerPlugin(
+                             *nvmeController, self->config);
+                     }
+-
+-                    // set StorageController Association
+-                    nvmeController->addSubsystemAssociation(self->path);
+                 }
+                 catch (const std::exception& e)
+                 {
+@@ -665,6 +680,8 @@ sdbusplus::message::object_path
+         throw std::logic_error("duplicate progress id");
+     }
+ 
++    updateAssociation();
++
+     // #4
+     return prog_path;
+ }
+@@ -700,6 +717,7 @@ void NVMeSubsystem::createVolumeFinished(std::string prog_id, nvme_ex_ptr ex,
+             NVMeVolume::create(objServer, conn, shared_from_this(), new_ns);
+         volumes.insert({new_ns, vol});
+         prog->createSuccess(vol);
++        updateAssociation();
+     }
+     catch (const std::exception& e)
+     {
+@@ -816,6 +834,78 @@ void NVMeSubsystem::updateVolumes()
+         });
+ }
+ 
++std::vector<uint32_t> NVMeSubsystem::attachedVolumes(uint16_t ctrlId) const
++{
++    std::vector<uint32_t> vols;
++
++    if (!controllers.contains(ctrlId))
++    {
++        std::cerr << "attachedVolumes bad controller " << ctrlId << std::endl;
++        return vols;
++    }
++
++    try
++    {
++        std::ranges::copy(attached.at(ctrlId), std::back_inserter(vols));
++    }
++    catch (std::out_of_range&)
++    {
++        // no volumes attached
++    }
++    return vols;
++}
++
++void NVMeSubsystem::attachCtrlVolume(uint16_t c, uint32_t ns)
++{
++    if (!controllers.contains(c))
++    {
++        std::cerr << "attachCtrlVolume bad controller " << c << std::endl;
++        return;
++    }
++    if (!volumes.contains(ns))
++    {
++        std::cerr << "attachCtrlVolume bad ns " << ns << std::endl;
++        return;
++    }
++    attached[c].insert(ns);
++    std::cout << name << " attached insert " << c << " " << ns << "\n";
++    controllers[c].first->updateAssociation();
++}
++
++void NVMeSubsystem::detachCtrlVolume(uint16_t c, uint32_t ns)
++{
++    if (!controllers.contains(c))
++    {
++        std::cerr << "detachCtrlVolume bad controller " << c << std::endl;
++        return;
++    }
++    if (!volumes.contains(ns))
++    {
++        std::cerr << "detachCtrlVolume bad ns " << ns << std::endl;
++        return;
++    }
++    attached[c].erase(ns);
++    std::cout << name << " attached erase " << c << " " << ns << "\n";
++    controllers[c].first->updateAssociation();
++}
++
++void NVMeSubsystem::detachAllCtrlVolume(uint32_t ns)
++{
++    if (!volumes.contains(ns))
++    {
++        std::cerr << "detachAllCtrlVolume bad ns " << ns << std::endl;
++        return;
++    }
++    // remove from attached controllers list
++    for (auto& [c, attach_vols] : attached)
++    {
++        if (attach_vols.erase(ns) == 1)
++        {
++            controllers[c].first->updateAssociation();
++        }
++    }
++}
++
+ void NVMeSubsystem::deleteVolume(boost::asio::yield_context yield,
+                                  std::shared_ptr<NVMeVolume> volume)
+ {
+@@ -854,4 +944,6 @@ void NVMeSubsystem::deleteVolume(boost::asio::yield_context yield,
+     {
+         throw std::runtime_error("volume disappeared unexpectedly");
+     }
++
++    updateAssociation();
+ }
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index 470d78d..4d54121 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -44,6 +44,13 @@ class NVMeSubsystem :
+     void deleteVolume(boost::asio::yield_context yield,
+                       std::shared_ptr<NVMeVolume> volume);
+ 
++    std::vector<uint32_t> attachedVolumes(uint16_t ctrlId) const;
++    void attachCtrlVolume(uint16_t ctrlId, uint32_t nsid);
++    void detachCtrlVolume(uint16_t ctrlId, uint32_t nsid);
++    void detachAllCtrlVolume(uint32_t nsid);
++
++    const std::string path;
++
+   private:
+     NVMeSubsystem(boost::asio::io_context& io,
+                   sdbusplus::asio::object_server& objServer,
+@@ -56,7 +63,6 @@ class NVMeSubsystem :
+     boost::asio::io_context& io;
+     sdbusplus::asio::object_server& objServer;
+     std::shared_ptr<sdbusplus::asio::connection> conn;
+-    std::string path;
+     std::string name;
+     SensorData config;
+ 
+@@ -93,6 +99,11 @@ class NVMeSubsystem :
+     */
+     std::map<uint32_t, std::shared_ptr<NVMeVolume>> volumes;
+ 
++    /*
++     * volumes attached to controllers
++     */
++    std::map<uint16_t, std::set<uint32_t>> attached;
++
+     /*
+     In-progress or completed create operations
+     */
+@@ -104,7 +115,9 @@ class NVMeSubsystem :
+     std::shared_ptr<NVMeControllerEnabled> primaryController;
+ 
+     std::shared_ptr<sdbusplus::asio::dbus_interface> assocIntf;
+-    void createStorageAssociation();
++    void createAssociation();
++    void updateAssociation();
++    std::vector<Association> makeAssociation() const;
+ 
+     // make the subsystem functional/functional be enabling/disabling the
+     // storage controller, namespaces and thermal sensors.
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0011-nvme-addVolume-and-forgetVolume-helpers.patch b/recipes-phosphor/sensors/dbus-sensors/0011-nvme-addVolume-and-forgetVolume-helpers.patch
new file mode 100644
index 0000000..06e4737
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0011-nvme-addVolume-and-forgetVolume-helpers.patch
@@ -0,0 +1,453 @@
+From ca0cbd57270fb54fc13d0ca561efcb723c7f65e2 Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Wed, 3 May 2023 19:47:09 +0800
+Subject: [PATCH 11/16] nvme: addVolume() and forgetVolume() helpers
+
+A common NVMeNSIdentify struct passes namespaces parameters.
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: Ie3bed896eb91259d8ca525d5660e6543b3ed1202
+(cherry picked from commit 8c1024231144f428991b4fdee3b16330fdd1970d)
+---
+ include/NVMeVolume.hpp |   4 +-
+ src/NVMeIntf.hpp       |  15 ++++-
+ src/NVMeMi.cpp         | 121 ++++++++++++++++++++++-------------------
+ src/NVMeMi.hpp         |  12 ++--
+ src/NVMeSubsys.cpp     |  82 ++++++++++++++++++----------
+ src/NVMeSubsys.hpp     |   9 ++-
+ src/NVMeVolume.cpp     |  24 +++++---
+ 7 files changed, 166 insertions(+), 101 deletions(-)
+
+diff --git a/include/NVMeVolume.hpp b/include/NVMeVolume.hpp
+index 09b04d1..958d66e 100644
+--- a/include/NVMeVolume.hpp
++++ b/include/NVMeVolume.hpp
+@@ -31,7 +31,7 @@ class NVMeVolume :
+     static std::shared_ptr<NVMeVolume>
+         create(sdbusplus::asio::object_server& objServer,
+                std::shared_ptr<sdbusplus::asio::connection> conn,
+-               std::shared_ptr<NVMeSubsystem> subsys, uint32_t nsid);
++               std::shared_ptr<NVMeSubsystem> subsys, const NVMeNSIdentify& ns);
+     ~NVMeVolume() override;
+ 
+     const std::string path;
+@@ -39,7 +39,7 @@ class NVMeVolume :
+   private:
+     NVMeVolume(sdbusplus::asio::object_server& objServer,
+                std::shared_ptr<sdbusplus::asio::connection> conn,
+-               std::shared_ptr<NVMeSubsystem> subsys, uint32_t nsid);
++               std::shared_ptr<NVMeSubsystem> subsys, const NVMeNSIdentify& ns);
+     void init();
+ 
+     void formatLuks(std::vector<uint8_t> password,
+diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
+index 894590f..4e7fdbc 100644
+--- a/src/NVMeIntf.hpp
++++ b/src/NVMeIntf.hpp
+@@ -11,6 +11,7 @@
+ 
+ class NVMeBasicIntf;
+ class NVMeMiIntf;
++struct NVMeNSIdentify;
+ /**
+  * @brief a container class to hold smart ptr to NVMe Basic or NVMe MI
+  * implementation instance
+@@ -209,7 +210,8 @@ class NVMeMiIntf
+         nvme_mi_ctrl_t ctrl, uint64_t size, size_t lba_format,
+         bool metadata_at_end,
+         std::function<void(nvme_ex_ptr ex)>&& submitted_cb,
+-        std::function<void(nvme_ex_ptr ex, uint32_t new_ns)>&& finished_cb) = 0;
++        std::function<void(nvme_ex_ptr ex, NVMeNSIdentify newid)>&&
++            finished_cb) = 0;
+ 
+     virtual void adminDeleteNamespace(
+         nvme_mi_ctrl_t ctrl, uint32_t nsid,
+@@ -262,3 +264,14 @@ class NVMeMiIntf
+                                      const nvme_mi_admin_resp_hdr& admin_resp,
+                                      std::span<uint8_t> resp_data)>&& cb) = 0;
+ };
++
++/* A subset of Namespace Identify details of interest */
++struct NVMeNSIdentify
++{
++    uint32_t namespaceId;
++    uint64_t size;
++    uint64_t capacity;
++    size_t blockSize;
++    size_t lbaFormat;
++    bool metadataAtEnd;
++};
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index 67c40f3..94323cb 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -1213,68 +1213,77 @@ size_t NVMeMi::getBlockSize(nvme_mi_ctrl_t ctrl, size_t lba_format)
+ void NVMeMi::createNamespace(
+     nvme_mi_ctrl_t ctrl, uint64_t size, size_t lba_format, bool metadata_at_end,
+     std::function<void(nvme_ex_ptr ex)>&& submitted_cb,
+-    std::function<void(nvme_ex_ptr ex, uint32_t new_ns)>&& finished_cb)
++    std::function<void(nvme_ex_ptr ex, NVMeNSIdentify newid)>&& finished_cb)
+ {
+     printf("createns %d\n", (int)gettid());
+-    std::error_code post_err =
+-        try_post([self{shared_from_this()}, ctrl, size, lba_format,
+-                  metadata_at_end, submitted_cb{std::move(submitted_cb)},
+-                  finished_cb{std::move(finished_cb)}]() {
+-            size_t block_size;
+-
+-            try
+-            {
+-                block_size = self->getBlockSize(ctrl, lba_format);
+-            }
+-            catch (nvme_ex_ptr e)
+-            {
+-                submitted_cb(e);
+-                return;
+-            }
+-
+-            if (size % block_size != 0)
+-            {
+-                auto msg =
+-                    std::string("Size must be a multiple of the block size ") +
+-                    std::to_string(block_size);
+-                submitted_cb(std::make_shared<NVMeSdBusPlusError>(msg));
+-                return;
+-            }
+-
+-            uint64_t blocks = size / block_size;
+-
+-            // TODO: this will become nvme_ns_mgmt_host_sw_specified in a newer
+-            // libnvme.
+-            struct nvme_id_ns data;
+-            uint32_t new_ns = 0;
+-
+-            uint8_t flbas = 0;
+-            if (metadata_at_end)
+-            {
+-                flbas |= (1 << 4);
+-            }
+-            // low 4 bits at 0:3
+-            flbas |= (lba_format & 0xf);
+-            // high 2 bits at 5:6
+-            flbas |= ((lba_format & 0x30) << 1);
++    std::error_code post_err = try_post(
++        [self{shared_from_this()}, ctrl, size, lba_format, metadata_at_end,
++         submitted_cb{std::move(submitted_cb)},
++         finished_cb{std::move(finished_cb)}]() {
++        size_t block_size;
+ 
+-            memset(&data, 0x0, sizeof(data));
+-            data.nsze = ::htole64(blocks);
+-            data.ncap = ::htole64(blocks);
+-            data.flbas = flbas;
++        try
++        {
++            block_size = self->getBlockSize(ctrl, lba_format);
++        }
++        catch (nvme_ex_ptr e)
++        {
++            submitted_cb(e);
++            return;
++        }
+ 
+-            printf("verified %d\n", (int)gettid());
++        if (size % block_size != 0)
++        {
++            auto msg =
++                std::string("Size must be a multiple of the block size ") +
++                std::to_string(block_size);
++            submitted_cb(std::make_shared<NVMeSdBusPlusError>(msg));
++            return;
++        }
+ 
+-            // submission has been verified.
+-            submitted_cb(nvme_ex_ptr());
+-            printf("after submitted_cb %d\n", (int)gettid());
++        uint64_t blocks = size / block_size;
+ 
+-            int status = nvme_mi_admin_ns_mgmt_create(ctrl, &data, 0, &new_ns);
+-            nvme_ex_ptr e = makeLibNVMeError(errno, status, "createVolume");
++        // TODO: this will become nvme_ns_mgmt_host_sw_specified in a newer
++        // libnvme.
++        struct nvme_id_ns data;
++        uint32_t new_nsid = 0;
+ 
+-            self->io.post([finished_cb{std::move(finished_cb)}, e, new_ns]() {
+-                finished_cb(e, new_ns);
+-            });
++        uint8_t flbas = 0;
++        if (metadata_at_end)
++        {
++            flbas |= (1 << 4);
++        }
++        // low 4 bits at 0:3
++        flbas |= (lba_format & 0xf);
++        // high 2 bits at 5:6
++        flbas |= ((lba_format & 0x30) << 1);
++
++        memset(&data, 0x0, sizeof(data));
++        data.nsze = ::htole64(blocks);
++        data.ncap = ::htole64(blocks);
++        data.flbas = flbas;
++
++        printf("verified %d\n", (int)gettid());
++
++        // submission has been verified.
++        submitted_cb(nvme_ex_ptr());
++        printf("after submitted_cb %d\n", (int)gettid());
++
++        int status = nvme_mi_admin_ns_mgmt_create(ctrl, &data, 0, &new_nsid);
++        nvme_ex_ptr e = makeLibNVMeError(errno, status, "createVolume");
++
++        NVMeNSIdentify newns = {
++            .namespaceId = new_nsid,
++            .size = size,
++            .capacity = size,
++            .blockSize = block_size,
++            .lbaFormat = lba_format,
++            .metadataAtEnd = metadata_at_end,
++        };
++
++        self->io.post([finished_cb{std::move(finished_cb)}, e, newns]() {
++            finished_cb(e, newns);
++        });
+ 
+ #if 0
+         // TODO testing purposes
+@@ -1290,7 +1299,7 @@ void NVMeMi::createNamespace(
+         auto err = std::make_error_code(static_cast<std::errc>(0));
+         cb(err, 0, new_ns);
+ #endif
+-        });
++    });
+ 
+     printf("submitted cb %d\n", (int)gettid());
+ 
+diff --git a/src/NVMeMi.hpp b/src/NVMeMi.hpp
+index 13d5bec..38aae4e 100644
+--- a/src/NVMeMi.hpp
++++ b/src/NVMeMi.hpp
+@@ -65,11 +65,13 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+         uint32_t cdw13, uint32_t cdw14, uint32_t cdw15,
+         std::function<void(const std::error_code&, int nvme_status,
+                            uint32_t comption_dw0)>&& cb);
+-    void createNamespace(nvme_mi_ctrl_t ctrl, uint64_t size, size_t lba_format,
+-                         bool metadata_at_end,
+-                         std::function<void(nvme_ex_ptr ex)>&& submitted_cb,
+-                         std::function<void(nvme_ex_ptr ex, uint32_t new_ns)>&&
+-                             finished_cb) override;
++
++    void createNamespace(
++        nvme_mi_ctrl_t ctrl, uint64_t size, size_t lba_format,
++        bool metadata_at_end,
++        std::function<void(nvme_ex_ptr ex)>&& submitted_cb,
++        std::function<void(nvme_ex_ptr ex, NVMeNSIdentify newid)>&& finished_cb)
++        override;
+ 
+     void adminDeleteNamespace(
+         nvme_mi_ctrl_t ctrl, uint32_t nsid,
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index 15cd269..e62abef 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -647,7 +647,7 @@ sdbusplus::message::object_path
+             },
+ 
+             // finished_cb
+-            [weak, prog_id](nvme_ex_ptr ex, uint32_t new_ns) mutable {
++            [weak, prog_id](nvme_ex_ptr ex, NVMeNSIdentify newns) mutable {
+             // #5. This will only be called once #4 completes.
+             // It will not be called if the submit failed.
+             auto self = weak.lock();
+@@ -658,7 +658,7 @@ sdbusplus::message::object_path
+                 return;
+             }
+             // The NS create has completed (either successfully or not)
+-            self->createVolumeFinished(prog_id, ex, new_ns);
++            self->createVolumeFinished(prog_id, ex, newns);
+         });
+         },
+         yield);
+@@ -687,7 +687,7 @@ sdbusplus::message::object_path
+ }
+ 
+ void NVMeSubsystem::createVolumeFinished(std::string prog_id, nvme_ex_ptr ex,
+-                                         uint32_t new_ns)
++                                         NVMeNSIdentify ns)
+ {
+     try
+     {
+@@ -704,18 +704,17 @@ void NVMeSubsystem::createVolumeFinished(std::string prog_id, nvme_ex_ptr ex,
+             return;
+         }
+ 
+-        if (volumes.contains(new_ns))
++        std::shared_ptr<NVMeVolume> vol;
++        try
+         {
+-            std::string err_msg = std::string("Internal error, NSID exists " +
+-                                              std::to_string(new_ns));
+-            std::cerr << err_msg << "\n";
+-            prog->createFailure(makeLibNVMeError(err_msg));
++            vol = addVolume(ns);
++        }
++        catch (nvme_ex_ptr e)
++        {
++            prog->createFailure(e);
+             return;
+         }
+ 
+-        auto vol =
+-            NVMeVolume::create(objServer, conn, shared_from_this(), new_ns);
+-        volumes.insert({new_ns, vol});
+         prog->createSuccess(vol);
+         updateAssociation();
+     }
+@@ -906,6 +905,49 @@ void NVMeSubsystem::detachAllCtrlVolume(uint32_t ns)
+     }
+ }
+ 
++// Will throw a nvme_ex_ptr if the NS already exists */
++std::shared_ptr<NVMeVolume> NVMeSubsystem::addVolume(const NVMeNSIdentify& ns)
++{
++    if (volumes.contains(ns.namespaceId))
++    {
++        std::string err_msg = std::string("Internal error, NSID exists " +
++                                          std::to_string(ns.namespaceId));
++        std::cerr << err_msg << "\n";
++        throw makeLibNVMeError(err_msg);
++    }
++
++    auto vol = NVMeVolume::create(objServer, conn, shared_from_this(), ns);
++    volumes.insert({ns.namespaceId, vol});
++
++    updateAssociation();
++    return vol;
++}
++
++void NVMeSubsystem::forgetVolume(std::shared_ptr<NVMeVolume> volume)
++{
++    // remove any progress references
++    for (const auto& [prog_id, prog] : createProgress)
++    {
++        std::string s = prog->volumePath();
++        if (prog->volumePath() == volume->path)
++        {
++            createProgress.erase(prog_id);
++            break;
++        }
++    }
++
++    // remove from attached controllers list
++    detachAllCtrlVolume(volume->namespaceId());
++
++    if (volumes.erase(volume->namespaceId()) != 1)
++    {
++        std::cerr << "volume " << volume->namespaceId()
++                  << " disappeared unexpectedly\n";
++    }
++
++    updateAssociation();
++}
++
+ void NVMeSubsystem::deleteVolume(boost::asio::yield_context yield,
+                                  std::shared_ptr<NVMeVolume> volume)
+ {
+@@ -929,21 +971,5 @@ void NVMeSubsystem::deleteVolume(boost::asio::yield_context yield,
+     // exception must be thrown outside of the async block
+     checkLibNVMeError(err, nvme_status, "Delete");
+ 
+-    // remove any progress references
+-    for (const auto& [prog_id, prog] : createProgress)
+-    {
+-        std::string s = prog->volumePath();
+-        if (prog->volumePath() == volume->path)
+-        {
+-            createProgress.erase(prog_id);
+-            break;
+-        }
+-    }
+-
+-    if (volumes.erase(volume->namespaceId()) != 1)
+-    {
+-        throw std::runtime_error("volume disappeared unexpectedly");
+-    }
+-
+-    updateAssociation();
++    forgetVolume(volume);
+ }
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index 4d54121..9f27e89 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -134,12 +134,19 @@ class NVMeSubsystem :
+ 
+     // callback when drive completes. not called in dbus method context.
+     void createVolumeFinished(std::string prog_id, nvme_ex_ptr ex,
+-                              uint32_t new_ns);
++                              NVMeNSIdentify ns);
+ 
+     void addIdentifyNamespace(uint32_t nsid);
+ 
+     void updateVolumes();
+ 
++    // removes state associated with the volume. Does not manipulate the drive.
++    void forgetVolume(std::shared_ptr<NVMeVolume> volume);
++
++    // adds state associated with the volume. Does not create a volume.
++    // may throw if the volume exists.
++    std::shared_ptr<NVMeVolume> addVolume(const NVMeNSIdentify& ns);
++
+     // a counter to skip health poll when NVMe subsystem becomes Unavailable
+     unsigned UnavailableCount = 0;
+     static constexpr unsigned UnavailableMaxCount = 60;
+diff --git a/src/NVMeVolume.cpp b/src/NVMeVolume.cpp
+index 2e262a5..54c23b8 100644
+--- a/src/NVMeVolume.cpp
++++ b/src/NVMeVolume.cpp
+@@ -2,15 +2,22 @@
+ 
+ NVMeVolume::NVMeVolume(sdbusplus::asio::object_server& objServer,
+                        std::shared_ptr<sdbusplus::asio::connection> conn,
+-                       std::shared_ptr<NVMeSubsystem> subsys, uint32_t nsid) :
++                       std::shared_ptr<NVMeSubsystem> subsys,
++                       const NVMeNSIdentify& ns) :
+     VolumeBase(dynamic_cast<sdbusplus::bus_t&>(*conn),
+-               subsys->volumePath(nsid).c_str()),
++               subsys->volumePath(ns.namespaceId).c_str()),
+     NvmeVolumeBase(dynamic_cast<sdbusplus::bus_t&>(*conn),
+-                   subsys->volumePath(nsid).c_str()),
+-    path(subsys->volumePath(nsid)), objServer(objServer), subsys(subsys)
++                   subsys->volumePath(ns.namespaceId).c_str()),
++    path(subsys->volumePath(ns.namespaceId)), objServer(objServer),
++    subsys(subsys)
+ {
+-    namespaceId(nsid, false);
+-    // see init()
++    namespaceId(ns.namespaceId, false);
++    size(ns.size, false);
++    blockSize(ns.blockSize, false);
++    lbaFormat(ns.lbaFormat, false);
++    metadataAtEnd(ns.metadataAtEnd, false);
++
++    // see init() for other initialisation
+ }
+ 
+ void NVMeVolume::init()
+@@ -40,10 +47,11 @@ void NVMeVolume::init()
+ std::shared_ptr<NVMeVolume>
+     NVMeVolume::create(sdbusplus::asio::object_server& objServer,
+                        std::shared_ptr<sdbusplus::asio::connection> conn,
+-                       std::shared_ptr<NVMeSubsystem> subsys, uint32_t nsid)
++                       std::shared_ptr<NVMeSubsystem> subsys,
++                       const NVMeNSIdentify& ns)
+ {
+     auto self = std::shared_ptr<NVMeVolume>(
+-        new NVMeVolume(objServer, conn, subsys, nsid));
++        new NVMeVolume(objServer, conn, subsys, ns));
+     self->init();
+     return self;
+ }
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0012-nvme-Make-adminIdentify-return-a-nvme_ex_ptr.patch b/recipes-phosphor/sensors/dbus-sensors/0012-nvme-Make-adminIdentify-return-a-nvme_ex_ptr.patch
new file mode 100644
index 0000000..53a6ea3
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0012-nvme-Make-adminIdentify-return-a-nvme_ex_ptr.patch
@@ -0,0 +1,184 @@
+From 8fb093c2ae4bb7180c16c87768d3f40ce9816472 Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Thu, 4 May 2023 14:27:30 +0800
+Subject: [PATCH 12/16] nvme: Make adminIdentify return a nvme_ex_ptr
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: Icae40ac743c339ec0ac906809c0bbd0a90a5b6e2
+(cherry picked from commit 924d7bacfbe39de3f3eba9fc701ea1179fa3b731)
+---
+ src/NVMeController.cpp | 14 +++++++-------
+ src/NVMeIntf.hpp       |  3 +--
+ src/NVMeMi.cpp         | 27 ++++++++++-----------------
+ src/NVMeMi.hpp         |  8 ++++----
+ src/NVMeSubsys.cpp     |  6 +++---
+ 5 files changed, 25 insertions(+), 33 deletions(-)
+
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index 3ee0f40..dcde2ff 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -251,15 +251,15 @@ sdbusplus::message::unix_fd
+         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) {
++    nvmeIntf->adminIdentify(nvmeCtrl, static_cast<nvme_identify_cns>(cns), nsid,
++                            cntid,
++                            [self{shared_from_this()},
++                             pipe](nvme_ex_ptr ex, std::span<uint8_t> data) {
+         ::close(pipe[0]);
+         int fd = pipe[1];
+-        if (ec)
++        if (ex)
+         {
+-            std::cerr << "fail to Identify: " << ec.message() << std::endl;
++            std::cerr << "fail to Identify: " << ex << std::endl;
+             close(fd);
+             return;
+         }
+@@ -269,7 +269,7 @@ sdbusplus::message::unix_fd
+                       << std::endl;
+         };
+         close(fd);
+-        });
++    });
+     return sdbusplus::message::unix_fd{pipe[0]};
+ }
+ 
+diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
+index 4e7fdbc..82e4e24 100644
+--- a/src/NVMeIntf.hpp
++++ b/src/NVMeIntf.hpp
+@@ -176,8 +176,7 @@ class NVMeMiIntf
+     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;
++        std::function<void(nvme_ex_ptr, 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,
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index 94323cb..11d97f7 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -404,13 +404,13 @@ void NVMeMi::miScanCtrl(std::function<void(const std::error_code&,
+ 
+ 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)
++    std::function<void(nvme_ex_ptr, 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), {});
++            cb(makeLibNVMeError("nvme endpoint is invalid"), {});
+         });
+         return;
+     }
+@@ -468,10 +468,6 @@ void NVMeMi::adminIdentify(
+                           << ", eid: " << static_cast<int>(self->eid) << "]"
+                           << "fail to do nvme identify: "
+                           << std::strerror(errno) << std::endl;
+-                self->io.post([cb{std::move(cb)}, last_errno{errno}]() {
+-                    cb(std::make_error_code(static_cast<std::errc>(last_errno)), {});
+-                });
+-                return;
+             }
+             else if (rc > 0)
+             {
+@@ -481,22 +477,19 @@ void NVMeMi::adminIdentify(
+                           << ", eid: " << static_cast<int>(self->eid) << "]"
+                           << "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;
+             }
+             
+             auto ex = makeLibNVMeError(errno, rc, "adminIdentify");
+             if (ex)
+             {
+-                fprintf(stderr, "fail to do nvme identify cns 0x%x: %s\n", cns,
+-                        ex->description());
++                std::cerr << "fail to do nvme identify: " << ex->description()
++                          << std::endl;
+             }
+ 
+-            self->io.post([cb{std::move(cb)}, data{std::move(data)}]() mutable {
++            self->io.post(
++                [cb{std::move(cb)}, ex, data{std::move(data)}]() mutable {
+                 std::span<uint8_t> span{data.data(), data.size()};
+-                cb({}, span);
++                cb(ex, span);
+             });
+         });
+     }
+@@ -505,9 +498,9 @@ void NVMeMi::adminIdentify(
+         std::cerr << "[bus: " << bus << ", addr: " << addr
+                   << ", eid: " << static_cast<int>(eid) << "]" << e.what()
+                   << std::endl;
+-        io.post([cb{std::move(cb)}]() {
+-            cb(std::make_error_code(std::errc::no_such_device), {});
+-        });
++        auto msg = std::string("Runtime error: ") + e.what();
++        std::cerr << msg << std::endl;
++        io.post([cb{std::move(cb)}, msg]() { cb(makeLibNVMeError(msg), {}); });
+         return;
+     }
+ }
+diff --git a/src/NVMeMi.hpp b/src/NVMeMi.hpp
+index 38aae4e..0af9dfe 100644
+--- a/src/NVMeMi.hpp
++++ b/src/NVMeMi.hpp
+@@ -28,10 +28,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;
++    void adminIdentify(
++        nvme_mi_ctrl_t ctrl, nvme_identify_cns cns, uint32_t nsid,
++        uint16_t cntid,
++        std::function<void(nvme_ex_ptr, std::span<uint8_t>)>&& cb) override;
+     void adminGetLogPage(nvme_mi_ctrl_t ctrl, nvme_cmd_get_log_lid lid,
+                          uint32_t nsid, uint8_t lsp, uint16_t lsi,
+                          std::function<void(const std::error_code&,
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index e62abef..4076212 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -233,9 +233,9 @@ void NVMeSubsystem::markFunctional(bool toggle)
+             nvme->adminIdentify(
+                 ctrl, nvme_identify_cns::NVME_IDENTIFY_CNS_SECONDARY_CTRL_LIST,
+                 0, 0,
+-                [self{self->shared_from_this()}](const std::error_code& ec,
++                [self{self->shared_from_this()}](nvme_ex_ptr ex,
+                                                  std::span<uint8_t> data) {
+-                if (ec || data.size() < sizeof(nvme_secondary_ctrl_list))
++                if (ex || data.size() < sizeof(nvme_secondary_ctrl_list))
+                 {
+                     // std::cerr << "fail to identify secondary controller list"
+                     //           << std::endl;
+@@ -246,7 +246,7 @@ void NVMeSubsystem::markFunctional(bool toggle)
+                     // fallback. This may need refiniing.
+                     std::cerr << "Failed to identify secondary controller "
+                                  "list. error "
+-                              << ec << " data size " << data.size()
++                              << ex << " data size " << data.size()
+                               << " expected size "
+                               << sizeof(nvme_secondary_ctrl_list)
+                               << ". Fallback, using arbitrary controller as "
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0013-nvmesensor-AttachVolume-and-DetachVolume.patch b/recipes-phosphor/sensors/dbus-sensors/0013-nvmesensor-AttachVolume-and-DetachVolume.patch
new file mode 100644
index 0000000..e0d09cf
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0013-nvmesensor-AttachVolume-and-DetachVolume.patch
@@ -0,0 +1,306 @@
+From 47c2160ebefb936790266b6a5097e6a48b9ea706 Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Thu, 20 Apr 2023 16:14:24 +0800
+Subject: [PATCH 13/16] nvmesensor: AttachVolume and DetachVolume
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: I6652f6a4754384e3c4bfd87d14ef90953e681534
+(cherry picked from commit 1cbaf4472f0a70ac6d62545cd199ccbdf221ba11)
+---
+ src/NVMeController.cpp | 106 +++++++++++++++++++++++++++++++++++++++++
+ src/NVMeController.hpp |   7 +++
+ src/NVMeIntf.hpp       |   4 ++
+ src/NVMeMi.cpp         |  41 ++++++++++++++++
+ src/NVMeMi.hpp         |   5 ++
+ src/NVMeSubsys.cpp     |  30 ++++++++++++
+ src/NVMeSubsys.hpp     |   2 +
+ 7 files changed, 195 insertions(+)
+
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index dcde2ff..3539a9d 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -96,6 +96,24 @@ void NVMeControllerEnabled::init()
+     // async methods
+     ctrlInterface = objServer.add_interface(
+         path, "xyz.openbmc_project.Inventory.Item.StorageController");
++    ctrlInterface->register_method(
++        "AttachVolume",
++        [weak{weak_from_this()}](boost::asio::yield_context yield,
++                                 sdbusplus::message::object_path volPath) {
++        if (auto self = weak.lock())
++        {
++            return self->attachVolume(yield, volPath);
++        }
++        });
++    ctrlInterface->register_method(
++        "DetachVolume",
++        [weak{weak_from_this()}](boost::asio::yield_context yield,
++                                 sdbusplus::message::object_path volPath) {
++        if (auto self = weak.lock())
++        {
++            return self->detachVolume(yield, volPath);
++        }
++        });
+ 
+     ctrlInterface->initialize();
+ 
+@@ -459,3 +477,91 @@ std::tuple<uint32_t, uint32_t, uint32_t>
+     }
+     return {mi_status, admin_status, completion_dw0};
+ }
++
++void NVMeControllerEnabled::attachVolume(
++    boost::asio::yield_context yield,
++    const sdbusplus::message::object_path& volumePath)
++{
++    uint32_t nsid;
++    if (auto s = subsys.lock())
++    {
++        auto vol = s->getVolume(volumePath);
++        if (!vol)
++        {
++            throw sdbusplus::exception::SdBusError(ENOENT, "attachVolume");
++        }
++        nsid = vol->namespaceId();
++    }
++    else
++    {
++        return;
++    }
++
++    using callback_t = void(std::tuple<std::error_code, int>);
++    uint16_t ctrlid = getCntrlId();
++    auto [err, nvme_status] =
++        boost::asio::async_initiate<boost::asio::yield_context, callback_t>(
++            [intf{nvmeIntf}, ctrl{nvmeCtrl}, ctrlid, nsid](auto&& handler) {
++        auto h = asio_helper::CopyableCallback(std::move(handler));
++
++        intf->adminAttachDetachNamespace(
++            ctrl, ctrlid, nsid, true,
++            [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, "attachVolume");
++
++    if (auto s = subsys.lock())
++    {
++        s->attachCtrlVolume(getCntrlId(), nsid);
++    }
++    updateAssociation();
++}
++
++void NVMeControllerEnabled::detachVolume(
++    boost::asio::yield_context yield,
++    const sdbusplus::message::object_path& volumePath)
++{
++    uint32_t nsid;
++    if (auto s = subsys.lock())
++    {
++        auto vol = s->getVolume(volumePath);
++        if (!vol)
++        {
++            throw sdbusplus::exception::SdBusError(ENOENT, "detachVolume");
++        }
++        nsid = vol->namespaceId();
++    }
++    else
++    {
++        return;
++    }
++
++    using callback_t = void(std::tuple<std::error_code, int>);
++    uint16_t ctrlid = getCntrlId();
++    auto [err, nvme_status] =
++        boost::asio::async_initiate<boost::asio::yield_context, callback_t>(
++            [intf{nvmeIntf}, ctrl{nvmeCtrl}, ctrlid, nsid](auto&& handler) {
++        auto h = asio_helper::CopyableCallback(std::move(handler));
++
++        intf->adminAttachDetachNamespace(
++            ctrl, ctrlid, nsid, false,
++            [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, "detachVolume");
++
++    if (auto s = subsys.lock())
++    {
++        s->detachCtrlVolume(getCntrlId(), nsid);
++    }
++    updateAssociation();
++}
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+index 6e83b86..f056c12 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -53,6 +53,7 @@ class NVMeController
+      *
+      * @return cntrl_id
+      */
++    // TODO: replace this with something from libnvme?
+     uint16_t getCntrlId() const
+     {
+         return *reinterpret_cast<uint16_t*>(
+@@ -192,4 +193,10 @@ class NVMeControllerEnabled :
+                               uint32_t cdw1, uint32_t cdw2, uint32_t cdw3,
+                               uint32_t cdw10, uint32_t cdw11, uint32_t cdw12,
+                               uint32_t cdw13, uint32_t cdw14, uint32_t cdw15);
++
++    void attachVolume(boost::asio::yield_context yield,
++                      const sdbusplus::message::object_path& volPath);
++
++    void detachVolume(boost::asio::yield_context yield,
++                      const sdbusplus::message::object_path& volPath);
+ };
+diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
+index 82e4e24..ae22781 100644
+--- a/src/NVMeIntf.hpp
++++ b/src/NVMeIntf.hpp
+@@ -227,6 +227,10 @@ class NVMeMiIntf
+         nvme_mi_ctrl_t ctrl,
+         std::function<void(nvme_ex_ptr ex, std::vector<uint32_t> ns)>&& cb) = 0;
+ 
++    virtual void adminAttachDetachNamespace(
++        nvme_mi_ctrl_t ctrl, uint16_t ctrlid, uint32_t nsid, bool attach,
++        std::function<void(const std::error_code&, int nvme_status)>&& 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 11d97f7..9d322e9 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -1379,3 +1379,44 @@ void NVMeMi::adminListNamespaces(
+         io.post([cb{std::move(cb)}, ex]() { cb(ex, std::vector<uint32_t>()); });
+     }
+ }
++
++// Attaches or detaches a namespace from a controller
++void NVMeMi::adminAttachDetachNamespace(
++    nvme_mi_ctrl_t ctrl, uint16_t ctrlid, uint32_t nsid, bool attach,
++    std::function<void(const std::error_code&, int nvme_status)>&& cb)
++{
++    std::error_code post_err = try_post([self{shared_from_this()}, ctrl, nsid,
++                                         attach, ctrlid, cb{std::move(cb)}]() {
++        struct nvme_ctrl_list ctrl_list;
++        struct nvme_ns_attach_args args;
++        memset(&args, 0x0, sizeof(args));
++
++        // TODO: add this to a newer libnvme
++        // uint16_t ctrl_id = nvme_mi_ctrl_id(ctrl);
++        uint16_t ctrl_id = ctrlid;
++        nvme_init_ctrl_list(&ctrl_list, 1, &ctrl_id);
++        args.ctrlist = &ctrl_list;
++        args.nsid = nsid;
++        if (attach)
++        {
++            args.sel = NVME_NS_ATTACH_SEL_CTRL_ATTACH;
++        }
++        else
++        {
++            args.sel = NVME_NS_ATTACH_SEL_CTRL_DEATTACH;
++        }
++        args.args_size = sizeof(args);
++
++        int status = nvme_mi_admin_ns_attach(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 << "adminAttachDetachNamespace 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 0af9dfe..4d2b14b 100644
+--- a/src/NVMeMi.hpp
++++ b/src/NVMeMi.hpp
+@@ -83,6 +83,11 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+         std::function<void(nvme_ex_ptr ex, std::vector<uint32_t> ns)>&& cb)
+         override;
+ 
++    void adminAttachDetachNamespace(
++        nvme_mi_ctrl_t ctrl, uint16_t ctrlid, uint32_t nsid, bool attach,
++        std::function<void(const std::error_code&, int nvme_status)>&& cb)
++        override;
++
+   private:
+     // the transfer size for nvme mi messages.
+     // define in github.com/linux-nvme/libnvme/blob/master/src/nvme/mi.c
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index 4076212..08909de 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -5,6 +5,7 @@
+ #include "NVMeUtil.hpp"
+ #include "Thresholds.hpp"
+ 
++#include <charconv>
+ #include <filesystem>
+ 
+ void NVMeSubsystem::createAssociation()
+@@ -833,6 +834,35 @@ void NVMeSubsystem::updateVolumes()
+         });
+ }
+ 
++std::shared_ptr<NVMeVolume> NVMeSubsystem::getVolume(
++    const sdbusplus::message::object_path& volPath) const
++{
++    if (volPath.parent_path() != path + "/volumes")
++    {
++        std::cerr << "getVolume path '" << volPath.str
++                  << "' doesn't match parent " << path << "\n";
++        return nullptr;
++    }
++
++    std::string id = volPath.filename();
++    uint32_t nsid;
++    auto e = std::from_chars(id.data(), id.data() + id.size(), nsid);
++    if (e.ptr != id.data() + id.size() || e.ec != std::errc())
++    {
++        std::cerr << "getVolume path '" << volPath.str << "' bad nsid\n";
++        return nullptr;
++    }
++
++    auto v = volumes.find(nsid);
++    if (v == volumes.end())
++    {
++        std::cerr << "getVolume nsid " << nsid << " not found\n";
++        return nullptr;
++    }
++
++    return v->second;
++}
++
+ std::vector<uint32_t> NVMeSubsystem::attachedVolumes(uint16_t ctrlId) const
+ {
+     std::vector<uint32_t> vols;
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index 9f27e89..b02d45f 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -48,6 +48,8 @@ class NVMeSubsystem :
+     void attachCtrlVolume(uint16_t ctrlId, uint32_t nsid);
+     void detachCtrlVolume(uint16_t ctrlId, uint32_t nsid);
+     void detachAllCtrlVolume(uint32_t nsid);
++    std::shared_ptr<NVMeVolume>
++        getVolume(const sdbusplus::message::object_path& volPath) const;
+ 
+     const std::string path;
+ 
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0014-nvme-Add-DriveErase-interface.patch b/recipes-phosphor/sensors/dbus-sensors/0014-nvme-Add-DriveErase-interface.patch
new file mode 100644
index 0000000..fb08c17
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0014-nvme-Add-DriveErase-interface.patch
@@ -0,0 +1,526 @@
+From b172298bd7646183c596ed891beebcc968dc49d6 Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Mon, 8 May 2023 17:49:55 +0800
+Subject: [PATCH 14/16] nvme: Add DriveErase interface
+
+This performs a NVMe Sanitize operation, with a polling timer
+to monitor for completion.
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: I4b1d0121a43ca58a507f0998cbf2ffb219ff115c
+(cherry picked from commit 30ccb30160ae862391099947ff46e7a411d64136)
+---
+ include/NVMeProgress.hpp |   4 +-
+ src/NVMeDrive.cpp        | 181 +++++++++++++++++++++++++++++++++++++++
+ src/NVMeDrive.hpp        |  73 ++++++++++++++--
+ src/NVMeIntf.hpp         |   5 ++
+ src/NVMeMi.cpp           |  28 ++++++
+ src/NVMeMi.hpp           |   4 +
+ src/NVMeSubsys.cpp       |  55 ++++++++++--
+ src/NVMeSubsys.hpp       |  11 ++-
+ src/meson.build          |   1 +
+ 9 files changed, 345 insertions(+), 17 deletions(-)
+ create mode 100644 src/NVMeDrive.cpp
+
+diff --git a/include/NVMeProgress.hpp b/include/NVMeProgress.hpp
+index f66c0eb..b472659 100644
+--- a/include/NVMeProgress.hpp
++++ b/include/NVMeProgress.hpp
+@@ -1,6 +1,8 @@
+ #pragma once
+ 
++#include "NVMeDrive.hpp"
+ #include "NVMeError.hpp"
++#include "NVMeSubsys.hpp"
+ #include "NVMeVolume.hpp"
+ 
+ #include <boost/asio.hpp>
+@@ -22,7 +24,7 @@ using CreateVolumeProgressFailure =
+     sdbusplus::xyz::openbmc_project::Nvme::server::CreateVolumeProgressFailure;
+ 
+ class NVMeProgress :
+-    protected sdbusplus::xyz::openbmc_project::Common::server::Progress
++    public sdbusplus::xyz::openbmc_project::Common::server::Progress
+ {
+   public:
+     NVMeProgress(std::shared_ptr<sdbusplus::asio::connection> conn,
+diff --git a/src/NVMeDrive.cpp b/src/NVMeDrive.cpp
+new file mode 100644
+index 0000000..38ea8a1
+--- /dev/null
++++ b/src/NVMeDrive.cpp
+@@ -0,0 +1,181 @@
++#include "NVMeDrive.hpp"
++
++#include "NVMeError.hpp"
++#include "NVMeSubsys.hpp"
++
++#include <iostream>
++
++NVMeDrive::NVMeDrive(boost::asio::io_context& io,
++                     std::shared_ptr<sdbusplus::asio::connection> conn,
++                     const std::string& path,
++                     std::weak_ptr<NVMeSubsystem> subsys) :
++    DriveBase(dynamic_cast<sdbusplus::bus_t&>(*conn), path.c_str()),
++    DriveErase(dynamic_cast<sdbusplus::bus_t&>(*conn), path.c_str()),
++    path(path), sanitizeTimer(io), io(io), subsys(subsys)
++{
++    DriveBase::emit_added();
++    DriveErase::emit_added();
++}
++
++NVMeDrive::~NVMeDrive()
++{
++    sanitizeTimer.cancel();
++    DriveErase::emit_removed();
++    DriveBase::emit_removed();
++}
++
++// DriveErase.Erase method handler
++void NVMeDrive::erase(EraseAction action)
++{
++    std::shared_ptr<NVMeSubsystem> s = subsys.lock();
++    if (!s)
++    {
++        throw std::runtime_error("Sanitize called while shutting down");
++    }
++
++    NVMeSanitizeParams params(action);
++
++    if (eraseInProgress())
++    {
++        // Already running
++        if (params == sanitizeParams)
++        {
++            return;
++        }
++        else
++        {
++            throw *makeLibNVMeError(
++                "sanitize already in progress with different parameters",
++                std::make_shared<CommonErr::Unavailable>());
++        }
++    }
++    else
++    {
++        sanitizeParams = params;
++    }
++
++    // Clear properties
++    erasePercentage(0.0);
++    errorName("");
++    errorDescription("");
++    // eraseInProgress() should always be set last.
++    eraseInProgress(true);
++
++    s->startSanitize(params, [self{shared_from_this()}](nvme_ex_ptr ex) {
++        if (ex)
++        {
++            // Update properties with the submission failure
++            self->erasePercentage(0.0);
++            self->errorName(ex->name());
++            self->errorDescription(ex->description());
++            self->eraseInProgress(false);
++        }
++        else
++        {
++            // start the timer
++            self->sanitizePoll();
++        }
++    });
++}
++
++void NVMeDrive::sanitizePoll()
++{
++    if (sanitizePollPending)
++    {
++        return;
++    }
++    sanitizePollPending = true;
++    sanitizeTimer.expires_after(std::chrono::seconds(sanitizePollIntervalSecs));
++    sanitizeTimer.async_wait(
++        [weak{weak_from_this()}](boost::system::error_code ec) {
++        if (ec == boost::asio::error::operation_aborted)
++        {
++            return;
++        }
++
++        auto self = weak.lock();
++        if (!self)
++        {
++            return;
++        }
++        std::shared_ptr<NVMeSubsystem> s = self->subsys.lock();
++        if (!s)
++        {
++            return;
++        }
++
++        self->sanitizePollPending = false;
++
++        if (self->eraseInProgress())
++        {
++            s->sanitizeStatus(
++                [self](nvme_ex_ptr ex, bool inProgress, bool failed,
++                       bool completed, uint16_t sstat, uint16_t sprog,
++                       uint32_t scdw10) {
++                (void)sstat;
++                (void)sprog;
++                (void)scdw10;
++                if (ex)
++                {
++                    std::cerr << "Error returned reading sanitize log: " << ex
++                              << std::endl;
++                }
++                else
++                {
++                    if (completed)
++                    {
++                        self->erasePercentage(0.0);
++                        self->eraseInProgress(false);
++                    }
++                    else if (failed)
++                    {
++                        self->erasePercentage(0.0);
++                        self->eraseInProgress(false);
++                        self->errorName(
++                            CommonErr::DeviceOperationFailed::errName);
++                        self->errorDescription("Sanitize operation failed");
++                    }
++                    else if (inProgress)
++                    {
++                        self->erasePercentage(100.0 * sprog / 0x10000);
++                    }
++                }
++
++                if (self->eraseInProgress())
++                {
++                    self->sanitizePoll();
++                }
++            });
++        }
++    });
++}
++
++NVMeSanitizeParams::NVMeSanitizeParams(EraseAction sanact) :
++    sanact(sanact), passes(1), pattern(0x0), patternInvert(0)
++{}
++
++enum nvme_sanitize_sanact NVMeSanitizeParams::nvmeAction() const
++{
++    switch (sanact)
++    {
++        case EraseAction::BlockErase:
++            return NVME_SANITIZE_SANACT_START_BLOCK_ERASE;
++        case EraseAction::CryptoErase:
++            return NVME_SANITIZE_SANACT_START_CRYPTO_ERASE;
++        case EraseAction::Overwrite:
++            return NVME_SANITIZE_SANACT_START_OVERWRITE;
++    }
++    throw std::logic_error("unreachable");
++}
++
++bool NVMeSanitizeParams::matchesDword10(uint32_t dword10) const
++{
++    uint32_t own_dword10 = 0;
++
++    // reconstruct the dword10 sent by libnvme
++    own_dword10 |= (uint32_t)nvmeAction();
++    own_dword10 |= (((uint32_t)patternInvert) << 8);
++    own_dword10 |= (((uint32_t)passes & 0xf) << 4);
++
++    return dword10 == own_dword10;
++}
+diff --git a/src/NVMeDrive.hpp b/src/NVMeDrive.hpp
+index c7a9ba8..1107e0f 100644
+--- a/src/NVMeDrive.hpp
++++ b/src/NVMeDrive.hpp
+@@ -1,16 +1,71 @@
++#pragma once
++
++#include <NVMeIntf.hpp>
++#include <boost/asio.hpp>
++#include <sdbusplus/asio/connection.hpp>
++#include <sdbusplus/asio/object_server.hpp>
+ #include <xyz/openbmc_project/Inventory/Item/Drive/server.hpp>
++#include <xyz/openbmc_project/Inventory/Item/DriveErase/server.hpp>
++
++#include <memory>
+ 
+ using DriveBase =
+     sdbusplus::xyz::openbmc_project::Inventory::Item::server::Drive;
+-class NVMeDrive : public DriveBase
++using DriveErase =
++    sdbusplus::xyz::openbmc_project::Inventory::Item::server::DriveErase;
++using EraseAction = sdbusplus::xyz::openbmc_project::Inventory::Item::server::
++    DriveErase::EraseAction;
++
++class NVMeSubsystem;
++
++class NVMeSanitizeParams
+ {
+   public:
+-    NVMeDrive(sdbusplus::bus_t& bus, const char* path) : DriveBase(bus, path)
+-    {
+-        emit_added();
+-    }
+-    ~NVMeDrive() override
+-    {
+-        emit_removed();
+-    }
++    NVMeSanitizeParams(EraseAction sanact);
++    auto operator<=>(const NVMeSanitizeParams&) const = default;
++
++    enum nvme_sanitize_sanact nvmeAction() const;
++
++    // returns true if the dword10 (from sanitize log page SCDW10) matches
++    // these parameters.
++    bool matchesDword10(uint32_t dword10) const;
++
++    EraseAction sanact;
++
++    // for overwrite action
++    uint8_t passes;
++    uint32_t pattern;
++    bool patternInvert;
++};
++
++class NVMeDrive :
++    public DriveBase,
++    public DriveErase,
++    public std::enable_shared_from_this<NVMeDrive>
++{
++  public:
++    static const int sanitizePollIntervalSecs = 4;
++
++    NVMeDrive(boost::asio::io_context& io,
++              std::shared_ptr<sdbusplus::asio::connection> conn,
++              const std::string& path, std::weak_ptr<NVMeSubsystem> subsys);
++
++    ~NVMeDrive() override;
++
++    void erase(EraseAction action) override;
++
++    const std::string path;
++
++  private:
++    void sanitizePoll();
++
++    /*
++    Parameters of the current/last sanitize operation
++    */
++    std::optional<NVMeSanitizeParams> sanitizeParams;
++    boost::asio::steady_timer sanitizeTimer;
++    bool sanitizePollPending = false;
++
++    boost::asio::io_context& io;
++    std::weak_ptr<NVMeSubsystem> subsys;
+ };
+diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
+index ae22781..2e130d3 100644
+--- a/src/NVMeIntf.hpp
++++ b/src/NVMeIntf.hpp
+@@ -231,6 +231,11 @@ class NVMeMiIntf
+         nvme_mi_ctrl_t ctrl, uint16_t ctrlid, uint32_t nsid, bool attach,
+         std::function<void(const std::error_code&, int nvme_status)>&& cb) = 0;
+ 
++    virtual void adminSanitize(nvme_mi_ctrl_t ctrl,
++                               enum nvme_sanitize_sanact sanact, uint8_t passes,
++                               uint32_t pattern, bool invert_pattern,
++                               std::function<void(nvme_ex_ptr ex)>&& 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 9d322e9..e11bb21 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -1420,3 +1420,31 @@ void NVMeMi::adminAttachDetachNamespace(
+         io.post([cb{std::move(cb)}, post_err]() { cb(post_err, -1); });
+     }
+ }
++
++void NVMeMi::adminSanitize(nvme_mi_ctrl_t ctrl,
++                           enum nvme_sanitize_sanact sanact, uint8_t passes,
++                           uint32_t pattern, bool invert_pattern,
++                           std::function<void(nvme_ex_ptr ex)>&& cb)
++{
++    std::error_code post_err =
++        try_post([self{shared_from_this()}, ctrl, sanact, passes, pattern,
++                  invert_pattern, cb{std::move(cb)}]() {
++            struct nvme_sanitize_nvm_args args;
++            memset(&args, 0x0, sizeof(args));
++            args.args_size = sizeof(args);
++            args.sanact = sanact;
++            args.owpass = passes;
++            args.oipbp = invert_pattern;
++
++            int status = nvme_mi_admin_sanitize_nvm(ctrl, &args);
++            printf("san status %d errno %d\n", status, errno);
++
++            auto ex = makeLibNVMeError(errno, status, "adminSanitize");
++            self->io.post([cb{std::move(cb)}, ex]() { cb(ex); });
++        });
++    if (post_err)
++    {
++        auto ex = makeLibNVMeError("post failed");
++        io.post([cb{std::move(cb)}, ex]() { cb(ex); });
++    }
++}
+diff --git a/src/NVMeMi.hpp b/src/NVMeMi.hpp
+index 4d2b14b..7a42085 100644
+--- a/src/NVMeMi.hpp
++++ b/src/NVMeMi.hpp
+@@ -88,6 +88,10 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+         std::function<void(const std::error_code&, int nvme_status)>&& cb)
+         override;
+ 
++    void adminSanitize(nvme_mi_ctrl_t ctrl, enum nvme_sanitize_sanact sanact,
++                       uint8_t passes, uint32_t pattern, bool invert_pattern,
++                       std::function<void(nvme_ex_ptr ex)>&& cb) override;
++
+   private:
+     // the transfer size for nvme mi messages.
+     // define in github.com/linux-nvme/libnvme/blob/master/src/nvme/mi.c
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index 08909de..ff1c20f 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -1,6 +1,8 @@
+ #include "NVMeSubsys.hpp"
+ 
+ #include "AsioHelper.hpp"
++#include "NVMeDrive.hpp"
++#include "NVMeError.hpp"
+ #include "NVMePlugin.hpp"
+ #include "NVMeUtil.hpp"
+ #include "Thresholds.hpp"
+@@ -75,8 +77,7 @@ NVMeSubsystem::NVMeSubsystem(boost::asio::io_context& io,
+     NVMeStorage(objServer, *dynamic_cast<sdbusplus::bus_t*>(conn.get()),
+                 path.c_str()),
+     path(path), io(io), objServer(objServer), conn(conn), name(name),
+-    config(configData), nvmeIntf(intf), status(Status::Stop),
+-    drive(*dynamic_cast<sdbusplus::bus_t*>(conn.get()), path.c_str())
++    config(configData), nvmeIntf(intf), status(Status::Stop)
+ {}
+ 
+ // Performs initialisation after shared_from_this() has been set up.
+@@ -99,15 +100,16 @@ void NVMeSubsystem::init()
+         throw std::runtime_error("Unsupported NVMe interface");
+     }
+ 
++    /* xyz.openbmc_project.Inventory.Item.Storage */
+     NVMeStorage::init(
+         std::static_pointer_cast<NVMeStorage>(shared_from_this()));
+ 
+     /* xyz.openbmc_project.Inventory.Item.Drive */
+-    drive.protocol(NVMeDrive::DriveProtocol::NVMe);
+-    drive.type(NVMeDrive::DriveType::SSD);
++    drive = std::make_shared<NVMeDrive>(io, conn, path, weak_from_this());
++    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
+     createAssociation();
+ }
+@@ -1003,3 +1005,46 @@ void NVMeSubsystem::deleteVolume(boost::asio::yield_context yield,
+ 
+     forgetVolume(volume);
+ }
++
++// submitCb is called once the sanitize has been submitted
++void NVMeSubsystem::startSanitize(
++    const NVMeSanitizeParams& params,
++    std::function<void(nvme_ex_ptr ex)>&& submitCb)
++{
++    nvme_mi_ctrl_t ctrl = primaryController->getMiCtrl();
++    auto intf = std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
++
++    intf->adminSanitize(ctrl, params.nvmeAction(), params.passes,
++                        params.pattern, params.patternInvert,
++                        [submitCb](nvme_ex_ptr ex) { submitCb(ex); });
++}
++
++void NVMeSubsystem::sanitizeStatus(
++    std::function<void(nvme_ex_ptr ex, bool inProgress, bool failed,
++                       bool completed, uint16_t sstat, uint16_t sprog,
++                       uint32_t scdw10)>&& cb)
++{
++    nvme_mi_ctrl_t ctrl = primaryController->getMiCtrl();
++    auto intf = std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
++
++    intf->adminGetLogPage(
++        ctrl, NVME_LOG_LID_SANITIZE, NVME_NSID_NONE, 0, 0,
++        [self{shared_from_this()}, cb](const std::error_code& ec,
++                                       std::span<uint8_t> data) {
++        if (ec)
++        {
++            std::string msg = "GetLogPage failed: " + ec.message();
++            auto ex = makeLibNVMeError(msg);
++            cb(ex, false, false, false, 0, 0, 0);
++            return;
++        }
++
++        nvme_sanitize_log_page* log =
++            reinterpret_cast<nvme_sanitize_log_page*>(data.data());
++        uint8_t san_status = log->sstat & NVME_SANITIZE_SSTAT_STATUS_MASK;
++        cb(nvme_ex_ptr(), san_status == NVME_SANITIZE_SSTAT_STATUS_IN_PROGESS,
++           san_status == NVME_SANITIZE_SSTAT_STATUS_COMPLETED_FAILED,
++           san_status == NVME_SANITIZE_SSTAT_STATUS_COMPLETE_SUCCESS,
++           log->sstat, log->sprog, log->scdw10);
++        });
++}
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index b02d45f..13b705d 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -51,6 +51,13 @@ class NVMeSubsystem :
+     std::shared_ptr<NVMeVolume>
+         getVolume(const sdbusplus::message::object_path& volPath) const;
+ 
++    void startSanitize(const NVMeSanitizeParams& params,
++                       std::function<void(nvme_ex_ptr ex)>&& submitCb);
++    void sanitizeStatus(
++        std::function<void(nvme_ex_ptr ex, bool inProgress, bool failed,
++                           bool completed, uint16_t sstat, uint16_t sprog,
++                           uint32_t scdw10)>&& cb);
++
+     const std::string path;
+ 
+   private:
+@@ -89,7 +96,7 @@ class NVMeSubsystem :
+     /*
+     Drive interface: xyz.openbmc_project.Inventory.Item.Drive
+     */
+-    NVMeDrive drive;
++    std::shared_ptr<NVMeDrive> drive;
+ 
+     // map from cntrlid to a pair of {controller, controller_plugin}
+     std::map<uint16_t, std::pair<std::shared_ptr<NVMeController>,
+@@ -132,7 +139,7 @@ class NVMeSubsystem :
+ 
+     sdbusplus::message::object_path
+         createVolume(boost::asio::yield_context yield, uint64_t size,
+-                     size_t lbaFormat, bool metadataAtEnd);
++                     size_t lbaFormat, bool metadataAtEnd) override;
+ 
+     // callback when drive completes. not called in dbus method context.
+     void createVolumeFinished(std::string prog_id, nvme_ex_ptr ex,
+diff --git a/src/meson.build b/src/meson.build
+index f630802..75c6ca3 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -195,6 +195,7 @@ if get_option('nvme').enabled()
+         'NVMeController.cpp',
+         'NVMeProgress.cpp',
+         'NVMeVolume.cpp',
++        'NVMeDrive.cpp',
+         'NVMeError.cpp',
+     )
+ 
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0015-NVMe-Add-SupportedFormats-to-NVMe.Storage.patch b/recipes-phosphor/sensors/dbus-sensors/0015-NVMe-Add-SupportedFormats-to-NVMe.Storage.patch
new file mode 100644
index 0000000..fd76169
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0015-NVMe-Add-SupportedFormats-to-NVMe.Storage.patch
@@ -0,0 +1,291 @@
+From c3faa7076bd90b470dbb9d2a93a7543dfa046674 Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Thu, 18 May 2023 09:54:18 +0800
+Subject: [PATCH 15/16] NVMe: Add SupportedFormats to NVMe.Storage
+
+Also split NVMeStorage.cpp out from NVMeStorage.hpp
+
+This queries the default namespace for supported formats on subsystem
+startup.
+
+```
+busctl get-property  xyz.openbmc_project.NVMe \
+    /xyz/openbmc_project/inventory/system/nvme/NVMe_MI_1/NVMe_MI_bus14_1 \
+    xyz.openbmc_project.Nvme.Storage SupportedFormats
+a(uuus) 5 0 512 0 "xyz.openbmc_project.Nvme.Storage.RelativePerformance.Better"
+    1 512 8 "xyz.openbmc_project.Nvme.Storage.RelativePerformance.Degraded"
+    2 4096 0 "xyz.openbmc_project.Nvme.Storage.RelativePerformance.Best"
+    3 4096 8 "xyz.openbmc_project.Nvme.Storage.RelativePerformance.Good"
+    4 4096 16 "xyz.openbmc_project.Nvme.Storage.RelativePerformance.Degraded"
+```
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: Id67e35c5cb4a48aa7dfce2adffda8ba37e2ba539
+(cherry picked from commit 064a695b9981a827c2042a07e3850a7d0297b95f)
+---
+ src/NVMeMi.cpp      |  3 ++-
+ src/NVMeStorage.cpp | 64 +++++++++++++++++++++++++++++++++++++++++++++
+ src/NVMeStorage.hpp | 53 ++++++++++++++++++-------------------
+ src/NVMeSubsys.cpp  | 45 +++++++++++++++++++++++++++++++
+ src/NVMeSubsys.hpp  |  2 ++
+ src/meson.build     |  1 +
+ 6 files changed, 139 insertions(+), 29 deletions(-)
+ create mode 100644 src/NVMeStorage.cpp
+
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index e11bb21..ed08cf7 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -1230,7 +1230,8 @@ void NVMeMi::createNamespace(
+             auto msg =
+                 std::string("Size must be a multiple of the block size ") +
+                 std::to_string(block_size);
+-            submitted_cb(std::make_shared<NVMeSdBusPlusError>(msg));
++            submitted_cb(makeLibNVMeError(
++                msg, std::make_shared<CommonErr::InvalidArgument>()));
+             return;
+         }
+ 
+diff --git a/src/NVMeStorage.cpp b/src/NVMeStorage.cpp
+new file mode 100644
+index 0000000..1d861d8
+--- /dev/null
++++ b/src/NVMeStorage.cpp
+@@ -0,0 +1,64 @@
++#include "NVMeStorage.hpp"
++
++#include "NVMeError.hpp"
++
++RelPerf relativePerformanceFromRP(uint8_t rp)
++{
++    switch (rp)
++    {
++        case 0:
++            return RelPerf::Best;
++        case 1:
++            return RelPerf::Better;
++        case 2:
++            return RelPerf::Good;
++        default:
++            return RelPerf::Degraded;
++    }
++}
++
++NVMeStorage::NVMeStorage(sdbusplus::asio::object_server& objServer,
++                         sdbusplus::bus_t& bus, const char* path) :
++    StorageBase(bus, path),
++    objServer(objServer), path(path)
++{}
++
++NVMeStorage::~NVMeStorage()
++{
++    objServer.remove_interface(nvmeStorageInterface);
++    emit_removed();
++}
++
++void NVMeStorage::init(std::shared_ptr<NVMeStorage> self)
++{
++    self->nvmeStorageInterface = self->objServer.add_interface(
++        self->path, "xyz.openbmc_project.Nvme.Storage");
++    self->nvmeStorageInterface->register_method(
++        "CreateVolume",
++        [weak{std::weak_ptr<NVMeStorage>(self)}](
++            boost::asio::yield_context yield, uint64_t size, size_t lbaFormat,
++            bool metadataAtEnd) {
++        if (auto self = weak.lock())
++        {
++            return self->createVolume(yield, size, lbaFormat, metadataAtEnd);
++        }
++        throw *makeLibNVMeError("storage removed");
++        });
++
++    std::vector<std::tuple<size_t, size_t, size_t, RelPerf>> prop;
++    self->nvmeStorageInterface->register_property("SupportedFormats", prop);
++
++    self->nvmeStorageInterface->initialize();
++    self->emit_added();
++}
++
++void NVMeStorage::setSupportedFormats(const std::vector<LBAFormat>& formats)
++{
++    std::vector<std::tuple<size_t, size_t, size_t, RelPerf>> prop;
++    for (auto& f : formats)
++    {
++        prop.push_back(
++            {f.index, f.blockSize, f.metadataSize, f.relativePerformance});
++    }
++    nvmeStorageInterface->set_property("SupportedFormats", prop);
++}
+diff --git a/src/NVMeStorage.hpp b/src/NVMeStorage.hpp
+index b8be4dc..761aefe 100644
+--- a/src/NVMeStorage.hpp
++++ b/src/NVMeStorage.hpp
+@@ -2,26 +2,38 @@
+ 
+ #include "NVMeError.hpp"
+ 
++#include <boost/asio.hpp>
++#include <sdbusplus/asio/connection.hpp>
++#include <sdbusplus/asio/object_server.hpp>
+ #include <xyz/openbmc_project/Inventory/Item/Storage/server.hpp>
++#include <xyz/openbmc_project/Nvme/Storage/server.hpp>
+ 
+ #include <memory>
+ 
+ using StorageBase =
+     sdbusplus::xyz::openbmc_project::Inventory::Item::server::Storage;
++using RelPerf =
++    sdbusplus::common::xyz::openbmc_project::nvme::Storage::RelativePerformance;
++
++struct LBAFormat
++{
++    size_t index;
++    // in bytes
++    size_t blockSize;
++    // in bytes
++    size_t metadataSize;
++    RelPerf relativePerformance;
++};
++
++RelPerf relativePerformanceFromRP(uint8_t rp);
++
+ class NVMeStorage : public StorageBase
+ {
+   public:
+     NVMeStorage(sdbusplus::asio::object_server& objServer,
+-                sdbusplus::bus_t& bus, const char* path) :
+-        StorageBase(bus, path),
+-        objServer(objServer), path(path)
+-    {}
++                sdbusplus::bus_t& bus, const char* path);
+ 
+-    ~NVMeStorage() override
+-    {
+-        objServer.remove_interface(nvmeStorageInterface);
+-        emit_removed();
+-    }
++    ~NVMeStorage() override;
+ 
+     virtual sdbusplus::message::object_path
+         createVolume(boost::asio::yield_context yield, uint64_t size,
+@@ -29,25 +41,10 @@ class NVMeStorage : public StorageBase
+ 
+   protected:
+     // Called by parent class for setup after shared_ptr has been initialised
+-    static void init(std::shared_ptr<NVMeStorage> self)
+-    {
+-        self->nvmeStorageInterface = self->objServer.add_interface(
+-            self->path, "xyz.openbmc_project.Nvme.Storage");
+-        self->nvmeStorageInterface->register_method(
+-            "CreateVolume", [weak{std::weak_ptr<NVMeStorage>(self)}](
+-                                boost::asio::yield_context yield, uint64_t size,
+-                                size_t lbaFormat, bool metadataAtEnd) {
+-                if (auto self = weak.lock())
+-                {
+-                    return self->createVolume(yield, size, lbaFormat,
+-                                              metadataAtEnd);
+-                }
+-                throw *makeLibNVMeError("storage removed");
+-            });
+-        self->nvmeStorageInterface->initialize();
+-
+-        self->emit_added();
+-    }
++    // Will complete asynchronously.
++    static void init(std::shared_ptr<NVMeStorage> self);
++
++    void setSupportedFormats(const std::vector<LBAFormat>& formats);
+ 
+   private:
+     // NVMe-specific interface.
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index ff1c20f..d5fb768 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -340,6 +340,8 @@ void NVMeSubsystem::markFunctional(bool toggle)
+ 
+                 // TODO: may need to wait for this to complete?
+                 self->updateVolumes();
++
++                self->querySupportedFormats();
+                 });
+         });
+     }
+@@ -980,6 +982,49 @@ void NVMeSubsystem::forgetVolume(std::shared_ptr<NVMeVolume> volume)
+     updateAssociation();
+ }
+ 
++void NVMeSubsystem::querySupportedFormats()
++{
++
++    nvme_mi_ctrl_t ctrl = primaryController->getMiCtrl();
++    auto intf = std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
++    intf->adminIdentify(
++        ctrl, nvme_identify_cns::NVME_IDENTIFY_CNS_NS, NVME_NSID_ALL,
++        NVME_CNTLID_NONE,
++        [self{shared_from_this()}](nvme_ex_ptr ex, std::span<uint8_t> data) {
++        if (ex)
++        {
++            std::cerr << self->name << ": Error getting LBA formats :" << ex
++                      << "\n";
++            return;
++        }
++
++        nvme_id_ns& id = *reinterpret_cast<nvme_id_ns*>(data.data());
++
++        size_t nlbaf = id.nlbaf;
++        if (nlbaf > 64)
++        {
++            std::cerr << self->name << ": Bad nlbaf " << nlbaf << "\n";
++            return;
++        }
++
++        std::cerr << self->name << ": Got nlbaf " << nlbaf << "\n";
++        std::vector<LBAFormat> formats;
++        for (size_t i = 0; i < nlbaf; i++)
++        {
++            size_t blockSize = 1ul << id.lbaf[i].ds;
++            size_t metadataSize = id.lbaf[i].ms;
++            RelPerf rp = relativePerformanceFromRP(id.lbaf[i].rp);
++            std::cerr << self->name << ": lbaf " << i << " blocksize "
++                      << blockSize << "\n";
++            formats.push_back({.index = i,
++                               .blockSize = blockSize,
++                               .metadataSize = metadataSize,
++                               .relativePerformance = rp});
++        }
++        self->setSupportedFormats(formats);
++        });
++}
++
+ void NVMeSubsystem::deleteVolume(boost::asio::yield_context yield,
+                                  std::shared_ptr<NVMeVolume> volume)
+ {
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index 13b705d..aee0986 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -156,6 +156,8 @@ class NVMeSubsystem :
+     // may throw if the volume exists.
+     std::shared_ptr<NVMeVolume> addVolume(const NVMeNSIdentify& ns);
+ 
++    void querySupportedFormats();
++
+     // a counter to skip health poll when NVMe subsystem becomes Unavailable
+     unsigned UnavailableCount = 0;
+     static constexpr unsigned UnavailableMaxCount = 60;
+diff --git a/src/meson.build b/src/meson.build
+index 75c6ca3..9a8a743 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -197,6 +197,7 @@ if get_option('nvme').enabled()
+         'NVMeVolume.cpp',
+         'NVMeDrive.cpp',
+         'NVMeError.cpp',
++        'NVMeStorage.cpp',
+     )
+ 
+     pdi_dep = dependency ('phosphor-dbus-interfaces')
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0016-NVMe-Add-Serial-Model-Firmware-Version.patch b/recipes-phosphor/sensors/dbus-sensors/0016-NVMe-Add-Serial-Model-Firmware-Version.patch
new file mode 100644
index 0000000..2103616
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0016-NVMe-Add-Serial-Model-Firmware-Version.patch
@@ -0,0 +1,240 @@
+From c8e70b8c830ce63548348ce9436f1286f6e31566 Mon Sep 17 00:00:00 2001
+From: Matt Johnston <matt@codeconstruct.com.au>
+Date: Fri, 19 May 2023 17:12:03 +0800
+Subject: [PATCH 16/16] NVMe: Add Serial, Model, Firmware Version
+
+Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
+Change-Id: I8532aa9841f1cc331decd5581b3e4b9dc5f2dd5b
+(cherry picked from commit d480c4b1222c97e79dc4875581f59c01c0bed949)
+---
+ src/NVMeController.cpp | 11 +++++++----
+ src/NVMeController.hpp |  9 +++++++++
+ src/NVMeDrive.cpp      |  5 ++++-
+ src/NVMeDrive.hpp      |  4 ++++
+ src/NVMeSubsys.cpp     | 35 +++++++++++++++++++++++++++++++++++
+ src/NVMeSubsys.hpp     |  1 +
+ src/NVMeUtil.hpp       | 26 ++++++++++++++++++++++++++
+ 7 files changed, 86 insertions(+), 5 deletions(-)
+
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index 3539a9d..49e4035 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -29,11 +29,10 @@ std::shared_ptr<NVMeControllerEnabled>
+ 
+ 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}}})
++              {{"FirmwareCommitStatus", {FwCommitStatus::Ready}}}),
++    SoftwareExtVersion(dynamic_cast<sdbusplus::bus_t&>(*conn), path.c_str()),
++    SoftwareVersion(dynamic_cast<sdbusplus::bus_t&>(*conn), path.c_str())
+ {}
+ 
+ void NVMeControllerEnabled::init()
+@@ -121,6 +120,8 @@ void NVMeControllerEnabled::init()
+     // StorageController::emit_added();
+ 
+     NVMeAdmin::emit_added();
++    SoftwareExtVersion::emit_added();
++    SoftwareVersion::emit_added();
+ }
+ 
+ void NVMeControllerEnabled::start(
+@@ -337,6 +338,8 @@ void NVMeControllerEnabled::firmwareCommitAsync(uint8_t commitAction,
+ NVMeControllerEnabled::~NVMeControllerEnabled()
+ {
+     objServer.remove_interface(securityInterface);
++    SoftwareVersion::emit_removed();
++    SoftwareExtVersion::emit_removed();
+     NVMeAdmin::emit_removed();
+     // StorageController::emit_removed();
+     objServer.remove_interface(ctrlInterface);
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+index f056c12..2036aa1 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -9,9 +9,16 @@
+ #include <sdbusplus/asio/object_server.hpp>
+ #include <xyz/openbmc_project/Inventory/Item/StorageController/server.hpp>
+ #include <xyz/openbmc_project/NVMe/NVMeAdmin/server.hpp>
++#include <xyz/openbmc_project/Software/ExtendedVersion/server.hpp>
++#include <xyz/openbmc_project/Software/Version/server.hpp>
+ 
+ #include <utility>
+ 
++using SoftwareVersion =
++    sdbusplus::server::xyz::openbmc_project::software::Version;
++using SoftwareExtVersion =
++    sdbusplus::server::xyz::openbmc_project::software::ExtendedVersion;
++
+ class NVMeControllerPlugin;
+ class NVMeSubsystem;
+ 
+@@ -120,6 +127,8 @@ class NVMeControllerEnabled :
+     // private sdbusplus::xyz::openbmc_project::Inventory::Item::server::
+     //     StorageController,
+     private sdbusplus::xyz::openbmc_project::NVMe::server::NVMeAdmin,
++    public SoftwareExtVersion,
++    public SoftwareVersion,
+     public std::enable_shared_from_this<NVMeControllerEnabled>
+ 
+ {
+diff --git a/src/NVMeDrive.cpp b/src/NVMeDrive.cpp
+index 38ea8a1..8a206c3 100644
+--- a/src/NVMeDrive.cpp
++++ b/src/NVMeDrive.cpp
+@@ -11,15 +11,18 @@ NVMeDrive::NVMeDrive(boost::asio::io_context& io,
+                      std::weak_ptr<NVMeSubsystem> subsys) :
+     DriveBase(dynamic_cast<sdbusplus::bus_t&>(*conn), path.c_str()),
+     DriveErase(dynamic_cast<sdbusplus::bus_t&>(*conn), path.c_str()),
+-    path(path), sanitizeTimer(io), io(io), subsys(subsys)
++    Asset(dynamic_cast<sdbusplus::bus_t&>(*conn), path.c_str()), path(path),
++    sanitizeTimer(io), io(io), subsys(subsys)
+ {
+     DriveBase::emit_added();
+     DriveErase::emit_added();
++    Asset::emit_added();
+ }
+ 
+ NVMeDrive::~NVMeDrive()
+ {
+     sanitizeTimer.cancel();
++    Asset::emit_removed();
+     DriveErase::emit_removed();
+     DriveBase::emit_removed();
+ }
+diff --git a/src/NVMeDrive.hpp b/src/NVMeDrive.hpp
+index 1107e0f..40f7588 100644
+--- a/src/NVMeDrive.hpp
++++ b/src/NVMeDrive.hpp
+@@ -4,6 +4,7 @@
+ #include <boost/asio.hpp>
+ #include <sdbusplus/asio/connection.hpp>
+ #include <sdbusplus/asio/object_server.hpp>
++#include <xyz/openbmc_project/Inventory/Decorator/Asset/server.hpp>
+ #include <xyz/openbmc_project/Inventory/Item/Drive/server.hpp>
+ #include <xyz/openbmc_project/Inventory/Item/DriveErase/server.hpp>
+ 
+@@ -15,6 +16,8 @@ using DriveErase =
+     sdbusplus::xyz::openbmc_project::Inventory::Item::server::DriveErase;
+ using EraseAction = sdbusplus::xyz::openbmc_project::Inventory::Item::server::
+     DriveErase::EraseAction;
++using Asset =
++    sdbusplus::server::xyz::openbmc_project::inventory::decorator::Asset;
+ 
+ class NVMeSubsystem;
+ 
+@@ -41,6 +44,7 @@ class NVMeSanitizeParams
+ class NVMeDrive :
+     public DriveBase,
+     public DriveErase,
++    public Asset,
+     public std::enable_shared_from_this<NVMeDrive>
+ {
+   public:
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index d5fb768..97a17fa 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -338,6 +338,8 @@ void NVMeSubsystem::markFunctional(bool toggle)
+ 
+                 self->status = Status::Start;
+ 
++                self->fillDrive();
++
+                 // TODO: may need to wait for this to complete?
+                 self->updateVolumes();
+ 
+@@ -838,6 +840,39 @@ void NVMeSubsystem::updateVolumes()
+         });
+ }
+ 
++void NVMeSubsystem::fillDrive()
++{
++    nvme_mi_ctrl_t ctrl = primaryController->getMiCtrl();
++    auto intf = std::get<std::shared_ptr<NVMeMiIntf>>(nvmeIntf.getInferface());
++    intf->adminIdentify(ctrl, nvme_identify_cns::NVME_IDENTIFY_CNS_CTRL,
++                        NVME_NSID_NONE, NVME_CNTLID_NONE,
++                        [self{shared_from_this()}, intf,
++                         ctrl](nvme_ex_ptr ex, std::span<uint8_t> data) {
++        if (ex)
++        {
++            std::cerr << self->name << ": Error for controller identify\n";
++            return;
++        }
++
++        nvme_id_ctrl& id = *reinterpret_cast<nvme_id_ctrl*>(data.data());
++        self->drive->serialNumber(nvmeString(id.sn, sizeof(id.sn)));
++        self->drive->model(nvmeString(id.mn, sizeof(id.mn)));
++
++        auto fwVer = nvmeString(id.fr, sizeof(id.fr));
++        if (!fwVer.empty())
++        {
++            // Formatting per
++            // https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/43458/2/xyz/openbmc_project/Software/Version.interface.yaml#47
++            // TODO find/write a better reference
++            std::string v("xyz.openbmc_project.NVMe.ControllerFirmwareVersion");
++            self->primaryController->version(v);
++            self->primaryController->purpose(
++                SoftwareVersion::VersionPurpose::Other);
++            self->primaryController->extendedVersion(v + ":" + fwVer);
++        }
++    });
++}
++
+ std::shared_ptr<NVMeVolume> NVMeSubsystem::getVolume(
+     const sdbusplus::message::object_path& volPath) const
+ {
+diff --git a/src/NVMeSubsys.hpp b/src/NVMeSubsys.hpp
+index aee0986..8ff4eb1 100644
+--- a/src/NVMeSubsys.hpp
++++ b/src/NVMeSubsys.hpp
+@@ -147,6 +147,7 @@ class NVMeSubsystem :
+ 
+     void addIdentifyNamespace(uint32_t nsid);
+ 
++    void fillDrive();
+     void updateVolumes();
+ 
+     // removes state associated with the volume. Does not manipulate the drive.
+diff --git a/src/NVMeUtil.hpp b/src/NVMeUtil.hpp
+index 846917c..ef66933 100644
+--- a/src/NVMeUtil.hpp
++++ b/src/NVMeUtil.hpp
+@@ -188,3 +188,29 @@ void pollCtemp(
+                                       std::move(delay), dataFetcher,
+                                       dataProcessor));
+ }
++
++// Strips space padding from a NVMe string, replaces invalid characters with
++// spaces.
++static inline std::string nvmeString(const char* data, size_t size)
++{
++    std::string s(data, size);
++
++    size_t n = s.find_last_not_of(' ');
++    if (n == std::string::npos)
++    {
++        s.clear();
++    }
++    else
++    {
++        s.resize(n + 1);
++    }
++
++    for (auto& c : s)
++    {
++        if (c < 0x20 || c > 0x7e)
++        {
++            c = ' ';
++        }
++    }
++    return s;
++}
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0017-nvmesensor-fix-compile-issue-after-mearging-cleaning.patch b/recipes-phosphor/sensors/dbus-sensors/0017-nvmesensor-fix-compile-issue-after-mearging-cleaning.patch
new file mode 100644
index 0000000..9492ed2
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0017-nvmesensor-fix-compile-issue-after-mearging-cleaning.patch
@@ -0,0 +1,90 @@
+From 8daedd60dc46d8bccacd6984cdf8c7346a9ae5d8 Mon Sep 17 00:00:00 2001
+From: Jinliang Wang <jinliangw@google.com>
+Date: Thu, 7 Sep 2023 00:56:14 -0700
+Subject: [PATCH] nvmesensor: fix compile issue after merging cleaning command
+
+Signed-off-by: Jinliang Wang <jinliangw@google.com>
+---
+ src/NVMeController.hpp            | 4 ++--
+ src/NVMeDrive.hpp                 | 2 +-
+ {include => src}/NVMeError.hpp    | 0
+ {include => src}/NVMeProgress.hpp | 0
+ src/NVMeStorage.hpp               | 2 +-
+ src/NVMeSubsys.cpp                | 3 ++-
+ {include => src}/NVMeVolume.hpp   | 0
+ 7 files changed, 6 insertions(+), 5 deletions(-)
+ rename {include => src}/NVMeError.hpp (100%)
+ rename {include => src}/NVMeProgress.hpp (100%)
+ rename {include => src}/NVMeVolume.hpp (100%)
+
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+index 2036aa1..54d03eb 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -15,9 +15,9 @@
+ #include <utility>
+ 
+ using SoftwareVersion =
+-    sdbusplus::server::xyz::openbmc_project::software::Version;
++    sdbusplus::xyz::openbmc_project::Software::server::Version;
+ using SoftwareExtVersion =
+-    sdbusplus::server::xyz::openbmc_project::software::ExtendedVersion;
++    sdbusplus::xyz::openbmc_project::Software::server::ExtendedVersion;
+ 
+ class NVMeControllerPlugin;
+ class NVMeSubsystem;
+diff --git a/src/NVMeDrive.hpp b/src/NVMeDrive.hpp
+index 40f7588..708a6e9 100644
+--- a/src/NVMeDrive.hpp
++++ b/src/NVMeDrive.hpp
+@@ -17,7 +17,7 @@ using DriveErase =
+ using EraseAction = sdbusplus::xyz::openbmc_project::Inventory::Item::server::
+     DriveErase::EraseAction;
+ using Asset =
+-    sdbusplus::server::xyz::openbmc_project::inventory::decorator::Asset;
++    sdbusplus::xyz::openbmc_project::Inventory::Decorator::server::Asset;
+ 
+ class NVMeSubsystem;
+ 
+diff --git a/include/NVMeError.hpp b/src/NVMeError.hpp
+similarity index 100%
+rename from include/NVMeError.hpp
+rename to src/NVMeError.hpp
+diff --git a/include/NVMeProgress.hpp b/src/NVMeProgress.hpp
+similarity index 100%
+rename from include/NVMeProgress.hpp
+rename to src/NVMeProgress.hpp
+diff --git a/src/NVMeStorage.hpp b/src/NVMeStorage.hpp
+index 761aefe..5936d6c 100644
+--- a/src/NVMeStorage.hpp
++++ b/src/NVMeStorage.hpp
+@@ -13,7 +13,7 @@
+ using StorageBase =
+     sdbusplus::xyz::openbmc_project::Inventory::Item::server::Storage;
+ using RelPerf =
+-    sdbusplus::common::xyz::openbmc_project::nvme::Storage::RelativePerformance;
++    sdbusplus::xyz::openbmc_project::Nvme::server::Storage::RelativePerformance;
+ 
+ struct LBAFormat
+ {
+diff --git a/src/NVMeSubsys.cpp b/src/NVMeSubsys.cpp
+index 97a17fa..32f0304 100644
+--- a/src/NVMeSubsys.cpp
++++ b/src/NVMeSubsys.cpp
+@@ -1035,7 +1035,8 @@ void NVMeSubsystem::querySupportedFormats()
+ 
+         nvme_id_ns& id = *reinterpret_cast<nvme_id_ns*>(data.data());
+ 
+-        size_t nlbaf = id.nlbaf;
++        // nlbaf is 0’s based
++        size_t nlbaf = id.nlbaf + 1;
+         if (nlbaf > 64)
+         {
+             std::cerr << self->name << ": Bad nlbaf " << nlbaf << "\n";
+diff --git a/include/NVMeVolume.hpp b/src/NVMeVolume.hpp
+similarity index 100%
+rename from include/NVMeVolume.hpp
+rename to src/NVMeVolume.hpp
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0018-nvmesensor-increase-timeout-for-namesapce-create-and.patch b/recipes-phosphor/sensors/dbus-sensors/0018-nvmesensor-increase-timeout-for-namesapce-create-and.patch
new file mode 100644
index 0000000..7955902
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0018-nvmesensor-increase-timeout-for-namesapce-create-and.patch
@@ -0,0 +1,56 @@
+From 3270c85f3d6bdbfbf14f3ae20b0dc4dac7093976 Mon Sep 17 00:00:00 2001
+From: Jinliang Wang <jinliangw@google.com>
+Date: Fri, 8 Sep 2023 11:06:11 -0700
+Subject: [PATCH 1/2] nvmesensor: increase timeout for namesapce create and
+ delete
+
+During test, we found that sometimes the create namespace command
+will timeout. Device may need to do some TCG operation on NOR flash.
+So we increase the timeout from 5 seconds to 20 seconds( similar with
+SecuritySend and SecurityReceive).
+
+Change-Id: Ib3a7cb35b5ae6d07d1dde6c95f30107aa0ef9731
+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 ed08cf7..3b7064c 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -17,6 +17,7 @@ nvme_root_t NVMeMi::nvmeRoot = nvme_mi_create_root(stderr, DEFAULT_LOGLEVEL);
+ 
+ constexpr size_t maxNVMeMILength = 4096;
+ constexpr int tcgDefaultTimeoutMS = 20*1000;
++constexpr int namespaceDefaultTimeoutMs = 20*1000;
+ 
+ NVMeMi::NVMeMi(boost::asio::io_context& io,
+                std::shared_ptr<sdbusplus::asio::connection> conn, int bus,
+@@ -1263,7 +1264,11 @@ void NVMeMi::createNamespace(
+         submitted_cb(nvme_ex_ptr());
+         printf("after submitted_cb %d\n", (int)gettid());
+ 
++        unsigned timeout = nvme_mi_ep_get_timeout(self->nvmeEP);
++        nvme_mi_ep_set_timeout(self->nvmeEP, namespaceDefaultTimeoutMs);
+         int status = nvme_mi_admin_ns_mgmt_create(ctrl, &data, 0, &new_nsid);
++        nvme_mi_ep_set_timeout(self->nvmeEP, timeout);
++
+         nvme_ex_ptr e = makeLibNVMeError(errno, status, "createVolume");
+ 
+         NVMeNSIdentify newns = {
+@@ -1314,7 +1319,11 @@ void NVMeMi::adminDeleteNamespace(
+ {
+     std::error_code post_err = try_post(
+         [self{shared_from_this()}, ctrl, nsid, cb{std::move(cb)}]() {
++
++        unsigned timeout = nvme_mi_ep_get_timeout(self->nvmeEP);
++        nvme_mi_ep_set_timeout(self->nvmeEP, namespaceDefaultTimeoutMs);
+         int status = nvme_mi_admin_ns_mgmt_delete(ctrl, nsid);
++        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));
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors/0019-nvmesensor-print-makeLibNVMeError-for-debug.patch b/recipes-phosphor/sensors/dbus-sensors/0019-nvmesensor-print-makeLibNVMeError-for-debug.patch
new file mode 100644
index 0000000..8b679aa
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0019-nvmesensor-print-makeLibNVMeError-for-debug.patch
@@ -0,0 +1,38 @@
+From 191f2de4aee8d2f1cdc96a4de2ebb8111e676fa1 Mon Sep 17 00:00:00 2001
+From: Jinliang Wang <jinliangw@google.com>
+Date: Fri, 8 Sep 2023 11:21:48 -0700
+Subject: [PATCH 2/2] nvmesensor: print makeLibNVMeError for debug
+
+Although the error message will be passed to bmcweb, printing
+those NVMe-MI error on nvmesensor layer is helpfull for early stage
+debug.
+
+Change-Id: I9115ea7734b51855df8d3c72e4b347fc89b46a68
+Signed-off-by: Jinliang Wang <jinliangw@google.com>
+---
+ src/NVMeError.cpp | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/NVMeError.cpp b/src/NVMeError.cpp
+index 5e0ca78..a538424 100644
+--- a/src/NVMeError.cpp
++++ b/src/NVMeError.cpp
+@@ -176,6 +176,7 @@ nvme_ex_ptr makeLibNVMeError(const std::error_code& err, int nvme_status,
+     if (nvme_status < 0)
+     {
+         auto desc = std::string("libnvme error: ") + err.message();
++        std::cerr << method_name << ":" << desc << std::endl;
+         return std::make_shared<NVMeSdBusPlusError>(desc);
+     }
+     else if (nvme_status > 0)
+@@ -198,6 +199,7 @@ nvme_ex_ptr makeLibNVMeError(const std::error_code& err, int nvme_status,
+                           << std::endl;
+                 desc = "Unknown libnvme error";
+         }
++        std::cerr << method_name << ":" << desc << std::endl;
+         return std::make_shared<NVMeSdBusPlusError>(desc, specific);
+     }
+     // No Error
+-- 
+2.42.0.283.g2d96d420d3-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors_%.bbappend b/recipes-phosphor/sensors/dbus-sensors_%.bbappend
index 8660415..156c738 100644
--- a/recipes-phosphor/sensors/dbus-sensors_%.bbappend
+++ b/recipes-phosphor/sensors/dbus-sensors_%.bbappend
@@ -61,6 +61,25 @@
   file://0045-nvmesensor-improve-handling-of-config-change.patch \
   file://0046-nvmesensor-release-resources-when-exit-poll.patch \
   file://0052-nvmesensor-add-Admin-non-data-command-passthru-DBus-.patch \
+  file://0001-NVMe-Add-NVMeError-as-a-common-error-type.patch \
+  file://0002-nvmesensor-Manually-implement-StorageController.patch \
+  file://0003-nvme-Simplify-Worker-post.patch \
+  file://0004-utils-add-getRandomId.patch \
+  file://0005-nvme-Hack-workaround-for-drives-without-secondary.patch \
+  file://0006-nvme-Split-constructor-for-NVMeSubsystem.patch \
+  file://0007-NVMe-Add-createVolume.patch \
+  file://0008-nvme-add-Delete-method-to-volume.patch \
+  file://0009-nvme-updateVolumes-to-populate-volumes.patch \
+  file://0010-nvme-Add-storage-associations.patch \
+  file://0011-nvme-addVolume-and-forgetVolume-helpers.patch \
+  file://0012-nvme-Make-adminIdentify-return-a-nvme_ex_ptr.patch \
+  file://0013-nvmesensor-AttachVolume-and-DetachVolume.patch \
+  file://0014-nvme-Add-DriveErase-interface.patch \
+  file://0015-NVMe-Add-SupportedFormats-to-NVMe.Storage.patch \
+  file://0016-NVMe-Add-Serial-Model-Firmware-Version.patch \
+  file://0017-nvmesensor-fix-compile-issue-after-mearging-cleaning.patch \
+  file://0018-nvmesensor-increase-timeout-for-namesapce-create-and.patch \
+  file://0019-nvmesensor-print-makeLibNVMeError-for-debug.patch \
 "
 PACKAGECONFIG[nvmesensor] = "-Dnvme=enabled, -Dnvme=disabled, libnvme libnvme-vu"
 SYSTEMD_SERVICE:${PN} += "${@bb.utils.contains('PACKAGECONFIG', 'nvmesensor', \