Skip to content

Commit 9fd816d

Browse files
authored
hw-model: retry mailbox lock in subsystem mode to fix nightly race (#3861)
The MCU ROM performs post-runtime mailbox activity that briefly races host commands on the FPGA subsystem profile, causing intermittent UnableToLockMailbox failures across the FPGA Subsystem Suite (smoke_test, fake_collateral_boot_test, etc.). Retry the mailbox lock in start_mailbox_execute when in subsystem mode, stepping the model between attempts so the MCU can release it. Extract the retry policy into a testable retry_acquire_mailbox_lock helper and add a unit test. Non-subsystem behavior is unchanged.
1 parent b5e7f2b commit 9fd816d

1 file changed

Lines changed: 94 additions & 2 deletions

File tree

hw-model/src/lib.rs

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,36 @@ pub const MCU_TEST_IV: [u8; 12] = [
789789
/// Stash Measurement Command Opcode.
790790
const STASH_MEASUREMENT_CMD_OPCODE: u32 = 0x4D45_4153;
791791

792+
const SUBSYSTEM_MBOX_LOCK_MAX_ATTEMPTS: u32 = 100;
793+
const SUBSYSTEM_MBOX_LOCK_STEPS_PER_ATTEMPT: u32 = 1000;
794+
795+
/// Operations needed by [`retry_acquire_mailbox_lock`], abstracted so the retry
796+
/// policy can be unit-tested without a hardware model.
797+
trait MailboxLockOps {
798+
/// Returns `true` if the mailbox lock is now held by us.
799+
fn try_acquire_lock(&mut self) -> bool;
800+
/// Advance the model so another agent can release the lock.
801+
fn step(&mut self);
802+
}
803+
804+
/// Acquire the mailbox lock, retrying while another agent holds it. Returns
805+
/// `true` if acquired within `max_attempts`.
806+
fn retry_acquire_mailbox_lock(
807+
ops: &mut impl MailboxLockOps,
808+
max_attempts: u32,
809+
steps_per_attempt: u32,
810+
) -> bool {
811+
for _ in 0..max_attempts {
812+
if ops.try_acquire_lock() {
813+
return true;
814+
}
815+
for _ in 0..steps_per_attempt {
816+
ops.step();
817+
}
818+
}
819+
false
820+
}
821+
792822
// Represents a emulator or simulation of the caliptra hardware, to be called
793823
// from tests. Typically, test cases should use [`crate::new()`] to create a model
794824
// based on the cargo features (and any model-specific environment variables).
@@ -1271,8 +1301,28 @@ pub trait HwModel: SocManager {
12711301
cmd: u32,
12721302
buf: &[u8],
12731303
) -> std::result::Result<(), ModelError> {
1274-
// Read a 0 to get the lock
1275-
if self.soc_mbox().lock().read().lock() {
1304+
// In the FPGA subsystem profile the MCU ROM performs post-runtime
1305+
// mailbox activity that briefly races host commands. Retry the lock,
1306+
// stepping the model so the MCU can release it.
1307+
if self.subsystem_mode() {
1308+
struct ModelLock<'a, M: HwModel + ?Sized>(&'a mut M);
1309+
impl<M: HwModel + ?Sized> MailboxLockOps for ModelLock<'_, M> {
1310+
fn try_acquire_lock(&mut self) -> bool {
1311+
!self.0.soc_mbox().lock().read().lock()
1312+
}
1313+
fn step(&mut self) {
1314+
self.0.step();
1315+
}
1316+
}
1317+
let acquired = retry_acquire_mailbox_lock(
1318+
&mut ModelLock(&mut *self),
1319+
SUBSYSTEM_MBOX_LOCK_MAX_ATTEMPTS,
1320+
SUBSYSTEM_MBOX_LOCK_STEPS_PER_ATTEMPT,
1321+
);
1322+
if !acquired {
1323+
return Err(ModelError::UnableToLockMailbox);
1324+
}
1325+
} else if self.soc_mbox().lock().read().lock() {
12761326
return Err(ModelError::UnableToLockMailbox);
12771327
}
12781328

@@ -1731,6 +1781,48 @@ mod tests {
17311781
rv32_gen.into_inner().empty_loop().build()
17321782
}
17331783

1784+
#[test]
1785+
fn test_retry_acquire_mailbox_lock() {
1786+
use crate::{retry_acquire_mailbox_lock, MailboxLockOps};
1787+
1788+
// Fake lock held by another agent until `free_after_steps` steps elapse.
1789+
struct FakeLock {
1790+
free_after_steps: u32,
1791+
steps: u32,
1792+
}
1793+
impl MailboxLockOps for FakeLock {
1794+
fn try_acquire_lock(&mut self) -> bool {
1795+
self.steps >= self.free_after_steps
1796+
}
1797+
fn step(&mut self) {
1798+
self.steps += 1;
1799+
}
1800+
}
1801+
1802+
// Free immediately: acquired without stepping.
1803+
let mut lock = FakeLock {
1804+
free_after_steps: 0,
1805+
steps: 0,
1806+
};
1807+
assert!(retry_acquire_mailbox_lock(&mut lock, 100, 1000));
1808+
assert_eq!(lock.steps, 0);
1809+
1810+
// Released within the retry budget.
1811+
let mut lock = FakeLock {
1812+
free_after_steps: 2500,
1813+
steps: 0,
1814+
};
1815+
assert!(retry_acquire_mailbox_lock(&mut lock, 100, 1000));
1816+
1817+
// Never released: fails after exhausting all attempts.
1818+
let mut lock = FakeLock {
1819+
free_after_steps: u32::MAX,
1820+
steps: 0,
1821+
};
1822+
assert!(!retry_acquire_mailbox_lock(&mut lock, 5, 10));
1823+
assert_eq!(lock.steps, 50);
1824+
}
1825+
17341826
#[test]
17351827
fn test_axi() {
17361828
let mut model = caliptra_hw_model::new_unbooted(InitParams {

0 commit comments

Comments
 (0)