nvmesensor: Add NVMe firmware async download/status

Tested: https://paste.googleplex.com/4665501367140352

Change-Id: Ib150815beab9557252fe4e535300b413d1d97e80
Signed-off-by: Muhammad Usama <muhammadusama@google.com>
diff --git a/recipes-phosphor/sensors/dbus-sensors/0048-NVMe-Add-the-NVMe-Admin-interface-within-the-repo.patch b/recipes-phosphor/sensors/dbus-sensors/0048-NVMe-Add-the-NVMe-Admin-interface-within-the-repo.patch
index 7f8ed8d..f9a3d78 100644
--- a/recipes-phosphor/sensors/dbus-sensors/0048-NVMe-Add-the-NVMe-Admin-interface-within-the-repo.patch
+++ b/recipes-phosphor/sensors/dbus-sensors/0048-NVMe-Add-the-NVMe-Admin-interface-within-the-repo.patch
@@ -1,4 +1,4 @@
-From 2fe10a701376d621a2d1d31a5f61c4fec098851b Mon Sep 17 00:00:00 2001
+From 24cee619e20abcb5cb02d2cb0c21e6eaf9070cb2 Mon Sep 17 00:00:00 2001
 From: Muhammad Usama <muhammadusama@google.com>
 Date: Sat, 8 Jul 2023 00:10:40 +0000
 Subject: [PATCH] NVMe: Add the NVMe Admin interface within the repo
@@ -14,16 +14,16 @@
 
 Signed-off-by: Muhammad Usama <muhammadusama@google.com>
 ---
- gen/meson.build                               |  15 +++
+ gen/meson.build                               |  15 ++
  gen/regenerate-meson                          |   4 +
  gen/xyz/meson.build                           |   2 +
- .../NVMe/NVMeAdmin/meson.build                |  15 +++
+ .../NVMe/NVMeAdmin/meson.build                |  15 ++
  gen/xyz/openbmc_project/NVMe/meson.build      |  16 +++
  gen/xyz/openbmc_project/meson.build           |   2 +
  meson.build                                   |  43 ++++++
  src/meson.build                               |   9 +-
- .../NVMe/NVMeAdmin.interface.yaml             | 123 ++++++++++++++++++
- 9 files changed, 222 insertions(+), 7 deletions(-)
+ .../NVMe/NVMeAdmin.interface.yaml             | 130 ++++++++++++++++++
+ 9 files changed, 229 insertions(+), 7 deletions(-)
  create mode 100644 gen/meson.build
  create mode 100755 gen/regenerate-meson
  create mode 100644 gen/xyz/meson.build
@@ -123,7 +123,7 @@
 +# Generated file; do not modify.
 +subdir('NVMe')
 diff --git a/meson.build b/meson.build
-index bdcbb93..49ee6db 100644
+index 91ee75a..56ee4de 100644
 --- a/meson.build
 +++ b/meson.build
 @@ -52,6 +52,11 @@ if not sdbusplus.found()
@@ -184,10 +184,10 @@
  subdir('src')
  
 diff --git a/src/meson.build b/src/meson.build
-index 0ba65d0..52c7b3a 100644
+index 20c6dd8..d8c0615 100644
 --- a/src/meson.build
 +++ b/src/meson.build
-@@ -207,16 +207,10 @@ if get_option('nvme').enabled()
+@@ -219,16 +219,10 @@ if get_option('nvme').enabled()
          include_type: 'system'
      )
  
