Compare commits

...

3 commits

11 changed files with 167 additions and 181 deletions

View file

@ -24,4 +24,5 @@ runner = """ qemu-system-riscv64
-s -s
-nographic -nographic
-serial mon:stdio -serial mon:stdio
-bios """ -bios none
-kernel """

22
Cargo.lock generated
View file

@ -26,7 +26,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"linked_list_allocator", "linked_list_allocator",
"riscv", "riscv",
"spinning_top 0.3.0", "spin",
] ]
[[package]] [[package]]
@ -35,7 +35,7 @@ version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286"
dependencies = [ dependencies = [
"spinning_top 0.2.5", "spinning_top",
] ]
[[package]] [[package]]
@ -64,6 +64,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]] [[package]]
name = "spinning_top" name = "spinning_top"
version = "0.2.5" version = "0.2.5"
@ -72,12 +81,3 @@ checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0"
dependencies = [ dependencies = [
"lock_api", "lock_api",
] ]
[[package]]
name = "spinning_top"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300"
dependencies = [
"lock_api",
]

View file

@ -6,4 +6,4 @@ edition = "2021"
[dependencies] [dependencies]
linked_list_allocator = "0.10.5" linked_list_allocator = "0.10.5"
riscv = "0.11.1" riscv = "0.11.1"
spinning_top = "0.3.0" spin = "0.9.8"

View file

@ -3,15 +3,17 @@ created: 2024-05-13T14:28:49-05:00
modified: 2024-05-19T17:41:01-05:00 modified: 2024-05-19T17:41:01-05:00
--- ---
# kernel
A kernel for RISC-V written in Rust. Currently focused on running on QEMU generic riscv64, advice and assistance welcome & encouraged. A kernel for RISC-V written in Rust. Currently focused on running on QEMU generic riscv64, advice and assistance welcome & encouraged.
# Progress ## Progress
* **2024-05-19:** As of right now, the basic "stuff" for initializing the kernel is there. The diagram below shows what I'm thinking as far as what the init process should be. This is what I'm currently working on implementing, and I will update this chart (and prettify it) more * **2024-05-19:** As of right now, the basic "stuff" for initializing the kernel is there. The diagram below shows what I'm thinking as far as what the init process should be. This is what I'm currently working on implementing, and I will update this chart (and prettify it) more
![Flowchart (tentatively) showing the plan for the kernel's initialization process. On the left is hart 0, the main hardware thread, and on the right is hart 1...n, all other hardware threads. All harts will set the global, stack, and thread pointers, disable paging, and set the mstatus and mepc control status registers, before doing their own independent init processes. Hart 0 will disable interrupts and enter the primary initialization function, kinit, before starting the first user process. Other harts will enable interrupts, do their own init, kinit_hart, then wait for a software inter-processor interrupt.](20240519-kernel_init_diagram.png) ![Flowchart (tentatively) showing the plan for the kernel's initialization process. On the left is hart 0, the main hardware thread, and on the right is hart 1...n, all other hardware threads. All harts will set the global, stack, and thread pointers, disable paging, and set the mstatus and mepc control status registers, before doing their own independent init processes. Hart 0 will disable interrupts and enter the primary initialization function, kinit, before starting the first user process. Other harts will enable interrupts, do their own init, kinit_hart, then wait for a software inter-processor interrupt.](20240519-kernel_init_diagram.png)
# Research and implement ## Research and implement
- [ ] Basics - [ ] Basics
- [ ] Processes - [ ] Processes
@ -30,6 +32,6 @@ A kernel for RISC-V written in Rust. Currently focused on running on QEMU generi
- [ ] Device drivers - [ ] Device drivers
- [ ] User applications - [ ] User applications
# Resources ## Resources
See [RESOURCES.md](RESOURCES.md) See [RESOURCES.md](RESOURCES.md)

View file

@ -2,7 +2,7 @@
fn main() { fn main() {
// Tell ld to use linker script. // Tell ld to use linker script.
println!("cargo::rustc-link-arg=-Tsrc/script.lds"); println!("cargo::rustc-link-arg=-Tlds/kernel.lds");
// Don't do any magic linker stuff. // Don't do any magic linker stuff.
println!("cargo::rustc-link-arg=--omagic"); println!("cargo::rustc-link-arg=--omagic");
} }

View file

@ -1,7 +1,7 @@
/* src/script.lds */ /* lds/kernel.lds */
OUTPUT_ARCH("riscv") OUTPUT_ARCH("riscv")
ENTRY(_enter) ENTRY(_entry)
MEMORY { MEMORY {
ram (wxa) : ORIGIN = 0x80000000, LENGTH = 128M ram (wxa) : ORIGIN = 0x80000000, LENGTH = 128M

43
src/entry.S Normal file
View file

@ -0,0 +1,43 @@
# 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
.option push
.option norelax
la gp, _global_pointer
.option pop
li t5, 0xffff
csrw medeleg, t5
csrw mideleg, t5
la sp, _stack_end
li t0, (0b11 << 11) | (1 << 7) | (1 << 3)
csrw mstatus, t0
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:
j 4f
3:
wfi
j 3b
4:
tail start

View file

@ -1,102 +1,86 @@
// src/entry.rs // src/entry.rs
/// This is the first function called when the system boots. In this function, static READY: spin::Once<()> = spin::Once::new();
/// we set up important control status registers (CSR) such as `mstatus` and
/// `satp`. The global, stack, and thread pointers are also configured here. /// After some initialization in asm/entry.S, the kernel will jump here and
/// each hart will have its own setup sequence.
#[no_mangle] #[no_mangle]
#[link_section = ".text.init"] unsafe extern "C" fn _start() {
unsafe extern "C" fn entry() {
use core::arch::asm; use core::arch::asm;
use riscv::register::{mhartid, mstatus, satp, sstatus}; use riscv::register::{mepc, mstatus, satp, sie, sstatus};
// Global pointer `gp` // Set previous privilege for all harts to M-mode, set previous interrupt
// // enable, and set floating-point unit to initial state
// Push current option stack to temporarily disable relaxation, load mstatus::set_mpp(mstatus::MPP::Supervisor);
// _global_pointer symbol (provided by linker), then pop option stack. mstatus::set_mpie();
//
// Important to keep relaxation off for this instruction, so that
// the instruction is emitted as:
// 1:
// auipc gp, %pcrel_hi(_global_pointer)
// addi gp, gp, %pcrel_lo(1b)"
// instead of:
// mv gp, gp
asm!(
".option push",
".option norelax",
"la gp, _global_pointer",
".option pop",
);
// Stack pointer `sp`
//
// Set up the stack pointer for each hart, since each hart will have its own stack.
asm!("la sp, _stack_end");
// Thread pointer `tp`
//
// Set up the thread pointer, which will hold the hart id for each hart.
write_tp(&mhartid::read());
satp::write(0);
mstatus::set_mpp(mstatus::MPP::Machine);
mstatus::set_fs(sstatus::FS::Initial); mstatus::set_fs(sstatus::FS::Initial);
asm!( // Store ref to main in M-mode exception program counter
"csrw mie, x0", mepc::write(main as usize);
"la t1, {kinit}", // Disable paging
"csrw mepc, t1", satp::write(0);
"la ra, 2f", // Delegate all traps to S-mode, using inline assembly since we have not
"mret", // provided a wrapper for it yet
"2:", asm!("li t0, 0xffff", "csrw medeleg, t0", "csrw mideleg, t0",);
kinit = sym kinit,
); // Enable S-mode external, timer, and software interrupts
sie::set_sext();
sie::set_stimer();
sie::set_ssoft();
// TODO configure PMP
// TODO timer init
// Use mret to jump to main
asm!("mret");
} }
#[no_mangle] extern "C" fn main() {
extern "C" fn start() { use riscv::interrupt;
// TODO start (start for hart 0) use riscv::register::{mstatus, sstatus};
}
#[no_mangle] if hartid() == 0 {
extern "C" fn hstart() { READY.call_once(|| {
// TODO starth (start for other harts) // Disable machine interrupts while initializing
} interrupt::machine::disable();
// TODO Initialize console
// TODO Write boot message
// TODO ...then kinit // 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();
});
} else {
READY.wait();
kinit_hart();
}
}
#[no_mangle] #[no_mangle]
extern "C" fn kinit() { extern "C" fn kinit() {
use crate::uart; use crate::uart;
use core::arch::asm;
uart::Device::new(0x1000_0000); uart::Device::new(0x1000_0000);
if read_tp() == 0 {
// Clear bss and set stack pointer
// Only hart 0 does this
unsafe {
asm!(
"la t0, _bss_start",
"la t1, _bss_end",
"bgeu t0, t1, 2f",
"1:",
"sw x0, (t0)", // Store zero as word at t0
"addi t0, t0, 4",
"bne t0, t1, 1b",
"2:",
);
}
}
} }
#[no_mangle] #[no_mangle]
extern "C" fn kinit_hart() {} extern "C" fn kinit_hart() {}
#[inline] #[inline]
fn read_tp() -> usize { fn hartid() -> usize {
use core::arch::asm; use core::arch::asm;
let id: usize; let id: usize;
unsafe { unsafe {
@ -107,14 +91,3 @@ fn read_tp() -> usize {
} }
id id
} }
#[inline]
fn write_tp(id: &usize) {
use core::arch::asm;
unsafe {
asm!(
"csrw mhartid, {id}",
id = in(reg) id,
);
}
}

37
src/entry_alt.S Normal file
View file

@ -0,0 +1,37 @@
# 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

View file

@ -10,79 +10,9 @@ mod heap;
mod trap; mod trap;
mod uart; mod uart;
#[naked] core::arch::global_asm!(include_str!("entry.S"));
#[no_mangle] #[no_mangle]
#[link_section = ".text.init"]
unsafe extern "C" fn _enter() -> ! {
// FIXME see if possible to replace this somehow...
use core::arch::asm;
asm!(
// load hartid into `tp``
// if hartid =/= 0, then jump to busy loop
"csrr tp, mhartid",
"bnez tp, 3f",
// before we use the `la` pseudo-instruction for the first time,
// we need to set `gp` (look up linker relaxation)
".option push", // pushes the current option stack
".option norelax", // disable relaxation so we can properly set `gp`
// this instruction compiles to:
// 1:
// auipc gp, %pcrel_hi(_global_pointer)
// addi gp, gp, %pcrel_lo(1b)
// if relaxation were on, this would simply be `mv gp, gp` or `addi gp, gp, 0`
"la gp, _global_pointer",
".option pop", // pops the current option stack
// delegate all traps to S-mode trap handler
"li t5, 0xffff",
"csrw medeleg, t5", // delegate all machine exceptions
"csrw mideleg, t5", // delegate all machine interrupts
// set the stack pointer
"la sp, _stack_end",
// Make sure machine mode is set, and enable coarse interrupts
"li t0, (0b11 << 11) | (1 << 7) | (1 << 3)",
"csrw mstatus, t0",
// Set mtvec to the location of our trap handler function
"la t1, {trap_vector}",
"csrw mtvec, t1",
// Set MSIE, MTIE, and MEIE on machine interrupt enable CSR:
// (1 << 3) = MSIE to enable machine-/M-mode software interrupts
// | (1 << 7) = MTIE to enable M-mode timer interrupts (disabled for now)
// | (1 << 11) = MEIE to enable M-mode external interrupts
"li t2, (1 << 3) | (1 << 11)",
"csrw mie, t2",
// clear the BSS
"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:",
"j 4f",
// BSS is clear!
// busy loop if hartid =/= 0
"3:",
"wfi", // wait for interrupt (or nop)
"j 3b",
"4:",
// "tail-call" to {start} (call without saving a return address)
"tail {start}",
start = sym start, // {start} refers to the function [start] below
trap_vector = sym trap::trap_handler, // {trap_vector} refers to function [trap::trap_handler]
options(noreturn) // we must handle "returning" from assembly
);
}
extern "C" fn start() -> ! { extern "C" fn start() -> ! {
// UNSAFE: Called exactly once, right here. // UNSAFE: Called exactly once, right here.
unsafe { heap::init() }; unsafe { heap::init() };

View file

@ -1,9 +1,9 @@
// src/uart.rs // src/uart.rs
//! This module provides access to the UART console. //! This module provides access to the UART console.
use spinning_top::Spinlock; use spin::Mutex; // Replaces spinning_top crate
pub static CONSOLE: Spinlock<Option<Device>> = Spinlock::new(None); pub static CONSOLE: Mutex<Option<Device>> = Mutex::new(None);
/// Represents an initialised UART device. /// Represents an initialised UART device.
pub struct Device { pub struct Device {
@ -65,7 +65,7 @@ impl core::fmt::Write for Device {
/// # Safety /// # Safety
/// `base` must point to the base address of a UART device. /// `base` must point to the base address of a UART device.
pub fn init_console(base: usize) { pub fn init_console(base: usize) {
let mut console: spinning_top::lock_api::MutexGuard<spinning_top::RawSpinlock, Option<Device>> = CONSOLE.lock(); let mut console = CONSOLE.lock();
*console = Some(Device::new(base)); *console = Some(Device::new(base));
} }