// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The exception model, DDI 0403E.b B1.5: taking an exception, returning from one, and the faults
//! instructions and accesses raise.
//!
//! [`Scs`](super::scs::Scs) decides which exception is taken next and when a fault escalates. This
//! is what the processor does around it: it stacks and unstacks the frame, reads the vector,
//! switches mode and stack, keeps the fault status, and locks up when nothing can take a fault.
//!
//! | Behaviour | Source | Test |
//! |---|---|---|
//! | Entry: the basic frame on the stack in use, 8-byte realignment noted in the stacked `xPSR<9>`, `LR` set to `EXC_RETURN`, the vector read from `VTOR`, Handler mode on the main stack, `ITSTATE` cleared, the monitor cleared and the event register set | B1.5.6, B1.5.7, A3.4.4 | `an_interrupt_from_thread_mode_is_stacked_and_returns`, `a_stack_four_bytes_off_is_realigned_and_put_back`, `four_byte_alignment_leaves_the_stack_where_it_is`, `thread_mode_on_the_process_stack_comes_back_to_it` |
//! | Nesting, and tail-chaining a pending exception that beats the return's target instead of popping and pushing the frame | B1.5.6, B1.5.12, 100166 3.9.2 | `a_higher_priority_interrupt_nests_and_returns_to_its_handler`, `a_pending_interrupt_is_tail_chained_on_the_same_frame` |
//! | Return: every `EXC_RETURN` of Table B1-9, the integrity checks raising INVPC with `EXC_RETURN` in `LR`, `NONBASETHRDENA`, `FAULTMASK` cleared except from NMI, HardFault made inactive, and `ICSR.VECTACTIVE` reading 0 back in Thread mode with an exception still active | B1.5.8, B3.2.4 | `every_exc_return_goes_where_table_b1_9_says`, `an_illegal_exc_return_raises_invpc_on_the_same_frame`, `returning_to_thread_mode_with_another_active_is_invpc_unless_allowed`, `a_frame_whose_ipsr_disagrees_with_the_mode_is_invpc`, `returning_from_an_inactive_exception_is_invpc`, `faultmask_clears_on_return_but_not_from_nmi`, `a_return_from_hardfault_clears_faultmask_and_makes_it_inactive`, `icsr_reads_thread_mode_with_an_exception_still_active` |
//! | Derived exceptions on entry: `STKERR` and `MSTKERR` taken in place of the entered exception on its frame, or escalated, or a lockup at NMI's priority | B1.5.11, B1.5.14, 100166 3.9.2 | `a_bus_fault_while_stacking_is_taken_on_the_same_frame`, `a_stacking_fault_with_busfault_off_escalates_to_hardfault`, `a_stacking_fault_entering_nmi_from_hardfault_locks_up` |
//! | A vector that can't be read: HardFault with `HFSR.VECTTBL`, and lockup at `0xFFFFFFFE` when it's HardFault's own | B1.5.11, B1.5.15 | `a_vector_read_fault_is_hardfault`, `a_hardfault_vector_read_fault_locks_up` |
//! | Derived exceptions on return: `UNSTKERR` and `MUNSTKERR` tail-chained with the frame left where it was | B1.5.12, B1.5.14 | `a_bus_fault_while_unstacking_leaves_the_frame_and_chains` |
//! | Faults an instruction raises, by exception and `CFSR` or `HFSR` bit, escalation to HardFault, and lockup at the instruction | B1.5.14, B1.5.15, B3.2.15 | `an_undefined_instruction_escalates_to_hardfault_and_then_locks_up`, `each_fault_sets_its_own_status_bit` |
//! | Each exception an instruction raises, run end to end through its block, the run loop and its vector: UNDEFINED, `BX` returning from Handler mode, a zero divide `DIV_0_TRP` traps, NOCP, an unaligned `LDRD`, a load the bus fails, `BKPT`, `SVC`, and a lazy floating-point save on a context switch | A3.2.1, B1.5.6, B1.5.8, B1.5.14, B1.6.3, B3.2.8, B3.2.18, C1.6.5 | `an_undefined_instruction_runs_into_usagefault`, `bx_in_handler_mode_returns_from_the_exception`, `sdiv_by_zero_with_div_0_trp_runs_into_usagefault`, `a_floating_point_instruction_with_cpacr_off_runs_into_usagefault`, `an_unaligned_ldrd_runs_into_usagefault`, `a_load_the_bus_fails_runs_into_a_precise_busfault`, `bkpt_runs_into_hardfault_with_debugevt`, `svc_runs_into_svcall_past_itself`, `a_lazy_save_on_a_context_switch_puts_the_thread_state_back`, and `mpu`'s `an_ldrt_the_mpu_refuses_runs_into_memmanage` |
//! | An instruction fetch: MemManage `IACCVIOL` with `MMARVALID` cleared when the MPU refused it, or the default map's execute-never with the MPU off, BusFault `IBUSERR` when nothing's mapped | B1.5.14, B2.3.8, B3.2.15, B3.5.2, B3.5.3, Table B3-1 | `a_fetch_from_execute_never_memory_is_iaccviol`, `a_fetch_nothing_answers_is_ibuserr`, `a_fetch_refused_from_mapped_memory_is_iaccviol`, `a_refused_fetch_leaves_mmfar_invalid` |
//! | Data faults: `UNALIGNED`, `DACCVIOL` with `MMFAR`, `PRECISERR` with `BFAR`, `MMFAR` the byte refused of an unaligned access split into bytes and `BFAR` the address the instruction asked for, the two address registers being one, and `BFHFNMIGN` ignoring a BusFault at a negative priority | B1.5.14, B3.2.8, B3.2.17, B3.2.18, 100166 table 4-1 | `data_faults_record_their_address`, `a_split_load_the_bus_refuses_puts_its_own_address_in_bfar`, `bfhfnmign_ignores_a_bus_fault_at_negative_priority` |
//! | Software without the privilege, and `LDRT` and its kind, refused the private peripheral bus with a precise BusFault, windows to the runtime's models included, but for `STIR` with `CCR.USERSETMPEND` set and for the ITM | B3.1.1, B3.2.8, B3.5.4, C1.2.1, C1.7.1 | `unprivileged_software_is_refused_the_private_peripheral_bus_but_the_itm` |
//! | SVC as SVCall, escalated under `PRIMASK`, and UNDEFINED when it locks up, at its own address and not retired | B1.5.4, B1.5.15 | `svc_is_svcall_and_escalates_under_primask`, `svc_in_hardfault_is_undefined_and_locks_up_at_itself`, `svc_in_hardfault_locks_up_unretired_when_it_runs` |
//! | `BKPT` as DebugMonitor with `DEMCR.MON_EN`, HardFault with `HFSR.DEBUGEVT` without | B1.5.14, C1.6.5 | `bkpt_is_debugmonitor_only_with_the_monitor_on` |
//! | Lockup left only by NMI, which returns to the lockup address, and an INVPC returning from NMI locking up with NMI still active | B1.5.15 | `an_nmi_takes_a_locked_up_processor_out`, `an_invpc_returning_from_nmi_locks_up_at_nmi_priority` |
//! | Without the floating-point extension, the basic frame whatever `EXC_RETURN<4>` says, and without the DSP extension, GE not unstacked | B1.5.6, B1.5.8 | `a_core_without_the_extensions_pops_the_basic_frame` |
//! | The extended frame: stacked whole with `LSPEN` clear, or room left and `FPCAR` and `FPCCR` set for lazy preservation, `EXC_RETURN<4>` and `CONTROL.FPCA`, and NOCP when `CPACR` refuses | B1.5.6, B1.5.7, B1.5.8, B3.2.21 | `the_extended_frame_is_stacked_and_unstacked_whole`, `a_lazy_frame_is_preserved_only_when_asked`, `a_lazy_frame_never_preserved_keeps_the_registers`, `stacking_fp_state_with_cpacr_off_is_nocp` |
//! | Lazy preservation and its faults, judged by the ready bits saved at entry, `HFSR.FORCED` for one that escalates, and a lockup at `0xFFFFFFFE` rather than at the instruction | B1.6.3, 100166 7.2.7 | `a_fault_while_preserving_is_judged_by_the_ready_bits`, `a_lazy_preservation_that_locks_up_does_so_at_the_lockup_address` |
//! | `WFI` and sleep-on-exit wake on what would preempt with `PRIMASK` clear, `WFE` on the event register or what would be taken now | B1.5.17, B1.5.18, B1.5.19 | `wfi_wakes_through_primask_and_wfe_does_not`, `wfe_with_the_event_register_set_goes_on`, `wfe_wakes_when_the_event_register_is_set_while_it_sleeps` |
//! | Sleep-on-exit sleeps instead of returning, then tail-chains or finishes the return | B1.5.8, B1.5.17 | `sleep_on_exit_sleeps_and_tail_chains`, `sleep_on_exit_woken_under_primask_finishes_the_return` |
//! | The stack accesses of entry, return and lazy preservation checked by the MPU in the context the pseudocode makes them in, and vector reads through the default map whatever it says | B1.5.6, B1.5.8, B1.6.3, B3.5.1 | `mpu`'s `a_frame_is_pushed_in_the_context_it_preempts`, `a_frame_is_popped_in_the_context_it_returns_to`, `lazy_preservation_runs_in_the_context_that_owns_the_state`, `a_vector_is_read_through_the_default_map` |
//! | The event register set when an exception goes pending with `SEVONPEND` set | B1.5.18, B3.2.7 | `an_interrupt_going_pending_is_an_event_with_sevonpend` |
//! | A vector with bit 0 clear leaves `EPSR.T` 0, so the handler's first instruction is INVSTATE | B1.5.6, 100166 3.9.1 | `a_vector_without_the_thumb_bit_faults_in_the_handler` |
//! | With halting debug on, which a debugger attaching turns on, a `BKPT` halts the core at itself rather than raising an exception | C1.5, C1.6.2 | `family`'s `a_bkpt_halts_the_core_while_a_debugger_is_attached` |
//! | `BKPT #0xAB`, the M profile's semihosting trap, stops the core before it retires for the run to answer the call, and unanswered is the `BKPT` it is | A7.7.17, Arm's semihosting specification, "The semihosting interface" | `family`'s `bkpt_0xab_stops_for_a_host_call_and_any_other_bkpt_is_a_breakpoint`, `an_unanswered_host_call_is_the_bkpt_it_was` |
//! | Vector catch: with halting debug on, taking a fault whose status bit and a `DEMCR` bit that catches it are both set halts the core on its handler's first instruction, with `DFSR.VCATCH` set, each bit catching what C1.6.5 says it does | B1.5.14, C1.5.2, C1.6.1, C1.6.5 | `a_usagefault_under_vector_catch_halts_at_its_handler_with_dfsr_vcatch`, `each_fault_is_caught_by_the_bits_c1_6_5_gives_it`, `an_interrupt_arriving_as_a_caught_fault_is_taken_waits_for_the_halt` |
//!
//! **What it leaves out, and why.**
//!
//! | Behaviour | Source | Reason |
//! |---|---|---|
//! | Late arrival of an interrupt during entry | B1.5.11 | Only an interrupt that pends while a stacking access is on the bus could arrive, and which arrivals count is IMPLEMENTATION DEFINED. It's taken by nesting right after, and a caught fault halts the core first, on its handler's first instruction as C1.5.2 asks, so the interrupt waits for the halt |
//! | The INVPC push after an inconsistent `IPSR` writing the same frame back | B1.5.8 | The frame stays where it was, which is what the push restores |
//! | Debug state's own registers and the DWT's watchpoints | C1 | The runtime's debugger stops the processor from outside, with breakpoints and watchpoints of its own |
//! | A locked-up processor running the instruction that locked it up again | B1.5.15 | Nothing runs while it's locked up, until NMI or a reset. A transient precise BusFault in a handler at priority -1 that would clear on a later try leaves it locked |
//! | Continuing a Load Multiple or Store Multiple from the register its ICI bits name | B1.5.10 | The processor abandons and restarts one from its first register, as B1.5.10 allows, and never sets ICI bits itself. Ones an exception return brings back restart the instruction, and are INVSTATE on anything else |
use super::family::{Family, Position, Request, helpers};
use super::mpu::Context;
use super::registers::{
ccr, cfsr, control, cpacr, demcr, dfsr, exc_return, fpcar, fpccr, get, hfsr, is_set, mask, set,
xpsr,
};
use super::scs::exception::{
BUS_FAULT, DEBUG_MONITOR, HARD_FAULT, MEM_MANAGE, NMI, SV_CALL, USAGE_FAULT,
};
use super::scs::{Masks, Preserved, Raised};
use super::translate::{Attributes, Fault, Instruction, Size};
use crate::soc::FieldSpec;
use crate::time::VirtualNanos;
use Status::{Cfsr, Hfsr};
/// Why an access didn't happen. `MMFAR` gets the byte the MPU refused, which for an unaligned
/// access split into bytes isn't where the access starts, B3.2.17. `BFAR` gets where it starts,
/// B3.2.18.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Refused {
/// It wasn't aligned and had to be, A3.2.1.
Unaligned,
/// The MPU refused the byte at this address, B3.5.
Mpu(u32),
/// The bus failed the access the instruction asked for at this address, or software without
/// the privilege asked for the system control space there, B3.2.
Bus(u32),
/// A debugger's watchpoint stops the core before the access. The architecture's watchpoints
/// are asynchronous, C1.5.3, so this is the debugger's choice, and the instruction is abandoned
/// and restarted from its start, as B1.5.10 lets a load or store multiple be.
Watched,
}
/// Where a locked up processor fetches from after a fault on entry or return, B1.5.15.
const LOCKUP: u32 = 0xFFFF_FFFE;
/// `DEMCR`'s offset, C1.6.5.
const DEMCR: u64 = 0xDFC;
/// Where a fault's status bit is: `CFSR`, the configurable faults', or `HFSR`, HardFault's, B3.2.15
/// and B3.2.16.
#[derive(Clone, Copy, Debug)]
enum Status {
Cfsr(&'static FieldSpec),
Hfsr(&'static FieldSpec),
}
/// What each `DEMCR` catch bit catches, C1.6.5, as the exception taken, a status bit set when
/// it's taken, B1.5.14, and the bit. `VC_HARDERR` catches a HardFault, `VC_BUSERR` a BusFault and
/// `VC_MMERR` a MemManage, whatever raised it. `VC_INTERR` catches a fault on exception entry or
/// return, the vector read's included, whatever it's taken as, so one escalated to HardFault too.
/// That's INVPC as well, which only an exception return's integrity checks raise, B1.5.14 and
/// B1.5.15. The three UsageFault bits each catch their own causes. B1.5.14 gives a lazy preservation's
/// `LSPERR` and `MLSPERR` no bit, and C1.6.5 catches them as the BusFault and the MemManage they
/// are. BusFault's `IMPRECISERR` isn't here, since the core's bus errors are all precise.
const CATCHES: [(u16, Status, &FieldSpec); 30] = [
(HARD_FAULT, Hfsr(hfsr::VECTTBL), demcr::VC_HARDERR),
(HARD_FAULT, Hfsr(hfsr::VECTTBL), demcr::VC_INTERR),
(HARD_FAULT, Hfsr(hfsr::FORCED), demcr::VC_HARDERR),
(HARD_FAULT, Hfsr(hfsr::DEBUGEVT), demcr::VC_HARDERR),
(HARD_FAULT, Cfsr(cfsr::STKERR), demcr::VC_INTERR),
(HARD_FAULT, Cfsr(cfsr::MSTKERR), demcr::VC_INTERR),
(HARD_FAULT, Cfsr(cfsr::UNSTKERR), demcr::VC_INTERR),
(HARD_FAULT, Cfsr(cfsr::MUNSTKERR), demcr::VC_INTERR),
(HARD_FAULT, Cfsr(cfsr::INVPC), demcr::VC_INTERR),
(BUS_FAULT, Cfsr(cfsr::STKERR), demcr::VC_INTERR),
(BUS_FAULT, Cfsr(cfsr::STKERR), demcr::VC_BUSERR),
(BUS_FAULT, Cfsr(cfsr::UNSTKERR), demcr::VC_INTERR),
(BUS_FAULT, Cfsr(cfsr::UNSTKERR), demcr::VC_BUSERR),
(BUS_FAULT, Cfsr(cfsr::IBUSERR), demcr::VC_BUSERR),
(BUS_FAULT, Cfsr(cfsr::PRECISERR), demcr::VC_BUSERR),
(BUS_FAULT, Cfsr(cfsr::LSPERR), demcr::VC_BUSERR),
(MEM_MANAGE, Cfsr(cfsr::MSTKERR), demcr::VC_INTERR),
(MEM_MANAGE, Cfsr(cfsr::MSTKERR), demcr::VC_MMERR),
(MEM_MANAGE, Cfsr(cfsr::MUNSTKERR), demcr::VC_INTERR),
(MEM_MANAGE, Cfsr(cfsr::MUNSTKERR), demcr::VC_MMERR),
(MEM_MANAGE, Cfsr(cfsr::DACCVIOL), demcr::VC_MMERR),
(MEM_MANAGE, Cfsr(cfsr::IACCVIOL), demcr::VC_MMERR),
(MEM_MANAGE, Cfsr(cfsr::MLSPERR), demcr::VC_MMERR),
(USAGE_FAULT, Cfsr(cfsr::NOCP), demcr::VC_NOCPERR),
(USAGE_FAULT, Cfsr(cfsr::UNDEFINSTR), demcr::VC_STATERR),
(USAGE_FAULT, Cfsr(cfsr::INVSTATE), demcr::VC_STATERR),
(USAGE_FAULT, Cfsr(cfsr::INVPC), demcr::VC_INTERR),
(USAGE_FAULT, Cfsr(cfsr::INVPC), demcr::VC_STATERR),
(USAGE_FAULT, Cfsr(cfsr::UNALIGNED), demcr::VC_CHKERR),
(USAGE_FAULT, Cfsr(cfsr::DIVBYZERO), demcr::VC_CHKERR),
];
/// `monitor vector-catch`'s words, each a `DEMCR` catch bit by its name less `VC_`, shortened,
/// C1.6.5.
const CATCH_WORDS: [(&str, &FieldSpec); 8] = [
("hard", demcr::VC_HARDERR),
("int", demcr::VC_INTERR),
("bus", demcr::VC_BUSERR),
("state", demcr::VC_STATERR),
("check", demcr::VC_CHKERR),
("nocp", demcr::VC_NOCPERR),
("mm", demcr::VC_MMERR),
("reset", demcr::VC_CORERESET),
];
/// What `monitor help` says about `monitor vector-catch`.
pub(super) const VECTOR_CATCH_HELP: &str = "monitor vector-catch: `vector-catch hard bus` halts the \
core before the first instruction of a fault's handler when the fault is one those catch, and \
catches nothing else. The words are DEMCR's VC_ bits: hard, int, bus, state, check, nocp and \
mm, and reset, which halts at the reset handler after a reset. `all` or `none` stand alone and \
do what they say, and with no words it says what's caught.\n";
/// The frames' sizes in bytes, B1.5.7, and where the floating-point state starts in one.
const BASIC: u32 = 0x20;
const EXTENDED: u32 = 0x68;
const FP_STATE: u32 = 0x20;
/// What `state.sleeping` holds. `WFE` wakes on other events than `WFI`, and sleep-on-exit still
/// has its return to finish, with `EXC_RETURN` in `r[15]`.
const FOR_INTERRUPT: u32 = 1;
const FOR_EVENT: u32 = 2;
pub(super) const ON_EXIT: u32 = 3;
/// Where an exception return goes, `EXC_RETURN<3:0>` of Table B1-9.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Target {
Handler,
ThreadMain,
ThreadProcess,
}
impl Target {
fn of(exc_return: u32) -> Option<Self> {
match get(exc_return::TARGET, exc_return) {
0b0001 => Some(Self::Handler),
0b1001 => Some(Self::ThreadMain),
0b1101 => Some(Self::ThreadProcess),
_ => None,
}
}
}
/// A frame read off the stack and not yet put back in the registers.
#[derive(Clone, Copy)]
struct Unstacked {
at: u32,
/// `R0` to `R3`, `R12`, `LR`, the return address and `xPSR`.
words: [u32; 8],
/// `S0` to `S15` and `FPSCR`, when the frame has them and nothing preserves them lazily.
fp: Option<[u32; 17]>,
}
/// The exception a fault on the processor's own access raises, and its status bit: `mem_manage`
/// for the MPU's refusal, `bus` for the bus's. The processor's accesses are word aligned and no
/// debugger's watchpoint stops them, so `Unaligned` and `Watched` never come back from one.
fn derived(refused: Refused, mem_manage: u32, bus: u32) -> (u16, u32) {
match refused {
Refused::Mpu(_) => (MEM_MANAGE, mem_manage),
Refused::Bus(_) | Refused::Unaligned | Refused::Watched => (BUS_FAULT, bus),
}
}
impl Family {
/// Takes the exceptions that preempt now, B1.5.6, waking a processor that sleeps when one
/// would. An NMI is the one way out of lockup, and it comes back to the lockup address,
/// B1.5.15.
pub(crate) fn take_exception(&mut self) {
if self.state.sleeping != 0 {
if !self.woken(self.state.sleeping) {
return;
}
match std::mem::take(&mut self.state.sleeping) {
// B1.5.18 leaves it to you whether waking clears the event register, and a WFE
// that's done has used it.
FOR_EVENT => self.state.event = 0,
ON_EXIT => self.finish_return(self.state.r[15]),
_ => {}
}
}
while let Some(number) = self.scs.acknowledge(self.masks()) {
self.state.locked_up = 0;
self.enter(number);
// C1.5.2 halts a caught fault on its handler's first instruction. Nothing arrives late
// here, B1.5.11, so an interrupt that pended during entry waits for the halt.
if self.state.locked_up != 0 || self.halted != 0 {
return;
}
}
}
/// Whether something wakes a processor sleeping as `sleeping` says. `WFE` wakes on the event
/// register or an exception that would be taken now, B1.5.18. `WFI` and sleep-on-exit wake on
/// an exception that would preempt with `PRIMASK` clear, B1.5.17 and B1.5.19.
fn woken(&self, sleeping: u32) -> bool {
let masks = self.masks();
if sleeping == FOR_EVENT {
self.state.event != 0 || self.scs.preempting(masks).is_some()
} else {
self.scs.wakes(masks)
}
}
/// B1.5.6's `ExceptionEntry()` for `number`, which the controller has just made active, with a
/// fault while stacking handled as B1.5.11's `DerivedLateArrival()` has it. `PushStack()` runs
/// before `ExceptionTaken()`, so the MPU checks the frame in the context `number` preempts.
fn enter(&mut self, number: u16) {
let masks = self.masks();
let mut taking = number;
let preempted = Context {
privileged: self.state.privileged(),
below_priority_zero: self.scs.execution_priority(masks, false, Some(number)) < 0,
};
let pushed = self.with_mpu_context(preempted, |core| core.push_stack(number, masks));
if let Err((fault, status)) = pushed {
self.system.cfsr |= status;
let raised = self.scs.raise_while_stacking(fault, number, masks);
self.note_pended();
match raised {
// 100166 3.9.2: stacking from HardFault to NMI locks up at NMI's priority.
Raised::Lockup => {
self.lock_up_in(number);
return;
}
Raised::Replaced => match self.scs.acknowledge(masks) {
Some(next) => taking = next,
None => return,
},
Raised::Pending => {}
}
}
self.take(taking, masks);
}
/// B1.5.6's `PushStack()`: the frame on the stack in use, `LR` set to the `EXC_RETURN` that
/// comes back to it, and the floating-point state stacked or its room left for later, B1.5.7.
/// A fault abandons the stores left and answers the derived exception and its status bit, with
/// the stack pointer and `LR` already moved, B1.5.11.
fn push_stack(&mut self, entering: u16, masks: Masks) -> Result<(), (u16, u32)> {
let state = &self.state;
let extended = is_set(control::FPCA, state.control);
let (size, align) = if extended {
(EXTENDED, true)
} else {
(BASIC, is_set(ccr::STKALIGN, self.system.ccr))
};
let process = state.on_process_stack();
// B1.4.1: the stack pointer's low two bits are RAZ.
let sp = if process { state.psp() } else { state.msp() } & !3;
let realigned = align && sp & 4 != 0;
let at = sp.wrapping_sub(size) & if align { !7 } else { !3 };
let privileged = state.privileged();
let psr = set(xpsr::REALIGNED, state.xpsr(), u32::from(realigned));
let r = &state.r;
let words = [r[0], r[1], r[2], r[3], r[12], r[14], r[15], psr];
let mut exc_return = if state.handler() {
0xFFFF_FFE1
} else {
set(
exc_return::PROCESS_STACK,
0xFFFF_FFE9,
get(control::SPSEL, state.control),
)
};
if !extended {
exc_return |= mask(exc_return::BASIC_FRAME);
}
if process {
self.state.set_psp(at);
} else {
self.state.set_msp(at);
}
self.state.r[14] = exc_return;
let lazy = is_set(fpccr::LSPEN, self.system.fpccr);
if extended && lazy {
self.allocate_fp_state(at, entering, masks);
}
let stacked = |refused| derived(refused, mask(cfsr::MSTKERR), mask(cfsr::STKERR));
self.store_words(at, &words, Position::Processor)
.map_err(stacked)?;
if extended && !lazy {
if !self.fp_enabled(privileged) {
return Err((USAGE_FAULT, mask(cfsr::NOCP)));
}
let fp = self.fp_words();
self.store_words(at.wrapping_add(FP_STATE), &fp, Position::Processor)
.map_err(stacked)?;
}
Ok(())
}
/// B1.5.6's `UpdateFPCCR()`: room left at `at` for the floating-point state, which waits for
/// the first floating-point instruction to be preserved, with what the preempted software
/// could take if that faults. It comes before the stores, since a stacking fault still runs a
/// handler on this frame and the preempted state is owed to it.
fn allocate_fp_state(&mut self, at: u32, entering: u16, masks: Masks) {
let allocation = mask(fpccr::LSPACT)
| mask(fpccr::USER)
| mask(fpccr::THREAD)
| mask(fpccr::HFRDY)
| mask(fpccr::MMRDY)
| mask(fpccr::BFRDY)
| mask(fpccr::MONRDY);
let mut value = self.system.fpccr & !allocation;
value |= mask(fpccr::LSPACT) | self.scs.fpccr_ready(entering, masks);
if !self.state.privileged() {
value |= mask(fpccr::USER);
}
if !self.state.handler() {
value |= mask(fpccr::THREAD);
}
self.system.fpccr = value;
self.system.fpcar = at.wrapping_add(FP_STATE) & mask(fpcar::ADDRESS);
}
/// `S0` to `S15` and `FPSCR`, as a frame holds them.
fn fp_words(&self) -> [u32; 17] {
let mut fp = [0; 17];
fp[..16].copy_from_slice(&self.state.s[..16]);
fp[16] = self.state.fpscr;
fp
}
/// B1.6.3's `CheckVFPEnabled()` against `CPACR.CP10`: full access, or privileged access for
/// `privileged` software. Its reserved value and a `CP11` that differs from it are
/// UNPREDICTABLE, and refusing the one and reading `CP10` alone is the simplest.
pub(super) fn fp_enabled(&self, privileged: bool) -> bool {
match get(cpacr::CP10, self.system.cpacr) {
0b11 => true,
0b01 => privileged,
_ => false,
}
}
/// Reads `number`'s vector and branches to it, B1.5.6's `ExceptionTaken()`. A vector that can't
/// be read is HardFault in its place on the same frame, and a lockup when that can't be taken
/// or is HardFault's own, B1.5.11 and B1.5.15.
fn take(&mut self, mut number: u16, masks: Masks) {
loop {
let address = self.system.vtor.wrapping_add(4 * u32::from(number));
let read = self.access(
Request::read(address, Size::Word),
Attributes::ALIGNED,
Position::Vector,
);
if let Ok(vector) = read {
self.taken(number, vector & !1, vector & 1);
self.catch(number);
return;
}
let raised = self.scs.raise_reading_vector(number, masks);
self.note_pended();
if raised != Raised::Replaced {
self.lock_up_in(number);
return;
}
let Some(next) = self.scs.acknowledge(masks) else {
return;
};
number = next;
}
}
/// The rest of `ExceptionTaken()`, B1.5.6: Handler mode for `number` on the main stack with no
/// floating-point context, from `address`, the monitor cleared, A3.4.4, and the event register
/// set, B1.5.18.
fn taken(&mut self, number: u16, address: u32, thumb: u32) {
let control = self.state.control & !(mask(control::SPSEL) | mask(control::FPCA));
self.state.set_mode(u32::from(number), control);
self.state.r[15] = address;
self.state.t = thumb;
self.state.it = 0;
self.state.exclusive_valid = 0;
self.state.event = 1;
}
/// C1.5.2's vector catch on taking `number`: with halting debug on, the core halts before the
/// handler's first instruction when a fault taken as it has its status bit set and a bit that
/// catches it set, C1.6.5.
fn catch(&mut self, number: u16) {
if !self.debugger {
return;
}
let (cfsr, hfsr) = (self.system.cfsr, self.scs.hfsr());
let caught = CATCHES.iter().any(|&(exception, status, catch)| {
let raised = match status {
Cfsr(bit) => is_set(bit, cfsr),
Hfsr(bit) => is_set(bit, hfsr),
};
exception == number && raised && self.scs.catches(catch)
});
if caught {
self.halted |= mask(dfsr::VCATCH);
}
}
/// `monitor vector-catch`: catches what `words` names and nothing else, when it names anything,
/// and says what's caught. `all` and `none` stand alone.
pub(super) fn vector_catch<'a>(&mut self, words: impl Iterator<Item = &'a str>) -> String {
let words: Vec<&str> = words.collect();
let named = |word: &str| CATCH_WORDS.iter().find(|&&(name, _)| name == word);
let catches = match words[..] {
[] => None,
["all"] => Some(
CATCH_WORDS
.iter()
.fold(0, |all, &(_, catch)| all | mask(catch)),
),
["none"] => Some(0),
_ if words.iter().all(|&word| named(word).is_some()) => Some(
words
.iter()
.filter_map(|&word| named(word))
.fold(0, |catches, &(_, catch)| catches | mask(catch)),
),
_ => {
let names: Vec<&str> = CATCH_WORDS.iter().map(|&(name, _)| name).collect();
let bad = words
.iter()
.find(|&&word| named(word).is_none())
.unwrap_or(&words[0]);
return format!(
"vector-catch takes all or none alone, or any of {}, and not \"{bad}\" there\n",
names.join(", "),
);
}
};
if let Some(catches) = catches {
self.scs.set_vector_catch(catches);
}
let caught: Vec<&str> = CATCH_WORDS
.iter()
.filter(|&&(_, catch)| self.scs.catches(catch))
.map(|&(name, _)| name)
.collect();
if caught.is_empty() {
"catching nothing\n".to_owned()
} else {
format!("catching {}\n", caught.join(" "))
}
}
/// The exception `IPSR` says is executing, zero in Thread mode, B1.4.2.
pub(crate) fn executing(&self) -> u16 {
u16::try_from(get(xpsr::EXCEPTION_NUMBER, self.state.ipsr))
.expect("IPSR's exception number is 9 bits")
}
/// Locks up in `number`'s handler at the lockup address, B1.5.15, which leaves `EPSR.T` as
/// it was.
fn lock_up_in(&mut self, number: u16) {
self.taken(number, LOCKUP, self.state.t);
self.state.locked_up = 1;
}
/// Returns from the exception being handled with `exc_return`, B1.5.8's `ExceptionReturn()`.
pub(crate) fn exception_return(&mut self, exc_return: u32) {
let returning = self.executing();
// B1.5.18 and A3.4.4: every way out of here sets the event register and clears the monitor.
self.state.event = 1;
self.state.exclusive_valid = 0;
// B1.5.8's NestedActivation, counted before DeActivate().
let alone = self.scs.active_count() == 1;
let active = self.scs.complete(returning);
if returning != NMI {
// DeActivate() clears FAULTMASK, and a return with HardFault active and NMI not makes
// HardFault inactive too.
self.state.faultmask = 0;
self.scs.complete(HARD_FAULT);
}
self.note_pended();
// B1.5.8 calls EXC_RETURN<27:5> other than all ones UNPREDICTABLE, and the simplest thing is
// to look only at the bits that mean something.
let nested_ok = alone || is_set(ccr::NONBASETHRDENA, self.system.ccr);
let target = Target::of(exc_return)
.filter(|&target| active && (target == Target::Handler || nested_ok));
let Some(target) = target else {
self.chain(USAGE_FAULT, mask(cfsr::INVPC), exc_return);
return;
};
// B1.5.17: sleep-on-exit is for a return to Thread mode from the only active exception.
// It sleeps with the return still to do, and the pseudocode's count of none active
// contradicts the text.
if target != Target::Handler && alone && self.system.sleep_on_exit {
self.state.r[15] = exc_return;
if self.scs.wakes(self.masks()) {
self.finish_return(exc_return);
} else {
self.state.sleeping = ON_EXIT;
}
return;
}
self.finish_return(exc_return);
}
/// The rest of a return whose checks passed: a pending exception that beats where the return
/// goes is tail-chained on the frame as it is, as the Cortex-M4 does, 100166 3.9.2, and
/// otherwise the frame is popped.
fn finish_return(&mut self, exc_return: u32) {
if self.tail_chain(exc_return) {
return;
}
let Some(target) = Target::of(exc_return) else {
return;
};
match self.pop_stack(exc_return, target) {
Err((fault, status)) => self.chain(fault, status, exc_return),
// B1.5.8's last integrity check. Leaving the frame where it is saves undoing a pop with
// a push of the same words.
Ok(frame)
if (target == Target::Handler)
!= is_set(xpsr::EXCEPTION_NUMBER, frame.words[7]) =>
{
self.chain(USAGE_FAULT, mask(cfsr::INVPC), exc_return);
}
Ok(frame) => self.unstack(frame, exc_return, target),
}
}
/// B1.5.12's `TailChain()` into what preempts now, if anything does: the frame stays where it
/// is and the handler gets the same `EXC_RETURN` in `LR`. Answers whether it chained.
fn tail_chain(&mut self, exc_return: u32) -> bool {
let masks = self.masks();
let Some(next) = self.scs.acknowledge(masks) else {
return false;
};
self.state.r[14] = exc_return;
self.take(next, masks);
true
}
/// B1.5.8's `PopStack()` up to its first write: the frame `target`'s stack holds, read with the
/// privilege of the mode the return goes to, with the floating-point state when the frame has
/// it and nothing preserves it lazily. A fault answers the derived exception and its status
/// bit, B1.5.12. It runs after `DeActivate()` and before `IPSR` changes, so the MPU checks it
/// at the priority the return leaves and the privilege of where it goes.
fn pop_stack(&mut self, exc_return: u32, target: Target) -> Result<Unstacked, (u16, u32)> {
let at = if target == Target::ThreadProcess {
self.state.psp()
} else {
self.state.msp()
} & !3;
let privileged = target == Target::Handler || !is_set(control::NPRIV, self.state.control);
let returning = Context {
privileged,
..self.mpu_context()
};
self.with_mpu_context(returning, |core| {
let unstacking =
|refused| derived(refused, mask(cfsr::MUNSTKERR), mask(cfsr::UNSTKERR));
let words = core.load_words(at).map_err(unstacking)?;
let mut fp = None;
if core.extended(exc_return) && !is_set(fpccr::LSPACT, core.system.fpccr) {
if !core.fp_enabled(privileged) {
return Err((USAGE_FAULT, mask(cfsr::NOCP)));
}
fp = Some(
core.load_words(at.wrapping_add(FP_STATE))
.map_err(unstacking)?,
);
}
Ok(Unstacked { at, words, fp })
})
}
/// The rest of `PopStack()`: the registers and the stack pointer back as the frame and
/// `exc_return` say, and the mode `target` is.
fn unstack(&mut self, frame: Unstacked, exc_return: u32, target: Target) {
let Unstacked { at, words, fp } = frame;
let extended = self.extended(exc_return);
self.state.r[..4].copy_from_slice(&words[..4]);
self.state.r[12] = words[4];
self.state.r[14] = words[5];
// A return address with bit 0 set is UNPREDICTABLE, B1.5.8, and clearing it is the simplest.
self.state.r[15] = words[6] & !1;
let psr = words[7];
match fp {
Some(fp) => {
self.state.s[..16].copy_from_slice(&fp[..16]);
self.state.fpscr = fp[16];
}
// Nothing was preserved, so the registers still hold the frame's state, B1.5.8.
None if extended => self.system.fpccr &= !mask(fpccr::LSPACT),
None => {}
}
let align = extended || is_set(ccr::STKALIGN, self.system.ccr);
let mut sp = at.wrapping_add(if extended { EXTENDED } else { BASIC });
if align && is_set(xpsr::REALIGNED, psr) {
sp |= 4;
}
let mut control = self.state.control & !(mask(control::SPSEL) | mask(control::FPCA));
if target == Target::ThreadProcess {
self.state.set_psp(sp);
control |= mask(control::SPSEL);
} else {
self.state.set_msp(sp);
}
if extended {
control |= mask(control::FPCA);
}
self.state
.set_mode(get(xpsr::EXCEPTION_NUMBER, psr), control);
let ge = self.state.ge;
self.state.set_xpsr(psr);
// B1.5.8 `PopStack()`: only a core with the DSP extension has GE to put back.
if !self.features.dsp {
self.state.ge = ge;
}
}
/// Whether a return with `exc_return` pops the extended frame, B1.5.8: `EXC_RETURN<4>` clear,
/// on a core with the floating-point extension. Without one, the bit is SBOP, and a clear one
/// is UNPREDICTABLE, which the pseudocode's `HaveFPExt()` reads as the basic frame.
fn extended(&self, exc_return: u32) -> bool {
self.features.fp() && !is_set(exc_return::BASIC_FRAME, exc_return)
}
/// Takes a fault an exception return raised in place of the return, tail-chained on the frame
/// as B1.5.12 has it. A fault nothing can take locks up at the lockup address, B1.5.15.
fn chain(&mut self, number: u16, status: u32, exc_return: u32) {
self.system.cfsr |= status;
let raised = self.scs.raise(number, self.masks());
self.note_pended();
if raised != Raised::Lockup {
self.tail_chain(exc_return);
return;
}
self.state.locked_up = 1;
self.state.r[15] = LOCKUP;
if status == mask(cfsr::INVPC) && self.state.ipsr == u32::from(NMI) {
// B1.5.15: INVPC returning from NMI locks up at NMI's priority, so NMI stays active.
self.scs.pend(NMI);
self.note_pended();
self.scs.acknowledge(self.masks());
}
}
/// Stores `words` from `at` up, as word-aligned accesses at `position`, in the context
/// [`Family::with_mpu_context`] gives them.
fn store_words(&mut self, at: u32, words: &[u32], position: Position) -> Result<(), Refused> {
for (offset, &word) in (0..).step_by(4).zip(words) {
self.access(
Request::write(at.wrapping_add(offset), Size::Word, word),
Attributes::ALIGNED,
position,
)?;
}
Ok(())
}
/// Loads `N` words from `at` up, as [`Family::store_words`] stores them.
fn load_words<const N: usize>(&mut self, at: u32) -> Result<[u32; N], Refused> {
let mut words = [0; N];
for (offset, word) in (0..).step_by(4).zip(&mut words) {
*word = self.access(
Request::read(at.wrapping_add(offset), Size::Word),
Attributes::ALIGNED,
Position::Processor,
)?;
}
Ok(words)
}
/// Raises `fault` for the instruction at `state.r[15]`, B1.5.14, with its status bit in `CFSR`,
/// B3.2.15. A fault nothing can take locks the processor up at that instruction, B1.5.15.
pub(crate) fn raise_fault(&mut self, fault: Fault) {
let (number, status) = match fault {
Fault::Undefined => (USAGE_FAULT, mask(cfsr::UNDEFINSTR)),
Fault::InvalidState => (USAGE_FAULT, mask(cfsr::INVSTATE)),
Fault::NoCoprocessor => (USAGE_FAULT, mask(cfsr::NOCP)),
Fault::Unaligned => (USAGE_FAULT, mask(cfsr::UNALIGNED)),
Fault::DivideByZero => (USAGE_FAULT, mask(cfsr::DIVBYZERO)),
Fault::InstructionFetch if self.fetch_refused(self.state.r[15]) => {
(MEM_MANAGE, mask(cfsr::IACCVIOL))
}
Fault::InstructionFetch => (BUS_FAULT, mask(cfsr::IBUSERR)),
Fault::SupervisorCall => (SV_CALL, 0),
// C1.5: with halting debug on, a debug event halts the core at its instruction.
Fault::Breakpoint if self.debugger => {
self.halted |= mask(dfsr::BKPT);
return;
}
Fault::Breakpoint => {
self.breakpoint();
return;
}
// The run hears of it before the instruction retires, and answers it or has it be the
// breakpoint it is.
Fault::HostCall => {
self.host_calling = true;
return;
}
};
if status == mask(cfsr::IACCVIOL) {
// B2.3.8 and B3.5.3: a refused fetch leaves MMFAR invalid.
self.system.cfsr &= !mask(cfsr::MMARVALID);
}
self.system.cfsr |= status;
let masks = self.masks();
if self.pend_fault(number, masks) && fault == Fault::SupervisorCall {
// B1.5.15: an SVC that can't be taken is UNDEFINED.
self.system.cfsr |= mask(cfsr::UNDEFINSTR);
}
}
/// Whether the fetch of the instruction at `pc` failed because it was refused, MemManage
/// `IACCVIOL`, rather than because nothing answered, BusFault `IBUSERR`, B1.5.14 and B3.2.15.
/// The MPU, or the default map's XN with it off, decides before the bus is asked, B3.5.2 and
/// B3.5.3, and the translator fails a fetch from mapped memory only when it says no. A 32-bit
/// instruction whose first halfword is mapped and second isn't is the second's bus error, since
/// the architecture doesn't order two aborts in one instruction, C1.5.3.
fn fetch_refused(&self, pc: u32) -> bool {
let refused = |address| !self.mpu_fetch_allows(address);
match helpers::mapped_halfword(self, pc) {
_ if refused(pc) => true,
None => false,
Some(first) if Instruction::is_wide(first) => {
let second = pc.wrapping_add(2);
refused(second) || helpers::mapped_halfword(self, second).is_some()
}
Some(_) => true,
}
}
/// A `BKPT`, B1.5.14: DebugMonitor with `DEMCR.MON_EN` set, and HardFault with `HFSR.DEBUGEVT`
/// otherwise. `PRIMASK` set holds DebugMonitor off at any priority it can have and leaves
/// HardFault exactly the chance it had, so it's how you ask the controller for a monitor that
/// can't be taken.
fn breakpoint(&mut self) {
let monitor = self
.scs
.read(DEMCR, VirtualNanos(0))
.is_some_and(|value| is_set(demcr::MON_EN, value));
let mut masks = self.masks();
if !monitor {
masks.primask = true;
}
self.pend_fault(DEBUG_MONITOR, masks);
}
/// Pends `number` by B1.5.4's escalation rules, and locks up when nothing can take it,
/// answering whether it did.
fn pend_fault(&mut self, number: u16, masks: Masks) -> bool {
let lockup = self.scs.raise(number, masks) == Raised::Lockup;
self.note_pended();
if lockup {
self.state.locked_up = 1;
}
lockup
}
/// Raises the fault an instruction's access was refused with, B1.5.14, and answers whether the
/// instruction faults. A precise BusFault at a negative priority with `CCR.BFHFNMIGN` set is
/// recorded and otherwise ignored, B3.2.8. A watched access raises nothing, and the instruction
/// leaves as a faulting one does, with the core halted.
pub(crate) fn raise_data_fault(&mut self, refused: Refused) -> bool {
let (number, status, address) = match refused {
Refused::Watched => return true,
Refused::Unaligned => (USAGE_FAULT, mask(cfsr::UNALIGNED), None),
Refused::Mpu(address) => (
MEM_MANAGE,
mask(cfsr::DACCVIOL) | mask(cfsr::MMARVALID),
Some(address),
),
Refused::Bus(address) => (
BUS_FAULT,
mask(cfsr::PRECISERR) | mask(cfsr::BFARVALID),
Some(address),
),
};
if let Some(address) = address {
// 100166 table 4-1: MMFAR and BFAR are one register, so only one valid bit holds.
self.system.mmfar = address;
self.system.bfar = address;
self.system.cfsr &= !(mask(cfsr::MMARVALID) | mask(cfsr::BFARVALID));
}
self.system.cfsr |= status;
if number == BUS_FAULT
&& is_set(ccr::BFHFNMIGN, self.system.ccr)
&& self.below_priority_zero()
{
return false;
}
let masks = self.masks();
self.pend_fault(number, masks);
true
}
/// B1.6.3's `PreserveFPState()`, for a floating-point instruction to call while `FPCCR.LSPACT`
/// is set: `S0` to `S15` and `FPSCR` stored where `FPCAR` points, with the privilege the
/// preempted software had, and below priority 0 when it was or the processor is now. Answers
/// whether the instruction goes on. When it doesn't, its exception is pending or the processor
/// locked up, and the instruction must leave as if it never ran. The stores are the
/// instruction's, at `position`.
pub(crate) fn preserve_fp_state(&mut self, position: Position) -> bool {
let fpccr = self.system.fpccr;
let fp = self.fp_words();
let preempted = Context {
privileged: !is_set(fpccr::USER, fpccr),
below_priority_zero: !is_set(fpccr::HFRDY, fpccr) || self.below_priority_zero(),
};
let at = self.system.fpcar;
match self.with_mpu_context(preempted, |core| core.store_words(at, &fp, position)) {
Ok(()) => {
self.system.fpccr &= !mask(fpccr::LSPACT);
true
}
Err(refused) => self.preserving_failed(refused, fpccr),
}
}
/// B1.6.3's `TakePreserveFPException()` for a preservation `refused`, judged by the ready bits
/// `fpccr` held, B3.2.21.
fn preserving_failed(&mut self, refused: Refused, fpccr: u32) -> bool {
if refused == Refused::Watched {
// Stopped before a store, with nothing raised and `LSPACT` still set, so the
// instruction preserves the state again when it runs.
return false;
}
let (number, status) = derived(refused, mask(cfsr::MLSPERR), mask(cfsr::LSPERR));
self.system.cfsr |= status;
let preserved = self.scs.raise_while_preserving(number, fpccr, self.masks());
self.note_pended();
match preserved {
// B1.6.3 lets a fault that abandons the stores clear LSPACT, and the instruction that
// goes on needs it clear.
Preserved::Deferred => {
self.system.fpccr &= !mask(fpccr::LSPACT);
true
}
// 100166 7.2.7: the preservation starts over once the handler returns, so LSPACT stays.
Preserved::Taken => false,
Preserved::Lockup => {
self.state.locked_up = 1;
self.state.r[15] = LOCKUP;
false
}
}
}
/// Leaves the processor waiting for an interrupt, or for an event when `event`, B1.5.18 and
/// B1.5.19. A `WFE` with the event register set clears it and goes on, and a wait something
/// would end already doesn't start.
pub(crate) fn sleep(&mut self, event: bool) {
if event && std::mem::take(&mut self.state.event) != 0 {
return;
}
let sleeping = if event { FOR_EVENT } else { FOR_INTERRUPT };
if !self.woken(sleeping) {
self.state.sleeping = sleeping;
}
}
}
#[cfg(test)]
mod tests {
use super::super::family::ICSR;
use super::super::registers::{fpscr, hfsr, icsr, mask, shcsr};
use super::super::scs::SCS_BASE;
use super::super::translate::testing::{Bench, CODE, DEVICE, RAM, SIZE};
use super::super::translate::testing::{spec, spec_of};
use super::*;
use crate::emulator::{AccessKind, Exit, Processor, Queued};
use crate::machine::armv7m::{Fpu, MpuRegions, Variant};
use crate::soc::Width;
/// `UDF #0`, A7.7.191, which is UNDEFINED whatever else the decoder learns.
const UDF: u16 = 0xDE00;
/// `LDR r0, [r1]`, A7.7.43, and `B .`, A7.7.12, which waits.
const LDR: u16 = 0x6808;
/// `FPSCR` with default NaN and flush-to-zero set, a value a context saves and restores.
const FPSCR_DN_FZ: u32 = mask(fpscr::DN) | mask(fpscr::FZ);
const SPIN: u16 = 0xE7FE;
const THREAD_CODE: u32 = 0x1000;
const HANDLERS: u32 = 0x400;
const TOP: u32 = RAM + 0x1_0000;
const IRQ0: u16 = 16;
const IRQ1: u16 = 17;
const NOW: VirtualNanos = VirtualNanos(0);
fn handler(number: u16) -> u32 {
HANDLERS + 4 * u32::from(number)
}
/// A processor in Thread mode at [`THREAD_CODE`] with every vector pointing at a handler of
/// its own, and every instruction anywhere a `UDF`.
fn processor() -> Bench {
processor_of(&spec())
}
/// [`processor`], as `command` describes it.
fn processor_of(command: &crate::emulator::CoreSpec) -> Bench {
let mut bench = Bench::of(command, &[UDF]);
let table: Vec<u8> = std::iter::once(TOP)
.chain((1..64).map(|number| handler(number) | 1))
.flat_map(u32::to_le_bytes)
.collect();
bench.write(0, &table);
for number in 1..64 {
bench.place(handler(number), &[UDF, UDF]);
}
bench.place(THREAD_CODE, &[UDF, UDF]);
bench.core.state.r[15] = THREAD_CODE;
assert_eq!(
bench.core.state.r[13], TOP,
"the stack starts at the top of RAM"
);
bench
}
/// Enables external interrupt `line` at `priority` and raises its input.
fn interrupt(bench: &mut Bench, line: u16, priority: u8) {
let scs = &mut bench.core.scs;
scs.write(0x100 + u64::from(line / 32) * 4, 1 << (line % 32), NOW);
assert!(scs.write_priority_byte(0x400 + u64::from(line), priority));
scs.set_line(line, true);
}
fn system_priority(bench: &mut Bench, number: u16, priority: u8) {
let offset = 0xD18 + u64::from(number - 4);
assert!(bench.core.scs.write_priority_byte(offset, priority));
}
/// Takes what preempts, with nothing run.
fn take(bench: &mut Bench) {
assert_eq!(bench.run(0), (Exit::Budget, 0));
}
fn frame(bench: &Bench, at: u32) -> [u32; 8] {
let mut words = [0; 8];
for (word, address) in words.iter_mut().zip((at..).step_by(4)) {
*word = bench.word(address);
}
words
}
fn scs(bench: &mut Bench, offset: u64) -> u32 {
bench.core.scs.read(offset, NOW).unwrap()
}
/// `ICSR`, read as software reads it.
fn icsr(bench: &mut Bench) -> u32 {
let address = SCS_BASE + ICSR;
bench
.core
.debugger_access(None, address, Width::W32, NOW)
.and_then(|value| u32::try_from(value).ok())
.unwrap()
}
/// `ICSR.VECTACTIVE`, and `ICSR.RETTOBASE`, which says no other exception is active, B3.2.4.
fn active(bench: &Bench) -> (u32, bool) {
let value = bench.core.scs.icsr(bench.core.state.ipsr, false);
(get(icsr::VECTACTIVE, value), is_set(icsr::RETTOBASE, value))
}
fn hfsr(bench: &mut Bench) -> u32 {
scs(bench, 0xD2C)
}
#[test]
fn an_interrupt_from_thread_mode_is_stacked_and_returns() {
let mut bench = processor();
let state = &mut bench.core.state;
state.r[..4].copy_from_slice(&[10, 11, 12, 13]);
state.r[12] = 0x12;
state.r[14] = 0x1234_5679;
(state.n, state.c, state.ge) = (1, 1, 0b0101);
state.exclusive_valid = 1;
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
let at = TOP - BASIC;
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[15], state.t, state.r[13], state.r[14]),
(16, handler(IRQ0), 1, at, 0xFFFF_FFF9)
);
assert_eq!((state.event, state.exclusive_valid, state.it), (1, 0, 0));
assert_eq!(
frame(&bench, at),
[10, 11, 12, 13, 0x12, 0x1234_5679, THREAD_CODE, 0xA105_0000]
);
assert_eq!(active(&bench), (16, true));
let state = &mut bench.core.state;
state.r[..4].copy_from_slice(&[0; 4]);
(state.n, state.c, state.ge, state.event) = (0, 0, 0, 0);
bench.core.scs.set_line(0, false);
bench.core.exception_return(0xFFFF_FFF9);
let state = &bench.core.state;
assert_eq!(&state.r[..4], &[10, 11, 12, 13]);
assert_eq!(
(
state.ipsr,
state.r[15],
state.r[13],
state.r[14],
state.r[12]
),
(0, THREAD_CODE, TOP, 0x1234_5679, 0x12)
);
assert_eq!(
(state.n, state.c, state.ge, state.t, state.event),
(1, 1, 0b0101, 1, 1)
);
assert_eq!(active(&bench), (0, true), "nothing active");
}
#[test]
fn a_stack_four_bytes_off_is_realigned_and_put_back() {
let mut bench = processor();
bench.core.state.r[13] = TOP - 4;
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
let at = TOP - 0x28;
assert_eq!(bench.core.state.r[13], at);
assert_eq!(frame(&bench, at)[7], mask(xpsr::T) | mask(xpsr::REALIGNED));
bench.core.scs.set_line(0, false);
bench.core.exception_return(0xFFFF_FFF9);
assert_eq!(bench.core.state.r[13], TOP - 4);
assert_eq!(bench.core.state.xpsr(), mask(xpsr::T));
}
#[test]
fn four_byte_alignment_leaves_the_stack_where_it_is() {
let mut bench = processor();
bench.core.system.ccr &= !mask(ccr::STKALIGN);
bench.core.state.r[13] = TOP - 4;
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
let at = TOP - 4 - BASIC;
assert_eq!(bench.core.state.r[13], at);
assert!(!is_set(xpsr::REALIGNED, frame(&bench, at)[7]));
bench.core.scs.set_line(0, false);
bench.core.exception_return(0xFFFF_FFF9);
assert_eq!(bench.core.state.r[13], TOP - 4);
}
#[test]
fn thread_mode_on_the_process_stack_comes_back_to_it() {
let mut bench = processor();
let state = &mut bench.core.state;
state.control = mask(control::SPSEL) | mask(control::NPRIV);
state.r[13] = TOP - 0x100;
state.other_sp = TOP;
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
let state = &bench.core.state;
assert_eq!(
(state.r[13], state.psp(), state.r[14], state.control),
(TOP, TOP - 0x120, 0xFFFF_FFFD, mask(control::NPRIV))
);
assert!(state.privileged(), "Handler mode whatever nPRIV says");
assert_eq!(frame(&bench, TOP - 0x120)[6], THREAD_CODE);
bench.core.scs.set_line(0, false);
bench.core.exception_return(0xFFFF_FFFD);
let state = &bench.core.state;
assert_eq!(
(state.r[13], state.msp(), state.control, state.ipsr),
(
TOP - 0x100,
TOP,
mask(control::SPSEL) | mask(control::NPRIV),
0
)
);
}
#[test]
fn a_higher_priority_interrupt_nests_and_returns_to_its_handler() {
let mut bench = processor();
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
interrupt(&mut bench, 1, 0x40);
take(&mut bench);
let at = TOP - 2 * BASIC;
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[14], state.r[13], state.r[15]),
(17, 0xFFFF_FFF1, at, handler(IRQ1))
);
let stacked = frame(&bench, at);
assert_eq!(
(stacked[5], stacked[6], stacked[7]),
(0xFFFF_FFF9, handler(IRQ0), 0x0100_0010)
);
bench.core.scs.set_line(1, false);
bench.core.exception_return(0xFFFF_FFF1);
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[14], state.r[13], state.r[15]),
(16, 0xFFFF_FFF9, TOP - BASIC, handler(IRQ0))
);
bench.core.scs.set_line(0, false);
bench.core.exception_return(0xFFFF_FFF9);
assert_eq!((bench.core.state.ipsr, bench.core.state.r[13]), (0, TOP));
}
#[test]
fn a_pending_interrupt_is_tail_chained_on_the_same_frame() {
let mut bench = processor();
interrupt(&mut bench, 0, 0x80);
interrupt(&mut bench, 1, 0x80);
take(&mut bench);
assert_eq!(bench.core.state.ipsr, 16, "the lower number first");
bench.core.state.r[0] = 77;
bench.core.state.r[14] = 0;
bench.core.scs.set_line(0, false);
bench.core.exception_return(0xFFFF_FFF9);
let state = &bench.core.state;
assert_eq!(
(
state.ipsr,
state.r[15],
state.r[13],
state.r[14],
state.r[0]
),
(17, handler(IRQ1), TOP - BASIC, 0xFFFF_FFF9, 77),
"no pop and no push"
);
bench.core.scs.set_line(1, false);
bench.core.exception_return(0xFFFF_FFF9);
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[13], state.r[15], state.r[0]),
(0, TOP, THREAD_CODE, 0)
);
}
/// Each `EXC_RETURN` of Table B1-9 is the one entry makes from its mode, stack and
/// `CONTROL.FPCA`, and returning with it puts all three back.
#[test]
fn every_exc_return_goes_where_table_b1_9_says() {
for (control, from_handler, exc_return) in [
(0, false, 0xFFFF_FFF9),
(mask(control::SPSEL), false, 0xFFFF_FFFD),
(mask(control::FPCA), false, 0xFFFF_FFE9),
(
mask(control::FPCA) | mask(control::SPSEL),
false,
0xFFFF_FFED,
),
(0, true, 0xFFFF_FFF1),
(mask(control::FPCA), true, 0xFFFF_FFE1),
] {
let mut bench = processor();
bench.core.state.other_sp = TOP - 0x200;
if from_handler {
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
}
bench.core.state.control |= control;
let state = &bench.core.state;
let before = (state.ipsr, state.r[15], state.r[13]);
interrupt(&mut bench, 1, 0x40);
take(&mut bench);
assert_eq!(bench.core.state.r[14], exc_return, "{exc_return:#x}");
assert_eq!(
bench.core.state.control & (mask(control::FPCA) | mask(control::SPSEL)),
0
);
bench.core.scs.set_line(1, false);
bench.core.exception_return(exc_return);
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[15], state.r[13], state.control),
(before.0, before.1, before.2, control),
"{exc_return:#x}"
);
assert_eq!(state.locked_up, 0);
}
}
/// Without the floating-point extension, `EXC_RETURN<4>` is SBOP and the frame always basic,
/// B1.5.6. A return with it clear is UNPREDICTABLE, and `PopStack()` with no `HaveFPExt()`
/// pops the basic frame and leaves `CONTROL.FPCA` alone, B1.5.8. Without the DSP extension,
/// the stacked GE isn't put back.
#[test]
fn a_core_without_the_extensions_pops_the_basic_frame() {
let mut bench = processor_of(&spec_of(Variant::M3, Fpu::None, MpuRegions::Eight));
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
assert_eq!(bench.core.state.r[14], 0xFFFF_FFF9);
let psr = TOP - BASIC + 0x1C;
let with_ge = bench.word(psr) | mask(xpsr::GE);
bench.write(psr, &with_ge.to_le_bytes());
bench.core.scs.set_line(0, false);
bench.core.exception_return(0xFFFF_FFE9);
let state = &bench.core.state;
assert_eq!(
(
state.ipsr,
state.r[13],
state.r[15],
state.control,
state.ge
),
(0, TOP, THREAD_CODE, 0, 0)
);
}
#[test]
fn an_illegal_exc_return_raises_invpc_on_the_same_frame() {
for nibble in (0..16).filter(|nibble| ![0b0001, 0b1001, 0b1101].contains(nibble)) {
for enabled in [false, true] {
let mut bench = processor();
if enabled {
bench.core.scs.write(0xD24, mask(shcsr::USGFAULTENA), NOW);
}
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
let exc_return = 0xFFFF_FFF0 | nibble;
bench.core.exception_return(exc_return);
let expected = if enabled { USAGE_FAULT } else { HARD_FAULT };
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[14], state.r[13], state.r[15]),
(
u32::from(expected),
exc_return,
TOP - BASIC,
handler(expected)
),
"{exc_return:#x}"
);
assert_eq!(bench.core.system.cfsr, mask(cfsr::INVPC));
assert_eq!(
hfsr(&mut bench),
if enabled { 0 } else { mask(hfsr::FORCED) }
);
assert_eq!(scs(&mut bench, 0x300), 0, "the interrupt isn't active");
}
}
}
/// `NONBASETHRDENA` lets software that made the frame say Thread mode return there with IRQ0
/// still active.
#[test]
fn returning_to_thread_mode_with_another_active_is_invpc_unless_allowed() {
let mut bench = processor();
bench.core.scs.write(0xD24, mask(shcsr::USGFAULTENA), NOW);
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
interrupt(&mut bench, 1, 0x40);
take(&mut bench);
bench.core.scs.set_line(1, false);
bench.core.exception_return(0xFFFF_FFF9);
assert_eq!(bench.core.state.ipsr, u32::from(USAGE_FAULT));
assert_eq!(bench.core.system.cfsr, mask(cfsr::INVPC));
let mut bench = processor();
bench.core.system.ccr |= mask(ccr::NONBASETHRDENA);
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
interrupt(&mut bench, 1, 0x40);
take(&mut bench);
let at = bench.core.state.r[13];
bench.write(at + 0x1C, &mask(xpsr::T).to_le_bytes());
bench.core.scs.set_line(1, false);
bench.core.exception_return(0xFFFF_FFF9);
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[13], state.r[15]),
(0, TOP - BASIC, handler(IRQ0))
);
assert_eq!(scs(&mut bench, 0x300), 1, "IRQ0 is still active");
}
/// B3.2.4: `VECTACTIVE` is `IPSR<8:0>`, so a return to Thread mode with IRQ0 still active
/// reads 0, and `RETTOBASE` says IRQ0 is still to come back to.
#[test]
fn icsr_reads_thread_mode_with_an_exception_still_active() {
let mut bench = processor();
bench.core.system.ccr |= mask(ccr::NONBASETHRDENA);
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
interrupt(&mut bench, 1, 0x40);
take(&mut bench);
let at = bench.core.state.r[13];
bench.write(at + 0x1C, &mask(xpsr::T).to_le_bytes());
bench.core.scs.set_line(1, false);
bench.core.exception_return(0xFFFF_FFF9);
assert_eq!(bench.core.state.ipsr, 0);
assert_eq!(
icsr(&mut bench) & (mask(icsr::VECTACTIVE) | mask(icsr::RETTOBASE)),
0
);
}
#[test]
fn a_frame_whose_ipsr_disagrees_with_the_mode_is_invpc() {
let mut bench = processor();
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
bench.write(
TOP - BASIC + 0x1C,
&set(xpsr::EXCEPTION_NUMBER, mask(xpsr::T), 5).to_le_bytes(),
);
bench.core.scs.set_line(0, false);
bench.core.exception_return(0xFFFF_FFF9);
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[13], state.r[14]),
(u32::from(HARD_FAULT), TOP - BASIC, 0xFFFF_FFF9),
"Thread mode with an IPSR of 5"
);
assert_eq!(bench.core.system.cfsr, mask(cfsr::INVPC));
let mut bench = processor();
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
interrupt(&mut bench, 1, 0x40);
take(&mut bench);
let at = bench.core.state.r[13];
bench.write(at + 0x1C, &mask(xpsr::T).to_le_bytes());
bench.core.scs.set_line(1, false);
bench.core.exception_return(0xFFFF_FFF1);
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[13]),
(u32::from(HARD_FAULT), at),
"Handler mode with an IPSR of 0"
);
assert_eq!(bench.core.system.cfsr, mask(cfsr::INVPC));
}
#[test]
fn returning_from_an_inactive_exception_is_invpc() {
let mut bench = processor();
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
bench.core.state.ipsr = u32::from(IRQ1);
bench.core.exception_return(0xFFFF_FFF9);
assert_eq!(bench.core.state.ipsr, u32::from(HARD_FAULT));
assert_eq!(bench.core.state.r[13], TOP - BASIC);
assert_eq!(bench.core.system.cfsr, mask(cfsr::INVPC));
}
#[test]
fn faultmask_clears_on_return_but_not_from_nmi() {
let mut bench = processor();
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
bench.core.state.faultmask = 1;
bench.core.scs.set_line(0, false);
bench.core.exception_return(0xFFFF_FFF9);
assert_eq!(bench.core.state.faultmask, 0);
bench.core.scs.pend(NMI);
take(&mut bench);
assert_eq!(bench.core.state.ipsr, u32::from(NMI));
bench.core.state.faultmask = 1;
bench.core.exception_return(0xFFFF_FFF9);
assert_eq!((bench.core.state.ipsr, bench.core.state.faultmask), (0, 1));
}
#[test]
fn a_return_from_hardfault_clears_faultmask_and_makes_it_inactive() {
let mut bench = processor();
bench.core.scs.pend(HARD_FAULT);
take(&mut bench);
bench.core.state.faultmask = 1;
bench.core.exception_return(0xFFFF_FFF9);
assert_eq!((bench.core.state.ipsr, bench.core.state.faultmask), (0, 0));
assert_eq!(active(&bench), (0, true), "nothing active");
}
#[test]
fn a_bus_fault_while_stacking_is_taken_on_the_same_frame() {
let mut bench = processor();
bench.bus.fails_from = Some(u64::from(DEVICE));
bench.core.state.r[13] = DEVICE + 0x100;
bench.core.scs.write(0xD24, mask(shcsr::BUSFAULTENA), NOW);
system_priority(&mut bench, BUS_FAULT, 0x00);
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[13], state.r[14], state.r[15]),
(
u32::from(BUS_FAULT),
DEVICE + 0x100 - BASIC,
0xFFFF_FFF9,
handler(BUS_FAULT)
)
);
assert_eq!(
bench.core.system.cfsr,
mask(cfsr::STKERR),
"BFAR isn't valid"
);
assert_eq!(
bench.bus.accesses.len(),
1,
"the stores after the first were dropped"
);
assert_eq!(scs(&mut bench, 0x200), 1, "IRQ0 waits, pending");
}
#[test]
fn a_stacking_fault_with_busfault_off_escalates_to_hardfault() {
let mut bench = processor();
bench.bus.fails_from = Some(u64::from(DEVICE));
bench.core.state.r[13] = DEVICE + 0x100;
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
assert_eq!(bench.core.state.ipsr, u32::from(HARD_FAULT));
assert_eq!(bench.core.system.cfsr, mask(cfsr::STKERR));
assert_eq!(hfsr(&mut bench), mask(hfsr::FORCED));
}
#[test]
fn a_stacking_fault_entering_nmi_from_hardfault_locks_up() {
let mut bench = processor();
bench.core.scs.pend(HARD_FAULT);
take(&mut bench);
bench.bus.fails_from = Some(u64::from(DEVICE));
bench.core.state.r[13] = DEVICE + 0x100;
bench.core.scs.pend(NMI);
assert_eq!(bench.run(0), (Exit::Lockup, 0));
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[15], state.locked_up),
(u32::from(NMI), LOCKUP, 1)
);
assert_eq!(bench.core.system.cfsr, mask(cfsr::STKERR));
}
#[test]
fn a_vector_read_fault_is_hardfault() {
let mut bench = processor();
bench.core.system.vtor = DEVICE;
bench.bus.fails_from = Some(u64::from(DEVICE + 4 * u32::from(IRQ0)));
bench.bus.answer = u64::from(handler(HARD_FAULT) | 1);
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[15], state.r[14], state.r[13]),
(
u32::from(HARD_FAULT),
handler(HARD_FAULT),
0xFFFF_FFF9,
TOP - BASIC
)
);
assert_eq!(hfsr(&mut bench), mask(hfsr::VECTTBL));
assert_eq!(scs(&mut bench, 0x200), 1, "IRQ0 pending again");
}
#[test]
fn a_hardfault_vector_read_fault_locks_up() {
let mut bench = processor();
bench.core.system.vtor = DEVICE;
bench.bus.fails_from = Some(u64::from(DEVICE));
interrupt(&mut bench, 0, 0x80);
assert_eq!(bench.run(0), (Exit::Lockup, 0));
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[15], state.locked_up),
(u32::from(HARD_FAULT), LOCKUP, 1)
);
assert_eq!(hfsr(&mut bench), mask(hfsr::VECTTBL));
}
/// Only an instruction returns from an exception inside a run, where the bus can fail an
/// access, but for the return sleep-on-exit left to do, which a wake under `PRIMASK` finishes.
#[test]
fn a_bus_fault_while_unstacking_leaves_the_frame_and_chains() {
let mut bench = processor();
bench.core.state.r[13] = DEVICE + 0x100;
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
let at = DEVICE + 0x100 - BASIC;
assert_eq!(bench.core.state.r[13], at);
bench.core.scs.set_line(0, false);
bench.core.system.sleep_on_exit = true;
bench.core.state.primask = 1;
bench.core.exception_return(0xFFFF_FFF9);
assert_eq!(bench.core.state.sleeping, ON_EXIT);
bench.bus.fails_from = Some(u64::from(DEVICE));
interrupt(&mut bench, 1, 0x80);
assert_eq!(bench.run(0), (Exit::Budget, 0));
take(&mut bench);
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[13], state.r[14], state.sleeping),
(u32::from(HARD_FAULT), at, 0xFFFF_FFF9, 0)
);
assert_eq!(bench.core.system.cfsr, mask(cfsr::UNSTKERR));
assert_eq!(hfsr(&mut bench), mask(hfsr::FORCED));
}
/// What a block does for an instruction that faults: the helper raises it at the
/// instruction's address, and the block leaves with the PC there.
fn fault(bench: &mut Bench, pc: u32, fault: Fault) {
helpers::raise(&mut bench.core, fault as u32, pc);
bench.core.state.r[15] = pc;
}
#[test]
fn an_undefined_instruction_escalates_to_hardfault_and_then_locks_up() {
let mut bench = processor();
fault(&mut bench, THREAD_CODE, Fault::Undefined);
take(&mut bench);
assert_eq!(bench.core.state.ipsr, u32::from(HARD_FAULT));
assert_eq!(hfsr(&mut bench), mask(hfsr::FORCED));
fault(&mut bench, handler(HARD_FAULT), Fault::Undefined);
assert_eq!(bench.run(10), (Exit::Lockup, 0));
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[15], state.locked_up),
(u32::from(HARD_FAULT), handler(HARD_FAULT), 1),
"locked up at the instruction"
);
assert_eq!(bench.core.system.cfsr, mask(cfsr::UNDEFINSTR));
assert_eq!(frame(&bench, TOP - BASIC)[6], THREAD_CODE);
}
#[test]
fn each_fault_sets_its_own_status_bit() {
for (fault, status) in [
(Fault::Undefined, mask(cfsr::UNDEFINSTR)),
(Fault::InvalidState, mask(cfsr::INVSTATE)),
(Fault::NoCoprocessor, mask(cfsr::NOCP)),
(Fault::Unaligned, mask(cfsr::UNALIGNED)),
(Fault::DivideByZero, mask(cfsr::DIVBYZERO)),
] {
let mut bench = processor();
bench.core.scs.write(0xD24, mask(shcsr::USGFAULTENA), NOW);
bench.core.raise_fault(fault);
take(&mut bench);
assert_eq!(bench.core.state.ipsr, u32::from(USAGE_FAULT), "{fault:?}");
assert_eq!(bench.core.system.cfsr, status, "{fault:?}");
assert_eq!(frame(&bench, TOP - BASIC)[6], THREAD_CODE, "{fault:?}");
}
}
#[test]
fn a_fetch_from_execute_never_memory_is_iaccviol() {
let mut bench = processor();
bench.core.scs.write(0xD24, mask(shcsr::MEMFAULTENA), NOW);
fault(&mut bench, DEVICE, Fault::InstructionFetch);
take(&mut bench);
assert_eq!(bench.core.state.ipsr, u32::from(MEM_MANAGE));
assert_eq!(
bench.core.system.cfsr,
mask(cfsr::IACCVIOL),
"MMFAR isn't valid"
);
assert_eq!(frame(&bench, TOP - BASIC)[6], DEVICE);
}
/// B2.3.8 and B3.5.3: a refused fetch sets `IACCVIOL` and clears `MMARVALID`, since `MMFAR`
/// doesn't hold the fetch's address.
#[test]
fn a_refused_fetch_leaves_mmfar_invalid() {
let mut bench = processor();
bench.core.scs.write(0xD24, mask(shcsr::MEMFAULTENA), NOW);
bench.core.system.cfsr = mask(cfsr::DACCVIOL) | mask(cfsr::MMARVALID);
fault(&mut bench, DEVICE, Fault::InstructionFetch);
assert_eq!(
bench.core.system.cfsr,
mask(cfsr::DACCVIOL) | mask(cfsr::IACCVIOL)
);
}
#[test]
fn a_fetch_nothing_answers_is_ibuserr() {
let mut bench = processor();
bench.core.scs.write(0xD24, mask(shcsr::BUSFAULTENA), NOW);
fault(&mut bench, 0x6000_0000, Fault::InstructionFetch);
take(&mut bench);
assert_eq!(bench.core.state.ipsr, u32::from(BUS_FAULT));
assert_eq!(
bench.core.system.cfsr,
mask(cfsr::IBUSERR),
"BFAR isn't valid"
);
assert_eq!(frame(&bench, TOP - BASIC)[6], 0x6000_0000);
}
/// A fetch that fails where memory's mapped was refused, which is how the MPU's refusal reaches
/// here, and a 32-bit instruction whose second halfword runs off the end of what's mapped
/// isn't.
#[test]
fn a_fetch_refused_from_mapped_memory_is_iaccviol() {
const WIDE: u32 = 0x2000;
let end = CODE + u32::try_from(SIZE).unwrap() - 2;
for (last, pc, number, status) in [
(UDF, THREAD_CODE, MEM_MANAGE, mask(cfsr::IACCVIOL)),
(UDF, WIDE, MEM_MANAGE, mask(cfsr::IACCVIOL)),
(UDF, end, MEM_MANAGE, mask(cfsr::IACCVIOL)),
(0xF000, end, BUS_FAULT, mask(cfsr::IBUSERR)),
] {
let mut bench = processor();
bench.place(WIDE, &[0xF000, 0xF800]);
bench.place(end, &[last]);
bench.core.scs.write(
0xD24,
mask(shcsr::MEMFAULTENA) | mask(shcsr::BUSFAULTENA),
NOW,
);
fault(&mut bench, pc, Fault::InstructionFetch);
take(&mut bench);
assert_eq!(bench.core.state.ipsr, u32::from(number), "{pc:#x}");
assert_eq!(bench.core.system.cfsr, status, "{pc:#x}");
assert_eq!(frame(&bench, TOP - BASIC)[6], pc, "{pc:#x}");
}
}
#[test]
fn data_faults_record_their_address() {
let mut bench = processor();
let system = |bench: &Bench| {
let system = &bench.core.system;
(system.cfsr, system.mmfar, system.bfar)
};
assert!(bench.core.raise_data_fault(Refused::Mpu(0x1234)));
assert_eq!(
system(&bench),
(mask(cfsr::DACCVIOL) | mask(cfsr::MMARVALID), 0x1234, 0x1234)
);
assert!(bench.core.raise_data_fault(Refused::Bus(0x5678)));
assert_eq!(
system(&bench),
(
mask(cfsr::DACCVIOL) | mask(cfsr::PRECISERR) | mask(cfsr::BFARVALID),
0x5678,
0x5678
),
"one address register, so one valid bit"
);
assert!(bench.core.raise_data_fault(Refused::Unaligned));
assert_eq!(
system(&bench),
(
mask(cfsr::DACCVIOL)
| mask(cfsr::PRECISERR)
| mask(cfsr::BFARVALID)
| mask(cfsr::UNALIGNED),
0x5678,
0x5678
)
);
take(&mut bench);
assert_eq!(
bench.core.state.ipsr,
u32::from(HARD_FAULT),
"all three disabled"
);
}
#[test]
fn bfhfnmign_ignores_a_bus_fault_at_negative_priority() {
let mut bench = processor();
bench.core.system.ccr |= mask(ccr::BFHFNMIGN);
bench.core.state.faultmask = 1;
assert!(!bench.core.raise_data_fault(Refused::Bus(0x40)));
assert_eq!(
bench.core.system.cfsr,
mask(cfsr::PRECISERR) | mask(cfsr::BFARVALID)
);
assert_eq!(bench.core.state.locked_up, 0);
assert!(
bench.core.raise_data_fault(Refused::Mpu(0x40)),
"only a BusFault"
);
assert_eq!(bench.core.state.locked_up, 1, "nothing can take it");
let mut bench = processor();
bench.core.scs.pend(HARD_FAULT);
take(&mut bench);
assert!(
bench.core.raise_data_fault(Refused::Bus(0x40)),
"BFHFNMIGN clear"
);
assert_eq!(bench.core.state.locked_up, 1);
}
#[test]
fn svc_is_svcall_and_escalates_under_primask() {
let mut bench = processor();
bench.core.state.r[15] = THREAD_CODE + 2;
bench.core.raise_fault(Fault::SupervisorCall);
take(&mut bench);
assert_eq!(bench.core.state.ipsr, u32::from(SV_CALL));
assert_eq!(frame(&bench, TOP - BASIC)[6], THREAD_CODE + 2);
assert_eq!(bench.core.system.cfsr, 0);
let mut bench = processor();
bench.core.state.primask = 1;
bench.core.raise_fault(Fault::SupervisorCall);
take(&mut bench);
assert_eq!(bench.core.state.ipsr, u32::from(HARD_FAULT));
assert_eq!(hfsr(&mut bench), mask(hfsr::FORCED));
}
/// The helper's answer is what tells the block to leave the SVC unretired at its own address.
#[test]
fn svc_in_hardfault_is_undefined_and_locks_up_at_itself() {
let svc = Fault::SupervisorCall as u32;
let mut bench = processor();
assert_eq!(
helpers::raise(&mut bench.core, svc, THREAD_CODE),
0,
"taken, so it retires"
);
let mut bench = processor();
bench.core.scs.pend(HARD_FAULT);
take(&mut bench);
let at = handler(HARD_FAULT) + 2;
assert_eq!(
helpers::raise(&mut bench.core, svc, at),
1,
"UNDEFINED, so it doesn't retire"
);
let state = &bench.core.state;
assert_eq!((state.locked_up, state.r[15]), (1, at));
assert_eq!(bench.core.system.cfsr, mask(cfsr::UNDEFINSTR));
assert_eq!(bench.run(10), (Exit::Lockup, 0));
}
/// B1.5.15: the block an SVC in HardFault runs in leaves it unretired at its own address.
#[test]
fn svc_in_hardfault_locks_up_unretired_when_it_runs() {
let mut bench = processor();
bench.place(handler(HARD_FAULT), &[0xDF00]);
bench.core.scs.pend(HARD_FAULT);
take(&mut bench);
assert_eq!(bench.run(10), (Exit::Lockup, 0));
let state = &bench.core.state;
assert_eq!((state.r[15], state.locked_up), (handler(HARD_FAULT), 1));
assert_eq!(bench.core.system.cfsr, mask(cfsr::UNDEFINSTR));
}
/// B3.2.18: an unaligned load split into bytes faults at the byte the bus refuses, but `BFAR`
/// holds the address the load asked for.
#[test]
fn a_split_load_the_bus_refuses_puts_its_own_address_in_bfar() {
let mut bench = processor();
bench.place(THREAD_CODE, &[LDR]);
bench.place(handler(BUS_FAULT), &[SPIN]);
bench.core.scs.write(0xD24, mask(shcsr::BUSFAULTENA), NOW);
bench.bus.fails_from = Some(u64::from(TOP));
bench.core.state.r[1] = TOP - 2;
assert_eq!(bench.run(1), (Exit::Returned, 0), "after the bus refused");
take(&mut bench);
let system = &bench.core.system;
assert_eq!(bench.core.state.ipsr, u32::from(BUS_FAULT));
assert_eq!(
(system.cfsr, system.bfar),
(mask(cfsr::PRECISERR) | mask(cfsr::BFARVALID), TOP - 2)
);
}
/// B3.1.1 and C1.2.1: software without the privilege gets a precise BusFault from the
/// processor's own bus, the MPU's registers included, B3.5.4, a window to the runtime's models
/// included, and `LDRT` never has it. `STIR` is the one register `CCR.USERSETMPEND` opens to
/// it, B3.2.8, and the ITM never refuses it, C1.7.1.
#[test]
fn unprivileged_software_is_refused_the_private_peripheral_bus_but_the_itm() {
const STR: u16 = 0x6008;
const LDRT: [u16; 2] = [0xF851, 0x0E00];
const CPUID: u32 = 0xE000_ED00;
const MPU_CTRL: u32 = 0xE000_ED94;
const STIR: u32 = 0xE000_EF00;
const ITM_STIM0: u32 = 0xE000_0000;
const DWT_CYCCNT: u32 = 0xE000_1004;
const FP_CTRL: u32 = 0xE000_2000;
const ROM_TABLE: u32 = 0xE00F_F000;
let run = |code: &[u16], address: u32, control: u32, ccr: u32| {
let mut bench = processor();
bench.place(THREAD_CODE, code);
bench.place(handler(BUS_FAULT), &[SPIN]);
bench.core.scs.write(0xD24, mask(shcsr::BUSFAULTENA), NOW);
bench.core.state.control = control;
bench.core.system.ccr |= ccr;
bench.core.state.r[..2].copy_from_slice(&[3, address]);
bench.run(1);
bench
};
for (code, address, control) in [
(&[LDR][..], CPUID, mask(control::NPRIV)),
(&[LDR][..], MPU_CTRL, mask(control::NPRIV)),
(&[STR][..], STIR, mask(control::NPRIV)),
(&LDRT[..], CPUID, 0),
(&[LDR][..], DWT_CYCCNT, mask(control::NPRIV)),
(&LDRT[..], DWT_CYCCNT, 0),
(&[LDR][..], FP_CTRL, mask(control::NPRIV)),
(&[STR][..], ROM_TABLE, mask(control::NPRIV)),
] {
let bench = run(code, address, control, 0);
let system = &bench.core.system;
assert_eq!(
(system.cfsr, system.bfar),
(mask(cfsr::PRECISERR) | mask(cfsr::BFARVALID), address),
"{address:#x}"
);
}
let mut privileged = run(&[LDR], CPUID, 0, 0);
assert_eq!(privileged.core.state.r[0], 0x410F_C241);
let mut stir = run(&[STR], STIR, mask(control::NPRIV), mask(ccr::USERSETMPEND));
assert_eq!((privileged.core.system.cfsr, stir.core.system.cfsr), (0, 0));
assert_eq!(scs(&mut stir, 0x200), 1 << 3, "IRQ3 pending");
assert_eq!(scs(&mut privileged, 0x200), 0);
for code in [LDR, STR] {
let itm = run(&[code], ITM_STIM0, mask(control::NPRIV), 0);
assert_eq!(itm.core.system.cfsr, 0, "{code:#x} at the ITM");
}
let window = run(&[LDR], DWT_CYCCNT, 0, 0);
assert_eq!(window.core.system.cfsr, 0, "privileged at a window");
assert_eq!(window.bus.accesses.len(), 1, "which reached the bus");
}
/// B1.5.18: with `SEVONPEND` set, an interrupt going pending sets the event register, and one
/// already pending doesn't.
#[test]
fn an_interrupt_going_pending_is_an_event_with_sevonpend() {
let mut bench = processor();
bench.core.set_line(0, true);
assert_eq!(bench.core.state.event, 0, "SEVONPEND clear");
bench.core.system.event_on_pending = true;
bench.core.set_line(0, false);
bench.core.set_line(0, true);
assert_eq!(bench.core.state.event, 0, "already pending");
bench.core.set_line(1, true);
assert_eq!(bench.core.state.event, 1);
}
#[test]
fn bkpt_is_debugmonitor_only_with_the_monitor_on() {
let mut bench = processor();
bench.core.raise_fault(Fault::Breakpoint);
take(&mut bench);
assert_eq!(bench.core.state.ipsr, u32::from(HARD_FAULT));
assert_eq!(hfsr(&mut bench), mask(hfsr::DEBUGEVT));
let mut bench = processor();
bench.core.scs.write(DEMCR, mask(demcr::MON_EN), NOW);
bench.core.raise_fault(Fault::Breakpoint);
take(&mut bench);
assert_eq!(bench.core.state.ipsr, u32::from(DEBUG_MONITOR));
assert_eq!(hfsr(&mut bench), 0);
let mut bench = processor();
bench.core.scs.write(DEMCR, mask(demcr::MON_EN), NOW);
bench.core.scs.pend(HARD_FAULT);
take(&mut bench);
bench.core.raise_fault(Fault::Breakpoint);
assert_eq!(bench.core.state.locked_up, 1, "a breakpoint in HardFault");
}
#[test]
fn an_nmi_takes_a_locked_up_processor_out() {
let mut bench = processor();
bench.core.scs.pend(HARD_FAULT);
take(&mut bench);
fault(&mut bench, handler(HARD_FAULT), Fault::Undefined);
assert_eq!(bench.run(10), (Exit::Lockup, 0));
bench.core.scs.pend(NMI);
take(&mut bench);
let state = &bench.core.state;
assert_eq!((state.ipsr, state.locked_up), (u32::from(NMI), 0));
assert_eq!(
frame(&bench, state.r[13])[6],
handler(HARD_FAULT),
"the lockup address"
);
bench.core.exception_return(0xFFFF_FFF1);
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[15], state.locked_up),
(u32::from(HARD_FAULT), handler(HARD_FAULT), 0)
);
fault(&mut bench, handler(HARD_FAULT), Fault::Undefined);
assert_eq!(bench.run(10), (Exit::Lockup, 0), "and locks up again");
}
#[test]
fn an_invpc_returning_from_nmi_locks_up_at_nmi_priority() {
let mut bench = processor();
bench.core.scs.pend(HARD_FAULT);
take(&mut bench);
bench.core.scs.pend(NMI);
take(&mut bench);
bench.core.exception_return(0xFFFF_FFF5);
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[15], state.locked_up),
(u32::from(NMI), LOCKUP, 1)
);
assert_eq!(active(&bench).0, u32::from(NMI));
bench.core.scs.pend(NMI);
assert_eq!(
bench.run(0),
(Exit::Lockup, 0),
"a second NMI can't preempt the first"
);
}
/// A floating-point context in `S0` to `S15` and `FPSCR`, with access to it and `fpccr`.
fn with_fp_context(bench: &mut Bench, fpccr: u32) {
let core = &mut bench.core;
core.system.cpacr = mask(cpacr::CP10) | mask(cpacr::CP11);
core.system.fpccr = fpccr;
core.state.control |= mask(control::FPCA);
for (value, register) in (0x100..).zip(core.state.s.iter_mut()) {
*register = value;
}
core.state.fpscr = FPSCR_DN_FZ;
}
fn clobber_fp(bench: &mut Bench) {
bench.core.state.s[..16].copy_from_slice(&[0; 16]);
bench.core.state.fpscr = 0;
}
#[test]
fn the_extended_frame_is_stacked_and_unstacked_whole() {
let mut bench = processor();
with_fp_context(&mut bench, mask(fpccr::ASPEN));
bench.core.system.ccr &= !mask(ccr::STKALIGN);
bench.core.state.r[13] = TOP - 4;
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
let at = TOP - 0x70;
let state = &bench.core.state;
assert_eq!(
(state.r[13], state.r[14], state.control),
(at, 0xFFFF_FFE9, 0)
);
assert_eq!(
bench.word(at + 0x1C) & mask(xpsr::REALIGNED),
mask(xpsr::REALIGNED),
"aligned whatever STKALIGN says"
);
for index in 0..16 {
assert_eq!(bench.word(at + 0x20 + 4 * index), 0x100 + index);
}
assert_eq!(bench.word(at + 0x60), FPSCR_DN_FZ);
assert_eq!(bench.core.system.fpccr & mask(fpccr::LSPACT), 0);
clobber_fp(&mut bench);
bench.core.state.s[16] = 7;
bench.core.scs.set_line(0, false);
bench.core.exception_return(0xFFFF_FFE9);
let state = &bench.core.state;
assert_eq!((state.r[13], state.control), (TOP - 4, mask(control::FPCA)));
assert_eq!((state.s[15], state.fpscr), (0x10F, FPSCR_DN_FZ));
assert_eq!(state.s[16], 7, "S16 up isn't in the frame");
}
#[test]
fn a_lazy_frame_is_preserved_only_when_asked() {
let mut bench = processor();
with_fp_context(&mut bench, mask(fpccr::ASPEN) | mask(fpccr::LSPEN));
bench.core.state.control |= mask(control::NPRIV);
bench.core.scs.write(0xD24, mask(shcsr::BUSFAULTENA), NOW);
system_priority(&mut bench, BUS_FAULT, 0x40);
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
let at = TOP - EXTENDED;
assert_eq!(bench.core.state.r[13], at);
assert_eq!(bench.word(at + 0x20), 0, "room left and nothing stored");
assert_eq!(bench.core.system.fpcar, at + 0x20);
assert_eq!(
bench.core.system.fpccr,
mask(fpccr::ASPEN)
| mask(fpccr::LSPEN)
| mask(fpccr::LSPACT)
| mask(fpccr::USER)
| mask(fpccr::THREAD)
| mask(fpccr::HFRDY)
| mask(fpccr::BFRDY),
"MemManage is off and the monitor isn't on"
);
assert!(bench.core.preserve_fp_state(Position::Processor));
assert_eq!(bench.core.system.fpccr & mask(fpccr::LSPACT), 0);
assert_eq!(bench.word(at + 0x20), 0x100);
assert_eq!(bench.word(at + 0x60), FPSCR_DN_FZ);
clobber_fp(&mut bench);
bench.core.scs.set_line(0, false);
bench.core.exception_return(0xFFFF_FFE9);
let state = &bench.core.state;
assert_eq!(
(state.s[0], state.fpscr, state.r[13]),
(0x100, FPSCR_DN_FZ, TOP)
);
assert_eq!(state.control, mask(control::FPCA) | mask(control::NPRIV));
}
/// The lazy preservation an instruction in a block sets off makes its stores as that
/// instruction's accesses: at its count and address.
#[test]
fn a_lazy_preservation_in_a_block_is_its_instructions_access() {
let mut bench = processor();
with_fp_context(&mut bench, mask(fpccr::ASPEN) | mask(fpccr::LSPEN));
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
bench.core.system.fpcar = DEVICE;
let code = handler(16);
// NOP, NOP, then VMOV.F32 s0, s1, A7.7.237, which preserves first.
bench.place(code, &[0xBF00, 0xBF00, 0xEEB0, 0x0A60, SPIN]);
bench.core.state.r[15] = code;
let before = bench.core.retired();
bench.run(10);
let first = bench
.bus
.accesses
.first()
.expect("the preservation's stores");
assert_eq!(
(first.instruction_count, first.program_counter),
(before + 3, Some(u64::from(code + 4)))
);
assert_eq!(bench.core.state.r[15], code + 8, "and the block went on");
}
/// An exception's stacking into a word a debugger's watchpoint watches is the processor's own
/// access, which no watchpoint stops, so it neither halts the core nor faults, and the frame
/// goes where B1.5.6 puts it.
#[test]
fn stacking_into_a_watched_word_neither_halts_nor_faults() {
let mut bench = processor();
let word = TOP - BASIC;
bench.core.route_page(u64::from(word), true);
bench.bus.watched = Some((u64::from(word), AccessKind::Write));
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
assert_eq!(bench.core.state.ipsr, u32::from(IRQ0));
assert_eq!(bench.core.system.cfsr, 0, "no stacking error");
assert!(
bench.bus.accesses.iter().any(|access| {
(access.kind, access.address) == (AccessKind::Write, u64::from(word))
}),
"stacked through the bus"
);
assert_ne!(bench.run(1).0, Exit::Halted);
}
/// A lazy preservation a debugger's watchpoint stops the core before leaves its instruction
/// unrun, with nothing raised and `LSPACT` still set, so the instruction preserves the state
/// from its first word when it runs again.
#[test]
fn a_watched_lazy_preservation_halts_its_instruction_and_starts_over() {
let mut bench = processor();
with_fp_context(&mut bench, mask(fpccr::ASPEN) | mask(fpccr::LSPEN));
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
bench.core.system.fpcar = DEVICE;
let code = handler(16);
// VMOV.F32 s0, s1, A7.7.237, which preserves first.
bench.place(code, &[0xEEB0, 0x0A60, SPIN]);
bench.core.state.r[15] = code;
bench.bus.watched = Some((u64::from(DEVICE + 4), AccessKind::Write));
let cfsr = bench.core.system.cfsr;
assert_eq!(bench.run(10), (Exit::Halted, 0));
assert_eq!(bench.core.state.r[15], code, "held at the instruction");
assert_eq!(bench.core.system.cfsr, cfsr, "nothing raised");
let lspact = mask(fpccr::LSPACT);
assert_eq!(bench.core.system.fpccr & lspact, lspact, "still to do");
bench.bus.watched = None;
assert_eq!(bench.run(1).1, 1);
assert_eq!(bench.core.system.fpccr & lspact, 0);
let first: Vec<u64> = bench
.bus
.accesses
.iter()
.map(|access| access.address)
.filter(|&address| address == u64::from(DEVICE))
.collect();
assert_eq!(first.len(), 2, "the first word stored again");
}
/// A floating-point instruction its IT condition skips checks nothing, so the next one
/// still makes the lazy preservation.
#[test]
fn a_skipped_floating_point_instruction_leaves_the_next_its_check() {
let mut bench = processor();
with_fp_context(&mut bench, mask(fpccr::ASPEN) | mask(fpccr::LSPEN));
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
bench.core.system.fpcar = DEVICE;
let code = handler(16);
// CMP r0, r0, IT NE, VMOVNE.F32 s0, s1, then VMOV.F32 s2, s3, A7.7.237.
bench.place(
code,
&[0x4280, 0xBF18, 0xEEB0, 0x0A60, 0xEEB0, 0x1A61, SPIN],
);
bench.core.state.r[15] = code;
bench.run(10);
let first = bench
.bus
.accesses
.first()
.expect("the preservation's stores");
assert_eq!(first.program_counter, Some(u64::from(code + 8)));
}
#[test]
fn a_lazy_frame_never_preserved_keeps_the_registers() {
let mut bench = processor();
with_fp_context(&mut bench, mask(fpccr::ASPEN) | mask(fpccr::LSPEN));
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
bench.core.scs.set_line(0, false);
bench.core.exception_return(0xFFFF_FFE9);
let state = &bench.core.state;
assert_eq!(
(state.s[0], state.fpscr, state.r[13], state.control),
(0x100, FPSCR_DN_FZ, TOP, mask(control::FPCA))
);
assert_eq!(bench.core.system.fpccr & mask(fpccr::LSPACT), 0);
}
#[test]
fn stacking_fp_state_with_cpacr_off_is_nocp() {
let mut bench = processor();
with_fp_context(&mut bench, mask(fpccr::ASPEN));
bench.core.system.cpacr = 0;
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[14], state.r[13]),
(u32::from(HARD_FAULT), 0xFFFF_FFE9, TOP - EXTENDED)
);
assert_eq!(bench.core.system.cfsr, mask(cfsr::NOCP));
assert_eq!(scs(&mut bench, 0x200), 1, "IRQ0 waits");
}
#[test]
fn a_fault_while_preserving_is_judged_by_the_ready_bits() {
let mut bench = processor();
bench.core.scs.write(0xD24, mask(shcsr::BUSFAULTENA), NOW);
let fpccr = mask(fpccr::LSPACT) | mask(fpccr::HFRDY) | mask(fpccr::BFRDY);
bench.core.system.fpccr = fpccr;
assert!(!bench.core.preserving_failed(Refused::Bus(0), fpccr));
assert_eq!(
bench.core.system.fpccr & mask(fpccr::LSPACT),
mask(fpccr::LSPACT),
"it starts over after the handler"
);
assert_eq!(bench.core.system.cfsr, mask(cfsr::LSPERR));
take(&mut bench);
assert_eq!(bench.core.state.ipsr, u32::from(BUS_FAULT));
let mut bench = processor();
bench.core.system.fpccr = mask(fpccr::LSPACT) | mask(fpccr::HFRDY);
assert!(
!bench
.core
.preserving_failed(Refused::Mpu(0), mask(fpccr::LSPACT) | mask(fpccr::HFRDY))
);
assert_eq!(bench.core.system.cfsr, mask(cfsr::MLSPERR));
assert_eq!(
hfsr(&mut bench),
mask(hfsr::FORCED),
"MemManage wasn't ready"
);
take(&mut bench);
assert_eq!(bench.core.state.ipsr, u32::from(HARD_FAULT));
let mut bench = processor();
bench.core.state.primask = 1;
bench.core.scs.write(0xD24, mask(shcsr::BUSFAULTENA), NOW);
bench.core.system.fpccr = fpccr;
assert!(
bench.core.preserving_failed(Refused::Bus(0), fpccr),
"BusFault can't preempt now, so it waits and the instruction goes on"
);
assert_eq!(bench.core.system.fpccr & mask(fpccr::LSPACT), 0);
let mut bench = processor();
bench.core.scs.pend(HARD_FAULT);
take(&mut bench);
bench.core.system.fpccr = mask(fpccr::LSPACT);
assert!(
!bench
.core
.preserving_failed(Refused::Bus(0), mask(fpccr::LSPACT))
);
let state = &bench.core.state;
assert_eq!((state.locked_up, state.r[15]), (1, LOCKUP));
assert_eq!(
hfsr(&mut bench),
mask(hfsr::FORCED),
"BusFault wasn't ready"
);
}
/// B1.6.3's `TakePreserveFPException()` locks up with `BranchWritePC(0xFFFFFFFE)`, so the
/// floating-point instruction that asked for the preservation isn't where it locks up, and
/// the BusFault it escalated on the way still sets `HFSR.FORCED`.
#[test]
fn a_lazy_preservation_that_locks_up_does_so_at_the_lockup_address() {
const VMOV_S0_1: [u16; 2] = [0xEEB7, 0x0A00];
let mut bench = processor();
bench.place(handler(HARD_FAULT), &VMOV_S0_1);
bench.core.system.cpacr = mask(cpacr::CP10) | mask(cpacr::CP11);
bench.core.scs.pend(HARD_FAULT);
take(&mut bench);
bench.core.system.fpccr |= mask(fpccr::LSPACT);
bench.core.system.fpcar = DEVICE;
bench.bus.fails_from = Some(u64::from(DEVICE));
let exit = loop {
match bench.run(16) {
(Exit::Returned, _) => {}
(exit, ran) => break (exit, ran),
}
};
assert_eq!(exit, (Exit::Lockup, 0));
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[15], state.locked_up),
(u32::from(HARD_FAULT), LOCKUP, 1)
);
assert_eq!(bench.core.system.cfsr, mask(cfsr::LSPERR));
assert_eq!(hfsr(&mut bench), mask(hfsr::FORCED));
}
#[test]
fn wfi_wakes_through_primask_and_wfe_does_not() {
let mut bench = processor();
bench.core.state.primask = 1;
bench.core.sleep(false);
assert_eq!(bench.run(0), (Exit::Waiting, 0));
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
let state = &bench.core.state;
assert_eq!(
(state.sleeping, state.ipsr),
(0, 0),
"awake, and PRIMASK holds it off"
);
bench.core.sleep(true);
assert_eq!(bench.run(0), (Exit::Waiting, 0), "WFE minds PRIMASK");
bench.core.state.primask = 0;
take(&mut bench);
assert_eq!((bench.core.state.sleeping, bench.core.state.ipsr), (0, 16));
let mut bench = processor();
interrupt(&mut bench, 0, 0x80);
bench.core.state.primask = 1;
bench.core.sleep(false);
assert_eq!(
bench.core.state.sleeping, 0,
"something would already wake it"
);
}
#[test]
fn wfe_with_the_event_register_set_goes_on() {
let mut bench = processor();
bench.core.state.event = 1;
bench.core.sleep(true);
assert_eq!((bench.core.state.sleeping, bench.core.state.event), (0, 0));
bench.core.sleep(true);
assert_eq!(bench.run(0), (Exit::Waiting, 0));
}
/// `SEV` on another processor or `SEVONPEND` sets the event register while `WFE` sleeps, and
/// that wakes it with a masked interrupt left pending.
#[test]
fn wfe_wakes_when_the_event_register_is_set_while_it_sleeps() {
let mut bench = processor();
bench.core.state.primask = 1;
interrupt(&mut bench, 0, 0x80);
bench.core.sleep(true);
assert_eq!(bench.run(0), (Exit::Waiting, 0), "the interrupt's masked");
bench.core.state.event = 1;
take(&mut bench);
let state = &bench.core.state;
assert_eq!((state.sleeping, state.event, state.ipsr), (0, 0, 0));
assert_eq!(scs(&mut bench, 0x200), 1, "IRQ0 still pending");
}
#[test]
fn sleep_on_exit_sleeps_and_tail_chains() {
let mut bench = processor();
bench.core.system.sleep_on_exit = true;
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
bench.core.scs.set_line(0, false);
bench.core.exception_return(0xFFFF_FFF9);
assert_eq!(bench.run(0), (Exit::Waiting, 0));
assert_eq!(
bench.core.state.r[13],
TOP - BASIC,
"the frame's still there"
);
interrupt(&mut bench, 1, 0x80);
take(&mut bench);
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[13], state.r[14], state.sleeping),
(17, TOP - BASIC, 0xFFFF_FFF9, 0)
);
bench.core.scs.set_line(1, false);
bench.core.system.sleep_on_exit = false;
bench.core.exception_return(0xFFFF_FFF9);
let state = &bench.core.state;
assert_eq!((state.ipsr, state.r[15]), (0, THREAD_CODE));
}
#[test]
fn sleep_on_exit_woken_under_primask_finishes_the_return() {
let mut bench = processor();
bench.core.system.sleep_on_exit = true;
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
bench.core.scs.set_line(0, false);
bench.core.state.primask = 1;
bench.core.exception_return(0xFFFF_FFF9);
assert_eq!(bench.core.state.sleeping, ON_EXIT);
interrupt(&mut bench, 1, 0x80);
take(&mut bench);
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[13], state.r[15], state.sleeping),
(0, TOP, THREAD_CODE, 0)
);
}
/// HardFault's vector lacks the bit too, so the run never reaches an instruction it would
/// translate.
#[test]
fn a_vector_without_the_thumb_bit_faults_in_the_handler() {
let mut bench = processor();
for number in [IRQ0, HARD_FAULT] {
bench.write(4 * u32::from(number), &handler(number).to_le_bytes());
}
interrupt(&mut bench, 0, 0x80);
take(&mut bench);
let state = &bench.core.state;
assert_eq!((state.t, state.r[15]), (0, handler(IRQ0)));
assert_eq!(bench.run(1), (Exit::Lockup, 0));
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[15], state.t),
(u32::from(HARD_FAULT), handler(HARD_FAULT), 0)
);
assert_eq!(bench.core.system.cfsr, mask(cfsr::INVSTATE));
assert_eq!(frame(&bench, TOP - 2 * BASIC)[6], handler(IRQ0));
}
/// A2.3.1 and A7.7.19: a BLX to an address with bit 0 clear is INVSTATE on the instruction
/// there, even once that block is translated and the BLX's exit could hand over to it.
#[test]
fn a_blx_that_clears_t_faults_though_its_target_is_translated() {
// blx r1, blx r2, six nops, bx lr: the first BLX translates the block at the BX.
let code = [
0x4788, 0x4790, 0xBF00, 0xBF00, 0xBF00, 0xBF00, 0xBF00, 0xBF00, 0x4770,
];
let target = THREAD_CODE + 0x10;
let (bench, _) = run_into(&code, USAGE_FAULT, |bench| {
bench.core.state.r[1] = target | 1;
bench.core.state.r[2] = target;
});
assert_eq!(bench.core.system.cfsr, mask(cfsr::INVSTATE));
assert_eq!(
frame(&bench, TOP - BASIC)[6],
target,
"stacked at the BX, unrun"
);
}
/// Runs `code` from [`THREAD_CODE`] after `prepare`, with MemManage, BusFault and UsageFault
/// enabled and `number`'s handler a branch to itself, until that handler waits. Answers the
/// instructions retired on the way, and checks the handler is where the processor waits.
fn run_into(code: &[u16], number: u16, prepare: impl FnOnce(&mut Bench)) -> (Bench, u64) {
let mut bench = processor();
bench.place(THREAD_CODE, code);
bench.place(handler(number), &[SPIN]);
bench.core.scs.write(
0xD24,
mask(shcsr::MEMFAULTENA) | mask(shcsr::BUSFAULTENA) | mask(shcsr::USGFAULTENA),
NOW,
);
prepare(&mut bench);
let mut retired = 0;
loop {
let (exit, ran) = bench.run(16);
retired += ran;
if exit != Exit::Returned {
assert_eq!(exit, Exit::Waiting);
break;
}
}
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[15], state.t),
(u32::from(number), handler(number), 1),
"in the handler"
);
(bench, retired)
}
/// A7.7.191 and B1.5.14: an UNDEFINED instruction is UsageFault `UNDEFINSTR`, stacked at itself
/// and not retired.
#[test]
fn an_undefined_instruction_runs_into_usagefault() {
let (bench, retired) = run_into(&[UDF], USAGE_FAULT, |_| {});
assert_eq!(bench.core.system.cfsr, mask(cfsr::UNDEFINSTR));
assert_eq!(frame(&bench, TOP - BASIC)[6], THREAD_CODE);
assert_eq!(retired, 1, "only the handler's branch");
}
/// A7.7.20 and B1.5.8: `BX LR` in Handler mode with `EXC_RETURN` in `LR` returns from the
/// exception, putting back the registers the frame holds.
#[test]
fn bx_in_handler_mode_returns_from_the_exception() {
const MOVS_R0_0: u16 = 0x2000;
const BX_LR: u16 = 0x4770;
let mut bench = processor();
bench.place(THREAD_CODE, &[SPIN]);
bench.place(handler(IRQ0), &[MOVS_R0_0, BX_LR]);
bench.core.state.r[0] = 0x55;
bench.core.scs.write(0x100, 1, NOW);
bench.core.scs.pend(IRQ0);
assert_eq!(bench.run(16), (Exit::Waiting, 3));
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[15], state.r[13], state.r[0]),
(0, THREAD_CODE, TOP, 0x55)
);
assert_eq!(frame(&bench, TOP - BASIC)[0], 0x55, "R0 came off the frame");
assert_eq!((active(&bench), bench.core.system.cfsr), ((0, true), 0));
}
/// B1.5.10: ICI bits an exception return brings back are INVSTATE on an instruction that isn't
/// a Load Multiple or Store Multiple, stacked at that instruction, which never runs.
#[test]
fn ici_bits_returned_to_anything_but_a_multiple_are_invstate() {
const MOVS_R0_1: u16 = 0x2001;
// ldr r1, [sp, #28], orr.w r1, r1, #0x1000, str r1, [sp, #28], bx lr: the stacked xPSR's
// ICI names r1.
const SET_ICI: [u16; 5] = [0x9907, 0xF441, 0x5180, 0x9107, 0x4770];
let mut bench = processor();
bench.place(THREAD_CODE, &[MOVS_R0_1, SPIN]);
bench.place(handler(IRQ0), &SET_ICI);
bench.place(handler(USAGE_FAULT), &[SPIN]);
bench.core.scs.write(0xD24, mask(shcsr::USGFAULTENA), NOW);
bench.core.scs.write(0x100, 1, NOW);
bench.core.scs.pend(IRQ0);
loop {
let (exit, _) = bench.run(16);
if exit != Exit::Returned {
assert_eq!(exit, Exit::Waiting);
break;
}
}
assert_eq!(bench.core.state.ipsr, u32::from(USAGE_FAULT));
assert_eq!(bench.core.system.cfsr, mask(cfsr::INVSTATE));
assert_eq!(bench.core.state.r[0], 0, "the MOVS never ran");
assert_eq!(frame(&bench, TOP - BASIC)[6], THREAD_CODE);
}
/// B1.5.10: ICI bits an exception return brings back to a Load Multiple are continued, which
/// this core does by running it again from its first register, from the base it had, with
/// `ITSTATE` 0. The bits name r2, so a core that went on from there would leave r1 alone.
#[test]
fn ici_bits_returned_to_a_load_multiple_run_it_from_its_first_register() {
const LDM_R0_WRITEBACK_R1_R2: u16 = 0xC806;
// ldr r1, [sp, #28], orr.w r1, r1, #0x2000, str r1, [sp, #28], bx lr: the stacked xPSR's
// ICI names r2.
const SET_ICI: [u16; 5] = [0x9907, 0xF441, 0x5100, 0x9107, 0x4770];
const DATA: u32 = RAM + 0x100;
let mut bench = processor();
bench.place(THREAD_CODE, &[LDM_R0_WRITEBACK_R1_R2, SPIN]);
bench.place(handler(IRQ0), &SET_ICI);
bench.place(DATA, &[0x1111, 0x1111, 0x2222, 0x2222]);
bench.core.state.r[0] = DATA;
bench.core.scs.write(0x100, 1, NOW);
bench.core.scs.pend(IRQ0);
loop {
let (exit, _) = bench.run(16);
if exit != Exit::Returned {
assert_eq!(exit, Exit::Waiting);
break;
}
}
assert_eq!(
bench.core.state.ipsr, 0,
"back in Thread mode, with no fault taken"
);
assert_eq!(bench.core.system.cfsr, 0);
assert_eq!(
bench.core.state.r[..3],
[DATA + 8, 0x1111_1111, 0x2222_2222]
);
assert_eq!(bench.core.state.it, 0);
}
/// A7.7.125 and B3.2.8: `SDIV` by zero with `CCR.DIV_0_TRP` set is UsageFault `DIVBYZERO`, and
/// leaves its destination alone.
#[test]
fn sdiv_by_zero_with_div_0_trp_runs_into_usagefault() {
const SDIV_R0_R0_R1: [u16; 2] = [0xFB90, 0xF0F1];
let (bench, retired) = run_into(&SDIV_R0_R0_R1, USAGE_FAULT, |bench| {
bench.core.system.ccr |= mask(ccr::DIV_0_TRP);
bench.core.state.r[..2].copy_from_slice(&[7, 0]);
});
assert_eq!(bench.core.system.cfsr, mask(cfsr::DIVBYZERO));
assert_eq!(bench.core.state.r[0], 7);
assert_eq!(frame(&bench, TOP - BASIC)[6], THREAD_CODE);
assert_eq!(retired, 1);
}
/// B1.6.3 and B3.2.20: a floating-point instruction with `CPACR` refusing CP10 is UsageFault
/// `NOCP`, and never makes a floating-point context.
#[test]
fn a_floating_point_instruction_with_cpacr_off_runs_into_usagefault() {
const VADD_S0_S0_S0: [u16; 2] = [0xEE30, 0x0A00];
let (bench, _) = run_into(&VADD_S0_S0_S0, USAGE_FAULT, |_| {});
assert_eq!(bench.core.system.cfsr, mask(cfsr::NOCP));
assert_eq!(bench.core.state.r[14], 0xFFFF_FFF9, "a basic frame");
assert_eq!(frame(&bench, TOP - BASIC)[6], THREAD_CODE);
}
/// A3.2.1 and A7.7.49: `LDRD` from an address that isn't word-aligned is UsageFault
/// `UNALIGNED` whatever `CCR.UNALIGN_TRP` says.
#[test]
fn an_unaligned_ldrd_runs_into_usagefault() {
const LDRD_R0_R1_R2: [u16; 2] = [0xE9D2, 0x0100];
let (bench, _) = run_into(&LDRD_R0_R1_R2, USAGE_FAULT, |bench| {
bench.core.state.r[..3].copy_from_slice(&[1, 2, RAM + 2]);
});
assert_eq!(bench.core.system.cfsr, mask(cfsr::UNALIGNED));
assert_eq!(bench.core.state.r[..2], [1, 2]);
assert_eq!(frame(&bench, TOP - BASIC)[6], THREAD_CODE);
}
/// B1.5.14 and B3.2.18: a load the bus fails is a precise BusFault with `BFAR` the address,
/// stacked at the load.
#[test]
fn a_load_the_bus_fails_runs_into_a_precise_busfault() {
let (bench, _) = run_into(&[LDR], BUS_FAULT, |bench| {
bench.bus.fails_from = Some(u64::from(DEVICE));
bench.core.state.r[..2].copy_from_slice(&[9, DEVICE + 0x10]);
});
let system = &bench.core.system;
assert_eq!(
(system.cfsr, system.bfar),
(mask(cfsr::PRECISERR) | mask(cfsr::BFARVALID), DEVICE + 0x10)
);
assert_eq!(bench.core.state.r[0], 9);
assert_eq!(frame(&bench, TOP - BASIC)[6], THREAD_CODE);
}
/// A7.7.17 and C1.6.5: `BKPT` with the monitor off is HardFault with `HFSR.DEBUGEVT`, stacked
/// at itself and not retired.
#[test]
fn bkpt_runs_into_hardfault_with_debugevt() {
let (mut bench, retired) = run_into(&[0xBE00], HARD_FAULT, |_| {});
assert_eq!(
(hfsr(&mut bench), bench.core.system.cfsr),
(mask(hfsr::DEBUGEVT), 0)
);
assert_eq!(frame(&bench, TOP - BASIC)[6], THREAD_CODE);
assert_eq!(retired, 1);
}
/// A7.7.175 and B1.5.6: `SVC` retires and is SVCall, stacked at the instruction after it.
#[test]
fn svc_runs_into_svcall_past_itself() {
let (mut bench, retired) = run_into(&[0xDF00], SV_CALL, |_| {});
assert_eq!((hfsr(&mut bench), bench.core.system.cfsr), (0, 0));
assert_eq!(frame(&bench, TOP - BASIC)[6], THREAD_CODE + 2);
assert_eq!(retired, 2, "the SVC and the handler's branch");
}
/// B1.5.7 and B1.6.3: Thread mode's first floating-point instruction sets `CONTROL.FPCA`, so
/// its `SVC` leaves room for the state, which the handler's own first floating-point
/// instruction preserves before it changes anything. The return puts `S0` to `S15` and `FPSCR`
/// back as the thread had them.
#[test]
fn a_lazy_save_on_a_context_switch_puts_the_thread_state_back() {
const VMOV_S0_1: [u16; 2] = [0xEEB7, 0x0A00];
const VMOV_S0_2: [u16; 2] = [0xEEB0, 0x0A00];
const VMOV_S15_2: [u16; 2] = [0xEEF0, 0x7A00];
const VMSR_FPSCR_R0: [u16; 2] = [0xEEE1, 0x0A10];
const BX_LR: u16 = 0x4770;
const ONE: u32 = 0x3F80_0000;
const THREAD_FPSCR: u32 = 0xA000_0011;
let mut bench = processor();
bench.place(THREAD_CODE, &[VMOV_S0_1[0], VMOV_S0_1[1], 0xDF00, SPIN]);
let handler_code: Vec<u16> = [VMOV_S0_2, VMOV_S15_2, VMSR_FPSCR_R0]
.concat()
.into_iter()
.chain([BX_LR])
.collect();
bench.place(handler(SV_CALL), &handler_code);
let core = &mut bench.core;
core.system.cpacr = mask(cpacr::CP10) | mask(cpacr::CP11);
for (value, register) in (0x100..).zip(core.state.s.iter_mut()) {
*register = value;
}
core.state.fpscr = THREAD_FPSCR;
assert_eq!(bench.run(32), (Exit::Waiting, 7));
let at = TOP - EXTENDED;
assert_eq!(bench.word(at + FP_STATE), ONE, "preserved lazily");
assert_eq!(bench.word(at + FP_STATE + 0x40), THREAD_FPSCR);
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[15], state.r[13]),
(0, THREAD_CODE + 6, TOP)
);
assert_eq!(state.s[0], ONE);
assert_eq!(state.s[1..16], (0x101..0x110).collect::<Vec<_>>()[..]);
assert_eq!(state.fpscr, THREAD_FPSCR);
assert_eq!(state.control & mask(control::FPCA), mask(control::FPCA));
assert_eq!(bench.core.system.fpccr & mask(fpccr::LSPACT), 0);
}
/// A debugger's step over an instruction that faults without retiring stops before the
/// handler's first instruction, C1.5, having run nothing.
#[test]
fn a_step_over_a_fault_stops_at_its_handler() {
let mut bench = processor();
bench.core.hold_interrupts(true);
assert_eq!(bench.run(1), (Exit::Budget, 0));
assert_eq!(bench.core.state.ipsr, u32::from(HARD_FAULT));
assert_eq!(bench.core.state.r[15], handler(HARD_FAULT));
}
/// A debugger's step over a branch to itself runs it, and doesn't wait for an interrupt the
/// step holds off.
#[test]
fn a_step_over_a_branch_to_itself_runs_it() {
let mut bench = processor();
bench.place(THREAD_CODE, &[0xE7FE]);
bench.core.hold_interrupts(true);
for _ in 0..3 {
assert_eq!(bench.run(1), (Exit::Budget, 1));
assert_eq!(bench.core.state.sleeping, 0);
assert_eq!(bench.core.state.r[15], THREAD_CODE);
}
}
/// A breakpoint two asked for stays until both take it back, and one passed runs its
/// instruction once.
#[test]
fn a_breakpoint_stays_while_anything_asks_for_it() {
let mut bench = processor();
bench.place(THREAD_CODE, &[0xBF00, 0xBF00]);
let at = u64::from(THREAD_CODE);
bench.core.add_breakpoint(at);
bench.core.add_breakpoint(at);
bench.core.remove_breakpoint(at);
assert_eq!(bench.run(2), (Exit::Breakpoint, 0));
bench.core.pass_breakpoint(at);
assert_eq!(bench.run(1), (Exit::Budget, 1));
bench.core.state.r[15] = THREAD_CODE;
assert_eq!(bench.run(2), (Exit::Breakpoint, 0), "passed once only");
bench.core.remove_breakpoint(at);
assert_eq!(bench.run(2), (Exit::Budget, 2));
}
/// Every `DEMCR` catch bit, C1.6.5.
fn every_catch() -> u32 {
CATCH_WORDS
.iter()
.fold(0, |all, &(_, catch)| all | mask(catch))
}
/// `DFSR`, as a debugger reads it, C1.6.1.
fn dfsr(bench: &mut Bench) -> u32 {
scs(bench, 0xD30)
}
/// C1.5.2 and B1.5.14: with halting debug on and `VC_STATERR` set, an UNDEFINED instruction
/// takes UsageFault and halts the core before the handler's first instruction, with the frame
/// stacked, `CFSR.UNDEFINSTR` and `DFSR.VCATCH` set, and nothing of the handler run. The next
/// stretch runs the handler. Another catch bit catches nothing here.
#[test]
fn a_usagefault_under_vector_catch_halts_at_its_handler_with_dfsr_vcatch() {
let mut bench = processor();
bench.place(handler(USAGE_FAULT), &[SPIN]);
bench.core.scs.write(0xD24, mask(shcsr::USGFAULTENA), NOW);
bench.core.attach_debugger(true);
bench.core.scs.set_vector_catch(mask(demcr::VC_STATERR));
assert_eq!(bench.run(16), (Exit::Halted, 0));
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[15]),
(u32::from(USAGE_FAULT), handler(USAGE_FAULT))
);
assert_eq!(frame(&bench, TOP - BASIC)[6], THREAD_CODE);
assert_eq!(bench.core.system.cfsr, mask(cfsr::UNDEFINSTR));
assert_eq!(dfsr(&mut bench), mask(dfsr::VCATCH));
assert_eq!(bench.run(16), (Exit::Waiting, 1), "the handler's branch");
let mut bench = processor();
bench.place(handler(USAGE_FAULT), &[SPIN]);
bench.core.scs.write(0xD24, mask(shcsr::USGFAULTENA), NOW);
bench.core.attach_debugger(true);
bench.core.scs.set_vector_catch(mask(demcr::VC_CHKERR));
assert_eq!(bench.run(16), (Exit::Waiting, 1));
assert_eq!(dfsr(&mut bench), 0);
}
/// Raises the fault `number` is with `status` set in `CFSR`, as the core raises one: taken as
/// itself when it's enabled, and as HardFault when not, B1.5.4.
fn raised(bench: &mut Bench, number: u16, status: &FieldSpec) {
bench.core.system.cfsr |= mask(status);
let masks = bench.core.masks();
bench.core.pend_fault(number, masks);
}
/// MemManage, BusFault and UsageFault enabled, B3.2.13.
fn enabled(bench: &mut Bench) {
let all = mask(shcsr::MEMFAULTENA) | mask(shcsr::BUSFAULTENA) | mask(shcsr::USGFAULTENA);
bench.core.scs.write(0xD24, all, NOW);
}
/// An interrupt whose vector the bus fails.
fn bad_vector(bench: &mut Bench) {
bench.core.system.vtor = DEVICE;
bench.bus.fails_from = Some(u64::from(DEVICE + 4 * u32::from(IRQ0)));
bench.bus.answer = u64::from(handler(HARD_FAULT) | 1);
interrupt(bench, 0, 0x80);
}
/// An interrupt stacking onto a main stack the bus fails.
fn bad_stack(bench: &mut Bench) {
bench.bus.fails_from = Some(u64::from(DEVICE));
bench.core.state.r[13] = DEVICE + 0x100;
system_priority(bench, BUS_FAULT, 0x00);
interrupt(bench, 0, 0x80);
}
/// A fault, as what it is, the exception it's taken as, what raises it, and the words for the
/// catch bits that catch it.
type Caught = (&'static str, u16, fn(&mut Bench), &'static [&'static str]);
/// Each fault B1.5.14 lists, each way it's taken, and the bits C1.6.5 catches it with: `hard` a
/// HardFault, `bus` a BusFault and `mm` a MemManage, whatever raised it, `int` a fault on
/// exception entry or return, a vector read's included, whatever it's taken as, and the
/// UsageFault bits each their own causes.
const CAUGHT: [Caught; 24] = [
(
"an UNDEFINED instruction, UsageFault off",
HARD_FAULT,
|bench| fault(bench, THREAD_CODE, Fault::Undefined),
&["hard"],
),
(
"a BKPT before halting debug was on",
HARD_FAULT,
|bench| fault(bench, THREAD_CODE, Fault::Breakpoint),
&["hard"],
),
(
"a vector that can't be read",
HARD_FAULT,
bad_vector,
&["hard", "int"],
),
(
"a frame that can't be stacked, BusFault off",
HARD_FAULT,
bad_stack,
&["hard", "int"],
),
(
"MSTKERR, MemManage off",
HARD_FAULT,
|bench| raised(bench, MEM_MANAGE, cfsr::MSTKERR),
&["hard", "int"],
),
(
"UNSTKERR, BusFault off",
HARD_FAULT,
|bench| raised(bench, BUS_FAULT, cfsr::UNSTKERR),
&["hard", "int"],
),
(
"MUNSTKERR, MemManage off",
HARD_FAULT,
|bench| raised(bench, MEM_MANAGE, cfsr::MUNSTKERR),
&["hard", "int"],
),
(
"a frame that can't be stacked",
BUS_FAULT,
|bench| {
enabled(bench);
bad_stack(bench);
},
&["int", "bus"],
),
(
"UNSTKERR",
BUS_FAULT,
|bench| {
enabled(bench);
raised(bench, BUS_FAULT, cfsr::UNSTKERR);
},
&["int", "bus"],
),
(
"a fetch nothing answers",
BUS_FAULT,
|bench| {
enabled(bench);
fault(bench, 0x6000_0000, Fault::InstructionFetch);
},
&["bus"],
),
(
"a load the bus fails",
BUS_FAULT,
|bench| {
enabled(bench);
bench.place(THREAD_CODE, &[LDR]);
bench.bus.fails_from = Some(u64::from(DEVICE));
bench.core.state.r[1] = DEVICE;
},
&["bus"],
),
(
"LSPERR",
BUS_FAULT,
|bench| {
enabled(bench);
raised(bench, BUS_FAULT, cfsr::LSPERR);
},
&["bus"],
),
(
"MSTKERR",
MEM_MANAGE,
|bench| {
enabled(bench);
raised(bench, MEM_MANAGE, cfsr::MSTKERR);
},
&["int", "mm"],
),
(
"MUNSTKERR",
MEM_MANAGE,
|bench| {
enabled(bench);
raised(bench, MEM_MANAGE, cfsr::MUNSTKERR);
},
&["int", "mm"],
),
(
"a load the MPU refuses",
MEM_MANAGE,
|bench| {
enabled(bench);
bench.core.raise_data_fault(Refused::Mpu(RAM));
},
&["mm"],
),
// The default map's peripheral region is execute-never, B3.5.1.
(
"a fetch from execute-never memory",
MEM_MANAGE,
|bench| {
enabled(bench);
bench.core.state.r[15] = DEVICE;
},
&["mm"],
),
(
"MLSPERR",
MEM_MANAGE,
|bench| {
enabled(bench);
raised(bench, MEM_MANAGE, cfsr::MLSPERR);
},
&["mm"],
),
(
"a floating-point instruction, CPACR off",
USAGE_FAULT,
|bench| {
enabled(bench);
bench.place(THREAD_CODE, &[0xEE30, 0x0A00]); // VADD.F32 s0, s0, s0
},
&["nocp"],
),
("an UNDEFINED instruction", USAGE_FAULT, enabled, &["state"]),
(
"EPSR.T clear",
USAGE_FAULT,
|bench| {
enabled(bench);
fault(bench, THREAD_CODE, Fault::InvalidState);
},
&["state"],
),
(
"INVPC",
USAGE_FAULT,
|bench| {
enabled(bench);
raised(bench, USAGE_FAULT, cfsr::INVPC);
},
&["int", "state"],
),
(
"INVPC, UsageFault off",
HARD_FAULT,
|bench| raised(bench, USAGE_FAULT, cfsr::INVPC),
&["hard", "int"],
),
(
"an unaligned LDRD",
USAGE_FAULT,
|bench| {
enabled(bench);
bench.place(THREAD_CODE, &[0xE9D2, 0x0100]); // LDRD r0, r1, [r2]
bench.core.state.r[2] = RAM + 2;
},
&["check"],
),
(
"a divide by zero",
USAGE_FAULT,
|bench| {
enabled(bench);
fault(bench, THREAD_CODE, Fault::DivideByZero);
},
&["check"],
),
];
/// C1.6.5: each fault in [`CAUGHT`], run into its handler with each catch bit set alone, is
/// caught by the bits it gives and by no other, halting the core before the handler's first
/// instruction, and runs the handler otherwise. Without halting debug nothing catches it.
#[test]
fn each_fault_is_caught_by_the_bits_c1_6_5_gives_it() {
for (what, number, raise, expected) in CAUGHT {
let run = |debugger: bool, catches: u32| {
let mut bench = processor();
bench.place(handler(number), &[SPIN]);
raise(&mut bench);
bench.core.attach_debugger(debugger);
bench.core.scs.set_vector_catch(catches);
let mut retired = 0;
let exit = loop {
let (exit, ran) = bench.run(16);
retired += ran;
if exit != Exit::Returned {
break exit;
}
};
let state = &bench.core.state;
assert_eq!(
(state.ipsr, state.r[15]),
(u32::from(number), handler(number)),
"{what} in its handler"
);
let caught = (exit, retired, dfsr(&mut bench));
if caught == (Exit::Halted, 0, mask(dfsr::VCATCH)) {
return true;
}
assert_eq!(caught, (Exit::Waiting, 1, 0), "{what} runs the handler");
false
};
let caught: Vec<&str> = CATCH_WORDS
.iter()
.filter(|&&(_, catch)| run(true, mask(catch)))
.map(|&(word, _)| word)
.collect();
assert_eq!(caught, expected, "{what}");
assert!(!run(false, every_catch()), "{what} without halting debug");
}
}
/// C1.5.2 halts a caught fault on its handler's first instruction. An interrupt that pends
/// while the core takes it, as a model raises it while the frame goes onto the stack, doesn't
/// arrive late, since the core nests rather than late-arrives, B1.5.11, so it waits for the halt
/// and is taken once the core runs on.
#[test]
fn an_interrupt_arriving_as_a_caught_fault_is_taken_waits_for_the_halt() {
let mut bench = processor();
bench.place(handler(USAGE_FAULT), &[SPIN]);
bench.place(handler(IRQ0), &[SPIN]);
enabled(&mut bench);
system_priority(&mut bench, USAGE_FAULT, 0x80);
bench.core.scs.write(0x100, 1, NOW);
assert!(bench.core.scs.write_priority_byte(0x400, 0x00));
bench.core.route_page(u64::from(TOP - 4), true);
bench.bus.on_access = vec![Queued::Line(0, true)];
bench.core.attach_debugger(true);
bench.core.scs.set_vector_catch(mask(demcr::VC_STATERR));
assert_eq!(bench.run(16), (Exit::Halted, 0));
assert_eq!(
(bench.core.state.ipsr, bench.core.state.r[15]),
(u32::from(USAGE_FAULT), handler(USAGE_FAULT))
);
assert_eq!(dfsr(&mut bench), mask(dfsr::VCATCH));
assert_eq!(scs(&mut bench, 0x200), 1, "IRQ0 pending");
// Its frame goes to the bus as well, which the core returns after.
while bench.run(16).0 == Exit::Returned {}
assert_eq!(
(bench.core.state.ipsr, bench.core.state.r[15]),
(u32::from(IRQ0), handler(IRQ0)),
"then IRQ0 preempts the handler"
);
}
/// `monitor vector-catch` sets the catch bits it names and only those, `all` and `none` as
/// they say, leaves `DEMCR`'s other bits, and says what's caught. A word it doesn't know, or
/// `all` or `none` with others, changes nothing.
#[test]
fn monitor_vector_catch_names_demcrs_catch_bits() {
let mut bench = processor();
let demcr = |bench: &mut Bench| scs(bench, DEMCR);
bench.core.scs.write(DEMCR, mask(demcr::TRCENA), NOW);
let mut said = |command: &str| bench.core.monitor_command(command).unwrap();
assert_eq!(said("vector-catch"), "catching nothing\n");
assert_eq!(said("vector-catch hard bus"), "catching hard bus\n");
assert_eq!(said("vector-catch"), "catching hard bus\n");
let refused = |word: &str| {
format!(
"vector-catch takes all or none alone, or any of hard, int, bus, state, check, nocp, mm, reset, and not \"{word}\" there\n"
)
};
assert_eq!(said("vector-catch hard sideways"), refused("sideways"));
assert_eq!(said("vector-catch hard none"), refused("none"));
assert_eq!(said("vector-catch all bus"), refused("all"));
assert_eq!(
demcr(&mut bench),
mask(demcr::TRCENA) | mask(demcr::VC_HARDERR) | mask(demcr::VC_BUSERR)
);
let mut said = |command: &str| bench.core.monitor_command(command).unwrap();
assert_eq!(
said("vector-catch all"),
"catching hard int bus state check nocp mm reset\n"
);
assert_eq!(demcr(&mut bench), mask(demcr::TRCENA) | every_catch());
let mut said = |command: &str| bench.core.monitor_command(command).unwrap();
assert_eq!(said("vector-catch none"), "catching nothing\n");
assert!(said("help").starts_with("monitor vector-catch: "));
assert_eq!(bench.core.monitor_command("vector catch"), None);
assert_eq!(demcr(&mut bench), mask(demcr::TRCENA));
assert_eq!(every_catch(), 0x7F1, "DEMCR[10:4,0], C1.5.2");
}
/// A debugger's step over a load the bus fails stops at the fault's handler, and the next
/// step runs the handler's first instruction.
#[test]
fn a_step_after_one_that_faulted_on_the_bus_runs_its_instruction() {
let mut bench = processor();
bench.place(THREAD_CODE, &[0x6808, UDF]); // LDR r0, [r1]
bench.place(handler(HARD_FAULT), &[0xBF00, UDF]);
bench.core.state.r[1] = DEVICE;
bench.bus.fails_from = Some(u64::from(DEVICE));
bench.core.hold_interrupts(true);
assert_eq!(bench.run(1).0, Exit::Budget);
assert_eq!(bench.core.state.r[15], handler(HARD_FAULT));
assert_eq!(bench.run(1), (Exit::Budget, 1));
assert_eq!(bench.core.state.r[15], handler(HARD_FAULT) + 2);
}
}