385 lines
16 KiB
Rust
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");
|
|
}
|