Caliptra Manufacturer Control Unit (MCU) Firmware and SDK

Spec revision: 0.3

The Caliptra MCU firmware is be provided as a reference software development kit (SDK) with a consistent foundation for building a quantum-resilient and standards-compliant Root of Trust (RoT) for SoC implementers. It extends the Caliptra core system to provide the Caliptra Subsystem set of services to the encompassing system.

While Caliptra Core provides support for Identity, Secure Boot, Measured Boot, and Attestation, the Caliptra MCU firmware will be responsible for enabling Recovery, RoT Services, and Platform integration support. All SoC RoTs have specific initialization sequences and scenarios that need to be supported beyond standard RoT features. Hence, the MCU firmware will be distributed as Rust SDK with batteries included to build RoT Applications.

The Caliptra MCU SDK is composed of two major parts:

ROM: When the MCU first boots, it executes a small, bare metal ROM. The ROM is responsible for sending non-secret fuses to Caliptra core so that it can complete booting. The MCU ROM will be silicon-integrator-specific. However, the MCU SDK will provide a ROM framework that binds the MCU and Caliptra. The ROM will also be used with the software emulator, RTL Simulator, and FPGA stacks. For more details, see the ROM specification.

Runtime: The majority of the MCU firmware SDK is the runtime firmware, which provides the majority of the services after booting. Most of the documentation here consists of the documentation for the runtime.

MCU Diagram

Principles

Caliptra 2.x firmware aspires to be the foundation for the RoT used in SoCs integrating Caliptra. Hence architecture, design and implementation must abide by certain guiding principles. Many of these principles are the founding principles for the Caliptra Project.

The MCU firmware SDK will follow the same principles as the Caliptra core's firmware.

  • Open and Extensible: The MCU SDK is published as open source and available via GitHub, and uses a build toolchain for repeatable builds. The MCU SDK provided is a reference implementation, and is intended to be extended and adaptable by integrators.

  • Consistent: Vendors require features like Identity, Secure Boot, Measured Boot, Attestation, Recovery, Update, Anti-Rollback, and ownership transfer. There are standards defined by various organizations around these feature areas. The MCU SDK will follow TCG, DMTF, OCP, PCIe and other standards to guarantee consistency on RoT features and behavior.

  • Compliant: The MCU SDK must be compliant with security standards and audits. Security audits will be performed and audit reports will be published on GitHub. In addition, the SDK will also be audited for OCP SAFE and short form reports will be published to GitHub.

  • Secure and Safe Code: The MCU SDK will follow the standards in Caliptra Core for security and memory safety as first principle and will be implemented in Rust.

SDK Features

The following sections give a short overview of the features and goals of the MCU SDK.

Development

Software emulators for Caliptra, MCU, and a reference SoC will be provided as part of the MCU SDK. Software emulators will be an integrated part of CI/CD pipelines. In addition, Caliptra also has RTL Simulators based on Verilator that can be used for regression testing. The Caliptra hardware team also has reference hardware running on FPGA platforms.

RTOS

MCU provides various simultaneous, complex RoT services. Hence an RTOS is needed. As the MCU SDK is implemented in Rust, we need Rust-based RTOS. MCU will leverage the Tock Embedded Operating System, an RTOS designed for secure embedded systems. MCU uses the Tock Kernel while providing a Caliptra async user mode API surface area. Tock provides a good security and isolation model to build an ROT stack.

Drivers

Tock has a clean user and kernel mode separation. Components that directly access hardware and provide abstractions to user mode are implemented as Tock drivers and capsules (kernel modules). The MCU SDK will provide the following drivers:

  • UART Driver
  • I3C Driver
  • Fuse Controller Driver
  • Caliptra Mailbox Driver
  • MCI Mailbox Driver
  • SPI Flash Driver (may be replaced by a silicon-specific flash driver)

Silicon integrators may provide their own drivers to implement SoC-specific features that are not provided by the MCU SDK. For example, if an SoC implements TEE-IO, the silicon vendor will be responsible for providing PCIe IDE and TDISP drivers.

Stacks

As the Caliptra RoT is built on the foundation of industry standard protocols, the MCU SDK will provide the following stacks:

  • MCTP
  • PLDM
  • PLDM Firmware Update
  • OCP Streaming Boot
  • SPDM based Attestation
  • PCIe IDE
  • TDISP
  • SPI Flash Boot

APIs and Services

Caliptra RoT will provide common foundational services and APIs required to be implemented by all RoT. The following are the APIs and services provided by MCU SDK:

  • Image Loading
  • Attestation
  • Signing Key management and revocation
  • Anti-Rollback protection
  • Firmware Update
  • Life Cycle Management
  • Secure Debug Unlock
  • Ownership Transfer
  • Cryptographic API
  • Certificate Store
  • Key-Value Store
  • Logging and Tracing API

Applications

Each SoC has unique features and may need a subset of security services. Hence silicon integrators will be responsible for authoring ROT applications leveraging MCU SDK. MCU SDK will ship with a set of applications that will demonstrate the features of the MCU SDK.

Tooling and Documentation

The MCU SDK will feature

  • Repeatable build environments to build RoT Applications
  • Image generation, verification, and signing tools
  • Libraries for interacting with Caliptra ROT to perform various functions like attestation, firmware updates, log retrieval, etc.
  • An MCU SDK Architecture and Design Specifications
  • An MCU SDK Developers' Guide
  • An MCU SDK Integrators' Guide
  • The MCU SDK API

Reference ROM Specification

The reference ROM is executed when the MCU starts.

The ROM's main responsibilities to the overall Caliptra subsystem are to:

  • Send non-secret fuses to Caliptra core
  • Initialize I3C and the firmware recovery interface
  • Jump to firmware

It can also handle any other custom SoC-specific initialization that needs to happen early.

Boot Flows

There are three main boot flows that needs to execute for its role in the Caliptra subsystem:

  • Cold Boot Flow
  • Firmware Update Flow
  • Warm Reset Flow

These are selected based on the MCI RESET_REASON register that is set by hardware whenver the MCU is reset.

Cold Boot Flow

  1. Check the MCI RESET_REASON register for MCU status (it should be in cold boot mode)
  2. Program and lock PMP registers
  3. Initialize I3C registers according to the initialization sequence.
  4. Initialize I3C recovery interface initialization sequence.
  5. Anything SoC-specific can happen here
    1. Stash to Caliptra if required (i.e., if any security-sensitive code is loaded, such as PLL programming or configuration loading)
  6. Read Caliptra SoC FLOW_STATUS register to wait for Caliptra Ready for Fuses state
  7. Read non-secret fuse registers from creator SW OTP partition in OpenTitan OTP controller. The list of fuses and their sizes are reproduced here, but the authoritative fuse map is contained in the main Caliptra specification.
    • KEY MANIFEST PK HASH: 384 bits
    • ECC REVOCATION (KEY MANIFEST PK HASH MASK): 4 bits
    • OWNER PK HASH: 384 bits
    • FMC KEY MANIFEST SVN: 32 bits
    • RUNTIME SVN: 128 bits
    • ANTI-ROLLBACK DISABLE: 1 bits
    • IDEVID CERT IDEVID ATTR: 768 bits
    • IDEVID MANUF HSM IDENTIFIER: 128 bits
    • LIFE CYCLE: 2 bits
    • LMS REVOCATION: 32 bits
    • MLDSA REVOCATION: 4 bits
    • SOC STEPPING ID: 16 bits
    • MANUF_DEBUG_UNLOCK_TOKEN: 128 bits
  8. Write fuse data to Caliptra SoC interface fuse registers.
  9. Poll on Caliptra FLOW_STATUS registers for Caliptra to deassert the Ready for Fuses state.
  10. Clear the watchdog timer
  11. Wait for reset to trigger firmware update flow.
sequenceDiagram
    note right of mcu: check reset reason
    note right of mcu: program and lock PMP
    note right of mcu: initialize I3C
    note right of mcu: initialize recovery interface
    note right of mcu: SoC-specific init
    opt if required
        mcu->>caliptra: stash
    end
    loop wait for ready for fuses
    mcu->>caliptra: read flow status
    end
    mcu->>otp: read non-secret fuses
    otp->>mcu: non-secret fuses
    mcu->>caliptra: set non-secret fuses
    loop wait for NOT ready for fuses
    mcu->>caliptra: read flow status
    end
    note right of mcu: clear watchdog
    note right of mcu: wait for reset

The main Caliptra ROM and runtime will continue executing and push the MCU runtime firmware to its SRAM, set the MCI register stating that the firmware is ready, and reset the MCU.

Firmware Update Flow

  1. Check the MCI RESET_REASON register for MCU status (it should be in firmware update mode)
  2. Program and lock PMP registers
  3. Anything SoC-specific can happen here
    1. Do stash if required
  4. Jump to runtime firmware

Warm Reset Flow

This is currently the same as the firmware update flow.

Failures

On any fatal or non-fatal failure, MCU ROM can use the MCI registers FW_ERROR_FATAL and FW_ERROR_NON_FATAL to assert the appropriate errors.

In addition, SoC-specific failure handling may occur.

There will also be a watchdog timer running to ensure that the MCU is reset if not the ROM flow is not progressing properly.

Runtime Specification

The MCU runtime firmware is based on the Tock real-time, embedded operation system. Tock provides the ability for us to run multiple high-level applications concurrently and securely. For more specific information on how Tock internals work, please see the Tock kernel documentation.

Any RISC-V code that needs to run in M-mode, e.g., low-level drivers, should run in the Tock board or a capsule loaded by the board.

In Tock, the "board" is the code that runs that does all of the hardware initialization and starts the Tock kernel. The Tock board is essentially a custom a kernel for each SoC.

The applications are higher-level RISC-V code that only interact with the rest of the world through Tock system calls. For instance, an app might be responsible for running a PLDM flow and uses a Tock capsule to interact with the MCTP stack to communicate with the rest of the SoC.

Architecture

The overall architecture for the MCU firmware stack is thought of in layers:

  • At the highest layer are the user-mode applications that run specific flows that are relevant for vendors. These run more complex, dynamic protocols, like PLDM and SPDM.
  • These interact with common user-mode APIs for protocols like MCTP. These handle the details of converting low-level system calls to the Tock kernel and capsules into synchronous and asynchronous APIs.
    • Neither the applications nor the user-mode APIs have the ability to access hardware directly.
  • The Tock kernel is responsible for scheduling the applications and routing system calls to the appropriate capsules. Everything at the Tock kernel level and below execute in machine mode with full privileges.
  • The capsules are Tock kernel modules that implement specific specific workflows and capabilities, like using the MCTP stack or accessing the Caliptra mailbox.
    • Everything at the capsule layer and above should be independent of the hardware specifics. Everything below the capsule layer is specific to the hardware implementations.
  • The capsules in turn talk to specific drivers. These are generally implementations of specific Rust traits, like Tock HILs, that provide access to hardware.
  • Two of the most fundamental pieces of Rust code sit at the bottom: the chip and board files. The chip file contains microcontroller-specific code, such as dealing with interrupts, can should be able to be reused on different boards that use the same microcontroller.
  • The board file is the heart of Tock: it is the main() function, and is responsible for creating and initializing all of the hardware drivers, the chip file, creating the Tock kernel, loading and configuring the capsules, and starting the main execution loop.

PLDM Update Package

This section describes the PLDM Update Package used for Caliptra streaming boot and firmware update.

The update package follows the DMTF Firmware Update Specification v.1.3.0.

PLDM FW Update Package
Package Header Information
Firmware Dev ID Descriptors
Downstream Dev ID Descriptors
Component Image Information
Package Header Checksum
Package Payload Checksum
Component 1 (Caliptra FMC + RT)
Component 2 (SoC Manifest)
Component 3 (MCU RT)
Component 4 (SoC Image 1)
...
Component N (SoC Image N-3)
Component N + 1 (Streaming Boot Image for full flash)

The PLDM FW Update Package contains:

  1. Caliptra FMC (First Mutable Code) and RT (Runtime) Image Bundle
  2. The SOC Manifest for the MCU RT image and other SOC Images
  3. The MCU RT image
  4. Other SOC Images, if any
  5. A full image of the flash containing all images (see SPI Flash Layout Documentation)

Note: All fields are little-endian byte-ordered unless specified otherwise.

Package Header Information

FieldSizeDefinition
PackageHeaderIdentifier16Set to 0x7B291C996DB64208801B0202E6463C78 (v1.3.0 UUID) (big endian)
PackageHeaderFormatRevision1Set to 0x04 (v1.3.0 header format revision)
PackageHeaderSize2The total byte count of this header structure, including fields within the Package Header Information,
Firmware Device Identification Area, Downstream Device Identification Area,
Component Image Information Area, and Checksum sections.
PackageReleaseDateTime13The date and time when this package was released.
Refer to the PLDM Base Specification for field format definition.
ComponentBitmapBitLength2Number of bits used to represent the bitmap in the ApplicableComponents field for a matching device.
This value is a multiple of 8 and is large enough to contain a bit for each component in the package.
PackageVersionStringType1The type of string used in the PackageVersionString field.
Refer to DMTF Firmware Update Specification v.1.3.0 Table 33 for values.
PackageVersionStringLength1Length, in bytes, of the PackageVersionString field.
PackageVersionStringVariablePackage version information, up to 255 bytes.
Contains a variable type string describing the version of this firmware update package.

Firmware Device ID Descriptor

FieldSizeDefinition
RecordLength2The total length in bytes for this record. The length includes the RecordLength, DescriptorCount, DeviceUpdateOptionFlags, ComponentImageSetVersionStringType, ComponentSetVersionStringLength, FirmwareDevicePackageDataLength, ApplicableComponents, ComponentImageSetVersionString, and RecordDescriptors, and FirmwareDevicePackageData fields.
DescriptorCount1The number of descriptors included within the RecordDescriptors field for this record.
DeviceUpdateOptionFlags432-bit field where each bit represents an update option.
bit 1 is set to 1 (Support Flashless/Streaming Boot)
For other options, refer to DeviceUpdateOptionFlags description in DMTF Firmware Update Specification v.1.3.0.
ComponentImageSetVersionStringType1The type of string used in the ComponentImageSetVersionString field. Refer to DMTF Firmware Update Specification v.1.3.0 Table 33 for values.
ComponentImageSetVersionStringLength1Length, in bytes, of the ComponentImageSetVersionString.
FirmwareDevicePackageDataLength2Length in bytes of the FirmwareDevicePackageData field.
If no data is provided, set to 0x0000.
ReferenceManifestLength4Length in bytes of the ReferenceManifestData field. If no data is provided, set to 0x00000000.
ApplicableComponentsVariableBitmap indicating which firmware components apply to devices matching this Device Identifier record. A set bit indicates the Nth component in the payload is applicable to this device.
bit 0: Caliptra FMC + RT
bit 1: SOC Manifest
bit 2 : MCU RT
bit 3 to N: Reserved for other SoC images
bit N+1: Full SPI Flash image used in streaming boot
ComponentImageSetVersionStringVariableComponent Image Set version information, up to 255 bytes.
Describes the version of component images applicable to the firmware device indicated in this record.
RecordDescriptorsVariableThese descriptors are defined by the vendor (i.e. integrators of Caliptra ROT)
Refer to DMTF Firmware Update Specification v.1.3.0 Table 7 for details of these fields and the values that can be selected.
FirmwareDevicePackageDataVariableOptional data provided within the firmware update package for the FD during the update process.
If FirmwareDevicePackageDataLength is 0x0000, this field contains no data.
ReferenceManifestDataVariableOptional data field containing a Reference Manifest for the firmware update.
If present, it describes the firmware update provided by this package.
If ReferenceManifestLength is 0x00000000, this field contains no data.

