//! Small, explicit AArch64 ESR classifications used by the fatal report. //! FAR is not an address witness for BRK, UDF, SVC or asynchronous slots. pub const FRAME_CANARY: usize = 0x6b7a; #[derive(Debug, PartialEq, Eq)] pub enum FatalReentry { First, StopOnce, ParkSilently, } pub fn fatal_reentry(prior: usize) -> FatalReentry { match prior { 0 => FatalReentry::First, 1 => FatalReentry::StopOnce, _ => FatalReentry::ParkSilently, } } #[repr(C, align(16))] pub struct ExceptionFrame { pub registers: [usize; 31], pub interrupted_sp: usize, pub canary: usize, } impl ExceptionFrame { pub fn canary_valid(&self) -> bool { self.canary == FRAME_CANARY } } pub fn class_name(ec: u8) -> &'static str { match ec { 0x00 => "UNKNOWN", 0x20 | 0x21 => "INSN-ABORT", 0x24 | 0x25 => "DATA-ABORT", 0x3c => "BRK", _ => "OTHER", } } pub fn origin_name(slot: usize) -> &'static str { match slot { 4..=7 => "EL1", 8..=11 => "EL0", 12..=15 => "EL0-32", _ => "UNKNOWN", } } pub fn kind_name(slot: usize) -> &'static str { match slot & 3 { 0 => "SYNC", 1 => "IRQ", 2 => "FIQ", _ => "SERROR", } } pub fn unexpected_el1_async(slot: usize) -> Option<&'static str> { match slot { 6 => Some("FIQ"), 7 => Some("SERROR"), _ => None, } } pub fn far_is_valid_for_slot(slot: usize, esr: u64) -> bool { // ESR/FAR may be left over from an earlier synchronous fault when an // asynchronous exception arrives. Never attribute it to IRQ/FIQ/SError. slot & 3 == 0 && far_is_valid(esr) } pub fn far_is_valid(esr: u64) -> bool { let ec = ((esr >> 26) & 0x3f) as u8; let dfsc = (esr & 0x3f) as u8; match ec { 0x20 | 0x21 | 0x22 | 0x34 | 0x35 => true, 0x24 | 0x25 => { // For a synchronous External abort, ESR.FnV=1 means the FAR is // not valid. Other supported aborts carry the faulting address. !(0x10..=0x1f).contains(&dfsc) || esr & (1 << 10) == 0 } _ => false, } } #[cfg(test)] mod tests { extern crate std; use super::*; use core::mem::{offset_of, size_of}; use std::format; #[test] fn every_fatal_general_register_has_its_assembly_stack_offset() { let source = include_str!("vectors.S"); assert_eq!(size_of::(), 272); assert_eq!(offset_of!(ExceptionFrame, registers), 0); assert_eq!(offset_of!(ExceptionFrame, interrupted_sp), 248); assert_eq!(offset_of!(ExceptionFrame, canary), 256); assert!(source.contains(".equ EXCEPTION_X3_OFFSET, {x3_offset}")); assert!(source.contains("str x3, [sp, #EXCEPTION_X3_OFFSET]")); for index in 0..31 { let offset = offset_of!(ExceptionFrame, registers) + index * size_of::(); assert_eq!(offset, index * 8, "x{index} Rust offset changed"); let instruction = match index { 2 => format!("str x2, [sp, #{offset}]"), 3 => continue, // Saved in the vector slot before x3 becomes the slot number. 30 => format!("str x30, [sp, #{offset}]"), n if n % 2 == 0 => format!("stp x{n}, x{}, [sp, #{offset}]", n + 1), _ => continue, // Odd xN is the second word in its stp pair. }; assert!( source.lines().any(|line| line.trim() == instruction), "x{index} assembly slot diverged: {instruction}" ); } } #[test] fn fatal_frame_canary_rejects_a_corrupted_tail() { let mut frame = ExceptionFrame { registers: [0; 31], interrupted_sp: 0, canary: FRAME_CANARY, }; assert!(frame.canary_valid()); frame.canary ^= 1; assert!(!frame.canary_valid()); } #[test] fn brk_and_undefined_instruction_do_not_claim_a_far() { assert_eq!(class_name(0x3c), "BRK"); assert_eq!(class_name(0x00), "UNKNOWN"); for ec in [0x00u8, 0x15, 0x3c] { assert!(!far_is_valid((ec as u64) << 26)); } } #[test] fn abort_alignment_and_watchpoint_classes_can_report_far() { for ec in [0x20u8, 0x21, 0x22, 0x24, 0x25, 0x34, 0x35] { assert!(far_is_valid(((ec as u64) << 26) | 0x07)); } assert_eq!(class_name(0x21), "INSN-ABORT"); assert_eq!(class_name(0x25), "DATA-ABORT"); } #[test] fn lower_el_vector_origin_is_not_reported_as_kernel_origin() { assert_eq!(origin_name(8), "EL0"); assert_eq!(origin_name(9), "EL0"); assert_eq!(origin_name(4), "EL1"); assert_eq!(origin_name(5), "EL1"); assert_eq!(origin_name(3), "UNKNOWN"); assert_eq!(class_name(0x20), "INSN-ABORT"); assert!(far_is_valid(0x20u64 << 26)); } #[test] fn third_fatal_entry_cannot_emit_another_stop_marker() { assert_eq!(fatal_reentry(0), FatalReentry::First); assert_eq!(fatal_reentry(1), FatalReentry::StopOnce); assert_eq!(fatal_reentry(2), FatalReentry::ParkSilently); assert_eq!(fatal_reentry(usize::MAX), FatalReentry::ParkSilently); } #[test] fn external_abort_fnv_discards_far_but_translation_fault_keeps_it() { let data_abort = 0x25u64 << 26; assert!(!far_is_valid(data_abort | 0x10 | (1 << 10))); assert!(far_is_valid(data_abort | 0x10)); assert!(far_is_valid(data_abort | 0x07 | (1 << 10))); } #[test] fn k143_el1_fiq_is_fatal_label_without_stale_far() { let stale_abort = (0x25u64 << 26) | 0x07; assert_eq!(origin_name(6), "EL1"); assert_eq!(kind_name(6), "FIQ"); assert_eq!(unexpected_el1_async(6), Some("FIQ")); assert!(!far_is_valid_for_slot(6, stale_abort)); assert!(far_is_valid_for_slot(4, stale_abort)); } #[test] fn k144_el1_serror_is_fatal_label_without_stale_far() { let stale_abort = (0x25u64 << 26) | 0x07; assert_eq!(origin_name(7), "EL1"); assert_eq!(kind_name(7), "SERROR"); assert_eq!(unexpected_el1_async(7), Some("SERROR")); assert!(!far_is_valid_for_slot(7, stale_abort)); assert!(!far_is_valid_for_slot(5, stale_abort)); } }