use core::fmt::Write as _;
use crate::pac;
use crate::uart::Uart;
#[cfg(target_arch = "arm")]
#[no_mangle]
extern "C" fn rpi_hal_fault_report(kind: u32, lr: usize, spsr: u32) -> ! {
let mut uart = console();
let pc = lr.wrapping_sub(if kind == KIND_DATA_ABORT { 8 } else { 4 });
let _ = writeln!(
uart,
"FAULT: {} on core {}",
kind_name(kind),
crate::cpu::core_id()
);
let _ = writeln!(uart, " pc {pc:#010x}");
match kind {
KIND_DATA_ABORT => {
let (dfsr, dfar) = data_fault_registers();
let direction = if dfsr & (1 << 11) != 0 {
"write"
} else {
"read"
};
let _ = writeln!(uart, " addr {dfar:#010x} {direction}");
let _ = writeln!(uart, " cause {}", short_fault_name(status(dfsr)));
let _ = writeln!(uart, " dfsr {dfsr:#010x} spsr {spsr:#010x}");
report_stack(&mut uart, &[dfar]);
}
KIND_PREFETCH_ABORT => {
let (ifsr, ifar) = instruction_fault_registers();
let _ = writeln!(uart, " addr {ifar:#010x} fetch");
let _ = writeln!(uart, " cause {}", short_fault_name(status(ifsr)));
let _ = writeln!(uart, " ifsr {ifsr:#010x} spsr {spsr:#010x}");
report_stack(&mut uart, &[ifar]);
}
_ => {
let _ = writeln!(uart, " spsr {spsr:#010x}");
report_stack(&mut uart, &[]);
}
}
crate::halt()
}
#[cfg(target_arch = "aarch64")]
#[no_mangle]
extern "C" fn rpi_hal_fault_report(kind: u32, faulting_sp: usize) -> ! {
let mut uart = console();
let (esr, far, elr, spsr) = exception_registers();
let class = (esr >> 26) & 0x3f;
let _ = writeln!(
uart,
"FAULT: {} on core {}",
kind_name(kind),
crate::cpu::core_id()
);
let _ = writeln!(uart, " pc {elr:#018x}");
let mut suspects = [faulting_sp, faulting_sp];
if is_synchronous(kind) {
if is_abort(class) {
let direction = if class == EC_DATA_ABORT_SAME || class == EC_DATA_ABORT_LOWER {
if esr & (1 << 6) != 0 {
"write"
} else {
"read"
}
} else {
"fetch"
};
let _ = writeln!(uart, " addr {far:#018x} {direction}");
let _ = writeln!(uart, " cause {}", long_fault_name(esr & 0x3f));
suspects[0] = far as usize;
}
let _ = writeln!(uart, " class {}", class_name(class));
let _ = writeln!(uart, " esr {esr:#010x} spsr {spsr:#010x}");
}
let _ = writeln!(uart, " sp {faulting_sp:#018x}");
report_stack(&mut uart, &suspects);
crate::halt()
}
fn console() -> Uart {
let peripherals = unsafe { pac::Peripherals::steal() };
Uart::init(&peripherals.GPIO, peripherals.UART0)
}
fn report_stack(uart: &mut Uart, suspects: &[usize]) {
let (bottom, top) = (crate::stack::bottom(), crate::stack::top());
let _ = writeln!(uart, " stack {bottom:#010x}..{top:#010x}");
let overflowed = suspects
.iter()
.any(|&address| address < bottom && bottom - address <= crate::stack::size());
if overflowed {
let _ = writeln!(
uart,
" *** past the bottom of the stack: this is a stack overflow"
);
}
}
#[cfg(target_arch = "arm")]
const KIND_DATA_ABORT: u32 = 4;
#[cfg(target_arch = "arm")]
const KIND_PREFETCH_ABORT: u32 = 3;
#[cfg(target_arch = "arm")]
fn kind_name(kind: u32) -> &'static str {
match kind {
1 => "undefined instruction",
2 => "supervisor call",
3 => "prefetch abort",
4 => "data abort",
7 => "FIQ with no handler",
_ => "unknown exception",
}
}
#[cfg(target_arch = "aarch64")]
fn kind_name(kind: u32) -> &'static str {
let group = match kind >> 2 {
0 => "at EL1 on SP_EL0",
1 => "at EL1",
2 => "from a lower EL (AArch64)",
_ => "from a lower EL (AArch32)",
};
match (kind & 3, group) {
(0, g) => match g {
"at EL1 on SP_EL0" => "synchronous exception at EL1 on SP_EL0",
"at EL1" => "synchronous exception at EL1",
"from a lower EL (AArch64)" => "synchronous exception from a lower EL (AArch64)",
_ => "synchronous exception from a lower EL (AArch32)",
},
(1, _) => "IRQ with no handler",
(2, _) => "FIQ with no handler",
_ => "SError",
}
}
#[cfg(target_arch = "aarch64")]
fn is_synchronous(kind: u32) -> bool {
matches!(kind & 3, 0 | 3)
}
#[cfg(target_arch = "aarch64")]
const EC_DATA_ABORT_SAME: u64 = 0x25;
#[cfg(target_arch = "aarch64")]
const EC_DATA_ABORT_LOWER: u64 = 0x24;
#[cfg(target_arch = "aarch64")]
fn is_abort(class: u64) -> bool {
matches!(
class,
0x20 | 0x21 | EC_DATA_ABORT_LOWER | EC_DATA_ABORT_SAME
)
}
#[cfg(target_arch = "aarch64")]
fn class_name(class: u64) -> &'static str {
match class {
0x00 => "unknown reason",
0x0e => "illegal execution state",
0x15 => "supervisor call",
0x18 => "trapped system register access",
0x20 | 0x21 => "instruction abort",
0x22 => "PC alignment fault",
0x24 | 0x25 => "data abort",
0x26 => "SP alignment fault",
0x2c => "floating-point exception",
0x2f => "SError interrupt",
0x3c => "breakpoint instruction",
_ => "see ESR_EL1.EC",
}
}
#[cfg(target_arch = "arm")]
fn status(fsr: u32) -> u32 {
((fsr >> 10) & 1) << 4 | (fsr & 0xf)
}
#[cfg(target_arch = "arm")]
fn short_fault_name(fs: u32) -> &'static str {
match fs {
0x01 => "alignment fault",
0x02 => "debug event",
0x03 => "access flag fault, first level",
0x04 => "instruction cache maintenance fault",
0x05 => "translation fault, first level",
0x06 => "access flag fault, second level",
0x07 => "translation fault, second level",
0x08 => "synchronous external abort",
0x09 => "domain fault, first level",
0x0b => "domain fault, second level",
0x0c => "external abort on translation table walk, first level",
0x0d => "permission fault, first level",
0x0e => "external abort on translation table walk, second level",
0x0f => "permission fault, second level",
0x16 => "asynchronous external abort",
0x18 => "asynchronous parity error",
0x19 => "synchronous parity error",
_ => "see DFSR/IFSR",
}
}
#[cfg(target_arch = "aarch64")]
fn long_fault_name(status: u64) -> &'static str {
match status {
0x00..=0x03 => "address size fault",
0x04 => "translation fault, level 0",
0x05 => "translation fault, level 1",
0x06 => "translation fault, level 2",
0x07 => "translation fault, level 3",
0x08..=0x0b => "access flag fault",
0x0c => "permission fault, level 0",
0x0d => "permission fault, level 1",
0x0e => "permission fault, level 2",
0x0f => "permission fault, level 3",
0x10 => "synchronous external abort",
0x14..=0x17 => "external abort on translation table walk",
0x18 => "synchronous parity error",
0x1c..=0x1f => "parity error on translation table walk",
0x21 => "alignment fault",
0x30 => "TLB conflict abort",
_ => "see ESR_EL1.DFSC",
}
}
#[cfg(target_arch = "arm")]
fn data_fault_registers() -> (u32, usize) {
let (dfsr, dfar): (u32, usize);
unsafe {
core::arch::asm!(
"mrc p15, 0, {dfsr}, c5, c0, 0",
"mrc p15, 0, {dfar}, c6, c0, 0",
dfsr = out(reg) dfsr,
dfar = out(reg) dfar,
options(nomem, nostack, preserves_flags),
);
}
(dfsr, dfar)
}
#[cfg(target_arch = "arm")]
fn instruction_fault_registers() -> (u32, usize) {
let (ifsr, ifar): (u32, usize);
unsafe {
core::arch::asm!(
"mrc p15, 0, {ifsr}, c5, c0, 1",
"mrc p15, 0, {ifar}, c6, c0, 2",
ifsr = out(reg) ifsr,
ifar = out(reg) ifar,
options(nomem, nostack, preserves_flags),
);
}
(ifsr, ifar)
}
#[cfg(target_arch = "aarch64")]
fn exception_registers() -> (u64, u64, u64, u64) {
let (esr, far, elr, spsr): (u64, u64, u64, u64);
unsafe {
core::arch::asm!(
"mrs {esr}, esr_el1",
"mrs {far}, far_el1",
"mrs {elr}, elr_el1",
"mrs {spsr}, spsr_el1",
esr = out(reg) esr,
far = out(reg) far,
elr = out(reg) elr,
spsr = out(reg) spsr,
options(nomem, nostack, preserves_flags),
);
}
(esr, far, elr, spsr)
}