Downstream Device ID Descriptor

There are no Downstream Device ID records for this package

FieldSizeDefinition
DownstreamDeviceIDRecordCount10

Component Image Information

FieldSizeDefinition
ComponentImageCount2Count of individually defined component images contained within this firmware update package.
ComponentImageInformationVariableContains details for each component image within this firmware update package.

Component Image Information Record

FieldSizeDefinition
ComponentClassification20x000A: For Caliptra FMC+RT  (Firmware)
0x0001: For SoC Manifest  (Other)
0x000A: For MCU RT: (Firmware)
For other SoC images, Refer to DMTF Firmware Update Specification v.1.3.0 Table 32 for values.
ComponentIdentifier2Unique value selected by the FD vendor to distinguish between component images.
0x0001: Caliptra FMC+RT
0x0002: SoC Manifest:
0x0003: MCU RT
0x1000-0xFFFF - Reserved for other vendor-defined SoC images
ComponentComparisonStamp4Value used as a comparison in determining if a firmware component is down-level or up-level. When ComponentOptions bit 1 is set, this field should use a comparison stamp format (e.g., MajorMinorRevisionPatch). If not set, use 0xFFFFFFFF.
ComponentOptions2Refer to ComponentOptions definition in DMTF Firmware Update Specification v.1.3.0
RequestedComponentActivationMethod2Refer to RequestedComponentActivationMethoddefinition inDMTF Firmware Update Specification v.1.3.0
ComponentLocationOffset4Offset in bytes from byte 0 of the package header to where the component image begins.
ComponentSize4Size in bytes of the Component image.
ComponentVersionStringType1Type of string used in the ComponentVersionString field. Refer toDMTF Firmware Update Specification v.1.3.0 Table 33 for values.
ComponentVersionStringLength1Length, in bytes, of the ComponentVersionString.
ComponentVersionStringVariableComponent version information, up to 255 bytes. Contains a variable type string describing the component version.
ComponentOpaqueDataLength4Length in bytes of the ComponentOpaqueData field. If no data is provided, set to 0x00000000.
ComponentOpaqueDataVariableOptional data transferred to the FD/FDP during the firmware update

Checksum

FieldSizeDefinition
PackageHeaderChecksum4The integrity checksum of the PLDM Package Header. It is calculated starting at the first byte of the PLDM Firmware Update Header and includes all bytes of the package header structure except for the bytes in this field and the PackagePayloadChecksum field. The CRC-32 algorithm with polynomial 0x04C11DB7 (as used by IEEE 802.3) is used for checksum computation, processing one byte at a time with the least significant bit first.
PackagePayloadChecksum4The integrity checksum of the PLDM Package Payload. It is calculated starting at the first byte immediately following this field and includes all bytes of the firmware package payload structure, including any padding between component images. The CRC-32 algorithm with polynomial 0x04C11DB7 (as used by IEEE 802.3) is used for checksum computation, processing one byte at a time with the least significant bit first.

Component 1 - Caliptra FMC + RT

This is the package bundle for the Caliptra FMC + RT defined in Caliptra Firmware Image Bundle Format.

Component 2 - SOC Manifest

This is the SoC Manifest defined in SoC Manifest.

It provides the signature verification and specific data needed to decode the image.

Component 3 - MCU RT

This is the Image binary of the MCU Realtime firmware.

Component 4 to N - SoC Images

These are reserved for vendor SoC images, if any.

Component N + 1 - Full SPI Flash image

This component contains the full flash image as defined in the SPI Flash Layout Documentation from the Header section until the end of the images section. This is can be used for loading all the images at once for streaming boot.

SPI Flash Layout

Overall, the SPI Flash consists of a Header, Checksum and an Image Payload (which includes the image information and the image binary).

The specific images of the flash consists of the Caliptra FW, MCU RT, SoC Manifest, and other SoC images, if any.

Typical flash Layout

Note: All fields are little-endian byte-ordered unless specified otherwise.

A typical overall flash layout is:

Flash Layout
Header
Checksum
Payload

The Payload contains the following fields:

Payload
Image Info (Caliptra FMC + RT)
Image Info (SoC Manifest)
Image Info (MCU RT)
Image Info (SoC Image 1)
...
Image Info (SoC Image N)
Caliptra FMC + RT Package
SoC Manifest
MCU RT
SoC Image 1
...
SoC Image N

The Header section contains the metadata for the images stored in the flash.

FieldSize (bytes)Description
Magic Number4A unique identifier to mark the start of the header.
The value must be 0x464C5348 ("FLSH" in ASCII)
Header Version2The header version format, allowing for backward compatibility if the package format changes over time.
(Current version is 0x0001)
Image Count2The number of image stored in the flash.
Each image will have its own image information section.

Checksum

The checksum section contains integrity checksums for the header and the payload sections.

FieldSize (bytes)Description
Header Checksum4The integrity checksum of the Header section.
It is calculated starting at the first byte of the Header until the last byte of the Image Count field.
For this specification, The CRC-32 algorithm with polynomial 0x04C11DB7 (as used by IEEE 802.3)
is used for checksum computation, processing one byte at a time with the least significant bit first.
Payload Checksum4The integrity checksum of the payload section.
It is calculated starting at the first byte of the first image information until the last byte of the last image.
For this specification, The CRC-32 algorithm with polynomial 0x04C11DB7 (as used by IEEE 802.3)
is used for checksum computation, processing one byte at a time with the least significant bit first.

Image Information

The Image Information section is repeated for each image and provides detailed manifest data specific to that image.

FieldSize (bytes)Descr
Identifier4Vendor selected unique value to distinguish between images.
0x0001: Caliptra FMC+RT
0x0002: SoC Manifest:
0x0003: MCU RT
0x1000-0xFFFF - Reserved for other Vendor-defined SoC images
ImageLocationOffset4Offset in bytes from byte 0 of the header to where the image content begins.
Size4Size in bytes of the image. This is the actual size of the image without padding.
The image itself as written to the flash should be 4-byte aligned and additional
padding will be required to guarantee alignment.

Image

The images (raw binary data) are appended after the Image Information section, and should be in the same order as their corresponding Image Information.

FieldSize (bytes)Description
DataNImage content.
Note: The image should be 4-byte aligned.
If the size of a firmware image is not a multiple of 4 bytes,
0x00 padding bytes will be added to meet the alignment requirement.

Flash Controller

Overview

The Flash Controller stack in the Caliptra MCU firmware is designed to provide efficient and reliable communication with flash devices, which is the foundation to enable flash-based boot flow.

We will primarily be targetting SPI flash controllers, so we will use that as our primary example throughout.

This document outlines the different SPI flash configurations being supported, the stack architecture, component interface and userspace API to interact with the SPI flash stack.

SPI Flash Configurations

The SPI flash stack supports various configurations to cater to different use cases and hardware setups. The diagram below shows the flash configurations supported.

flash_config

1. Single-Flash Configuration In this setup, a single SPI flash device is connected to the flash controller. Typically, the flash device is divided into two halves: the first half serves as the primary flash, storing the active running firmware image, while the second half is designated as the recovery flash, containing the recovery image. Additional partitions, such as a staging area for firmware updates, flash store for certificates and debug logging can also be incorporated into the primary flash.

2. Dual-Flash Configuration In this setup, two SPI flash devices are connected to the same flash controller using different chip selects. This configuration provides increased storage capacity and redundancy. Typically, flash device 0 serves as the primary flash, storing the active running firmware image and additional partitions such as a staging area for firmware updates, flash store for certificates and debug logging. Flash device 1 is designated as the recovery flash, containing the recovery image.

3. Multi-Flash Configuration In more complex systems, multiple flash controllers may be used, each with one or more SPI flash devices. This configuration provides flexibility and scalability. For example, a backup flash can be added to recover the SoC and provide more resiliency for the system.

Architecture

The SPI flash stack design leverages TockOS's kernel space support for flash and associated virtualizer layers. The stack, from top to bottom, comprises the flash userland API, flash partition capsule, flash virtualizer and vendor-specific flash controller layer. SPI flash stack architecture with simplified flash controller layer is shown in the diagram below.

SPI flash stack architecture diagram

  • Flash Userland API

    • Provides syscall library for userspace applications to issue IO requests (read, write, erase) to flash devices. Userspace application will instantiate the syscall library with unique driver number for individual flash partition.
  • Flash Partition Capsule

    • Defines the flash partition structure with offset and size, providing methods for reading, writing, and erasing arbitrary lengths of data within the partitions. Each partition is logically represented by a FlashUser, which leverages the existing flash virtualizer layer to ensure flash operations are serialized and managed correctly. It also implements SyscallDriver trait to interact with the userland API.
  • Vendor-specific Flash controller Layer

    • Flash controller driver implements the kernel::hil::flash::Flash trait, which defines the standard interface (read, write, erase) for flash page-based operations.
      • Additional methods provided in the flash controller driver include:
        • Initializing the SPI flash device and configuring settings such as clock speed, address mode, and other parameters.
        • Checking the status of the flash device, such as whether it is busy or ready for a new operation.
        • Erasing larger regions of flash memory, such as sectors or blocks, in addition to individual pages.
        • Reading the device ID, manufacturer ID, or other identifying information from the flash device.
        • Retrieving information about the flash memory layout, such as the size of pages, sectors, and blocks from SFDP.
        • Performing advanced read/write operations using specific commands supported by the flash device.
    • A flash controller virtualizer VirtualFlashCtrl should be implemented to support the configuration of multiple flash devices connected to the same flash controller via different chip selects. The diagram below shows the stack to enable this scenario.

flash controller layer with virtualizer

Common Interfaces

Flash Userland API

It is defined in SPI flash syscall library to provide async interface (read, write, erase) to underlying flash devices.

/// spi_flash/src/lib.rs
///
/// A structure representing an asynchronous SPI flash memory interface.
struct AsyncSpiFlash {
    // The driver number associated with this SPI flash interface.
    driver_num: u32,
}

/// Represents an asynchronous SPI flash memory interface.
///
/// This struct provides methods to interact with SPI flash memory in an asynchronous manner,
/// allowing for non-blocking read, write, and erase operations.
impl AsyncSpiFlash {
    /// Creates a new instance of `AsyncSpiFlash`.
    ///
    /// # Arguments
    ///
    /// * `driver_num` - The driver number associated with the SPI flash.
    fn new(driver_num: u32) -> Self {};

    /// Checks if the SPI flash exists.
    ///
    /// # Returns
    ///
    /// * `Ok(())` if the SPI flash exists.
    /// * `Err(ErrorCode)` if there is an error.
    fn exists() -> Result<(), ErrorCode> {};

    /// Reads an arbitrary number of bytes from the flash memory.
    ///
    /// This method reads `len` bytes from the flash memory starting at the specified `address`
    /// and stores them in the provided `buf`.
    ///
    /// # Arguments
    ///
    /// * `address` - The starting address to read from.
    /// * `len` - The number of bytes to read.
    /// * `buf` - The buffer to store the read bytes.
    ///
    /// # Returns
    ///
    /// * `Ok(())` if the read operation is successful.
    /// * `Err(ErrorCode)` if there is an error.
    async fn read(&self, address: usize, len: usize, buf: &mut [u8]) -> Result<(), ErrorCode>;

    /// Writes an arbitrary number of bytes to the flash memory.
    ///
    /// This method writes the bytes from the provided `buf` to the flash memory starting at the
    /// specified `address`.
    ///
    /// # Arguments
    ///
    /// * `address` - The starting address to write to.
    /// * `buf` - The buffer containing the bytes to write.
    ///
    /// # Returns
    ///
    /// * `Ok(())` if the write operation is successful.
    /// * `Err(ErrorCode)` if there is an error.
    async fn write(&self, address: usize, buf: &[u8]) -> Result<(), ErrorCode>;

    /// Erases an arbitrary number of bytes from the flash memory.
    ///
    /// This method erases `len` bytes from the flash memory starting at the specified `address`.
    ///
    /// # Arguments
    ///
    /// * `address` - The starting address to erase from.
    /// * `len` - The number of bytes to erase.
    ///
    /// # Returns
    ///
    /// * `Ok(())` if the erase operation is successful.
    /// * `Err(ErrorCode)` if there is an error.
    async fn erase(&self, address: usize, len: usize) -> Result<(), ErrorCode>;
}

Flash partition capsule

/// A structure representing a partition of a flash memory.
///
/// This structure allows for operations on a specific partition of the flash memory,
/// defined by a start address and a size.
///
/// # Type Parameters
/// - `F`: The type of the flash memory, which must implement the `Flash` trait.
///
/// # Fields
/// - `flash_user`: A reference to the `FlashUser` that provides access to the flash memory.
/// - `start_address`: The starting address of the flash partition.
/// - `size`: The size of the flash partition.
/// - `client`: An optional reference to a client that implements the `FlashPartitionClient` trait.
struct FlashPartition<F: Flash> {
    flash_user: &FlashUser<F>,
    start_address: usize,
    size: usize,
    client: OptionalCell<&dyn FlashPartitionClient>,
}

/// A partition of a flash memory device.
///
/// This struct represents a partition of a flash memory device, allowing
/// operations such as reading, writing, and erasing within the partition.
///
/// # Type Parameters
///
/// - `F`: A type that implements the `Flash` trait.
impl<F: Flash> FlashPartition<F> {
    /// Creates a new `FlashPartition`.
    ///
    /// # Arguments
    ///
    /// - `flash_user`: A reference to the `FlashUser` that owns the flash memory device.
    /// - `start_address`: The starting address of the partition within the flash memory device.
    /// - `size`: The size of the partition in bytes.
    ///
    /// # Returns
    ///
    /// A new `FlashPartition` instance.
    fn new(
        flash_user: &FlashUser<, F>,
        start_address: usize,
        size: usize,
    ) -> FlashPartition<F> {}

    /// Sets the client for the flash partition.
    ///
    /// # Arguments
    ///
    /// - `client`: A reference to an object that implements the `FlashPartitionClient` trait.
    fn set_client(&self, client: &dyn FlashPartitionClient) {}

