Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
2 changes: 2 additions & 0 deletions fw/plat/uno/fw/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"app",
"crates/bulk_copy",
"crates/error",
"crates/fault",
"crates/key_vault",
"crates/single_cell",
"crates/static_init",
Expand Down Expand Up @@ -64,6 +65,7 @@ azihsm_fw_uno_drivers_uart = { path = "drivers/uart" }
azihsm_fw_uno_drivers_upka = { path = "drivers/upka" }
azihsm_fw_uno_drivers_vault = { path = "drivers/vault" }
azihsm_fw_uno_error = { path = "crates/error" }
azihsm_fw_uno_fault = { path = "crates/fault" }
azihsm_fw_uno_key_vault = { path = "crates/key_vault" }
azihsm_fw_uno_pac = { path = "drivers/pac" }
azihsm_fw_uno_pal = { path = "pal" }
Expand Down
8 changes: 6 additions & 2 deletions fw/plat/uno/fw/app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ azihsm_fw_hsm_core = { workspace = true }
azihsm_fw_hsm_core_tracing = { workspace = true }
azihsm_fw_hsm_pal_traits = { workspace = true }
azihsm_fw_uno_drivers_profile = { workspace = true }
azihsm_fw_uno_drivers_semihosting = { optional = true, workspace = true }
azihsm_fw_uno_drivers_systick = { workspace = true }
azihsm_fw_uno_fault = { workspace = true }
azihsm_fw_uno_pac = { workspace = true }
azihsm_fw_uno_pal = { workspace = true }
azihsm_fw_uno_trace = { workspace = true }
Expand All @@ -44,10 +44,14 @@ default = []
# `SYS_EXIT(-1)` so the emulator stops on firmware panic.
# Disable on targets (e.g. production silicon) that cannot snoop semihosting.
semihosting = [
"azihsm_fw_uno_fault/semihosting",
"azihsm_fw_uno_pal/semihosting",
"dep:azihsm_fw_uno_drivers_semihosting",
]

# Development aid: include a raw stack hexdump in the HardFault report.
# Passed through to the fault crate; off by default.
fault-stackdump = ["azihsm_fw_uno_fault/fault-stackdump"]

# Trace output backend (mutually exclusive). Selecting one compiles the
# trace emitter at INFO level and routes output to that backend. With
# neither enabled, trace output is disabled.
Expand Down
30 changes: 1 addition & 29 deletions fw/plat/uno/fw/app/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@
mod trampoline;

use azihsm_fw_hsm_core::Hsm;
use azihsm_fw_hsm_core_tracing::error;
use azihsm_fw_hsm_core_tracing::info;
use azihsm_fw_hsm_pal_traits::*;
use azihsm_fw_uno_drivers_profile as _;
use azihsm_fw_uno_fault as _;
use azihsm_fw_uno_pac as _;
use azihsm_fw_uno_pal::BootPhase;
use azihsm_fw_uno_pal::UnoHsmIo;
Expand Down Expand Up @@ -192,31 +192,3 @@ async fn main(spawner: Spawner) {
hsm.pal().run().await;
hsm.pal().deinit();
}

/// Panic handler — emits an error trace and halts.
///
/// # Parameters
/// - `info`: Panic metadata (location and message) provided by core.
///
/// # Returns
/// Never returns (`!`).
///
/// # Side Effects
/// - Emits an error-level trace describing the panic to the configured
/// trace backend for diagnostic visibility.
/// - With the `semihosting` feature enabled (emulator), terminates via
/// `SYS_EXIT(-1)` so the host stops on a firmware panic. Otherwise
/// (e.g. silicon), halts forward progress by looping forever.
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
// Referenced unconditionally: `error!` compiles out without a trace
// backend (the default firmware build), which would otherwise leave
// `info` unused.
let _ = &info;
error!("panic", HsmError::InternalError, "{}", info);

#[cfg(feature = "semihosting")]
azihsm_fw_uno_drivers_semihosting::sys_exit(-1i32 as u32);

loop {}
}
33 changes: 33 additions & 0 deletions fw/plat/uno/fw/crates/fault/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
[package]
edition = "2021"
name = "azihsm_fw_uno_fault"
version = "0.1.0"

[lib]
bench = false
doctest = false
test = false

[features]
default = []

