Skip to content

Commit

Permalink
[feat] Firmware image download from Recovery Interface (#1867)
Browse files Browse the repository at this point in the history
This change adds the ROM flow to download the firmware image from the recovery interface.
  • Loading branch information
mhatrevi authored Jan 23, 2025
1 parent 051960e commit b764d0a
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 28 deletions.
3 changes: 3 additions & 0 deletions api/src/mailbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ impl CommandId {
// The authorize and stash command.
pub const AUTHORIZE_AND_STASH: Self = Self(0x4154_5348); // "ATSH"

// The download firmware from recovery interface command.
pub const RI_DOWNLOAD_FIRMWARE: Self = Self(0x5249_4644); // "RIFD"

// The get IDevID ECC CSR command.
pub const GET_IDEV_ECC_CSR: Self = Self(0x4944_4352); // "IDCR"
}
Expand Down
9 changes: 9 additions & 0 deletions drivers/src/dma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,4 +347,13 @@ impl Dma {
self.do_transaction()?;
Ok(())
}

/// Indicates if payload is available.
///
/// # Returns
/// true if payload is available, false otherwise
///
pub fn payload_available(&self) -> bool {
self.dma.regs().status0().read().payload_available()
}
}
8 changes: 8 additions & 0 deletions drivers/src/mailbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ impl Mailbox {
}
}

// Fake mailbox transaction used when downloading firmware image from Recovery Interface.
pub fn fake_recv_txn(&mut self) -> MailboxRecvTxn {
MailboxRecvTxn {
state: MailboxOpState::Execute,
mbox: &mut self.mbox,
}
}

/// Lets the caller peek into the mailbox without touching the transaction.
pub fn peek_recv(&mut self) -> Option<MailboxRecvPeek> {
let mbox = self.mbox.regs();
Expand Down
6 changes: 6 additions & 0 deletions drivers/src/soc_ifc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,12 @@ impl SocIfc {
let high = self.soc_ifc.regs().ss_otp_fc_base_addr_h().read();
(high as u64) << 32 | low as u64
}

pub fn recovery_interface_base_addr(&self) -> u64 {
let low = self.soc_ifc.regs().ss_recovery_ifc_base_addr_l().read();
let high = self.soc_ifc.regs().ss_recovery_ifc_base_addr_h().read();
(high as u64) << 32 | low as u64
}
}