    /// Reads data from the flash partition.
    ///
    /// # Arguments
    ///
    /// - `buffer`: A mutable reference to a buffer where the read data will be stored.
    /// - `offset`: The offset within the partition from which to start reading.
    /// - `length`: The number of bytes to read.
    ///
    /// # Returns
    ///
    /// A `Result` indicating success or an error code.
    fn read(
        &self,
        buffer: &'static mut [u8],
        offset: usize,
        length: usize,
    ) -> Result<(), ErrorCode> {}

    /// Writes data to the flash partition.
    ///
    /// # Arguments
    ///
    /// - `buffer`: A mutable reference to a buffer containing the data to be written.
    /// - `offset`: The offset within the partition at which to start writing.
    /// - `length`: The number of bytes to write.
    ///
    /// # Returns
    ///
    /// A `Result` indicating success or an error code.
    fn write(
        &self,
        buffer: &'static mut [u8],
        offset: usize,
        length: usize,
    ) -> Result<(), ErrorCode> {}

    /// Erases data from the flash partition.
    ///
    /// # Arguments
    ///
    /// - `offset`: The offset within the partition at which to start erasing.
    /// - `length`: The number of bytes to erase.
    ///
    /// # Returns
    ///
    /// A `Result` indicating success or an error code.
    fn erase(&self, offset: usize, length: usize) -> Result<(), ErrorCode> {}
}

/// Implementation of the `SyscallDriver` trait for the `FlashPartition` struct.
/// This implementation provides support for reading, writing, and erasing flash memory,
/// as well as allowing read/write and read-only buffers, and subscribing to callbacks.
impl<F: Flash> SyscallDriver for FlashPartition< F> {
    ///
    /// Handles commands from userspace.
    ///
    /// # Arguments
    ///
    /// * `command_number` - The command number to execute.
    /// * `arg1` - The first argument for the command.
    /// * `arg2` - The second argument for the command.
    /// * `process_id` - The ID of the process making the command.
    ///
    /// # Returns
    ///
    /// A `CommandReturn` indicating the result of the command.
    ///
    /// Commands:
    /// - `0`: Success (no operation).
    /// - `1`: Read operation. Reads `arg2` bytes from offset `arg1`.
    /// - `2`: Write operation. Writes `arg2` bytes to offset `arg1`.
    /// - `3`: Erase operation. Erases `arg2` bytes from offset `arg1`.
    /// - Any other command: Not supported.
    fn command(
        &self,
        command_number: usize,
        arg1: usize,
        arg2: usize,
        process_id: ProcessId,
    ) -> CommandReturn {};

    ///
    /// Allows a process to provide a read/write buffer.
    ///
    /// # Arguments
    ///
    /// * `process_id` - The ID of the process providing the buffer.
    /// * `readwrite_number` - The identifier for the buffer.
    /// * `buffer` - The buffer to be used for read/write operations.
    ///
    /// # Returns
    ///
    /// A `Result` indicating success or failure.
    ///
    /// Buffers:
    /// - `0`: Write buffer.
    /// - Any other buffer: Not supported.
    fn allow_readwrite(
        &self,
        process_id: ProcessId,
        readwrite_number: usize,
        buffer: Option<WriteableProcessBuffer>,
    ) -> Result<(), ErrorCode>;

    ///
    /// Allows a process to provide a read-only buffer.
    ///
    /// # Arguments
    ///
    /// * `process_id` - The ID of the process providing the buffer.
    /// * `readonly_number` - The identifier for the buffer.
    /// * `buffer` - The buffer to be used for read-only operations.
    ///
    /// # Returns
    ///
    /// A `Result` indicating success or failure.
    ///
    /// Buffers:
    /// - `0`: Read buffer.
    /// - Any other buffer: Not supported.
    fn allow_readonly(
        &self,
        process_id: ProcessId,
        readonly_number: usize,
        buffer: Option<ReadableProcessBuffer>,
    ) -> Result<(), ErrorCode>{}

    ///
    /// Subscribes a process to a callback.
    ///
    /// # Arguments
    ///
    /// * `subscribe_number` - The identifier for the callback.
    /// * `callback` - The callback to be subscribed.
    /// * `process_id` - The ID of the process subscribing to the callback.
    ///
    /// # Returns
    ///
    /// A `Result` containing the previous callback if successful, or an error code if not.
    ///
    /// Callbacks:
    /// - `0`: General callback.
    /// - Any other callback: Not supported.
    fn subscribe(
        &self,
        subscribe_number: usize,
        callback: Option<Callback>,
        process_id: ProcessId,
    ) -> Result<Callback, (Option<Callback>, ErrorCode)>;
}

Flash Controller Driver Capsule

This module is vendor-specific. The common trait to be implemented is within kernel::hil::flash::Flash.

Image Loading

Overview

The Image Loading module is a component of the MCU Runtime SDK designed for managing SOC images. This module provides APIs for:

  • Loading SOC images to target components. The SOC images could come from a flash storage or from another platform capable of streaming images through PLDM T5 (e.g., a BMC Recovery Agent).
  • Verifying and authenticating the SOC Images through the Caliptra Core. Images that are loaded to the target SOC components will be authenticated using a mailbox command to the Caliptra core and are verified against the measurements in the SOC Manifest.

The diagram below provides an example of how the Caliptra subsystem, integrated with custom SOC elements (highlighted in green), facilitates the loading of SOC images to vendor components.

Custom SOC elements:

  • External Flash : A flash storage containing SOC manifest and the SOC images.
  • Vendor CPU: A custom CPU that executes code from a coupled Vendor RAM
  • Vendor RAM: RAM exclusively used by the Vendor CPU and is programmable via AXI bus.
  • Vendor Cfg Storage: A volatile memory storage used to contain vendor specific configurations.
  • SOC Images SOC Image 1 is a firmware for Vendor CPU and loaded to Vendor RAM. SOC Image 2 is a configuration binary to be loaded to Vendor Cfg Storage.
  • SOC Config : A register accessible by the MCU ROM to select appropriate source (flash or PLDM) for loading the SOC images.
  • Caliptra 'Go' Wire : A signal controlled by the Caliptra core routed to the reset line of the Vendor CPU.

flash_config

Supported Topologies

Definitions

  • Image Component : The binary data (e.g. firmware, configuration) identified uniquely by a component_id.
  • Image Location : The location where the image component is loaded (e.g. an instruction or data memory) identified by an image_id. The location should have an associated AXI address.

Image and Instance Mapping

  • Each image location will be associated to an image component in the SOC Manifest.

Topology 1: One Image Component per Image Location

In this topology, each image location has its own component. For example, 2 MCUs with their own instruction memories running 2 different firmware images.

flowchart TD
        img1a["Image Component (component_id=1)"]
        mem1a["Instruction Memory 1 (image_id=1)"]
        mcu1a["Vendor MCU 1"]
        img1a --> mem1a --> mcu1a

        img2["Image Component (component_id=2)"]
        mem2["Instruction Memory 2 (image_id=2)"]
        mcu2["Vendor MCU 2"]
        img2 --> mem2 --> mcu2

Another configuration for this topology is when the component is shared across multiple processing elements.

flowchart TD


        img1b["Image Component (component_id=1)"]
        mem1b["Instruction Memory 1 (image_id=1)"]
        mcu1b["Vendor MCU 1"]
        mcu2b["Vendor MCU 2"]
        img1b --> mem1b
        mem1b --> mcu1b
        mem1b --> mcu2b

Topology 2: One Image for multiple instances

flowchart TD

%% Image
img["Image Component(component_id=1)"]

%% Instruction Memories
mem1["Instruction Memory 1 (image_id=1)"]
mem2["Instruction Memory 2 (image_id=2)"]
mem3["Instruction Memory 3 (image_id=3)"]

%% Vendor MCUs
mcu1["Vendor MCU 1"]
mcu2["Vendor MCU 2"]
mcu3["Vendor MCU 3"]

%% Connections
img --> mem1 --> mcu1
img --> mem2 --> mcu2
img --> mem3 --> mcu3

Image Loader supports architectures with a combination of these topologies.

Image Loading Steps

The sequence diagram below shows the high level steps of loading MCU RT image and SOC images.

  • Red Arrows indicates actions taken by Caliptra RT
  • Purple Arrows indicates actions taken by MCU ROM
  • Blue Arrows indicates actions taken by MCU RT
  • Black Arrows indicates actions taken by the PLDM FW Update Agent

flash_config

The following steps are done for every SOC image:

flash_config

The following outlines the steps carried out by the MCU RT during the SOC boot process:

  1. MCU ROM reads a SOC Configuration register (implementation specific) to determine the source of the images to load (Flash/PLDM).

  2. Caliptra RT authorizes and loads Caliptra RT (refer to Caliptra Subsystem boot flow for the detailed steps).

  3. Caliptra switches to Caliptra RT FW.

  4. Caliptra RT indicates to Recovery I/F that it is ready for the SOC manifest image (refer to Caliptra Subsystem Recovery Sequence for the detailed steps).

  5. Retrieve SOC Manifest

    1. If image is coming from PLDM, PLDM FW Update Agent transfers SOC manifest to Recovery I/F
    2. If Image is coming from Flash, MCU ROM transfers SOC manifest from flash to Recovery I/F
  6. Caliptra RT transfers SOC Manifest to Caliptra Mailbox (MB) SRAM

  7. Caliptra RT will authenticate its image sitting in Caliptra MB SRAM

  8. Caliptra RT indicates to Recovery I/F that it is ready for the next image that should be the MCU RT Image (refer to Caliptra Subsystem Recovery Sequence for the detailed steps)..

  9. Retrieve MCU RT Image

    1. If Image is coming from PLDM, PLDM FW Update Agent sends MCU RT Image to Recovery I/F (refer to Caliptra Subsystem boot flow).
    2. If Image is coming from Flash, MCU ROM transfers MCU RT Image to Recovery I/F
  10. Caliptra RT FW will read the recovery interface registers over AXI manager interface and write the image to MCU SRAM aperture

  11. Caliptra RT FW will instruct its SHA accelerator to hash the MCU RT Image in the MCU SRAM.

  12. Caliptra RT FW will use this hash and verify it against the hash in the SOC manifest.

  13. Once the digest is verified, Caliptra RT FW sets the EXEC/GO bit.

  14. The EXEC/GO bit sets a Caliptra wire to MCU (as a consequence of setting the EXEC/GO bit in the previous step). When MCU detects this event, it sets a parameter using the FW HandOff table to indicate the image source (i.e. the image source where it booted from).

  15. MCU switches to MCU RT

  16. MCU RT retrieves the image source from HandOff table

For every image that needs to be loaded, user initiates a call to load an image identified by an image_id:

  1. MCU RT application initializes the image loader based on the boot_source. The application need to specify the boot_source as Flash or PLDM.
  2. Retrieve TOC 18.1 If boot_source = PLDM: 18.1.1 MCU starts the PLDM service. 18.1.2–18.1.3 PLDM Update agent queries device identifiers and receives a response. Device Identifier is vendor specific and should correspond to the device identifier in the PLDM Package in the Update Agent. 18.1.4–18.1.5 PLDM Update agent requests and receives firmware parameters. The PLDM firmware parameter to be used should be for the streaming boot component (refer to the PLDM Package documentation)
Field NameDescriptionValue to Use
ComponentActivationMethodsDefines activation methods supported by the FD.0x0000 (Automatic)
CapabilitiesDuringUpdateCapabilities of the firmware component during update.0x00000002 (Downgrade permitted)
ActiveComponentVersionStringDescribes the currently active version of the component.None
PendingComponentVersionStringDescribes the version of the component that is pending activation.None
ComponentIdentifierUnique ID for the component.0xFFFF
ComponentClassificationIndexUsed to distinguish identical component classifications with different instances.0x00
ActiveComponentComparisonStampComparison stamp for active version.0x00000000
ActiveComponentVersionStringTypeString type for active version string.0x01 (ASCII)
ActiveComponentVersionStringLengthLength of the active version string.0x00
ActiveComponentReleaseDateRelease date of the active component in YYYYMMDD format."00000000"
PendingComponentComparisonStampComparison stamp for pending version.None
PendingComponentVersionStringTypeString type for pending version string.0x01 (ASCII)
PendingComponentVersionStringLengthLength of the pending version string.0x00
PendingComponentReleaseDateRelease date of the pending version in YYYYMMDD format."00000000"
ComponentCountNumber of components on the FD.0x0001
ActiveComponentImageSetVersionStringTypeType for image set version string (active).0x01 (ASCII)
ActiveComponentImageSetVersionStringLengthLength of image set version string (active).0x00
PendingComponentImageSetVersionStringTypeType for image set version string (pending).0x01 (ASCII)
PendingComponentImageSetVersionStringLengthLength of image set version string (pending).0x00
ActiveComponentImageSetVersionStringVersion string of the active image set.None
PendingComponentImageSetVersionStringVersion string of the pending image set.None

18.1.6–18.1.7 PLDM sends a RequestUpdate, MCU responds with approval. 18.1.8–18.1.9 PLDM sends component information using PassTableComponent request, MCU responds with success. 18.1.10–18.1.11 PLDM sends an UpdateComponent, MCU acknowledges.

18.1.12. Retrieve TOC (Table of Contents) via PLDM starting from offset 0 until the end of TOC 18.1.12.1 MCU send RequestFirmwareData 18.1.12.2 PLDM responds with the TOC chunk

18.2 If boot_source = Flash: 18.2.1 MCU reads the TOC directly from Flash.

For each SOC instance to be loaded and authorized:

19.1 MCU RT initiates to load the an image component for location for image_id=1 by executing image_loader.load_and_authorize(image_id).

19.2–19.3 MCU retrieves the image_info using Caliptra mailbox for the provided image_id. This should return the load_address and the corresponding component_id associated to the image_id.

19.4 MCU determines the image offset and length using the TOC and component_id.

19.5. Transfer image in chunks (from image_offset to image_offset + image_size): 19.5.1.1 If from Flash: Flash sends the image chunk to MCU.

19.5.2.1–19.5.2.2 If from PLDM:

MCU requests firmware data from PLDM.

PLDM responds with the image chunk.

19.5.3 MCU writes the image chunk to the appropriate component destination (e.g., Vendor RAM or Config) identified by the load_address.

19.6 After download is completed, MCU sends an authorize(instance_id) command to Caliptra via Mailbox.

19.7 Caliptra forwards the image to the SHA Accelerator.

19.8 Caliptra verifies the image hash in SHA Acc against the value in the SOC manifest.

19.9 On successful verification, Caliptra returns a success response via the Mailbox to the MCU

20 After all SOC instances' images are loaded, MCU RT application deinitializes the image loader.

21 If boot_source = PLDM, MCU finalizes the PLDM firmware update sequence.

