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