pldmd: Implement Marvell OEM crash dump extraction and dynamic MCTP discovery This commit introduces the necessary proprietary handling for Marvell Iliad endpoint topologies, bridging the gap between standard OpenBMC PLDM infrastructure and the CodeConstruct native AF_MCTP stack. It extends the Requester (platform-mc) subsystem to securely track volatile CodeConstruct endpoints and aggressively process OEM telemetry delivery mechanisms. Key architectural additions: 1. Dynamic MCTPReactor Integration (`pldmd.cpp`) Standard OpenBMC `pldmd` generically waits for `MCTP.Endpoint` signals. This introduces active startup sweeps and asynchronous DBus matchers specifically targeting `/au/com/codeconstruct/mctp1/` topologies. It guarantees `pldmd` will autonomously discover and register endpoints physically routed through the `MCTP_0` interface, inherently overcoming boot race conditions where endpoints initialize prior to the daemon. 2. OEM Crash Dump Polling Overrides (`event_manager.cpp`) Because Marvell firmware bypasses standard asynchronous `PlatformEventMessage` mechanisms for crash telemetry, the BMC is forced to actively extract the payload chunk-by-chunk. This overrides the generic `pollForPlatformEventMessage` loop to immediately intercept and hijack OEM Event Classes (0xFA through 0xFE), safely flushing the disjointed payload directly to /var/run/pldm/event/repo/. 3. Synchronous FaultLog Orchestration To unify Marvell's custom PLDM file transfers with the broader OpenBMC diagnostic ecosystem, a synchronous hook was injected directly into `saveOemCrashDump`. Immediately after the chunked payload is completely flushed to disk, this triggers `xyz.openbmc_project.Dump.Create` on the DBus (flagged as "Crashdump"). This ensures `phosphor-dump-manager` is instantly alerted to ingest the file upon transfer completion without requiring external file-watcher scripts. Change-Id: Idac52c7f97e68a5be3c384259c4e23913411e4ed Signed-off-by: Vikram Gara <vikramgara@google.com>
diff --git a/meson.build b/meson.build index a3bd07b..53f24dd 100644 --- a/meson.build +++ b/meson.build
@@ -49,6 +49,10 @@ if get_option('mctp-reactor').allowed() add_project_arguments('-DMCTP_REACTOR', language: 'cpp') endif +if get_option('sensor-polling').allowed() + conf_data.set('SENSOR_POLLING', 1) +endif + if get_option('libpldmresponder').allowed() conf_data.set_quoted('BIOS_JSONS_DIR', join_paths(package_datadir, 'bios')) conf_data.set( @@ -252,6 +256,7 @@ subdir('oem/ampere') endif + if get_option('libpldmresponder').allowed() subdir('libpldmresponder') deps += [libpldmresponder_dep]
diff --git a/meson.options b/meson.options index a95afaa..d7c8772 100644 --- a/meson.options +++ b/meson.options
@@ -252,3 +252,10 @@ description: 'Force using libpldm subproject wrap instead of system dependency' ) + +option( + 'sensor-polling', + type: 'feature', + value: 'enabled', + description: 'Enable numeric sensor polling in platform-mc', +)
diff --git a/platform-mc/event_manager.cpp b/platform-mc/event_manager.cpp index 4697b41..5fcbfb7 100644 --- a/platform-mc/event_manager.cpp +++ b/platform-mc/event_manager.cpp
@@ -9,7 +9,11 @@ #include <xyz/openbmc_project/Logging/Entry/server.hpp> #include <cerrno> +#include <filesystem> +#include <fstream> +#include <iomanip> #include <memory> +#include <sstream> PHOSPHOR_LOG2_USING; @@ -19,6 +23,90 @@ { namespace fs = std::filesystem; +static void saveOemCrashDump(pldm_tid_t tid, uint8_t eventClass, + const std::vector<uint8_t>& eventMessage) +{ + std::string coreName; + switch (eventClass) + { + case 0xFA: + coreName = "CCP"; + break; + case 0xFB: + coreName = "SCP"; + break; + case 0xFC: + coreName = "MCP"; + break; + case 0xFD: + coreName = "PCP"; + break; + case 0xFE: + coreName = "AP"; + break; + default: + return; + } + + auto now = std::chrono::system_clock::now(); + std::time_t time = std::chrono::system_clock::to_time_t(now); + std::tm tm = *std::gmtime(&time); + + std::stringstream ss; + ss << "CRASH_DUMP_" << coreName << "_TID" << static_cast<unsigned>(tid) + << "_" << std::put_time(&tm, "%Y%m%d_%H%M%S"); + + std::string filename = ss.str(); + std::string dirPath = "/var/run/pldm/event/repo/"; + + std::error_code ec; + fs::create_directories(dirPath, ec); + + std::ofstream ofs(dirPath + filename, std::ios::binary); + if (!ofs) + { + lg2::error( + "Failed to open file {FILE} for OEM crash dump from TID {TID}", + "FILE", dirPath + filename, "TID", tid); + return; + } + + ofs.write(reinterpret_cast<const char*>(eventMessage.data()), + eventMessage.size()); + lg2::info("Saved OEM crash dump from TID {TID} to {FILE}, size: {SIZE}", + "TID", tid, "FILE", dirPath + filename, "SIZE", + eventMessage.size()); + + try + { + auto bus = sdbusplus::bus::new_default(); + auto method = bus.new_method_call( + "xyz.openbmc_project.Dump.Manager", + "/xyz/openbmc_project/dump/faultlog", + "xyz.openbmc_project.Dump.Create", "CreateDump"); + std::map<std::string, std::variant<std::string, uint64_t>> createParams; + createParams["Type"] = "Crashdump"; + createParams["PrimaryLogId"] = std::to_string(tid); + createParams["Log"] = dirPath + filename; + createParams["PrettyName"] = "RP " + std::to_string(tid) + " crashdump"; + method.append(createParams); + auto reply = bus.call(method); + lg2::info( + "Successfully transmitted CreateDump to FaultLog DBus endpoint for {FILE}", + "FILE", filename); + } + catch (const sdbusplus::exception::SdBusError& e) + { + lg2::error("Failed to CreateDump DBus call: {ERROR}", "ERROR", + e.what()); + } + catch (const std::exception& e) + { + lg2::error("Standard exception triggering DBus logic: {ERROR}", "ERROR", + e.what()); + } +} + int EventManager::handlePlatformEvent( pldm_tid_t tid, uint16_t eventId, uint8_t eventClass, const uint8_t* eventData, size_t eventDataSize) @@ -67,7 +155,13 @@ } } - /* EventClass CPEREvent as `Table 11 - PLDM Event Types` DSP0248 V1.3.0 */ + /* EventClass OEM Crashdump (0xFA - 0xFE) */ + if (eventClass >= 0xFA && eventClass <= 0xFE) + { + return processOemCrashdumpEvent(tid, eventId, eventClass, eventData, + eventDataSize); + } + if (eventClass == PLDM_CPER_EVENT) { return processCperEvent(tid, eventId, eventData, eventDataSize); @@ -371,6 +465,90 @@ return rc; } +int EventManager::processOemCrashdumpEvent( + pldm_tid_t tid, uint16_t eventId, uint8_t eventClass, + const uint8_t* eventData, const size_t eventDataSize) +{ + lg2::info( + "Received OEM Crashdump EventClass {CLASS} for TID {TID}, Event ID {EVENTID}, Data Size {SIZE}", + "CLASS", eventClass, "TID", tid, "EVENTID", eventId, "SIZE", + eventDataSize); + + (void)eventData; // eventData is preserved for payload extraction if needed + std::string dirPath = "/var/run/pldm/event/repo/"; + std::string filenameSuffix = "_TID" + std::to_string(tid) + "_"; + std::string latestFile = ""; + std::filesystem::file_time_type latestTime = + std::filesystem::file_time_type::min(); + + try + { + for (const auto& entry : std::filesystem::directory_iterator(dirPath)) + { + if (entry.is_regular_file()) + { + std::string currentPath = entry.path().string(); + if (currentPath.find("CRASH_DUMP_") != std::string::npos && + currentPath.find(filenameSuffix) != std::string::npos) + { + auto ftime = std::filesystem::last_write_time(entry); + if (latestFile.empty() || ftime > latestTime) + { + latestFile = currentPath; + latestTime = ftime; + } + } + } + } + } + catch (const std::exception& e) + { + lg2::error("Failed to parse directory", "ERROR", e.what()); + } + + if (!latestFile.empty()) + { + lg2::info("Located most recent Crashdump payload: {FILE} for TID {TID}", + "FILE", latestFile, "TID", tid); + try + { + auto bus = sdbusplus::bus::new_default(); + auto method = bus.new_method_call( + "xyz.openbmc_project.Dump.Manager", + "/xyz/openbmc_project/dump/faultlog", + "xyz.openbmc_project.Dump.Create", "CreateDump"); + std::map<std::string, std::variant<std::string, uint64_t>> + createParams; + createParams["Type"] = "Crashdump"; + createParams["PrimaryLogId"] = std::to_string(tid); + createParams["Log"] = latestFile; + createParams["PrettyName"] = + "RP " + std::to_string(tid) + " crashdump"; + method.append(createParams); + auto reply = bus.call(method); + lg2::info( + "Successfully transmitted CreateDump to FaultLog DBus endpoint."); + } + catch (const sdbusplus::exception::SdBusError& e) + { + lg2::error("Failed to CreateDump DBus call: {ERROR}", "ERROR", + e.what()); + } + catch (const std::exception& e) + { + lg2::error("Standard exception triggering DBus logic: {ERROR}", + "ERROR", e.what()); + } + } + else + { + lg2::error( + "Failed to locate any valid CRASH_DUMP payload for TID {TID} locally in {DIR}", + "TID", tid, "DIR", dirPath); + } + return PLDM_SUCCESS; +} + int EventManager::createCperDumpEntry(const std::string& dataType, const std::string& dataPath, const std::string& typeName) @@ -536,6 +714,11 @@ callPolledEventHandlers(polledEventTid, polledEventClass, polledEventId, eventMessage); } + else if (polledEventClass >= 0xFA && polledEventClass <= 0xFE) + { + saveOemCrashDump(polledEventTid, polledEventClass, + eventMessage); + } eventMessage.clear(); if (eventId == PLDM_PLATFORM_EVENT_ID_ACK)
diff --git a/platform-mc/event_manager.hpp b/platform-mc/event_manager.hpp index 0eb52d7..a7eca54 100644 --- a/platform-mc/event_manager.hpp +++ b/platform-mc/event_manager.hpp
@@ -156,6 +156,20 @@ const uint8_t* eventData, const size_t eventDataSize); + /** @brief Helper method to process the OEM Crashdump event class + * + * @param[in] tid - tid where the event is from + * @param[in] eventId - Event ID which is the source of event + * @param[in] eventClass - OEM event class + * @param[in] eventData - OEM event data + * @param[in] eventDataSize - event data length + * + * @return PLDM completion code + */ + virtual int processOemCrashdumpEvent( + pldm_tid_t tid, uint16_t eventId, uint8_t eventClass, + const uint8_t* eventData, const size_t eventDataSize); + /** @brief Helper method to create CPER dump log * * @param[in] dataType - CPER event data type
diff --git a/platform-mc/platform_manager.cpp b/platform-mc/platform_manager.cpp index ae3a421..d78b90d 100644 --- a/platform-mc/platform_manager.cpp +++ b/platform-mc/platform_manager.cpp
@@ -127,6 +127,14 @@ exec::task<int> PlatformManager::configEventReceiver(pldm_tid_t tid) { + auto mctpInfo = terminusManager.toMctpInfo(tid); + if (mctpInfo) + { + mctp_eid_t eid = std::get<0>(mctpInfo.value()); + lg2::info("TID {TID} physically maps to EID {EID}", "TID", tid, "EID", + eid); + } + if (!termini.contains(tid)) { co_return PLDM_ERROR; @@ -161,6 +169,7 @@ } } + lg2::info("CHECKING IF TERMINUS SUPPORTS SET_EVENT_RECEIVER..."); if (!terminus->doesSupportCommand(PLDM_PLATFORM, PLDM_SET_EVENT_RECEIVER)) { lg2::error("Terminus {TID} does not support Event", "TID", tid); @@ -207,6 +216,8 @@ if (eventMessageGlobalEnable != PLDM_EVENT_MESSAGE_GLOBAL_DISABLE) { + lg2::info("ABOUT TO SEND SET EVENT RECEIVER WITH ENABLE FLAG: {FLAG}", + "FLAG", eventMessageGlobalEnable); auto rc = co_await setEventReceiver(tid, eventMessageGlobalEnable, PLDM_TRANSPORT_PROTOCOL_TYPE_MCTP, heartbeatTimer); @@ -513,9 +524,27 @@ } Request request(sizeof(pldm_msg_hdr) + requestBytes); auto requestMsg = new (request.data()) pldm_msg; + auto mctpInfo = terminusManager.toMctpInfo(tid); + if (!mctpInfo) + { + lg2::error("Failed to get MCTP info for terminus {TID}", "TID", tid); + co_return PLDM_ERROR; + } + mctp_eid_t destEid = std::get<0>(mctpInfo.value()); + + auto dynamicLocalEidOpt = terminusManager.getLocalEidForRemote(destEid); + if (!dynamicLocalEidOpt) + { + lg2::error( + "Failed to dynamically resolve local EID for terminus ID {TID}, Remote EID {EID}", + "TID", tid, "EID", destEid); + co_return PLDM_ERROR; + } + mctp_eid_t dynamicLocalEid = dynamicLocalEidOpt.value(); + auto rc = encode_set_event_receiver_req( - 0, eventMessageGlobalEnable, protocolType, - terminusManager.getLocalEid(), heartbeatTimer, requestMsg); + 0, eventMessageGlobalEnable, protocolType, dynamicLocalEid, + heartbeatTimer, requestMsg); if (rc) { lg2::error( @@ -524,6 +553,10 @@ co_return rc; } + lg2::info( + "Sending SetEventReceiver to terminus ID {TID} urging it to send events to our local EID {EID}...", + "TID", tid, "EID", dynamicLocalEid); + const pldm_msg* responseMsg = nullptr; size_t responseLen = 0; rc = co_await terminusManager.sendRecvPldmMsg(tid, request, &responseMsg, @@ -555,6 +588,10 @@ co_return completionCode; } + lg2::info( + "Successfully SetEventReceiver! Terminus ID {TID} acknowledged and accepted it.", + "TID", tid); + co_return completionCode; }
diff --git a/platform-mc/sensor_manager.cpp b/platform-mc/sensor_manager.cpp index 1cd4826..7a44a5a 100644 --- a/platform-mc/sensor_manager.cpp +++ b/platform-mc/sensor_manager.cpp
@@ -162,9 +162,7 @@ { uint64_t t0 = 0; uint64_t t1 = 0; - uint64_t elapsed = 0; uint64_t pollingTimeInUsec = pollingTime * 1000; - uint8_t rc = PLDM_SUCCESS; do { @@ -218,6 +216,7 @@ sd_event_now(event.get(), CLOCK_MONOTONIC, &t1); +#ifdef SENSOR_POLLING auto& numericSensors = terminus->numericSensors; auto toBeUpdated = numericSensors.size(); @@ -248,10 +247,10 @@ auto sensor = numericSensors[sensorIt]; sd_event_now(event.get(), CLOCK_MONOTONIC, &t1); - elapsed = t1 - sensor->timeStamp; + uint64_t elapsed = t1 - sensor->timeStamp; if ((sensor->updateTime <= elapsed) || (!sensor->timeStamp)) { - rc = co_await getSensorReading(sensor); + uint8_t rc = co_await getSensorReading(sensor); if ((!sensorPollTimers.contains(tid)) || (sensorPollTimers[tid] && @@ -277,6 +276,7 @@ sd_event_now(event.get(), CLOCK_MONOTONIC, &t1); } +#endif sd_event_now(event.get(), CLOCK_MONOTONIC, &t1); } while ((t1 - t0) >= pollingTimeInUsec);
diff --git a/platform-mc/terminus_manager.cpp b/platform-mc/terminus_manager.cpp index 0de6ce9..5270da8 100644 --- a/platform-mc/terminus_manager.cpp +++ b/platform-mc/terminus_manager.cpp
@@ -4,6 +4,12 @@ #include <phosphor-logging/lg2.hpp> +#include <array> +#include <cstdio> +#include <memory> +#include <regex> +#include <stdexcept> + PHOSPHOR_LOG2_USING; namespace pldm @@ -276,7 +282,8 @@ auto rc = co_await getTidOverMctp(eid, &tid); if (rc != PLDM_SUCCESS) { - lg2::error("Failed to Get Terminus ID, error {ERROR}.", "ERROR", rc); + lg2::error("Failed to Get Terminus ID for EID {EID}, error {ERROR}.", + "EID", eid, "ERROR", rc); co_return PLDM_ERROR; } @@ -804,5 +811,63 @@ return std::nullopt; } + +std::optional<mctp_eid_t> TerminusManager::getLocalEidForRemote( + mctp_eid_t remoteEid) +{ + auto exec = [](const std::string& cmd) -> std::string { + std::array<char, 128> buffer; + std::string result; + FILE* pipe = popen(cmd.c_str(), "r"); + if (!pipe) + { + return ""; + } + while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) + { + result += buffer.data(); + } + pclose(pipe); + return result; + }; + + // 1. Ask the kernel which physical bus handles the remote EID + std::string routeCmd = + "mctp route show | grep \"eid min " + std::to_string(remoteEid) + + " max " + std::to_string(remoteEid) + " \""; + + std::string routeOutput = exec(routeCmd); + + std::smatch match; + std::regex devRegex(R"(dev\s+(mctp[^\s]+))"); + if (!std::regex_search(routeOutput, match, devRegex) || match.size() < 2) + { + lg2::error("Failed to find kernel route for remote EID: {EID}", "EID", + remoteEid); + return std::nullopt; + } + std::string deviceName = match[1]; + + // 2. Ask the kernel what Local EID it assigned to that bus + std::string addrCmd = "mctp addr show | grep \"dev " + deviceName + "\""; + std::string addrOutput = exec(addrCmd); + + std::regex eidRegex(R"(eid\s+(\d+)\s+net)"); + if (!std::regex_search(addrOutput, match, eidRegex) || match.size() < 2) + { + lg2::error("Failed to find local EID bound to device: {DEV}", "DEV", + deviceName); + return std::nullopt; + } + + uint8_t dynamicLocalEid = static_cast<uint8_t>(std::stoi(match[1])); + + lg2::info( + "MCTP Routing Success: Remote EID {REMOTE} -> {DEV} -> Local EID {LOCAL}", + "REMOTE", remoteEid, "DEV", deviceName, "LOCAL", dynamicLocalEid); + + return dynamicLocalEid; +} + } // namespace platform_mc } // namespace pldm
diff --git a/platform-mc/terminus_manager.hpp b/platform-mc/terminus_manager.hpp index 8382201..9447b14 100644 --- a/platform-mc/terminus_manager.hpp +++ b/platform-mc/terminus_manager.hpp
@@ -155,6 +155,10 @@ return localEid; } + /** @brief Discover BMC local EID bound to the interface of the remote EID + */ + std::optional<mctp_eid_t> getLocalEidForRemote(mctp_eid_t remoteEid); + /** @brief Helper function to invoke registered handlers for * updating the availability status of the MCTP endpoint *
diff --git a/platform-mc/test/mock_event_manager.hpp b/platform-mc/test/mock_event_manager.hpp index 8e2349e..e6f2211 100644 --- a/platform-mc/test/mock_event_manager.hpp +++ b/platform-mc/test/mock_event_manager.hpp
@@ -19,6 +19,11 @@ (pldm_tid_t tid, uint16_t eventId, const uint8_t* eventData, size_t eventDataSize), (override)); + + MOCK_METHOD(int, processOemCrashdumpEvent, + (pldm_tid_t tid, uint16_t eventId, uint8_t eventClass, + const uint8_t* eventData, size_t eventDataSize), + (override)); }; } // namespace platform_mc