21.1–21.2 MCU sends VerifyComplete, and PLDM Update agent responds.

21.3–21.4 MCU sends ApplyComplete, and PLDM Update agent responds.

21.5–21.6 PLDM Update agent issues an Activate, and MCU confirms.

21.7 MCU stops the PLDM service.

Architecture

The following diagram presents the software stack architecture where the Image Loading module resides.

sw_stack

At the top of the stack, the user application interacts with the Image Loading module through high-level APIs. The user application is responsible for initiating the image loading and verification.

The Image Loading module provides the interface to retrieve and parse the manifest from the flash storage, and transfer SOC images from the storage to the target destination.

Application Interfaces

The APIs are presented as methods of the ImageLoader trait.

#![allow(unused)]


fn main() {
/// Trait defining the Image Loading module
pub trait ImageLoaderAPI {
    /// Loads the specified SoC image to a storage mapped to the AXI bus memory map.
    ///
    /// # Parameters
    /// image_id: The unsigned integer identifier for the image location
    ///
    /// # Returns
    /// - `Ok()`: Image has been loaded and authorized succesfully.
    /// - `Err(ErrorCode)`: Indication of the failure to load or authorize the image.
    async fn load_and_authorize(&self, image_id: u32) -> Result<(), ErrorCode>;


    /// Releases any resources held by ImageLoader
    /// Finalizes the PLDM Update and stops the PLDM service if started
    async fn finalize();

}


/// ImageLoader Implementation
pub struct ImageLoader {
    pub fn new(boot_source: ImageSource) -> Result<(), ErrorCode> {
        // if boot_source = Flash, read TOC from flash
        // if boot_source = PLDM, start PLDM service, and download TOC from offset 0
    }
}

impl ImageLoaderAPI for ImageLoader {
    // API Implementation
}

pub enum ImageSource {
   // Image is located in Flash
   Flash,
   // Image is retrieved via PLDM
   // The PLDM DeviceID to be used should be specified
   Pldm(DeviceId),
}




Example Usage:

/// Load image for instances 1 and 2 from flash
let image_loader = ImageLoader::new(ImageSource::Flash)?;
image_loader.load_and_authorize(1).await?;
image_loader.load_and_authorize(2).await?;
image_loader.finalize();

/// Load image for instances 3 and 4 from PLDM
let image_loader = ImageLoader::new(ImageSource::Pldm(DEVICE_ID))?;
image_loader.load_and_authorize(3).await?;
image_loader.load_and_authorize(4).await?;
image_loader.finalize();




}

Recovery Boot Flow

During system initialization, the recovery boot flow ensures that valid firmware is successfully loaded into the Caliptra core, MCU, and other SoC elements.

In flash-based systems, an A/B partitioning mechanism is used to enhance reliability by allowing fallback to a working firmware image in case of boot failure.

The method of selecting which partition to boot from is system-specific. For example, the active partition may be determined by a hardware pin, a soft fuse or register, or an entry in the flash partition table.

Caliptra Boot Flow

This section describes the actions taken by the Caliptra ROM and Caliptra FMC/Runtime (RT) during boot, particularly in handling boot failures.

If a corrupted or unauthorized Caliptra FMC+RT image is loaded, the Caliptra ROM asserts an error signal to the MCU, which initiates a recovery mechanism to provide the Caliptra core with a valid firmware image.

If the firmware image fails to execute properly (e.g., hangs), the Caliptra watchdog triggers a timeout, causing an error to be asserted to the MCU ROM, which then initiates the recovery process.

flowchart TD
    CaliptraFlow([Start]) -->|Reset Deasserted by MCU| Start
    Start[Execute Caliptra ROM] --> StartWD[Start Watchdog]
    StartWD --> DisableMCURT[Clear MCU Exec/Go Bit]
    DisableMCURT --> SetReady[Set RECOVERY_STATUS = Awaiting Image]

    SetReady -->|Recovery Image Available| DownloadFMC[Stream FMC + RT from Recovery Interface]
    DownloadFMC --> AuthFMC[Authenticate FMC + RT]

    AuthFMC -->|Auth Pass| BootRuntime[Execute FMC + RT]
    AuthFMC -->|Auth Fail| ClearSignals[Set RECOVERY_STATUS = AuthFailed]

    ClearSignals -->|Warm Boot| NonFatal[Set cptra_error_non_fatal]
    ClearSignals -->|Cold Boot| Fatal[Set cptra_error_fatal]

    NonFatal --> WaitReset[Wait for MCU Reset] -->|Reset| Start
    Fatal --> WaitReset

    BootRuntime --> LoadManifest[Load & <br>Auth SoC Manifest]
    LoadManifest -->|Auth Pass| LoadMCURT[Load & Auth MCU RT]
    LoadMCURT -->|Auth Pass| StartMCURT[Set Exec/Go for MCU<br>Set RECOVERY_STATUS = 'Recovery successful']
    LoadMCURT -->|Auth Fail| ClearSignals

    CPTRA_WD_TIMEOUT[Watchdog Timeout] --> Fatal

    StartMCURT --> Done_cptra([Done])

MCU ROM Boot Flow

This section describes how the MCU ROM handles failures during the loading of Caliptra firmware and MCU Runtime firmware.

To avoid infinite reboot loops caused by a faulty firmware image, each partition maintains a boot count, which tracks how many times the system has attempted to boot from it.

On power-up, the MCU ROM checks the partition table for the status and boot count of the currently active partition.

If the count is below a predefined threshold (e.g., MAX_COUNT), the system increments the count and attempts to boot the partition.

If the count exceeds MAX_COUNT, the partition is marked as unhealthy, and the MCU triggers a recovery flow, switching to another valid partition if available.

After a successful boot, the partition status is marked as "Boot Successful". Future boots may reset the count or ignore it.

This mechanism ensures automatic rollback to a working firmware without manual intervention.

flowchart TD
    MCUFlow([Start])-->MCUROM
    TIMEOUT([Watchdog<br>Timeout])-->MCUROM
    MCUROM[Execute MCU ROM]
    MCUROM-->DeassertCaliptraReset[Deassert Caliptra<br>Reset] --> StartMCUWD[Start MCU<br>Watchdog]-->
    READ_PART[Retrieve Active<br>Partition]-->CHECK_BOOT_COUNT

    CHECK_BOOT_COUNT[Check active partition<br>boot count<br>and status] --> |Status =='Valid'<br>&&<br>Boot count >= MAX_COUNT| Recover
    CHECK_BOOT_COUNT --> |Status =='Boot Successful'<br>i.e. partition booted before| CHECK_RESET_REASON
    CHECK_BOOT_COUNT --> |Status='Valid'<br>&&<br>Boot count < MAX_COUNT| INCREMENT_COUNT[Increment Boot count] -->  CHECK_RESET_REASON

    CHECK_RESET_REASON[Get RESET_REASON]-->|Cold Boot|LoadFMC[Load Caliptra<br>FMC + Runtime<br>from Active Flash<br>Partition]
    CHECK_RESET_REASON-->|Warm Boot|LoadMCURTWarm[Load MCU RT<br>from Active Partition]-->AuthorizeMCURT[Authorize MCU RT]

    AuthorizeMCURT-->|Auth Pass|MCURTStart
    AuthorizeMCURT-->|Auth Fail|Recover
    LoadFMC --> WaitRecoveryReady[Wait for<br>RECOVERY_STATUS<br>to be 'Awaiting<br>Image']
        --> |RECOVERY_STATUS =<br>'Awaiting Image'| StreamFMC[Stream Caliptra<br>FMC + Runtime<br>to Recovery Interface]
    StreamFMC --> WaitForCaliptraBoot[Wait Caliptra<br>RT Boot]

    WaitForCaliptraBoot --> |cptra_error_non_fatal<br>cptra_error_fatal| Recover
    WaitForCaliptraBoot --> |Caliptra RT Booted,<br>RECOVERY_STATUS =<br>'Recovery successful'| StreamManifest[Load Manifest<br>from Active<br>partition and<br>stream to Recovery<br>Interface]
        --> StreamMCURT[Load MCU RT<br>from Active<br>partition and<br>stream to Recovery<br>Interface]

    StreamMCURT--> WaitMCURTBoot[Wait for MCU<br>Exec/Go Bit<br>to be set]

    WaitMCURTBoot-->|Exec Go/Bit<br>set|MCURTStart([MCU Runtime Boot Flow])


    Recover([MCU Recovery Flow])

MCU Recovery Flow

When a boot failure occurs (e.g., due to exceeding maximum boot attempts, authentication failure, or watchdog timeout), the MCU Recovery Flow is triggered to restore system operability by switching to a known-good firmware partition.

If the active partition fails to boot, the system attempts to revert to the other available and valid partition.

flowchart TD
    Start([MCU Recovery Flow])--> InvalidateActivePartition[Set Active<br>Partition as<br>Invalid] --> SetStatusBootFailed[Set Partition<br>Status as Boot Failed]
        --> SetNewActivePartition[Set other valid<br>partition, if any,<br>as Active]
        --> Reset[Assert Caliptra Reset]
        --> MCUReboot[MCU Reboot]

MCU Runtime and SoC Image(s) Boot Flow

When the MCU ROM hands off execution to the MCU Runtime firmware, it keeps the MCU watchdog timer active. If the watchdog expires at any point—indicating a hang or prolonged stall—the MCU will be reset. Upon reset, the MCU ROM will evaluate the boot count and initiate the recovery flow if necessary (e.g., if the boot count exceeds the defined threshold).

The MCU Runtime firmware is responsible for loading and authorizing the remaining SoC images. If all SoC images are successfully loaded, authenticated, and booted, the partition's boot status can be marked as "Boot Successful." However, if any of the images fail to load or validate, the system behavior is left to user-defined policy. For example, the user may choose to trigger the recovery flow to fall back to a previous version of the SoC images or halt execution entirely to prevent further operation under an unstable configuration.

flowchart TD
    Start([MCU Runtime<br>Boot Flow])-->MCURuntime
    MCURuntime[Execute MCU<br>Runtime FW]
    MCURuntime-->StartWatchdog[Start MCU<br>Watchdog]
    StartWatchdog-->LoadAndAuthorizeSocImage[Load and<br>Authorize SOC<br>Images]

    LoadAndAuthorizeSocImage --> |Auth Pass| SUCCESSFUL_BOOT[Set Partition <br>Status as <br>'Boot Successful'] --> Done([Done])
    LoadAndAuthorizeSocImage --> |Auth Fail| UserDefinedOption[User Defined<br>Logic - e.g.<br>Switch to other<br>partition or Hang]

    TIMEOUT([MCU  Watchdog<br>Timeout])-->MCURESET[MCU Reset]-->MCUROM([MCU ROM])

References:

  • OCP Recovery Interface [https://www.opencompute.org/documents/ocp-recovery-document-1p0-final-1-pdf]
  • Caliptra Error and Recovery Flow [https://github.com/chipsalliance/caliptra-rtl/blob/main/docs/CaliptraIntegrationSpecification.md#caliptra-error-and-recovery-flow]
  • Fatal and Non-Fatal Errors [https://github.com/chipsalliance/Caliptra/blob/main/doc/Caliptra.md#error-reporting-and-handling]

MCTP Stack

The Caliptra subsystem supports SPDM, PLDM, and Caliptra vendor-defined message protocols over MCTP.

The MCTP base protocol is implemented as a Tock Capsule, which also handles the essential MCTP Control messages. Additionally, it offers a syscall interface to userspace, enabling the sending and receiving of MCTP messages for other supported protocols. Caliptra MCTP endpoint has only one EID and supports dynamic assignment by the MCTP bus owner.

MCTP Packets are delivered over physical I3C medium using I3C transfers. Caliptra MCTP endpoint always plays the role of I3C Target and is managed by an external I3C controller. Minimum transmission size is based on the MCTP baseline MTU (for I3C it is 69 bytes: 64 bytes MCTP payload + 4 bytes MCTP header + 1 byte PEC). Larger than the baseline transfer may be possible after discovery and negotiation with the I3C controller. The negotiated MTU size will be queried from the I3C Target peripheral driver by MCTP capsule.

MCTP Send Sequence

sequenceDiagram
    participant Application
    participant VirtualMCTPDriver
    participant MuxMCTPDriver
    participant MCTPI3CBinding
    participant I3CTarget
    participant I3CController
    Application--)VirtualMCTPDriver: Send request/response <br/>message to a destination EID
    VirtualMCTPDriver->>VirtualMCTPDriver: mctp_sender.send_message. <br/>Sets the mctp_sender context.
    VirtualMCTPDriver->> MuxMCTPDriver: mctp_mux_sender.send() <br/> Adds mctp_sender to the tail of sender_list.
    loop Packetize the entire message payload
        MuxMCTPDriver->>MuxMCTPDriver: Add MCTP Transport header.
        MuxMCTPDriver->>MCTPI3CBinding: transmit() MCTP packet
        MCTPI3CBinding->>MCTPI3CBinding: Compute PEC and add <br/>to the end of the packet.
        MCTPI3CBinding->>I3CTarget: transmit() MCTP packet with PEC
        alt IBI mode enabled
            I3CTarget->>I3CController: IBI with MDB
            I3CController--)I3CTarget: Private Read Request
            I3CTarget--)I3CController: MCTP packet
            I3CTarget->>I3CTarget: result = SUCCESS
        else Polling mode
            I3CTarget->>I3CTarget: 1. Set the pending read bit. <br/> 2. Set the alarm for tx_timeout time
            alt Controller sent GETSTATUS CCC
                I3CTarget--)I3CController: Report nonzero value
                I3CController--)I3CTarget: Private Read Request
                I3CTarget--)I3CController: MCTP packet
                I3CTarget->>I3CTarget: set result = SUCCESS
            else alarm fired
                I3CTarget->>I3CTarget: set result = TIMEOUT
            end
        end
        I3CTarget->>MCTPI3CBinding: send_done() with result. <br/>Return packet buffer.
        MCTPI3CBinding->>MuxMCTPDriver: send_done() with result.<br/> Return packet buffer.
    end

The send stack is as shown in the picture below:

The MCTP Send stack

MCTP Receive sequence