# Emulator builds: after reporting a panic/fault, terminate the firmware
# via semihosting `SYS_EXIT(-1)` so the emulator host stops instead of
# spinning. On silicon (no semihosting host) the handlers halt in a loop.
semihosting = ["dep:azihsm_fw_uno_drivers_semihosting"]

# Development aid: append a raw stack-memory hexdump (several words from
# the captured SP) to the HardFault report. Off by default to keep the
# fault path minimal — the default dump mirrors the mcr-hsm minimalist
# register/CFSR report.
fault-stackdump = []

[dependencies]
azihsm_fw_uno_drivers_semihosting = { optional = true, workspace = true }
azihsm_fw_uno_drivers_uart.workspace = true
azihsm_fw_uno_reg_cortex_m.workspace = true
cortex-m.workspace = true
cortex-m-rt.workspace = true
tock-registers.workspace = true
80 changes: 80 additions & 0 deletions fw/plat/uno/fw/crates/fault/src/decode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! Configurable Fault Status Register (CFSR) decoding.
//!
//! Translates the bit fields of the ARMv7-M `CFSR` (MMFSR + BFSR + UFSR)
//! into human-readable lines so a HardFault report names the precise
//! fault cause instead of just a raw hex value. The bus/mem-fault address
//! registers (`BFAR`/`MMFAR`) are reported by the caller when the matching
//! `*VALID` bit is set.

use azihsm_fw_uno_reg_cortex_m::scb::regs::ScbRegs;
use azihsm_fw_uno_reg_cortex_m::scb::CFSR;
use tock_registers::interfaces::Readable;

use crate::println_fault;

/// Print one decoded line per set CFSR fault bit, grouped by fault unit.
///
/// Covers the MemManage (`MMFSR`), BusFault (`BFSR`) and UsageFault
/// (`UFSR`) sub-registers. Only set bits are printed; a clean register
/// produces no output.
pub fn report_cfsr(scb: &ScbRegs) {
// MemManage faults (MMFSR, CFSR[7:0]).
if scb.cfsr.is_set(CFSR::IACCVIOL) {
println_fault!(" MemManage: IACCVIOL instruction-fetch access violation");
}
if scb.cfsr.is_set(CFSR::DACCVIOL) {
println_fault!(" MemManage: DACCVIOL data access violation");
}
if scb.cfsr.is_set(CFSR::MUNSTKERR) {
println_fault!(" MemManage: MUNSTKERR fault on exception return unstacking");
}
if scb.cfsr.is_set(CFSR::MSTKERR) {
println_fault!(" MemManage: MSTKERR fault on exception entry stacking");
}
if scb.cfsr.is_set(CFSR::MLSPERR) {
println_fault!(" MemManage: MLSPERR fault during FP lazy state preservation");
}

// Bus faults (BFSR, CFSR[15:8]).
if scb.cfsr.is_set(CFSR::IBUSERR) {
println_fault!(" BusFault: IBUSERR instruction prefetch bus error");
}
if scb.cfsr.is_set(CFSR::PRECISERR) {
println_fault!(" BusFault: PRECISERR precise data bus error (BFAR valid)");
}
if scb.cfsr.is_set(CFSR::IMPRECISERR) {
println_fault!(" BusFault: IMPRECISERR imprecise data bus error (BFAR unreliable)");
}
if scb.cfsr.is_set(CFSR::UNSTKERR) {
println_fault!(" BusFault: UNSTKERR bus fault on exception return unstacking");
}
if scb.cfsr.is_set(CFSR::STKERR) {
println_fault!(" BusFault: STKERR bus fault on exception entry stacking");
}
if scb.cfsr.is_set(CFSR::LSPERR) {
println_fault!(" BusFault: LSPERR bus fault during FP lazy state preservation");
}

// Usage faults (UFSR, CFSR[31:16]).
if scb.cfsr.is_set(CFSR::UNDEFINSTR) {
println_fault!(" UsageFault: UNDEFINSTR undefined instruction");
}
if scb.cfsr.is_set(CFSR::INVSTATE) {
println_fault!(" UsageFault: INVSTATE invalid EPSR/Thumb state");
}
if scb.cfsr.is_set(CFSR::INVPC) {
println_fault!(" UsageFault: INVPC invalid PC load (exception return)");
}
if scb.cfsr.is_set(CFSR::NOCP) {
println_fault!(" UsageFault: NOCP coprocessor access denied/absent");
}
if scb.cfsr.is_set(CFSR::UNALIGNED) {
println_fault!(" UsageFault: UNALIGNED unaligned access trap");
}
if scb.cfsr.is_set(CFSR::DIVBYZERO) {
println_fault!(" UsageFault: DIVBYZERO divide-by-zero trap");
}
}
191 changes: 191 additions & 0 deletions fw/plat/uno/fw/crates/fault/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! Panic and CPU-exception handlers for the Uno HSM firmware.
//!
//! Installs the firmware's single `#[panic_handler]` plus overrides for the
//! ARMv7-M `HardFault` and `DefaultHandler` exceptions. The goal is fault
//! *visibility*: the default `cortex-m-rt` HardFault handler is a silent
//! infinite loop, so a bus fault (for example, a stray write to a read-only
//! peripheral register) escalates to HardFault and hangs the core with no
//! output. These handlers instead dump the fault cause — decoded `CFSR`
//! bits, the faulting address (`BFAR`/`MMFAR`), the stacked register frame,
//! and `MSP` — so a future fault is diagnosable from the serial log alone.
//!
//! Output goes through [`sink::FaultWriter`] (the SoC UART today), which is
//! compiled unconditionally and therefore works even in production builds
//! that ship without a trace backend.
//!
//! # Linking
//!
//! The panic and exception symbols only take effect if this crate is part
//! of the final binary's dependency graph. The application forces that with
//! `use azihsm_fw_uno_fault as _;` (the `panic-halt` pattern).
//!
//! # Scope
//!
//! This is deliberately a CPU-fault *reporter*. Cross-core crash
//! notification, persistent crash dumps, and peripheral-error ISRs (present
//! in the mcr-hsm `exception-handlers` crate) depend on infrastructure the
//! Uno port does not yet have (Tcon mailbox, crashdump store) and are out of
//! scope here.

