PLDM (Platform Level Data Model)

OpenSMA implements the PLDM firmware-device (FD) role: it is the responder that a host-side Update Agent (UA) talks to in order to inventory and update the MCU’s own firmware (and any attached AP firmware). PLDM messages arrive as MCTP message type 5 (firmware update) or type 0 (control & discovery), and are dispatched into an Ada/SPARK state machine that lives in corepdk/modules/pldm-fd/.

The subsystem splits cleanly into two layers:

Layer Language Path Responsibility
Task / glue C++ src/nv/pldm/ RTOS task, queues, event loop, MCTP framing, flash writes
Protocol core Ada/SPARK corepdk/modules/pldm-fd/ Base (T0) commands + firmware-update (T5) state machine
Platform wrap C++ corepdk/platforms/mcxn236/pldm-fd/ send_pldm_msg_to_mctp, pldm_write, version/UUID hooks

The PLDM task

nv::pldm::Task derives from the common IPC task and owns two receive queues (a “base” queue and a 4 KB queue for large transfers), a timer, and an event object — see the class in src/nv/pldm/task.h:60 and the constructor wiring at src/nv/pldm/task.cpp:183. The task’s work is driven entirely by event bits declared in src/nv/pldm/task.h:78 (PldmTaskRxBit, PldmTaskRx4kBit, TimerBit, BgStartBit, EventRequestBit, ProtocolResetBit, …), each mapped from the PldmTask enum at src/nv/pldm/task.h:65.

Because the Ada core is written in a synchronous, single-context style, the C++ layer holds two context records for the life of the task and passes them into every Ada entry point. Task::main initialises them once via the exported Ada initialisers ada_pldmfw_context_init and ada_init_pldm_base_ctx (src/nv/pldm/task.cpp:196) — both declared as extern "C" at src/nv/pldm/task.cpp:45. The context layout itself (PldmContextRecord) is the C mirror of the Ada Pldm_Context record and is defined at corepdk/platforms/mcxn236/pldm-fd/src/pldm_wrap.h:198.

Inbound path: MCTP → PLDM → Ada dispatch

The MCTP driver forwards any packet whose client is Client::Pldm into the task by calling nv::pldm::Task::pldm_tx (src/nv/mctp/driver.cpp:421). pldm_tx (src/nv/pldm/task.cpp:489) picks the base or 4 KB queue by packet length, enqueues the raw MCTP packet, and sets the matching event bit.

The event loop then drains the queue and routes by the PLDM message-type byte. The base type (NV_PLDM_TYPE_MSG_CTRL_AND_DISCOVERY, value 0) goes to ada_process_base_commands (src/nv/pldm/task.cpp:314); the firmware -update type (NV_PLDM_TYPE_FIRMWARE_UPDATE, value 5) goes to ada_pldmfw_state_runner (src/nv/pldm/task.cpp:318). Both type constants are defined at corepdk/platforms/mcxn236/pldm-fd/src/pldm_wrap.h:27. Before dispatch, get_interface_info (src/nv/pldm/task.cpp:109) peels the MCTP private

  • transport header off the buffer and records the source EID / message tag so the response can be routed back to the same UA.
sequenceDiagram
    participant UA as Update Agent (host)
    participant M as MCTP driver
    participant T as pldm::Task loop
    participant A as pldm-fd (Ada)
    UA->>M: MCTP type 5 (RequestUpdate)
    M->>T: pldm_tx() enqueue + set PldmTaskRxBit
    T->>T: get_interface_info() strip MCTP header
    T->>A: ada_pldmfw_state_runner(context, size)
    A->>A: state = IDLE -> route to Idle handler
    A-->>M: send_pldm_msg_to_mctp() response
    M-->>UA: MCTP response (+ transition to LEARN COMPONENT)

Base (T0) command handling

The base command set is discovery only. Process_Base_Commands (corepdk/modules/pldm-fd/src/base/pdk-pldm-base-cmd.ads:226, body at corepdk/modules/pldm-fd/src/base/pdk-pldm-base-cmd.adb:38) switches on the PLDM command code (corepdk/modules/pldm-fd/src/base/pdk-pldm-base-cmd.adb:54) and handles SetTID, GetTID, GetPLDMVersion, GetPLDMTypes, and GetPLDMCommands; anything else returns PLDM_CC_ERR_UNSUPPORTED_CMD. The advertised capabilities (base + firmware-update versions) are seeded in Init_Pldm_Base_Ctx.

Firmware-update (T5) state machine

The heart of PLDM-FD is a seven-state machine defined in Ada. Pldmfw_State_Runner (corepdk/modules/pldm-fd/src/fwupdate/pdk-pldm-fwupdate-state.ads:89, exported as ada_pldmfw_state_runner) switches on the current state and delegates to a per-state handler (corepdk/modules/pldm-fd/src/fwupdate/pdk-pldm-fwupdate-state.adb:428). The states themselves are declared as constants in corepdk/modules/pldm-fd/src/fwupdate/pdk-pldm-fwupdate.ads:76:

State Value Handler DSP0267 phase
IDLE 0 Pldmfw_State_Idle_Handler inventory + RequestUpdate
LC 1 Pldmfw_State_Lc_Handler learn components / PassComponentTable
RDY_XFER 2 Pldmfw_State_Rdy_Xfer_Handler UpdateComponent
DL 3 Pldmfw_State_Dl_Handler RequestFirmwareData loop
VERIFY 4 Pldmfw_State_Verify_Handler VerifyComplete
APPLY 5 Pldmfw_State_Apply_Handler ApplyComplete
ACTIVATE 6 Pldmfw_State_Activate_Handler ActivateFirmware

Each incoming command is validated against the current state. For example, in IDLE only queries and RequestUpdate are accepted; a PassComponentTable there is rejected with PLDMFW_CC_NOT_IN_UPDATE_MODE (corepdk/modules/pldm-fd/src/fwupdate/pdk-pldm-fwupdate-state.adb:99). A successful RequestUpdate calls Pldmfw_Change_State to advance to LC (corepdk/modules/pldm-fd/src/fwupdate/pdk-pldm-fwupdate-state.adb:94). State transitions also arm the FD timers (T1/T2/verify), so a stalled UA eventually times out — Pldmfw_Change_State sets them per target state (corepdk/modules/pldm-fd/src/fwupdate/pdk-pldm-fwupdate-state.adb:39) and the C++ TimerBit drives ada_pldmfw_timeout_handler (src/nv/pldm/task.cpp:353). The full PLDM command and completion-code sets are enumerated at corepdk/modules/pldm-fd/src/fwupdate/pdk-pldm-fwupdate.ads:61 and :40.

The command codes the FD understands (a subset of DSP0267) include QueryDeviceIdentifiers (0x01), GetFirmwareParameters (0x02), RequestUpdate (0x10), PassComponentTable (0x13), UpdateComponent (0x14), RequestFirmwareData (0x15), TransferComplete (0x16), VerifyComplete (0x17), ApplyComplete (0x18), and ActivateFirmware (0x1a).

Writing the image and asking SPDM to authenticate

The Ada core cannot touch flash directly; it calls back into C++ through the platform wrap. pldm_write (corepdk/platforms/mcxn236/pldm-fd/src/pldm_wrap.cpp:188) erases and programs the downloaded chunks — for the MCU component it writes 256-byte pages into the inactive slot; for an AP component it routes through the vRoT metadata/firmware writers. Outbound responses are framed and handed back to MCTP by send_pldm_msg_to_mctp (corepdk/platforms/mcxn236/pldm-fd/src/pldm_wrap.cpp:138, declared at corepdk/platforms/mcxn236/pldm-fd/src/pldm_wrap.h:254), which rebuilds the MCTP header via the composer and calls Driver::mctp_send_from_pldm.

Verification is delegated to the SPDM subsystem. During the verify phase the FD calls request_authentication (corepdk/platforms/mcxn236/pldm-fd/src/pldm_wrap.cpp:796), which for the MCU invokes nv::spdm::crypto::authenticate_mcu_firmware. SPDM runs the check asynchronously and posts the result back to the PLDM task: the Pldm-specialised send_authenticate_mcu_firmware_result (corepdk/platforms/mcxn236/pldm-fd/src/pldm_wrap.cpp:50) builds a Request and hands it to Task::to_pldm (src/nv/pldm/task.cpp:608), which enqueues it and sets EventRequestBit. The loop then decodes the result (switching on request.type at src/nv/pldm/task.cpp:462) and calls ada_pldmfw_authenticate_handler (src/nv/pldm/task.cpp:466), mapping the crypto status onto a PLDM VerifyComplete completion code.

Interpretation: this indirection keeps all cryptographic work on the SPDM task (which owns the key material and mbedTLS contexts) while the PLDM FD state machine stays purely protocol-focused and non-blocking.

Background copy and protocol reset

Beyond the update flow, the task also drives background copy — the MCU replicating its freshly written slot — via BgStartBit / BgEndBit (src/nv/pldm/task.cpp:355), gated by the flash-stored policy read in is_background_copy_automatic (src/nv/pldm/task.cpp:138). AP background copy has its own ApBgStartBit path (src/nv/pldm/task.cpp:418). A ProtocolResetBit (src/nv/pldm/task.cpp:243) flushes both receive queues and re-inits the Ada contexts, returning the FD to IDLE. The AP component-id list the FD reports is built at compile time from the vRoT ApList by make_component_id_list (src/nv/pldm/common.h:28).

Where to look next


Built with Just the Docs. Source-traced against NVIDIA/OpenSMA at 9b183d3 (v02.0020.0000). Methodology under appendices/trace-methodology.

This site uses Just the Docs, a documentation theme for Jekyll.