sequenceDiagram
    participant I3CController
    participant I3CTarget
    participant MCTPI3CBinding
    participant MuxMCTPDriver
    participant VirtualMCTPDriver
    participant Application
    loop Assemble packets until eom
        I3CController--)I3CTarget: I3C Private Write transfer
        I3CTarget->>MCTPI3CBinding: if no rx buffer, call write_expected() callback
        MCTPI3CBinding->> MuxMCTPDriver: write_expected() callback
        MuxMCTPDriver->>MCTPI3CBinding: set_rx_buffer() with buffer to receive packet
        MCTPI3CBinding->> I3CTarget: set_rx_buffer() with buffer to receive the packet
        I3CTarget--) I3CController : Send ACK
        I3CController--)I3CTarget: MCTP packet
        Note over I3CController, I3CTarget: Receive entire MCTP packet <br/>including the PEC until Sr/P.
        I3CTarget->> MCTPI3CBinding: receive() to receive the packet
        MCTPI3CBinding ->> MCTPI3CBinding: Check the PEC, and pass the packet <br/>with MCTPHeader to Mux MCTP layer
        MCTPI3CBinding->>MuxMCTPDriver: receive() to receive the packet
        MuxMCTPDriver->>MuxMCTPDriver: Process MCTP transport header on packet, <br/> and assemble if matches any pending Rx states<br/>or handle MCTP control msg.
    end
    MuxMCTPDriver->>VirtualMCTPDriver: receive() call to receive the assembled message.
    VirtualMCTPDriver--)Application: Schedule upcall to receive the request/response.

(The receive stack picture is nearly identical to the send stack above.)picture below:

Syscall Library in userspace

Userspace applications can use syscall library in to send and receive MCTP messages. The following APIs are provided by the MCTP syscall library. Each user space application will instantiate the Mctp module with appropriate driver number. The Mctp module provides the following APIs to send and receive MCTP messages.

//! The MCTP library provides the interface to send and receive MCTP messages.
//! The MCTP library is implemented as an async library to allow the userspace application to send and receive MCTP messages asynchronously.
//!
//! Usage
//! -----
//!```Rust
//! use mctp::Mctp;
//!
//! const SPDM_MESSAGE_TYPE: u8 = 0x5;
//! const SECURE_SPDM_MESSAGE_TYPE: u8 = 0x6;
//!
//! #[embassy_executor::task]
//! async fn async_main() {
//!     /// Initialize the MCTP driver with the driver number
//!     let mctp = Mctp::<TockSyscalls>::new(MCTP_SPDM_DRIVER_NUM);
//!
//!     loop {
//!         /// Receive the MCTP request
//!         let mut rx_buf = [0; 1024];
//!         let res = mctp.receive_request(&mut rx_buf).await;
//!         match res {
//!             Ok((req_len, msg_info)) => {
//!                 /// Process the received message
//!                 /// ........
//!                 /// Send the response message
//!                 let mut tx_buf = [0; 1024];
//!                 let result = mctp.send_response(&tx_buf, msg_info).await;
//!                 match result {
//!                     Ok(_) => {
//!                         /// Handle the send response success
//!                     }
//!                     Err(e) => {
//!                         /// Handle the send response error
//!                     }
//!                 }
//!             }
//!             Err(e) => {
//!                 /// Handle the receive request error
//!             }
//!         }
//!     }
//! }
//!```

/// mctp/src/lib.rs
type EndpointId = u8;
type Tag = u8;

pub struct MessageInfo {
    eid: EndpointId,
    tag: Tag,
}

pub struct Mctp<S: Syscalls> {
    syscall: PhantomData<S>,
    driver_num: u32,
}

impl<S: Syscalls> Mctp<S> {
    /// Create a new instance of the MCTP driver
    ///
    /// # Arguments
    /// * `driver_num` - The driver number for the MCTP driver
    ///
    /// # Returns
    /// * `Mctp` - The MCTP driver instance
    pub fn new(drv_num: u32) -> Self;
    /// Check if the MCTP driver for a specific message type exists
    ///
    /// # Returns
    /// * `bool` - `true` if the driver exists, `false` otherwise
    pub fn exists() -> Result<(), ErrorCode>;
    /// Receive the MCTP request.
    /// Receives a message from any source EID. The user should use the returned MessageInfo to send a response.
    ///
    /// # Arguments
    /// * `req` - The buffer to store the received request payload
    ///
    /// # Returns
    /// * `(u32, MessageInfo)` - On success, returns tuple containing length of the request received and the message information containing the source EID, message tag
    /// * `ErrorCode` - The error code on failure
    pub async fn receive_request(&self, req: &mut [u8]) -> Result<(u32, MessageInfo), ErrorCode>;
    /// Send the MCTP response to an endpoint
    ///
    /// # Arguments
    /// * `resp` - The buffer containing the response payload
    /// * `info` - The message information containing the destination EID, message tag which was received in `receive_request` call
    ///
    /// # Returns
    /// * `()` - On success
    /// * `ErrorCode` - The error code on failure
    pub async fn send_response(&self, resp: &[u8], info: MessageInfo) -> Result<(), ErrorCode>;
    /// Send the MCTP request to the destination EID
    /// The function returns the message tag assigned to the request by the MCTP Capsule.
    /// This tag will be used in the `receive_response` call to receive the corresponding response.
    ///
    /// # Arguments
    /// * `dest_eid` - The destination EID to which the request is to be sent
    /// * `req` - The payload to be sent in the request
    ///
    /// # Returns
    /// * `Tag` - The message tag assigned to the request
    /// * `ErrorCode` - The error code on failure
    pub async fn send_request(&self, dest_eid: u8, req: &[u8]) -> Result<Tag, ErrorCode>;
    /// Receive the MCTP response from an endpoint
    ///
    /// # Arguments
    /// * `resp` - The buffer to store the received response payload from the endpoint
    /// * `tag` - The message tag to match against the response message
    /// 
    /// # Returns
    /// * `(u32, MessageInfo)` - On success, returns tuple containing length of the response received and the message information containing the source EID, message tag
    /// * `ErrorCode` - The error code on failure
    pub async fn receive_response(&self, resp: &mut [u8], tag: Tag) -> Result<(u32, MessageInfo), ErrorCode>;
}

Userspace Driver and Virtualizer layer

During the board initialization, three separate instances of the virtual MCTP driver are created, each assigned a unique driver number. These instances correspond to the SPDM, PLDM, and Vendor Defined message types. Each driver instance is designed to communicate directly with its corresponding protocol application.

/// define custom driver numbers for Caliptra
pub enum NUM {
    ...
    // Mctp
    MctpSpdm                  = 0xA0000,
    MctpSecureSpdm            = 0xA0001,
    MctpPldm                  = 0xA0002,
    MctpVenDef                = 0xA0003,
    ...
}

/// mctp/driver.rs
pub const MCTP_SPDM_DRIVER_NUM: usize = driver::NUM::MctpSpdm;
pub const MCTP_PLDM_DRIVER_NUM: usize = driver::NUM::MctpPldm;
pub const MCTP_VENDEF_DRIVER_NUM: usize = driver::NUM::MctpVenDef;

Syscalls provided

Virtual MCTP driver provides system calls to interact with the userspace application. This layer implements the SyscallDriver trait.

The following are the list of system calls provided by the MCTP Capsule.

  1. Read-Write Allow

    • Allow number: 0
      • Description: Used to set up the Rx buffer for receiving the MCTP Request/Response message payload.
      • Argument: Slice into which the received MCTP Request/Response message should be stored.
  2. Read-Only Allow

    • Allow number: 0
      • Description: Used to set up the Tx buffer for sending the MCTP Request/Response message payload.
      • Argument: Slice containing the MCTP message payload to be transmitted.
  3. Subscribe

    • Subscribe number 0:
      • Description: Callback when message is received.
      • Argument 1: The callback
      • Argument 2: App specific data
    • Subscribe number 1:
      • Description: Callback when message is transmitted.
      • Argument 1: The callback
      • Argument 2: App specific data
  4. Command

    • Command number 0:
      • Description: Existence check
    • Command number 1:
      • Description: Receive Request
      • Argument1 : Source EID
      • Argument2 : Message Tag
    • Command number 2:
      • Description: Receive Response
      • Argument1 : Source EID
      • Argument2 : Message Tag
    • Command number 3:
      • Description: Send Request
      • Argument1 : Destination EID
      • Argument2 : Message Tag
    • Command number 4:
      • Description: Send Response
      • Argument1 : Destination EID
      • Argument2 : Message Tag

Virtualized Layer

MCTP capsule stores the following process context specific information in the Process's grant region.

enum OperationType {
    Tx,
    Rx,
    Idle
}
struct OperationCtx {
    msg_tag : u8,
    peer_eid : u8,
    is_busy: bool,
    op_type:OperationType,
}

#[derive(default)]
pub struct App {
    pending_rx: OperationCtx,
    bound_msg_type : u8,
}

/// Implements userspace driver for a particular msg_type.
pub struct VirtualMCTPDriver {
    mctp_sender: &dyn MCTPSender,
    apps : Grant<App, 2 /*upcalls*/, 1 /*allowro*/, 1/*allow_rw*/>,
    app_id: Cell<Option<ProcessID>>,
    msg_types: [u8],
    kernel_msg_buffer: MapCell<SubSliceMut<'static, u8>>,
}

MCTP Send state

/// The trait that provides an interface to send the MCTP messages to MCTP kernel stack.
pub trait MCTPSender {
    /// Sets the client for the `MCTPSender` instance.
    /// In this case it is MCTPTxState which is instantiated at the time of
    fn set_client(&self, client: &dyn MCTPTxClient);

    /// Sends the message to the MCTP kernel stack.
    fn send_msg(&self, dest_eid: u8, msg_tag: u8, msg_payload: SubSliceMut<'static, u8>) -> Result<(), SubSliceMut<'static, u8>>;
}

/// This is the trait implemented by VirtualMCTPDriver instance to get notified after
/// message is sent.
/// The 'send_done' function in this trait is invoked after the MCTPSender
/// has completed sending the requested message.
pub trait MCTPTxClient {
    fn send_done(&self, msg_tag: Option<u8>, result: Result<(), ErrorCode>, msg_payload: SubSliceMut<'static, u8> )
}

pub struct MCTPTxState<M:MCTPTransportBinding> {
    /// MCTP Mux driver reference
    mctp_mux_sender: &MuxMCTPDriver<M>,
    /// Client to invoke when send done. This is set to the corresponding VirtualMCTPDriver instance.
    client: OptionalCell<&dyn MCTPTxClient>,
    /// next MCTPTxState node in the list
    next: ListLink<MCTPTxState<M: MCTPTransportBinding>>,
    /// The message buffer is set by the virtual MCTP driver when it issues the Tx request.
    msg_payload: MapCell<SubSliceMut<'static, u8>>,
}

MCTP Receive state

/// This is the trait implemented by VirtualMCTPDriver instance to get notified of
/// the messages received on corresponding msg_type.
pub trait MCTPRxClient {
    fn receive(&self, dst_eid: u8, msg_type: u8, msg_tag: u8, msg_payload: &[u8]);
}

/// Receive state
pub struct MCTPRxState {
    /// Client (implements the MCTPRxClient trait)
    client: OptionalCell<&dyn MCTPRxClient>,
    /// static Message buffer
    msg_payload: MapCell<'static, [u8]>,
    /// next MCTPRxState node
    next: ListLink<MCTPRxState>,
}

MCTP Mux Layer

The MCTP Mux layer acts as the sole Tx client to the rest of the MCTP stack. The MuxMCTPDriver struct contains a list of statically allocated sender structs that implement the MCTPSender trait. This struct provides methods to packetize the message of the inflight (popped from head of the list) send request. The MCTP Mux layer also contains a list of statically allocated receiver structs that implement the MCTPRxClient trait. This struct provides methods to assemble the received packets into a complete message.

If the message originates from the device (with msg_tag = 0x8), a new msg_tag will be allocated and provided to the client via the send_done callback. This msg_tag is passed to the application layer which uses it to issue the receive response command. For response messages, where msg_tag values range between 0 and 7, the same value is used to encapsulate the MCTP transport header on each packet.

MCTP Mux layer is the single receive client for the MCTP Device Layer. This layer is instantiated with a single contiguous buffer for Rx packet of size kernel::hil:i3c::MAX_TRANSMISSION_UNIT. The Rx buffer is provided to the I3C target driver layer to receive the packets when the I3C controller initiates a private write transfer to the I3C Target.

The virtualized upper layer ensures that only one message is transmitted per driver instance at a time. Receive is event based. The received packet in the Rx buffer is matched against the pending receive requests by the use

/// The MUX struct manages multiple MCTP driver users (clients).
pub struct MuxMCTPDriver<M: MCTPTransportBinding> {
    /// Reference to the MCTP transport binding layer that implements the MCTPTransportBinding trait.
    mctp_device: &dyn M,
    /// Global message tag. Increment by 1 for next tag up to 7 and wrap around.
    next_msg_tag: u8,
    /// Local EID assigned to the MCTP endpoint.
    local_eid: u8,
    /// Maximum transmission unit (MTU) size.
    mtu: u8,
    /// List of outstanding send requests
    sender_list: List<MCTPTxState<M>>,
    /// List of outstanding receive requests
    receiver_list: List<MCTPRxState>,
    /// Static buffer for tx packet. (may not be needed)
    tx_pkt_buffer: MapCell<SubSliceMut<'static, u8>>,
    /// Static buffer for rx packet
    rx_pkt_buffer: MapCell<SubSliceMut<'static, u8>>,
}

MCTP Transport binding layer

The following is the generic interface for the MCTP physical transport binding layer. Implementer of this trait will add physical medium specific header/trailer to the MCTP packet.

/// This trait contains the interface definition
/// for sending the MCTP packet through MCTP transport binding layer.
pub trait MCTPTransportBinding {
	/// Set the client that will be called when the packet is transmitted.
	fn set_tx_client(&self, client: &TxClient);

	/// Set the client that will be called when the packet is received.
	fn set_rx_client(&self, client: &RxClient);