@@ -205,7 +205,7 @@
      ]
  
      executable(
-@@ -225,6 +219,7 @@ if get_option('nvme').enabled()
+@@ -237,6 +231,7 @@ if get_option('nvme').enabled()
          dependencies: nvme_deps,
          cpp_args: uring_args + ['-frtti', '-UBOOST_ASIO_NO_DEPRECATED', '-UBOOST_ASIO_DISABLE_THREADS', '-UBOOST_ASIO_HAS_IO_URING'],
          install: true,
@@ -215,10 +215,10 @@
  
 diff --git a/yaml/xyz/openbmc_project/NVMe/NVMeAdmin.interface.yaml b/yaml/xyz/openbmc_project/NVMe/NVMeAdmin.interface.yaml
 new file mode 100644
-index 0000000..9040cea
+index 0000000..5fd7cb2
 --- /dev/null
 +++ b/yaml/xyz/openbmc_project/NVMe/NVMeAdmin.interface.yaml
-@@ -0,0 +1,123 @@
+@@ -0,0 +1,130 @@
 +description: >
 +  NVMe MI Admin inteface for vendor commands
 +
@@ -304,7 +304,14 @@
 +        type: boolean
 +        description: >
 +          Boot Partition ID
-+
++  - name: FirmwareDownloadAsync
++    description: >
++      Send Firmware Download command to NVMe device
++    parameters:
++      - name: PathToImage
++        type: string
++        description: >
++          Path to the firmware Image
 +
 +enumerations:
 +  - name: FwCommitStatus
@@ -343,5 +350,5 @@
 +        description: >
 +          Success
 -- 
-2.41.0.255.g8b1d071c50-goog
+2.41.0.640.ga95def55d0-goog
 
diff --git a/recipes-phosphor/sensors/dbus-sensors/0050-nvmesensor-Add-FirmwareDownload-for-NVMe-devices.patch b/recipes-phosphor/sensors/dbus-sensors/0050-nvmesensor-Add-FirmwareDownload-for-NVMe-devices.patch
new file mode 100644
index 0000000..68164d9
--- /dev/null
+++ b/recipes-phosphor/sensors/dbus-sensors/0050-nvmesensor-Add-FirmwareDownload-for-NVMe-devices.patch
@@ -0,0 +1,486 @@
+From 5d27676736cc8cfa33770f0d12a4784c9e059dbc Mon Sep 17 00:00:00 2001
+From: Muhammad Usama <muhammadusama@google.com>
+Date: Wed, 12 Jul 2023 23:31:15 +0000
+Subject: [PATCH] nvmesensor: Add FirmwareDownload for NVMe devices
+
+Patch Tracking Bug: b/295436281
+Upstream info / review: N/A
+Upstream-Status: Pending (Will move to separate repo as part of other patches)
+Justification: Enable OOB FirmwareDownload for NVMe devices
+
+Signed-off-by: Muhammad Usama <muhammadusama@google.com>
+---
+ src/NVMeController.cpp |  49 +++++++++++-
+ src/NVMeController.hpp |  14 ++++
+ src/NVMeIntf.hpp       |   4 +
+ src/NVMeMi.cpp         | 175 +++++++++++++++++++++++++++++++++--------
+ src/NVMeMi.hpp         |   8 ++
+ 5 files changed, 217 insertions(+), 33 deletions(-)
+
+diff --git a/src/NVMeController.cpp b/src/NVMeController.cpp
+index 1297bb4..215b2e4 100644
+--- a/src/NVMeController.cpp
++++ b/src/NVMeController.cpp
+@@ -10,6 +10,7 @@
+ 
+ #include <cstdio>
+ #include <filesystem>
++#include <fstream>
+ #include <iostream>
+ 
+ using sdbusplus::xyz::openbmc_project::Inventory::Item::server::
+@@ -45,7 +46,8 @@ NVMeControllerEnabled::NVMeControllerEnabled(
+     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}}})
++              {{"FirmwareCommitStatus", {FwCommitStatus::Ready}},
++              {"FirmwareDownloadStatus", {FwDownloadStatus::Ready}}})
+ {}
+ 
+ NVMeControllerEnabled::NVMeControllerEnabled(NVMeController&& nvmeController) :
+@@ -54,7 +56,8 @@ NVMeControllerEnabled::NVMeControllerEnabled(NVMeController&& nvmeController) :
+         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}},
++              {"FirmwareDownloadStatus", {FwDownloadStatus::Ready}}})
+ {}
+ 
+ void NVMeControllerEnabled::init()
+@@ -284,6 +287,48 @@ void NVMeControllerEnabled::firmwareCommitAsync(uint8_t commitAction,
+         });
+ }
+ 
++NVMeAdmin::FwDownloadStatus
++    NVMeControllerEnabled::firmwareDownloadStatus(NVMeAdmin::FwDownloadStatus status)
++{
++    auto downloadStatus = this->NVMeAdmin::firmwareDownloadStatus();
++    // The function is only allowed to reset the status back to ready
++    if (status != FwDownloadStatus::Ready ||
++        downloadStatus == FwDownloadStatus::Ready ||
++        downloadStatus == FwDownloadStatus::InProgress)
++    {
++        throw sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed{};
++    }
++    return this->NVMeAdmin::firmwareDownloadStatus(status);
++}
++
++void NVMeControllerEnabled::firmwareDownloadAsync(std::string pathToImage)
++{
++    auto downloadStatus = this->NVMeAdmin::firmwareDownloadStatus();
++    if (downloadStatus != FwDownloadStatus::Ready)
++    {
++        throw sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed();
++    }
++    if (std::filesystem::exists(pathToImage))
++    {
++        this->NVMeAdmin::firmwareDownloadStatus(FwDownloadStatus::InProgress);
++        nvmeIntf->adminFwDownload(
++            nvmeCtrl, pathToImage,
++            [self{shared_from_this()}](const std::error_code& ec,
++                                       nvme_status_field status) {
++            if (ec || status != NVME_SC_SUCCESS)
++            {
++                self->NVMeAdmin::firmwareDownloadStatus(FwDownloadStatus::Failed);
++                return;
++            }
++            self->NVMeAdmin::firmwareDownloadStatus(FwDownloadStatus::Success);
++        });
++    }
++    else
++    {
++        throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
++    }
++}
++
+ NVMeControllerEnabled::~NVMeControllerEnabled()
+ {
+     objServer.remove_interface(securityInterface);
+diff --git a/src/NVMeController.hpp b/src/NVMeController.hpp
+index 538d9de..fc798dc 100644
+--- a/src/NVMeController.hpp
++++ b/src/NVMeController.hpp
+@@ -178,6 +178,20 @@ class NVMeControllerEnabled :
+     void firmwareCommitAsync(uint8_t commitAction, uint8_t firmwareSlot,
+                              bool bpid) override;
+ 
++    /** Set value of FirmwareDownloadStatus
++     * Used to reset the the status back to ready if the download is not in
++     * process.
++     */
++    NVMeAdmin::FwDownloadStatus
++        firmwareDownloadStatus(NVMeAdmin::FwDownloadStatus) override;
++
++    /** @brief Implementation for FirmwareDownloadAsync
++     *  Send Firmware Image to the NVMe device
++     *
++     *  @param[in] pathToImage - Path to the firmware image
++     */
++    void firmwareDownloadAsync(std::string pathToImage) override;
++
+     void securitySendMethod(boost::asio::yield_context yield, uint8_t proto,
+                             uint16_t proto_specific, std::span<uint8_t> data);
+ 
+diff --git a/src/NVMeIntf.hpp b/src/NVMeIntf.hpp
+index 3a2fa32..7917748 100644
+--- a/src/NVMeIntf.hpp
++++ b/src/NVMeIntf.hpp
+@@ -184,6 +184,10 @@ class NVMeMiIntf
+                                std::function<void(const std::error_code&,
+                                                   nvme_status_field)>&& cb) = 0;
+ 
++    virtual void adminFwDownload(nvme_mi_ctrl_t ctrl, std::string firmwarefile,
++                               std::function<void(const std::error_code&,
++                                                  nvme_status_field)>&& cb) = 0;
++
+     virtual void adminSecuritySend(
+         nvme_mi_ctrl_t ctrl, uint8_t proto, uint16_t proto_specific,
+         std::span<uint8_t> data,
+diff --git a/src/NVMeMi.cpp b/src/NVMeMi.cpp
+index be0688e..74d6e27 100644
+--- a/src/NVMeMi.cpp
++++ b/src/NVMeMi.cpp
+@@ -5,6 +5,7 @@
+ #include <boost/endian.hpp>
+ 
+ #include <cerrno>
++#include <fstream>
+ #include <iostream>
+ 
+ std::map<int, std::weak_ptr<NVMeMi::Worker>> NVMeMi::workerMap{};
+@@ -13,7 +14,7 @@ std::map<int, std::weak_ptr<NVMeMi::Worker>> NVMeMi::workerMap{};
+ nvme_root_t NVMeMi::nvmeRoot = nvme_mi_create_root(stderr, DEFAULT_LOGLEVEL);
+ 
+ constexpr size_t maxNVMeMILength = 4096;
+-constexpr int tcgDefaultTimeoutMS = 20*1000;
++constexpr int tcgDefaultTimeoutMS = 20 * 1000;
+ 
+ NVMeMi::NVMeMi(boost::asio::io_context& io,
+                std::shared_ptr<sdbusplus::asio::connection> conn, int bus,
+@@ -35,7 +36,6 @@ NVMeMi::NVMeMi(boost::asio::io_context& io,
+ 
+     if (singleThreadMode)
+     {
+-
+         auto root = deriveRootBus(bus);
+ 
+         if (!root || *root < 0)
+@@ -292,7 +292,6 @@ void NVMeMi::miSubsystemHealthStatusPoll(
+                                                               true, &ss_health);
+             if (rc < 0)
+             {
+-
+                 std::cerr << "[bus: " << self->bus << ", addr: " << self->addr
+                           << ", eid: " << static_cast<int>(self->eid) << "]"
+                           << "fail to subsystem_health_status_poll: "
+@@ -360,7 +359,8 @@ void NVMeMi::miScanCtrl(std::function<void(const std::error_code&,
+                           << "fail to scan controllers: "
+                           << 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)), {});
++                    cb(std::make_error_code(static_cast<std::errc>(last_errno)),
++                       {});
+                 });
+                 return;
+             }
+@@ -466,7 +466,8 @@ void NVMeMi::adminIdentify(
+                           << "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)), {});
++                    cb(std::make_error_code(static_cast<std::errc>(last_errno)),
++                       {});
+                 });
+                 return;
+             }
+@@ -553,10 +554,8 @@ void NVMeMi::getTelemetryLogChuck(
+     std::vector<uint8_t>&& data,
+     std::function<void(const std::error_code&, std::span<uint8_t>)>&& cb)
+ {
+-
+     if (offset >= data.size())
+     {
+-
+         std::cerr << "[bus: " << bus << ", addr: " << addr
+                   << ", eid: " << static_cast<int>(eid) << "]"
+                   << "get telemetry log: offset exceed the log size. "
+@@ -615,9 +614,9 @@ void NVMeMi::getTelemetryLogChuck(
+         {
+             boost::asio::post(
+                 self->io, [cb{std::move(cb)}, data{std::move(data)}]() mutable {
+-                    std::span<uint8_t> span{data.data(), data.size()};
+-                    cb({}, span);
+-                });
++                std::span<uint8_t> span{data.data(), data.size()};
++                cb({}, span);
++            });
+             return;
+         }
+ 
+@@ -662,8 +661,8 @@ void NVMeMi::adminGetLogPage(
+                     // The number of entries for most recent error logs.
+                     // Currently we only do one nvme mi transfer for the
+                     // error log to avoid blocking other tasks
+-                    static constexpr int num =
+-                        nvme_mi_xfer_size / sizeof(nvme_error_log_page);
++                    static constexpr int num = nvme_mi_xfer_size /
++                                               sizeof(nvme_error_log_page);
+                     nvme_error_log_page* log =
+                         reinterpret_cast<nvme_error_log_page*>(data.data());
+ 
+@@ -752,8 +751,8 @@ void NVMeMi::adminGetLogPage(
+                     data.resize(sizeof(nvme_ns_list));
+                     nvme_ns_list* log =
+                         reinterpret_cast<nvme_ns_list*>(data.data());
+-                    rc =
+-                        nvme_mi_admin_get_log_changed_ns_list(ctrl, false, log);
++                    rc = nvme_mi_admin_get_log_changed_ns_list(ctrl, false,
++                                                               log);
+                     if (rc)
+                     {
+                         std::cerr
+@@ -769,8 +768,8 @@ void NVMeMi::adminGetLogPage(
+                 // fall through to NVME_LOG_LID_TELEMETRY_CTRL
+                 case NVME_LOG_LID_TELEMETRY_CTRL:
+                 {
+-                    bool host =
+-                        (lid == NVME_LOG_LID_TELEMETRY_HOST) ? true : false;
++                    bool host = (lid == NVME_LOG_LID_TELEMETRY_HOST) ? true
++                                                                     : false;
+ 
+                     uint32_t size = 0;
+                     rc = getTelemetryLogSize(ctrl, host, size);
+@@ -792,8 +791,8 @@ void NVMeMi::adminGetLogPage(
+                         reinterpret_cast<nvme_resv_notification_log*>(
+                             data.data());
+ 
+-                    int rc =
+-                        nvme_mi_admin_get_log_reservation(ctrl, false, log);
++                    int rc = nvme_mi_admin_get_log_reservation(ctrl, false,
++                                                               log);
+                     if (rc)
+                     {
+                         std::cerr
+@@ -841,7 +840,8 @@ void NVMeMi::adminGetLogPage(
+                           << "fail to get log page: " << std::strerror(errno)
+                           << std::endl;
+                 logHandler = [cb{std::move(cb)}, last_errno{errno}]() {
+-                    cb(std::make_error_code(static_cast<std::errc>(last_errno)), {});
++                    cb(std::make_error_code(static_cast<std::errc>(last_errno)),
++                       {});
+                 };
+             }
+             else if (rc > 0)
+@@ -858,14 +858,13 @@ void NVMeMi::adminGetLogPage(
+ 
+             if (!logHandler)
+             {
+-                logHandler =
+-                    [cb{std::move(cb)}, data{std::move(data)}]() mutable {
++                logHandler = [cb{std::move(cb)},
++                              data{std::move(data)}]() mutable {
+                     std::span<uint8_t> span{data.data(), data.size()};
+                     cb({}, span);
+                 };
+             }
+             boost::asio::post(self->io, logHandler);
+-
+         });
+     }
+     catch (const std::runtime_error& e)
+@@ -934,8 +933,8 @@ void NVMeMi::adminXfer(
+                           << ", eid: " << static_cast<int>(self->eid) << "]"
+                           << "failed to nvme_mi_admin_xfer" << std::endl;
+                 self->io.post([cb{std::move(cb)}, last_errno{errno}]() {
+-                    cb(std::make_error_code(static_cast<std::errc>(last_errno)), {},
+-                       {});
++                    cb(std::make_error_code(static_cast<std::errc>(last_errno)),
++                       {}, {});
+                 });
+                 return;
+             }
+@@ -993,7 +992,6 @@ void NVMeMi::adminFwCommit(
+             int rc = nvme_mi_admin_fw_commit(ctrl, &args);
+             if (rc < 0)
+             {
+-
+                 std::cerr << "[bus: " << self->bus << ", addr: " << self->addr
+                           << ", eid: " << static_cast<int>(self->eid) << "]"
+                           << "fail to nvme_mi_admin_fw_commit: "
+@@ -1044,14 +1042,129 @@ void NVMeMi::adminFwCommit(
+     }
+ }
+ 
++void NVMeMi::adminFwDownloadChunk(
++    nvme_mi_ctrl_t ctrl, std::string firmwarefile, size_t size, size_t offset,
++    int attempt_count,
++    std::function<void(const std::error_code&, nvme_status_field)>&& cb)
++{
++    if (!nvmeEP)
++    {
++        std::cerr << "nvme endpoint is invalid" << std::endl;
++        io.post([cb{std::move(cb)}]() {
++            cb(std::make_error_code(std::errc::no_such_device),
++               nvme_status_field::NVME_SC_MASK);
++        });
++        return;
++    }
++    try
++    {
++        post([ctrl, firmwarefile, size, offset, attempt_count,
++              cb{std::move(cb)}, self{shared_from_this()}]() mutable {
++            char data[nvme_mi_xfer_size];
++            std::ifstream fwFile(firmwarefile, std::ios::in | std::ios::binary);
++            if (fwFile.fail())
++            {
++                std::cerr << "fail to open fw image file: " << firmwarefile
++                          << strerror(errno) << std::endl;
++                self->io.post([cb{std::move(cb)}]() {
++                    cb(std::make_error_code(static_cast<std::errc>(errno)),
++                       nvme_status_field::NVME_SC_MASK);
++                });
++                return;
++            }
++            fwFile.seekg(offset, std::ios::beg);
++            nvme_fw_download_args args;
++            memset(&args, 0, sizeof(args));
++            args.args_size = sizeof(args);
++            int data_len = std::min(size - offset, nvme_mi_xfer_size);
++            fwFile.read(data, data_len);
++            fwFile.close();
++            args.offset = offset;
++            args.data_len = data_len;
++            args.data = data;
++
++            int rc = nvme_mi_admin_fw_download(ctrl, &args);
++            if (rc < 0)
++            {
++                if (attempt_count > 0)
++                {
++                    std::cout << "Retrying the firmware chunk. With Offset :"
++                              << offset << " Total firmware Size :" << size
++                              << std::endl;
++                    attempt_count = attempt_count - 1;
++                }
++                else
++                {
++                    std::cerr << "fail to nvme_mi_admin_fw_download: "
++                              << std::strerror(errno) << std::endl;
++                    self->io.post([cb{std::move(cb)}]() {
++                        cb(std::make_error_code(static_cast<std::errc>(errno)),
++                           nvme_status_field::NVME_SC_MASK);
++                    });
++                    return;
++                }
++            }
++            else
++            {
++                attempt_count = 3; /* Reset the attempt count*/
++                offset = offset + args.data_len;
++            }
++            if (offset >= size)
++            {
++                std::cout
++                    << "Successfully transferred the firmware. Transfer Size : "
++                    << offset << " Total Size :" << size << std::endl;
++                self->io.post([rc, cb{std::move(cb)}]() {
++                    cb({}, static_cast<nvme_status_field>(rc));
++                });
++                return;
++            }
++            self->adminFwDownloadChunk(ctrl, firmwarefile, size, offset,
++                                       attempt_count, std::move(cb));
++        });
++    }
++    catch (const std::runtime_error& e)
++    {
++        std::cerr << e.what() << std::endl;
++        io.post([cb{std::move(cb)}]() {
++            cb(std::make_error_code(std::errc::no_such_device),
++               nvme_status_field::NVME_SC_MASK);
++        });
++        return;
++    }
++}
++
++void NVMeMi::adminFwDownload(
++    nvme_mi_ctrl_t ctrl, std::string firmwarefile,
++    std::function<void(const std::error_code&, nvme_status_field)>&& cb)
++{
++    size_t offset = 0;
++    int tryCount = 3;
++    std::ifstream imageFile(firmwarefile,
++                            std::ios::in | std::ios::binary | std::ios::ate);
++    if (imageFile.fail())
++    {
++        std::cerr << "Can't open the firmware file: " << std::strerror(errno)
++                  << std::endl;
++        io.post([cb{std::move(cb)}]() {
++            cb(std::make_error_code(std::errc::no_such_device),
++               nvme_status_field::NVME_SC_MASK);
++        });
++    }
++    size_t size = imageFile.tellg();
++    imageFile.close();
++    adminFwDownloadChunk(ctrl, firmwarefile, size, offset, tryCount,
++                         std::move(cb));
++}
++
+ void NVMeMi::adminSecuritySend(
+     nvme_mi_ctrl_t ctrl, uint8_t proto, uint16_t proto_specific,
+     std::span<uint8_t> data,
+     std::function<void(const std::error_code&, int nvme_status)>&& cb)
+ {
+-    std::error_code post_err = try_post(
+-        [self{shared_from_this()}, ctrl, proto, proto_specific, data,
+-         cb{std::move(cb)}]() {
++    std::error_code post_err =
++        try_post([self{shared_from_this()}, ctrl, proto, proto_specific, data,
++                  cb{std::move(cb)}]() {
+         struct nvme_security_send_args args;
+         memset(&args, 0x0, sizeof(args));
+         args.secp = proto;
+@@ -1093,9 +1206,9 @@ void NVMeMi::adminSecurityReceive(
+         return;
+     }
+ 
+-    std::error_code post_err = try_post(
+-        [self{shared_from_this()}, ctrl, proto, proto_specific, transfer_length,
+-         cb{std::move(cb)}]() {
++    std::error_code post_err =
++        try_post([self{shared_from_this()}, ctrl, proto, proto_specific,
++                  transfer_length, cb{std::move(cb)}]() {
+         std::vector<uint8_t> data(transfer_length);
+ 
+         struct nvme_security_receive_args args;
+diff --git a/src/NVMeMi.hpp b/src/NVMeMi.hpp
+index 0a86dcf..054382f 100644
+--- a/src/NVMeMi.hpp
++++ b/src/NVMeMi.hpp
+@@ -42,6 +42,10 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+         std::function<void(const std::error_code&, nvme_status_field)>&& cb)
+         override;
+ 
++    void adminFwDownload(nvme_mi_ctrl_t ctrl, std::string firmwarefile,
++        std::function<void(const std::error_code&, nvme_status_field)>&& cb)
++        override;
++
+     void adminXfer(nvme_mi_ctrl_t ctrl, const nvme_mi_admin_req_hdr& admin_req,
+                    std::span<uint8_t> data, unsigned int timeout_ms,
+                    std::function<void(const std::error_code&,
+@@ -128,6 +132,10 @@ class NVMeMi : public NVMeMiIntf, public std::enable_shared_from_this<NVMeMi>
+ 
+     std::error_code try_post(std::function<void(void)>&& func);
+ 
++    void adminFwDownloadChunk(nvme_mi_ctrl_t ctrl, std::string firmwarefile, size_t size,
++                              size_t offset, int attempt_count,
++                              std::function<void(const std::error_code&, nvme_status_field)>&& cb);
++
+     void getTelemetryLogChuck(
+         nvme_mi_ctrl_t ctrl, bool host, uint64_t offset,
+         std::vector<uint8_t>&& data,
+-- 
+2.41.0.640.ga95def55d0-goog
+
diff --git a/recipes-phosphor/sensors/dbus-sensors_%.bbappend b/recipes-phosphor/sensors/dbus-sensors_%.bbappend
index 35505de..97bcd0c 100644
--- a/recipes-phosphor/sensors/dbus-sensors_%.bbappend
+++ b/recipes-phosphor/sensors/dbus-sensors_%.bbappend
@@ -65,6 +65,7 @@
   file://0047-NVMed-Enable-Primary-controller-when-SC-is-empty.patch \
   file://0048-NVMe-Add-the-NVMe-Admin-interface-within-the-repo.patch \
   file://0049-nvmesensor-Limit-logPage-07-to-Area-1-and-2.patch \
+  file://0050-nvmesensor-Add-FirmwareDownload-for-NVMe-devices.patch \
 "
 PACKAGECONFIG[nvmesensor] = "-Dnvme=enabled, -Dnvme=disabled, libnvme"
 SYSTEMD_SERVICE:${PN} += "${@bb.utils.contains('PACKAGECONFIG', 'nvmesensor', \