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