diff --git a/20240521-hello_harts.png b/20240521-hello_harts.png new file mode 100644 index 0000000..4bcd4dc Binary files /dev/null and b/20240521-hello_harts.png differ diff --git a/README.md b/README.md index b1af09e..70618a4 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ A kernel for RISC-V written in Rust. Currently focused on running on QEMU generi ![A flowchart showing the kernel initialization process. On the left column are hart 0-specific steps, on the right are steps for other harts. The first stage happens in the _entry code. 1. Setting global, stack, and thread pointers. 2. Initializing the BSS (main hart only). The second stage shows the start code, where both the main hart and the other harts prepare CSRs for S-mode interrupts. This stage is also where PMP and timers would be set up. The final stage is for the main function, where the main hart (hart 0) initializes the console, paging system, processing system, and interrupts before the first user process. While hart 0 is initializing, the other harts wait for it to finish. Once hart 0 is done, the other harts will enable paging and interrupts, and all harts will enter the scheduler.](20240520-kernel_init_diagram.png) +* **2024-05-21:** I've removed the original `entry.s` and replaced it with what was formerly `entry_alt.s`. The new start() and main() functions are based on the implementations in the xv6 kernel. Next steps are setting up the page tables and process tables, so that everything works better. + +![A picture of the current console output as of May 21, 2024. Each hart prints a message which says "Hello from hart" followed by the hart id.](20240521-hello_harts.png) + ## Research and implement - [ ] Basics diff --git a/src/entry.S b/src/entry.S index 37cbd36..2365cbd 100644 --- a/src/entry.S +++ b/src/entry.S @@ -1,32 +1,30 @@ -# entry.S +# entry.s .option norvc .section .data .section .text.init .global _entry _entry: - # Any hardware threads (hart) that are not bootstrapping - # need to wait for an IPI - csrr t0, mhartid - bnez t0, 3f - - # SATP should be zero, but let's make sure - csrw satp, zero - + # Set global pointer (gp) + # Important to have relaxation off for this instruction .option push .option norelax - la gp, _global_pointer + la gp, _global_pointer .option pop - li t5, 0xffff - csrw medeleg, t5 - csrw mideleg, t5 + # Set thread pointer (tp) to hart id + csrr tp, mhartid + # Set stack pointer (sp) for all harts la sp, _stack_end + li t0, 0x10000 # Give each hart plenty of space for their stacks + mul t0, t0, tp + sub sp, sp, t0 - li t0, (0b11 << 11) | (1 << 7) | (1 << 3) - csrw mstatus, t0 + # Jump to start if not hart 0 + bnez tp, 2f + # Prepare BSS section if hart 0 la t0, _bss_start la t1, _bss_end bgeu t0, t1, 2f @@ -35,9 +33,5 @@ _entry: addi t0, t0, 1 bne t0, t1, 1b 2: - j 4f -3: - wfi - j 3b -4: - tail start \ No newline at end of file + # Tail call Rust start function + tail start diff --git a/src/entry.rs b/src/entry.rs index e41550e..cdc5970 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -1,7 +1,16 @@ // src/entry.rs +#![no_std] +#![no_main] + +use crate::uart; + +mod heap; +mod uart; static INIT_LOCK: spin::Once<()> = spin::Once::new(); +core::arch::global_asm!(include_str!("entry.s")); + /// After some initialization in asm/entry.S, the kernel will jump here and /// each hart will have its own setup sequence. #[no_mangle] @@ -45,7 +54,7 @@ extern "C" fn main() { INIT_LOCK.call_once(|| { // Disable machine interrupts while initializing interrupt::machine::disable(); - // TODO Initialize console + console_init(); // TODO Write boot message // TODO Set up paging @@ -64,6 +73,12 @@ extern "C" fn main() { riscv::asm::fence(); // Emit a fence just in case }); + + // poll console for input and print characters back + loop { + let c = crate::uart::CONSOLE.lock().as_mut().and_then(crate::uart::Device::get); + if let Some(c) = c { crate::print!("{}", c as char); } + } } else { INIT_LOCK.wait(); riscv::asm::fence(); // Emit a fence just in case @@ -71,14 +86,18 @@ extern "C" fn main() { } } -#[no_mangle] -extern "C" fn kinit() { - use crate::uart; - uart::Device::new(0x1000_0000); +fn console_init() { + // Initialize heap + unsafe { crate::heap::init(); }; + + // Set up UART and print to console + crate::uart::init_console(0x1000_0000); + crate::println!("Hello from hart {}!", hartid()); } -#[no_mangle] -extern "C" fn kinit_hart() {} +fn kinit() {} + +fn kinit_hart() {} #[inline] fn hartid() -> usize { diff --git a/src/entry_alt.S b/src/entry_alt.S deleted file mode 100644 index 23d4d01..0000000 --- a/src/entry_alt.S +++ /dev/null @@ -1,37 +0,0 @@ -# entry.S -.option norvc -.section .data - -.section .text.init -.global _entry_alt -_entry_alt: - # Set global pointer (gp) - # Important to have relaxation off for this instruction -.option push -.option norelax - la gp, _global_pointer -.option pop - - # Set thread pointer (tp) to hart id - csrr tp, mhartid - - # Set stack pointer (sp) for all harts - la sp, _stack_end - li t0, 0x10000 # Give each hart plenty of space for their stacks - mul t0, t0, tp - sub sp, sp, t0 - - # Jump to start if not hart 0 - bnez tp, 2f - - # Prepare BSS section if hart 0 - la t0, _bss_start - la t1, _bss_end - bgeu t0, t1, 2f -1: - sb zero, 0(t0) - addi t0, t0, 1 - bne t0, t1, 1b -2: - # Tail call Rust start function - tail start diff --git a/src/main.rs b/src/main.rs index eb30fea..17aa2ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,40 +5,120 @@ use core::panic::PanicInfo; -mod entry; mod heap; -mod trap; mod uart; -core::arch::global_asm!(include_str!("entry.S")); +static INIT_LOCK: spin::Once<()> = spin::Once::new(); +core::arch::global_asm!(include_str!("entry.s")); + +/// After some initialization in asm/entry.s, the kernel will jump here and +/// each hart will have its own setup sequence. #[no_mangle] -extern "C" fn start() -> ! { - // UNSAFE: Called exactly once, right here. - unsafe { heap::init() }; +unsafe extern "C" fn start() { + use core::arch::asm; + use riscv::register::{mepc, mstatus, satp, sie, sstatus}; - // Now we're free to use dynamic allocation! - // { - // extern crate alloc; - // use alloc::boxed::Box; - // let _my_heap_pointer = Box::new(10); - // // _my_heap_pointer lives on the heap! - // } + // Set previous privilege for all harts to M-mode, set previous interrupt + // enable, and set floating-point unit to initial state + mstatus::set_mpp(mstatus::MPP::Supervisor); + mstatus::set_mpie(); + mstatus::set_fs(sstatus::FS::Initial); - // println!("This should not print because the console is not initialised."); - uart::init_console(0x1000_0000); - println!("Hello, world!"); + // Disable paging + satp::write(0); - // print current hartid - println!("hartid: {}", riscv::register::mhartid::read()); + // Delegate all traps to S-mode, using inline assembly since we have not + // provided a wrapper for it yet + asm!("li t0, 0xffff", "csrw medeleg, t0", "csrw mideleg, t0",); - // poll console for input and print characters back - loop { - let c = uart::CONSOLE.lock().as_mut().and_then(uart::Device::get); - if let Some(c) = c { print!("{}", c as char); } + // Enable S-mode external, timer, and software interrupts + sie::set_sext(); + sie::set_stimer(); + sie::set_ssoft(); + + // TODO configure PMP + // TODO timer init + + // Trying to figure out why I can't use mret here. Might need + // asm!( + // "la t1, 1f", + // "csrw mepc, t1", + // "la ra, 1f", + // "mret", + // "1:", + // ); + + main(); +} + +fn main() { + use riscv::interrupt; + use riscv::register::{mstatus, sstatus}; + + if hartid() == 0 { + INIT_LOCK.call_once(|| { + // Disable machine interrupts while initializing + interrupt::machine::disable(); + console_init(); + // TODO Write boot message + + // TODO Set up paging + // TODO Set up processes + + // TODO Set up trap vectors + // TODO Set up PLIC + + kinit(); + unsafe { + mstatus::set_mpp(mstatus::MPP::User); + mstatus::set_mpie(); + mstatus::set_spie(); + mstatus::set_fs(sstatus::FS::Initial); + } + + riscv::asm::fence(); // Emit a fence just in case + }); + + // poll console for input and print characters back + loop { + let c = uart::CONSOLE.lock().as_mut().and_then(uart::Device::get); + if let Some(c) = c { print!("{}", c as char); } + } + } else { + INIT_LOCK.wait(); + riscv::asm::fence(); // Emit a fence just in case + kinit_hart(); + println!("Hello from hart {}!", hartid()); } } +fn console_init() { + // Initialize heap + unsafe { heap::init(); }; + + // Set up UART and print to console + uart::init_console(0x1000_0000); + println!("Hello from hart {}!", hartid()); +} + +fn kinit() {} + +fn kinit_hart() {} + +#[inline] +fn hartid() -> usize { + use core::arch::asm; + let id: usize; + unsafe { + asm!( + "mv {id}, tp", + id = out(reg) id, + ); + } + id +} + #[no_mangle] extern "C" fn eh_personality() {}