Add trap handling and test

This commit is contained in:
Berkus Decker 2020-10-15 23:42:01 +03:00
parent 504dcd1f65
commit 0692c324af
4 changed files with 301 additions and 0 deletions

View File

@ -6,6 +6,7 @@
mod boot;
pub mod memory;
pub mod traps;
/// Loop forever in sleep mode.
#[inline]

View File

@ -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");
}

View File

@ -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

View File

@ -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.");
}
}