From 9d6725658c538e2ae6d7b7b4b59b62f74135befb Mon Sep 17 00:00:00 2001 From: ryanhuang Date: Fri, 19 Dec 2025 14:36:58 +0800 Subject: [PATCH] rde: Add oem rde cache manager feature This feature address scenario where the Host MCU is temporarily unable to process these requests. Signed-off-by: ryanhuang --- meson.build | 7 + oem/amd/cache_manager_dbus.cpp | 118 +++++++++ oem/amd/cache_manager_dbus.hpp | 60 +++++ oem/amd/meson.build | 8 + oem/amd/rde_cache_manager.cpp | 187 ++++++++++++++ oem/amd/rde_cache_manager.hpp | 127 ++++++++++ rde/device.cpp | 451 +++++++++++++++++++++++++-------- rde/device.hpp | 109 ++++++++ rde/discov_session.cpp | 7 + rde/manager.cpp | 113 +++++---- rde/manager.hpp | 18 +- rde/operation_session.cpp | 29 --- rde/utils.cpp | 97 +++++++ rde/utils.hpp | 15 ++ 14 files changed, 1156 insertions(+), 190 deletions(-) create mode 100644 oem/amd/cache_manager_dbus.cpp create mode 100644 oem/amd/cache_manager_dbus.hpp create mode 100644 oem/amd/meson.build create mode 100644 oem/amd/rde_cache_manager.cpp create mode 100644 oem/amd/rde_cache_manager.hpp diff --git a/meson.build b/meson.build index 3c27c12e22..b5f64aa6bd 100644 --- a/meson.build +++ b/meson.build @@ -115,6 +115,7 @@ endif if get_option('oem-amd').allowed() add_project_arguments('-DOEM_AMD', language: 'cpp') endif + conf_data.set( 'NUMBER_OF_REQUEST_RETRIES', get_option('number-of-request-retries'), @@ -239,6 +240,12 @@ deps = [ ] oem_files = [] + +if get_option('oem-amd').allowed() + subdir('oem/amd') + deps += oem_amd_dep +endif + if get_option('oem-ampere').allowed() subdir('oem/ampere') endif diff --git a/oem/amd/cache_manager_dbus.cpp b/oem/amd/cache_manager_dbus.cpp new file mode 100644 index 0000000000..9f01ad0c21 --- /dev/null +++ b/oem/amd/cache_manager_dbus.cpp @@ -0,0 +1,118 @@ +#include "cache_manager_dbus.hpp" +#include "rde/device_common.hpp" +#include "rde/utils.hpp" +#include +#include +#include +#include +#include + +PHOSPHOR_LOG2_USING; + +using namespace sdbusplus::xyz::openbmc_project::Common::Error; + +namespace pldm::rde +{ + +std::vector CacheManagerObject::getCurrentCache(std::string uuid) +{ + std::vector results; + + // Get internal cache data from singleton + auto internalCache = RDECacheManager::getInstance().getCache(uuid); + + for (const auto& entry : internalCache) + { + nlohmann::json j; + j["Operation"] = sdbusplus::message::convert_to_string(entry.operationType); + j["URI"] = entry.targetURI; + j["Payload"] = entry.payload; + j["PayloadFormat"] = sdbusplus::message::convert_to_string(entry.payloadFormat); + j["EncodingFormat"] = sdbusplus::message::convert_to_string(entry.encodingType); + j["SessionId"] = entry.sessionId; + j["Timestamp"] = entry.timestamp; + j["Status"] = (entry.status == CacheStatus::Pending) ? "Pending" : "Processing"; + + results.push_back(j.dump()); + } + + return results; +} + +bool CacheManagerObject::createCache( + sdbusplus::common::xyz::openbmc_project::rde::Common::OperationType + operationType, + std::string targetURI, std::string deviceUUID, + std::string payload, + sdbusplus::xyz::openbmc_project::RDE::server::Manager::PayloadFormatType + payloadFormat, + sdbusplus::xyz::openbmc_project::RDE::server::Manager::EncodingFormatType + encodingFormat, + std::string sessionId) +{ + info("RDE Cache: createCache called for UUID={UUID}", "UUID", deviceUUID); + + // Validate deviceUUID exists in metadata + std::string processorURI = loadProcessorURI(deviceUUID); + if (processorURI.empty()) + { + error( + "RDE Cache: Device UUID={UUID} not found in rde_device_metadata.json, cannot create cache", + "UUID", deviceUUID); + throw InvalidArgument(); + } + + // Validate payload format if JSON encoding + if (encodingFormat == + sdbusplus::xyz::openbmc_project::RDE::server::Manager::EncodingFormatType:: + JSON) + { + if (!payload.empty()) + { + try + { + [[maybe_unused]] auto _ = nlohmann::json::parse(payload); + } + catch (const nlohmann::json::parse_error& e) + { + error( + "RDE Cache: Invalid JSON payload for UUID={UUID}: {MSG}", + "UUID", deviceUUID, "MSG", e.what()); + throw InvalidArgument(); + } + } + } + + // Build OperationInfo (eid will be set during replay from Device) + OperationInfo opInfo{ + 0, // operationID will be generated during replay + static_cast(operationType), + targetURI, + deviceUUID, + 0, // eid will be retrieved from Device during replay + payload, + static_cast(payloadFormat), + static_cast(encodingFormat), + sessionId, + "" // opTaskPath will be generated during replay + }; + + // Call RDECacheManager::cacheOperation() + auto& cacheManager = RDECacheManager::getInstance(); + if (!cacheManager.cacheOperation(opInfo)) + { + error("RDE Cache: Failed to cache operation for device UUID={UUID}", + "UUID", deviceUUID); + throw InternalFailure(); + } + + info( + "RDE Cache: Successfully created cache entry via D-Bus for UUID={UUID}, type={TYPE}, URI={URI}", + "UUID", deviceUUID, "TYPE", static_cast(operationType), "URI", + targetURI); + + return true; +} + +} // namespace pldm::rde + diff --git a/oem/amd/cache_manager_dbus.hpp b/oem/amd/cache_manager_dbus.hpp new file mode 100644 index 0000000000..dd91253885 --- /dev/null +++ b/oem/amd/cache_manager_dbus.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "rde_cache_manager.hpp" +#include +#include +#include + +#include +#include + +namespace pldm::rde +{ + +using CacheManagerIface = sdbusplus::server::object_t< + sdbusplus::xyz::openbmc_project::RDE::server::CacheManager>; + +class CacheManagerObject : public CacheManagerIface +{ + public: + CacheManagerObject(sdbusplus::bus_t& bus, const char* path) : + CacheManagerIface(bus, path) + {} + + /** + * @brief Implementation for GetCurrentCache + * Retrieve all cached operations for a specific RDE device. + * + * @param[in] uuid - The UUID of the device to query. + * @return Operations - List of cached operations as JSON strings. + */ + std::vector getCurrentCache(std::string uuid) override; + + /** + * @brief Implementation for CreateCache + * Manually create a cache entry for a specific RDE device. + * Parameter order matches StartRedfishOperation (excluding OperationID and EID). + * + * @param[in] operationType - Operation type + * @param[in] targetURI - Target URI + * @param[in] deviceUUID - Device UUID + * @param[in] payload - Operation payload (JSON string) + * @param[in] payloadFormat - Payload format (default: Inline) + * @param[in] encodingFormat - Encoding format (default: JSON) + * @param[in] sessionId - Session ID (optional) + * @return bool - true if successfully cached, false otherwise + */ + bool createCache( + sdbusplus::common::xyz::openbmc_project::rde::Common::OperationType + operationType, + std::string targetURI, std::string deviceUUID, + std::string payload, + sdbusplus::xyz::openbmc_project::RDE::server::Manager::PayloadFormatType + payloadFormat, + sdbusplus::xyz::openbmc_project::RDE::server::Manager::EncodingFormatType + encodingFormat, + std::string sessionId) override; +}; + +} // namespace pldm::rde + diff --git a/oem/amd/meson.build b/oem/amd/meson.build new file mode 100644 index 0000000000..217a86497c --- /dev/null +++ b/oem/amd/meson.build @@ -0,0 +1,8 @@ +oem_amd_dep = declare_dependency( + sources: files( + 'cache_manager_dbus.cpp', + 'rde_cache_manager.cpp', + ), + include_directories: include_directories('.') +) + diff --git a/oem/amd/rde_cache_manager.cpp b/oem/amd/rde_cache_manager.cpp new file mode 100644 index 0000000000..202bb134e2 --- /dev/null +++ b/oem/amd/rde_cache_manager.cpp @@ -0,0 +1,187 @@ +#include "rde_cache_manager.hpp" + +#include +#include + +#include +#include + +PHOSPHOR_LOG2_USING; + +namespace pldm::rde +{ + +RDECacheManager& RDECacheManager::getInstance() +{ + static RDECacheManager instance; + return instance; +} + +bool RDECacheManager::cacheOperation(const OperationInfo& opInfo) +{ + auto& deviceCache = cache_[opInfo.deviceUUID]; + + for (auto& entry : deviceCache) + { + if (entry.status == CacheStatus::Pending && + entry.operationType == opInfo.operationType && + entry.targetURI == opInfo.targetURI) + { + try + { + auto jExisting = nlohmann::json::parse(entry.payload); + auto jNew = nlohmann::json::parse(opInfo.payload); + + jExisting.update(jNew); + + entry.payload = jExisting.dump(); + entry.timestamp = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + info("RDE Cache: Merged payload for UUID={UUID}, URI={URI}", + "UUID", opInfo.deviceUUID, "URI", entry.targetURI); + return true; + } + catch (...) + { + break; + } + } + } + + try + { + CacheEntry entry(opInfo.deviceUUID, opInfo.operationType, + opInfo.targetURI, opInfo.payload, + opInfo.payloadFormat, opInfo.encodingType, + opInfo.sessionId); + deviceCache.push_back(std::move(entry)); + + info( + "RDE Cache: Added new cache entry for UUID={UUID}, type={TYPE}, URI={URI}, payload_size={SIZE}, total_entries={COUNT}", + "UUID", opInfo.deviceUUID, "TYPE", + static_cast(opInfo.operationType), "URI", opInfo.targetURI, + "SIZE", opInfo.payload.size(), "COUNT", deviceCache.size()); + + return true; + } + catch (const std::exception& e) + { + error("RDE Cache: Failed to cache operation for UUID={UUID}: {MSG}", + "UUID", opInfo.deviceUUID, "MSG", e.what()); + return false; + } +} + +std::optional + RDECacheManager::markNextPendingForProcessing(const std::string& deviceUUID) +{ + auto it = cache_.find(deviceUUID); + if (it == cache_.end()) + { + return std::nullopt; + } + + for (auto& entry : it->second) + { + entry.status = CacheStatus::Processing; + return entry; // Return a copy + } + + return std::nullopt; +} + +void RDECacheManager::completeOperation(const std::string& deviceUUID, + uint64_t timestamp) +{ + auto it = cache_.find(deviceUUID); + if (it == cache_.end()) + { + return; + } + + auto& deviceCache = it->second; + auto entryIt = std::find_if(deviceCache.begin(), deviceCache.end(), + [timestamp](const CacheEntry& e) { + return e.timestamp == timestamp; + }); + + if (entryIt != deviceCache.end()) + { + info("RDE Cache: Operation completed and removed from cache for UUID={UUID}, URI={URI}", + "UUID", deviceUUID, "URI", entryIt->targetURI); + deviceCache.erase(entryIt); + } +} + +void RDECacheManager::markOperationAsFailed(const std::string& deviceUUID, + uint64_t timestamp) +{ + auto it = cache_.find(deviceUUID); + if (it == cache_.end()) + { + return; + } + + auto& deviceCache = it->second; + auto entryIt = std::find_if(deviceCache.begin(), deviceCache.end(), + [timestamp](const CacheEntry& e) { + return e.timestamp == timestamp; + }); + + if (entryIt != deviceCache.end()) + { + entryIt->status = CacheStatus::Failed; + info("RDE Cache: Operation marked as failed for UUID={UUID}, URI={URI}", + "UUID", deviceUUID, "URI", entryIt->targetURI); + } +} + +void RDECacheManager::resetProcessingToPending(const std::string& deviceUUID) +{ + auto it = cache_.find(deviceUUID); + if (it == cache_.end()) + { + return; + } + + for (auto& entry : it->second) + { + if (entry.status == CacheStatus::Processing) + { + entry.status = CacheStatus::Pending; + } + } + info("RDE Cache: Reset all 'Processing' entries to 'Pending' for UUID={UUID}", + "UUID", deviceUUID); +} + +std::vector + RDECacheManager::getCache(const std::string& deviceUUID) const +{ + auto it = cache_.find(deviceUUID); + if (it != cache_.end()) + { + return it->second; + } + return {}; +} + +OperationInfo RDECacheManager::toOperationInfo(const CacheEntry& entry, + uint32_t operationID, + pldm::eid eid) +{ + std::string taskPathStr = + "/xyz/openbmc_project/RDE/OperationTask/" + std::to_string(operationID); + + OperationInfo opInfo{ + operationID, entry.operationType, entry.targetURI, + entry.deviceUUID, eid, entry.payload, + entry.payloadFormat, entry.encodingType, entry.sessionId, + taskPathStr}; + + return opInfo; +} + +} // namespace pldm::rde diff --git a/oem/amd/rde_cache_manager.hpp b/oem/amd/rde_cache_manager.hpp new file mode 100644 index 0000000000..d0c59b2a4a --- /dev/null +++ b/oem/amd/rde_cache_manager.hpp @@ -0,0 +1,127 @@ +#pragma once + +#include "rde/device_common.hpp" + +#include +#include +#include +#include +#include + +namespace pldm::rde +{ + +/** + * @enum CacheStatus + * @brief Represents the current processing state of a cache entry + */ +enum class CacheStatus +{ + Pending, // Waiting for replay + Processing, // Currently being replayed + Failed // Replay failed +}; + +/** + * @struct CacheEntry + * @brief Represents a cached RDE operation entry + */ +struct CacheEntry +{ + std::string deviceUUID; + OperationType operationType; + std::string targetURI; + std::string payload; + PayloadFormatType payloadFormat; + EncodingFormatType encodingType; + std::string sessionId; + uint64_t timestamp; // Cache creation timestamp + CacheStatus status = CacheStatus::Pending; + + CacheEntry() = default; + CacheEntry(const std::string& uuid, OperationType opType, + const std::string& uri, const std::string& pay, + PayloadFormatType pFormat, EncodingFormatType eFormat, + const std::string& sessId) : + deviceUUID(uuid), + operationType(opType), targetURI(uri), payload(pay), + payloadFormat(pFormat), encodingType(eFormat), sessionId(sessId), + timestamp(std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count()), + status(CacheStatus::Pending) + {} +}; + +/** + * @class RDECacheManager + * @brief Manages cache for failed RDE operations using UUID as the base key + */ +class RDECacheManager +{ + public: + RDECacheManager() = default; + ~RDECacheManager() = default; + RDECacheManager(const RDECacheManager&) = delete; + RDECacheManager& operator=(const RDECacheManager&) = delete; + RDECacheManager(RDECacheManager&&) = delete; + RDECacheManager& operator=(RDECacheManager&&) = delete; + + /** + * @brief Get the singleton instance of RDECacheManager + * @return Reference to the singleton instance + */ + static RDECacheManager& getInstance(); + + /** + * @brief Cache a failed RDE operation using OperationInfo + */ + bool cacheOperation(const OperationInfo& opInfo); + + /** + * @brief Get the next pending or failed operation for replay and mark it as processing + * @param[in] deviceUUID Device UUID to query + * @return The next pending or failed cache entry, or std::nullopt if none exist + */ + std::optional + markNextPendingForProcessing(const std::string& deviceUUID); + + /** + * @brief Mark an operation as completed and remove it from cache + * @param[in] deviceUUID Device UUID + * @param[in] timestamp Unique timestamp of the entry to remove + */ + void completeOperation(const std::string& deviceUUID, uint64_t timestamp); + + /** + * @brief Mark an operation as failed and keep it in cache + * @param[in] deviceUUID Device UUID + * @param[in] timestamp Unique timestamp of the entry to mark as failed + */ + void markOperationAsFailed(const std::string& deviceUUID, uint64_t timestamp); + + /** + * @brief Reset all 'Processing' entries back to 'Pending' + * @param[in] deviceUUID Device UUID + */ + void resetProcessingToPending(const std::string& deviceUUID); + + /** + * @brief Get a copy of all cached operations (including those in processing) + * @param[in] deviceUUID Device UUID to query + * @return Vector of cache entries + */ + std::vector getCache(const std::string& deviceUUID) const; + + /** + * @brief Convert CacheEntry to OperationInfo for replay + */ + static OperationInfo toOperationInfo(const CacheEntry& entry, + uint32_t operationID, pldm::eid eid); + + private: + // Map: deviceUUID -> vector of CacheEntry + std::map> cache_; +}; + +} // namespace pldm::rde diff --git a/rde/device.cpp b/rde/device.cpp index d79e838214..9fc0d4367b 100644 --- a/rde/device.cpp +++ b/rde/device.cpp @@ -1,15 +1,15 @@ #include "device.hpp" -#include -#include - #ifdef OEM_AMD -constexpr auto rdeCacheManagerService = "xyz.openbmc_project.RDE.CacheManager"; -constexpr auto rdeCacheManagerPath = "/xyz/openbmc_project/CacheManager"; -constexpr auto rdeCacheManagerInterface = - "xyz.openbmc_project.RDE.CacheManager"; +#include "manager.hpp" +#include "rde_cache_manager.hpp" +#include "utils.hpp" + +#include #endif +#include + namespace pldm::rde { Device::Device(sdbusplus::bus::bus& bus, sdeventplus::Event& event, @@ -92,165 +92,402 @@ void Device::refreshDeviceInfo() deviceUpdated(); } -#ifdef OEM_AMD -inline std::string loadProcessorURI(std::string devUUID) +void Device::performRDEOperation(const OperationInfo& oipInfo) { - constexpr const char* rdeDeviceMetadataFile = - "/etc/pldm/rde_device_metadata.json"; - - std::string schema; - std::string deviceId; + info("Operation Session Started"); - if (!std::filesystem::exists(rdeDeviceMetadataFile)) +#ifdef OEM_AMD + if (shouldDeferOperation(oipInfo)) { - error("RDE: Device metadata file {FILE} not found: ", "FILE", - rdeDeviceMetadataFile); - return ""; + return; } +#endif - std::ifstream file(rdeDeviceMetadataFile); - if (!file.is_open()) + std::shared_ptr self; + try { - error("RDE: Failed to open device metadata file:{FILE} ", "FILE", - rdeDeviceMetadataFile); - return ""; + self = shared_from_this(); + if (!self) + { + error("Device::shared_from_this() returned null shared_ptr"); + return; + } + } + catch (const std::bad_weak_ptr& e) + { + error("Device shared_from_this() failed: Msg={MSG}", "MSG", e.what()); + return; } try { - if (file.peek() == std::ifstream::traits_type::eof()) + opSession_ = std::make_unique(self, oipInfo); + if (!opSession_) { - error("RDE: Device metadata file{FILE} is empty: ", "FILE", - rdeDeviceMetadataFile); - return ""; + error("OperationSession creation failed"); + return; } - nlohmann::json jsonData; - file >> jsonData; - - if (!jsonData.is_object()) + info("Operation is in progress"); + opSession_->doOperationInit(); + } + catch (const std::exception& e) + { + error("OperationSession setup failed: Msg={MSG}", "MSG", e.what()); +#ifdef OEM_AMD + // Cache failed operations (skip replayed operations to avoid infinite + // loop) + if (!isReplayOperation(oipInfo) && canCacheOperation(oipInfo)) { - error( - "RDE: Device metadata file does not contain a valid JSON object."); - return ""; + cacheOperation(oipInfo, "failed"); } +#endif + return; + } +} - for (const auto& [jsonSchema, deviceEntries] : jsonData.items()) +#ifdef OEM_AMD +bool Device::shouldDeferOperation(const OperationInfo& oipInfo) +{ + if (isReplayOperation(oipInfo)) + { + return false; + } + + if (isReplayInProgress_) + { + if (canCacheOperation(oipInfo)) { - if (!deviceEntries.is_object()) - { - error("RDE: Invalid schema section {SCHEMA} ", "SCHEMA", - jsonSchema); - continue; - } - - for (const auto& [jsonDeviceId, deviceInfo] : deviceEntries.items()) - { - if (!deviceInfo.contains("UUIDs") || - !deviceInfo["UUIDs"].is_array()) + cacheOperation(oipInfo, "during replay"); + return true; // Indicate that it has been deferred + } + } + return false; +} + +bool Device::isReplayOperation(const OperationInfo& opInfo) const +{ + return (currentReplayOperationId_ != 0 && + opInfo.operationID == currentReplayOperationId_); +} + +bool Device::canCacheOperation(const OperationInfo& opInfo) const +{ + std::string processorURI = loadProcessorURI(opInfo.deviceUUID); + if (processorURI.empty()) + { + info( + "RDE Cache: Device UUID={UUID} not found in rde_device_metadata.json, skipping cache", + "UUID", opInfo.deviceUUID); + return false; + } + + // Don't cache BIOS zero length command + if (opInfo.targetURI == SocConfigurationTokenURI && + opInfo.payload.empty() && opInfo.operationType == OperationType::UPDATE) + { + return false; + } + + return true; +} + +bool Device::cacheOperation(const OperationInfo& opInfo, + const std::string& context) +{ + auto& cacheManager = RDECacheManager::getInstance(); + if (!cacheManager.cacheOperation(opInfo)) + { + error("RDE Cache: Failed to cache operation for device UUID={UUID}", + "UUID", opInfo.deviceUUID); + return false; + } + + if (isReplayInProgress_) + { + info( + "RDE Cache: Cached operation {CONTEXT} for device UUID={UUID}, EID={EID} (will be processed after replay completes)", + "CONTEXT", context, "UUID", opInfo.deviceUUID, "EID", opInfo.eid); + } + else + { + info( + "RDE Cache: Successfully cached {CONTEXT} operation for device UUID={UUID}, EID={EID}", + "CONTEXT", context, "UUID", opInfo.deviceUUID, "EID", opInfo.eid); + } + return true; +} + +void Device::replayCachedOperations() +{ + if (isReplayInProgress_) + { + return; + } + + isReplayInProgress_ = true; + + // Setup signal match to listen for TaskUpdated signals + if (!taskUpdatedMatch_) + { + std::weak_ptr weakSelf = shared_from_this(); + taskUpdatedMatch_ = std::make_unique( + bus_, + sdbusplus::bus::match::rules::type::signal() + + sdbusplus::bus::match::rules::member("TaskUpdated") + + sdbusplus::bus::match::rules::interface( + "xyz.openbmc_project.RDE.OperationTask"), + [weakSelf](sdbusplus::message::message& msg) { + auto self = weakSelf.lock(); + if (!self) { - error( - "RDE: Missing or invalid 'UUIDs' for {SCHEMA} {DEVID}", - "SCHEMA", jsonSchema, "DEVID", jsonDeviceId); - continue; + return; } - const std::string deviceKeyFromJson = - jsonSchema + "/" + jsonDeviceId + "/"; + std::string path = msg.get_path(); - for (const auto& uuid : deviceInfo["UUIDs"]) + if (self->currentReplayOperationId_ == 0) { - if (!uuid.is_string()) + return; + } + + std::string expectedPath = + "/xyz/openbmc_project/RDE/OperationTask/" + + std::to_string(self->currentReplayOperationId_); + if (path != expectedPath) + { + return; + } + + // Extract return code from signal + std::map> + changed; + msg.read(changed); + + auto it = changed.find("return_code"); + if (it == changed.end()) + { + return; + } + + uint16_t returnCode = std::get(it->second); + + if (returnCode == + static_cast(OpState::OperationCompleted)) + { + // Success: delete cache entry + if (self->currentReplayTimestamp_ != 0) { - error("RDE: Invalid UUID format in {KEY}", "KEY", - deviceKeyFromJson); - continue; + RDECacheManager::getInstance().completeOperation( + self->deviceUUID(), self->currentReplayTimestamp_); + self->currentReplayTimestamp_ = 0; } - if (devUUID == uuid.get()) + info( + "RDE Cache Replay: Operation {OID} succeeded (TaskStatus=OperationCompleted), cache entry removed, processing next operation", + "OID", self->currentReplayOperationId_); + + self->currentReplayOperationId_ = 0; + self->processNextCachedOperation(); + } + else if (returnCode == + static_cast(OpState::OperationFailed) || + returnCode == + static_cast(OpState::Cancelled) || + returnCode == static_cast(OpState::TimedOut)) + { + // Failure: mark as failed and continue to next + if (self->currentReplayTimestamp_ != 0) { - return deviceKeyFromJson; + RDECacheManager::getInstance().markOperationAsFailed( + self->deviceUUID(), self->currentReplayTimestamp_); + self->currentReplayTimestamp_ = 0; } + info( + "RDE Cache Replay: Operation {OID} failed with TaskStatus={CODE}, marked as failed, processing next operation", + "OID", self->currentReplayOperationId_, "CODE", + returnCode); + + self->currentReplayOperationId_ = 0; + self->processNextCachedOperation(); } - } - } - } - catch (const std::exception& e) - { - error("RDE: Unexpected error while reading device metadata file:{MSG} ", - "MSG", e.what()); - return ""; + else + { + info( + "RDE Cache Replay: Operation {OID} status updated to {CODE}, waiting for final state", + "OID", self->currentReplayOperationId_, "CODE", + returnCode); + } + }); } - return ""; + // Start processing the first cache entry + processNextCachedOperation(); } -#endif -void Device::performRDEOperation(const OperationInfo& oipInfo) +void Device::processNextCachedOperation() { - info("Operation Session Started"); + auto& cacheManager = RDECacheManager::getInstance(); + auto entryOpt = cacheManager.markNextPendingForProcessing(deviceUUID()); + + if (!entryOpt.has_value()) + { + // No more pending cached operations, replay is complete + info( + "RDE Cache Replay: All cached operations processed for UUID={UUID}, sending BIOS zero length command", + "UUID", deviceUUID()); + currentReplayOperationId_ = 0; + currentReplayTimestamp_ = 0; + isReplayInProgress_ = false; + sendBiosZeroLengthCommand(); + return; + } + + CacheEntry entry = *entryOpt; + currentReplayTimestamp_ = entry.timestamp; + + info( + "RDE Cache Replay: Replaying operation type={TYPE}, URI={URI}, payload={PAYLOAD}, timestamp={TS}", + "TYPE", static_cast(entry.operationType), "URI", entry.targetURI, + "PAYLOAD", entry.payload, "TS", entry.timestamp); - std::shared_ptr self; try { - self = shared_from_this(); - if (!self) + if (!manager_) { - error("Device::shared_from_this() returned null shared_ptr"); + error( + "RDE Cache Replay: Manager not set, cannot generate operation ID, stopping replay for UUID={UUID}", + "UUID", deviceUUID()); + stopReplay(); return; } + + uint32_t operationID = manager_->getNextAvailableOperationId(); + if (operationID == 0) + { + error( + "RDE Cache Replay: Failed to generate operation ID (all IDs in use), stopping replay for UUID={UUID}", + "UUID", deviceUUID()); + stopReplay(); + return; + } + + currentReplayOperationId_ = operationID; + + OperationInfo opInfo = + RDECacheManager::toOperationInfo(entry, operationID, eid()); + + auto task = std::make_shared(bus_, opInfo.opTaskPath); + manager_->registerOperationTask(operationID, task); + performRDEOperation(opInfo); } - catch (const std::bad_weak_ptr& e) + catch (const std::exception& e) { - error("Device shared_from_this() failed: Msg={MSG}", "MSG", e.what()); + error( + "RDE Cache Replay: Failed to replay operation for UUID={UUID}: {MSG}, continuing with next", + "UUID", deviceUUID(), "MSG", e.what()); + + // Mark failed-to-start operation as complete so we can move to next + cacheManager.completeOperation(deviceUUID(), currentReplayTimestamp_); + currentReplayOperationId_ = 0; + currentReplayTimestamp_ = 0; + processNextCachedOperation(); + } +} + +void Device::stopReplay() +{ + RDECacheManager::getInstance().resetProcessingToPending(deviceUUID()); + currentReplayOperationId_ = 0; + currentReplayTimestamp_ = 0; + isReplayInProgress_ = false; +} + +void Device::sendBiosZeroLengthCommand() +{ + // Check if UUID exists in rde_device_metadata.json + std::string processorURI = loadProcessorURI(deviceUUID()); + if (processorURI.empty()) + { + info( + "RDE Cache Replay: Device UUID={UUID} not found in rde_device_metadata.json, skipping BIOS zero length command", + "UUID", deviceUUID()); + return; + } + + // Send BIOS zero length command when cache replay is complete + if (!manager_) + { + error( + "RDE Cache Replay: Manager not set, cannot send BIOS zero length command for UUID={UUID}", + "UUID", deviceUUID()); return; } try { - opSession_ = std::make_unique(self, oipInfo); - if (!opSession_) + uint32_t operationID = manager_->getNextAvailableOperationId(); + if (operationID == 0) { - error("OperationSession creation failed"); + error( + "RDE Cache Replay: Failed to generate operation ID for BIOS zero length command, UUID={UUID}", + "UUID", deviceUUID()); return; } - info("Operation is in progress"); - opSession_->doOperationInit(); - } - catch (const std::exception& e) - { - error("OperationSession setup failed: Msg={MSG}", "MSG", e.what()); -#ifdef OEM_AMD - auto& bus = pldm::utils::DBusHandler::getBus(); - auto method = - bus.new_method_call(rdeCacheManagerService, rdeCacheManagerPath, - rdeCacheManagerInterface, "CreateCache"); - - const std::string redfishRootURI = "/redfish/v1/Systems/system/"; - const std::string redfishProcessorURI = - loadProcessorURI(oipInfo.deviceUUID); - if (redfishProcessorURI.empty()) + OperationType operationType = OperationType::UPDATE; + std::string subURI = SocConfigurationTokenURI; + std::string payload = ""; + PayloadFormatType payloadFormat = PayloadFormatType::Inline; + EncodingFormatType encodingType = EncodingFormatType::JSON; + std::string sessionID = manager_->getJSONSchema(); + if (sessionID.empty()) { - error("Caching failed. Unable to find Processor URI"); + error( + "RDE Cache Replay: Cannot find session ID for BIOS zero length command, UUID={UUID}", + "UUID", deviceUUID()); return; } - const std::string fullURI = - redfishRootURI + redfishProcessorURI + oipInfo.targetURI; + std::string taskPathStr = "/xyz/openbmc_project/RDE/OperationTask/" + + std::to_string(operationID); - if (oipInfo.operationType == OperationType::UPDATE) - method.append(deviceUUID(), "patch", fullURI, oipInfo.payload); - else + OperationInfo opInfo{operationID, operationType, subURI, + deviceUUID(), eid(), payload, + payloadFormat, encodingType, sessionID, + taskPathStr}; + + auto task = std::make_shared(bus_, opInfo.opTaskPath); + manager_->registerOperationTask(operationID, task); + + std::shared_ptr self = shared_from_this(); + opSession_ = std::make_unique(self, opInfo); + if (!opSession_) + { + error( + "RDE Cache Replay: Failed to create OperationSession for BIOS zero length command, UUID={UUID}", + "UUID", deviceUUID()); return; + } - info("Caching the data for RDE Device of EID={EID} and UUID={UUID}", - "EID", oipInfo.eid, "UUID", oipInfo.deviceUUID); - auto reply = bus.call(method, dbusTimeout); -#endif - return; + info( + "RDE Cache Replay: Sending BIOS zero length command for UUID={UUID}, OperationID={OID}", + "UUID", deviceUUID(), "OID", operationID); + opSession_->doOperationInit(); + } + catch (const std::exception& e) + { + error( + "RDE Cache Replay: Failed to send BIOS zero length command for UUID={UUID}: {MSG}", + "UUID", deviceUUID(), "MSG", e.what()); } } +void Device::setManager(Manager* manager) +{ + manager_ = manager; +} +#endif + Metadata& Device::getMetadata() { return metaData_; diff --git a/rde/device.hpp b/rde/device.hpp index 358657ce59..17a15714bb 100644 --- a/rde/device.hpp +++ b/rde/device.hpp @@ -5,6 +5,10 @@ #include "discov_session.hpp" #include "operation_session.hpp" #include "resource_registry.hpp" +#ifdef OEM_AMD +#include "operation_task.hpp" +#include "rde_cache_manager.hpp" +#endif #include "xyz/openbmc_project/Common/UUID/server.hpp" #include "xyz/openbmc_project/RDE/Common/common.hpp" #include "xyz/openbmc_project/RDE/Device/server.hpp" @@ -30,6 +34,7 @@ namespace pldm::rde { +class Manager; // Forward declaration using VariantValue = std::variant; using PropertyMap = std::map; using SchemaResourcesType = std::map; @@ -83,6 +88,90 @@ class Device : public EntryIfaces, public std::enable_shared_from_this void performRDEOperation(const OperationInfo& opInfo); +#ifdef OEM_AMD + /** + * @brief Check if the operation should be deferred (cached) instead of + * executed. + * @param opInfo Operation information + * @return true if the operation was deferred, false otherwise + */ + bool shouldDeferOperation(const OperationInfo& opInfo); + + /** + * @brief Check if the operation is being replayed + * @param opInfo Operation information + * @return true if this is a replayed operation + */ + bool isReplayOperation(const OperationInfo& opInfo) const; + + /** + * @brief Check if operation can be cached (validates type, UUID, etc.) + * @param opInfo Operation information + * @return true if operation can be cached + */ + bool canCacheOperation(const OperationInfo& opInfo) const; + + /** + * @brief Cache the operation + * @param opInfo Operation information + * @param context Context string for logging (e.g., "during replay" or + * "failed") + * @return true if successfully cached + */ + bool cacheOperation(const OperationInfo& opInfo, + const std::string& context); + + /** + * @brief Start replaying cached operations for this device + * + * This method initializes the cache replay queue and starts replaying + * cached operations asynchronously. Each operation is replayed one at a + * time, and the next operation is processed after the current one completes + * (success or failure). + * + * Should be called after negotiation is successful. + * Cache entries are automatically cleared after being replayed. + */ + void replayCachedOperations(); + + /** + * @brief Process the next cached operation in the queue + * + * This method is called when the current operation completes (success or + * failure). It processes the next cache entry in the queue and clears the + * completed one. + */ + void processNextCachedOperation(); + + /** + * @brief Set the Manager reference for operation ID generation + * + * This method sets the Manager reference so that Device can use + * the shared operation ID generator with conflict detection. + * + * @param[in] manager Pointer to the Manager instance + */ + void setManager(Manager* manager); + + /** + * @brief Stop cache replay and reset all replay state + * + * Clears the replay queue, resets current operation ID, and clears + * the replay in progress flag. Used when replay must be stopped due to + * errors. + */ + void stopReplay(); + + /** + * @brief Send BIOS zero length command (RDEReplayComplete operationInit) + * + * This method sends the BIOS zero length command when cache replay + * is complete or when there are no cached operations to replay. + * Only sends if the device UUID is found in rde_device_metadata.json. + */ + void sendBiosZeroLengthCommand(); +#endif + /** * @brief Access the device metadata. * @return Reference to metadata. @@ -206,6 +295,14 @@ class Device : public EntryIfaces, public std::enable_shared_from_this } private: +#ifdef OEM_AMD + /** + * @brief URI for SocConfiguration/Token + */ + static constexpr const char* SocConfigurationTokenURI = + "Oem/AMD/SocConfiguration/Token"; +#endif + /** * @brief Constructs schema resource payload based on discovered resources. * @@ -234,6 +331,18 @@ class Device : public EntryIfaces, public std::enable_shared_from_this std::unique_ptr dictionaryManager_; std::unique_ptr discovSession_; std::unique_ptr opSession_; +#ifdef OEM_AMD + // Current operation ID being replayed + uint32_t currentReplayOperationId_ = 0; + // Current operation timestamp being replayed (used as key for completion) + uint64_t currentReplayTimestamp_ = 0; + // Flag to track if cache replay is in progress + bool isReplayInProgress_ = false; + // Manager reference for shared operation ID generation + Manager* manager_ = nullptr; + // Signal match for TaskUpdated to track operation completion + std::unique_ptr taskUpdatedMatch_; +#endif }; } // namespace pldm::rde diff --git a/rde/discov_session.cpp b/rde/discov_session.cpp index 29d451241a..ce779329b2 100644 --- a/rde/discov_session.cpp +++ b/rde/discov_session.cpp @@ -4,6 +4,9 @@ #include "device.hpp" #include "dictionary_manager.hpp" #include "requester/handler.hpp" +#ifdef OEM_AMD +#include "rde_cache_manager.hpp" +#endif extern "C" { @@ -353,6 +356,10 @@ void DiscoverySession::runNextDictionaryCommand(size_t index) info("RDE: All schema dictionary commands completed."); // Update negotiation status device_->negotiationStatus(device_->NegotiationStatus::Success, false); +#ifdef OEM_AMD + info("RDE: Discovery completed successfully, triggering cache replay for UUID={UUID}", "UUID", device_->deviceUUID()); + device_->replayCachedOperations(); +#endif return; } diff --git a/rde/manager.cpp b/rde/manager.cpp index 61ebc15729..9c27cd3f74 100644 --- a/rde/manager.cpp +++ b/rde/manager.cpp @@ -29,6 +29,12 @@ Manager::Manager(sdbusplus::bus::bus& bus, sdeventplus::Event& event, objManager_ = std::make_unique(bus_, DeviceObjectPath); +#ifdef OEM_AMD + // Initialize Cache Manager D-Bus object + cacheManagerObj_ = std::make_unique( + bus_, "/xyz/openbmc_project/RDE/CacheManager"); +#endif + // match for all RDEDeviceDetected signals signalMatch_ = std::make_unique( bus_, @@ -48,11 +54,27 @@ Manager::Manager(sdbusplus::bus::bus& bus, sdeventplus::Event& event, "UUID", devUUID, "EID", static_cast(signalEid), "TID", static_cast(signalTid)); - if (!eidMap_.count(signalEid)) + auto it = eidMap_.find(signalEid); + if (it == eidMap_.end()) { this->createDeviceDbusObject(signalEid, devUUID, signalTid, pdrPayloads); } +#ifdef OEM_AMD + else + { + auto& context = it->second; + + if (context.uuid == devUUID && context.devicePtr) + { + info( + "RDE: Same device detected (UUID={UUID}, EID={EID}), refreshing to enable cache replay", + "UUID", devUUID, "EID", static_cast(signalEid)); + + context.devicePtr->refreshDeviceInfo(); + } + } +#endif }); } @@ -76,6 +98,10 @@ void Manager::createDeviceDbusObject( std::make_shared(bus_, event_, path, instanceIdDb_, handler_, devEID, tid, devUUID, pdrPayloads); +#ifdef OEM_AMD + devicePtr->setManager(this); +#endif + DeviceContext context; context.uuid = devUUID; context.deviceEID = devEID; @@ -90,64 +116,47 @@ void Manager::createDeviceDbusObject( eidMap_[devEID] = std::move(context); devicePtr->refreshDeviceInfo(); -#ifdef OEM_AMD - // match for all RDEReplayComplete signals - cacheCompleteSignal_ = std::make_unique( - bus_, - sdbusplus::bus::match::rules::type::signal() + - sdbusplus::bus::match::rules::member("RDEReplayComplete") + - sdbusplus::bus::match::rules::interface( - "xyz.openbmc_project.RDE.CacheManager") + - sdbusplus::bus::match::rules::path( - "/xyz/openbmc_project/CacheManager"), - [this](sdbusplus::message::message& msg) { - pldm::UUID devUUID; - pldm::eid devEid; +} - msg.read(devUUID); - devEid = getEidFromUuid(devUUID); - if (devEid == INVALID_EID) - { - error( - "RDEReplayComplete: Erro no matching EID found for UUID '{UUID}'", - "UUID", devUUID); - return; - } +#ifdef OEM_AMD +uint32_t Manager::getNextAvailableOperationId() +{ + uint32_t operationId = 1; + const uint32_t startId = operationId; - uint32_t operationID = nextOperationId(); - OperationType operationType = OperationType::UPDATE; - std::string subURI = "Oem/AMD/SocConfiguration/Token"; - std::string payload = ""; - PayloadFormatType payloadFormat = PayloadFormatType::Inline; - EncodingFormatType encodingType = EncodingFormatType::JSON; - std::string sessionID = getJSONSchema(); - if (sessionID.empty()) - { - error("RDEReplayComplete: Cannot find session ID"); - return; - } + // Find the next available operation ID by checking taskMap_ + // Start from 1 and increment until we find an ID that's not in use + while (taskMap_.find(operationId) != taskMap_.end()) + { + operationId++; - std::string taskPathStr = - "/xyz/openbmc_project/RDE/OperationTask/" + std::to_string(1); - ObjectPath objPath{taskPathStr}; + // Prevent infinite loop: if we've wrapped around to the start ID, + // it means all IDs are in use (extremely unlikely in practice) + if (operationId == 0 || operationId == startId) + { + error( + "RDE: All operation IDs are in use (checked {COUNT} tasks), cannot generate new ID", + "COUNT", taskMap_.size()); + // Return 0 as error indicator (caller should handle this) + return 0; + } + } - OperationInfo opInfo{operationID, operationType, subURI, - devUUID, devEid, payload, - payloadFormat, encodingType, sessionID, - taskPathStr}; + info( + "RDE: Generated next available operationID={OID} (checked {COUNT} existing tasks)", + "OID", operationId, "COUNT", taskMap_.size()); - opSession_ = std::make_unique( - eidMap_[devEid].devicePtr, opInfo); - if (!opSession_) - { - error("RDEReplayComplete: Failed to send zero length request"); - return; - } + return operationId; +} - opSession_->doOperationInit(); - }); -#endif +void Manager::registerOperationTask(uint32_t operationID, + std::shared_ptr task) +{ + taskMap_[operationID] = task; + info("RDE: Registered OperationTask with operationID={OID}", "OID", + operationID); } +#endif DeviceContext* Manager::getDeviceContext(eid devEID) { diff --git a/rde/manager.hpp b/rde/manager.hpp index c7bc8db4fb..30f974c5fa 100644 --- a/rde/manager.hpp +++ b/rde/manager.hpp @@ -2,6 +2,7 @@ #ifdef OEM_AMD #include "operation_session.hpp" +#include "cache_manager_dbus.hpp" #endif #include "operation_task.hpp" #include "requester/handler.hpp" @@ -241,6 +242,20 @@ class Manager : pldm_tid_t tid, const PdrPayloadList& pdrPayloads); + /** + * @brief Register an operation task with the manager. + * @param[in] operationID - Unique identifier for the operation. + * @param[in] task - Pointer to the OperationTask instance. + */ + void registerOperationTask(uint32_t operationID, + std::shared_ptr task); + + /** + * @brief Get the next available operation ID. + * @return uint32_t - The next available operation ID, or 0 if none available. + */ + uint32_t getNextAvailableOperationId(); + /** * @brief Retrieve the map of currently active OperationTask D-Bus objects. * @@ -364,8 +379,7 @@ class Manager : taskMap_; std::unique_ptr objManager_; #ifdef OEM_AMD - std::unique_ptr cacheCompleteSignal_; - std::unique_ptr opSession_; + std::unique_ptr cacheManagerObj_; #endif }; diff --git a/rde/operation_session.cpp b/rde/operation_session.cpp index 215b1cd0d3..29ed59fdc9 100644 --- a/rde/operation_session.cpp +++ b/rde/operation_session.cpp @@ -18,13 +18,6 @@ PHOSPHOR_LOG2_USING; constexpr uint32_t maxBufferSize = 64 * 1024; constexpr uint8_t CONTAINS_REQ_PAYLOAD = 1; -#ifdef OEM_AMD -constexpr auto rdeCacheManagerService = "xyz.openbmc_project.RDE.CacheManager"; -constexpr auto rdeCacheManagerPath = "/xyz/openbmc_project/CacheManager"; -constexpr auto rdeCacheManagerInterface = - "xyz.openbmc_project.RDE.CacheManager"; -#endif - namespace pldm::rde { @@ -256,20 +249,6 @@ std::string OperationSession::getJsonStrPayload() return decoder.getOutput(); } -#ifdef OEM_AMD -void emitCacheConsumedSignal(std::string matchString, std::string deviceUUID) -{ - auto& bus = pldm::utils::DBusHandler::getBus(); - auto method = - bus.new_method_call(rdeCacheManagerService, rdeCacheManagerPath, - rdeCacheManagerInterface, "RegisterSignal"); - - method.append(deviceUUID, matchString); - - auto reply = bus.call(method, dbusTimeout); -} -#endif - void OperationSession::doOperationInit() { auto instanceId = device_->getInstanceIdDb().next(eid_); @@ -577,10 +556,6 @@ void OperationSession::handleOperationInitResp(const pldm_msg* respMsg, { info("Multipartsend completed"); multiPartTransferFlag = false; -#ifdef OEM_AMD - emitCacheConsumedSignal(oipInfo.opTaskPath, - oipInfo.deviceUUID); -#endif emitTaskUpdatedSignal( device_->getBus(), oipInfo.opTaskPath, "", static_cast( @@ -612,10 +587,6 @@ void OperationSession::handleOperationInitResp(const pldm_msg* respMsg, "RID", currentResourceId_, "ERR", ex.what()); } } -#ifdef OEM_AMD - if (!oipInfo.payload.empty()) - emitCacheConsumedSignal(oipInfo.opTaskPath, oipInfo.deviceUUID); -#endif emitTaskUpdatedSignal( device_->getBus(), oipInfo.opTaskPath, "", static_cast(OpState::OperationCompleted)); diff --git a/rde/utils.cpp b/rde/utils.cpp index 2628acfe81..6d20d702aa 100644 --- a/rde/utils.cpp +++ b/rde/utils.cpp @@ -1,5 +1,11 @@ #include "utils.hpp" +#ifdef OEM_AMD +#include +#include +#include +#endif + namespace pldm::rde { void logCompletionCodeError(uint8_t cc) @@ -70,4 +76,95 @@ void logHexPayload(const std::vector& payload) } lg2::info("Payload HEX dump: {BYTES}", "BYTES", oss.str()); } + +#ifdef OEM_AMD +std::string loadProcessorURI(const std::string& devUUID) +{ + constexpr const char* rdeDeviceMetadataFile = + "/etc/pldm/rde_device_metadata.json"; + + if (!std::filesystem::exists(rdeDeviceMetadataFile)) + { + error("RDE: Device metadata file {FILE} not found: ", "FILE", + rdeDeviceMetadataFile); + return ""; + } + + std::ifstream file(rdeDeviceMetadataFile); + if (!file.is_open()) + { + error("RDE: Failed to open device metadata file:{FILE} ", "FILE", + rdeDeviceMetadataFile); + return ""; + } + + try + { + if (file.peek() == std::ifstream::traits_type::eof()) + { + error("RDE: Device metadata file{FILE} is empty: ", "FILE", + rdeDeviceMetadataFile); + return ""; + } + + nlohmann::json jsonData; + file >> jsonData; + + if (!jsonData.is_object()) + { + error( + "RDE: Device metadata file does not contain a valid JSON object."); + return ""; + } + + for (const auto& [jsonSchema, deviceEntries] : jsonData.items()) + { + if (!deviceEntries.is_object()) + { + error("RDE: Invalid schema section {SCHEMA} ", "SCHEMA", + jsonSchema); + continue; + } + + for (const auto& [jsonDeviceId, deviceInfo] : deviceEntries.items()) + { + if (!deviceInfo.contains("UUIDs") || + !deviceInfo["UUIDs"].is_array()) + { + error( + "RDE: Missing or invalid 'UUIDs' for {SCHEMA} {DEVID}", + "SCHEMA", jsonSchema, "DEVID", jsonDeviceId); + continue; + } + + const std::string deviceKeyFromJson = + jsonSchema + "/" + jsonDeviceId + "/"; + + for (const auto& uuid : deviceInfo["UUIDs"]) + { + if (!uuid.is_string()) + { + error("RDE: Invalid UUID format in {KEY}", "KEY", + deviceKeyFromJson); + continue; + } + if (devUUID == uuid.get()) + { + return deviceKeyFromJson; + } + } + } + } + } + catch (const std::exception& e) + { + error("RDE: Unexpected error while reading device metadata file:{MSG} ", + "MSG", e.what()); + return ""; + } + + return ""; +} +#endif + } // namespace pldm::rde diff --git a/rde/utils.hpp b/rde/utils.hpp index 9c087bf14f..afcc8fc15d 100644 --- a/rde/utils.hpp +++ b/rde/utils.hpp @@ -38,4 +38,19 @@ void logCompletionCodeError(uint8_t cc); */ void logHexPayload(const std::vector& payload); +#ifdef OEM_AMD +/** + * @brief Load processor URI from device metadata file + * + * This function reads the RDE device metadata file and looks up the processor + * URI for a given device UUID. The processor URI is used to identify devices + * that support caching operations. + * + * @param[in] devUUID Device UUID to lookup + * @return Processor URI string in format "schema/deviceId/", or empty string + * if not found + */ +std::string loadProcessorURI(const std::string& devUUID); +#endif + } // namespace pldm::rde