#![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 {}
}
"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",
extern "C" fn entry() -> ! {
// Init serial console
use crate::console;
+ use core::arch::asm;
console::init_console();
println!("Console init complete");
// 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
}
--- /dev/null
+# 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
+
--- /dev/null
+#[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
+}