From 0692c324afe5f3de54c2b3c8b0bef43c4901fc83 Mon Sep 17 00:00:00 2001 From: Berkus Decker <berkus+github@metta.systems> Date: Thu, 15 Oct 2020 23:42:01 +0300 Subject: [PATCH] Add trap handling and test --- nucleus/src/arch/aarch64/mod.rs | 1 + nucleus/src/arch/aarch64/traps.rs | 169 +++++++++++++++++++++++++++++ nucleus/src/arch/aarch64/vectors.S | 97 +++++++++++++++++ nucleus/src/main.rs | 34 ++++++ 4 files changed, 301 insertions(+) create mode 100644 nucleus/src/arch/aarch64/traps.rs create mode 100644 nucleus/src/arch/aarch64/vectors.S diff --git a/nucleus/src/arch/aarch64/mod.rs b/nucleus/src/arch/aarch64/mod.rs index 775e291..7c49eb5 100644 --- a/nucleus/src/arch/aarch64/mod.rs +++ b/nucleus/src/arch/aarch64/mod.rs @@ -6,6 +6,7 @@ mod boot; pub mod memory; +pub mod traps; /// Loop forever in sleep mode. #[inline] diff --git a/nucleus/src/arch/aarch64/traps.rs b/nucleus/src/arch/aarch64/traps.rs new file mode 100644 index 0000000..21ef27c --- /dev/null +++ b/nucleus/src/arch/aarch64/traps.rs @@ -0,0 +1,169 @@ +/* + * SPDX-License-Identifier: BlueOak-1.0.0 + */ + +//! 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}, + }, +}; + +global_asm!(include_str!("vectors.S")); + +/// 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<(), ()> { + if vec_base_addr.trailing_zeros() < 11 { + return Err(()); + } + + 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) { + 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() +} + +// 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) { + println!(" ESR_EL1: {:#010x} (syndrome)", ESR_EL1.get()); + println!(" EC: {:#06b} (cause)", ESR_EL1.read(ESR_EL1::EC)); + println!(" FAR_EL1: {:#016x} (location)", FAR_EL1.get()); + println!(" ELR_EL1: {:#010x}", e.elr_el1); + + println!( + " Incrementing ELR_EL1 by 4 now 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"); +} diff --git a/nucleus/src/arch/aarch64/vectors.S b/nucleus/src/arch/aarch64/vectors.S new file mode 100644 index 0000000..4d89595 --- /dev/null +++ b/nucleus/src/arch/aarch64/vectors.S @@ -0,0 +1,97 @@ +/* + * SPDX-License-Identifier: MIT OR BlueOak-1.0.0 + * Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com> + * Copyright (c) Berkus Decker <berkus+vesper@metta.systems> + * Original code distributed under MIT, additional changes are under BlueOak-1.0.0 + */ + +.macro SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE handler +.balign 0x80 + + sub sp, sp, #16 * 17 + + stp x0, x1, [sp, #16 * 0] + stp x2, x3, [sp, #16 * 1] + stp x4, x5, [sp, #16 * 2] + stp x6, x7, [sp, #16 * 3] + stp x8, x9, [sp, #16 * 4] + stp x10, x11, [sp, #16 * 5] + stp x12, x13, [sp, #16 * 6] + stp x14, x15, [sp, #16 * 7] + stp x16, x17, [sp, #16 * 8] + stp x18, x19, [sp, #16 * 9] + stp x20, x21, [sp, #16 * 10] + stp x22, x23, [sp, #16 * 11] + stp x24, x25, [sp, #16 * 12] + stp x26, x27, [sp, #16 * 13] + stp x28, x29, [sp, #16 * 14] + + mrs x1, SPSR_EL1 + mrs x2, ELR_EL1 + + stp x30, x1, [sp, #16 * 15] + str x2, [sp, #16 * 16] + + mov x0, sp + bl \handler + b __restore_context +.endm + +.macro FIQ_DUMMY +.balign 0x80 +1: wfe + b 1b +.endm + +// The vector definitions +.section .vectors, "ax" +.global __exception_vectors_start +__exception_vectors_start: + SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE current_el0_synchronous // 0x000 + SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE current_el0_irq // 0x080 + FIQ_DUMMY // 0x100 + SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE current_el0_serror // 0x180 + + SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE current_elx_synchronous // 0x200 + SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE current_elx_irq // 0x280 + FIQ_DUMMY // 0x300 + SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE current_elx_serror // 0x380 + + SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE lower_aarch64_synchronous // 0x400 + SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE lower_aarch64_irq // 0x480 + FIQ_DUMMY // 0x500 + SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE lower_aarch64_serror // 0x580 + + SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE lower_aarch32_synchronous // 0x600 + SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE lower_aarch32_irq // 0x680 + FIQ_DUMMY // 0x700 + SAVE_CONTEXT_CALL_HANDLER_AND_RESTORE lower_aarch32_serror // 0x780 + +.balign 0x80 +.global __restore_context +__restore_context: + ldr x19, [sp, #16 * 16] + ldp x30, x20, [sp, #16 * 15] + + msr ELR_EL1, x19 + msr SPSR_EL1, x20 + + ldp x0, x1, [sp, #16 * 0] + ldp x2, x3, [sp, #16 * 1] + ldp x4, x5, [sp, #16 * 2] + ldp x6, x7, [sp, #16 * 3] + ldp x8, x9, [sp, #16 * 4] + ldp x10, x11, [sp, #16 * 5] + ldp x12, x13, [sp, #16 * 6] + ldp x14, x15, [sp, #16 * 7] + ldp x16, x17, [sp, #16 * 8] + ldp x18, x19, [sp, #16 * 9] + ldp x20, x21, [sp, #16 * 10] + ldp x22, x23, [sp, #16 * 11] + ldp x24, x25, [sp, #16 * 12] + ldp x26, x27, [sp, #16 * 13] + ldp x28, x29, [sp, #16 * 14] + + add sp, sp, #16 * 17 + + eret diff --git a/nucleus/src/main.rs b/nucleus/src/main.rs index 9a16aa3..84735ea 100644 --- a/nucleus/src/main.rs +++ b/nucleus/src/main.rs @@ -9,6 +9,7 @@ #![no_std] #![no_main] #![feature(asm)] +#![feature(global_asm)] #![feature(ptr_internals)] #![feature(format_args_nl)] #![feature(custom_test_frameworks)] @@ -47,10 +48,25 @@ fn init_mmu() { println!("MMU initialised"); } +fn init_exception_traps() { + extern "C" { + static __exception_vectors_start: u64; + } + + unsafe { + let exception_vectors_start: u64 = &__exception_vectors_start as *const _ as u64; + + arch::traps::set_vbar_el1_checked(exception_vectors_start) + .expect("Vector table properly aligned!"); + } + println!("Exception traps set up"); +} + /// Kernel entry point. /// `arch` crate is responsible for calling it. #[inline] pub fn kmain() -> ! { + init_exception_traps(); init_mmu(); #[cfg(test)] @@ -63,3 +79,21 @@ pub fn kmain() -> ! { fn panicked(_info: &core::panic::PanicInfo) -> ! { endless_sleep() } + +#[cfg(test)] +mod main_tests { + use super::*; + + #[test_case] + fn check_data_abort_trap() { + // Cause an exception by accessing a virtual address for which no + // address translations have been set up. + // + // This line of code accesses the address 3 GiB, but page tables are + // only set up for the range [0..1) GiB. + let big_addr: u64 = 3 * 1024 * 1024 * 1024; + unsafe { core::ptr::read_volatile(big_addr as *mut u64) }; + + println!("[i] Whoa! We recovered from an exception."); + } +}