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.
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
- Check the MCI
RESET_REASON
register for MCU status (it should be in cold boot mode) - Program and lock PMP registers
- Initialize I3C registers according to the initialization sequence.
- Initialize I3C recovery interface initialization sequence.
- Anything SoC-specific can happen here
- Stash to Caliptra if required (i.e., if any security-sensitive code is loaded, such as PLL programming or configuration loading)
- Read Caliptra SoC
FLOW_STATUS
register to wait for Caliptra Ready for Fuses state - 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 bitsECC REVOCATION (KEY MANIFEST PK HASH MASK)
: 4 bitsOWNER PK HASH
: 384 bitsFMC KEY MANIFEST SVN
: 32 bitsRUNTIME SVN
: 128 bitsANTI-ROLLBACK DISABLE
: 1 bitsIDEVID CERT IDEVID ATTR
: 768 bitsIDEVID MANUF HSM IDENTIFIER
: 128 bitsLIFE CYCLE
: 2 bitsLMS REVOCATION
: 32 bitsMLDSA REVOCATION
: 4 bitsSOC STEPPING ID
: 16 bitsMANUF_DEBUG_UNLOCK_TOKEN
: 128 bits
- Write fuse data to Caliptra SoC interface fuse registers.
- Poll on Caliptra
FLOW_STATUS
registers for Caliptra to deassert the Ready for Fuses state. - Clear the watchdog timer
- 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
- Check the MCI
RESET_REASON
register for MCU status (it should be in firmware update mode) - Program and lock PMP registers
- Anything SoC-specific can happen here
- Do stash if required
- 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:
- Caliptra FMC (First Mutable Code) and RT (Runtime) Image Bundle
- The SOC Manifest for the MCU RT image and other SOC Images
- The MCU RT image
- Other SOC Images, if any
- 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
Field | Size | Definition |
---|---|---|
PackageHeaderIdentifier | 16 | Set to 0x7B291C996DB64208801B0202E6463C78 (v1.3.0 UUID) (big endian) |
PackageHeaderFormatRevision | 1 | Set to 0x04 (v1.3.0 header format revision) |
PackageHeaderSize | 2 | The 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. | ||
PackageReleaseDateTime | 13 | The date and time when this package was released. |
Refer to the PLDM Base Specification for field format definition. | ||
ComponentBitmapBitLength | 2 | Number 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. | ||
PackageVersionStringType | 1 | The type of string used in the PackageVersionString field. |
Refer to DMTF Firmware Update Specification v.1.3.0 Table 33 for values. | ||
PackageVersionStringLength | 1 | Length, in bytes, of the PackageVersionString field. |
PackageVersionString | Variable | Package version information, up to 255 bytes. |
Contains a variable type string describing the version of this firmware update package. |
Firmware Device ID Descriptor
Field | Size | Definition |
---|---|---|
RecordLength | 2 | The total length in bytes for this record. The length includes the RecordLength, DescriptorCount, DeviceUpdateOptionFlags, ComponentImageSetVersionStringType, ComponentSetVersionStringLength, FirmwareDevicePackageDataLength, ApplicableComponents, ComponentImageSetVersionString, and RecordDescriptors, and FirmwareDevicePackageData fields. |
DescriptorCount | 1 | The number of descriptors included within the RecordDescriptors field for this record. |
DeviceUpdateOptionFlags | 4 | 32-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. | ||
ComponentImageSetVersionStringType | 1 | The type of string used in the ComponentImageSetVersionString field. Refer to DMTF Firmware Update Specification v.1.3.0 Table 33 for values. |
ComponentImageSetVersionStringLength | 1 | Length, in bytes, of the ComponentImageSetVersionString. |
FirmwareDevicePackageDataLength | 2 | Length in bytes of the FirmwareDevicePackageData field. |
If no data is provided, set to 0x0000 . | ||
ReferenceManifestLength | 4 | Length in bytes of the ReferenceManifestData field. If no data is provided, set to 0x00000000 . |
ApplicableComponents | Variable | Bitmap 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 | ||
ComponentImageSetVersionString | Variable | Component Image Set version information, up to 255 bytes. |
Describes the version of component images applicable to the firmware device indicated in this record. | ||
RecordDescriptors | Variable | These 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. | ||
FirmwareDevicePackageData | Variable | Optional data provided within the firmware update package for the FD during the update process. |
If FirmwareDevicePackageDataLength is 0x0000 , this field contains no data. | ||
ReferenceManifestData | Variable | Optional 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
Field | Size | Definition |
---|---|---|
DownstreamDeviceIDRecordCount | 1 | 0 |
Component Image Information
Field | Size | Definition |
---|---|---|
ComponentImageCount | 2 | Count of individually defined component images contained within this firmware update package. |
ComponentImageInformation | Variable | Contains details for each component image within this firmware update package. |
Component Image Information Record
Field | Size | Definition |
---|---|---|
ComponentClassification | 2 | 0x000A : 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. | ||
ComponentIdentifier | 2 | Unique 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 | ||
ComponentComparisonStamp | 4 | Value 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 . |
ComponentOptions | 2 | Refer to ComponentOptions definition in DMTF Firmware Update Specification v.1.3.0 |
RequestedComponentActivationMethod | 2 | Refer to RequestedComponentActivationMethoddefinition inDMTF Firmware Update Specification v.1.3.0 |
ComponentLocationOffset | 4 | Offset in bytes from byte 0 of the package header to where the component image begins. |
ComponentSize | 4 | Size in bytes of the Component image. |
ComponentVersionStringType | 1 | Type of string used in the ComponentVersionString field. Refer toDMTF Firmware Update Specification v.1.3.0 Table 33 for values. |
ComponentVersionStringLength | 1 | Length, in bytes, of the ComponentVersionString. |
ComponentVersionString | Variable | Component version information, up to 255 bytes. Contains a variable type string describing the component version. |
ComponentOpaqueDataLength | 4 | Length in bytes of the ComponentOpaqueData field. If no data is provided, set to 0x00000000. |
ComponentOpaqueData | Variable | Optional data transferred to the FD/FDP during the firmware update |
Checksum
Field | Size | Definition |
---|---|---|
PackageHeaderChecksum | 4 | The 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. |
PackagePayloadChecksum | 4 | The 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 |
- Caliptra FMC and RT (refer to the Caliptra Firmware Image Bundle Format)
- SoC Manifest (refer to the description of the SoC Manifest)
- MCU RT: This is the image binary of the MCU Runtime firmware
- Other SoC images (if any)
Header
The Header section contains the metadata for the images stored in the flash.
Field | Size (bytes) | Description |
---|---|---|
Magic Number | 4 | A unique identifier to mark the start of the header. The value must be 0x464C5348 ("FLSH" in ASCII) |
Header Version | 2 | The header version format, allowing for backward compatibility if the package format changes over time. (Current version is 0x0001 ) |
Image Count | 2 | The 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.
Field | Size (bytes) | Description |
---|---|---|
Header Checksum | 4 | The 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 Checksum | 4 | The 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.
Field | Size (bytes) | Descr |
---|---|---|
Identifier | 4 | Vendor selected unique value to distinguish between images. |
0x0001 : Caliptra FMC+RT | ||
0x0002 : SoC Manifest: | ||
0x0003 : MCU RT0x1000 -0xFFFF - Reserved for other Vendor-defined SoC images | ||
ImageLocationOffset | 4 | Offset in bytes from byte 0 of the header to where the image content begins. |
Size | 4 | Size 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.
Field | Size (bytes) | Description |
---|---|---|
Data | N | Image 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.
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.
-
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 implementsSyscallDriver
trait to interact with the userland API.
- 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
-
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.
- Additional methods provided in the flash controller driver include:
- 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 driver implements the
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.
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
The following steps are done for every SOC image:
The following outlines the steps carried out by the MCU RT during the SOC boot process:
-
MCU ROM reads a SOC Configuration register (implementation specific) to determine the source of the images to load (Flash/PLDM).
-
Caliptra RT authorizes and loads Caliptra RT (refer to Caliptra Subsystem boot flow for the detailed steps).
-
Caliptra switches to Caliptra RT FW.
-
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).
-
Retrieve SOC Manifest
- If image is coming from PLDM, PLDM FW Update Agent transfers SOC manifest to Recovery I/F
- If Image is coming from Flash, MCU ROM transfers SOC manifest from flash to Recovery I/F
-
Caliptra RT transfers SOC Manifest to Caliptra Mailbox (MB) SRAM
-
Caliptra RT will authenticate its image sitting in Caliptra MB SRAM
-
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)..
-
Retrieve MCU RT Image
- If Image is coming from PLDM, PLDM FW Update Agent sends MCU RT Image to Recovery I/F (refer to Caliptra Subsystem boot flow).
- If Image is coming from Flash, MCU ROM transfers MCU RT Image to Recovery I/F
-
Caliptra RT FW will read the recovery interface registers over AXI manager interface and write the image to MCU SRAM aperture
-
Caliptra RT FW will instruct its SHA accelerator to hash the MCU RT Image in the MCU SRAM.
-
Caliptra RT FW will use this hash and verify it against the hash in the SOC manifest.
-
Once the digest is verified, Caliptra RT FW sets the EXEC/GO bit.
-
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).
-
MCU switches to MCU RT
-
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:
- MCU RT application initializes the image loader based on the boot_source. The application need to specify the boot_source as Flash or PLDM.
- 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 Name | Description | Value to Use |
---|---|---|
ComponentActivationMethods | Defines activation methods supported by the FD. | 0x0000 (Automatic) |
CapabilitiesDuringUpdate | Capabilities of the firmware component during update. | 0x00000002 (Downgrade permitted) |
ActiveComponentVersionString | Describes the currently active version of the component. | None |
PendingComponentVersionString | Describes the version of the component that is pending activation. | None |
ComponentIdentifier | Unique ID for the component. | 0xFFFF |
ComponentClassificationIndex | Used to distinguish identical component classifications with different instances. | 0x00 |
ActiveComponentComparisonStamp | Comparison stamp for active version. | 0x00000000 |
ActiveComponentVersionStringType | String type for active version string. | 0x01 (ASCII) |
ActiveComponentVersionStringLength | Length of the active version string. | 0x00 |
ActiveComponentReleaseDate | Release date of the active component in YYYYMMDD format. | "00000000" |
PendingComponentComparisonStamp | Comparison stamp for pending version. | None |
PendingComponentVersionStringType | String type for pending version string. | 0x01 (ASCII) |
PendingComponentVersionStringLength | Length of the pending version string. | 0x00 |
PendingComponentReleaseDate | Release date of the pending version in YYYYMMDD format. | "00000000" |
ComponentCount | Number of components on the FD. | 0x0001 |
ActiveComponentImageSetVersionStringType | Type for image set version string (active). | 0x01 (ASCII) |
ActiveComponentImageSetVersionStringLength | Length of image set version string (active). | 0x00 |
PendingComponentImageSetVersionStringType | Type for image set version string (pending). | 0x01 (ASCII) |
PendingComponentImageSetVersionStringLength | Length of image set version string (pending). | 0x00 |
ActiveComponentImageSetVersionString | Version string of the active image set. | None |
PendingComponentImageSetVersionString | Version 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.
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:
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.
-
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.
- Allow number: 0
-
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.
- Allow number: 0
-
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
- Subscribe number 0:
-
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
- Command number 0:
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
Specification | Document Link |
---|---|
Security Protocol and Data Model | DSP0274 |
Secured Messages using SPDM | DSP0277 |
SPDM over MCTP Binding Specification | DSP0275 |
Secured Messages using SPDM over MCTP Binding | DSP0276 |
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:
Message | Description |
---|---|
VERSION | Retrieves version information |
CAPABILITIES | Retrieves SPDM capabilities |
ALGORITHMS | Retrieves the negotiated algorithms |
DIGESTS | Retrieves digest of the certificate chains |
CERTIFICATE | Retrieves certificate chains |
MEASUREMENTS | Retrieves measurements of elements such as intenral state |
KEY_EXCHANGE_RSP | Retrieves the responder's public key information |
FINISH_RSP | Provide key confirmation, bind the identity of each party to the exchanged keys |
END_SESSION_ACK | End session acknowledgment |
ERROR | Error 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 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 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 Name | Command Code | Direction | Requirement |
---|---|---|---|
GetTID | 0x02 | UA -> FD | Mandatory |
GetPLDMVersion | 0x03 | UA -> FD | Mandatory |
GetPLDMTypes | 0x04 | UA -> FD | Mandatory |
GetPLDMCommands | 0x05 | UA -> FD | Mandatory |
SetTID | 0x01 | UA -> FD | Optional |
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 Name | Command Code | Direction | Requirement |
---|---|---|---|
QueryDeviceIdentifiers | 0x01 | UA -> FD | Mandatory |
GetFirmwareParameters | 0x02 | UA -> FD | Mandatory |
RequestUpdate | 0x10 | UA -> FD | Mandatory |
PassComponentTable | 0x13 | UA -> FD | Mandatory |
UpdateComponent | 0x14 | UA -> FD | Mandatory |
RequestFirmwareData | 0x15 | FD -> UA | Mandatory |
TransferComplete | 0x16 | FD -> UA | Mandatory |
VerifyComplete | 0x17 | FD -> UA | Mandatory |
ApplyComplete | 0x18 | FD -> UA | Mandatory |
GetMetaData | 0x19 | FD -> UA | Mandatory |
ActivateFirmware | 0x1A | UA -> FD | Mandatory |
GetStatus | 0x1B | UA -> FD | Mandatory |
CancelUpdateComponent | 0x1C | UA -> FD | Mandatory |
CancelUpdate | 0x1D | UA -> FD | Mandatory |
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
Name | Command Code | Description |
---|---|---|
MC_FIRMWARE_VERSION | 0x4D46_5756 ("MFWV") | Retrieves the version of the target firmware. |
MC_DEVICE_CAPABILITIES | 0x4D43_4150 ("MCAP") | Retrieve the device capabilities. |
MC_DEVICE_ID | 0x4D44_4944 ("MDID") | Retrieves the device ID. |
MC_DEVICE_INFO | 0x4D44_494E ("MDIN") | Retrieves information about the target device. |
MC_EXPORT_IDEV_CSR | 0x4D49_4352 ("MICR") | Exports the IDEVID Self-Signed Certificate Signing Request. |
MC_IMPORT_IDEV_CERT | 0x4D49_4943 ("MIIC") | Allows SoC to import DER-encoded IDevId certificate on every boot. |
MC_GET_LOG | 0x4D47_4C47 ("MGLG") | Retrieves the internal log for the RoT. |
MC_CLEAR_LOG | 0x4D43_4C47 ("MCLG") | Clears the log in the RoT subsystem. |
MC_SHA_INIT | 0x4D43_5349 ("MCSI") | Starts the computation of a SHA hash of data. |
MC_SHA_UPDATE | 0x4D43_5355 ("MCSU") | Continues a SHA computation started by MC_SHA_INIT or another MC_SHA_UPDATE . |
MC_SHA_FINAL | 0x4D43_5346 ("MCSF") | Finalizes the computation of a SHA and produces the hash of all the data. |
MC_AES_ENCRYPT_INIT | 0x4D43_4349 ("MCCI") | Starts an AES encryption operation. |
MC_AES_ENCRYPT_UPDATE | 0x4D43_4355 ("MCMU") | Continues an AES encryption operation started by MC_AES_ENCRYPT_INIT . |
MC_AES_DECRYPT_INIT | 0x4D43_414A ("MCAJ") | Starts an AES-256 decryption operation. |
MC_AES_DECRYPT_UPDATE | 0x4D43_4155 ("MCAU") | Continues an AES decryption operation started by MC_AES_DECRYPT_INIT . |
MC_AES_GCM_ENCRYPT_INIT | 0x4D43_4749 ("MCGI") | Starts an AES-256-GCM encryption operation. |
MC_AES_GCM_ENCRYPT_UPDATE | 0x4D43_4755 ("MCGU") | Continues an AES-GCM encryption operation started by MC_AES_GCM_ENCRYPT_INIT . |
MC_AES_GCM_ENCRYPT_FINAL | 0x4D43_4746 ("MCGF") | Finalizes the AES-GCM encryption operation and produces the final ciphertext and tag. |
MC_AES_GCM_DECRYPT_INIT | 0x4D43_4449 ("MCDI") | Starts an AES-256-GCM decryption operation. |
MC_AES_GCM_DECRYPT_UPDATE | 0x4D43_4455 ("MCDU") | Continues an AES-GCM decryption operation started by MC_AES_GCM_DECRYPT_INIT . |
MC_AES_GCM_DECRYPT_FINAL | 0x4D43_4446 ("MCDF") | Finalizes the AES-GCM decryption operation and verifies the tag. |
MC_ECDH_GENERATE | 0x4D43_4547 ("MCEG") | Computes the first half of an Elliptic Curve Diffie-Hellman exchange. |
MC_ECDH_FINISH | 0x4D43_4546 ("MCEF") | Computes the second half of an Elliptic Curve Diffie-Hellman exchange. |
MC_RANDOM_STIR | 0x4D43_5253 ("MCRS") | Adds additional entropy to the internal deterministic random bit generator. |
MC_RANDOM_GENERATE | 0x4D43_5247 ("MCRG") | Generates random bytes from the internal RNG. |
MC_IMPORT | 0x4D43_494D ("MCIM") | Imports a specified key and returns a CMK for it. |
MC_DELETE | 0x4D43_444C ("MCDL") | Deletes the object stored with the given mailbox ID. |
MC_ECDSA384_SIG_VERIFY | 0x4D45_4356 ("MECV") | Verifies an ECDSA P-384 signature. |
MC_LMS_SIG_VERIFY | 0x4D4C_4D56 ("MLMV") | Verifies an LMS signature. |
MC_ECDSA_SIGN | 0x4D45_4353 ("MECS") | Requests to sign a SHA-384 digest with the DPE leaf certificate. |
MC_MLDSA_SIGN | 0x4D4C_4D53 ("MMLS") | Requests to sign a SHA-384 digest with the DPE leaf certificate using MLDSA. |
MC_PRODUCTION_DEBUG_UNLOCK_REQ | 0x4D44_5552 ("MDUR") | Requests debug unlock in a production environment. |
MC_PRODUCTION_DEBUG_UNLOCK_TOKEN | 0x4D44_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
Name | Type | Description |
---|---|---|
chksum | u32 | |
index | u32 | - 00h = Caliptra core firmware |
- 01h = MCU runtime firmware | ||
- 02h = SoC firmware | ||
Additional indexes are firmware-specific |
Table: MC_FIRMWARE_VERSION
output arguments
Name | Type | Description |
---|---|---|
chksum | u32 | |
fips_status | u32 | FIPS approved or an error |
version | u8[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
Name | Type | Description |
---|---|---|
chksum | u32 |
Table: MC_DEVICE_CAPABILITIES
output arguments
Name | Type | Description |
---|---|---|
chksum | u32 | |
fips_status | u32 | FIPS approved or an error |
caps | u8[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
Name | Type | Description |
---|---|---|
chksum | u32 |
Table: MC_DEVICE_ID
output arguments
Name | Type | Description |
---|---|---|
chksum | u32 | |
fips_status | u32 | FIPS approved or an error |
vendor_id | u16 | Vendor ID; LSB |
device_id | u16 | Device ID; LSB |
subsystem_vendor_id | u16 | Subsystem Vendor ID; LSB |
subsystem_id | u16 | Subsystem ID; LSB |
MC_DEVICE_INFO
Retrieves information about the target device.
Command Code: 0x4D44_494E
("MDIN")
Table: MC_DEVICE_INFO
input arguments
Name | Type | Description |
---|---|---|
chksum | u32 | |
index | u32 | Information Index: |
- 00h = Unique Chip Identifier | ||
Additional indexes are firmware-specific |
Table: MC_DEVICE_INFO
output arguments
Name | Type | Description |
---|---|---|
chksum | u32 | |
fips_status | u32 | FIPS approved or an error |
data_size | u32 | Size of the requested data in bytes |
data | u8[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
Name | Type | Description |
---|---|---|
chksum | u32 | |
index | u32 | Information Index: |
- 00h = IDEVID ECC CSR | ||
- 01h = IDEVID MLDSA CSR |
Table: MC_EXPORT_IDEV_CSR
output arguments
Name | Type | Description |
---|---|---|
chksum | u32 | |
fips_status | u32 | FIPS approved or an error |
data_size | u32 | Length in bytes of the valid data in the data field. |
data | u8[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
Name | Type | Description |
---|---|---|
chksum | u32 | |
cert_size | u32 | Size of the DER-encoded IDevID certificate. |
cert | u8[1024] | DER-encoded IDevID certificate. |
Table: MC_IMPORT_IDEV_CERT
output arguments
Name | Type | Description |
---|---|---|
chksum | u32 | |
fips_status | u32 | FIPS 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
Name | Type | Description |
---|---|---|
chksum | u32 | Checksum over input data |
log type | u32 | Type of log to retrieve: |
- 0 = Debug Log | ||
- 1 = Attestation Log |
Table: MC_GET_LOG
output arguments
Name | Type | Description |
---|---|---|
chksum | u32 | |
fips_status | u32 | FIPS approved or an error. |
data_size | u32 | Size of the log data in bytes |
data | u8[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:
Offset | Description |
---|---|
1:7 | Log Entry Header |
8:9 | Format of the entry (e.g., 1 for current format) |
10 | Severity of the entry |
11 | Identifier for the component that generated the message |
12 | Identifier for the entry message |
13:16 | Message-specific argument |
17:20 | Message-specific argument |
MC_CLEAR_LOG
Clears the log in the RoT subsystem.
Command Code: 0x4D43_4C47
("MCLG")
Table: MC_CLEAR_LOG
input arguments
Name | Type | Description |
---|---|---|
chksum | u32 | Checksum over input data |
log type | u32 | Type of log to retrieve: |
- 0 = Debug Log | ||
- 1 = Attestation Log |
Table: MC_CLEAR_LOG
output arguments
Name | Type | Description |
---|---|---|
chksum | u32 | |
fips_status | u32 | FIPS 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
Name | Type | Description |
---|---|---|
chksum | u32 | Checksum over other input arguments, computed by the caller. Little endian. |
pub_key_x | u8[48] | X portion of the ECDSA verification key. |
pub_key_y | u8[48] | Y portion of the ECDSA verification key. |
signature_r | u8[48] | R portion of the signature to verify. |
signature_s | u8[48] | S portion of the signature to verify. |
hash | u8[48] | SHA-384 digest to verify. |
Table: MC_ECDSA384_SIG_VERIFY
output arguments
Name | Type | Description |
---|---|---|
chksum | u32 | Checksum over other output arguments, computed by responder. Little endian. |
fips_status | u32 | Indicates 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
Name | Type | Description |
---|---|---|
chksum | u32 | Checksum over other input arguments, computed by the caller. Little endian. |
pub_key_tree_type | u8[4] | LMS public key algorithm type. Must equal 12. |
pub_key_ots_type | u8[4] | LM-OTS algorithm type. Must equal 7. |
pub_key_id | u8[16] | "I" Private key identifier |
pub_key_digest | u8[24] | "T[1]" Public key hash value |
signature_q | u8[4] | Leaf of the Merkle tree where the OTS public key appears |
signature_ots | u8[1252] | LM-OTS signature |
signature_tree_type | u8[4] | LMS signature Algorithm type. Must equal 12. |
signature_tree_path | u8[360] | Path through the tree from the leaf associated with the LM-OTS signature to the root |
hash | u8[48] | SHA384 digest to verify. |
Table: MC_LMS_SIG_VERIFY
output arguments
Name | Type | Description |
---|---|---|
chksum | u32 | Checksum over other output arguments, computed by MCU. Little endian. |
fips_status | u32 | Indicates 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
Name | Type | Description |
---|---|---|
chksum | u32 | Checksum over other input arguments, computed by the caller. Little endian. |
digest | u8[48] | SHA-384 digest to be signed. |
Table: MC_ECDSA384_SIGN
output arguments
Name | Type | Description |
---|---|---|
chksum | u32 | Checksum over other output arguments, computed by MCU. Little endian. |
fips_status | u32 | Indicates if the command is FIPS approved or an error. |
derived_pubkey_x | u8[48] | The X BigNum of the ECDSA public key associated with the signing key. |
derived_pubkey_y | u8[48] | The Y BigNum of the ECDSA public key associated with the signing key. |
signature_r | u8[48] | The R BigNum of an ECDSA signature. |
signature_s | u8[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
Name | Type | Description |
---|---|---|
chksum | u32 | Checksum over other input arguments, computed by the caller. Little endian. |
digest | u8[48] | SHA-384 digest to be signed. |
Table: MC_MLDSA_SIGN
output arguments
Name | Type | Description |
---|---|---|
chksum | u32 | |
fips_status | u32 | FIPS approved or an error |
pub_key_tree_type | u8[4] | LMS public key algorithm type. |
pub_key_ots_type | u8[4] | LM-OTS algorithm type. |
pub_key_id | u8[16] | Private key identifier. |
pub_key_digest | u8[24] | Public key hash value. |
signature_q | u8[4] | Leaf of the Merkle tree for the OTS key. |
signature_ots | u8[1252] | LM-OTS signature. |
signature_tree_path | u8[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
Name | Type | Description |
---|---|---|
chksum | u32 | |
length | u32 | Length of the message in DWORDs |
unlock_level | u8 | Debug unlock Level (Number 1-8) |
reserved | u8[3] | Reserved field |
Table: MC_PRODUCTION_DEBUG_UNLOCK_REQ
output arguments
Name | Type | Description |
---|---|---|
chksum | u32 | Checksum over other output arguments. |
fips_status | u32 | FIPS approved or an error |
length | u32 | Length of the message in DWORDs. |
unique_device_identifier | u8[32] | Device identifier of the Caliptra device. |
challenge | u8[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
Name | Type | Description |
---|---|---|
chksum | u32 | Checksum over other input arguments. |
fips_status | u32 | FIPS approved or an error |
length | u32 | Length of the message in DWORDs. |
unique_device_identifier | u8[32] | Device identifier of the Caliptra device. |
unlock_level | u8 | Debug unlock level (1-8). |
reserved | u8[3] | Reserved field. |
challenge | u8[48] | Random number challenge. |
ecc_public_key | u32[24] | ECC public key in hardware format (little endian). |
mldsa_public_key | u32[648] | MLDSA public key in hardware format (little endian). |
ecc_signature | u32[24] | ECC P-384 signature of the message hashed using SHA2-384 (R and S coordinates). |
mldsa_signature | u32[1157] | MLDSA signature of the message hashed using SHA2-512 (4627 bytes + 1 reserved byte). |
Table: MC_PRODUCTION_DEBUG_UNLOCK_TOKEN
output arguments
Name | Type | Description |
---|---|---|
chksum | u32 | |
fips_status | u32 | FIPS 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 Commands | Caliptra Mailbox Crypto Commands |
---|---|
MC_SHA_INIT | CM_SHA_INIT |
MC_SHA_UPDATE | CM_SHA_UPDATE |
MC_SHA_FINAL | CM_SHA_FINAL |
MC_AES_ENCRYPT_INIT | CM_AES_ENCRYPT_INIT |
MC_AES_ENCRYPT_UPDATE | CM_AES_ENCRYPT_UPDATE |
MC_AES_DECRYPT_INIT | CM_AES_DECRYPT_INIT |
MC_AES_DECRYPT_UPDATE | CM_AES_DECRYPT_UPDATE |
MC_AES_GCM_ENCRYPT_INIT | CM_AES_GCM_ENCRYPT_INIT |
MC_AES_GCM_ENCRYPT_UPDATE | CM_AES_GCM_ENCRYPT_UPDATE |
MC_AES_GCM_ENCRYPT_FINAL | CM_AES_GCM_ENCRYPT_FINAL |
MC_AES_GCM_DECRYPT_INIT | CM_AES_GCM_DECRYPT_INIT |
MC_AES_GCM_DECRYPT_UPDATE | CM_AES_GCM_DECRYPT_UPDATE |
MC_AES_GCM_DECRYPT_FINAL | CM_AES_GCM_DECRYPT_FINAL |
MC_ECDH_GENERATE | CM_ECDH_GENERATE |
MC_ECDH_FINISH | CM_ECDH_FINISH |
MC_RANDOM_STIR | CM_RANDOM_STIR |
MC_RANDOM_GENERATE | CM_RANDOM_GENERATE |
MC_IMPORT | CM_IMPORT |
MC_DELETE | CM_DELETE |