trap, main: implement trap handler
authorKit Rhett Aultman <kit@kitaultman.com>
Sat, 24 Aug 2024 15:30:25 +0000 (11:30 -0400)
committerKit Rhett Aultman <kit@kitaultman.com>
Sat, 24 Aug 2024 15:43:16 +0000 (11:43 -0400)
This commit implements a trap handler, which handles a number of basic
exception cases and sets the code up for handling interrupts, which will
be really useful for making a serial console properly without relying on
looping.

Changes:
* trap.rs implements a TrapFrame struct and declares one in memory.
  This will allow the code to preserve registers and context without
  using the stack.
* trap.S implements the low-level parts of the trap handler, copying the
  general-purpose and floating-point registers, plus the trap cause and
  other context info, into the TrapFrame, and calls the Rust function
  m_trap.
* trap.rs implements m_trap, which performs dispatch based on the
  exception code and, if the exception is non-fatal, returns the address
  of the PC that the trap returns to (typically PC+4).
* main.rs expands the panic handler to log panic information to serial
  out so that fatal exceptions are logged.  It also adds the trap vector
  init code.

This particular method of performing traps is heavily inspired from
Stephen Marz' blog on bare metal Rust for RISC-V:
http://osblog.stephenmarz.com/ch4.html

src/main.rs
src/trap.S [new file with mode: 0644]
src/trap.rs [new file with mode: 0644]

index d322270ec938ce51cd414e1461c2f006f3c58ca8..d25754e85a2f67071d9f6e60d4e6d79dd6c46869 100644 (file)
@@ -1,18 +1,41 @@
 #![no_std]
 #![no_main]
 #![feature(naked_functions)] // enable functions that are pure inline assembly
+#![feature(decl_macro)]
+#![feature(raw_ref_op)]
+#![feature(panic_info_message)]
 
 #[macro_use]
 mod console;
 mod heap;
+mod trap;
 
+use core::arch::asm;
+use core::arch::global_asm;
 use core::panic::PanicInfo;
+global_asm!(include_str!("trap.S"));
 
 // cfg not test is set here because the analyzer and the test system
 // complained about the panic handler being doubly-defined
 #[cfg(not(test))]
 #[panic_handler]
