Repository: chipsalliance/caliptra-sw
Pull request #517 (2023-07-19 21:35:51)

Warm reset test
HW-model support for warm reset.
Set ready_for_runtime in RT FW to check if boot is complete without parsing log.

jlmahowa-amd: @korran I am open to ideas to make warm_reset_flow and warm_reset more clear.
jhand2: I think there was a merge issue. The commit list contains a lot of commits that I think are already in main.

Patch:
diff --git a/hw-model/src/lib.rs b/hw-model/src/lib.rs
index ec08d8d2..beee225e 100644
--- a/hw-model/src/lib.rs
+++ b/hw-model/src/lib.rs
@@ -295,7 +295,10 @@ fn mbox_read_fifo(mbox: mbox::RegisterBlock<impl MmioMut>) -> Vec<u8> {
     result
 }
 
-fn mbox_write_fifo(mbox: &mbox::RegisterBlock<impl MmioMut>, buf: &[u8]) -> Result<(), ModelError> {
+pub fn mbox_write_fifo(
+    mbox: &mbox::RegisterBlock<impl MmioMut>,
+    buf: &[u8],
+) -> Result<(), ModelError> {
     const MAILBOX_SIZE: u32 = 128 * 1024;
 
     let Ok(input_len) = u32::try_from(buf.len()) else {
@@ -376,6 +379,14 @@ pub trait HwModel {
         Ok(hw)
     }
 
+    /// Trigger a warm reset and advance the boot
+    fn warm_reset_flow(&mut self, fuses: &Fuses) {
+        self.warm_reset();
+
+        self.init_fuses(fuses);
+        self.soc_ifc().cptra_bootfsm_go().write(|w| w.go(true));
+    }
+
     /// The APB bus from the SoC to Caliptra
     ///
     /// WARNING: Reading or writing to this bus may involve the Caliptra
@@ -395,6 +406,12 @@ pub trait HwModel {
         }
     }
 
+    /// Toggle reset pins and wait for ready_for_fuses
+    fn warm_reset(&mut self) {
+        // sw-emulator lacks support: https://github.com/chipsalliance/caliptra-sw/issues/540
+        panic!("warm_reset unimplemented");
+    }
+
     /// Returns true if the microcontroller has signalled that it is ready for
     /// firmware to be written to the mailbox. For RTL implementations, this
     /// should come via a caliptra_top wire rather than an APB register.
@@ -409,10 +426,12 @@ pub trait HwModel {
     /// If the cptra_fuse_wr_done has already been written, or the
     /// hardware prevents cptra_fuse_wr_done from being set.
     fn init_fuses(&mut self, fuses: &Fuses) {
-        assert!(
-            !self.soc_ifc().cptra_fuse_wr_done().read().done(),
-            "Fuses are already locked in place (according to cptra_fuse_wr_done)"
-        );
+        if !self.soc_ifc().cptra_reset_reason().read().warm_reset() {
+            assert!(
+                !self.soc_ifc().cptra_fuse_wr_done().read().done(),
+                "Fuses are already locked in place (according to cptra_fuse_wr_done)"
+            );
+        }
 
         self.soc_ifc().fuse_uds_seed().write(&fuses.uds_seed);
         self.soc_ifc()
diff --git a/hw-model/src/model_fpga_realtime.rs b/hw-model/src/model_fpga_realtime.rs
index c8006f4d..48e64bce 100644
--- a/hw-model/src/model_fpga_realtime.rs
+++ b/hw-model/src/model_fpga_realtime.rs
@@ -203,6 +203,14 @@ impl HwModel for ModelFpgaRealtime {
         &mut self.output
     }
 
+    fn warm_reset(&mut self) {
+        // Toggle reset pin
+        self.set_cptra_rst_b(false);
+        self.set_cptra_rst_b(true);
+        // Wait for ready_for_fuses
+        while !self.is_ready_for_fuses() {}
+    }
+
     fn ready_for_fw(&self) -> bool {
         unsafe {
             GpioInput(self.gpio.offset(GPIO_INPUT_OFFSET).read_volatile()).ready_for_fw() != 0
diff --git a/hw-model/src/model_verilated.rs b/hw-model/src/model_verilated.rs
index 35ef25a2..e3c3637f 100644
--- a/hw-model/src/model_verilated.rs
+++ b/hw-model/src/model_verilated.rs
@@ -230,6 +230,20 @@ impl crate::HwModel for ModelVerilated {
         &mut self.output
     }
 
+    fn warm_reset(&mut self) {
+        // Toggle reset pin
+        self.v.input.cptra_rst_b = false;
+        self.v.next_cycle_high(1);
+
+        self.v.input.cptra_rst_b = true;
+        self.v.next_cycle_high(1);
+
+        // Wait for ready_for_fuses
+        while !self.v.output.ready_for_fuses {
+            self.v.next_cycle_high(1);
+        }
+    }
+
     fn ready_for_fw(&self) -> bool {
         self.v.output.ready_for_fw_push
     }
diff --git a/runtime/src/main.rs b/runtime/src/main.rs
index 3a204c48..ec3e62dc 100644
--- a/runtime/src/main.rs
+++ b/runtime/src/main.rs
@@ -37,6 +37,14 @@ pub extern "C" fn entry_point() -> ! {
     cprintln!("{}", BANNER);
     if let Some(mut fht) = caliptra_common::FirmwareHandoffTable::try_load() {
         let mut drivers = unsafe { Drivers::new_from_registers(&mut fht) };
+
+        // Indicator to SOC that RT firmware is ready
+        drivers
+            .soc_ifc
+            .regs_mut()
+            .cptra_flow_status()
+            .write(|w| w.ready_for_runtime(true));
+
         cprintln!("Caliptra RT listening for mailbox commands...");
         caliptra_runtime::handle_mailbox_commands(&mut drivers);
 


Test patch:
diff --git a/test/tests/warm_reset.rs b/test/tests/warm_reset.rs
new file mode 100644
index 00000000..1736679e
--- /dev/null
+++ b/test/tests/warm_reset.rs
@@ -0,0 +1,142 @@
+// Licensed under the Apache-2.0 license
+
+#![cfg(any(feature = "verilator", feature = "fpga_realtime"))]
+
+use caliptra_builder::{ImageOptions, APP_WITH_UART, FMC_WITH_UART, ROM_WITH_UART};
+use caliptra_hw_model::{mbox_write_fifo, BootParams, HwModel, InitParams, SecurityState};
+use caliptra_hw_model_types::{DeviceLifecycle, Fuses};
+use caliptra_test::swap_word_bytes_inplace;
+use openssl::sha::sha384;
+use zerocopy::AsBytes;
+
+fn bytes_to_be_words_48(buf: &[u8; 48]) -> [u32; 12] {
+    let mut result: [u32; 12] = zerocopy::transmute!(*buf);
+    swap_word_bytes_inplace(&mut result);
+    result
+}
+
+#[test]
+fn warm_reset_basic() {
+    let security_state = *SecurityState::default()
+        .set_debug_locked(true)
+        .set_device_lifecycle(DeviceLifecycle::Production);
+
+    let rom = caliptra_builder::build_firmware_rom(&ROM_WITH_UART).unwrap();
+    let image = caliptra_builder::build_and_sign_image(
+        &FMC_WITH_UART,
+        &APP_WITH_UART,
+        ImageOptions {
+            fmc_min_svn: 5,
+            fmc_svn: 9,
+            ..Default::default()
+        },
+    )
+    .unwrap();
+    let vendor_pk_hash =
+        bytes_to_be_words_48(&sha384(image.manifest.preamble.vendor_pub_keys.as_bytes()));
+    let owner_pk_hash =
+        bytes_to_be_words_48(&sha384(image.manifest.preamble.owner_pub_keys.as_bytes()));
+
+    let mut hw = caliptra_hw_model::new(BootParams {
+        init_params: InitParams {
+            rom: &rom,
+            security_state,
+            ..Default::default()
+        },
+        fuses: Fuses {
+            key_manifest_pk_hash: vendor_pk_hash,
+            owner_pk_hash,
+            fmc_key_manifest_svn: 0b1111111,
+            ..Default::default()
+        },
+        fw_image: Some(&image.to_bytes().unwrap()),
+        ..Default::default()
+    })
+    .unwrap();
+
+    // Wait for boot
+    while !hw.soc_ifc().cptra_flow_status().read().ready_for_runtime() {
+        hw.step();
+    }
+
+    // Perform warm reset
+    hw.warm_reset_flow(&Fuses {
+        key_manifest_pk_hash: vendor_pk_hash,
+        owner_pk_hash,
+        fmc_key_manifest_svn: 0b1111111,
+        ..Default::default()
+    });
+
+    // Wait for boot
+    while !hw.soc_ifc().cptra_flow_status().read().ready_for_runtime() {
+        hw.step();
+    }
+}
+
+#[test]
+fn warm_reset_during_fw_load() {
+    let security_state = *SecurityState::default()
+        .set_debug_locked(true)
+        .set_device_lifecycle(DeviceLifecycle::Production);
+
+    let rom = caliptra_builder::build_firmware_rom(&ROM_WITH_UART).unwrap();
+    let image = caliptra_builder::build_and_sign_image(
+        &FMC_WITH_UART,
+        &APP_WITH_UART,
+        ImageOptions {
+            fmc_min_svn: 5,
+            fmc_svn: 9,
+            ..Default::default()
+        },
+    )
+    .unwrap();
+    let vendor_pk_hash =
+        bytes_to_be_words_48(&sha384(image.manifest.preamble.vendor_pub_keys.as_bytes()));
+    let owner_pk_hash =
+        bytes_to_be_words_48(&sha384(image.manifest.preamble.owner_pub_keys.as_bytes()));
+
+    let mut hw = caliptra_hw_model::new(BootParams {
+        init_params: InitParams {
+            rom: &rom,
+            security_state,
+            ..Default::default()
+        },
+        fuses: Fuses {
+            key_manifest_pk_hash: vendor_pk_hash,
+            owner_pk_hash,
+            fmc_key_manifest_svn: 0b1111111,
+            ..Default::default()
+        },
+        fw_image: None,
+        ..Default::default()
+    })
+    .unwrap();
+
+    // Start the FW load
+    // Wait for rom to be ready for firmware
+    while !hw.ready_for_fw() {
+        hw.step();
+    }
+    // Lock the mailbox
+    assert!(!hw.soc_mbox().lock().read().lock());
+    // Write load firmware command and data
+    hw.soc_mbox().cmd().write(|_| 0x4657_4C44);
+    let buf = &image.to_bytes().unwrap();
+    assert!(!mbox_write_fifo(&hw.soc_mbox(), buf).is_err());
+    // Ask the microcontroller to execute this command
+    hw.soc_mbox().execute().write(|w| w.execute(true));
+
+    // Perform warm reset while ROM is executing the firmware load
+    hw.warm_reset_flow(&Fuses {
+        key_manifest_pk_hash: vendor_pk_hash,
+        owner_pk_hash,
+        fmc_key_manifest_svn: 0b1111111,
+        ..Default::default()
+    });
+
+    // Wait for error
+    while hw.soc_ifc().cptra_fw_error_fatal().read() == 0 {
+        hw.step();
+    }
+    assert_ne!(hw.soc_ifc().cptra_fw_error_fatal().read(), 0);
+}