vesper/nucleus/src/arch/aarch64/traps.rs

385 lines
16 KiB
Rust

/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
//! Interrupt handling
//!
//! The base address is given by VBAR_ELn and each entry has a defined offset from this
//! base address. Each table has 16 entries, with each entry being 128 bytes (32 instructions)
//! in size. The table effectively consists of 4 sets of 4 entries.
//!
//! Minimal implementation to help catch MMU traps.
//! Reads ESR_ELx to understand why trap was taken.
//!
//! VBAR_EL1, VBAR_EL2, VBAR_EL3
//!
//! CurrentEL with SP0: +0x0
//!
//! * Synchronous
//! * IRQ/vIRQ
//! * FIQ
//! * SError/vSError
//!
//! CurrentEL with SPx: +0x200
//!
//! * Synchronous
//! * IRQ/vIRQ
//! * FIQ
//! * SError/vSError
//!
//! Lower EL using AArch64: +0x400
//!
//! * Synchronous
//! * IRQ/vIRQ
//! * FIQ
//! * SError/vSError
//!
//! Lower EL using AArch32: +0x600
//!
//! * Synchronous
//! * IRQ/vIRQ
//! * FIQ
//! * SError/vSError
//!
//! When the processor takes an exception to AArch64 execution state,
//! all of the PSTATE interrupt masks is set automatically. This means
//! that further exceptions are disabled. If software is to support
//! nested exceptions, for example, to allow a higher priority interrupt
//! to interrupt the handling of a lower priority source, then software needs
//! to explicitly re-enable interrupts
use {
crate::{arch::endless_sleep, println},
cortex_a::{
barrier,
regs::{RegisterReadOnly, RegisterReadWrite, ESR_EL1, FAR_EL1, VBAR_EL1},
},
register::{register_bitfields, LocalRegisterCopy},
snafu::Snafu,
};
global_asm!(include_str!("vectors.S"));
/// Errors possibly returned from the traps module.
#[derive(Debug, Snafu)]
pub enum Error {
/// IVT address is unaligned.
#[snafu(display("Unaligned base address for interrupt vector table"))]
Unaligned,
}
/// Configure base address of interrupt vectors table.
/// Checks that address is properly 2KiB aligned.
///
/// # Safety
///
/// Totally unsafe in the land of the hardware.
pub unsafe fn set_vbar_el1_checked(vec_base_addr: u64) -> Result<(), Error> {
if vec_base_addr.trailing_zeros() < 11 {
return Err(Error::Unaligned);
}
VBAR_EL1.set(vec_base_addr);
// Force VBAR update to complete before next instruction.
barrier::isb(barrier::SY);
Ok(())
}
/// A blob of general-purpose registers.
#[repr(C)]
pub struct GPR {
x: [u64; 31],
}
/// Saved exception context.
#[repr(C)]
pub struct ExceptionContext {
// General Purpose Registers
gpr: GPR,
spsr_el1: u64,
elr_el1: u64,
}
/// The default exception, invoked for every exception type unless the handler
/// is overridden.
/// Default pointer is configured in the linker script.
///
/// # Safety
///
/// Totally unsafe in the land of the hardware.
#[no_mangle]
unsafe extern "C" fn default_exception_handler() -> ! {
println!("Unexpected exception. Halting CPU.");
endless_sleep()
}
// To implement an exception handler, override it by defining the respective
// function below.
// Don't forget the #[no_mangle] attribute.
//
/// # Safety
///
/// Totally unsafe in the land of the hardware.
#[no_mangle]
unsafe extern "C" fn current_el0_synchronous(e: &mut ExceptionContext) {
let cause = ESR_EL1.read(ESR_EL1::EC);
if cause == ESR_EL1::EC::SVC64.read(ESR_EL1::EC) {
let syscall = ESR_EL1.read(ESR_EL1::ISS);
return crate::api::handle_syscall(syscall);
}
println!("[!] USER synchronous exception happened.");
synchronous_common(e)
}
// unsafe extern "C" fn current_el0_irq(e: &mut ExceptionContext);
// unsafe extern "C" fn current_el0_serror(e: &mut ExceptionContext);
/// # Safety
///
/// Totally unsafe in the land of the hardware.
#[no_mangle]
unsafe extern "C" fn current_elx_synchronous(e: &mut ExceptionContext) {
println!("[!] KERNEL synchronous exception happened.");
synchronous_common(e);
}
// unsafe extern "C" fn current_elx_irq(e: &mut ExceptionContext);
/// # Safety
///
/// Totally unsafe in the land of the hardware.
#[no_mangle]
unsafe extern "C" fn current_elx_serror(e: &mut ExceptionContext) {
println!("[!] KERNEL serror exception happened.");
synchronous_common(e);
endless_sleep()
}
fn cause_to_string(cause: u64) -> &'static str {
if cause == ESR_EL1::EC::DataAbortCurrentEL.read(ESR_EL1::EC) {
"Data Alignment Check"
} else {
"Unknown"
}
}
register_bitfields! {
u64,
/// ISS structure for Data Abort exceptions
ISS_DA [
/// Instruction Syndrome Valid. Indicates whether the syndrome information in ISS[23:14] is valid.
/// (This includes SAS, SSE, SRT, SF, and AR)
ISV OFFSET(24) NUMBITS(1) [],
SAS OFFSET(22) NUMBITS(2) [
Byte = 0b00,
Halfword = 0b01,
Word = 0b10,
DoubleWord = 0b11
],
SSE OFFSET(21) NUMBITS(1) [],
SRT OFFSET(16) NUMBITS(5) [],
SF OFFSET(15) NUMBITS(1) [],
AR OFFSET(14) NUMBITS(1) [],
VNCR OFFSET(13) NUMBITS(1) [],
SET OFFSET(11) NUMBITS(2) [
UER = 0b00, // Recoverable state
UC = 0b10, // Uncontainable
UEO = 0b11 // Restartable state
],
FNV OFFSET(10) NUMBITS(1) [],
EA OFFSET(9) NUMBITS(1) [],
CM OFFSET(8) NUMBITS(1) [],
S1PTW OFFSET(7) NUMBITS(1) [],
WNR OFFSET(6) NUMBITS(1) [],
DFSC OFFSET(0) NUMBITS(6) [
/// Address size fault, level 0 of translation or translation table base register.
AddressSizeTL0 = 0b000000,
/// Address size fault, level 1.
AddressSizeTL1 = 0b000001,
///Address size fault, level 2.
AddressSizeTL2 = 0b000010,
/// Address size fault, level 3.
AddressSizeTL3 = 0b000011,
/// Translation fault, level 0.
TranslationFaultTL0 = 0b000100,
/// Translation fault, level 1.
TranslationFaultTL1 = 0b000101,
/// Translation fault, level 2.
TranslationFaultTL2 = 0b000110,
/// Translation fault, level 3.
TranslationFaultTL3 = 0b000111,
/// Access flag fault, level 1.
AccessFaultTL1 = 0b001001,
/// Access flag fault, level 2.
AccessFaultTL2 = 0b001010,
/// Access flag fault, level 3.
AccessFaultTL3 = 0b001011,
/// Permission fault, level 1.
PermissionFaultTL1 = 0b001101,
/// Permission fault, level 2.
PermissionFaultTL2 = 0b001110,
/// Permission fault, level 3.
PermissionFaultTL3 = 0b001111,
/// Synchronous External abort, not on translation table walk or hardware update of translation table.
SyncExternalAbort = 0b010000,
/// Synchronous Tag Check Fault.
/// (When FEAT_MTE is implemented)
SyncTagCheckFault = 0b010001,
/// Synchronous External abort on translation table walk or hardware update of translation table, level 0.
SyncAbortOnTranslationTL0 = 0b010100,
/// Synchronous External abort on translation table walk or hardware update of translation table, level 1.
SyncAbortOnTranslationTL1 = 0b010101,
/// Synchronous External abort on translation table walk or hardware update of translation table, level 2.
SyncAbortOnTranslationTL2 = 0b010110,
/// Synchronous External abort on translation table walk or hardware update of translation table, level 3.
SyncAbortOnTranslationTL3 = 0b010111,
/// Synchronous parity or ECC error on memory access, not on translation table walk.
/// (When FEAT_RAS is not implemented)
SyncParityError = 0b011000,
/// Synchronous parity or ECC error on memory access on translation table walk or hardware update of translation table, level 0.
/// (When FEAT_RAS is not implemented)
SyncParityErrorOnTranslationTL0 = 0b011100,
/// Synchronous parity or ECC error on memory access on translation table walk or hardware update of translation table, level 1.
/// (When FEAT_RAS is not implemented)
SyncParityErrorOnTranslationTL1 = 0b011101,
/// Synchronous parity or ECC error on memory access on translation table walk or hardware update of translation table, level 2.
/// (When FEAT_RAS is not implemented)
SyncParityErrorOnTranslationTL2 = 0b011110,
/// Synchronous parity or ECC error on memory access on translation table walk or hardware update of translation table, level 3.
/// (When FEAT_RAS is not implemented)
SyncParityErrorOnTranslationTL3 = 0b011111,
/// Alignment fault.
AlignmentFault = 0b100001,
/// TLB conflict abort.
TlbConflictAbort = 0b110000,
/// Unsupported atomic hardware update fault.
/// (When FEAT_HAFDBS is implemented)
UnsupportedAtomicUpdate = 0b110001,
/// IMPLEMENTATION DEFINED fault (Lockdown).
Lockdown = 0b110100,
/// IMPLEMENTATION DEFINED fault (Unsupported Exclusive or Atomic access).
UnsupportedAccess = 0b110101
]
]
}
type IssForDataAbort = LocalRegisterCopy<u64, ISS_DA::Register>;
fn iss_dfsc_to_string(iss: IssForDataAbort) -> &'static str {
match iss.read_as_enum(ISS_DA::DFSC) {
Some(ISS_DA::DFSC::Value::AddressSizeTL0) => "Address size fault, level 0 of translation or translation table base register",
Some(ISS_DA::DFSC::Value::AddressSizeTL1) => "Address size fault, level 1",
Some(ISS_DA::DFSC::Value::AddressSizeTL2) => "Address size fault, level 2",
Some(ISS_DA::DFSC::Value::AddressSizeTL3) => "Address size fault, level 3",
Some(ISS_DA::DFSC::Value::TranslationFaultTL0) => "Translation fault, level 0",
Some(ISS_DA::DFSC::Value::TranslationFaultTL1) => "Translation fault, level 1",
Some(ISS_DA::DFSC::Value::TranslationFaultTL2) => "Translation fault, level 2",
Some(ISS_DA::DFSC::Value::TranslationFaultTL3) => "Translation fault, level 3",
Some(ISS_DA::DFSC::Value::AccessFaultTL1) => "Access flag fault, level 1",
Some(ISS_DA::DFSC::Value::AccessFaultTL2) => "Access flag fault, level 2",
Some(ISS_DA::DFSC::Value::AccessFaultTL3) => "Access flag fault, level 3",
Some(ISS_DA::DFSC::Value::PermissionFaultTL1) => "Permission fault, level 1",
Some(ISS_DA::DFSC::Value::PermissionFaultTL2) => "Permission fault, level 2",
Some(ISS_DA::DFSC::Value::PermissionFaultTL3) => "Permission fault, level 3",
Some(ISS_DA::DFSC::Value::SyncExternalAbort) => "Synchronous External abort, not on translation table walk or hardware update of translation table",
Some(ISS_DA::DFSC::Value::SyncTagCheckFault) => "Synchronous Tag Check Fault",
Some(ISS_DA::DFSC::Value::SyncAbortOnTranslationTL0) => "Synchronous External abort on translation table walk or hardware update of translation table, level 0",
Some(ISS_DA::DFSC::Value::SyncAbortOnTranslationTL1) => "Synchronous External abort on translation table walk or hardware update of translation table, level 1",
Some(ISS_DA::DFSC::Value::SyncAbortOnTranslationTL2) => "Synchronous External abort on translation table walk or hardware update of translation table, level 2",
Some(ISS_DA::DFSC::Value::SyncAbortOnTranslationTL3) => "Synchronous External abort on translation table walk or hardware update of translation table, level 3",
Some(ISS_DA::DFSC::Value::SyncParityError) => "Synchronous parity or ECC error on memory access, not on translation table walk",
Some(ISS_DA::DFSC::Value::SyncParityErrorOnTranslationTL0) => "Synchronous parity or ECC error on memory access on translation table walk or hardware update of translation table, level 0",
Some(ISS_DA::DFSC::Value::SyncParityErrorOnTranslationTL1) => "Synchronous parity or ECC error on memory access on translation table walk or hardware update of translation table, level 1",
Some(ISS_DA::DFSC::Value::SyncParityErrorOnTranslationTL2) => "Synchronous parity or ECC error on memory access on translation table walk or hardware update of translation table, level 2",
Some(ISS_DA::DFSC::Value::SyncParityErrorOnTranslationTL3) => "Synchronous parity or ECC error on memory access on translation table walk or hardware update of translation table, level 3",
Some(ISS_DA::DFSC::Value::AlignmentFault) => "Alignment fault",
Some(ISS_DA::DFSC::Value::TlbConflictAbort) => "TLB conflict abort",
Some(ISS_DA::DFSC::Value::UnsupportedAtomicUpdate) => "Unsupported atomic hardware update fault",
Some(ISS_DA::DFSC::Value::Lockdown) => "Lockdown (IMPLEMENTATION DEFINED fault)",
Some(ISS_DA::DFSC::Value::UnsupportedAccess) => "Unsupported Exclusive or Atomic access (IMPLEMENTATION DEFINED fault)",
_ => "Unknown",
}
}
// unsafe extern "C" fn lower_aarch64_synchronous(e: &mut ExceptionContext);
// unsafe extern "C" fn lower_aarch64_irq(e: &mut ExceptionContext);
// unsafe extern "C" fn lower_aarch64_serror(e: &mut ExceptionContext);
// unsafe extern "C" fn lower_aarch32_synchronous(e: &mut ExceptionContext);
// unsafe extern "C" fn lower_aarch32_irq(e: &mut ExceptionContext);
// unsafe extern "C" fn lower_aarch32_serror(e: &mut ExceptionContext);
/// Helper function to 1) display current exception, 2) skip the offending asm instruction.
/// Not for production use!
fn synchronous_common(e: &mut ExceptionContext) {
let cause = ESR_EL1.read(ESR_EL1::EC);
println!(" ESR_EL1: {:#010x} (syndrome)", ESR_EL1.get());
println!(
" EC: {:#06b} (cause) -- {}",
cause,
cause_to_string(cause)
);
// Print more details about Data Alignment Check
if cause == ESR_EL1::EC::DataAbortCurrentEL.read(ESR_EL1::EC) {
let iss = ESR_EL1.read(ESR_EL1::ISS);
let iss = IssForDataAbort::new(iss);
if iss.is_set(ISS_DA::ISV) {
println!(
" Access size: {} bytes ({}signed) to {}{}",
2u64.pow(iss.read(ISS_DA::SAS) as u32),
if iss.is_set(ISS_DA::SSE) { "" } else { "un" },
if iss.is_set(ISS_DA::SF) { "x" } else { "r" },
iss.read(ISS_DA::SRT)
);
println!(
" Acq/Rel semantics: {}present",
if iss.is_set(ISS_DA::AR) { "" } else { "not " }
);
}
// data abort specific encoding
println!(
" {} address {:#016x} ({}valid)",
if iss.is_set(ISS_DA::WNR) {
"Writing to"
} else {
"Reading from"
},
FAR_EL1.get(),
if iss.is_set(ISS_DA::FNV) { "not " } else { "" }
);
println!(" Specific fault: {}", iss_dfsc_to_string(iss));
} else {
println!(" FAR_EL1: {:#016x} (location)", FAR_EL1.get());
println!(" Stack: {:#016x}", e.spsr_el1);
}
println!(" ELR_EL1: {:#010x}", e.elr_el1);
println!(" x00: 0000000000000000 x01: {:016x}", e.gpr.x[0]);
for index in 0..15 {
println!(
" x{:02}: {:016x} x{:02}: {:016x}",
index * 2 + 2,
e.gpr.x[index * 2 + 1],
index * 2 + 3,
e.gpr.x[index * 2 + 2]
);
}
println!(
" Incrementing ELR_EL1 by 4 to continue with the first \
instruction after the exception!"
);
e.elr_el1 += 4;
println!(" ELR_EL1 modified: {:#010x}", e.elr_el1);
println!(" Returning from exception...\n");
}