-fn on_panic(_info: &PanicInfo) -> ! {
+fn on_panic(info: &PanicInfo) -> ! {
+    println!("Abort due to panic: {}", info.message());
+    if let Some(p) = info.location() {
+        println!("Aborted at file {} line {}", p.file(), p.line())
+    } else {
+        println!("Abort location not available")
+    }
+    unsafe {
+        asm!(
+            "wfi",
+            // The next ones should never be run but
+            // provide an orienting point in code
+            "li t0, 0x29a",
+            "j 0"
+        )
+    }
+    // *DEFINITELY* should not be reached.
     loop {}
 }
 
@@ -40,6 +63,9 @@ unsafe extern "C" fn _start() -> ! {
       "la gp, _global_pointer",
       ".option pop",
 
+      // Interrupts off (this shouldn't impact exceptions, though)
+      "csrw mie, zero",
+
       // set the stack pointer
       "la sp, _stack_top",
 
@@ -64,6 +90,7 @@ unsafe extern "C" fn _start() -> ! {
 extern "C" fn entry() -> ! {
     // Init serial console
     use crate::console;
+    use core::arch::asm;
     console::init_console();
     println!("Console init complete");
 
@@ -72,6 +99,29 @@ extern "C" fn entry() -> ! {
     // Safety: initialize once (and that's done now)
     unsafe { heap::init() };
 
+    // Load the trap frame for interrupt handling
+    // This must be done here rather than in the _start function because the _start function is naked and
+    // we can't use keywords other than const and sym
+    unsafe {
+        asm!(
+        // Establish the stack frame reference in mscratch
+        "csrw mscratch, {trap_frame}",
+        "lla t2, m_trap_vector",
+        "csrw mtvec, t2",
+        trap_frame = in(reg)(&raw mut trap::TRAP_FRAME as usize)
+          );
+    }
     println!("Kernel init complete");
-    loop {}
+
+    // ecall from machine mode is currently a panic so uncommenting this block will trigger an
+    // exception and execute the panic handler.
+    /*
+    unsafe {
+        asm!("ecall")
+    }
+    */
+    println!("Going idle!");
+    unsafe { asm!("1:","wfi", "j 1b") }  // Hopefully wfi will make this less busy (1b is
+                                         // 'backwards to 1:'
+    loop {} // Definitely not gonna happen
 }
diff --git a/src/trap.S b/src/trap.S
new file mode 100644 (file)
index 0000000..f0835e6
--- /dev/null
@@ -0,0 +1,192 @@
+# This disables instruction compression, making all instructions 32 bits in
+# length.  This helps with keeping trap vectors aligned on a 4-byte boundary.
+# We want to keep it on a 4-byte boundary because when we specify the vector
+# in the mtvec register, we don't get to use the 2 lowest bits (they're
+# used for permission bits.
+
+.option norvc
+
+# do we really need to say this is text section?!
+# .section text
+.global m_trap_vector
+
+.align 4
+m_trap_vector:
+# To save a trap frame, we first need a register to serve as the base used
+# in all of the sd instructions (they're all "sd reg2, offset(reg1)"), so we
+# need a base.  We can keep a pointer to the trap frame struct in mscratch,
+# but then we'll need to transfer it into a working register.  So, step one
+# is to swap mscratch with a convenient register.  We could use register x0,
+# but this is the 0 register so we can't really use it that way.  The next
+# logical choice is to use register x31 (aka t6)
+       csrrw t6, mscratch, t6
+
+# So, now we can save all the general purpose registers (except t6) into the
+# trap frame.
+       sd x0, 0(t6)
+       sd x1, 8(t6)
+       sd x2, 16(t6)
+       sd x3, 24(t6)
+       sd x4, 32(t6)
+       sd x5, 40(t6)
+       sd x6, 48(t6)
+       sd x7, 56(t6)
+       sd x8, 64(t6)
+       sd x9, 72(t6)
+       sd x10, 80(t6)
+       sd x11, 88(t6)
+       sd x12, 96(t6)
+       sd x13, 104(t6)
+       sd x14, 112(t6)
+       sd x15, 120(t6)
+       sd x16, 128(t6)
+       sd x17, 136(t6)
+       sd x18, 144(t6)
+       sd x19, 152(t6)
+       sd x20, 160(t6)
+       sd x21, 168(t6)
+       sd x22, 176(t6)
+       sd x23, 184(t6)
+       sd x24, 192(t6)
+       sd x25, 200(t6)
+       sd x26, 208(t6)
+       sd x27, 216(t6)
+       sd x28, 224(t6)
+       sd x29, 232(t6)
+       sd x30, 240(t6)
+# Once we're done populating the trap frame, we will get t6 with:
+# sd x31, 248(t6) or something similar.
+#
+# But for now we can get the floating point registers too
+       fsd f0, 256(t6)
+       fsd f1, 264(t6)
+       fsd f2, 272(t6)
+       fsd f3, 280(t6)
+       fsd f4, 288(t6)
+       fsd f5, 296(t6)
+       fsd f6, 304(t6)
+       fsd f7, 312(t6)
+       fsd f8, 320(t6)
+       fsd f9, 328(t6)
+       fsd f10, 336(t6)
+       fsd f11, 344(t6)
+       fsd f12, 352(t6)
+       fsd f13, 360(t6)
+       fsd f14, 368(t6)
+       fsd f15, 376(t6)
+       fsd f16, 384(t6)
+       fsd f17, 392(t6)
+       fsd f18, 400(t6)
+       fsd f19, 408(t6)
+       fsd f20, 416(t6)
+       fsd f21, 424(t6)
+       fsd f22, 432(t6)
+       fsd f23, 440(t6)
+       fsd f24, 448(t6)
+       fsd f25, 456(t6)
+       fsd f26, 464(t6)
+       fsd f27, 472(t6)
+       fsd f28, 480(t6)
+       fsd f29, 488(t6)
+       fsd f30, 496(t6)
+       fsd f31, 504(t6)
+
+# Alright, so at this point t6 is in mscratch and we need to get it back
+# but to do so we will lose our base register.  Move it to t5 (which was saved
+# already), and then use it as the base for t6.  We'll go ahead and swap t6
+# and mscratch while we're at it, putting the trap frame back in mscratch
+       mv t5, t6
+       csrrw t6, mscratch, t6
+       sd x31, 248(t5)
+
+# Whew.  Preserved the registers in a trap frame!  Now we can look at jumping
+# into Rust.  Note we're just keeping the stack where the interrupt occurred
+# for now, but we'll likely want to have a stack for interrupt handling in the
+# future.  We'll also pass along all the information about the interrupt so that
+# the Rust function can act on it.
+       csrr a0, mepc # mepc is the address of the instruction that was interrupted
+       csrr a1, mtval # extra information specific to this exception trap
+       csrr a2, mcause # the cause of the exception
+       csrr a3, mhartid # which hart encountered the exception
+       csrr a4, mstatus # machine status register
+       csrr a5, mscratch # base address for the trap frame
+       call m_trap
+
+# Now we process the return value, restore the registers and get out of here.
+# The return value will be in a0 and denotes the next instruction to execute
+# when we return from this trap.
+       csrw mepc, a0 # Place the updated PC in mepc so mret will return to it
+
+# Restore the registers.  This time, we'll *copy* mscratch into t6 and
+# overwrite t6 from the trap frame.  We'll actually restore the floating point
+# registers first so that we can overwrite t6 as our final load-out
+       csrr t6, mscratch
+
+       fld f0, 256(t6)
+       fld f1, 264(t6)
+       fld f2, 272(t6)
+       fld f3, 280(t6)
+       fld f4, 288(t6)
+       fld f5, 296(t6)
+       fld f6, 304(t6)
+       fld f7, 312(t6)
+       fld f8, 320(t6)
+       fld f9, 328(t6)
+       fld f10, 336(t6)
+       fld f11, 344(t6)
+       fld f12, 352(t6)
+       fld f13, 360(t6)
+       fld f14, 368(t6)
+       fld f15, 376(t6)
+       fld f16, 384(t6)
+       fld f17, 392(t6)
+       fld f18, 400(t6)
+       fld f19, 408(t6)
+       fld f20, 416(t6)
+       fld f21, 424(t6)
+       fld f22, 432(t6)
+       fld f23, 440(t6)
+       fld f24, 448(t6)
+       fld f25, 456(t6)
+       fld f26, 464(t6)
+       fld f27, 472(t6)
+       fld f28, 480(t6)
+       fld f29, 488(t6)
+       fld f30, 496(t6)
+       fld f31, 504(t6)
+       ld x0, 0(t6)
+       ld x1, 8(t6)
+       ld x2, 16(t6)
+       ld x3, 24(t6)
+       ld x4, 32(t6)
+       ld x5, 40(t6)
+       ld x6, 48(t6)
+       ld x7, 56(t6)
+       ld x8, 64(t6)
+       ld x9, 72(t6)
+       ld x10, 80(t6)
+       ld x11, 88(t6)
+       ld x12, 96(t6)
+       ld x13, 104(t6)
+       ld x14, 112(t6)
+       ld x15, 120(t6)
+       ld x16, 128(t6)
+       ld x17, 136(t6)
+       ld x18, 144(t6)
+       ld x19, 152(t6)
+       ld x20, 160(t6)
+       ld x21, 168(t6)
+       ld x22, 176(t6)
+       ld x23, 184(t6)
+       ld x24, 192(t6)
+       ld x25, 200(t6)
+       ld x26, 208(t6)
+       ld x27, 216(t6)
+       ld x28, 224(t6)
+       ld x29, 232(t6)
+       ld x30, 240(t6)
+       ld x31, 248(t6)
+
+# Return from trap.  Later, skater.
+       mret
+
diff --git a/src/trap.rs b/src/trap.rs
new file mode 100644 (file)
index 0000000..ac45c5f
--- /dev/null
@@ -0,0 +1,132 @@
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct TrapFrame {
+    pub regs: [usize; 32], // 8 bytes per register; 32 gp regs (0 - 255)
+    pub fregs: [usize; 32], // 8 bytes per register; 32 fp regs (256 - 511)
+                           // Supervisor address translation and protection (SATP) register.
+                           // This is the paging and MMU system, which we're not using and thus can also comment out.
+                           // pub satp: usize,         // (512 - 519)
+                           // For now, we're not going to bother with the trap stack.  Traps will just happen on
+                           // the main stack.
+                           // pub trap_stack: *mut u8, // 520
+
+                           // We're also skipping this; it's not needed when we've got only one hart and besides
+                           // that we're in machine mode.
+                           //pub hartid: usize,       // 528
+}
+
+impl TrapFrame {
+    pub const fn new() -> Self {
+        TrapFrame {
+            regs: [0; 32],
+            fregs: [0; 32],
+        }
+    }
+}
+
+pub static mut TRAP_FRAME: TrapFrame = TrapFrame::new();
+
+#[no_mangle]
+extern "C" fn m_trap(
+    epc: usize,
+    tval: usize,
+    cause: usize,
+    hart: usize,
+    _status: usize,
+    _frame: &mut TrapFrame,
+) -> usize {
+    // We're going to handle all traps in machine mode. RISC-V lets
+    // us delegate to supervisor mode, but switching out SATP (virtual memory)
+    // gets hairy.
+    let is_async = {
+        if cause >> 63 & 1 == 1 {
+            true
+        } else {
+            false
+        }
+    };
+    // The cause contains the type of trap (sync, async) as well as the cause
+    // number. So, here we narrow down just the cause number.
+    let cause_num = cause & 0xfff;
+    let mut return_pc = epc;
+    if is_async {
+        // Asynchronous trap
+        match cause_num {
+            3 => {
+                // Machine software
+                println!("Machine software interrupt CPU#{}", hart);
+            }
+            7 => unsafe {
+                // Machine timer
+                let mtimecmp = 0x0200_4000 as *mut u64;
+                let mtime = 0x0200_bff8 as *const u64;
+                // The frequency given by QEMU is 10_000_000 Hz, so this sets
+                // the next interrupt to fire one second from now.
+                mtimecmp.write_volatile(mtime.read_volatile() + 10_000_000);
+            },
+            11 => {
+                // Machine external (interrupt from Platform Interrupt Controller (PLIC))
+                println!("Machine external interrupt CPU#{}", hart);
+            }
+            _ => {
+                panic!("Unhandled async trap CPU#{} -> {}\n", hart, cause_num);
+            }
+        }
+    } else {
+        // Synchronous trap
+        match cause_num {
+            2 => {
+                // Illegal instruction
+                panic!(
+                    "Illegal instruction CPU#{} -> 0x{:08x}: 0x{:08x}\n",
+                    hart, epc, tval
+                );
+            }
+            8 => {
+                // Environment (system) call from User mode
+                println!("E-call from User mode! CPU#{} -> 0x{:08x}", hart, epc);
+                return_pc += 4;
+            }
+            9 => {
+                // Environment (system) call from Supervisor mode
+                println!("E-call from Supervisor mode! CPU#{} -> 0x{:08x}", hart, epc);
+                return_pc += 4;
+            }
+            11 => {
+                // Environment (system) call from Machine mode
+                panic!("E-call from Machine mode! CPU#{} -> 0x{:08x}\n", hart, epc);
+                //return_pc += 4;
+            }
+            // Page faults
+            12 => {
+                // Instruction page fault
+                println!(
+                    "Instruction page fault CPU#{} -> 0x{:08x}: 0x{:08x}",
+                    hart, epc, tval
+                );
+                return_pc += 4;
+            }
+            13 => {
+                // Load page fault
+                println!(
+                    "Load page fault CPU#{} -> 0x{:08x}: 0x{:08x}",
+                    hart, epc, tval
+                );
+                return_pc += 4;
+            }
+            15 => {
+                // Store page fault
+                println!(
+                    "Store page fault CPU#{} -> 0x{:08x}: 0x{:08x}",
+                    hart, epc, tval
+                );
+                return_pc += 4;
+            }
+            _ => {
+                panic!("Unhandled sync trap CPU#{} -> {}\n", hart, cause_num);
+            }
+        }
+    };
+    // Finally, return the updated program counter
+    return_pc
+}