-
Notifications
You must be signed in to change notification settings - Fork 5
feat(uno): add CPU fault/panic exception handlers #494
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
radutta99
wants to merge
7
commits into
main
Choose a base branch
from
user/radutta/fw-exception-handlers
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+398
−31
Open
Changes from 5 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
d466dbf
Add CPU fault/panic exception handlers to Uno firmware
radutta99 c807aaa
fault: address HardFault handler review comments
radutta99 bb3eec5
fault: apply cargo fmt
radutta99 d95452e
fault: route output via semihosting on emulator; clarify exit code
radutta99 b7c0944
fault: add # Safety docs to HardFault/DefaultHandler
radutta99 d23f90c
Potential fix for pull request finding
radutta99 4a7c61a
fault: filter UART output and fix macro re-export path
radutta99 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); | ||
| } | ||
| } | ||
|
|
||
| /// 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. | ||
|
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); | ||
|
|
||
|
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); | ||
|
radutta99 marked this conversation as resolved.
|
||
| } | ||
| print_fault!(" {:08x}", val); | ||
| } | ||
| println_fault!(""); | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.