Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions fw/pal/traits/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ pub enum HsmError {
/// Distinct from [`HsmError::InvalidArg`], which signals a
/// caller bug (unknown id, kind mismatch, etc.).
PartPropNotFound = 0x087000FF,

/// Returned by [`HsmSeedStore`](crate::HsmSeedStore)
/// (`mfgr_seed` / `owner_seed`) when no provisioned BKS seed row
/// carries the requested selector (SVN / owner id).
Expand Down Expand Up @@ -430,6 +431,10 @@ pub enum HsmError {
/// opted in via policy.
SdPeerCloningNotAllowed = 0x0870010A,

/// A cryptographic algorithm self-test (CAST) produced output that
/// did not match its known-answer vector.
SelfTestKatMismatch = 0x0870010B,

// Firmware-internal diagnostic codes logged by the CPU fault and panic
// exception handlers (`azihsm_fw_uno_fault`). These are not DDI protocol
// statuses: they use the PAL diagnostic facility (`0x08F`) to stay clear of
Expand Down
4 changes: 4 additions & 0 deletions fw/plat/uno/fw/app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@

#![no_std]
#![no_main]
// The `handle_io` Embassy task future is a deeply nested async state machine;
// with `trace-uart` enabled its layout computation exceeds rustc's default
// recursion limit of 128. Raise it to give headroom.
#![recursion_limit = "256"]

mod trampoline;

Expand Down
284 changes: 284 additions & 0 deletions fw/plat/uno/fw/drivers/rng/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,18 @@

use azihsm_fw_hsm_pal_traits::DmaBuf;
use azihsm_fw_static_ref::StaticRef;
use azihsm_fw_uno_error::HsmError;
use azihsm_fw_uno_error::HsmResult;
use azihsm_fw_uno_reg_soc::rng::regs::RngRegs;
use azihsm_fw_uno_reg_soc::rng::APT_CUTOFF;
use azihsm_fw_uno_reg_soc::rng::CHISQ_CUTOFF;
use azihsm_fw_uno_reg_soc::rng::CLK_DIV_MSB;
use azihsm_fw_uno_reg_soc::rng::CTRL;
use azihsm_fw_uno_reg_soc::rng::FWIN_DATA;
use azihsm_fw_uno_reg_soc::rng::FWOUT_DATA;
use azihsm_fw_uno_reg_soc::rng::GENERATE_INTERVAL;
use azihsm_fw_uno_reg_soc::rng::REPCNT_CUTOFF;
use azihsm_fw_uno_reg_soc::rng::RESEED_INTERVAL;
use azihsm_fw_uno_reg_soc::rng::RNG_BASE;
use azihsm_fw_uno_reg_soc::rng::RN_DATA;
use azihsm_fw_uno_reg_soc::rng::STATUS;
Expand Down Expand Up @@ -53,6 +58,113 @@ const REGS: StaticRef<RngRegs> = unsafe { StaticRef::new(RNG_BASE as *const RngR
/// and an imprecise bus fault on the `CTRL` MMIO write.
const RNG_INIT_DELAY_NOPS: u32 = 100_000 * 450 / 2;

/// Upper bound on wait-loop poll iterations in [`RngDriver::self_test`].
///
/// The reference firmware bounds each DRBG wait with a ~4 µs `Tcon::tsc()`
/// counter that Uno has no equivalent for. This poll-iteration budget is the
/// stand-in: generous enough never to trip during a healthy DRBG run (which
/// completes in microseconds), but finite so a stuck engine fails the CAST at
/// the boot gate instead of hanging forever.
const RNG_SELF_TEST_MAX_SPINS: u32 = 1_000_000;

/// DRBG generate interval (in generate cycles) used during the FW-mode KAT.
const DRBG_GENERATE_INTERVAL: u32 = 2;

/// DRBG reseed interval (in generate cycles) used during the FW-mode KAT.
const DRBG_RESEED_INTERVAL: u32 = 2;

/// DRBG FW-mode self-test input vector (seed material).
///
/// Ported byte-for-byte from the reference firmware
/// (`drivers/crypto/rng/src/rng.rs::RNG_SELF_TEST_INPUT`).
const RNG_SELF_TEST_INPUT: [u32; 16] = [
0x2cb8_5c71,
0xdef8_49bf,
0x5346_88e3,
0x03bf_f6bd,
0x9923_dfd1,
0x28e0_a0d7,
0xf38f_606c,
0xcd88_cb0b,
0xd41f_21da,
0x16b2_d32f,
0x041b_8db2,
0xb5b5_2b5b,
0x1700_01ef,
0x602d_910b,
0x5a17_e20f,
0xf9a0_b0b9,
];

/// Expected DRBG FW-mode generate output for [`RNG_SELF_TEST_INPUT`].
///
/// Ported byte-for-byte from the reference firmware
/// (`RNG_SELF_TEST_EXPECTED_OUTPUT`).
const RNG_SELF_TEST_EXPECTED_OUTPUT: [u32; 16] = [
0x2df5_26c1,
0x4ed2_fea1,
0xe03e_4e33,
0x773b_820d,
0x5363_125e,
0x5731_b848,
0xf922_7325,
0x8364_e0f5,
0xc0fd_533e,
0x1572_b04f,
0x678f_4cdc,
0xf989_cd2b,
0x580a_18c2,
0xe9d9_8573,
0x4789_01b9,
0xf6aa_61ed,
];

/// DRBG FW-mode self-test reseed input vector.
///
/// Ported byte-for-byte from the reference firmware
/// (`RNG_SELF_TEST_RESEED_INPUT`).
const RNG_SELF_TEST_RESEED_INPUT: [u32; 16] = [
0x9fc0_ef1b,
0x69bd_5cee,
0x0536_d040,
0x84e3_24ac,
0xe803_252f,
0x51f6_cb45,
0x9f42_5836,
0x85a1_550b,
0x1ebf_fe37,
0xcca0_724b,
0x77ec_8fcc,
0x1cd9_9781,
0x0022_6942,
0x3add_2d04,
0xb677_7a10,
0xac8f_0743,
];

/// Expected DRBG FW-mode reseed output for [`RNG_SELF_TEST_RESEED_INPUT`].
///
/// Ported byte-for-byte from the reference firmware
/// (`RNG_SELF_TEST_EXPECTED_RESEED_OUTPUT`).
const RNG_SELF_TEST_EXPECTED_RESEED_OUTPUT: [u32; 16] = [
0xa6fc_aa14,
0xd9cb_3539,
0x9ba9_b84e,
0x9864_e8ff,
0x079b_82c3,
0x32d3_18f6,
0x8fac_5900,
0x73fd_9211,
0xe2bd_a914,
0xaafe_a144,
0x523b_af42,
0x97f2_049e,
0x3221_4767,
0xa1fd_de1c,
0x141d_a708,
0x14b6_5a29,
];

/// TRBG / DRBG calibration parameters.
///
/// [`RngCalibration::DEFAULT`] provides the silicon-specific values
Expand Down Expand Up @@ -190,4 +302,176 @@ impl RngDriver {
}
}
}

/// DRBG FW-mode known-answer self-test (pre-operational CAST).
///
/// Ported as-is from the reference firmware
/// (`drivers/crypto/rng/src/rng.rs::self_test`). Drives the DRBG through
/// its firmware-mode back door: seeds the FW input FIFO with a fixed
/// vector, runs instantiate + generate, and compares the FW output FIFO
/// against the expected known answer; then exercises the reseed path with
/// a second fixed vector. The generate/reseed intervals are saved on entry
/// and restored on every path. On the success path the full saved control
/// word is additionally written back; an early-exit (failure) path instead
/// returns the control word to normal DRBG mode by clearing the
/// FW-mode/instantiate/generate bits it set (matching the reference
/// firmware, which heads to a self-test error state on failure). Either
/// way the RNG is left in its normal operating mode.
///
/// # Returns
/// * `Ok(())` if both the generate and reseed outputs match their vectors.
///
/// # Errors
/// * [`HsmError::SelfTestKatMismatch`] on any output mismatch, DRBG fault,
/// or wait-loop timeout.
///
/// # Timeout
/// The reference bounds each wait with a ~4 µs `Tcon::tsc()` counter that
/// Uno lacks; here each wait loop is bounded by [`RNG_SELF_TEST_MAX_SPINS`]
/// poll iterations instead. Exhausting the budget fails the test (never a
/// hang), so a stuck DRBG surfaces a clean CAST failure at the boot gate.
pub fn self_test(&self) -> HsmResult<()> {
// Before switching to FW mode, ensure the DRBG is not actively reading
// entropy — FW mode cannot be entered while an entropy read is in
// flight.
Self::spin_while(|| REGS.status.is_set(STATUS::ENTROPY_FIFO_READ))?;

// Save the production register state so it can be restored on exit.
let saved_generate_interval = REGS.generate_interval.get();
let saved_reseed_interval = REGS.reseed_interval.get();
let saved_ctrl = REGS.ctrl.get();

// Shorten the generate/reseed intervals so the KAT exercises a reseed.
REGS.generate_interval
.write(GENERATE_INTERVAL::VALUE.val(DRBG_GENERATE_INTERVAL));
REGS.reseed_interval
.write(RESEED_INTERVAL::VALUE.val(DRBG_RESEED_INTERVAL));

// Enter FW mode and kick off instantiate + generate.
REGS.ctrl.modify(
CTRL::ENABLE::SET
+ CTRL::FW_MODE::SET
+ CTRL::DRBG_INSTANTIATE::SET
+ CTRL::DRBG_GENERATE::SET
+ CTRL::DRBG_UNINSTANTIATE::CLEAR,
);

// Restore the RNG to normal mode; run on every early-exit path.
let cleanup = || {
REGS.ctrl.modify(
CTRL::DRBG_GENERATE::CLEAR + CTRL::DRBG_INSTANTIATE::CLEAR + CTRL::FW_MODE::CLEAR,
);
REGS.generate_interval.set(saved_generate_interval);
REGS.reseed_interval.set(saved_reseed_interval);
};

// Fill the input FIFO; once full the DRBG starts processing it.
for word in RNG_SELF_TEST_INPUT {
REGS.fwin_data.write(FWIN_DATA::DATA.val(word));
}

Self::wait_for_drbg_instantiate().inspect_err(|_e| cleanup())?;
Self::wait_for_drbg_generate().inspect_err(|_e| cleanup())?;
Self::compare_output(&RNG_SELF_TEST_EXPECTED_OUTPUT).inspect_err(|_e| cleanup())?;

// Reseed path: wait for the DRBG to request a reseed, feed it the
// reseed vector, then wait for it to complete.
Self::wait_till_status_is_set_to_busy().inspect_err(|_e| cleanup())?;
Self::wait_till_reseed_is_set().inspect_err(|_e| cleanup())?;

for word in RNG_SELF_TEST_RESEED_INPUT {
REGS.fwin_data.write(FWIN_DATA::DATA.val(word));
}

Self::wait_for_reseed_clear().inspect_err(|_e| cleanup())?;
Self::wait_for_busy_status_clear().inspect_err(|_e| cleanup())?;

// Compare the reseed output (the reference does not discard here).
for word in RNG_SELF_TEST_EXPECTED_RESEED_OUTPUT {
if REGS.fwout_data.read(FWOUT_DATA::DATA) != word {
cleanup();
return Err(HsmError::SelfTestKatMismatch);
}
}

// Restore the RNG to its normal operating mode.
REGS.generate_interval.set(saved_generate_interval);
REGS.reseed_interval.set(saved_reseed_interval);
REGS.ctrl.set(saved_ctrl);
REGS.ctrl.modify(
CTRL::FW_MODE::CLEAR + CTRL::DRBG_INSTANTIATE::CLEAR + CTRL::DRBG_GENERATE::CLEAR,
);

Ok(())
}

/// Poll `busy` until it returns `false`, bounded by
/// [`RNG_SELF_TEST_MAX_SPINS`]. Returns [`HsmError::SelfTestKatMismatch`]
/// if the budget is exhausted (the Uno stand-in for the reference's
/// `Tcon::tsc()` timeout).
fn spin_while<F: Fn() -> bool>(busy: F) -> HsmResult<()> {
let mut budget = RNG_SELF_TEST_MAX_SPINS;
while busy() {
budget -= 1;
if budget == 0 {
return Err(HsmError::SelfTestKatMismatch);
}
}
Ok(())
}

/// Wait for the DRBG instantiate operation to finish, failing early on a
/// DRBG fault.
fn wait_for_drbg_instantiate() -> HsmResult<()> {
let mut budget = RNG_SELF_TEST_MAX_SPINS;
while REGS.status.is_set(STATUS::DRBG_INST_BUSY) {
if REGS.status.is_set(STATUS::DRBG_FAULT_ERROR) {
return Err(HsmError::SelfTestKatMismatch);
}
budget -= 1;
if budget == 0 {
return Err(HsmError::SelfTestKatMismatch);
}
}
Ok(())
}

/// Wait for the DRBG generate operation to complete (`STATUS.BUSY` clear).
fn wait_for_drbg_generate() -> HsmResult<()> {
Self::spin_while(|| REGS.status.is_set(STATUS::BUSY))
}

/// Wait for `STATUS.BUSY` to be set (a reseed request is pending).
fn wait_till_status_is_set_to_busy() -> HsmResult<()> {
Self::spin_while(|| !REGS.status.is_set(STATUS::BUSY))
}

/// Wait for the DRBG reseed-busy flag to be set.
fn wait_till_reseed_is_set() -> HsmResult<()> {
Self::spin_while(|| !REGS.status.is_set(STATUS::DRBG_RESEED_BUSY))
}

/// Wait for the DRBG reseed-busy flag to clear.
fn wait_for_reseed_clear() -> HsmResult<()> {
Self::spin_while(|| REGS.status.is_set(STATUS::DRBG_RESEED_BUSY))
}

/// Wait for `STATUS.BUSY` to clear.
fn wait_for_busy_status_clear() -> HsmResult<()> {
Self::spin_while(|| REGS.status.is_set(STATUS::BUSY))
}

/// Read and discard the first 16 FW-output words, then compare the next 16
/// against `expected`. Mirrors the reference `compare_output`.
fn compare_output(expected: &[u32; 16]) -> HsmResult<()> {
for _ in 0..expected.len() {
let _ = REGS.fwout_data.read(FWOUT_DATA::DATA);
}
for &word in expected {
if REGS.fwout_data.read(FWOUT_DATA::DATA) != word {
return Err(HsmError::SelfTestKatMismatch);
}
}
Ok(())
}
}
4 changes: 2 additions & 2 deletions fw/plat/uno/fw/drivers/sha/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -622,8 +622,8 @@ fn write_cmd(idx: usize, req: &ShaRequest<'_>, mode: u8) {
0
};
let ref_digest_addr = req.ref_digest.map_or(0, |d| d.as_ptr() as u32);
// Plain pointer writes — SHA_CMD is in DTCM, not MMIO.
// The COMMAND register write (submit_cmd) is the barrier.
// Plain pointer writes — SHA_CMD is in DTCM, not MMIO. A `dmb()` in
// `submit_cmd` orders these writes before the doorbell.
let entry_ptr = (&SHA_Q.sha_cmd[idx]) as *const ShaCmdEntry as *mut u32;
unsafe {
entry_ptr.add(0).write(cmd.into());
Expand Down
28 changes: 23 additions & 5 deletions fw/plat/uno/fw/drivers/upka/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,10 @@ impl<const DEPTH: usize, const ENGINES: usize> UpkaEngine<'_, DEPTH, ENGINES> {
/// # Parameters
///
/// - `key_type`: RSA key type (size + format selector).
/// - `key`: DMA-capable private key buffer.
/// - `key`: DMA-capable private key buffer. For standard keys this is a
/// `d ‖ n` blob at least `2 * mod_size` bytes long; for CRT keys it is a
/// contiguous `param1 ‖ param2` blob (`param1` = `p ‖ q ‖ dp ‖ dq`,
/// `param2` = `n ‖ n1q ‖ n2p`) at least `5 * mod_size` bytes long.
/// - `input`: DMA-capable input block buffer.
/// - `output`: DMA-capable output block buffer.
///
Expand All @@ -404,16 +407,31 @@ impl<const DEPTH: usize, const ENGINES: usize> UpkaEngine<'_, DEPTH, ENGINES> {
output: &mut DmaBuf,
) -> HsmResult<()> {
let mod_size = rsa_mod_size(key_type);
Self::ensure_cmd_input(
!key.is_empty() && input.len() == mod_size && output.len() == mod_size,
)?;

// CRT private keys are a contiguous `param1 ‖ param2` blob: `param1`
// (`p ‖ q ‖ dp ‖ dq`, 2·mod_size) is read from the key/arg2 pointer and
// `param2` (`n ‖ n1q ‖ n2p`, 3·mod_size) is read from arg3. Standard
// (non-CRT) keys are a single `d ‖ n` blob read from arg2 with arg3 = 0.
let (key_ok, arg3) = if rsa_is_crt(key_type) {
let param2_off = rsa_crt_param1_len(key_type);
(
key.len() >= param2_off + 3 * mod_size,
key.as_ptr() as u32 + param2_off as u32,
)
} else {
// Standard keys are a single `d ‖ n` blob (2·mod_size) read from
// arg2; the buffer must be long enough for the hardware's full
// fetch to stay in bounds.
(key.len() >= 2 * mod_size, 0)
};
Self::ensure_cmd_input(key_ok && input.len() == mod_size && output.len() == mod_size)?;

self.execute_cmd(
rsa_priv_opcode(key_type),
output.as_mut_ptr() as u32,
input.as_ptr() as u32,
key.as_ptr() as u32,
0,
arg3,
)
.await
}
Expand Down
Loading