bitflags::bitflags! {
Expand Down
2 changes: 2 additions & 0 deletions error/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,8 @@ impl CaliptraError {
pub const FW_PROC_MAILBOX_RESERVED_PAUSER: CaliptraError = CaliptraError::new_const(0x01020009);
pub const FW_PROC_MAILBOX_GET_IDEV_CSR_UNPROVISIONED_CSR: CaliptraError =
CaliptraError::new_const(0x0102000A);
pub const FW_PROC_MAILBOX_FW_LOAD_CMD_IN_ACTIVE_MODE: CaliptraError =
CaliptraError::new_const(0x0102000B);

/// FMC Alias Layer : Certificate Verification Failure.
pub const FMC_ALIAS_CERT_VERIFY: CaliptraError = CaliptraError::new_const(0x01030001);
Expand Down
201 changes: 174 additions & 27 deletions rom/dev/src/flow/cold_reset/fw_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use caliptra_image_types::{ImageManifest, IMAGE_BYTE_SIZE};
use caliptra_image_verify::{ImageVerificationInfo, ImageVerificationLogInfo, ImageVerifier};
use caliptra_kat::KatsEnv;
use caliptra_x509::{NotAfter, NotBefore};
use core::mem::ManuallyDrop;
use core::mem::{size_of, ManuallyDrop};
use zerocopy::{AsBytes, LayoutVerified};
use zeroize::Zeroize;

Expand Down Expand Up @@ -87,10 +87,11 @@ impl FirmwareProcessor {
sha_acc_lock_state: ShaAccLockState::NotAcquired,
};
// Process mailbox commands.
let mut txn = Self::process_mailbox_commands(
let (mut txn, image_size_bytes) = Self::process_mailbox_commands(
&mut env.soc_ifc,
&mut env.mbox,
&mut env.pcr_bank,
&mut env.dma,
&mut kats_env,
env.persistent_data.get_mut(),
)?;
Expand All @@ -103,7 +104,11 @@ impl FirmwareProcessor {
};

// Load the manifest into DCCM.
let manifest = Self::load_manifest(&mut env.persistent_data, &mut txn);
let manifest = Self::load_manifest(
&mut env.persistent_data,
&mut txn,
env.soc_ifc.active_mode(),
);
let manifest = okref(&manifest)?;

let mut venv = FirmwareImageVerificationEnv {
Expand All @@ -118,7 +123,7 @@ impl FirmwareProcessor {
};

// Verify the image
let info = Self::verify_image(&mut venv, manifest, txn.dlen());
let info = Self::verify_image(&mut venv, manifest, image_size_bytes);
let info = okref(&info)?;

Self::update_fuse_log(&mut env.persistent_data.get_mut().fuse_log, &info.log_info)?;
Expand All @@ -137,7 +142,7 @@ impl FirmwareProcessor {
report_boot_status(FwProcessorExtendPcrComplete.into());

// Load the image
Self::load_image(manifest, &mut txn)?;
Self::load_image(manifest, &mut txn, env.soc_ifc.active_mode())?;

// Complete the mailbox transaction indicating success.
txn.complete(true)?;
Expand Down Expand Up @@ -168,7 +173,8 @@ impl FirmwareProcessor {
/// * `soc_ifc` - SOC Interface
/// * `mbox` - Mailbox
/// * `pcr_bank` - PCR Bank
/// * `sha384` - SHA384
/// * `dma` - DMA engine
/// * `env` - KAT Environment
/// * `persistent_data` - Persistent data
///
/// # Returns
Expand All @@ -184,10 +190,12 @@ impl FirmwareProcessor {
soc_ifc: &mut SocIfc,
mbox: &'a mut Mailbox,
pcr_bank: &mut PcrBank,
dma: &mut Dma,
env: &mut KatsEnv,
persistent_data: &mut PersistentData,
) -> CaliptraResult<ManuallyDrop<MailboxRecvTxn<'a>>> {
) -> CaliptraResult<(ManuallyDrop<MailboxRecvTxn<'a>>, u32)> {
let mut self_test_in_progress = false;
let active_mode = soc_ifc.active_mode();

cprintln!("[fwproc] Wait for Commands...");
loop {
Expand All @@ -206,6 +214,10 @@ impl FirmwareProcessor {

// Handle FW load as a separate case due to the re-borrow explained below
if txn.cmd() == CommandId::FIRMWARE_LOAD.into() {
if active_mode {
Err(CaliptraError::FW_PROC_MAILBOX_FW_LOAD_CMD_IN_ACTIVE_MODE)?;
}

// Re-borrow mailbox to work around https://github.com/rust-lang/rust/issues/54663
let txn = mbox
.peek_recv()
Expand All @@ -215,14 +227,15 @@ impl FirmwareProcessor {
// transaction will be completed by either handle_fatal_error() (on
// failure) or by a manual complete call upon success.
let txn = ManuallyDrop::new(txn.start_txn());
if txn.dlen() == 0 || txn.dlen() > IMAGE_BYTE_SIZE as u32 {
cprintln!("Invalid Img size: {} bytes" txn.dlen());
let image_size_bytes = txn.dlen();
if image_size_bytes == 0 || image_size_bytes > IMAGE_BYTE_SIZE as u32 {
cprintln!("Invalid Image of size {} bytes", image_size_bytes);
return Err(CaliptraError::FW_PROC_INVALID_IMAGE_SIZE);
}

cprintln!("[fwproc] Recv'd Img size: {} bytes" txn.dlen());
cprintln!("[fwproc] Received Image of size {} bytes", image_size_bytes);
report_boot_status(FwProcessorDownloadImageComplete.into());
return Ok(txn);
return Ok((txn, image_size_bytes));
}

// NOTE: We use ManuallyDrop here because any error here becomes a fatal error
Expand Down Expand Up @@ -343,6 +356,27 @@ impl FirmwareProcessor {
resp.populate_chksum();
txn.send_response(resp.as_bytes())?;
}
CommandId::RI_DOWNLOAD_FIRMWARE => {
if !active_mode {
cprintln!(
"[fwproc] RI_DOWNLOAD_FIRMWARE cmd not supported in passive mode"
);
txn.complete(false)?;
Err(CaliptraError::FW_PROC_MAILBOX_INVALID_COMMAND)?;
}
txn.complete(true)?;

// Download the firmware image from the recovery interface.
let image_size_bytes =
Self::retrieve_image_from_recovery_interface(dma, soc_ifc)?;
let txn = ManuallyDrop::new(mbox.fake_recv_txn());
cprintln!(
"[fwproc] Received Image from Recovery Interface of size {} bytes",
image_size_bytes
);
report_boot_status(FwProcessorDownloadImageComplete.into());
return Ok((txn, image_size_bytes));
}
_ => {
cprintln!("[fwproc] Invalid command received");
// Don't complete the transaction here; let the fatal
Expand All @@ -364,9 +398,19 @@ impl FirmwareProcessor {
fn load_manifest(
persistent_data: &mut PersistentDataAccessor,
txn: &mut MailboxRecvTxn,
active_mode: bool,
) -> CaliptraResult<ImageManifest> {
let manifest = &mut persistent_data.get_mut().manifest1;
txn.copy_request(manifest.as_bytes_mut())?;
if active_mode {
let mbox_sram = txn.raw_mailbox_contents();
let manifest_buf = manifest.as_bytes_mut();
if mbox_sram.len() < manifest_buf.len() {
Err(CaliptraError::FW_PROC_INVALID_IMAGE_SIZE)?;
}
manifest_buf.copy_from_slice(&mbox_sram[..manifest_buf.len()]);
} else {
txn.copy_request(manifest.as_bytes_mut())?;
}
report_boot_status(FwProcessorManifestLoadComplete.into());
Ok(*manifest)
}
Expand Down Expand Up @@ -507,38 +551,68 @@ impl FirmwareProcessor {
///
/// # Arguments
///
/// * `env` - ROM Environment
/// * `manifest` - Manifest
/// * `txn` - Mailbox Receive Transaction
/// * `active_mode` - Indicates if ROM is running in the Active mode
// Inlined to reduce ROM size
#[inline(always)]
#[cfg_attr(not(feature = "no-cfi"), cfi_impl_fn)]
fn load_image(manifest: &ImageManifest, txn: &mut MailboxRecvTxn) -> CaliptraResult<()> {
fn load_image(
manifest: &ImageManifest,
txn: &mut MailboxRecvTxn,
active_mode: bool,
) -> CaliptraResult<()> {
cprintln!(
"[fwproc] Load FMC at address 0x{:08x} len {}",
manifest.fmc.load_addr,
manifest.fmc.size
);

let fmc_dest = unsafe {
let addr = (manifest.fmc.load_addr) as *mut u32;
core::slice::from_raw_parts_mut(addr, manifest.fmc.size as usize / 4)
};

txn.copy_request(fmc_dest.as_bytes_mut())?;
if active_mode {
let mbox_sram = txn.raw_mailbox_contents();
let fmc_dest = unsafe {
let addr = (manifest.fmc.load_addr) as *mut u8;
core::slice::from_raw_parts_mut(addr, manifest.fmc.size as usize)
};
let start = size_of::<ImageManifest>();
let end = start + fmc_dest.len();
if start > end || mbox_sram.len() < end {
Err(CaliptraError::FW_PROC_INVALID_IMAGE_SIZE)?;
}
fmc_dest.copy_from_slice(&mbox_sram[start..end]);
} else {
let fmc_dest = unsafe {
let addr = (manifest.fmc.load_addr) as *mut u32;
core::slice::from_raw_parts_mut(addr, manifest.fmc.size as usize / 4)
};
txn.copy_request(fmc_dest.as_bytes_mut())?;
}

cprintln!(
"[fwproc] Load Runtime at address 0x{:08x} len {}",
manifest.runtime.load_addr,
manifest.runtime.size
);

let runtime_dest = unsafe {
let addr = (manifest.runtime.load_addr) as *mut u32;
core::slice::from_raw_parts_mut(addr, manifest.runtime.size as usize / 4)
};

txn.copy_request(runtime_dest.as_bytes_mut())?;
if active_mode {
let mbox_sram = txn.raw_mailbox_contents();
let runtime_dest = unsafe {
let addr = (manifest.runtime.load_addr) as *mut u8;
core::slice::from_raw_parts_mut(addr, manifest.runtime.size as usize)
};
let start = size_of::<ImageManifest>() + manifest.fmc.size as usize;
let end = start + runtime_dest.len();
if start > end || mbox_sram.len() < end {
Err(CaliptraError::FW_PROC_INVALID_IMAGE_SIZE)?;
}
runtime_dest.copy_from_slice(&mbox_sram[start..end]);
} else {
let runtime_dest = unsafe {
let addr = (manifest.runtime.load_addr) as *mut u32;
core::slice::from_raw_parts_mut(addr, manifest.runtime.size as usize / 4)
};
txn.copy_request(runtime_dest.as_bytes_mut())?;
}

report_boot_status(FwProcessorLoadImageComplete.into());
Ok(())
Expand Down Expand Up @@ -699,7 +773,7 @@ impl FirmwareProcessor {
Self::log_measurement(persistent_data, stash_measurement)
}

/// Log mesaure data to the Stash Measurement log
/// Log measurement data to the Stash Measurement log
///
/// # Arguments
/// * `persistent_data` - Persistent data
Expand Down Expand Up @@ -735,4 +809,77 @@ impl FirmwareProcessor {

Ok(())
}

/// Retrieve the fw image from the recovery interface and store it in the mailbox sram.
///
/// # Arguments
/// * `dma` - DMA driver
/// * `soc_ifc` - SOC Interface
///
/// # Returns
/// * `()` - Ok
/// Error code on failure.
fn retrieve_image_from_recovery_interface(
dma: &mut Dma,
soc_ifc: &mut SocIfc,
) -> CaliptraResult<u32> {
let rri_base_addr: u64 = soc_ifc.recovery_interface_base_addr();
const PROT_CAP_2_OFFSET: u32 = 0xC;
const DEVICE_STATUS_0: u32 = 0x30;
const RECOVERY_STATUS_OFFSET: u32 = 0x40;
const FW_IMAGE_INDEX: u32 = 0x0;
// const INDIRECT_FIFO_CTRL_0: u32 = 0x48;
const INDIRECT_FIFO_CTRL_1: u32 = 0x4C;
const INDIRECT_FIFO_DATA_OFFSET: u32 = 0x68;
const RECOVERY_DMA_BLOCK_SIZE_BYTES: u32 = 256;

// Set PROT_CAP:Byte11 Bit3 (i.e. DWORD2:Bit27) to 1 ('Flashless boot').
let addr = AxiAddr::from(rri_base_addr + PROT_CAP_2_OFFSET as u64);
let mut prot_cap_val = dma.read_dword(addr)?;
prot_cap_val |= 1 << 27;
dma.write_dword(addr, prot_cap_val)?;

// Set DEVICE_STATUS:Byte0 to 0x3 ('Recovery mode - ready to accept recovery image').
let addr = AxiAddr::from(rri_base_addr + DEVICE_STATUS_0 as u64);
let mut device_status_val = dma.read_dword(addr)?;
device_status_val = (device_status_val & 0xFFFFFF00) | 0x03;

// Set DEVICE_STATUS:Byte[2:3] to 0x12 ('Recovery Reason Codes' 0x12 = 0 Flashless/Streaming Boot (FSB)).
device_status_val = (device_status_val & 0xFF00FFFF) | (0x12 << 16);

dma.write_dword(addr, device_status_val)?;

// Set RECOVERY_STATUS:Byte0 Bit[3:0] to 0x1 ('Awaiting recovery image') &
// Byte0 Bit[7:4] to 0 (Recovery image index).
let addr = AxiAddr::from(rri_base_addr + RECOVERY_STATUS_OFFSET as u64);
let mut recovery_status_val = dma.read_dword(addr)?;

// Set Byte0 Bit[3:0] to 0x1 ('Awaiting recovery image')
recovery_status_val = (recovery_status_val & 0xFFFFFFF0) | 0x1;

// Set Byte0 Bit[7:4] to FW_IMAGE_INDEX (Recovery image index)
recovery_status_val = (recovery_status_val & 0xFFFFFF0F) | (FW_IMAGE_INDEX << 4);

dma.write_dword(addr, recovery_status_val)?;

// Loop on the 'payload_available' signal for the recovery image details to be available.
cprintln!("[fwproc] Waiting for payload available signal...");
while !dma.payload_available() {}

// Read the image size from INDIRECT_FIFO_CTRL:Byte[2:5]. Image size in DWORDs.
// let addr0 = AxiAddr::from(rri_base_addr + INDIRECT_FIFO_CTRL_0 as u64);
// [TODO][CAP2] we need to program CMS bits, currently they are not available in RDL. Using bytes[4:7] for now for size
let addr1 = AxiAddr::from(rri_base_addr + INDIRECT_FIFO_CTRL_1 as u64);
// let indirect_fifo_ctrl_val0 = dma.read_dword(addr0)?;
let indirect_fifo_ctrl_val1 = dma.read_dword(addr1)?;
// let image_size_dwords =
// ((indirect_fifo_ctrl_val0 >> 16) & 0xFFFF) | ((indirect_fifo_ctrl_val1 & 0xFFFF) << 16);
let image_size_dwords = indirect_fifo_ctrl_val1;

// Transfer the image from the recovery interface to the mailbox SRAM.
let image_size_bytes = image_size_dwords * 4;
let addr = AxiAddr::from(rri_base_addr + INDIRECT_FIFO_DATA_OFFSET as u64);
dma.transfer_payload_to_mbox(addr, image_size_bytes, true, RECOVERY_DMA_BLOCK_SIZE_BYTES)?;
Ok(image_size_bytes)
}
}
Loading

0 comments on commit b764d0a

Please sign in to comment.