#![no_std]
#![allow(unsafe_code)]

mod decode;
mod sink;

use azihsm_fw_uno_reg_cortex_m::scb::regs::ScbRegs;
use azihsm_fw_uno_reg_cortex_m::scb::CFSR;
use azihsm_fw_uno_reg_cortex_m::scb::HFSR;
use azihsm_fw_uno_reg_cortex_m::scb::SCB_BASE;
use cortex_m_rt::exception;
use cortex_m_rt::ExceptionFrame;
pub use sink::FaultWriter;
use tock_registers::interfaces::Readable;

/// Borrow the System Control Block MMIO register block.
///
/// # Safety
///
/// `SCB_BASE` is a fixed architectural address that is always mapped on
/// ARMv7-M, so the dereference is sound. Called only from fault context
/// where the firmware is already halting, so aliasing is not a concern.
#[inline(always)]
fn scb() -> &'static ScbRegs {
unsafe { &*(SCB_BASE as *const ScbRegs) }
}

/// Terminate the firmware after a fault has been reported.
///
/// On emulator builds (`semihosting`) this issues `SYS_EXIT(-1)` so the
/// host stops; on silicon it spins forever (the core is already wedged).
fn halt() -> ! {
#[cfg(feature = "semihosting")]
// Semihosting SYS_EXIT status -1 (failure).
azihsm_fw_uno_drivers_semihosting::sys_exit(u32::MAX);

loop {
cortex_m::asm::nop();
}
}
Comment thread
radutta99 marked this conversation as resolved.

/// Firmware panic handler.
///
/// Emits the panic location and message (via [`core::panic::PanicInfo`]'s
/// `Display`, which already includes `file:line:col` plus the formatted
/// message) to the fault sink, then halts.
#[panic_handler]
fn panic(info: &core::panic::PanicInfo<'_>) -> ! {
println_fault!("");
println_fault!("#### PANIC ####");
println_fault!("{}", info);
halt();
}