	/// Set the buffer that will be used for receiving packets.
	fn set_rx_buffer(&self, rx_buf: &'static mut [u8]);

	fn transmit(&self, tx_buffer: &'static mut [u8]);

	/// Enable/Disable the I3C target device
	fn enable();
	fn disable();

	/// Get the maximum transmission unit (MTU) size.
	fn get_mtu_size() -> usize;
}

MCTP I3C Transport binding layer is responsible for checking the PEC for received packets and adding the PEC for transmitted packets over the I3C medium. It is mostly a passthrough for the MCTP Base layer except, it will need the I3C target device address for PEC calculation.

This layer is also a sole Rx and Tx client for the I3C Target device driver.

pub struct MCTPI3CBinding {
	/// Reference to the I3C Target device driver.
	mctp_i3c : &dyn I3CTarget,
	rx_client: OptionCell<&dyn RxClient>,
	tx_client: OptionCell<&dyn TxClient>,
	/// I3C Target device address needed for PEC calculation.
	device_address: u8,
}

HIL for I3C Target Device

The following trait defined standard and shared interface for I3C Target hardware driver.

/// hil/i3c.rs

pub trait TxClient {
	/// Called when the packet has been transmitted. (Calls this after the ACK is received from Controller)
	fn send_done(&self,
		  tx_buffer: &'static mut [u8],
		  acked: bool,
		  result : Result<(), ErrorCode>);
}

pub trait RxClient {
	/// Called when a complete MCTP packet is received and ready to be processed.
	fn receive(&self, rx_buffer: &'static mut [u8], len : usize);

	/// Called when the I3C Controller has requested a private Write by addressing the target
	/// and the driver needs buffer to receive the data.
	/// The client should call set_rx_buffer() to set the buffer.
	fn write_expected(&self);
}


pub trait I3CTarget {
	/// Set the client that will be called when the packet is transmitted.
	fn set_tx_client(&self, client: &TxClient);

	/// Set the client that will be called when the packet is received.
	fn set_rx_client(&self, client: &RxClient);

	/// Set the buffer that will be used for receiving packets.
	fn set_rx_buffer(&self, rx_buf: &'static mut [u8]);
I
	/// Transmit a packet.
	fn transmit(&self, tx_buf: &'static mut[u8], len : usize) -> Result<(), (ErrorCode, &'static mut [u8])>;

	/// Enable/disable the I3C target device
	fn enable();
	fn disable();

	/// Get the maximum transmission unit (MTU) size.
	fn get_mtu_size() -> usize;

	/// Get the address of the I3C target device. Needed for PEC calculation.
	fn get_address() -> u8;
}

SPDM

The Security Protocol and Data Model (SPDM) is a protocol designed to ensure secure communication between hardware components by focusing on mutual authentication and the establishment of secure channels over potentially insecure media. SPDM enables devices to verify each other's identities and configurations, leveraging X.509v3 certificates to ensure cryptographic security. Designed for interoperability, it can work across various transport and physical media, often utilizing the Management Component Transport Protocol (MCTP). This protocol is especially valuable in environments where secure hardware communication is crucial, such as data centers and enterprise systems.

Specifications

SpecificationDocument Link
Security Protocol and Data ModelDSP0274
Secured Messages using SPDMDSP0277
SPDM over MCTP Binding SpecificationDSP0275
Secured Messages using SPDM over MCTP BindingDSP0276

SPDM Protocol Sequence

sequenceDiagram
    participant Requester
    participant Responder

    Requester->>Responder: GetVersion
    Responder-->>Requester: Version
    Requester->>Responder: GetCapabilities
    Responder-->>Requester: Capabilities
    Requester->>Responder: NegotiateAlgorithms
    Responder-->>Requester: Algorithms
    opt If supported
        Requester->>Responder: GetDigests
        Responder-->>Requester: Digests
    end
    opt If needed
        Requester->>Responder: GetCertificate
        Responder-->>Requester: Certificate
    end
    opt If supported
        Requester->>Responder: Challenge
        Responder-->>Requester: ChallengeAuth
    end
    opt If supported
        Requester->>Responder: GetMeasurements
        Responder-->>Requester: Measurements
    end
    opt If supported
        Requester->>Responder: KeyExchange
        Responder-->>Requester: KeyExchangeRsp
    end
    rect rgb(255, 255, 204)
        note right of Requester: Secure Session
        opt If supported
            Requester->>Responder: Finish
            Responder-->>Requester: FinishRsp
        end
    end

Class Diagram

classDiagram
    direction RL
    MCTP Transport <|--|> SPDMResponder: processRequest() / sendResponse()
    SecureSessionMgr <|-- SPDMResponder: processSecureMessage()
    TranscriptMgr <|-- SPDMResponder
    TranscriptMgr <|-- SecureSessionMgr
    class SPDMResponder{
      - transcriptMgr
      - sessionMgr
      + processRequest()
      + sendResponse()
    }
    class SecureSessionMgr {
      -transcriptMgr
      +TraitMethods
    }
    class TranscriptMgr{
      +TraitMethods
    }

SPDM Responder

The Responder is responsible for receiving and processing requests from the Requestor. It authenticates the Requestor's identity, attests its own state and configuration, and establishes a secure communication channel. The Responder uses cryptographic techniques, such as validating X.509v3 certificates, to ensure the integrity and confidentiality of the exchanged data.

Responder supported messages

The SPDM Responder supports the following messages:

MessageDescription
VERSIONRetrieves version information
CAPABILITIESRetrieves SPDM capabilities
ALGORITHMSRetrieves the negotiated algorithms
DIGESTSRetrieves digest of the certificate chains
CERTIFICATERetrieves certificate chains
MEASUREMENTSRetrieves measurements of elements such as intenral state
KEY_EXCHANGE_RSPRetrieves the responder's public key information
FINISH_RSPProvide key confirmation, bind the identity of each party to the exchanged keys
END_SESSION_ACKEnd session acknowledgment
ERRORError message

Responder Interface

pub struct SpdmResponder<T: MctpTransport, U: SpdmTranscriptManager, V: SpdmSecureSessionManager> {
    transport: T,
    transcript_manager: U,
    session_manager: V,
    data_buffer: [u8; MAX_SPDM_MESSAGE_SIZE],
}

impl<T: MctpTransport, U: SpdmTranscriptManager, V: SpdmSecureSessionManager> SpdmResponder<T, U, V> {
    pub fn new(transport: T, transcript_manager: U, session_manager: V) -> Self {
        SpdmResponder {
            transport,
            transcript_manager,
            session_manager,
            data_buffer: [0; MAX_SPDM_MESSAGE_SIZE],
        }
    }

    pub async fn handle_request(&mut self, request_info: u32) {
        // request_info: Bits[16:23] Message Type [SPDM | Secure SPDM]
    }
}

Transcript Manager

The Transcript Manager is for managing the transcript and the transcript hash. The transcript is a sequential concatenation of prescribed full messages or message fields. The transcript hash is the cryptographic hash of this transcript, computed using the negotiated hash algorithm. This component ensures the integrity and authenticity of SPDM communications by managing these essential elements.

Transcript Manager Interface

pub trait SpdmTranscriptManager {
    /// Set the hash algorithm. The algorithm can be set only once.
    ///
    /// # Parameters
    /// - `hash_algo`: Hash algorithm to set.
    ///
    /// # Returns
    /// - `Result<(), SpdmError>`: Returns `Ok(())` if the hash algorithm was set, or an error code.
    fn set_hash_algo(&self, hash_algo: HashType) -> Result<(), SpdmError>;

    /// Set the SPDM negotiated version to be used for communication.
    ///
    /// # Parameters
    /// - `spdm_version`: SPDM negotiated version.
    fn set_spdm_version(&self, spdm_version: u8);

    /// Update the transcript with a message.
    ///
    /// # Parameters
    /// - `context_type`:        Transcript context to update.
    /// - `message`:             Message to add to the transcript.
    /// - `use_session_context`: Use session context to update an SPDM session transcript.
    /// - `session_idx`:         SPDM session index.
    ///
    /// # Returns
    /// - `Result<(), SpdmError>`:
    /// Returns `Ok(())` if the message was added to the transcript successfully, or an error code.
    async fn update(
        &self,
        context_type: SpdmTranscriptManagerContextType, // [VCA, M1M2, L1L2, TH]
        message: &[u8],
        use_session_context: bool,
        session_idx: u8,
    ) -> Result<(), SpdmError>;

    /// Get the hash based on the hash type. The hashing operation is finished if `finish_hash` is set
    /// to `true`. In that case, an additional call to update will start a new hashing operation.
    /// If `finish_hash` is set to `false`, the hash is not finalized and can be updated with additional
    /// calls to update.
    ///
    /// # Parameters
    /// - `context_type`:        Transcript context type to get the hash from.
    /// - `finish_hash`:         Flag to indicate to finish the hash.
    /// - `use_session_context`: Use session context to update an SPDM session transcript.
    /// - `session_idx`:         SPDM session index.
    /// - `hash`:                Buffer to copy the hash to.
    ///
    /// # Returns
    /// - `Result<Vec<u8>, SpdmError>`: Returns the hash if the operation was successful, or an error code.
    fn get_hash(
        &self,
        context_type: SpdmTranscriptManagerContextType, // [VCA, M1M2, L1L2, TH]
        finish_hash: bool,
        use_session_context: bool,
        session_idx: u8,
        hash: &mut [u8]
    ) -> Result<(), SpdmError>;

    /// Reset a transcript context.
    ///
    /// # Parameters
    /// - `context_type`:        Transcript context to reset.
    /// - `use_session_context`: Use session context to update an SPDM session transcript.
    /// - `session_idx`:         SPDM session index.
    fn reset_transcript(
        &self,
        context_type: SpdmTranscriptManagerContextType, // [VCA, M1M2, L1L2, TH]
        use_session_context: bool,
        session_idx: u8,
    );
}

SPDM Secure Session Manager

The SPDM Secure Session Manager is responsible for managing secure sessions within the SPDM protocol framework. It provides mechanisms to create, release, and retrieve secure sessions. The manager can set and query the state of a session, ensuring secure communication between devices. It generates necessary cryptographic keys, including shared secrets, handshake keys, and data keys, through asynchronous methods. Additionally, it verifies the integrity and optionally decrypts secure messages, and encodes messages with appropriate security measures. The manager also tracks session validity and can reset session states and identifiers as needed, ensuring robust and secure session management.

Secure Session Manager Interface

pub trait SpdmSecureSessionManager {
    /// Create a new SPDM secure session.
    ///
    /// # Parameters
    /// - `session_id`:      Session Id for the session.
    /// - `is_requester`:    True if the session is for the requester, false otherwise.
    /// - `connection_info`: SPDM connection info.
    ///
    /// # Returns
    /// - `Option<&SpdmSecureSession>`:
    ///    A pointer to the created SPDM secure session or `None` if the session could not be created.
    fn create_session(
        &self,
        session_id: u32,
        is_requester: bool,
        connection_info: &SpdmConnectionInfo,
    ) -> Result<&SpdmSecureSession, SpdmError>;


    /// Release an SPDM secure session.
    ///
    /// # Parameters
    /// - `session_id`: Session Id for the session.
    fn release_session(&self, session_id: u32);

    /// Get an SPDM secure session.
    ///
    /// # Parameters
    /// - `session_id`: Session Id for the session.
    ///
    /// # Returns
    /// - `Option<&SpdmSecureSession>`: A pointer to the SPDM secure session or `None` if the session does not exist.
    fn get_session(& self, session_id: u32) -> Option<& SpdmSecureSession>;

    /// Set the session state for an SPDM secure session.
    ///
    /// # Parameters
    /// - `session_id`:    Session Id for the session.
    /// - `session_state`: Session state to set.
    fn set_session_state(&self, session_id: u32, session_state: SpdmSecureSessionState);

    /// Reset the Session Manager.
    fn reset(&self);

    /// Generate the shared secret from peer and local public keys.
    ///
    /// # Parameters
    /// - `session`:            SPDM session info.
    /// - `peer_pub_key_point`: Peer public key in point format.
    /// - `local_public_key`: Generated local public key in point format on return.
    ///
    /// # Returns
    /// - `Result<(), SpdmError>`:
    ///    Returns the local public key in point format if the shared secret is generated successfully, or an error code.
    fn generate_shared_secret(
        &self,
        session: &SpdmSecureSession,
        peer_pub_key_point: &EccPointPublicKey,
        local_public_key: &mut [u8]
    ) -> Result<(), SpdmError>;

    /// Generate handshake keys for an SPDM secure session.
    ///
    /// # Parameters
    /// - `session`: Secure Session.
    ///
    /// # Returns
    /// - `Result<(), SpdmError>`: Returns `Ok(())` if the handshake keys are generated successfully, or an error code.
    async fn generate_session_handshake_keys(&self, session: &mut SpdmSecureSession) -> Result<(), SpdmError>;

    /// Generate data keys for an SPDM secure session.
    ///
    /// # Parameters
    /// - `session`: SPDM Secure Session.
    ///
    /// # Returns
    /// - `Result<(), SpdmError>`: Returns `Ok(())` if the data keys are generated successfully, or an error code.
    async fn generate_session_data_keys(&self, session: &mut SpdmSecureSession) -> Result<(), SpdmError>;

    /// Query if the last session is active.
    ///
    /// # Returns
    /// - `bool`: True if the last session is active, false otherwise.
    fn is_last_session_id_valid(&self) -> bool;

    /// Get the last session id.
    ///
    /// # Returns
    /// - `u32`: Last session id.
    fn get_last_session_id(&self) -> u32;

    /// Reset the last session id validity.
    fn reset_last_session_id_validity(&self);

    /// Decode a secure message. This includes MAC verification and optionally decryption.
    ///
    /// # Parameters
    /// - `request`: SPDM request message to be decoded.
    ///
    /// # Returns
    /// - `Result<(), SpdmError>`: Returns `Ok(())` if the secure message is decoded successfully, or an error code.
    async fn decode_secure_message(&self, request: &mut [u8]) -> Result<(), SpdmError>;

    /// Encode a secure message. This includes MAC generation and optionally encryption.
    ///
    /// # Parameters
    /// - `response`: SPDM response message to be encoded.
    ///
    /// # Returns
    /// - `Result<(), SpdmError>`: Returns `Ok(())` if the secure message is encoded successfully, or an error code.
    async fn encode_secure_message(&self, response: &mut [u8]) -> Result<(), SpdmError>;
}

PLDM Stack

Overview

The Platform Level Data Model (PLDM) is a suite of specifications that define a common data model and message formats for communication between management controllers and managed devices. It is designed to standardize the way devices communicate in a platform management environment, enabling interoperability and simplifying the management of hardware components. In the context of Caliptra MCU, PLDM Base Protocol and PLDM for Firmware Update Protocol are supported by the PLDM stack to facilitate the following use cases:

  • PLDM message control and discovery: This feature enables the device to be discovered by the platform management controller (typically a BMC) following MCTP enumeration. It forms the foundation for running other PLDM protocols.

  • Streaming boot remainder firmware: The PLDM firmware update protocol defines standardized messages and data structures for obtaining firmware code and data. The MCU leverages it to stream boot the remainder firmware, which is any vendor-specific SoC or other firmware. There are several customized amendments to the PLDM firmware update specification to enable streaming boot and automatic activation. Details are available in the OCP whitepaper, Flashless Boot using OCP, PCIe, and DMTF Standards.

  • Impactless firmware update: PLDM firmware update over MCTP is a well-established approach for firmware updates, supporting multiple firmware components within a single package. Updates can be applied to a subset of components supported by the Firmware Device (FD), which is a valuable property to enable impactless updates. Details can be found in the firmware update spec.

Architecture

The PLDM stack in MCU runtime is a modular implementation that supports the PLDM base protocol as a responder and the PLDM firmware update protocol as a Firmware Device (FD). It operates in the userspace of MCU runtime and interacts with the MCTP transport layer to handle communication.

PLDM Stack for Base Protocol

PLDM stack base

  • PLDM service
    • Listens for incoming PLDM requests.
    • Extracts the command opcode from the request and invokes the corresponding handler to process.
    • Sends the response via the MCTP transport layer.
  • Command handler
    • Interacts with the message decoding library to decode the request.
    • Executes the command.
    • Encodes the response by interacting with the message encoding library.

PLDM Stack for Firmware Update Protocol

PLDM stack firmware update

  • PLDM service

    • Listens for incoming firmware update requests and responses.
    • Extracts the command opcode from the request or response and invokes the corresponding handler to process.
    • Sends the response via the MCTP transport layer.
  • Firmware update service

    • Firmware update command handler

      • Decodes the request or response.
      • Executes the appropriate actions based on the command and interacts with the state machine to trigger the state transition.
      • Encodes responses by interacting with the message encoding library.
    • Firmware update state control

      • Maintains the current state of the firmware update process.
      • Provides mechanisms to transition between different states, ensuring the correct sequence of operations.
      • Interacts with the firmware update service to manage state transitions based on the stage of the update process and signals the request initiator to process the outgoing request.
    • Request initiator

      • Listens for the signal from state control to process the outgoing request.
      • Interacts with the message encoding library to encode the request.

PLDM Base Protocol Sequence

The table below shows the command codes of the base protocol that are supported by the PLDM stack as a responder.

Command NameCommand CodeDirectionRequirement
GetTID0x02UA -> FDMandatory
GetPLDMVersion0x03UA -> FDMandatory
GetPLDMTypes0x04UA -> FDMandatory
GetPLDMCommands0x05UA -> FDMandatory
SetTID0x01UA -> FDOptional

The diagram below shows the PLDM message control and discovery sequence.

sequenceDiagram
        participant UA as Update Agent
        participant FD as Firmware Device
        UA->>FD: GetTID
        FD-->>UA: TID Response
        UA->>FD: GetPLDMTypes
        FD-->>UA: PLDMTypes Response (type-0, type-5)
        UA->>FD: GetPLDMVersion for type-0
        FD-->>UA: PLDMVersion Response
        UA->>FD: GetPLDMCommands for type-0
        FD-->>UA: PLDMCommands Response
        UA->>FD: GetPLDMVersion for type-5
        FD-->>UA: PLDMVersion Response
        UA->>FD: GetPLDMCommands for type-5
        FD-->>UA: PLDMCommands Response

PLDM Firmware Update Protocol Sequence

The table below shows the inventory commands and firmware update commands supported by the PLDM stack as FD.

Command NameCommand CodeDirectionRequirement
QueryDeviceIdentifiers0x01UA -> FDMandatory
GetFirmwareParameters0x02UA -> FDMandatory
RequestUpdate0x10UA -> FDMandatory
PassComponentTable0x13UA -> FDMandatory
UpdateComponent0x14UA -> FDMandatory
RequestFirmwareData0x15FD -> UAMandatory
TransferComplete0x16FD -> UAMandatory
VerifyComplete0x17FD -> UAMandatory
ApplyComplete0x18FD -> UAMandatory
GetMetaData0x19FD -> UAMandatory
ActivateFirmware0x1AUA -> FDMandatory
GetStatus0x1BUA -> FDMandatory
CancelUpdateComponent0x1CUA -> FDMandatory
CancelUpdate0x1DUA -> FDMandatory

The diagram below shows a complete PLDM firmware update sequence:

sequenceDiagram
        participant UA as Update Agent
        participant FD as Firmware Device
        UA->>FD: QueryDeviceIdentifiers
        FD-->>UA: DeviceIdentifiers Response
        UA->>FD: GetFirmwareParameters
        FD-->>UA: FirmwareParameters Response
        UA->>FD: RequestUpdate
        FD-->>UA: Update Response
        UA->>FD: PassComponentTable
        FD-->>UA: ComponentTable Response
        UA->>FD: UpdateComponent
        FD-->>UA: UpdateComponent Response
        FD->>UA: RequestFirmwareData
        UA-->>FD: FirmwareData Response
        FD->>UA: TransferComplete
        UA-->>FD: TransferComplete Response
        Note over FD: Verifying component
        FD->>UA: VerifyComplete
        UA-->>FD: VerifyComplete Response
        FD->>UA: ApplyComplete
        UA-->>FD: ApplyComplete Response
        UA->>FD: ActivateFirmware
        FD-->>UA: ActivateFirmware Response
        UA->>FD: GetStatus
        FD-->>UA: Status Response

Interface

The PLDM stack is designed as a library that supports the PLDM base protocol as a responder and the PLDM firmware update protocol as a Firmware Device (FD). The diagram below shows the interface and components inside the stack. PldmFwUpdateServiceMgr serves as the interface between the PLDM stack and upper-level APIs, such as Firmware Update and Streaming Boot.

classDiagram
        class PldmFwUpdateServiceMgr {
                <<interface>>
                +start_service() Result<(), PldmServiceError>
                +stop_service() Result<(), PldmServiceError>
        }

        class PldmFwUpdateService {
                +transport: T
                +cmd_interface_responder: MessageResponder
                +cmd_interface_requester: MessageRequester
                +notification: Notifications
                +start_service() Result<(), PldmServiceError>
                +stop_service() Result<(), PldmServiceError>
        }

        class MessageResponder {
                <<interface>>
                +process_request(payload: &mut [u8]) Result<usize, MessageHandlerError>
                +process_response(payload: &mut [u8]) Result<usize, MessageHandlerError>
        }

        class MessageRequester {
                <<interface>>
                +generate_request(payload: &mut [u8]) Result<usize, MessageHandlerError>
        }

        class CommandHandler {
                <<interface>>
                +execute(payload: &mut [u8], context: &mut PldmContext) usize
        }

        PldmFwUpdateServiceMgr <|-- PldmFwUpdateService
        PldmFwUpdateService o-- MessageResponder
        PldmFwUpdateService o-- MessageRequester
        MessageResponder ..> CommandHandler : invoke
/// Trait representing a PLDM Firmware Update Service Manager.
///
/// This trait defines the necessary methods to start and stop the PLDM firmware update service.
///
/// # Methods
///
/// * `start_service` - Asynchronously starts the PLDM firmware update service.
///    This method creates an async task to listen for and process incoming requests.
/// * `stop_service` - Asynchronously stops the PLDM firmware update service.
///
/// # Errors
///
/// Both methods return a `Result` which, on error, contains a `PldmServiceError`.
pub trait PldmFwUpdateServiceMgr {
    async fn start_service(&self) -> Result<(), PldmServiceError>;
    async fn stop_service(&self) -> Result<(), PldmServiceError>;
}
pub struct PldmServiceError(pub NonZeroU32);

/// Represents a PLDM Firmware Update Service.
///
/// This struct is responsible for handling the PLDM firmware update process
/// using the specified transport, message responder, and message requester.
///
/// # Type Parameters
///
/// * `T` - A type that implements the `Mctp` trait, representing the transport layer.
/// * `U` - A type that implements the `MessageResponder` trait, representing the command interface responder.
/// * `R` - A type that implements the `MessageRequester` trait, representing the command interface requester.
/// * `N` - A type that implements the `Notifications` trait, representing the notification interface.
///
/// # Fields
///
/// * `transport` - The transport layer used for communication.
/// * `cmd_interface_responder` - The command interface responder.
/// * `cmd_interface_requester` - The command interface requester.
/// *  `notification` - The notification interface to upper API.
/// * `other fields` - Additional fields required for the service.
pub struct PldmFwUpdateService<T: Mctp, U: MessageResponder, R: MessageRequester, N: Notifications> {
    transport: T,
    cmd_interface_responder: U,
    cmd_interface_requester: R,
    notifications: N,
    // other fields
}

/// Trait representing the notification interface for the PLDM firmware update service.
///
/// This trait defines the necessary methods that will be called by the `PldmFwUpdateService`
/// to notify the upper-level API of specific events during the firmware update process.
pub trait Notifications {
    fn on_request_update_received(&self, payload: &[u8]) -> Result<(), ErrCode>;
    fn on_image_verify(&self, payload: &[u8]) -> Result<(), ErrCode>;
    fn on_image_apply(&self, payload: &[u8]) -> Result<(), ErrCode>;
    fn on_update_component_received(&self, payload: &[u8]) -> Result<(), ErrCode>;
    fn on_activate_firmware_received(&self, payload: &[u8]) -> Result<(), ErrCode>;
}

impl PldmFwUpdateServiceMgr for PldmFwUpdateService {
    async fn start_service(&self) -> Result<(), PldmServiceError> {};
    async fn stop_service(&self) -> Result<(), PldmServiceError> {};
}

/// Trait representing a message responder that can process requests and responses asynchronously.
///
/// # Required Methods
///
/// - `process_request(&self, payload: &mut [u8]) -> Result<usize, MessageHandlerError>`:
///   Processes an incoming request message.
///   - `payload`: A mutable reference to the payload data to be processed.
///   - Returns a `Result` containing the size of the processed data or a `MessageHandlerError`.
///
/// - `process_response(&self, payload: &mut [u8]) -> Result<usize, MessageHandlerError>`:
///   Processes an incoming response message.
///   - `payload`: A mutable reference to the payload data to be processed.
///   - Returns a `Result` containing the size of the processed data or a `MessageHandlerError`.
///
/// # Errors
///
/// Both methods return a `Result` which, on failure, contains a `MessageHandlerError`.
pub trait MessageResponder: Send + Sync {
    async fn process_request(&self, payload: &mut [u8]) -> Result<usize, MessageHandlerError>;
    async fn process_response(&self, payload: &mut [u8]) -> Result<usize, MessageHandlerError>;
}

/// Trait representing a message requester that can generate requests asynchronously.
pub trait MessageRequester: Send + Sync {
    /// Asynchronously generates a request with the given payload.
    ///
    /// # Arguments
    ///
    /// * `payload` - A mutable reference to a byte slice that will be used to generate the request.
    ///
    /// # Returns
    ///
    /// A `Result` containing the size of the generated request on success, or a `MessageHandlerError` on failure.
    async fn generate_request(&self, payload: &mut [u8]) -> Result<usize, MessageHandlerError>;
}
pub struct MessageHandlerError(pub NonZeroU32);

/// Trait representing a command handler that can execute commands asynchronously.
///
/// # Required Methods
///
/// - `execute(&self, payload: &mut [u8], context: &mut PldmContext) -> usize`:
///   Executes a command with the given payload and context.
///   - `payload`: A mutable reference to the payload data to be processed.
///   - `context`: A mutable reference to the PLDM context.
///   - Returns the size of the processed data.
pub trait CommandHandler: Send + Sync {
    async fn execute(&self, payload: &mut [u8], context: &mut PldmContext) -> usize;
}

/// Represents a PLDM Command Interface Responder.
///
/// This struct is responsible for handling the PLDM command interface
/// by maintaining a collection of command handlers and the PLDM context.
///
/// # Fields
///
/// * `handlers` - A heapless hash table that maps command codes to their respective command handlers.
/// * `context` - The PLDM context used for processing commands.
/// * `other fields` - Additional fields required for the command interface responder.
pub struct CmdInterfaceResponder<'a, const N: usize> {
    handlers: heapless::FnvIndexMap<u8, &'a dyn CommandHandler, N>,
    context: PldmContext,
    // Other fields
}

pub struct CmdInterfaceRequester<'a, const N: usize> {
    handlers: heapless::FnvIndexMap<u8, &'a dyn CommandHandler, N>,
    // Other fields
}

External Mailbox (MCI Mailbox) Commands Spec

Overview

This document outlines the external mailbox commands that enable SoC agents to interact with the MCU via MCI mailbox. These commands support a wide range of functionalities, including querying device-specific information, retrieving debug and attestation logs, managing certificates, utilizing cryptographic services and secure debugging in production environment.

  • Device Identification and Capabilities

    • Retrieve firmware versions, unique device identifiers, and device capabilities to ensure compatibility and proper configuration.
    • Query device-specific information such as chip identifiers or subsystem details.
  • Debugging and Diagnostics

    • Retrieve debug logs to analyze device behavior, diagnose issues, and monitor runtime states.
    • Clear logs to reset diagnostic data and maintain storage efficiency.
  • Certificate Management

    • Export Certificate Signing Requests (CSRs) for device keys to facilitate secure provisioning.
    • Import signed certificates to establish a trusted certificate chain for device authentication.
  • Cryptographic Services

    • AES encryption and decryption
    • SHA hashing
    • Random number generation
    • Digital signing
    • Signature verification
    • Key exchange
  • Debug Unlock Mechanisms

    • Facilitate secure debugging in production environments
    • Ensure controlled access to debugging features

Mailbox Commands List

NameCommand CodeDescription
MC_FIRMWARE_VERSION0x4D46_5756 ("MFWV")Retrieves the version of the target firmware.
MC_DEVICE_CAPABILITIES0x4D43_4150 ("MCAP")Retrieve the device capabilities.
MC_DEVICE_ID0x4D44_4944 ("MDID")Retrieves the device ID.
MC_DEVICE_INFO0x4D44_494E ("MDIN")Retrieves information about the target device.
MC_EXPORT_IDEV_CSR0x4D49_4352 ("MICR")Exports the IDEVID Self-Signed Certificate Signing Request.
MC_IMPORT_IDEV_CERT0x4D49_4943 ("MIIC")Allows SoC to import DER-encoded IDevId certificate on every boot.
MC_GET_LOG0x4D47_4C47 ("MGLG")Retrieves the internal log for the RoT.
MC_CLEAR_LOG0x4D43_4C47 ("MCLG")Clears the log in the RoT subsystem.
MC_SHA_INIT0x4D43_5349 ("MCSI")Starts the computation of a SHA hash of data.
MC_SHA_UPDATE0x4D43_5355 ("MCSU")Continues a SHA computation started by MC_SHA_INIT or another MC_SHA_UPDATE.
MC_SHA_FINAL0x4D43_5346 ("MCSF")Finalizes the computation of a SHA and produces the hash of all the data.
MC_AES_ENCRYPT_INIT0x4D43_4349 ("MCCI")Starts an AES encryption operation.
MC_AES_ENCRYPT_UPDATE0x4D43_4355 ("MCMU")Continues an AES encryption operation started by MC_AES_ENCRYPT_INIT.
MC_AES_DECRYPT_INIT0x4D43_414A ("MCAJ")Starts an AES-256 decryption operation.
MC_AES_DECRYPT_UPDATE0x4D43_4155 ("MCAU")Continues an AES decryption operation started by MC_AES_DECRYPT_INIT.
MC_AES_GCM_ENCRYPT_INIT0x4D43_4749 ("MCGI")Starts an AES-256-GCM encryption operation.
MC_AES_GCM_ENCRYPT_UPDATE0x4D43_4755 ("MCGU")Continues an AES-GCM encryption operation started by MC_AES_GCM_ENCRYPT_INIT.
MC_AES_GCM_ENCRYPT_FINAL0x4D43_4746 ("MCGF")Finalizes the AES-GCM encryption operation and produces the final ciphertext and tag.
MC_AES_GCM_DECRYPT_INIT0x4D43_4449 ("MCDI")Starts an AES-256-GCM decryption operation.
MC_AES_GCM_DECRYPT_UPDATE0x4D43_4455 ("MCDU")Continues an AES-GCM decryption operation started by MC_AES_GCM_DECRYPT_INIT.
MC_AES_GCM_DECRYPT_FINAL0x4D43_4446 ("MCDF")Finalizes the AES-GCM decryption operation and verifies the tag.
MC_ECDH_GENERATE0x4D43_4547 ("MCEG")Computes the first half of an Elliptic Curve Diffie-Hellman exchange.
MC_ECDH_FINISH0x4D43_4546 ("MCEF")Computes the second half of an Elliptic Curve Diffie-Hellman exchange.
MC_RANDOM_STIR0x4D43_5253 ("MCRS")Adds additional entropy to the internal deterministic random bit generator.
MC_RANDOM_GENERATE0x4D43_5247 ("MCRG")Generates random bytes from the internal RNG.
MC_IMPORT0x4D43_494D ("MCIM")Imports a specified key and returns a CMK for it.
MC_DELETE0x4D43_444C ("MCDL")Deletes the object stored with the given mailbox ID.
MC_ECDSA384_SIG_VERIFY0x4D45_4356 ("MECV")Verifies an ECDSA P-384 signature.
MC_LMS_SIG_VERIFY0x4D4C_4D56 ("MLMV")Verifies an LMS signature.
MC_ECDSA_SIGN0x4D45_4353 ("MECS")Requests to sign a SHA-384 digest with the DPE leaf certificate.
MC_MLDSA_SIGN0x4D4C_4D53 ("MMLS")Requests to sign a SHA-384 digest with the DPE leaf certificate using MLDSA.
MC_PRODUCTION_DEBUG_UNLOCK_REQ0x4D44_5552 ("MDUR")Requests debug unlock in a production environment.
MC_PRODUCTION_DEBUG_UNLOCK_TOKEN0x4D44_5554 ("MDUT")Sends the debug unlock token.

Command Format

MC_FIRMWARE_VERSION

Retrieves the version of the target firmware.

Command Code: 0x4D46_5756 ("MFWV")

Table: MC_FIRMWARE_VERSION input arguments

NameTypeDescription
chksumu32
indexu32- 00h = Caliptra core firmware
- 01h = MCU runtime firmware
- 02h = SoC firmware
Additional indexes are firmware-specific

Table: MC_FIRMWARE_VERSION output arguments

NameTypeDescription
chksumu32
fips_statusu32FIPS approved or an error
versionu8[32]Firmware Version Number in ASCII format

MC_DEVICE_CAPABILITIES

Retrieve the device capabilites.

Command Code: 0x4D43_4150 ("MCAP")

Table: MC_DEVICE_CAPABILITIES input arguments

NameTypeDescription
chksumu32

Table: MC_DEVICE_CAPABILITIES output arguments

NameTypeDescription
chksumu32
fips_statusu32FIPS approved or an error
capsu8[32]- Bytes [0:7]: Reserved for Caliptra RT
- Bytes [8:11]: Reserved for Caliptra FMC
- Bytes [12:15]: Reserved for Caliptra ROM
- Bytes [16:23]: Reserved for MCU RT
- Bytes [24:27]: Reserved for MCU ROM
- Bytes [28:31]: Reserved

MC_DEVICE_ID

Retrieves the device ID.

Command Code: 0x4D44_4944 ("MDID")

Table: MC_DEVICE_ID input arguments

NameTypeDescription
chksumu32

Table: MC_DEVICE_ID output arguments

NameTypeDescription
chksumu32
fips_statusu32FIPS approved or an error
vendor_idu16Vendor ID; LSB
device_idu16Device ID; LSB
subsystem_vendor_idu16Subsystem Vendor ID; LSB
subsystem_idu16Subsystem ID; LSB

MC_DEVICE_INFO

Retrieves information about the target device.

Command Code: 0x4D44_494E ("MDIN")

Table: MC_DEVICE_INFO input arguments

NameTypeDescription
chksumu32
indexu32Information Index:
- 00h = Unique Chip Identifier
Additional indexes are firmware-specific

Table: MC_DEVICE_INFO output arguments

NameTypeDescription
chksumu32
fips_statusu32FIPS approved or an error
data_sizeu32Size of the requested data in bytes
datau8[data_size]Requested information in binary format

MC_EXPORT_IDEV_CSR

Exports the IDEVID Self-Signed Certificate Signing Request.

Command Code: 0x4D49_4352 ("MICR")

Table: MC_EXPORT_IDEV_CSR input arguments

NameTypeDescription
chksumu32
indexu32Information Index:
- 00h = IDEVID ECC CSR
- 01h = IDEVID MLDSA CSR

Table: MC_EXPORT_IDEV_CSR output arguments

NameTypeDescription
chksumu32
fips_statusu32FIPS approved or an error
data_sizeu32Length in bytes of the valid data in the data field.
datau8[data_size]DER-encoded IDevID certificate signing request.

MC_IMPORT_IDEV_CERT

Allows SoC to import DER-encoded IDevId certificate on every boot. The IDevId certificate is added to the start of the certificate chain.

Command Code: 0x4D49_4943 ("MIIC")

Table: MC_IMPORT_IDEV_CERT input arguments

NameTypeDescription
chksumu32
cert_sizeu32Size of the DER-encoded IDevID certificate.
certu8[1024]DER-encoded IDevID certificate.

Table: MC_IMPORT_IDEV_CERT output arguments

NameTypeDescription
chksumu32
fips_statusu32FIPS approved or an error.

MC_GET_LOG

Retrieves the internal log for the RoT. There are two types of logs available: the Debug Log, which contains RoT application information and machine state, and the Attestation Measurement Log, which is similar to the TCG log.

Command Code: 0x4D47_4C47 ("MGLG")

Table: MC_GET_LOG input arguments

NameTypeDescription
chksumu32Checksum over input data
log typeu32Type of log to retrieve:
- 0 = Debug Log
- 1 = Attestation Log

Table: MC_GET_LOG output arguments

NameTypeDescription
chksumu32
fips_statusu32FIPS approved or an error.
data_sizeu32Size of the log data in bytes
datau8[data_size]Log contents

Debug Log Format:

The debug log reported by the device has no specified format, as this can vary between different devices and is not necessary for attestation. It is expected that diagnostic utilities for the device will be able to understand the exposed log information. A recommended entry format is provided here:

OffsetDescription
1:7Log Entry Header
8:9Format of the entry (e.g., 1 for current format)
10Severity of the entry
11Identifier for the component that generated the message
12Identifier for the entry message
13:16Message-specific argument
17:20Message-specific argument

MC_CLEAR_LOG

Clears the log in the RoT subsystem.

Command Code: 0x4D43_4C47 ("MCLG")

Table: MC_CLEAR_LOG input arguments

NameTypeDescription
chksumu32Checksum over input data
log typeu32Type of log to retrieve:
- 0 = Debug Log
- 1 = Attestation Log

Table: MC_CLEAR_LOG output arguments

NameTypeDescription
chksumu32
fips_statusu32FIPS approved or an error.

MC_ECDSA384_SIG_VERIFY

Verifies an ECDSA P-384 signature. The hash to be verified is taken from the input.

Command Code: 0x4D45_4356 ("MECV")

Table: MC_ECDSA384_SIG_VERIFY input arguments

NameTypeDescription
chksumu32Checksum over other input arguments, computed by the caller. Little endian.
pub_key_xu8[48]X portion of the ECDSA verification key.
pub_key_yu8[48]Y portion of the ECDSA verification key.
signature_ru8[48]R portion of the signature to verify.
signature_su8[48]S portion of the signature to verify.
hashu8[48]SHA-384 digest to verify.

Table: MC_ECDSA384_SIG_VERIFY output arguments

NameTypeDescription
chksumu32Checksum over other output arguments, computed by responder. Little endian.
fips_statusu32Indicates if the command is FIPS approved or an error.

MC_LMS_SIG_VERIFY

Verifies an LMS signature. The hash to be verified is taken from the input.

Command Code: 0x4D4C_4D56 ("MLMV")

Table: MC_LMS_SIG_VERIFY input arguments

NameTypeDescription
chksumu32Checksum over other input arguments, computed by the caller. Little endian.
pub_key_tree_typeu8[4]LMS public key algorithm type. Must equal 12.
pub_key_ots_typeu8[4]LM-OTS algorithm type. Must equal 7.
pub_key_idu8[16]"I" Private key identifier
pub_key_digestu8[24]"T[1]" Public key hash value
signature_qu8[4]Leaf of the Merkle tree where the OTS public key appears
signature_otsu8[1252]LM-OTS signature
signature_tree_typeu8[4]LMS signature Algorithm type. Must equal 12.
signature_tree_pathu8[360]Path through the tree from the leaf associated with the LM-OTS signature to the root
hashu8[48]SHA384 digest to verify.

Table: MC_LMS_SIG_VERIFY output arguments

NameTypeDescription
chksumu32Checksum over other output arguments, computed by MCU. Little endian.
fips_statusu32Indicates if the command is FIPS approved or an error.

MC_ECDSA_SIGN

Requests to sign SHA-384 digest with DPE leaf cert.

Command Code: 0x4D45_4353 ("MECS")

Table: MC_ECDSA384_SIGN input arguments

NameTypeDescription
chksumu32Checksum over other input arguments, computed by the caller. Little endian.
digestu8[48]SHA-384 digest to be signed.

Table: MC_ECDSA384_SIGN output arguments

NameTypeDescription
chksumu32Checksum over other output arguments, computed by MCU. Little endian.
fips_statusu32Indicates if the command is FIPS approved or an error.
derived_pubkey_xu8[48]The X BigNum of the ECDSA public key associated with the signing key.
derived_pubkey_yu8[48]The Y BigNum of the ECDSA public key associated with the signing key.
signature_ru8[48]The R BigNum of an ECDSA signature.
signature_su8[48]The S BigNum of an ECDSA signature.

MC_MLDSA_SIGN

Request to sign the SHA-384 digest with DPE leaf cert.

Command Code: 0x4D4C_4D53 ("MMLS")

Table: MC_MLDSA_SIGN input arguments

NameTypeDescription
chksumu32Checksum over other input arguments, computed by the caller. Little endian.
digestu8[48]SHA-384 digest to be signed.

Table: MC_MLDSA_SIGN output arguments

NameTypeDescription
chksumu32
fips_statusu32FIPS approved or an error
pub_key_tree_typeu8[4]LMS public key algorithm type.
pub_key_ots_typeu8[4]LM-OTS algorithm type.
pub_key_idu8[16]Private key identifier.
pub_key_digestu8[24]Public key hash value.
signature_qu8[4]Leaf of the Merkle tree for the OTS key.
signature_otsu8[1252]LM-OTS signature.
signature_tree_pathu8[360]Path through the Merkle tree to the root.

MC_PRODUCTION_DEBUG_UNLOCK_REQ

Requests debug unlock in production environment.

Command Code: 0x4D44_5552 ("MDUR")

Table: MC_PRODUCTION_DEBUG_UNLOCK_REQ input arguments

NameTypeDescription
chksumu32
lengthu32Length of the message in DWORDs
unlock_levelu8Debug unlock Level (Number 1-8)
reservedu8[3]Reserved field

Table: MC_PRODUCTION_DEBUG_UNLOCK_REQ output arguments

NameTypeDescription
chksumu32Checksum over other output arguments.
fips_statusu32FIPS approved or an error
lengthu32Length of the message in DWORDs.
unique_device_identifieru8[32]Device identifier of the Caliptra device.
challengeu8[48]Random number challenge.

MC_PRODUCTION_DEBUG_UNLOCK_TOKEN

Sends the debug unlock token.

Command Code: 0x4D44_5554 ("MDUT")

Table: MC_PRODUCTION_DEBUG_UNLOCK_TOKEN input arguments

NameTypeDescription
chksumu32Checksum over other input arguments.
fips_statusu32FIPS approved or an error
lengthu32Length of the message in DWORDs.
unique_device_identifieru8[32]Device identifier of the Caliptra device.
unlock_levelu8Debug unlock level (1-8).
reservedu8[3]Reserved field.
challengeu8[48]Random number challenge.
ecc_public_keyu32[24]ECC public key in hardware format (little endian).
mldsa_public_keyu32[648]MLDSA public key in hardware format (little endian).
ecc_signatureu32[24]ECC P-384 signature of the message hashed using SHA2-384 (R and S coordinates).
mldsa_signatureu32[1157]MLDSA signature of the message hashed using SHA2-512 (4627 bytes + 1 reserved byte).

Table: MC_PRODUCTION_DEBUG_UNLOCK_TOKEN output arguments

NameTypeDescription
chksumu32
fips_statusu32FIPS approved or an error

Cryptographic Command Format

The MCI mailbox cryptographic commands are mapped to their corresponding Caliptra Mailbox Cryptographic commands. The mapping is detailed in the table below. For the specific format of each command, refer to the Mailbox Commands: Cryptographic Mailbox (2.0).

Table: mapping MCI Mailbox Crypto Commands to Caliptra Crypto Mailbox Commands

MCI Mailbox Crypto CommandsCaliptra Mailbox Crypto Commands
MC_SHA_INITCM_SHA_INIT
MC_SHA_UPDATECM_SHA_UPDATE
MC_SHA_FINALCM_SHA_FINAL
MC_AES_ENCRYPT_INITCM_AES_ENCRYPT_INIT
MC_AES_ENCRYPT_UPDATECM_AES_ENCRYPT_UPDATE
MC_AES_DECRYPT_INITCM_AES_DECRYPT_INIT
MC_AES_DECRYPT_UPDATECM_AES_DECRYPT_UPDATE
MC_AES_GCM_ENCRYPT_INITCM_AES_GCM_ENCRYPT_INIT
MC_AES_GCM_ENCRYPT_UPDATECM_AES_GCM_ENCRYPT_UPDATE
MC_AES_GCM_ENCRYPT_FINALCM_AES_GCM_ENCRYPT_FINAL
MC_AES_GCM_DECRYPT_INITCM_AES_GCM_DECRYPT_INIT
MC_AES_GCM_DECRYPT_UPDATECM_AES_GCM_DECRYPT_UPDATE
MC_AES_GCM_DECRYPT_FINALCM_AES_GCM_DECRYPT_FINAL
MC_ECDH_GENERATECM_ECDH_GENERATE
MC_ECDH_FINISHCM_ECDH_FINISH
MC_RANDOM_STIRCM_RANDOM_STIR
MC_RANDOM_GENERATECM_RANDOM_GENERATE
MC_IMPORTCM_IMPORT
MC_DELETECM_DELETE