From 53043fd3cdbbc10e6f30af2915c40d252b1eb177 Mon Sep 17 00:00:00 2001 From: Julien THILLARD Date: Fri, 30 Jan 2026 16:22:53 +0100 Subject: [PATCH] Supervisor mode --- .gdbinit | 3 + ilm.ld | 59 ++++++------- src/boot.rs | 57 ++++++++++--- src/boot/sbi.rs | 11 +++ src/critical_section.rs | 8 +- src/interrupt.rs | 178 ++++++++++++++++++++++++++++++++++++++++ src/main.rs | 18 ++-- src/panic_handler.rs | 29 +++---- src/riscv.rs | 93 +++++++++++++++++++-- src/time.rs | 66 +++++++++++++++ src/vga.rs | 43 +++------- 11 files changed, 457 insertions(+), 108 deletions(-) create mode 100644 src/boot/sbi.rs create mode 100644 src/interrupt.rs create mode 100644 src/time.rs diff --git a/.gdbinit b/.gdbinit index 24efe1e..d4d99b1 100644 --- a/.gdbinit +++ b/.gdbinit @@ -1 +1,4 @@ +file target/riscv64/debug/kernel-rust target remote localhost:1234 +break machine_mode_entry +c diff --git a/ilm.ld b/ilm.ld index 11a3b5d..d1e45b0 100644 --- a/ilm.ld +++ b/ilm.ld @@ -1,45 +1,36 @@ /* * ld directives the for barmetal RISCV */ -OUTPUT_ARCH( "riscv" ) +OUTPUT_ARCH(riscv) +ENTRY(entry) -MEMORY -{ - ram (wxa) : ORIGIN = 0x80000000, LENGTH = 128M +MEMORY { + RAM (wxa) : ORIGIN = 0x80000000, LENGTH = 128M } SECTIONS { - /* The kernel starts at 0x80000000 */ - . = 0x80000000; - .text : { - ENTRY(entry) - KEEP(*(.text.entry)) + /* The kernel starts at 0x80000000 */ + . = 0x80000000; + .text : { + KEEP(*(.text.entry)) - *(.text.init) *(.text) *(.text.*) - _etext = .; - } > ram + *(.text .text.*) + } > RAM - .data : { - *(.sdata) *(.sdata.*) - *(.fini) - *(.anno) - *(.rodata) *(.rodata.*) - *(__ex_table) - *(.data) *(.data.*) - _edata = .; - } > ram + .rodata : { + *(.rodata .rodata.*) + } > RAM - .bss : { - /* On veut un alignement sur 8 octets */ - . = ALIGN(8); - __bss_start = .; - *(.sbss) *(.sbss.*) - *(.bss) *(.bss.*) - *(scommon) *(COMMON) - . = ALIGN(8); - __bss_end = .; - PROVIDE(_heap_start = __bss_end + 8); - PROVIDE(_heap_end = ORIGIN(ram) + LENGTH(ram)); - } > ram - _end = .; + .data : { + *(.data .data.*) + } > RAM + + .bss : ALIGN(8) { + __bss_start = .; + *(.bss .bss.*) + __bss_end = .; + } > RAM + + _heap_start = ALIGN(8); + _heap_end = ORIGIN(RAM) + LENGTH(RAM); } diff --git a/src/boot.rs b/src/boot.rs index d45500f..e504b76 100644 --- a/src/boot.rs +++ b/src/boot.rs @@ -1,11 +1,48 @@ -use core::arch::global_asm; +use core::arch::naked_asm; -global_asm!( - ".section .text.entry - .globl entry -entry: - la sp, _heap_end - jal main -loop: - j loop" -); +use crate::{ + clear_csr, + interrupt::{setup_machine_trap_handler, setup_supervisor_trap_handler}, + mret, set_csr, supervisor_mode_entry, write_csr, +}; + +pub mod sbi; + +#[unsafe(naked)] +#[unsafe(no_mangle)] +pub extern "C" fn entry() { + naked_asm!( + " + la sp, _heap_end + jal machine_mode_entry + 1: + j 1b" + ) +} + +#[unsafe(no_mangle)] +pub extern "C" fn machine_mode_entry() { + unsafe { setup_machine_trap_handler() }; + + // Set supervisor mode + set_csr!(mstatus, 1 << 11); + clear_csr!(mstatus, 1 << 12); + + write_csr!(mepc, supervisor_mode_entry as *const () as u64); + // Allow all R/W/X + write_csr!(pmpaddr0, 0xffff_ffff_ffff_ffffu64); + // NAPOT + write_csr!(pmpcfg0, 0b0000_1111); + + // Desactivate paging + write_csr!(satp, 0); + + // Delegate timer interrupt + set_csr!(mideleg, 1 << 5); + + unsafe { + setup_supervisor_trap_handler(); + }; + + mret!(); +} diff --git a/src/boot/sbi.rs b/src/boot/sbi.rs new file mode 100644 index 0000000..ae81078 --- /dev/null +++ b/src/boot/sbi.rs @@ -0,0 +1,11 @@ +#[non_exhaustive] +#[repr(u64)] +pub enum EID { + Time = 0x54494D45, +} + +#[non_exhaustive] +#[repr(u64)] +pub enum TimeFID { + SetTimer = 0x0, +} diff --git a/src/critical_section.rs b/src/critical_section.rs index 604626d..8948b18 100644 --- a/src/critical_section.rs +++ b/src/critical_section.rs @@ -1,18 +1,18 @@ use critical_section::RawRestoreState; -use crate::riscv::{disable_interrupt, get_interrupt_state, restore_interrupt}; +use crate::riscv::{disable_supervisor_interrupt, get_supervisor_interrupt_state, restore_supervisor_interrupt}; struct MyCriticalSection; critical_section::set_impl!(MyCriticalSection); unsafe impl critical_section::Impl for MyCriticalSection { unsafe fn acquire() -> RawRestoreState { - let restore = get_interrupt_state(); - disable_interrupt(); + let restore = get_supervisor_interrupt_state(); + disable_supervisor_interrupt(); restore } unsafe fn release(token: RawRestoreState) { - restore_interrupt(token); + restore_supervisor_interrupt(token); } } diff --git a/src/interrupt.rs b/src/interrupt.rs new file mode 100644 index 0000000..7f1a716 --- /dev/null +++ b/src/interrupt.rs @@ -0,0 +1,178 @@ +use crate::{ + boot::sbi::{TimeFID, EID}, + clear_csr, generate_trap_handler, read_csr, + riscv::disable_interrupt, + set_csr, + time::{setup_next_timer_interrupt, IRQ_M_TIMER}, + write_csr, +}; +use core::arch::naked_asm; + +use crate::time::{setup_timer_interrupt, timer_interrupt}; + +#[unsafe(no_mangle)] +unsafe extern "C" fn machine_trap_handler(mcause: u64, mie: u64, mip: u64) { + let mepc = read_csr!(mepc); + let mtval = read_csr!(mtval); + if mcause & (1 << 63) == 0 { + let message = match mcause & !(1 << 63) { + 0 => "Instruction address misaligned", + 1 => "Instruction access fault", + 2 => "Illegal instruction", + 3 => "Breakpoint", + 4 => "Load address misaligned", + 5 => "Load access fault", + 6 => "Store/AMO address misaligned", + 7 => "Store/AMO access fault", + 8 => "Environment call from U-mode", + 9 => { + // Environment call from S-mode + let eid: u64; + let fid: u64; + unsafe { + core::arch::asm!( + "mv {}, a7", + "mv {}, a6", + out(reg) eid, + out(reg) fid, + ); + } + + #[allow(clippy::single_match)] + match eid { + c if c == EID::Time as u64 => match fid { + c if c == TimeFID::SetTimer as u64 => { + clear_csr!(mip, 1 << 5); + setup_next_timer_interrupt(); + } + _ => {} + }, + _ => {} + } + + // Advance mepc to exit the ecall + let mepc = read_csr!(mepc); + write_csr!(mepc, mepc + 4); + return; + } + 11 => "Environment call from M-mode", + 12 => "Instruction page fault", + 13 => "Load page fault", + 15 => "Store/AMO page fault", + 16 => "Double trap", + 18 => "Software check", + 19 => "Hardware error", + _ => panic!( + "unknown exception, mcause={}, mie={}, mip={}", + mcause, mie, mip + ), + }; + disable_interrupt(); + panic!("{} at PC=0x{:x}, mtval={}", message, mepc, mtval); + } else { + #[allow(clippy::single_match)] + match mcause & !(1 << 63) { + 7 => { + setup_next_timer_interrupt(); + set_csr!(mip, 1 << 5); + } + _ => {} + } + } +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn supervisor_trap_handler(scause: u64, _sie: u64, _sip: u64) { + #[allow(clippy::single_match)] + match scause & !(1 << 63) { + 5 => { + unsafe { + core::arch::asm!( + "ecall", + in("a0") 0, + in("a6") TimeFID::SetTimer as u64, + in("a7") EID::Time as u64, + ); + } + timer_interrupt(); + } + _ => {} + } +} + +pub unsafe fn setup_machine_trap_handler() { + write_csr!(mtvec, _machine_mode_trap); + set_csr!(mie, IRQ_M_TIMER); +} +pub unsafe fn setup_supervisor_trap_handler() { + write_csr!(stvec, _supervisor_mode_trap); + setup_timer_interrupt(); +} + +generate_trap_handler! { + _machine_mode_trap, machine_trap_handler, m +} +generate_trap_handler! { + _supervisor_mode_trap, supervisor_trap_handler, s +} + +#[macro_export] +macro_rules! generate_trap_handler { + ($name:ident, $jump_to:ident, $mode:ident) => { + #[unsafe(naked)] + #[unsafe(no_mangle)] + unsafe extern "C" fn $name() { + naked_asm!( + concat!(" + addi sp, sp, -128 + + sd ra, 120(sp) + + sd a0, 0(sp) + sd a1, 8(sp) + sd a2, 16(sp) + sd a3, 24(sp) + sd a4, 32(sp) + sd a5, 40(sp) + sd a6, 48(sp) + sd a7, 56(sp) + + sd t0, 64(sp) + sd t1, 72(sp) + sd t2, 80(sp) + sd t3, 88(sp) + sd t4, 96(sp) + sd t5, 104(sp) + sd t6, 112(sp) + + csrr a0, ", stringify!($mode),"cause + csrr a1, ", stringify!($mode),"ie + csrr a2, ", stringify!($mode),"ip + jal ", stringify!($jump_to), " + + ld a0, 0(sp) + ld a1, 8(sp) + ld a2, 16(sp) + ld a3, 24(sp) + ld a4, 32(sp) + ld a5, 40(sp) + ld a6, 48(sp) + ld a7, 56(sp) + + ld t0, 64(sp) + ld t1, 72(sp) + ld t2, 80(sp) + ld t3, 88(sp) + ld t4, 96(sp) + ld t5, 104(sp) + ld t6, 112(sp) + + ld ra, 120(sp) + + addi sp, sp, 128 + + ", stringify!($mode),"ret") + ) + } + }; +} diff --git a/src/main.rs b/src/main.rs index 01b943e..7d94f56 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,16 @@ #![no_std] #![no_main] +#![allow(static_mut_refs)] +#![feature(riscv_ext_intrinsics)] -use core::arch::asm; +use core::arch::riscv64::wfi; use embedded_alloc::LlffHeap as Heap; use log::info; use crate::{ io::init_log, + riscv::enable_supervisor_interrupt, vga::{Color, Vga}, }; @@ -15,9 +18,11 @@ extern crate alloc; mod boot; mod critical_section; +mod interrupt; mod io; mod panic_handler; mod riscv; +mod time; mod uart; mod vga; @@ -26,17 +31,18 @@ pub const HEAP_SIZE: usize = 40960; static HEAP: Heap = Heap::empty(); #[unsafe(no_mangle)] -pub extern "C" fn main() { +pub extern "C" fn supervisor_mode_entry() { unsafe { embedded_alloc::init!(HEAP, HEAP_SIZE); + init_log().unwrap(); + Vga::init(); + enable_supervisor_interrupt(); } - init_log().unwrap(); - Vga::init(); info!("Hello World !"); - unsafe { Vga::draw_string(10, 10, "Hello World !", Color::WHITE) }; + unsafe { Vga::draw_string(10, 10, "Hello World !", Color::WHITE, Color::BLACK) }; loop { - unsafe { asm!("wfi") } + unsafe { wfi() } } } diff --git a/src/panic_handler.rs b/src/panic_handler.rs index f16a772..3233194 100644 --- a/src/panic_handler.rs +++ b/src/panic_handler.rs @@ -1,31 +1,32 @@ -use core::arch::asm; +use core::arch::riscv64::wfi; -use alloc::{ - format, - string::{String, ToString}, -}; +use alloc::{format, string::ToString}; use log::error; -use crate::vga::{Color, FONT_HEIGHT, Vga}; +use crate::vga::{Color, Vga, FONT_HEIGHT}; #[panic_handler] fn panic(panic_info: &core::panic::PanicInfo) -> ! { error!("PANIC !"); - let mut panic_message = if let Some(message) = panic_info.message().as_str() { - message.to_string() - } else { - String::new() - }; + let mut panic_message = panic_info.message().to_string(); if let Some(location) = panic_info.location() { panic_message = format!("{panic_message} at {}:{}", location.file(), location.line()); } error!("{panic_message}"); Vga::clear_screen(Color::WHITE); - unsafe { Vga::draw_string(0, 0, "PANIC !", Color::BLACK) }; - unsafe { Vga::draw_string(0, FONT_HEIGHT as u16, panic_message, Color::BLACK) }; + unsafe { Vga::draw_string(0, 0, "PANIC !", Color::BLACK, Color::WHITE) }; + unsafe { + Vga::draw_string( + 0, + FONT_HEIGHT as u16, + panic_message, + Color::BLACK, + Color::WHITE, + ) + }; loop { - unsafe { asm!("wfi") } + unsafe { wfi() } } } diff --git a/src/riscv.rs b/src/riscv.rs index 1ec6fbf..21fdd5d 100644 --- a/src/riscv.rs +++ b/src/riscv.rs @@ -1,17 +1,40 @@ -use core::arch::asm; +#![allow(unused)] -const MSTATUS_MIE: usize = 0x8; +use core::arch::naked_asm; + +use crate::clear_csr; +use crate::read_csr; +use crate::set_csr; + +pub struct MStatus; +pub struct SStatus; + +impl MStatus { + pub const MIE: usize = 1 << 3; + pub const MPIE: usize = 1 << 7; +} +impl SStatus { + pub const SIE: usize = 1 << 1; + pub const SPIE: usize = 1 << 5; +} pub fn get_interrupt_state() -> bool { - let res: u64; - unsafe { asm!("csrr {}, mstatus", out(reg) res) }; - (res & MSTATUS_MIE as u64) != 0 + (read_csr!(mstatus) & MStatus::MIE as u64) != 0 +} +pub fn get_supervisor_interrupt_state() -> bool { + (read_csr!(sstatus) & SStatus::SIE as u64) != 0 } pub fn enable_interrupt() { - unsafe { asm!("csrs mstatus, {}", in(reg) MSTATUS_MIE) }; + set_csr!(mstatus, MStatus::MIE); +} +pub fn enable_supervisor_interrupt() { + set_csr!(sstatus, SStatus::SIE); } pub fn disable_interrupt() { - unsafe { asm!("csrc mstatus, {}", in(reg) MSTATUS_MIE) }; + clear_csr!(mstatus, MStatus::MIE); +} +pub fn disable_supervisor_interrupt() { + clear_csr!(sstatus, SStatus::SIE); } pub fn restore_interrupt(previous_state: bool) { if previous_state { @@ -20,3 +43,59 @@ pub fn restore_interrupt(previous_state: bool) { disable_interrupt(); } } +pub fn restore_supervisor_interrupt(previous_state: bool) { + if previous_state { + enable_supervisor_interrupt(); + } else { + disable_supervisor_interrupt(); + } +} + +#[macro_export] +macro_rules! read_csr { + ($name:ident) => {{ + let res: u64; + unsafe { core::arch::asm!(concat!("csrr {}, ", stringify!($name)), out(reg) res, options(nomem, nostack)) }; + res + }}; +} +#[macro_export] +macro_rules! write_csr { + ($name:ident, $value:expr) => { + let val = $value; + unsafe { core::arch::asm!(concat!("csrw ", stringify!($name), ", {}"), in(reg) val, options(nomem, nostack)) }; + }; +} +#[macro_export] +macro_rules! set_csr { + ($name:ident, $value:expr) => { + let val = $value; + unsafe { core::arch::asm!(concat!("csrs ", stringify!($name), ", {}"), in(reg) val, options(nomem, nostack)) }; + }; +} +#[macro_export] +macro_rules! clear_csr { + ($name:ident, $value:expr) => { + let val = $value; + unsafe { core::arch::asm!(concat!("csrc ", stringify!($name), ", {}"), in(reg) val, options(nomem, nostack)) }; + }; +} + +#[macro_export] +macro_rules! mret { + () => { + unsafe { core::arch::asm!("mret", options(noreturn)) } + }; +} + +#[unsafe(naked)] +pub extern "C" fn exit_qemu() { + naked_asm!( + " + li a0, 0x100000 + li a1, 0x5555 + sw a1, 0(a0) + wfi + " + ) +} diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..d9125d7 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,66 @@ +use core::time::Duration; + +use alloc::format; + +use crate::{ + set_csr, + vga::{Color, Vga, FONT_WIDTH, WIDTH}, +}; + +pub const IRQ_M_TIMER: u8 = 1 << 7; +pub const IRQ_S_TIMER: u8 = 1 << 5; +const CLINT_TIMER_CMP: *mut u64 = 0x02004000 as *mut u64; +const CLINT_TIMER: *const u64 = 0x0200bff8 as *const u64; +const TIMER_FREQUENCY: u64 = 10000000; // 10MHz +const INTERRUPT_FREQUENCY: u64 = 20; // 20Hz + +static mut START_TIME: Instant = Instant(0); + +pub fn setup_timer_interrupt() { + unsafe { START_TIME = Instant::now() }; + set_csr!(sie, IRQ_S_TIMER); + setup_next_timer_interrupt(); +} + +pub fn setup_next_timer_interrupt() { + unsafe { + core::ptr::write_volatile( + CLINT_TIMER_CMP, + Instant::now().0 + TIMER_FREQUENCY / INTERRUPT_FREQUENCY, + ); + } +} +pub fn timer_interrupt() { + let current_time = elapsed_time_since_startup(); + let seconds = current_time.as_secs(); + let minutes = seconds / 60 % 60; + let hours = seconds / 3600 % 60; + let seconds = seconds % 60; + let formated_time = format!("{:02}:{:02}:{:02}", hours, minutes, seconds); + unsafe { + Vga::draw_string( + (WIDTH - formated_time.len() * FONT_WIDTH) as u16, + 0, + formated_time, + Color::WHITE, + Color::BLACK, + ) + }; +} + +pub fn elapsed_time_since_startup() -> Duration { + unsafe { START_TIME.elapsed() } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Instant(u64); + +impl Instant { + pub fn now() -> Self { + Instant(unsafe { core::ptr::read_volatile(CLINT_TIMER) }) + } + pub fn elapsed(&self) -> Duration { + let now = Self::now(); + Duration::from_nanos((now.0 - self.0) * (1_000_000_000 / TIMER_FREQUENCY)) + } +} diff --git a/src/vga.rs b/src/vga.rs index becfc5b..516fe17 100644 --- a/src/vga.rs +++ b/src/vga.rs @@ -30,7 +30,7 @@ impl Color { pub struct Vga {} impl Vga { - pub fn init() { + pub unsafe fn init() { for i in 0..32 { let addr = PCI_ECAM_BASE_ADDRESS.wrapping_byte_add(i << 11); let header = unsafe { core::ptr::read_volatile(addr) }; @@ -72,35 +72,6 @@ impl Vga { unsafe { *VGA_ADDRESS.add(pixel_index) = color } } - /// # Safety - /// `x` must be less than `WIDTH` and `y` must be less than `HEIGHT` - pub unsafe fn draw_char(x: u16, y: u16, c: char, color: Color) { - let c = if (c as u8 > b'~') || ((c as u8) < b' ') { - b'/' - b' ' - } else { - c as u8 - b' ' - }; - - // Get char position within font plate - let char_x = (c as usize % 32) * FONT_WIDTH; - let char_y = (c as usize / 32) * FONT_HEIGHT; - - for i in 0..(FONT_WIDTH as u16) { - for j in 0..(FONT_HEIGHT as u16) { - let xx = x + i; - let yy = y + j; - - if xx < (WIDTH as u16) - && yy < (HEIGHT as u16) - && unsafe { Self::font_plate_index(char_x as u16 + i, char_y as u16 + j) } - { - unsafe { Self::write_pixel_unsafe(xx, yy, color) } - } - } - } - } - - #[allow(unused)] pub unsafe fn draw_char_bg(x: u16, y: u16, c: char, color: Color, bg_color: Color) { let c = if (c as u8 > b'~') || ((c as u8) < b' ') { b'/' - b' ' @@ -130,7 +101,13 @@ impl Vga { /// # Safety /// The text must have a length that can fit within a `u16` - pub unsafe fn draw_string>(x: u16, mut y: u16, str: T, color: Color) { + pub unsafe fn draw_string>( + x: u16, + mut y: u16, + str: T, + color: Color, + bg_color: Color, + ) { let mut current_x = x; str.as_ref().chars().for_each(|c| unsafe { match c { @@ -142,7 +119,7 @@ impl Vga { current_x = x; } c => { - Self::draw_char(current_x, y, c, color); + Self::draw_char_bg(current_x, y, c, color, bg_color); current_x += FONT_WIDTH as u16; } } @@ -169,4 +146,4 @@ pub const FONT_HEIGHT: usize = 13; pub const FONTPLATE_WIDTH: usize = 32 * FONT_WIDTH; pub const FONTPLATE_HEIGHT: usize = 3 * FONT_HEIGHT; pub const FONTPLATE_SIZE: usize = FONTPLATE_WIDTH * FONTPLATE_HEIGHT / 8; -pub const FONTPLATE: [u8; FONTPLATE_SIZE] = include_font_plate! {"assets/fontplate.png"}; +pub static FONTPLATE: [u8; FONTPLATE_SIZE] = include_font_plate! {"assets/fontplate.png"};