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', \