fw_update: target specific startUpdate implementation Description: This commit implements the `startUpdate` handling of the target specific update flow [1], which introduced `ItemUpdateManager` for handling target specific updates. Motivation: We have introduced the `FirmwareInventory`/`FirmwareInventoryManager` classes to manage D-Bus interfaces for firmware update, After that, we would like to implement the `StartUpdate` method for `FirmwareInventory` properly for the update process implementation, which `AggregateUpdateManager` will help route the command to the specific update sessions. Test results: - Successful FD item Firmware update performed on Yosemite4, by manually triggering the `StartUpdate` method call on the `xyz.openbmc_project.Software.Update` interface. - Successful FD item Firmware update performed on Yosemite4, by using Redfish API to trigger the update. (main stream version, no patch needed) - Successful firmware update performed on Yosemite4 via the dir inotify based flow. - Successful firmware update performed on Yosemite4 via the multipart image flow. - Successful firmware update performed on Yosemite4 after a previous failed update attempt. - [1]: https://github.com/openbmc/docs/blob/master/designs/code-update.md Change-Id: I724f0987eb882a1b4e6bce8c209b319c927689e9 Signed-off-by: Unive Tien <unive.tien.wiwynn@gmail.com>
diff --git a/common/utils.cpp b/common/utils.cpp index b7de564..d0bd39e 100644 --- a/common/utils.cpp +++ b/common/utils.cpp
@@ -2,9 +2,12 @@ #include "common/start_lifetime_as.hpp" +#include <fcntl.h> #include <libpldm/pdr.h> #include <libpldm/pldm_types.h> #include <linux/mctp.h> +#include <sys/mman.h> +#include <sys/stat.h> #include <xyz/openbmc_project/BIOSConfig/Manager/client.hpp> #include <xyz/openbmc_project/Common/error.hpp> @@ -15,6 +18,7 @@ #include <algorithm> #include <cctype> +#include <cstdlib> #include <ctime> #include <fstream> #include <iostream> @@ -22,6 +26,7 @@ #include <memory> #include <stdexcept> #include <string> +#include <system_error> #include <vector> PHOSPHOR_LOG2_USING; @@ -971,5 +976,179 @@ return random() % 10000; } +MMapHandler::MMapHandler(int fd, std::optional<size_t> size) : + fd(fd), ownsFd(false) // External fd, not owned by MMapHandler +{ + struct stat sb; + if (fstat(fd, &sb) == -1) + { + error("Failed to get the actual file size"); + data = nullptr; + throw std::runtime_error("Failed to get the actual file size"); + } + + if (size.has_value()) + { + this->size = size.value(); + if (this->size > static_cast<size_t>(sb.st_size)) + { + error("Requested size {REQSIZE} exceeds file size {FILESIZE}", + "REQSIZE", this->size, "FILESIZE", sb.st_size); + data = nullptr; + throw std::invalid_argument("Requested size exceeds file size"); + } + } + else + { + this->size = static_cast<size_t>(sb.st_size); + } + + data = static_cast<char*>( + mmap(nullptr, this->size, PROT_READ, MAP_PRIVATE, fd, 0)); + if (data == MAP_FAILED) + { + int savedErrno = errno; + data = nullptr; + error("mmap failed: size {SIZE}, error: {ERROR}", "SIZE", this->size, + "ERROR", savedErrno); + throw std::system_error(savedErrno, std::system_category(), + "mmap failed"); + } +} + +MMapHandler::MMapHandler(const std::filesystem::path& path, + std::optional<size_t> size) : + ownsFd(true) // MMapHandler owns this fd and will close it +{ + fd = open(path.c_str(), O_RDONLY); + if (fd < 0) + { + int savedErrno = errno; + error("Failed to open file: {PATH}, error: {ERROR}", "PATH", + path.c_str(), "ERROR", savedErrno); + throw std::system_error(savedErrno, std::system_category(), + "Failed to open file: " + path.string()); + } + + try + { + struct stat sb; + if (fstat(fd, &sb) == -1) + { + int savedErrno = errno; + close(fd); + fd = -1; + error("Failed to fstat file: {ERROR}", "ERROR", savedErrno); + throw std::system_error(savedErrno, std::system_category(), + "Failed to fstat file"); + } + + if (size.has_value()) + { + this->size = size.value(); + if (this->size > static_cast<size_t>(sb.st_size)) + { + close(fd); + fd = -1; + error("Requested size {REQSIZE} exceeds file size {FILESIZE}", + "REQSIZE", this->size, "FILESIZE", sb.st_size); + throw std::invalid_argument("Requested size exceeds file size"); + } + } + else + { + this->size = static_cast<size_t>(sb.st_size); + } + + data = static_cast<char*>( + mmap(nullptr, this->size, PROT_READ, MAP_PRIVATE, fd, 0)); + if (data == MAP_FAILED) + { + int savedErrno = errno; + close(fd); + fd = -1; + data = nullptr; + error("mmap failed: size {SIZE}, error: {ERROR}", "SIZE", + this->size, "ERROR", savedErrno); + throw std::system_error(savedErrno, std::system_category(), + "mmap failed"); + } + } + catch (...) + { + if (fd >= 0) + { + close(fd); + fd = -1; + } + throw; + } +} + +MMapHandler::~MMapHandler() +{ + if (data) + { + munmap(data, size); + } + + // Only close fd if we own it (opened via path constructor) + if (ownsFd && fd >= 0) + { + close(fd); + } +} + +char* MMapHandler::getData() +{ + return data; +} + +const char* MMapHandler::getData() const +{ + return data; +} + +std::span<const uint8_t> MMapHandler::getBytes() const +{ + if (!data) + { + return {}; + } + return {reinterpret_cast<const uint8_t*>(data), size}; +} + +std::span<uint8_t> MMapHandler::getBytes() +{ + if (!data) + { + return {}; + } + return {reinterpret_cast<uint8_t*>(data), size}; +} + +std::span<const char> MMapHandler::getChars() const +{ + if (!data) + { + return {}; + } + return {data, size}; +} + +std::span<char> MMapHandler::getChars() +{ + if (!data) + { + return {}; + } + return {data, size}; +} + +size_t MMapHandler::getSize() const +{ + return size; +} + } // namespace utils } // namespace pldm
diff --git a/common/utils.hpp b/common/utils.hpp index b0b14e0..7003ad5 100644 --- a/common/utils.hpp +++ b/common/utils.hpp
@@ -21,6 +21,7 @@ #include <cstdint> #include <filesystem> #include <map> +#include <span> #include <string> #include <variant> #include <vector> @@ -751,5 +752,116 @@ */ void setBiosAttr(const PendingAttributesList& biosAttrList); +/** @brief RAII class to handle mmap and munmap */ +class MMapHandler +{ + public: + /** @brief Constructor to handle mmap with file descriptor + * + * @param[in] fd - file descriptor to be mapped + * @param[in] size - size to be mapped (optional) + * + * @note The file descriptor is not owned by MMapHandler and will not be + * closed in the destructor when constructed with this method. + */ + MMapHandler(int fd, std::optional<size_t> size = std::nullopt); + + /** @brief Constructor to handle mmap with file path + * + * Opens the file, memory maps it, and manages the file descriptor + * lifecycle. + * + * @param[in] path - filesystem path to the file to be opened and mapped + * @param[in] size - size to be mapped (optional) + * + * @note The file descriptor is owned by MMapHandler and will be + * automatically closed in the destructor. + */ + explicit MMapHandler(const std::filesystem::path& path, + std::optional<size_t> size = std::nullopt); + + /** @brief Copy operations (deleted) + * + * Copying is not allowed because the mapped memory region is owned + * by a single MMapHandler instance to prevent double munmap. + */ + MMapHandler(const MMapHandler&) = delete; + MMapHandler& operator=(const MMapHandler&) = delete; + + /** @brief Move operations (deleted) + * + * Transfer of ownership is not allowed at the moment + * to keep the implementation simple and avoid complexities around + * ensuring only one instance manages the mapped memory region. + */ + MMapHandler(MMapHandler&& other) = delete; + MMapHandler& operator=(MMapHandler&& other) = delete; + + /** @brief Destructor to handle munmap and optionally close file descriptor + * + * Unmaps the memory and closes the file descriptor if it was opened + * by MMapHandler (i.e., constructed with file path). + */ + ~MMapHandler(); + + /** @brief Get the data pointer and size of the mapped data + * + * @return char* - pointer to the mapped data + */ + char* getData(); + + /** @brief Get the const data pointer and size of the mapped data + * + * @return const char* - const pointer to the mapped data + */ + const char* getData() const; + + /** @brief Get the size of the mapped data + * + * @return size_t - size of the mapped data + */ + size_t getSize() const; + + /** @brief Get a read-only view of the mapped data as bytes + * + * @return std::span<const uint8_t> - read-only byte view of mapped data + * + * @note The caller is responsible for parsing and interpreting the bytes, + * including handling endianness, alignment, and data validation. + */ + std::span<const uint8_t> getBytes() const; + + /** @brief Get a mutable view of the mapped data as bytes + * + * @return std::span<uint8_t> - mutable byte view of mapped data + * + * @note The caller is responsible for writing valid data, + * including handling endianness, alignment, and data validation. + */ + std::span<uint8_t> getBytes(); + + /** @brief Get a read-only view of the mapped data as chars + * + * @return std::span<const char> - read-only char view of mapped data + * + * @note The caller is responsible for parsing and interpreting the chars. + */ + std::span<const char> getChars() const; + + /** @brief Get a mutable view of the mapped data as chars + * + * @return std::span<char> - mutable char view of mapped data + * + * @note The caller is responsible for writing valid data. + */ + std::span<char> getChars(); + + private: + int fd; + size_t size; + char* data = nullptr; + bool ownsFd = false; +}; + } // namespace utils } // namespace pldm
diff --git a/fw-update/activation.cpp b/fw-update/activation.cpp index abafd96..b5790c9 100644 --- a/fw-update/activation.cpp +++ b/fw-update/activation.cpp
@@ -12,6 +12,10 @@ { if (value == ActivationIntf::Activations::Activating) { + if (ActivationIntf::activation() != ActivationIntf::Activations::Ready) + { + return ActivationIntf::activation(); + } deleteImpl.reset(); updateManager->activatePackage(); } @@ -29,7 +33,7 @@ void Delete::delete_() { - updateManager->clearActivationInfo(); + updateManager->resetActivationState(); } } // namespace fw_update } // namespace pldm
diff --git a/fw-update/activation.hpp b/fw-update/activation.hpp index f072124..cb56d24 100644 --- a/fw-update/activation.hpp +++ b/fw-update/activation.hpp
@@ -13,7 +13,7 @@ namespace fw_update { -class UpdateManager; +class UpdateManagerBase; using ActivationIntf = sdbusplus::server::object_t< sdbusplus::xyz::openbmc_project::Software::server::Activation>; @@ -57,7 +57,7 @@ * @param[in] updateManager - Reference to FW update manager */ Delete(sdbusplus::bus_t& bus, const std::string& objPath, - UpdateManager* updateManager) : + UpdateManagerBase* updateManager) : DeleteIntf(bus, objPath.c_str(), action::emit_interface_added), updateManager(updateManager) {} @@ -66,7 +66,7 @@ void delete_() override; private: - UpdateManager* updateManager; + UpdateManagerBase* updateManager; }; /** @class Activation @@ -84,13 +84,16 @@ * @param[in] updateManager - Reference to FW update manager */ Activation(sdbusplus::bus_t& bus, std::string objPath, - Activations activationStatus, UpdateManager* updateManager) : + Activations activationStatus, UpdateManagerBase* updateManager) : ActivationIntf(bus, objPath.c_str(), ActivationIntf::action::defer_emit), bus(bus), objPath(objPath), updateManager(updateManager) { activation(activationStatus); - deleteImpl = std::make_unique<Delete>(bus, objPath, updateManager); + if (!deleteImpl) + { + deleteImpl = std::make_unique<Delete>(bus, objPath, updateManager); + } emit_object_added(); } @@ -122,7 +125,7 @@ private: sdbusplus::bus_t& bus; const std::string objPath; - UpdateManager* updateManager; + UpdateManagerBase* updateManager; std::unique_ptr<Delete> deleteImpl; };
diff --git a/fw-update/aggregate_update_manager.cpp b/fw-update/aggregate_update_manager.cpp index 3bdaacb..8a2cc39 100644 --- a/fw-update/aggregate_update_manager.cpp +++ b/fw-update/aggregate_update_manager.cpp
@@ -21,7 +21,8 @@ { response = updateManager->handleRequest(eid, command, request, reqMsgLen); - if (responseMsg->payload[0] != PLDM_FWUP_COMMAND_NOT_EXPECTED) + auto relayedResponseMsg = new (response.data()) pldm_msg; + if (relayedResponseMsg->payload[0] != PLDM_FWUP_COMMAND_NOT_EXPECTED) { return response; } @@ -29,6 +30,24 @@ return response; } +void AggregateUpdateManager::createUpdateManager( + const SoftwareIdentifier& softwareIdentifier, + const Descriptors& descriptors, const ComponentInfo& componentInfo, + const std::string& updateObjPath, const std::string& generatedId) +{ + auto eid = softwareIdentifier.first; + + descriptorMap[softwareIdentifier] = + std::make_unique<Descriptors>(descriptors); + componentInfoMap[softwareIdentifier] = + std::make_unique<ComponentInfo>(componentInfo); + + updateManagers[softwareIdentifier] = std::make_unique<ItemUpdateManager>( + eid, event, handler, instanceIdDb, updateObjPath, generatedId, + *descriptorMap[softwareIdentifier], + *componentInfoMap[softwareIdentifier]); +} + void AggregateUpdateManager::eraseUpdateManager( const SoftwareIdentifier& softwareIdentifier) {
diff --git a/fw-update/aggregate_update_manager.hpp b/fw-update/aggregate_update_manager.hpp index eb15dc9..c9acc6c 100644 --- a/fw-update/aggregate_update_manager.hpp +++ b/fw-update/aggregate_update_manager.hpp
@@ -1,5 +1,6 @@ #pragma once +#include "item_update_manager.hpp" #include "update_manager.hpp" namespace pldm::fw_update @@ -63,11 +64,12 @@ * @param[in] componentInfo - The component information associated with the * software identifier * @param[in] updateObjPath - The D-Bus object path for the update manager + * @param[in] generatedId - The software hash identifier */ - void createUpdateManager(const SoftwareIdentifier& softwareIdentifier, - const Descriptors& descriptors, - const ComponentInfo& componentInfo, - const std::string& updateObjPath); + void createUpdateManager( + const SoftwareIdentifier& softwareIdentifier, + const Descriptors& descriptors, const ComponentInfo& componentInfo, + const std::string& updateObjPath, const std::string& generatedId); /** * @brief Erase an existing UpdateManager instance associated with a @@ -99,7 +101,8 @@ /** * @brief Map of UpdateManager instances keyed by software identifier */ - std::map<SoftwareIdentifier, std::unique_ptr<UpdateManager>> updateManagers; + std::map<SoftwareIdentifier, std::unique_ptr<ItemUpdateManager>> + updateManagers; /** * @brief Map of descriptor maps keyed by software identifier
diff --git a/fw-update/device_updater.cpp b/fw-update/device_updater.cpp index 7c02190..39fbd2d 100644 --- a/fw-update/device_updater.cpp +++ b/fw-update/device_updater.cpp
@@ -94,7 +94,7 @@ mctp_eid_t eid, std::istream& package, const FirmwareDeviceIDRecord& fwDeviceIDRecord, const ComponentImageInfos& compImageInfos, const ComponentInfo& compInfo, - uint32_t maxTransferSize, UpdateManager* updateManager) : + uint32_t maxTransferSize, UpdateManagerBase* updateManager) : eid(eid), package(package), fwDeviceIDRecord(fwDeviceIDRecord), compImageInfos(compImageInfos), compInfo(compInfo), maxTransferSize(maxTransferSize), updateManager(updateManager),
diff --git a/fw-update/device_updater.hpp b/fw-update/device_updater.hpp index a4c2d03..a0621c3 100644 --- a/fw-update/device_updater.hpp +++ b/fw-update/device_updater.hpp
@@ -21,7 +21,7 @@ */ using ComponentUpdateStatusMap = std::map<size_t, bool>; -class UpdateManager; +class UpdateManagerBase; /** @class UpdateProgress * @@ -141,7 +141,7 @@ const ComponentImageInfos& compImageInfos, const ComponentInfo& compInfo, uint32_t maxTransferSize, - UpdateManager* updateManager); + UpdateManagerBase* updateManager); /** @brief Get the progress of updating this device as percentage * @@ -309,7 +309,7 @@ uint32_t maxTransferSize; /** @brief To update the status of fw update of the FD */ - UpdateManager* updateManager; + UpdateManagerBase* updateManager; /** @brief Component index is used to track the current component being * updated if multiple components are applicable for the FD.
diff --git a/fw-update/firmware_inventory.cpp b/fw-update/firmware_inventory.cpp index ffbb4e8..8ac3fb9 100644 --- a/fw-update/firmware_inventory.cpp +++ b/fw-update/firmware_inventory.cpp
@@ -4,18 +4,22 @@ { FirmwareInventory::FirmwareInventory( - SoftwareIdentifier /*softwareIdentifier*/, const std::string& softwarePath, - const std::string& softwareVersion, const std::string& associatedEndpoint, - SoftwareVersionPurpose purpose) : - softwarePath(softwarePath), + SoftwareIdentifier softwareIdentifier, const std::string& softwarePath, + const std::string& generatedId, const std::string& softwareVersion, + const std::string& associatedEndpoint, SoftwareVersionPurpose purpose) : + softwareIdentifier(softwareIdentifier), + softwarePath(std::format("{}_{}", softwarePath, generatedId)), association(this->bus, this->softwarePath.c_str()), version(this->bus, this->softwarePath.c_str(), - SoftwareVersion::action::defer_emit) + SoftwareVersion::action::defer_emit), + activation(this->bus, this->softwarePath.c_str(), + SoftwareActivation::action::defer_emit) { this->association.associations( {{"running", "ran_on", associatedEndpoint.c_str()}}); this->version.version(softwareVersion.c_str()); this->version.purpose(purpose); + this->activation.activation(SoftwareActivation::Activations::Active); this->version.emit_added(); }
diff --git a/fw-update/firmware_inventory.hpp b/fw-update/firmware_inventory.hpp index 3ef9bfb..ef7b5e3 100644 --- a/fw-update/firmware_inventory.hpp +++ b/fw-update/firmware_inventory.hpp
@@ -4,14 +4,18 @@ #include "common/utils.hpp" #include <xyz/openbmc_project/Association/Definitions/server.hpp> +#include <xyz/openbmc_project/Software/Activation/server.hpp> #include <xyz/openbmc_project/Software/Version/server.hpp> class FirmwareInventoryTest; +class FirmwareInventoryTestInstance; namespace pldm::fw_update { class FirmwareInventory; +using SoftwareActivation = sdbusplus::server::object_t< + sdbusplus::xyz::openbmc_project::Software::server::Activation>; using SoftwareVersion = sdbusplus::server::object_t< sdbusplus::xyz::openbmc_project::Software::server::Version>; using SoftwareAssociationDefinitions = sdbusplus::server::object_t< @@ -23,12 +27,12 @@ { public: friend class ::FirmwareInventoryTest; + friend class ::FirmwareInventoryTestInstance; FirmwareInventory() = delete; FirmwareInventory(const FirmwareInventory&) = delete; FirmwareInventory(FirmwareInventory&&) = delete; FirmwareInventory& operator=(const FirmwareInventory&) = delete; FirmwareInventory& operator=(FirmwareInventory&&) = delete; - ~FirmwareInventory() = default; /** * @brief Constructor @@ -36,6 +40,7 @@ * component identifier * @param[in] softwarePath - D-Bus object path for the firmware inventory * entry + * @param[in] generatedId - Software hash identifier * @param[in] softwareVersion - Active version of the firmware * @param[in] associatedEndpoint - D-Bus object path of the endpoint * associated with the firmware @@ -47,8 +52,8 @@ * future use and currently not used in the implementation. */ explicit FirmwareInventory( - SoftwareIdentifier /*softwareIdentifier*/, - const std::string& softwarePath, const std::string& softwareVersion, + SoftwareIdentifier softwareIdentifier, const std::string& softwarePath, + const std::string& generatedId, const std::string& softwareVersion, const std::string& associatedEndpoint, SoftwareVersionPurpose purpose = SoftwareVersionPurpose::Unknown); @@ -59,6 +64,11 @@ sdbusplus::bus_t& bus = utils::DBusHandler::getBus(); /** + * @brief Software identifier containing EID and component identifier + */ + SoftwareIdentifier softwareIdentifier; + + /** * @brief The D-Bus object path for the firmware inventory entry, obtained * by */ @@ -74,6 +84,12 @@ * @brief Software version object that represents the firmware version */ SoftwareVersion version; + + /** + * @brief Software activation object that represents the activation state + * of the firmware + */ + SoftwareActivation activation; }; } // namespace pldm::fw_update
diff --git a/fw-update/firmware_inventory_manager.cpp b/fw-update/firmware_inventory_manager.cpp index b4274eb..d093d3e 100644 --- a/fw-update/firmware_inventory_manager.cpp +++ b/fw-update/firmware_inventory_manager.cpp
@@ -20,7 +20,7 @@ void FirmwareInventoryManager::createFirmwareEntry( const SoftwareIdentifier& softwareIdentifier, const SoftwareName& softwareName, const std::string& activeVersion, - const Descriptors& /*descriptors*/, const ComponentInfo& /*componentInfo*/) + const Descriptors& descriptors, const ComponentInfo& componentInfo) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); @@ -40,14 +40,18 @@ .value_or(std::filesystem::path{ "/xyz/openbmc_project/inventory/system/board/PLDM_Device"}); const auto boardName = boardPath.filename().string(); - const auto softwarePath = - std::format("{}/{}_{}_{}", SoftwareVersion::namespace_path, boardName, - softwareName, utils::generateSwId()); - softwareMap.insert_or_assign( - softwareIdentifier, - std::make_unique<FirmwareInventory>(softwareIdentifier, softwarePath, - activeVersion, boardPath)); + const auto softwarePath = std::format( + "{}/{}_{}", SoftwareVersion::namespace_path, boardName, softwareName); + const auto generatedId = std::to_string(utils::generateSwId()); + + updateManager.createUpdateManager(softwareIdentifier, descriptors, + componentInfo, softwarePath, generatedId); + + softwareMap.insert_or_assign(softwareIdentifier, + std::make_unique<FirmwareInventory>( + softwareIdentifier, softwarePath, + generatedId, activeVersion, boardPath)); } void FirmwareInventoryManager::deleteFirmwareEntry(const pldm::eid& eid)
diff --git a/fw-update/item_update_manager.cpp b/fw-update/item_update_manager.cpp new file mode 100644 index 0000000..850b78f --- /dev/null +++ b/fw-update/item_update_manager.cpp
@@ -0,0 +1,266 @@ +#include "item_update_manager.hpp" + +#include "activation.hpp" +#include "common/utils.hpp" +#include "package_parser.hpp" + +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> + +#include <phosphor-logging/lg2.hpp> + +#include <cassert> +#include <cmath> +#include <cstdlib> +#include <span> +#include <spanstream> +#include <string> +#include <system_error> + +PHOSPHOR_LOG2_USING; + +namespace pldm::fw_update +{ + +bool ItemUpdateManager::processPackage() +{ + inProgressActivation = std::make_unique<Activation>( + pldm::utils::DBusHandler::getBus(), objPathWithSwId, + software::Activation::Activations::NotReady, this); + + if (packageMap->getSize() < sizeof(pldm_package_header_information)) + { + error( + "PLDM fw update package length {SIZE} less than the length of the package header information '{PACKAGE_HEADER_INFO_SIZE}'.", + "SIZE", packageMap->getSize(), "PACKAGE_HEADER_INFO_SIZE", + sizeof(pldm_package_header_information)); + inProgressActivation->activation( + software::Activation::Activations::Invalid); + packageMap.reset(); + return false; + } + + auto buffer = std::vector<uint8_t>(packageMap->getBytes().begin(), + packageMap->getBytes().end()); + parser = parsePkgHeader(buffer); + if (parser == nullptr) + { + error("Invalid PLDM package header information"); + inProgressActivation->activation( + software::Activation::Activations::Invalid); + packageMap.reset(); + return false; + } + try + { + parser->parse(buffer, buffer.size()); + } + catch (const std::exception& e) + { + error("Invalid PLDM package header, error - {ERROR}", "ERROR", e); + inProgressActivation->activation( + software::Activation::Activations::Invalid); + parser.reset(); + packageMap.reset(); + return false; + } + + auto deviceIdRecordOffset = + associatePkgToDevice(parser->getFwDeviceIDRecords(), descriptors); + if (!deviceIdRecordOffset) + { + error("Failed to associate package to device"); + inProgressActivation->activation( + software::Activation::Activations::Invalid); + packageMap.reset(); + return false; + } + + const auto& fwDeviceIDRecords = parser->getFwDeviceIDRecords(); + const auto& compImageInfos = parser->getComponentImageInfos(); + static constexpr uint32_t MAXIMUM_TRANSFER_SIZE = 4096; + + auto packageSpan = packageMap->getChars(); + packageDataStream = + std::make_unique<std::ispanstream>(packageSpan, std::ios::binary); + deviceUpdater = std::make_unique<DeviceUpdater>( + eid, *packageDataStream, fwDeviceIDRecords[*deviceIdRecordOffset], + compImageInfos, componentInfo, MAXIMUM_TRANSFER_SIZE, this); + inProgressActivation->activation(software::Activation::Activations::Ready); + activationProgress = std::make_unique<ActivationProgress>( + pldm::utils::DBusHandler::getBus(), objPathWithSwId); + inProgressActivation->activation( + software::Activation::Activations::Activating); + + return true; +} + +std::string ItemUpdateManager::processFd(int fd) +{ + objPathWithSwId = std::format("{}_{}", objPath, utils::generateSwId()); + auto rawDupFd = dup(fd); + if (rawDupFd < 0) + { + error("Failed to duplicate package file descriptor"); + throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable(); + } + this->dupFd = std::make_unique<pldm::utils::CustomFD>(rawDupFd); + deferHandler = std::make_unique< + sdeventplus::source::Defer>(event, [this](sdeventplus::source:: + EventBase&) { + try + { + packageMap = + std::make_unique<pldm::utils::MMapHandler>((*this->dupFd)()); + } + catch (const std::exception& e) + { + error("Failed to mmap package file, error - {ERROR}", "ERROR", e); + updateInProgress = false; + this->dupFd.reset(); + throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable(); + } + if (!processPackage()) + { + error("Failed to process firmware update package"); + updateInProgress = false; + packageMap.reset(); + this->dupFd.reset(); + throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable(); + } + }); + return objPathWithSwId; +} + +std::optional<DeviceIDRecordOffset> ItemUpdateManager::associatePkgToDevice( + const FirmwareDeviceIDRecords& fwDeviceIDRecords, + const Descriptors& descriptors) +{ + for (size_t index = 0; index < fwDeviceIDRecords.size(); ++index) + { + const auto& deviceIDDescriptors = + std::get<Descriptors>(fwDeviceIDRecords[index]); + if (std::includes(descriptors.begin(), descriptors.end(), + deviceIDDescriptors.begin(), + deviceIDDescriptors.end())) + { + return index; + } + } + return std::nullopt; +} + +void ItemUpdateManager::updateDeviceCompletion(mctp_eid_t /*eid*/, bool status) +{ + activationProgress->progress(100); + packageMap.reset(); + dupFd.reset(); + + auto endTime = std::chrono::steady_clock::now(); + auto dur = + std::chrono::duration<double, std::milli>(endTime - startTime).count(); + info("Firmware update time: {DURATION}ms", "DURATION", dur); + activationProgress.reset(); + inProgressActivation->activation( + status ? software::Activation::Activations::Active + : software::Activation::Activations::Failed); + deviceUpdater.reset(); + packageDataStream.reset(); + packageMap.reset(); + dupFd.reset(); + updateInProgress = false; + return; +} + +Response ItemUpdateManager::handleRequest(mctp_eid_t /*eid*/, uint8_t command, + const pldm_msg* request, + size_t reqMsgLen) +{ + Response response(sizeof(pldm_msg), 0); + if (deviceUpdater) + { + if (command == PLDM_REQUEST_FIRMWARE_DATA) + { + return deviceUpdater->requestFwData(request, reqMsgLen); + } + else if (command == PLDM_TRANSFER_COMPLETE) + { + return deviceUpdater->transferComplete(request, reqMsgLen); + } + else if (command == PLDM_VERIFY_COMPLETE) + { + return deviceUpdater->verifyComplete(request, reqMsgLen); + } + else if (command == PLDM_APPLY_COMPLETE) + { + return deviceUpdater->applyComplete(request, reqMsgLen); + } + else + { + auto ptr = new (response.data()) pldm_msg; + auto rc = encode_cc_only_resp( + request->hdr.instance_id, request->hdr.type, + request->hdr.command, PLDM_ERROR_INVALID_DATA, ptr); + assert(rc == PLDM_SUCCESS); + } + } + else + { + auto ptr = new (response.data()) pldm_msg; + auto rc = encode_cc_only_resp(request->hdr.instance_id, + request->hdr.type, +request->hdr.command, + PLDM_FWUP_COMMAND_NOT_EXPECTED, ptr); + assert(rc == PLDM_SUCCESS); + } + + return response; +} + +void ItemUpdateManager::activatePackage() +{ + startTime = std::chrono::steady_clock::now(); + deviceUpdater->startFwUpdateFlow(); +} + +void ItemUpdateManager::resetActivationState() +{ + inProgressActivation.reset(); + activationProgress.reset(); + dupFd.reset(); + updateInProgress = false; +} + +void ItemUpdateManager::updateActivationProgress() +{ + if (deviceUpdater) + { + auto progress = deviceUpdater->getProgress(); + if (progress != lastProgress) + { + activationProgress->progress(progress); + lastProgress = progress; + } + } +} + +sdbusplus::message::object_path ItemUpdateManager::startUpdate( + sdbusplus::message::unix_fd image, + ApplyTimeIntf::RequestedApplyTimes /*applyTime*/) +{ + if (updateInProgress) + { + error("Update already in progress"); + throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable(); + } + if (image.fd < 0) + { + error("Invalid package file descriptor"); + throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable(); + } + updateInProgress = true; + + return processFd(image.fd); +} + +} // namespace pldm::fw_update
diff --git a/fw-update/item_update_manager.hpp b/fw-update/item_update_manager.hpp new file mode 100644 index 0000000..04542dd --- /dev/null +++ b/fw-update/item_update_manager.hpp
@@ -0,0 +1,186 @@ +#pragma once + +#include "update_manager.hpp" + +#include <xyz/openbmc_project/Software/ApplyTime/server.hpp> +#include <xyz/openbmc_project/Software/Update/server.hpp> + +#include <span> +#include <spanstream> + +namespace pldm::fw_update +{ + +namespace software = sdbusplus::xyz::openbmc_project::Software::server; + +using ItemUpdateIntf = sdbusplus::server::object_t< + sdbusplus::xyz::openbmc_project::Software::server::Update>; +using ApplyTimeIntf = + sdbusplus::xyz::openbmc_project::Software::server::ApplyTime; + +class ItemUpdateManager : public UpdateManagerBase, public ItemUpdateIntf +{ + public: + ItemUpdateManager() = delete; + ItemUpdateManager(const ItemUpdateManager&) = delete; + ItemUpdateManager(ItemUpdateManager&&) = delete; + ItemUpdateManager& operator=(const ItemUpdateManager&) = delete; + ItemUpdateManager& operator=(ItemUpdateManager&&) = delete; + ~ItemUpdateManager() = default; + + /** + * @brief Constructor for ItemUpdateManager + * + * @param[in] eid The MCTP EID + * @param[in] event The event object + * @param[in] handler The request handler + * @param[in] instanceIdDb The instance ID database + * @param[in] objPath The D-Bus object path + * @param[in] generatedId The software hash identifier + * @param[in] descriptors The descriptors for the device + * @param[in] componentInfo The component information for the device + */ + explicit ItemUpdateManager( + mctp_eid_t eid, Event& event, + pldm::requester::Handler<pldm::requester::Request>& handler, + InstanceIdDb& instanceIdDb, const std::string& objPath, + const std::string& generatedId, const Descriptors& descriptors, + const ComponentInfo& componentInfo) : + UpdateManagerBase(event, handler, instanceIdDb), + ItemUpdateIntf(pldm::utils::DBusHandler::getBus(), + std::format("{}_{}", objPath, generatedId).c_str()), + eid(eid), objPath(objPath), descriptors(descriptors), + componentInfo(componentInfo) + {} + + /** + * @brief Handle PLDM requests for the item-based update manager + * + * @param[in] eid - Remote MCTP Endpoint ID + * @param[in] command - PLDM command code + * @param[in] request - PLDM request message + * @param[in] reqMsgLen - PLDM request message length + * @return PLDM response message + */ + Response handleRequest(mctp_eid_t eid, uint8_t command, + const pldm_msg* request, size_t reqMsgLen); + + /** + * @brief Update the device completion status + * + * @param[in] eid - The MCTP EID of the device + * @param[in] status - The completion status (true for success, false for + * failure) + */ + void updateDeviceCompletion(mctp_eid_t eid, bool status) override; + + /** + * @brief Update the activation progress status + */ + void updateActivationProgress() override; + + /** + * @brief Activate the firmware update package + */ + void activatePackage() override; + + /** + * @brief Clear the activation information + */ + void resetActivationState() override; + + /** + * @brief Start the update process + * + * D-Bus method implementation for starting the update process + * + * @param[in] image The image file descriptor + * @param[in] applyTime The requested apply time + */ + virtual sdbusplus::message::object_path startUpdate( + sdbusplus::message::unix_fd image, + ApplyTimeIntf::RequestedApplyTimes applyTime = + ApplyTimeIntf::RequestedApplyTimes::Immediate) override; + + /** + * @brief Associate the firmware update package with the target device + * + * This function associates the firmware update package with the specified + * target device by matching the device ID records and descriptors. + * + * @param[in] fwDeviceIDRecords The firmware device ID records + * @param[in] descriptors The descriptors to match against + * + * @return The offset of the device ID record if found, std::nullopt + * otherwise. + */ + std::optional<DeviceIDRecordOffset> associatePkgToDevice( + const FirmwareDeviceIDRecords& fwDeviceIDRecords, + const Descriptors& descriptors); + + private: + mctp_eid_t eid; + std::string objPath; + std::string objPathWithSwId; + + /** + * @brief The descriptors to match against + */ + const Descriptors& descriptors; + + /** + * @brief The component information of the target device + */ + const ComponentInfo& componentInfo; + + /** + * @brief The package data for the firmware update + */ + std::unique_ptr<pldm::utils::MMapHandler> packageMap; + + /** + * @brief The package data stream for the firmware update + */ + std::unique_ptr<std::ispanstream> packageDataStream; + + /** + * @brief Process the firmware update package + * + * @return true on success, false on failure + */ + bool processPackage(); + + /** + * @brief Send the defer request of the firmware update package + * + * @param[in] fd - The firmware update package file descriptor + * @return The D-Bus object path of the firmware update package + */ + std::string processFd(int fd); + + std::unique_ptr<Activation> inProgressActivation; + std::unique_ptr<ActivationProgress> activationProgress; + std::unique_ptr<PackageParser> parser; + std::unique_ptr<DeviceUpdater> deviceUpdater; + decltype(std::chrono::steady_clock::now()) startTime; + + /** + * @brief The defer handler for processing package + */ + std::unique_ptr<sdeventplus::source::Defer> deferHandler; + + /** + * @brief RAII wrapper for the duplicated package file descriptor + */ + std::unique_ptr<pldm::utils::CustomFD> dupFd; + + bool updateInProgress = false; + + /** @brief The last progress that was calculated. Used to avoid spamming + * dbus + * + */ + uint8_t lastProgress; +}; + +} // namespace pldm::fw_update
diff --git a/fw-update/test/firmware_inventory_manager_test.cpp b/fw-update/test/firmware_inventory_manager_test.cpp index c7effaa..d279b35 100644 --- a/fw-update/test/firmware_inventory_manager_test.cpp +++ b/fw-update/test/firmware_inventory_manager_test.cpp
@@ -11,7 +11,7 @@ using namespace pldm::fw_update; // Helper class for testing: inherits FirmwareInventory and exposes protected -class FirmwareInventoryTest : public pldm::fw_update::FirmwareInventory +class FirmwareInventoryTestInstance : public pldm::fw_update::FirmwareInventory { public: using FirmwareInventory::FirmwareInventory; @@ -95,7 +95,7 @@ inventoryManager.getSoftwareMap().find(softwareIdentifier); ASSERT_NE(inventoryIt, inventoryManager.getSoftwareMap().end()); const auto* inventory = - static_cast<FirmwareInventoryTest*>(inventoryIt->second.get()); + static_cast<FirmwareInventoryTestInstance*>(inventoryIt->second.get()); ASSERT_NE(inventory, nullptr); EXPECT_NE(inventory->getSoftwarePath().find( "/xyz/openbmc_project/software/PLDM_Device_TestDevice_"),
diff --git a/fw-update/test/firmware_inventory_test.cpp b/fw-update/test/firmware_inventory_test.cpp index 47d8396..beb17ec 100644 --- a/fw-update/test/firmware_inventory_test.cpp +++ b/fw-update/test/firmware_inventory_test.cpp
@@ -1,10 +1,13 @@ +#include "fw-update/aggregate_update_manager.hpp" #include "fw-update/firmware_inventory.hpp" +#include "test/test_instance_id.hpp" #include <string> #include <gtest/gtest.h> using namespace pldm::fw_update; +using namespace std::chrono; class FirmwareInventoryTest : public FirmwareInventory { @@ -28,19 +31,32 @@ { SoftwareIdentifier softwareIdentifier{1, 100}; std::string expectedSoftwarePath = - "/xyz/openbmc_project/software/PLDM_Device_TestDevice_1234"; + "/xyz/openbmc_project/software/PLDM_Device_TestDevice"; + std::string expectedSoftwareHash = "1234"; std::string expectedSoftwareVersion = "2.3.4"; std::string expectedEndpointPath = "/xyz/openbmc_project/inventory/system/board/PLDM_Device"; Descriptors firmwareDescriptors; + DescriptorMap firmwareDescriptorMap{}; ComponentInfo firmwareComponentInfo; + ComponentInfoMap firmwareComponentInfoMap{}; SoftwareVersionPurpose expectedPurpose = SoftwareVersionPurpose::Unknown; - FirmwareInventoryTest inventory(softwareIdentifier, expectedSoftwarePath, - expectedSoftwareVersion, - expectedEndpointPath, expectedPurpose); + Event event(sdeventplus::Event::get_default()); + TestInstanceIdDb instanceIdDb; + requester::Handler<requester::Request> handler( + nullptr, event, instanceIdDb, false, seconds(1), 2, milliseconds(100)); - EXPECT_EQ(inventory.getSoftwarePath(), expectedSoftwarePath); + AggregateUpdateManager updateManager( + event, handler, instanceIdDb, firmwareDescriptorMap, + firmwareComponentInfoMap); + + FirmwareInventoryTest inventory( + softwareIdentifier, expectedSoftwarePath, expectedSoftwareHash, + expectedSoftwareVersion, expectedEndpointPath, expectedPurpose); + + EXPECT_EQ(inventory.getSoftwarePath(), + std::format("{}_{}", expectedSoftwarePath, expectedSoftwareHash)); auto associationTuples = inventory.getAssociation().associations(); ASSERT_FALSE(associationTuples.empty()); EXPECT_EQ(std::get<2>(associationTuples[0]), expectedEndpointPath);
diff --git a/fw-update/test/meson.build b/fw-update/test/meson.build index fd510c6..92ba092 100644 --- a/fw-update/test/meson.build +++ b/fw-update/test/meson.build
@@ -1,6 +1,4 @@ -fw_update_test_src = declare_dependency( - sources: fw_update_sources, -) +fw_update_test_src = declare_dependency(sources: fw_update_sources) tests = [ 'inventory_manager_test',
diff --git a/fw-update/update.cpp b/fw-update/update.cpp index 9113056..f113445 100644 --- a/fw-update/update.cpp +++ b/fw-update/update.cpp
@@ -23,7 +23,7 @@ } else { - updateManager->clearActivationInfo(); + updateManager->resetActivationState(); } }
diff --git a/fw-update/update_manager.cpp b/fw-update/update_manager.cpp index 3cf5fde..fae8034 100644 --- a/fw-update/update_manager.cpp +++ b/fw-update/update_manager.cpp
@@ -55,7 +55,7 @@ } else { - clearActivationInfo(); + resetActivationState(); } } @@ -311,7 +311,7 @@ } } -void UpdateManager::clearActivationInfo() +void UpdateManager::resetActivationState() { activation.reset(); activationProgress.reset();
diff --git a/fw-update/update_manager.hpp b/fw-update/update_manager.hpp index 2b896ab..d6a8924 100644 --- a/fw-update/update_manager.hpp +++ b/fw-update/update_manager.hpp
@@ -37,7 +37,44 @@ using DeviceUpdaterInfos = std::vector<DeviceUpdaterInfo>; using TotalComponentUpdates = size_t; -class UpdateManager +/** + * @brief The base class of the UpdateManager and the + * ItemBaseUpdateManager + */ +class UpdateManagerBase +{ + public: + virtual ~UpdateManagerBase() = default; + + UpdateManagerBase() = delete; + UpdateManagerBase(const UpdateManagerBase&) = delete; + UpdateManagerBase(UpdateManagerBase&&) = delete; + UpdateManagerBase& operator=(const UpdateManagerBase&) = delete; + UpdateManagerBase& operator=(UpdateManagerBase&&) = delete; + /** @brief Constructor + * + * @param[in] event - PLDM daemon's main event loop + * @param[in] handler - PLDM request handler + * @param[in] instanceIdDb - Managing instance ID for PLDM requests + */ + UpdateManagerBase( + Event& event, + pldm::requester::Handler<pldm::requester::Request>& handler, + InstanceIdDb& instanceIdDb) : + event(event), handler(handler), instanceIdDb(instanceIdDb) + {} + + virtual void updateDeviceCompletion(mctp_eid_t eid, bool status) = 0; + virtual void updateActivationProgress() = 0; + virtual void activatePackage() = 0; + virtual void resetActivationState() = 0; + + Event& event; //!< reference to PLDM daemon's main event loop + pldm::requester::Handler<pldm::requester::Request>& handler; + InstanceIdDb& instanceIdDb; //!< reference to an InstanceIdDb +}; + +class UpdateManager : public UpdateManagerBase { public: UpdateManager() = delete; @@ -52,7 +89,7 @@ pldm::requester::Handler<pldm::requester::Request>& handler, InstanceIdDb& instanceIdDb, const DescriptorMap& descriptorMap, const ComponentInfoMap& componentInfoMap) : - event(event), handler(handler), instanceIdDb(instanceIdDb), + UpdateManagerBase(event, handler, instanceIdDb), descriptorMap(descriptorMap), componentInfoMap(componentInfoMap), #ifdef FW_UPDATE_INOTIFY_ENABLED watch(event.get(), @@ -102,17 +139,17 @@ std::string processStreamDefer(std::istream& packageStream, uintmax_t packageSize); - void updateDeviceCompletion(mctp_eid_t eid, bool status); + void updateDeviceCompletion(mctp_eid_t eid, bool status) override; - void updateActivationProgress(); + void updateActivationProgress() override; /** @brief Callback function that will be invoked when the * RequestedActivation will be set to active in the Activation * interface */ - void activatePackage(); + void activatePackage() override; - void clearActivationInfo(); + void resetActivationState() override; /** @brief * @@ -129,10 +166,6 @@ static std::string getSwId(); const std::string swRootPath{"/xyz/openbmc_project/software/"}; - Event& event; //!< reference to PLDM daemon's main event loop - /** @brief PLDM request handler */ - pldm::requester::Handler<pldm::requester::Request>& handler; - InstanceIdDb& instanceIdDb; //!< reference to an InstanceIdDb std::unique_ptr<Activation> activation;
diff --git a/meson.build b/meson.build index 8e2282f..cd6f800 100644 --- a/meson.build +++ b/meson.build
@@ -261,6 +261,7 @@ 'fw-update/firmware_inventory.cpp', 'fw-update/firmware_inventory_manager.cpp', 'fw-update/inventory_manager.cpp', + 'fw-update/item_update_manager.cpp', 'fw-update/package_parser.cpp', 'fw-update/update.cpp', 'fw-update/update_manager.cpp',