/// HardFault exception handler.
///
/// Reads the SCB fault-status registers, classifies the fault, and dumps:
/// decoded `CFSR` bits, the faulting address when valid, the stacked
/// [`ExceptionFrame`] (R0-R3, R12, LR, PC, xPSR), and `MSP`. A stack
/// overflow (`HFSR.FORCED` + `CFSR.MSTKERR`/`STKERR`) is reported specially
/// because exception stacking failed and the frame is unreliable.
///
/// # Safety
///
/// Required to be an `unsafe fn` by `cortex-m-rt`. Invoked only by the
/// hardware exception mechanism on a HardFault and must never be called
/// directly; it reads fixed architectural SCB registers and the
/// hardware-supplied exception frame, then halts.
#[exception]
unsafe fn HardFault(ef: &ExceptionFrame) -> ! {
let scb = scb();
let cfsr = scb.cfsr.get();
let hfsr = scb.hfsr.get();
let msp = cortex_m::register::msp::read();

let forced = scb.hfsr.is_set(HFSR::FORCED);
// Exception-entry stacking can fail via either a MemManage fault
// (MSTKERR, e.g. an MPU stack-guard hit on overflow) or a BusFault
// (STKERR); both escalate to HardFault and leave the pushed register
// frame unreliable.
let mstkerr = scb.cfsr.is_set(CFSR::MSTKERR);
let stkerr = scb.cfsr.is_set(CFSR::STKERR);

println_fault!("");
println_fault!("#### HardFault ####");

if forced && (mstkerr || stkerr) {
// Exception-entry stacking failed and escalated to HardFault: the
// CPU could not push {R0-R3,R12,LR,PC,xPSR}, so `ef` is garbage. The
// faulting PC/LR are unrecoverable on ARMv7-M; only MSP locates
// where the stack was when it overran its guard region.
Comment thread
radutta99 marked this conversation as resolved.
println_fault!("cause: stack overflow (exception frame unreliable)");
println_fault!("MSP={:#010x} CFSR={:#010x} HFSR={:#010x}", msp, cfsr, hfsr);
} else {
decode::report_cfsr(scb);

if scb.cfsr.is_set(CFSR::BFARVALID) {
println_fault!("BFAR={:#010x} (faulting bus address)", scb.bfar.get());
}
if scb.cfsr.is_set(CFSR::MMARVALID) {
println_fault!("MMFAR={:#010x} (faulting memory address)", scb.mmfar.get());
}

println_fault!("CFSR={:#010x} HFSR={:#010x} MSP={:#010x}", cfsr, hfsr, msp);
println_fault!("frame: {:#?}", ef);

Comment thread
radutta99 marked this conversation as resolved.
#[cfg(feature = "fault-stackdump")]
unsafe {
stack_dump(msp);
}
}

halt();
}

/// Catch-all handler for any exception/interrupt without a dedicated
/// handler. Reports the offending exception number so an unexpected or
/// spurious interrupt is no longer silent, then halts.
///
/// # Safety
///
/// Required to be an `unsafe fn` by `cortex-m-rt`. Invoked only by the
/// hardware exception mechanism for an otherwise-unhandled exception/IRQ
/// and must never be called directly.
#[exception]
unsafe fn DefaultHandler(irqn: i16) -> ! {
println_fault!("");
println_fault!("#### Unexpected exception/IRQ: {} ####", irqn);
halt();
}

/// Dump `WORDS` 32-bit words of raw stack memory starting at `sp`.
///
/// Development aid behind the `fault-stackdump` feature — useful for
/// eyeballing return addresses and locals near the fault, at the cost of
/// reading memory that may extend past the live stack.
///
/// # Safety
///
/// Performs volatile reads of arbitrary stack addresses; the range may run
/// past valid RAM. Intended for debug builds on hardware only.
#[cfg(feature = "fault-stackdump")]
unsafe fn stack_dump(sp: u32) {
const WORDS: usize = 32;
// `read_volatile::<u32>` requires a 4-byte-aligned pointer; `sp` may be
// corrupted or misaligned at the fault, so align the base down first to
// avoid undefined behaviour while keeping the dump word-oriented.
let base = sp & !0b11;
println_fault!("stack dump @ {:#010x}:", base);
for i in 0..WORDS {
let addr = base.wrapping_add((i * 4) as u32);
let val = unsafe { core::ptr::read_volatile(addr as *const u32) };
if i % 4 == 0 {
print_fault!("\n {:#010x}:", addr);
Comment thread
radutta99 marked this conversation as resolved.
}
print_fault!(" {:08x}", val);
}
println_fault!("");
}
Loading
Loading