DOE Stack

The Caliptra subsystem supports SPDM, Secure-SPDM over PCI Data Object Exchange (DOE) mailbox protocol. The following diagram gives the over view of the DOE send and receive stack.

DOE Receive stack:

The DOE Tock receive stack

DOE Send stack:

The DOE Tock send stack

sequenceDiagram
    participant Host as "Host(TSM)"
    participant SoC_PCI_DOE_FSM as "SoC PCI DOE Listener"
    participant MCU_DOE_TRANSPORT_DRIVER as "MCU DOE Transport Driver"
    participant DOE_CAPSULE as "DOE Capsule"
    participant SPDM_APP as "SPDM App"
    SPDM_APP -->> DOE_CAPSULE: App invokes receive_message<br> for SPDM or Secure-SPDM Data Object type 
    Host ->> Host: Host waits until<br> the `DOE Busy` bit is cleared in <br>DOE Status Register
    loop While there is remaining DOE object to send
        Host ->> SoC_PCI_DOE_FSM  : Host starts sending DOE data object
        SoC_PCI_DOE_FSM ->> SoC_PCI_DOE_FSM: Prepare the message in staging area <br> (eg: Mailbox or shared memory)
        Note right of Host: Repeat until Host sets DOE Go bit
        Host ->> SoC_PCI_DOE_FSM: Host writes `DOE Go` bit <br>in DOE Control Register<br> to indicate message ready
    end
    SoC_PCI_DOE_FSM ->> MCU_DOE_TRANSPORT_DRIVER: Notify that a new DOE object<br> is available to consume
    MCU_DOE_TRANSPORT_DRIVER ->> DOE_CAPSULE: receive() callback

    alt if DOE object is `Data Object 0`
        DOE_CAPSULE ->> DOE_CAPSULE: Copy DOE object payload <br>into local buffer
        DOE_CAPSULE ->> DOE_CAPSULE: Handle DOE Discovery
        DOE_CAPSULE ->> DOE_CAPSULE: Prepare DOE Discovery response object
    else if DOE object is `Data Object 1 or 2`
        DOE_CAPSULE ->> DOE_CAPSULE: Copy DOE object payload <br>into app buffer
        DOE_CAPSULE -->> SPDM_APP: Invoke upcall to userspace<br> to receive() message
    end
    DOE_CAPSULE ->> MCU_DOE_TRANSPORT_DRIVER: set_rx_buffer()<br> to set the receive buffer for the next DOE object
    SPDM_APP ->> SPDM_APP: App processes message <br>and prepares DOE response
    SPDM_APP -->> DOE_CAPSULE: App invokes send_message <br>to send DOE response
    DOE_CAPSULE ->> MCU_DOE_TRANSPORT_DRIVER: invoke transmit()<br> to send the DOE response
    MCU_DOE_TRANSPORT_DRIVER ->> SoC_PCI_DOE_FSM: Notify that DOE response is ready to send
    SoC_PCI_DOE_FSM ->> Host: Set `Data Object Ready` bit in<br> DOE Status Register

DOE Capsule

The DOE capsule implements the system calls for the user space applications to send and receive the DOE data objects.

During board initialization, a DoeDriver instance is created and registered with a unique driver number. This instance manages the handling of DOE Discovery (Data Object Type 0), SPDM (Data Object Type 1), and Secure-SPDM (Data Object Type 2) data objects.


/// PCI-SIG Vendor ID that defined the data object type
const PCI_SIG_VENDOR_ID: u16 = 0x0001;
/// Data Object Protocol
const DATA_OBJECT_PROTOCOL_DOE_DISCOVERY: u8 = 0x00;
const DATA_OBJECT_PROTOCOL_CMA_SPDM: u8 = 0x01;
const DATA_OBJECT_PROTOCOL_SECURE_CMA_SPDM: u8 = 0x02;

pub const DOE_SPDM_DRIVER_NUM: usize = 0xA000_0010;

/// IDs for subscribe calls
mod upcall {
    /// Callback for when the message is received
    pub const MESSAGE_RECEIVED: usize = 0;

    /// Callback for when the message is transmitted.
    pub const MESSAGE_TRANSMITTED: usize = 1;

    /// Number of upcalls
    pub const COUNT: u8 = 2;
}

/// IDs for read-only allow buffers
mod ro_allow {
    /// Buffer for the message to be transmitted
    pub const MESSAGE_WRITE: usize = 0;

    /// Number of read-only allow buffers
    pub const COUNT: u8 = 1;
}

/// IDs for read-write allow buffers
mod rw_allow {
    /// Buffer for the message to be received
    pub const MESSAGE_READ: usize = 0;

    /// Number of read-write allow buffers
    pub const COUNT: u8 = 1;
}

#[derive(Default)]
pub struct App {
    waiting_rx: Cell<bool>, // Indicates if a message is waiting to be received
    pending_tx: Cell<bool>, // Indicates if a message is in progress
}


pub struct DoeDriver {
    doe_transport: & dyn DoeTransport,
    apps: Grant<
        App,
        UpcallCount<{ upcall::COUNT }>,
        AllowRoCount<{ ro_allow::COUNT }>,
        AllowRwCount<{ rw_allow::COUNT }>,
    >,
    current_app: Cell<Option<ProcessId>>,
}

DOE Transport Trait

The DOE Transport trait defines a platform-agnostic interface for sending and receiving DOE data objects. Integrators must provide a SoC-specific implementation of this trait to enable PCI-DOE communication with the host.


pub trait DoeTransportTxClient<'a> {
    /// Called by driver to notify that the DOE data object transmission is done.
    ///
    /// # Arguments
    /// * `result` - Result indicating success or failure of the transmission
    fn send_done(&self, result: Result<(), ErrorCode>);
}

pub trait DoeTransportRxClient {
    /// Called to receive a DOE data object.
    ///
    /// # Arguments
    /// * `rx_buf` - buffer containing the received DOE data object
    /// * `len_dw` - The length of the data received in dwords
    fn receive(&self, rx_buf: &'static mut [u32], len_dw: usize);
}

pub trait DoeTransport<'a> {
    /// Sets the transmit and receive clients for the DOE transport instance
    fn set_tx_client(&self, client: &'a dyn DoeTransportTxClient<'a>);
    fn set_rx_client(&self, client: &'a dyn DoeTransportRxClient);

    /// Sets the buffer used for receiving incoming DOE Objects.
    /// This should be called in receive()
    fn set_rx_buffer(&self, rx_buf: &'static mut [u32]);

    /// Gets the maximum size of the data object that can be sent or received over DOE Transport.
    fn max_data_object_size_dw(&self) -> usize;

    /// Enable the DOE transport driver instance.
    fn enable(&self);

    /// Disable the DOE transport driver instance.
    fn disable(&self);

    /// Send DOE Object to be transmitted over SoC specific DOE transport.
    ///
    /// # Arguments
    /// * `tx_buf` - Iterator that yields u32 values from data object to be transmitted.
    /// * `len` - The length of the message in dwords (4-byte words).
    fn transmit(&self, tx_buf: impl Iterator<Item = u32>, len_dw: usize) -> Result<(), ErrorCode>;
}