diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..d985023 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,8 @@ +[build] +target = "riscv64gc-unknown-none-elf" + +[target.riscv64gc-unknown-none-elf] +rustflags = "-C target-feature=-c" # Disable compressed instructions + +[unstable] +build-std = ["core", "alloc", "compiler_builtins"] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d91d49 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +log.txt \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..553806a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "rust-analyzer.cargo.target": "riscv64gc-unknown-none-elf", + "rust-analyzer.check.allTargets": false, + "rust-analyzer.imports.preferNoStd": true, + "rust-analyzer.showUnlinkedFileNotification": false, +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..cbce09b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,127 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "allocator" +version = "0.1.0" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + +[[package]] +name = "hal-core" +version = "0.1.0" + +[[package]] +name = "hal-riscv" +version = "0.1.0" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "owo-colors" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" + +[[package]] +name = "pathos" +version = "0.1.0" +dependencies = [ + "allocator", + "elf", + "hal-core", + "hal-riscv", + "once_cell", + "owo-colors", + "spin", + "uart_16550", +] + +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +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]] +name = "uart_16550" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dc00444796f6c71f47c85397a35e9c4dbf9901902ac02386940d178e2b78687" +dependencies = [ + "bitflags", + "rustversion", + "x86", +] + +[[package]] +name = "x86" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2781db97787217ad2a2845c396a5efe286f87467a5810836db6d74926e94a385" +dependencies = [ + "bit_field", + "bitflags", + "raw-cpuid", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2c7b000 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,32 @@ +[workspace] +members = ["allocator", "hal-core", "hal-riscv"] +exclude = ["runner", "usercode"] +resolver = "2" + +[package] +name = "pathos" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +harness = false +test = false + +[[bin]] +name = "pathos" +harness = false + +[dependencies] +hal-riscv = { path = "hal-riscv" } +hal-core = { path = "hal-core" } +allocator = { path = "allocator" } +owo-colors = "4.0.0" +spin = "0.9.8" +once_cell = { version = "1.19.0", features = [ + "alloc", + "race", +], default-features = false } +elf = { version = "0.7.4", default-features = false } +uart_16550 = "0.3.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..2d6f278 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +## PathOS kernel code + +This is the kernel code for the PathOS operating system. It is a simple kernel that runs on the RISC-V architecture. + +## Development + +Install riscv64 toolchain to ~/.local/bin +https://mirrors.edge.kernel.org/pub/tools/crosstool/files/bin/x86_64/13.2.0/x86_64-gcc-13.2.0-nolibc-riscv64-linux.tar.gz + +Install `qemu-system` and `qemu-system-riscv64`. + +zzz \ No newline at end of file diff --git a/allocator/Cargo.toml b/allocator/Cargo.toml new file mode 100644 index 0000000..4714695 --- /dev/null +++ b/allocator/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "allocator" +version = "0.1.0" +edition = "2021" diff --git a/allocator/README.md b/allocator/README.md new file mode 100644 index 0000000..b98cc67 --- /dev/null +++ b/allocator/README.md @@ -0,0 +1,18 @@ +``` +// Smallest block = 8 bytes +// Allocatable memory size = 64 bytes +// Highest order = 3 (2^3 * 8 = 64) +// Calculate order from smallest block and allocatable memory size: +// 2 ^ n = memsize / blocksize +// n = log2(memsize / blocksize) + +// 3 64 +// 2 32 32 +// 1 16 16 16 16 +// 0 8 8 8 8 8 8 8 8 + +// Number of nodes = 2 ^ (order + 1) - 1 +// Total size required to bookkeep allocatable memory = num_nodes * node_size +// For order = 3 and node_size = 8, total size = (2 ^ (3 + 1) - 1) * 8 = 15 * 8 +// = 120 +``` \ No newline at end of file diff --git a/allocator/justfile b/allocator/justfile new file mode 100644 index 0000000..07ba380 --- /dev/null +++ b/allocator/justfile @@ -0,0 +1,5 @@ +test: + cargo test --lib --target x86_64-unknown-linux-gnu --jobs 1 + + + diff --git a/allocator/src/buddy.rs b/allocator/src/buddy.rs new file mode 100644 index 0000000..9c9b10d --- /dev/null +++ b/allocator/src/buddy.rs @@ -0,0 +1,515 @@ +use core::ptr; + +#[cfg(test)] +use std::println; + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum State { + Coalesced = 0, + Allocated = 1, + Free = 2, +} + +#[derive(Debug)] +pub struct BuddyAllocator { + nodes: &'static mut [State], + heap_size: usize, + pub min_block_size: usize, +} + +impl BuddyAllocator { + pub unsafe fn new(ptr: usize, heap_size: usize, min_block_size: usize) -> Self { + let max_order: usize = (heap_size / min_block_size).ilog2() as usize; + let num_nodes: usize = 2usize.pow(max_order as u32 + 1) - 1; + + // This assumes that the memory is zeroed out + let nodes = &mut *ptr::slice_from_raw_parts_mut(ptr as *mut State, num_nodes); + + nodes[0] = State::Free; + + Self { + nodes, + heap_size, + min_block_size, + } + } + + fn split_ancestors(&mut self, idx: usize) { + match Self::parent(idx) { + None => return, // Root node + Some(next) => { + // Avoid messing up already split ancestors + if self.nodes[next] == State::Allocated { + return; + } + + self.split(next); + self.split_ancestors(next); + } + } + } + + fn split(&mut self, idx: usize) { + if let Some(left) = self.left(idx) { + self.nodes[left] = State::Free; + } + if let Some(right) = self.right(idx) { + self.nodes[right] = State::Free; + } + } + + fn mark_ancestors_allocated(&mut self, idx: usize) { + if idx == 0 { + return; + } + + let parent = Self::parent(idx).unwrap(); + self.nodes[parent] = State::Allocated; + self.mark_ancestors_allocated(parent); + } + + fn mark_descendants_allocated(&mut self, idx: usize) { + if let Some(left) = self.left(idx) { + self.nodes[left] = State::Allocated; + self.mark_descendants_allocated(left); + } + if let Some(right) = self.right(idx) { + self.nodes[right] = State::Allocated; + self.mark_descendants_allocated(right); + } + } + + fn mark_descendants_unavailable(&mut self, idx: usize) { + if let Some(left) = self.left(idx) { + self.nodes[left] = State::Coalesced; + self.mark_descendants_unavailable(left); + } + if let Some(right) = self.right(idx) { + self.nodes[right] = State::Coalesced; + self.mark_descendants_unavailable(right); + } + } + + pub fn free_block(&mut self, idx: usize) { + assert!(idx < self.nodes.len(), "Index {} out of bounds", idx); + assert_eq!( + self.nodes[idx], + State::Allocated, + "Block not allocated, double free" + ); + + self.nodes[idx] = State::Free; + + if let Some(buddy) = self.buddy(idx) { + if self.nodes[buddy] == State::Free { + return self.coalesce(idx); + } + } + + // When freeing blocks > min_block_size, we need to mark descendants as + // unavailable, because they are marked as allocated. However, here we + // delay coalescing previously possibly allocated blocks, until we cannot + // coalesce parents anymore. + self.mark_descendants_unavailable(idx); + } + + fn coalesce(&mut self, idx: usize) { + let parent = Self::parent(idx).unwrap(); + + if let Some(left) = self.left(parent) { + self.nodes[left] = State::Coalesced; + } + if let Some(right) = self.right(parent) { + self.nodes[right] = State::Coalesced; + } + + self.free_block(parent); // Recursively coalesce ancestors if possible + } + + pub fn order_start_index(&self, block_size: usize) -> usize { + assert!(block_size >= self.min_block_size, "Block size too small"); + + let max_order = (self.heap_size / self.min_block_size).ilog2(); + let order = (block_size / self.min_block_size).ilog2(); + 2usize.pow(max_order as u32 - order) - 1 + } + + pub fn find_block(&mut self, size: usize) -> Result { + assert!(size.is_power_of_two(), "Size is not a power of 2"); + assert!( + size <= self.heap_size, + "Requested size is greater than memory size" + ); + + let search_start_idx = self.order_start_index(size); + let end = 2 * search_start_idx + 1; + assert!( + end <= self.nodes.len(), + "End index out of bounds, not enough nodes for heap size" + ); + + let blocks = &self.nodes[search_start_idx..end]; + + if let Some(first_free) = blocks.iter().position(|b| *b == State::Free) { + let idx = search_start_idx + first_free; + + self.mark_ancestors_allocated(idx); + self.mark_descendants_allocated(idx); + self.nodes[idx] = State::Allocated; + + return Ok(idx); + } + + if let Some(first_to_split) = blocks.iter().position(|b| *b == State::Coalesced) { + let idx = search_start_idx + first_to_split; + + assert_ne!(idx, 0, "Highest order block cannot be unavailable"); + + self.split_ancestors(idx); + self.mark_ancestors_allocated(idx); + self.mark_descendants_allocated(idx); + self.nodes[idx] = State::Allocated; + + return Ok(idx); + } + + Err("No block found for allocation") + } + + fn left(&self, idx: usize) -> Option { + let i = 2 * idx + 1; + if i < self.nodes.len() { + return Some(i); + } + None + } + + fn right(&self, idx: usize) -> Option { + let i = 2 * idx + 2; + if i < self.nodes.len() { + return Some(i); + } + None + } + + fn parent(idx: usize) -> Option { + if idx == 0 { + return None; + } + Some((idx - 1) / 2) + } + + fn buddy(&self, idx: usize) -> Option { + match idx { + 0 => None, + idx if idx % 2 == 0 => Some(idx - 1), + _ => { + assert!(idx < self.nodes.len(), "Index out of bounds"); + Some(idx + 1) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use std::{assert_matches::assert_matches, println}; + + const HEAP_SIZE: usize = 64; + const MIN_BLOCK_SIZE: usize = 8; + const MAX_ORDER: usize = (HEAP_SIZE / MIN_BLOCK_SIZE).ilog2() as usize; + const NUM_NODES: usize = 2usize.pow(MAX_ORDER as u32 + 1) - 1; + + fn assert_descendants_allocated(idx: usize, allocator: &BuddyAllocator, msg: &str) { + assert_eq!( + allocator.nodes[idx], + State::Allocated, + "Node (idx: {}) not marked as allocated: {}", + idx, + msg + ); + + if let Some(left) = allocator.left(idx) { + assert_descendants_allocated(left, allocator, msg); + } + + if let Some(right) = allocator.right(idx) { + assert_descendants_allocated(right, allocator, msg); + } + } + + fn assert_descendants_unallocated(idx: usize, allocator: &BuddyAllocator) { + assert_ne!( + allocator.nodes[idx], + State::Allocated, + "Left child not marked as allocated" + ); + + if let Some(left) = allocator.left(idx) { + assert_descendants_unallocated(left, allocator); + } + if let Some(right) = allocator.right(idx) { + assert_descendants_unallocated(right, allocator); + } + } + + #[test] + fn test_split_ancestors() { + static mut HEAP: [u8; 64] = [0; 64]; + + let mut allocator = + unsafe { BuddyAllocator::new(HEAP.as_ptr() as usize, HEAP_SIZE, MIN_BLOCK_SIZE) }; + + assert_eq!( + allocator.nodes[1], + State::Coalesced, + "32 byte block not marked as unavailable" + ); + + allocator.split_ancestors(7); // First 8 byte block + + for i in &[0, 1, 3, 4, 7, 8] { + assert_eq!( + allocator.nodes[*i], + State::Free, + "Ancestor not marked as free" + ); + } + + // Assert the rest of the nodes are still unavailable + assert_eq!(allocator.nodes[5], State::Coalesced); + assert_eq!(allocator.nodes[6], State::Coalesced); + + for i in 9..NUM_NODES { + assert_eq!( + allocator.nodes[i], + State::Coalesced, + "Node not marked as unavailable" + ); + } + } + + #[test] + fn test_find_smallest_free_block() { + static mut HEAP: [u8; 64] = [0; 64]; + + let mut allocator = + unsafe { BuddyAllocator::new(HEAP.as_ptr() as usize, HEAP_SIZE, MIN_BLOCK_SIZE) }; + + println!("{:#?}", allocator.nodes); + + let idx = allocator.find_block(8).unwrap(); + + assert_eq!(idx, 7, "Block not found at expected index"); + assert_eq!( + allocator.nodes[idx], + State::Allocated, + "Block not marked as allocated" + ); + + // Check if ancestors are also marked as allocated + assert_eq!( + allocator.nodes[3], + State::Allocated, + "16 byte block not marked as allocated" + ); // 16 byte block + assert_eq!( + allocator.nodes[1], + State::Allocated, + "32 byte block not marked as allocated" + ); // 32 byte block + assert_eq!( + allocator.nodes[0], + State::Allocated, + "64 byte block not marked as allocated" + ); // 64 byte block + } + + #[test] + fn test_find_largest_free_block() { + static mut HEAP: [u8; 64] = [0; 64]; + + let mut allocator = + unsafe { BuddyAllocator::new(HEAP.as_ptr() as usize, HEAP_SIZE, MIN_BLOCK_SIZE) }; + + let idx = allocator.find_block(64).unwrap(); + + assert_eq!(idx, 0, "Block not found at expected index"); + assert_eq!( + allocator.nodes[idx], + State::Allocated, + "Block not marked as allocated" + ); + assert_descendants_allocated(idx, &allocator, ""); + } + + #[test] + #[should_panic(expected = "Size is not a power of 2")] + fn test_find_block_not_powerof_2() { + static mut HEAP: [u8; 64] = [0; 64]; + + let mut allocator = + unsafe { BuddyAllocator::new(HEAP.as_ptr() as usize, HEAP_SIZE, MIN_BLOCK_SIZE) }; + allocator.find_block(61).unwrap(); + } + + #[test] + #[should_panic(expected = "Requested size is greater than memory size")] + fn test_find_block_too_big() { + static mut HEAP: [u8; 64] = [0; 64]; + + let mut allocator = + unsafe { BuddyAllocator::new(HEAP.as_ptr() as usize, HEAP_SIZE, MIN_BLOCK_SIZE) }; + allocator.find_block(128).unwrap(); + } + + #[test] + fn test_simple_out_of_space() { + static mut HEAP: [u8; 64] = [0; 64]; + + let mut allocator = + unsafe { BuddyAllocator::new(HEAP.as_ptr() as usize, HEAP_SIZE, MIN_BLOCK_SIZE) }; + + allocator.find_block(32).unwrap(); + allocator.find_block(16).unwrap(); + allocator.find_block(8).unwrap(); + allocator.find_block(8).unwrap(); + + let last = allocator.find_block(8); + assert_matches!(last, Err("No block found for allocation")); + + for i in 0..NUM_NODES { + assert_eq!( + allocator.nodes[i], + State::Allocated, + "Node not marked as allocated" + ); + } + } + + #[test] + fn test_allocate_own_descendants() { + static mut HEAP: [u8; 64] = [0; 64]; + + let mut allocator = + unsafe { BuddyAllocator::new(HEAP.as_ptr() as usize, HEAP_SIZE, MIN_BLOCK_SIZE) }; + + let idx = allocator.find_block(32).unwrap(); + let buddy = allocator.buddy(idx).expect("Node has no buddy"); + + assert_descendants_allocated(idx, &allocator, ""); + assert_descendants_unallocated(buddy, &allocator); + } + + #[test] + fn test_find_block_multiple_times() { + static mut HEAP: [u8; 64] = [0; 64]; + + let mut allocator = + unsafe { BuddyAllocator::new(HEAP.as_ptr() as usize, HEAP_SIZE, MIN_BLOCK_SIZE) }; + + let first = allocator.find_block(32).unwrap(); + let first_buddy = allocator.buddy(first).expect("Node has no buddy"); + assert_descendants_allocated(first, &allocator, ""); + assert_eq!(allocator.nodes[first_buddy], State::Free); + + let second = allocator.find_block(16).unwrap(); + let second_buddy = allocator.buddy(second).expect("Node has no buddy"); + assert_eq!(allocator.nodes[second_buddy], State::Free); + assert_descendants_allocated(second, &allocator, ""); + assert_descendants_allocated(first, &allocator, "Second allocation messes up first"); + + let expected_idx = allocator.left(first_buddy).unwrap(); // Left child of buddy of first + + assert_eq!( + second, expected_idx, + "Block not found at expected index: {:#?}", + allocator.nodes + ); + } + + #[test] + fn test_basic_coalesce() { + static mut HEAP: [u8; 64] = [0; 64]; + + let mut allocator = + unsafe { BuddyAllocator::new(HEAP.as_ptr() as usize, HEAP_SIZE, MIN_BLOCK_SIZE) }; + + let first = allocator.find_block(32).unwrap(); + assert_eq!( + allocator.nodes[1], + State::Allocated, + "Block not marked as allocated" + ); + assert_eq!( + allocator.nodes[allocator.buddy(first).unwrap()], + State::Free, + "Buddy not marked as free" + ); + assert_eq!( + allocator.nodes[0], + State::Allocated, + "Root not marked as allocated" + ); + + allocator.free_block(first); + + assert_eq!(allocator.nodes[1], State::Coalesced, "Block not coalesced"); + assert_eq!( + allocator.nodes[allocator.buddy(first).unwrap()], + State::Coalesced, + "Buddy not coalesced" + ); + assert_eq!(allocator.nodes[0], State::Free, "Root not marked as free"); + } + + #[test] + fn test_full_coalesce() { + static mut HEAP: [u8; 64] = [0; 64]; + + let mut allocator = + unsafe { BuddyAllocator::new(HEAP.as_ptr() as usize, HEAP_SIZE, MIN_BLOCK_SIZE) }; + + let a = allocator.find_block(32).unwrap(); + let b = allocator.find_block(16).unwrap(); + let c = allocator.find_block(8).unwrap(); + let d = allocator.find_block(8).unwrap(); + + println!("{:#?}", allocator.nodes); + + allocator.free_block(d); + allocator.free_block(c); + allocator.free_block(b); + allocator.free_block(a); + + println!("{:#?}", allocator.nodes); + + assert_eq!( + allocator.nodes[0], + State::Free, + "Root not marked as free after coalescing" + ); + + for i in 1..NUM_NODES { + assert_eq!( + allocator.nodes[i], + State::Coalesced, + "Node ({i}) not marked as unavailable" + ); + } + } + + #[test] + #[should_panic] + fn test_request_less_than_min_block_size() { + static mut HEAP: [u8; 64] = [0; 64]; + + let mut allocator = + unsafe { BuddyAllocator::new(HEAP.as_ptr() as usize, HEAP_SIZE, MIN_BLOCK_SIZE) }; + + let idx = allocator.find_block(4); + } +} diff --git a/allocator/src/lib.rs b/allocator/src/lib.rs new file mode 100644 index 0000000..fb4b8bb --- /dev/null +++ b/allocator/src/lib.rs @@ -0,0 +1,6 @@ +#![cfg_attr(target_os = "none", no_std)] +#![feature(assert_matches)] +#![feature(const_mut_refs)] +#![feature(const_slice_from_raw_parts_mut)] + +pub mod buddy; diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..d791c86 --- /dev/null +++ b/build.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cargo:rustc-link-arg-bin=pathos=-Tkernel.ld"); +} diff --git a/cmds.gdb b/cmds.gdb new file mode 100644 index 0000000..249c229 --- /dev/null +++ b/cmds.gdb @@ -0,0 +1,4 @@ +set architecture riscv:rv64 +set disassemble-next-line on +file target/riscv64gc-unknown-none-elf/release/pathos +target remote :1234 \ No newline at end of file diff --git a/hal-core/Cargo.toml b/hal-core/Cargo.toml new file mode 100644 index 0000000..af46b17 --- /dev/null +++ b/hal-core/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "hal-core" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/hal-core/justfile b/hal-core/justfile new file mode 100644 index 0000000..28cbb16 --- /dev/null +++ b/hal-core/justfile @@ -0,0 +1,5 @@ +test: + @ cargo test --lib --target x86_64-unknown-linux-gnu + + + diff --git a/hal-core/src/lib.rs b/hal-core/src/lib.rs new file mode 100644 index 0000000..896edc1 --- /dev/null +++ b/hal-core/src/lib.rs @@ -0,0 +1,3 @@ +#![cfg_attr(not(test), no_std)] + +pub mod page; diff --git a/hal-core/src/page.rs b/hal-core/src/page.rs new file mode 100644 index 0000000..5ecf4d9 --- /dev/null +++ b/hal-core/src/page.rs @@ -0,0 +1,224 @@ +/// Sv39 page table +#[repr(align(4096))] +pub struct PageTable { + entries: [PageTableEntry; 512], +} + +/// Sv39 page table entry +#[derive(Clone, Copy, Debug)] +#[repr(transparent)] +pub struct PageTableEntry(u64); + +#[derive(Clone, Copy, Debug)] +#[repr(transparent)] +pub struct Vaddr(u64); + +#[repr(transparent)] +#[derive(Clone, Copy, Debug)] +pub struct Paddr(u64); + +/// 4KiB aligned physical frame +#[repr(transparent)] +#[derive(Clone, Copy, Debug)] +pub struct Frame(Paddr); + +/// 4KiB aligned page +#[repr(transparent)] +#[derive(Clone, Copy, Debug)] +pub struct Page(Vaddr); + +/// Sv39 page table entry flags +#[repr(u64)] +#[derive(Debug, Clone)] +pub enum EntryFlags { + Valid = 1 << 0, + Read = 1 << 1, + Write = 1 << 2, + Execute = 1 << 3, + User = 1 << 4, + Global = 1 << 5, + Accessed = 1 << 6, + Dirty = 1 << 7, + + // Convenience combinations + RW = 1 << 1 | 1 << 2, + RX = 1 << 1 | 1 << 3, + RWX = 1 << 1 | 1 << 2 | 1 << 3, + RWXU = 1 << 1 | 1 << 2 | 1 << 3 | 1 << 4, + RWU = 1 << 1 | 1 << 2 | 1 << 4, +} + +impl EntryFlags { + pub fn as_u64(self) -> u64 { + self as u64 + } + + pub fn from(bits: u64) -> Self { + unsafe { core::mem::transmute(bits) } + } +} + +impl PageTableEntry { + pub fn new(flags: u64) -> Self { + Self(flags) + } + + pub fn is_valid(&self) -> bool { + self.0 & EntryFlags::Valid.as_u64() != 0 + } + + pub fn is_leaf(&self) -> bool { + self.0 & EntryFlags::RWX.as_u64() != 0 + } + + pub fn flags(&self) -> u64 { + // unsafe { core::mem::transmute(self.0 & 0xff) } + self.0 + } + + pub fn set_paddr(&mut self, paddr: Paddr) { + let ppn = paddr.ppn(); + self.0 |= ppn << 10; + } + + pub fn paddr(&self) -> Paddr { + let ppn = self.0 >> 10; + Paddr(ppn << 12) + } +} + +impl PageTable { + pub fn new() -> Self { + Self { + entries: [PageTableEntry::new(0); 512], + } + } + + pub fn entry_mut(&mut self, index: usize) -> &mut PageTableEntry { + &mut self.entries[index] + } +} + +impl Vaddr { + pub fn new(addr: u64) -> Self { + // Make bits 39-63 copy of bit 38 to form a canonical address + Self(((addr << 25) as i64 >> 25) as u64) + } + + pub fn offset(&self) -> u64 { + self.0 & 0xfff + } + + pub fn indexed_vpn(self) -> [usize; 3] { + let vpns = self.0 >> 12; + [ + (vpns & 0x1ff) as usize, + ((vpns >> 9) & 0x1ff) as usize, + ((vpns >> 18) & 0x1ff) as usize, + ] + } + + pub fn inner(&self) -> u64 { + self.0 + } +} + +impl Paddr { + pub fn new(addr: u64) -> Self { + Self(addr) + } + + pub fn as_mut_ptr(self) -> *mut T { + self.0 as *mut T + } + + pub fn ppn(&self) -> u64 { + self.0 >> 12 + } + + pub fn inner(&self) -> u64 { + self.0 + } +} + +impl Frame { + pub fn containing_address(addr: u64) -> Self { + // Align physical address down to the nearest 4KiB + Frame(Paddr(addr & !0xfff)) + } + + pub fn addr(&self) -> Paddr { + self.0 + } +} + +impl Page { + pub fn containing_address(addr: u64) -> Self { + Page(Vaddr(addr & !0xfff)) + } + + pub fn addr(&self) -> Vaddr { + self.0 + } +} + +pub struct PageRange { + start: Vaddr, + end: Vaddr, +} + +impl PageRange { + pub fn new(start: Vaddr, end: Vaddr) -> Self { + Self { start, end } + } +} + +impl Iterator for PageRange { + type Item = Page; + + fn next(&mut self) -> Option { + if self.start.0 <= self.end.0 { + let page = Page::containing_address(self.start.0); + self.start.0 += 0x1000; + Some(page) + } else { + None + } + } +} + +pub struct FrameRange { + start: Paddr, + end: Paddr, +} + +impl FrameRange { + pub fn new(start: Paddr, end: Paddr) -> Self { + Self { start, end } + } +} + +impl Iterator for FrameRange { + type Item = Frame; + + fn next(&mut self) -> Option { + if self.start.0 <= self.end.0 { + let frame = Frame::containing_address(self.start.0); + self.start.0 += 0x1000; + Some(frame) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_page_table_entry() { + let entry = PageTableEntry::new(EntryFlags::Valid as u64); + assert!(entry.is_valid()); + } +} diff --git a/hal-riscv/Cargo.toml b/hal-riscv/Cargo.toml new file mode 100644 index 0000000..5685489 --- /dev/null +++ b/hal-riscv/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "hal-riscv" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/hal-riscv/src/cpu.rs b/hal-riscv/src/cpu.rs new file mode 100644 index 0000000..30b0b4f --- /dev/null +++ b/hal-riscv/src/cpu.rs @@ -0,0 +1,684 @@ +use core::{arch::asm, fmt}; + +#[derive(Debug, Clone)] +pub enum Cause { + Interrupt(Interrupt), + Exception(Exception), +} + +#[repr(u8)] +#[derive(Debug, Clone)] +pub enum Interrupt { + SupervisorSoftware = 1, + MachineSoftware = 3, + SupervisorTimer = 5, + MachineTimer = 7, + SupervisorExternal = 9, + MachineExternal = 11, +} + +#[repr(u8)] +#[derive(Debug, Clone)] +pub enum Exception { + InstructionMisaligned, + InstructionFault, + IllegalInstruction, + Breakpoint, + LoadMisaligned, + LoadFault, + StoreMisaligned, + StoreFault, + UserEcall, + SupervisorEcall, + MachineEcall, + InstructionPageFault, + LoadPageFault, + StorePageFault, +} + +impl From for Interrupt { + fn from(value: u8) -> Self { + match value { + 1 => Interrupt::SupervisorSoftware, + 3 => Interrupt::MachineSoftware, + 5 => Interrupt::SupervisorTimer, + 7 => Interrupt::MachineTimer, + 9 => Interrupt::SupervisorExternal, + 11 => Interrupt::MachineExternal, + _ => unreachable!(), + } + } +} + +impl From for Exception { + fn from(value: u8) -> Self { + match value { + 0 => Exception::InstructionMisaligned, + 1 => Exception::InstructionFault, + 2 => Exception::IllegalInstruction, + 3 => Exception::Breakpoint, + 4 => Exception::LoadMisaligned, + 5 => Exception::LoadFault, + 6 => Exception::StoreMisaligned, + 7 => Exception::StoreFault, + 8 => Exception::UserEcall, + 9 => Exception::SupervisorEcall, + 11 => Exception::MachineEcall, + 12 => Exception::InstructionPageFault, + 13 => Exception::LoadPageFault, + 15 => Exception::StorePageFault, + _ => unreachable!(), + } + } +} + +#[derive(Default, Clone)] +pub struct Mstatus { + pub sie: u8, + pub mie: u8, + pub upie: u8, + pub spie: u8, + pub mpie: u8, + pub spp: u8, + pub mpp: u8, + pub fs: u8, +} + +#[derive(Default, Clone)] +pub struct Mie { + pub ssie: u8, + pub stie: u8, + pub mtie: u8, + pub msie: u8, +} + +#[derive(Default, Clone)] +pub struct Mip { + pub ssip: u8, + pub stip: u8, + pub mtip: u8, + pub msip: u8, +} + +#[derive(Default, Clone)] +pub struct Mideleg { + pub ssi: u8, + pub sti: u8, + pub mti: u8, + pub msi: u8, +} + +#[derive(Default, Clone)] +pub struct Medeleg { + pub uecall: u8, +} + +#[derive(Debug, Default)] +pub struct Sstatus { + pub sie: u8, + pub spie: u8, + pub spp: u8, +} + +pub struct Sip { + pub ssip: u8, + pub stip: u8, +} + +pub struct Sie { + pub ssie: u8, + pub stie: u8, +} + +#[derive(Debug, Clone)] +pub struct Satp { + mode: u64, + ppn: u64, +} + +impl Satp { + pub fn new(mode: u64, addr: usize) -> Self { + let ppn = (addr >> 12) as u64; + Self { mode, ppn } + } +} + +#[inline(always)] +pub fn read_sip() -> Sip { + let sip: u64; + unsafe { + asm!( + "csrr {}, sip", + out(reg) sip + ) + } + + Sip { + ssip: (sip >> 1) as u8 & 1, + stip: (sip >> 5) as u8 & 1, + } +} + +#[inline(always)] +pub fn read_sie() -> Sie { + let sie: u64; + unsafe { + asm!( + "csrr {}, sie", + out(reg) sie + ) + } + + Sie { + ssie: (sie >> 1) as u8 & 1, + stie: (sie >> 5) as u8 & 1, + } +} + +#[inline(always)] +pub fn write_satp(satp: Satp) { + let satp = (satp.mode << 60) | satp.ppn; + unsafe { + asm!( + "csrw satp, {}", + "sfence.vma x0, x0", + in(reg) satp + ) + } +} + +#[inline(always)] +pub fn write_sscratch(addr: usize) { + unsafe { + asm!( + "csrw sscratch, {}", + in(reg) addr + ) + } +} + +#[inline(always)] +pub fn write_mscratch(addr: *const T) { + unsafe { + asm!( + "csrw mscratch, {}", + in(reg) addr + ) + } +} + +#[inline(always)] +pub fn read_sscratch() -> usize { + let sscratch: usize; + unsafe { + asm!( + "csrr {}, sscratch", + out(reg) sscratch + ) + } + + sscratch +} + +#[inline(always)] +pub fn read_mscratch() -> *const T { + let mscratch: *const T; + unsafe { + asm!( + "csrr {}, mscratch", + out(reg) mscratch + ) + } + + mscratch +} + +#[inline(always)] +pub fn write_sp(addr: *const u8) { + unsafe { + asm!( + "mv sp, {}", + in(reg) addr + ) + } +} + +#[inline(always)] +pub fn read_sp() -> *const u8 { + let sp: *const u8; + unsafe { + asm!( + "mv {}, sp", + out(reg) sp + ) + } + + sp +} + +#[inline(always)] +pub fn read_mip() -> Mip { + let mip: u64; + unsafe { + asm!( + "csrr {}, mip", + out(reg) mip + ) + } + + Mip { + ssip: (mip >> 1) as u8 & 1, + stip: (mip >> 5) as u8 & 1, + mtip: (mip >> 7) as u8 & 1, + msip: (mip >> 3) as u8 & 1, + } +} + +#[inline(always)] +pub fn read_mie() -> Mie { + let mie: u64; + unsafe { + asm!( + "csrr {}, mie", + out(reg) mie + ) + } + + Mie { + ssie: (mie >> 1) as u8 & 1, + stie: (mie >> 5) as u8 & 1, + mtie: (mie >> 7) as u8 & 1, + msie: (mie >> 3) as u8 & 1, + } +} + +#[inline(always)] +pub fn read_mstatus() -> Mstatus { + let mstatus: u64; + unsafe { + asm!( + "csrr {}, mstatus", + out(reg) mstatus + ) + } + + Mstatus { + sie: (mstatus >> 1) as u8 & 1, + mie: (mstatus >> 3) as u8 & 1, + upie: (mstatus >> 4) as u8 & 1, + spie: (mstatus >> 5) as u8 & 1, + mpie: (mstatus >> 7) as u8 & 1, + spp: (mstatus >> 8) as u8 & 1, + mpp: (mstatus >> 11) as u8 & 3, + fs: (mstatus >> 13) as u8 & 3, + } +} + +#[inline(always)] +pub fn read_mtval() -> *const u32 { + let mtval: *const u32; + unsafe { + asm!( + "csrr {}, mtval", + out(reg) mtval + ) + } + + mtval +} + +#[inline(always)] +pub fn read_stval() -> *const () { + let stval: *const (); + unsafe { + asm!( + "csrr {}, stval", + out(reg) stval + ) + } + + stval +} + +#[inline(always)] +pub fn write_mstatus(mstatus: Mstatus) { + let mstatus = (mstatus.sie as u64) << 1 + | (mstatus.mie as u64) << 3 + | (mstatus.upie as u64) << 4 + | (mstatus.spie as u64) << 5 + | (mstatus.mpie as u64) << 7 + | (mstatus.spp as u64) << 8 + | (mstatus.mpp as u64) << 11 + | (mstatus.fs as u64) << 13; + unsafe { + asm!( + "csrw mstatus, {}", + in(reg) mstatus + ) + } +} + +#[inline(always)] +pub fn read_mideleg() -> Mideleg { + let mideleg: u64; + unsafe { + asm!( + "csrr {}, mideleg", + out(reg) mideleg + ) + } + + Mideleg { + ssi: (mideleg >> 1) as u8 & 1, + sti: (mideleg >> 5) as u8 & 1, + mti: (mideleg >> 7) as u8 & 1, + msi: (mideleg >> 3) as u8 & 1, + } +} + +#[inline(always)] +pub fn write_mideleg(mideleg: Mideleg) { + let mideleg = (mideleg.ssi as u64) << 1 + | (mideleg.sti as u64) << 5 + | (mideleg.mti as u64) << 7 + | (mideleg.msi as u64) << 3; + unsafe { + asm!( + "csrw mideleg, {}", + in(reg) mideleg + ) + } +} + +#[inline(always)] +pub fn write_medeleg(medeleg: Medeleg) { + let medeleg = (medeleg.uecall as u64) << 8; + unsafe { + asm!( + "csrw medeleg, {}", + in(reg) medeleg + ) + } +} + +#[inline(always)] +pub fn write_mie(mie: Mie) { + let mie = (mie.ssie as u64) << 1 + | (mie.stie as u64) << 5 + | (mie.mtie as u64) << 7 + | (mie.msie as u64) << 3; + unsafe { + asm!( + "csrw mie, {}", + in(reg) mie + ) + } +} + +#[inline(always)] +pub fn write_mip(mip: Mip) { + let mip = (mip.ssip as u64) << 1 + | (mip.stip as u64) << 5 + | (mip.mtip as u64) << 7 + | (mip.msip as u64) << 3; + + unsafe { + asm!( + "csrs mip, {}", + in(reg) mip + ) + } +} + +#[inline(always)] +pub fn clear_mip(bit: u8) { + unsafe { asm!("csrc mip, {}", in(reg) 1 << bit) } +} + +#[inline(always)] +pub fn read_mepc() -> *const () { + let mepc: *const (); + unsafe { + asm!( + "csrr {}, mepc", + out(reg) mepc + ) + } + + mepc +} + +#[inline(always)] +pub fn read_sepc() -> *const () { + let sepc: *const (); + unsafe { + asm!( + "csrr {}, sepc", + out(reg) sepc + ) + } + + sepc +} + +#[inline(always)] +pub fn write_mepc(addr: *const ()) { + unsafe { + asm!( + "csrw mepc, {}", + in(reg) addr + ) + } +} + +#[inline(always)] +pub fn write_sepc(addr: *const ()) { + unsafe { + asm!( + "csrw sepc, {}", + in(reg) addr + ) + } +} + +#[inline(always)] +pub fn write_mepc_next() { + unsafe { + asm!( + "csrr t0, mepc", + "addi t0, t0, 4", + "csrw mepc, t0", + "mv t0, zero" + ) + } +} + +#[inline(always)] +pub fn write_mtvec_vectored(fun: fn()) { + unsafe { + asm!( + "addi {0}, {0}, 1", + "csrw mtvec, {0}", + in(reg) fun + ) + } +} + +#[inline(always)] +pub fn write_mtvec(fun: fn()) { + unsafe { + asm!( + "csrw mtvec, {0}", + in(reg) fun + ) + } +} + +#[inline(always)] +pub fn write_stvec_vectored(addr: fn()) { + unsafe { + asm!( + "addi {0}, {0}, 1", + "csrw stvec, {0}", + in(reg) addr + ) + } +} + +#[inline(always)] +pub fn read_sstatus() -> Sstatus { + let sstatus: u64; + unsafe { + asm!( + "csrr {}, sstatus", + out(reg) sstatus + ) + } + + Sstatus { + sie: (sstatus >> 1) as u8 & 1, + spie: (sstatus >> 5) as u8 & 1, + spp: (sstatus >> 8) as u8 & 1, + } +} + +#[inline(always)] +pub fn set_sstatus(sstatus: Sstatus) { + let sstatus = + (sstatus.sie as u64) << 1 | (sstatus.spie as u64) << 5 | (sstatus.spp as u64) << 8; + unsafe { + asm!( + "csrs sstatus, {}", + in(reg) sstatus + ) + } +} + +#[inline(always)] +pub fn read_scause() -> Cause { + let scause: u64; + unsafe { + asm!( + "csrr {}, scause", + out(reg) scause + ) + } + + let cause = scause as i64; + match cause.signum() { + 0 | 1 => Cause::Exception(Exception::from(cause as u8)), + -1 => Cause::Interrupt(Interrupt::from(cause as u8)), + _ => unreachable!(), + } +} + +#[inline(always)] +pub fn read_mcause() -> Cause { + let mcause: u64; + unsafe { + asm!( + "csrr {}, mcause", + out(reg) mcause + ) + } + + let cause = mcause as i64; + match cause.signum() { + 1 => Cause::Exception(Exception::from(cause as u8)), + -1 => Cause::Interrupt(Interrupt::from(cause as u8)), + _ => unreachable!(), + } +} + +impl fmt::Display for Satp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "satp ::: mode: {}, ppn: 0x{:x} (0x{:x})", + self.mode, + self.ppn, + self.mode << 60 | self.ppn + ) + } +} + +impl fmt::Display for Mstatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "mstatus ::: sie: {:b}, mie: {:b}, upie: {:b}, spie: {:b}, mpie: {:b}, spp: {:b}, mpp: {:b}, fs: {:b}", + self.sie, self.mie, self.upie, self.spie, self.mpie, self.spp, self.mpp, self.fs + ) + } +} + +impl fmt::Display for Mie { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "mie ::: ssie: {:b}, stie: {:b}, mtie: {:b}, msie: {:b}", + self.ssie, self.stie, self.mtie, self.msie + ) + } +} + +impl fmt::Display for Mip { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "mip ::: ssip: {:b}, stip: {:b}, mtip: {:b}, msip: {:b}", + self.ssip, self.stip, self.mtip, self.msip + ) + } +} + +impl fmt::Display for Mideleg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "mideleg ::: ssi: {:b}, sti: {:b}, mti: {:b}, msi: {:b}", + self.ssi, self.sti, self.mti, self.msi + ) + } +} + +impl fmt::Display for Medeleg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "medeleg ::: uecall: {:b}", self.uecall) + } +} + +impl fmt::Display for Sstatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "sstatus ::: sie: {:b}, spie: {:b}, spp: {:b}", + self.sie, self.spie, self.spp + ) + } +} + +impl fmt::Display for Sip { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "sip ::: ssip: {:b}, stip: {:b}", self.ssip, self.stip) + } +} + +impl fmt::Display for Sie { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "sie ::: ssie: {:b}, stie: {:b}", self.ssie, self.stie) + } +} + +impl fmt::Display for Cause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Cause::Interrupt(cause) => { + write!(f, "[INT] cause {:?} = {}", cause, (*cause).clone() as u8) + } + Cause::Exception(cause) => { + write!(f, "[EXC] cause {:?} = {}", cause, (*cause).clone() as u8) + } + } + } +} diff --git a/hal-riscv/src/lib.rs b/hal-riscv/src/lib.rs new file mode 100644 index 0000000..c5976ef --- /dev/null +++ b/hal-riscv/src/lib.rs @@ -0,0 +1,6 @@ +#![cfg_attr(not(test), no_std)] + +// #[cfg(not(test))] +pub mod cpu; +// #[cfg(not(test))] +pub mod timer; diff --git a/hal-riscv/src/timer.rs b/hal-riscv/src/timer.rs new file mode 100644 index 0000000..ab2608a --- /dev/null +++ b/hal-riscv/src/timer.rs @@ -0,0 +1,21 @@ +use core::arch::asm; + +const RISCV_MTIME_ADDR: u64 = 0x0200BFF8; +const RISCV_MTIMECMP_ADDR: u64 = 0x02004000; + +#[inline(always)] +pub fn read_mtime() -> u64 { + let mut mtime: u64; + unsafe { + asm!("ld {1}, 0({0})", in(reg) RISCV_MTIME_ADDR, out(reg) mtime); + } + + mtime +} + +#[inline(always)] +pub fn write_mtimecmp(mtime: u64) { + unsafe { + asm!("sd {0}, 0({1})", in(reg) mtime, in(reg) RISCV_MTIMECMP_ADDR); + } +} diff --git a/justfile b/justfile new file mode 100644 index 0000000..94d6364 --- /dev/null +++ b/justfile @@ -0,0 +1,30 @@ +bin := "target/riscv64gc-unknown-none-elf/release/pathos" + +dump: + @ cargo objdump --quiet --release --bin pathos -- --disassemble-all \ + --no-show-raw-insn -M no-aliases + +run: + @ qemu-system-riscv64 --machine virt --smp 1 --cpu rv64 --serial stdio --monitor none \ + --bios {{bin}} --nographic \ + -d guest_errors,unimp -D log.txt -m 128M + +debug: + @ qemu-system-riscv64 -s -S --machine virt --smp 1 --cpu rv64 --serial stdio --monitor none \ + --bios {{bin}} --nographic \ + -d guest_errors,unimp -D log.txt -m 128M + +gdb: + @ gdb-multiarch --init-command cmds.gdb + +build: + @ cargo build --release + +clean: + @ cargo clean + +test: + @ cargo test --target x86_64-unknown-linux-gnu + + + diff --git a/kernel.ld b/kernel.ld new file mode 100644 index 0000000..4a9351b --- /dev/null +++ b/kernel.ld @@ -0,0 +1,54 @@ +OUTPUT_ARCH("riscv") + +ENTRY(_start) + +MEMORY { + ram (wxa) : ORIGIN = 0x80000000, LENGTH = 128M +} + +PHDRS +{ + text PT_LOAD; + data PT_LOAD; + bss PT_LOAD; +} + +SECTIONS { + .text : { + PROVIDE(_text_start = .); + *(.text.boot) *(.text .text.*) + PROVIDE(_text_end = .); + } > ram :text + + .rodata : { + PROVIDE(_rodata_start = .); + *(.rodata .rodata.*) + PROVIDE(_rodata_end = .); + } > ram :text + + .data : { + PROVIDE(_data_start = .); + *(.sdata .sdata.*) *(.data .data.*) + PROVIDE(_data_end = .); + } > ram :data + + .bss : { + PROVIDE(_bss_start = .); + *(.sbss .sbss.*) *(.bss .bss.*) + PROVIDE(_bss_end = .); + } > ram :bss + + PROVIDE(_memory_start = ORIGIN(ram)); + PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); + + PROVIDE(_stack_start = _bss_end); + PROVIDE(_stack_end = _stack_start + 1M); + + PROVIDE(_heap_start = _stack_end); + PROVIDE(_heap_size = 4M); + + . = _heap_start + _heap_size; + . = ALIGN(4096 * 4); + PROVIDE(_alloc_start = .); + PROVIDE(_alloc_size = 4M); +} \ No newline at end of file diff --git a/path0/README.md b/path0/README.md new file mode 100644 index 0000000..1d73093 --- /dev/null +++ b/path0/README.md @@ -0,0 +1,5 @@ +riscv64-linux-as -march=rv64gc -mabi=lp64d -o rt.o -c rt.s +riscv64-linux-ld app.o rt.o --no-dynamic-linker -m elf64lriscv -nostdlib -s -o app +riscv64-linux-objdump -D target/riscv64gc-unknown-none-elf/release/usercode -j .text | grep -C 10 _start + +rustflags = "-C link-arg=rt.o" \ No newline at end of file diff --git a/path0/default.ld b/path0/default.ld new file mode 100644 index 0000000..8b31c75 --- /dev/null +++ b/path0/default.ld @@ -0,0 +1,46 @@ +OUTPUT_ARCH("riscv") + +ENTRY(_start) + +MEMORY { + ram (wxa) : ORIGIN = 0x2000000000, LENGTH = 4M +} + +PHDRS +{ + text PT_LOAD; + data PT_LOAD; + bss PT_LOAD; +} + +SECTIONS { + .text : { + PROVIDE(_text_start = .); + *(.text .text.*) + PROVIDE(_text_end = .); + } > ram :text + + .rodata : { + PROVIDE(_rodata_start = .); + *(.rodata .rodata.*) + PROVIDE(_rodata_end = .); + } > ram :text + + .data : { + PROVIDE(_data_start = .); + *(.sdata .sdata.*) *(.data .data.*) + PROVIDE(_data_end = .); + } > ram :text + + .bss : { + PROVIDE(_bss_start = .); + *(.sbss .sbss.*) *(.bss .bss.*) + PROVIDE(_bss_end = .); + } > ram :text + + PROVIDE(_memory_start = ORIGIN(ram)); + PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); + + /* PROVIDE(_stack_start = _memory_end); */ + PROVIDE(_stack_end = 0x2000010000); +} \ No newline at end of file diff --git a/path0/justfile b/path0/justfile new file mode 100644 index 0000000..daf607f --- /dev/null +++ b/path0/justfile @@ -0,0 +1,2 @@ +build: + riscv64-linux-as -march=rv64gc -mabi=lp64d -o path0.o -c path0.s \ No newline at end of file diff --git a/path0/path0.o b/path0/path0.o new file mode 100644 index 0000000..96920c1 Binary files /dev/null and b/path0/path0.o differ diff --git a/path0/path0.s b/path0/path0.s new file mode 100644 index 0000000..6686fb4 --- /dev/null +++ b/path0/path0.s @@ -0,0 +1,9 @@ + .globl _start + .section .text + +_start: + la sp, _stack_end + call main + mv x31, a0 # Copy return value as is to a syscall argument register + li x30, 3 # Call exit syscall + ecall diff --git a/runner/Cargo.toml b/runner/Cargo.toml new file mode 100644 index 0000000..d3afa1d --- /dev/null +++ b/runner/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "qemu" +version = "0.1.0" +edition = "2021" diff --git a/runner/src/main.rs b/runner/src/main.rs new file mode 100644 index 0000000..13499e4 --- /dev/null +++ b/runner/src/main.rs @@ -0,0 +1,50 @@ +use std::process::{Command, Stdio}; + +// qemu-system-riscv64 --machine virt --serial stdio --monitor none \ +// --bios {{bin}} --nographic \ +// -d guest_errors,unimp -D log.txt -m 128M + +fn main() { + let bin = std::env::args() + .nth(1) + .expect("No binary file was provided."); + + println!("Using binary file: {}", bin); + + let mut cmd = Command::new("qemu-system-riscv64"); + cmd.args([ + "--machine", + "virt", + "--serial", + "stdio", + "--monitor", + "none", + "--bios", + &bin, + "--nographic", + "-d", + "guest_errors,unimp", + "-D", + "log.txt", + "-m", + "128M", + ]) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()); + + println!("{cmd:?}"); + + let mut child = cmd.spawn().expect("Failed to start QEMU"); + + // { + // let stdout = child.stdout.as_mut().unwrap(); + // let stdout_reader = BufReader::new(stdout); + // let stdout_lines = stdout_reader.lines(); + + // for line in stdout_lines { + // println!("Read: {:?}", line); + // } + // } + + child.wait().expect("Failed to wait for QEMU to exit"); +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..bb4edb5 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly-x86_64-unknown-linux-gnu" +components = ["llvm-tools"] diff --git a/src/alloc.rs b/src/alloc.rs new file mode 100644 index 0000000..d2ef6d6 --- /dev/null +++ b/src/alloc.rs @@ -0,0 +1,97 @@ +extern crate alloc; + +use crate::{ALLOC_START, HEAP_SIZE, HEAP_START}; + +use allocator::buddy::BuddyAllocator; +use core::alloc::{GlobalAlloc, Layout}; +use once_cell::unsync::OnceCell; + +const MIN_BLOCK_SIZE: usize = 64; + +pub struct Locked { + inner: spin::Mutex, +} + +impl Locked { + pub const fn new(inner: A) -> Self { + Locked { + inner: spin::Mutex::new(inner), + } + } + + pub fn lock(&self) -> spin::MutexGuard { + self.inner.lock() + } +} + +unsafe impl GlobalAlloc for Locked> { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let mut cell = self.lock(); + let allocator = cell.get_mut().expect("Allocator not initialized"); + + // Align size to next power of 2 and respect layout alignment. To + // respect layout alignment in the context of buddy allocation, we need + // to find the smallest power of 2 that is greater than or equal to the alignment. + // This assumes that the heap is aligned to the largest alignment + // required by any type. In PathOS, we assume that it is at least 4KiB aligned. + + // We still need to align the size to the next power of two, + // because default alignment is 1 while the size can be not a power of two. + let size = layout.pad_to_align().size().next_power_of_two(); + + // Align size to at least MIN_BLOCK_SIZE. + let size = size.max(allocator.min_block_size); + // serial_debug!("Allocating {} bytes", size); + + let idx = allocator.find_block(size).unwrap(); + // serial_debug!("Found index: {}", idx); + + let order_start_idx = allocator.order_start_index(size); + + // The start address of the block is as far from the start of the heap + // as the index is from the first index of the order multiplied by the + // size of the block. This assumes that the allocator always returns the + // next free block in the order. + let block_start_addr = ALLOC_START + (idx - order_start_idx) * size; + + // serial_debug!("Found block at adress: {:?}", block_start_addr as *mut u8); + block_start_addr as *mut u8 + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + let mut cell = self.lock(); + let allocator = cell.get_mut().expect("Allocator not initialized"); + + let size = layout.pad_to_align().size().next_power_of_two(); + // Align size to at least MIN_BLOCK_SIZE. This is required because e.g. + // a request to free 1 byte will have layout size 1 (with default + // alignment = 1). + let size = size.max(allocator.min_block_size); + + // serial_debug!("Deallocating {} bytes at {:?}", size, ptr); + + let order_start_idx = allocator.order_start_index(size); + + // To find the index of the block, we reverse the formula used in the + // alloc function. + // index - order_start_idx = (block_start - heap_start) / size + // index = order_start_idx + (block_start - heap_start) / size + let idx = order_start_idx + (ptr as usize - ALLOC_START) / size; + // serial_debug!("Calculated index: {}", idx); + + allocator.free_block(idx); + // serial_debug!("Deallocated block at index: {}", idx); + } +} + +#[global_allocator] +static ALLOCATOR: Locked> = Locked::new(OnceCell::new()); + +pub fn init_allocator() { + let allocator = ALLOCATOR.lock(); + unsafe { + allocator + .set(BuddyAllocator::new(HEAP_START, HEAP_SIZE, MIN_BLOCK_SIZE)) + .expect("Allocator already initialized"); + } +} diff --git a/src/app b/src/app new file mode 100755 index 0000000..bb22b76 Binary files /dev/null and b/src/app differ diff --git a/src/asm.rs b/src/asm.rs new file mode 100644 index 0000000..a6bb9ec --- /dev/null +++ b/src/asm.rs @@ -0,0 +1,4 @@ +use core::arch::global_asm; + +global_asm!(include_str!("asm/boot.s")); +global_asm!(include_str!("asm/mem.s")); diff --git a/src/asm/boot.s b/src/asm/boot.s new file mode 100644 index 0000000..3f6d27c --- /dev/null +++ b/src/asm/boot.s @@ -0,0 +1,24 @@ + .option norvc + + .section .text.boot + .global _start +_start: + csrw satp, zero # Disable paging + + la a0, _bss_start # Initialize BSS section to zero + la a1, _bss_end + bgeu a0, a1, 2f + +1: + sd zero, (a0) + addi a0, a0, 8 + bltu a0, a1, 1b + +2: + la sp, _stack_end # Prepare to switch to Rust-based entry code + + csrwi pmpcfg0, 0xf # Let S-mode access all physical memory + li t0, 0xffffffffffffff >> 2 + csrw pmpaddr0, t0 + + call kinit diff --git a/src/asm/mem.s b/src/asm/mem.s new file mode 100644 index 0000000..483bfab --- /dev/null +++ b/src/asm/mem.s @@ -0,0 +1,70 @@ + .section .rodata + + .global HEAP_START +HEAP_START: + .dword _heap_start + + .global HEAP_SIZE +HEAP_SIZE: + .dword _heap_size + +# .global HEAP_END +# HEAP_END: +# .dword _heap_end + + .global ALLOC_START +ALLOC_START: + .dword _alloc_start + + .global ALLOC_SIZE +ALLOC_SIZE: + .dword _alloc_size + + .global TEXT_START +TEXT_START: + .dword _text_start + + .global TEXT_END +TEXT_END: + .dword _text_end + + .global DATA_START +DATA_START: + .dword _data_start + + .global DATA_END +DATA_END: + .dword _data_end + + .global RODATA_START +RODATA_START: + .dword _rodata_start + + .global RODATA_END +RODATA_END: + .dword _rodata_end + + .global BSS_START +BSS_START: + .dword _bss_start + + .global BSS_END +BSS_END: + .dword _bss_end + + .global KERNEL_STACK_START +KERNEL_STACK_START: + .dword _stack_start + + .global KERNEL_STACK_END +KERNEL_STACK_END: + .dword _stack_end + + .global MEMORY_START +MEMORY_START: + .dword _memory_start + + .global MEMORY_END +MEMORY_END: + .dword _memory_end + diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..65871f4 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1 @@ +pub const TASK_BEGIN_VADDR: u64 = 0x20_0000_0000; diff --git a/src/debug.rs b/src/debug.rs new file mode 100644 index 0000000..3ee0997 --- /dev/null +++ b/src/debug.rs @@ -0,0 +1,40 @@ +use crate::SCHEDULER; + +#[inline(always)] +pub fn dump_machine_registers() { + let mip = hal_riscv::cpu::read_mip(); + let mie = hal_riscv::cpu::read_mie(); + let mstatus = hal_riscv::cpu::read_mstatus(); + let mtval = hal_riscv::cpu::read_mtval(); + let mepc = hal_riscv::cpu::read_mepc(); + + crate::serial_debug!("{}", mstatus); + crate::serial_debug!("{}", mie); + crate::serial_debug!("{}", mip); + crate::serial_debug!("mepc ::: {:?}", mepc); + crate::serial_debug!("mtval ::: {:?}", mtval); + crate::serial_debug!("sp ::: {:?}", hal_riscv::cpu::read_sp()); +} + +#[inline(always)] +pub unsafe fn dump_trap_frame() { + let guard = SCHEDULER.lock(); + let scheduler = guard.get().expect("Scheduler not initialized"); + let current = scheduler.task(scheduler.current()); + crate::serial_debug!("{:x?}", current.trap_frame); +} + +#[inline(always)] +pub fn dump_supervisor_registers() { + let sstatus = hal_riscv::cpu::read_sstatus(); + let sie = hal_riscv::cpu::read_sie(); + let sip = hal_riscv::cpu::read_sip(); + let stval = hal_riscv::cpu::read_stval(); + let sepc = hal_riscv::cpu::read_sepc(); + + crate::serial_debug!("{}", sstatus); + crate::serial_debug!("{}", sie); + crate::serial_debug!("{}", sip); + crate::serial_debug!("sepc ::: {:?}", sepc); + crate::serial_debug!("stval ::: {:?}", stval); +} diff --git a/src/ecall.rs b/src/ecall.rs new file mode 100644 index 0000000..902eea5 --- /dev/null +++ b/src/ecall.rs @@ -0,0 +1,34 @@ +use core::arch::asm; + +#[repr(u8)] +#[derive(Debug)] +pub enum Ecall { + SModeFinishBootstrap, + ClearPendingInterrupt(u8), + Exit(u8), +} + +pub fn ecall(call: Ecall) { + match call { + Ecall::SModeFinishBootstrap => unsafe { asm!("ecall", in("x30") 1) }, + Ecall::ClearPendingInterrupt(cause) => unsafe { + asm!("ecall", in("x30") 2, in("x31") cause) + }, + Ecall::Exit(code) => unsafe { asm!("ecall", in("x30") 3, in("x31") code) }, + } +} + +pub fn read_ecall() -> Ecall { + let ecall: u8; + let payload: u8; + unsafe { + asm!("", out("x30") ecall, out("x31") payload); + } + + match ecall { + 1 => Ecall::SModeFinishBootstrap, + 2 => Ecall::ClearPendingInterrupt(payload), + 3 => Ecall::Exit(payload), + _ => panic!("Unknown ecall: {}", ecall), + } +} diff --git a/src/elf.rs b/src/elf.rs new file mode 100644 index 0000000..b869e12 --- /dev/null +++ b/src/elf.rs @@ -0,0 +1,16 @@ +use elf::{endian::LittleEndian, section::SectionHeader, ElfBytes}; + +pub fn parse_text(data: &[u8]) -> &[u8] { + let file = ElfBytes::::minimal_parse(data).expect("Failed to parse ELF file"); + + let text_section: SectionHeader = file + .section_header_by_name(".text") + .expect("Failed to find .text section") + .expect("Failed to parse .text section"); + + let (text, _) = file + .section_data(&text_section) + .expect("Failed to read .text section"); + + text +} diff --git a/src/interrupts.rs b/src/interrupts.rs new file mode 100644 index 0000000..a944791 --- /dev/null +++ b/src/interrupts.rs @@ -0,0 +1,162 @@ +extern crate alloc; + +use crate::constants::TASK_BEGIN_VADDR; +use crate::debug::dump_machine_registers; +use crate::ecall::{self, Ecall}; +use crate::serial::write_empty_line; +use crate::trap::{restore_cpu_registers, save_cpu_registers, TrapFrame}; +use crate::{serial_debug, SCHEDULER}; + +use core::arch::asm; +use core::panic; +use hal_riscv::cpu::{self, read_mstatus, Cause, Exception, Interrupt, Mie, Mstatus}; + +#[inline(always)] +pub fn init_m_mode_ivt() { + hal_riscv::cpu::write_mtvec(interrupt_handler_naked); +} + +#[inline(always)] +fn handle_mti() { + let mut mtime = hal_riscv::timer::read_mtime(); + mtime += 10_000_000; + hal_riscv::timer::write_mtimecmp(mtime); + + let mstatus = read_mstatus(); + let mstatus = Mstatus { mpp: 0, ..mstatus }; + cpu::write_mstatus(mstatus); + + let mepc = cpu::read_mepc() as u64; + + schedule_task(UserspaceState::Running(mepc)); +} + +enum UserspaceState { + Running(u64), + Pending, +} + +#[inline(always)] +fn schedule_task(state: UserspaceState) { + let (next_tid, next_mepc) = match state { + UserspaceState::Pending => (0, TASK_BEGIN_VADDR), + UserspaceState::Running(mepc) => { + let mut cell = SCHEDULER.lock(); + let scheduler = cell.get_mut().expect("Scheduler not initialized"); + scheduler.save_state(mepc); + let (tid, task) = scheduler.next(); + (tid, task.pc.inner()) + } + }; + + cpu::write_mepc(next_mepc as *const ()); + + unsafe { + asm!( + "jal {restore_cpu_registers}", + "csrw mscratch, a0", + "ld ra, 0(a0)", + "ld a0, 168(a0)", + "mret", + in("a0") get_task_frame_ptr(next_tid), + restore_cpu_registers = sym restore_cpu_registers + ) + } +} + +#[no_mangle] +fn dispatch_machine_exception(mcause: Cause) { + match mcause { + Cause::Exception(Exception::SupervisorEcall) => { + // dump_machine_registers(); + let ecall = ecall::read_ecall(); + match ecall { + Ecall::SModeFinishBootstrap => handle_smode_finish_bootstrap(), + _ => { + panic!("Unimplemented S-mode ecall handler ::: {:?}", mcause) + } + } + } + Cause::Exception(Exception::UserEcall) => { + dump_machine_registers(); + serial_debug!("{:?} ::: {:?}", Exception::UserEcall, mcause); + schedule_task(UserspaceState::Pending) + } + Cause::Exception(ref exc) => { + dump_machine_registers(); + serial_debug!("{:?} ::: {:?}", exc, mcause); + loop {} + } + _ => { + dump_machine_registers(); + panic!("Unimplemented M-mode exception ::: {:?}", mcause) + } + } + + unsafe { asm!("mret", clobber_abi("system")) } +} + +#[inline(never)] +fn get_task_frame_ptr(tid: usize) -> *const TrapFrame { + let mut cell = SCHEDULER.lock(); + let scheduler = cell.get_mut().expect("Scheduler not initialized"); + &scheduler.task(tid).trap_frame as *const _ +} + +#[inline(always)] +fn handle_smode_finish_bootstrap() { + let mie = Mie { + mtie: 1, + ..Default::default() + }; + + let mstatus = hal_riscv::cpu::read_mstatus(); + let mstatus = Mstatus { + mpp: 0, + mpie: 1, + fs: 1, + ..mstatus + }; + + hal_riscv::cpu::write_mie(mie.clone()); + hal_riscv::cpu::write_mstatus(mstatus.clone()); + schedule_task(UserspaceState::Pending) +} + +#[inline(always)] +#[no_mangle] +#[repr(align(4))] +fn interrupt_handler_naked() { + unsafe { + asm!( + "csrrw a0, mscratch, a0", + "beqz a0, {interrupt_handler}", + "sd ra, 0(a0)", + "jal {save_cpu_registers}", + "ld sp, 232(a0)", + "j {interrupt_handler}", + save_cpu_registers = sym save_cpu_registers, + interrupt_handler = sym interrupt_handler, + options(noreturn) + ) + } +} + +#[inline(always)] +fn interrupt_handler() { + let mcause = cpu::read_mcause(); + + if matches!(mcause, Cause::Exception(_)) { + serial_debug!("Machine mode exception cause: {:?}", mcause); + dispatch_machine_exception(mcause.clone()); + } + + if matches!(mcause, Cause::Interrupt(Interrupt::MachineTimer)) { + write_empty_line(); + // serial_debug!("Machine mode interrupt cause: {:?}", mcause); + // unsafe { dump_trap_frame() } + handle_mti(); + } + + unsafe { asm!("mret") } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..481d9b1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,153 @@ +#![cfg_attr(target_os = "none", no_std)] +#![no_main] +#![feature(fn_align)] +#![feature(abi_riscv_interrupt)] +#![feature(fn_ptr_trait)] +#![feature(asm_const)] +#![feature(naked_functions)] + +use core::panic::PanicInfo; + +use hal_core::page::{EntryFlags, Page, PageTable, Vaddr}; +use page::*; +use trap::{Scheduler, Task, SCHEDULER}; + +pub mod alloc; +pub mod constants; +pub mod debug; +pub mod ecall; +pub mod elf; +pub mod interrupts; +pub mod page; +pub mod serial; +pub mod trap; + +extern "C" { + pub static TEXT_START: usize; + pub static TEXT_END: usize; + pub static DATA_START: usize; + pub static DATA_END: usize; + pub static BSS_START: usize; + pub static BSS_END: usize; + pub static KERNEL_STACK_START: usize; + pub static KERNEL_STACK_END: usize; + pub static HEAP_START: usize; + pub static HEAP_SIZE: usize; + pub static ALLOC_START: usize; + pub static ALLOC_SIZE: usize; + pub static MEMORY_START: usize; + pub static MEMORY_END: usize; + pub static RODATA_START: usize; + pub static RODATA_END: usize; +} + +pub const APP_CODE: &[u8] = include_bytes!("app"); + +#[cfg(all(not(test), target_os = "none"))] +#[panic_handler] +#[no_mangle] +fn panic(info: &PanicInfo) -> ! { + use debug::{dump_machine_registers, dump_supervisor_registers}; + + crate::serial_error!(" "); + crate::serial_error!("*** KERNEL PANIC ***"); + crate::serial_error!(" "); + crate::serial_error!("{}", info); + + dump_machine_registers(); + dump_supervisor_registers(); + + loop {} +} + +pub unsafe fn init_page_tables(root: &mut PageTable) { + // I needed to set .text and .rodata to X because otherwise I + // get store page faults on AMO instructions in the kernel + // (which probably belong to mutexes or spinlocks). + + id_map_range(root, TEXT_START, TEXT_END, EntryFlags::RWX); + serial_debug!( + "Identity mapped kernel .text: 0x{:x} - 0x{:x}", + TEXT_START, + TEXT_END + ); + + id_map_range(root, RODATA_START, RODATA_END, EntryFlags::RWX); + serial_debug!( + "Identity mapped kernel .rodata: 0x{:x} - 0x{:x}", + RODATA_START, + RODATA_END + ); + + id_map_range(root, DATA_START, DATA_END, EntryFlags::RW); + serial_debug!( + "Identity mapped kernel .data: 0x{:x} - 0x{:x}", + DATA_START, + DATA_END + ); + + id_map_range(root, BSS_START, BSS_END, EntryFlags::RW); + serial_debug!( + "Identity mapped kernel .bss: 0x{:x} - 0x{:x}", + BSS_START, + BSS_END + ); + + id_map_range(root, KERNEL_STACK_START, KERNEL_STACK_END, EntryFlags::RW); + serial_debug!( + "Identity mapped kernel stack: 0x{:x} - 0x{:x}", + KERNEL_STACK_START, + KERNEL_STACK_END + ); + + id_map_range(root, HEAP_START, HEAP_START + HEAP_SIZE, EntryFlags::RW); + serial_debug!( + "Identity mapped kernel heap: 0x{:x} - 0x{:x}", + HEAP_START, + HEAP_START + HEAP_SIZE + ); + + id_map_range(root, ALLOC_START, ALLOC_START + ALLOC_SIZE, EntryFlags::RW); + serial_debug!( + "Identity mapped kernel allocatable memory: 0x{:x} - 0x{:x}", + ALLOC_START, + ALLOC_START + ALLOC_SIZE + ); + + id_map(root, Page::containing_address(0x10000000), EntryFlags::RW); + serial_debug!("Identity mapped UART device: 0x{:x}", 0x10000000); + + // Now perform sanity check by trying to translate each section's start + // and end virtual addresses to a physical address. + for vaddr in [ + TEXT_START, + TEXT_END, + RODATA_START, + RODATA_END, + DATA_START, + DATA_END, + BSS_START, + BSS_END, + KERNEL_STACK_START, + KERNEL_STACK_END, + // HEAP_START, + // HEAP_START + HEAP_SIZE, + // ALLOC_START, + // ALLOC_START + ALLOC_SIZE, + ] { + let vaddr = Vaddr::new(vaddr as u64); + if translate_vaddr(root, vaddr).is_none() { + panic!("0x{:x} cannot be translated", vaddr.inner()); + } + } + + // TODO: Check why not every address translation in HEAP and ALLOCATE + // sections works. +} + +pub fn init_scheduler(tasks: [Task; 3]) { + let scheduler = SCHEDULER.lock(); + scheduler + .set(Scheduler::new(tasks)) + .expect("Scheduler already initialized"); +} diff --git a/src/logo.txt b/src/logo.txt new file mode 100644 index 0000000..6ac0032 --- /dev/null +++ b/src/logo.txt @@ -0,0 +1,26 @@ + + -=+**+ +***+- + =: +@@@@@%- :%@@@@@+ .=. + +@+ +@%#*=: .=*#%@* =@* + .%@@....: :....@@@. + #@@@@@@@* *@@@@@@@% + :+ .@@@@@@#- .=*###+- -#@@@@@@: =- + *@= :+-:.. +@@@@@@@@@= ..:-+: -@* + #@@+ -+*- *@@@@@@@@@@@= -**=. =@@% +-@@@@+@@@: @@@@@@@@@@@@* .%@@+@@@@= +:@@@@@@@: . =@@@@@@@@@@@: . .@@@@@@@- + +@@@@@* -# -%@@@@@@@#: *= +@@@@@+ + .+%@@- %@- .-=+=-. :@@ :@@@*: + =. .-.:@@@: : : :@@@-.=. .= + -@%+-. +@@@@:.@%+- :+#@:.@@@@* :+%@= + =@@@@@@@@@@% +@@@@= -@@@@* #@@@@@@@@@@+ + .+@@@@@@@@@..@@@@@= -@@@@@: @@@@@@@@@*. + :-++*++=. @@@@@#. .*@@@@@ =++*++=: + +*=-::::-+@@@@@#-. =*@@@@@+=::::-=** + =%@@@@@@@@@@@@=+= -*-@@@@@@@@@@@@@= + :=*#%%%%%#*=:@= -@:=*##%%%%#*=: + .:-=+*#*+*++*+*#*+=-:. + .=#%%#=: + .**- :**: + +PATHOS v0.1 (riscv64gc-unknown-none-elf) \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..44bc8ab --- /dev/null +++ b/src/main.rs @@ -0,0 +1,123 @@ +#![no_std] +#![no_main] +#![feature(fn_align)] +#![feature(abi_riscv_interrupt)] +#![feature(fn_ptr_trait)] +#![feature(asm_const)] + +mod asm; + +extern crate alloc; + +use ::core::arch::asm; +use ::core::marker::FnPtr; +use alloc::vec::Vec; +use hal_core::page::{EntryFlags, Frame, Page, PageTable, Vaddr}; +use hal_riscv::cpu::{Mideleg, Mstatus, Satp, Sstatus}; +use pathos::alloc::init_allocator; +use pathos::constants::TASK_BEGIN_VADDR; +use pathos::ecall::{ecall, Ecall}; +use pathos::elf::parse_text; +use pathos::trap::Task; +use pathos::{init_page_tables, init_scheduler, interrupts, page, APP_CODE}; +use pathos::{serial_debug, serial_info, serial_println}; + +const LOGO: &str = include_str!("logo.txt"); + +#[no_mangle] +pub fn kinit() { + serial_println!("{}", LOGO); + + let mideleg = Mideleg { + ..Default::default() + }; + + // let medeleg = Medeleg { uecall: 1 }; + let mstatus = Mstatus { + mpp: 1, + fs: 1, + ..Default::default() + }; + + hal_riscv::cpu::write_mideleg(mideleg.clone()); + hal_riscv::cpu::write_mstatus(mstatus.clone()); + hal_riscv::cpu::write_mepc((main as fn()).addr()); + + init_scheduler([ + Task::new(Vaddr::new(0x20_0000_0000), 0), + Task::new(Vaddr::new(0x20_0000_0000), 1), + Task::new(Vaddr::new(0x20_0000_0000), 2), + ]); + + serial_info!("Initialized task scheduler"); + + interrupts::init_m_mode_ivt(); + serial_info!("Initialized machine mode interrupt vector table"); + + unsafe { asm!("mret") } +} + +#[no_mangle] +pub fn main() { + init_allocator(); + serial_info!("Initialized global heap allocator"); + + // Identity map kernel code and data before switching to Sv39 paging + let root = page::allocate_root(); + unsafe { + init_page_tables(root); + } + + map_userspace_program(root); + + // Create satp entry and enable Sv39 paging + let satp = Satp::new(8, root as *mut PageTable as usize); + hal_riscv::cpu::write_satp(satp); + + serial_info!("Enabled Sv39 paging"); + + let sstatus = Sstatus { + spp: 0, + ..Default::default() + }; + + hal_riscv::cpu::set_sstatus(sstatus); + ecall(Ecall::SModeFinishBootstrap); + + loop { + unsafe { asm!("wfi") } + } +} + +fn map_userspace_program(root: &mut PageTable) { + // Parse userspace binary text section and put data somewhere in the heap + let program_text_section = parse_text(APP_CODE); + let data = Vec::from(program_text_section).leak(); + serial_debug!("Copied user program to address: {:#x?}", data.as_ptr()); + + let pstart = data.as_ptr() as usize; + + page::map_range( + root, + TASK_BEGIN_VADDR as usize, + pstart, + 1024 * 1024, + EntryFlags::RWXU, + ); + + // Expose UART MMIO + page::map( + root, + Page::containing_address(0x10_0000_0000), + Frame::containing_address(0x10000000), + EntryFlags::RWU, + ); + + unsafe { asm!("sfence.vma zero, zero") } + + serial_debug!( + "Mapped user space memory: {:#x?} - {:#x?}", + TASK_BEGIN_VADDR, + TASK_BEGIN_VADDR + 1024 * 1024 + ); +} diff --git a/src/page.rs b/src/page.rs new file mode 100644 index 0000000..fa2f28b --- /dev/null +++ b/src/page.rs @@ -0,0 +1,141 @@ +extern crate alloc; + +use core::alloc::Layout; + +use alloc::{alloc::alloc_zeroed, boxed::Box}; +use hal_core::page::{ + EntryFlags, Frame, FrameRange, Paddr, Page, PageRange, PageTable, PageTableEntry, Vaddr, +}; + +use crate::serial_debug; + +fn map_to_frame(root: &mut PageTable, page: Page, frame: Frame, flags: EntryFlags) { + let vpn = page.addr().indexed_vpn(); + let mut table = root; + + for lv in (0..=2).rev() { + let index = vpn[lv]; + let entry = table.entry_mut(index); + + if entry.is_valid() { + if entry.is_leaf() { + // This address is already mapped, nothing to do + return; + } + + let next_page_table_paddr = entry.paddr(); + table = unsafe { &mut *next_page_table_paddr.as_mut_ptr::() }; + } else { + if lv == 0 { + // Create a leaf entry and return + *entry = PageTableEntry::new( + EntryFlags::Valid.as_u64() + | EntryFlags::Accessed.as_u64() + | EntryFlags::Dirty.as_u64() + | flags.as_u64(), + ); + entry.set_paddr(frame.addr()); + return; + } + + let next_page_table = PageTable::new(); + let ptr = Box::into_raw(Box::new(next_page_table)); + let next_page_table_paddr = Paddr::new(ptr as u64); + + *entry = PageTableEntry::new(EntryFlags::Valid.as_u64()); + entry.set_paddr(next_page_table_paddr); + table = unsafe { &mut *ptr }; + } + } +} + +pub fn allocate_root() -> &'static mut PageTable { + let root = PageTable::new(); + let ptr = Box::into_raw(Box::new(root)); + + serial_debug!("Allocated L2 (root) page table at 0x{:x}", ptr as usize); + + unsafe { &mut *ptr } +} + +pub fn id_map(root: &mut PageTable, page: Page, flags: EntryFlags) { + let frame = Frame::containing_address(page.addr().inner()); + map_to_frame(root, page, frame, flags); +} + +pub fn map(root: &mut PageTable, page: Page, frame: Frame, flags: EntryFlags) { + map_to_frame(root, page, frame, flags); +} + +pub fn map_range( + root: &mut PageTable, + vstart: usize, + pstart: usize, + size: usize, + flags: EntryFlags, +) { + let vrange = PageRange::new( + Vaddr::new(vstart as u64), + Vaddr::new((vstart + size) as u64), + ); + let prange = FrameRange::new( + Paddr::new(pstart as u64), + Paddr::new((pstart + size) as u64), + ); + let range = vrange.zip(prange); + for (page, frame) in range { + map(root, page, frame, flags.clone()); + } +} + +pub fn map_alloc(root: &mut PageTable, page: Page, flags: EntryFlags) { + let layout = Layout::from_size_align(4096, 4096).expect("Invalid layout"); + let frame = unsafe { alloc_zeroed(layout) }; + map_to_frame(root, page, Frame::containing_address(frame as u64), flags); +} + +pub fn map_alloc_range(root: &mut PageTable, start: usize, end: usize, flags: EntryFlags) { + let start = Vaddr::new(start as u64); + let end = Vaddr::new(end as u64); + + let range = PageRange::new(start, end); + for page in range { + map_alloc(root, page, flags.clone()); + } +} + +pub fn id_map_range(root: &mut PageTable, start: usize, end: usize, flags: EntryFlags) { + let start = Vaddr::new(start as u64); + let end = Vaddr::new(end as u64); + + let range = PageRange::new(start, end); + for page in range { + id_map(root, page, flags.clone()); + } +} + +pub fn translate_vaddr(root: &mut PageTable, vaddr: Vaddr) -> Option { + let vpn = vaddr.indexed_vpn(); + let mut table = root; + + for lv in (0..=2).rev() { + let index = vpn[lv]; + let entry = table.entry_mut(index); + if !entry.is_valid() { + serial_debug!("Entry is not valid: LV: {}, INDEX: {}", lv, index); + return None; + } + + if entry.is_leaf() { + let frame = entry.paddr(); + let paddr = frame.inner() | vaddr.offset(); + return Some(Paddr::new(paddr)); + } + + let next_page_table_paddr = entry.paddr(); + table = unsafe { &mut *next_page_table_paddr.as_mut_ptr::() }; + } + + serial_debug!("This code must not be reached"); + None +} diff --git a/src/serial.rs b/src/serial.rs new file mode 100644 index 0000000..a7ad490 --- /dev/null +++ b/src/serial.rs @@ -0,0 +1,146 @@ +use core::{ + fmt::{self, Write}, + ptr, +}; +use owo_colors::OwoColorize; +use spin::Mutex; + +const UART_MMIO_ADDR: usize = 0x10000000; + +const INFO: &str = "INFO"; +const DEBUG: &str = "DEBUG"; +const ERROR: &str = "ERROR"; +const PATHOS: &str = "PathOS"; + +struct Serial(usize); +static SERIAL: Mutex = Mutex::new(Serial(UART_MMIO_ADDR)); + +impl fmt::Write for Serial { + fn write_str(&mut self, s: &str) -> fmt::Result { + for byte in s.bytes() { + unsafe { ptr::write_volatile(self.0 as *mut u8, byte) } + } + Ok(()) + } +} + +pub fn write_empty_line() { + let mut serial = SERIAL.lock(); + serial.write_str("\n").expect("Printing to serial failed"); +} + +#[doc(hidden)] +pub fn _info(args: ::core::fmt::Arguments) { + let mut serial = SERIAL.lock(); + + serial + .write_fmt(format_args!("{} ", PATHOS.blue())) + .expect("Printing to serial failed"); + + serial + .write_fmt(format_args!("[{}] ", INFO.bright_green())) + .expect("Printing to serial failed"); + + serial.write_fmt(args).expect("Printing to serial failed"); +} + +#[doc(hidden)] +pub fn _error(args: ::core::fmt::Arguments) { + let mut serial = SERIAL.lock(); + + serial + .write_fmt(format_args!("{} ", PATHOS.blue())) + .expect("Printing to serial failed"); + + serial + .write_fmt(format_args!("[{}] ", ERROR.bright_red())) + .expect("Printing to serial failed"); + + serial.write_fmt(args).expect("Printing to serial failed"); +} + +#[doc(hidden)] +pub fn _debug(args: ::core::fmt::Arguments) { + let mut serial = SERIAL.lock(); + + serial + .write_fmt(format_args!("{} ", PATHOS.blue())) + .expect("Printing to serial failed"); + + serial + .write_fmt(format_args!("[{}] ", DEBUG.bright_cyan())) + .expect("Printing to serial failed"); + + serial.write_fmt(args).expect("Printing to serial failed"); +} + +/// Prints to the host through the serial interface. +#[macro_export] +macro_rules! _serial_info { + ($($arg:tt)*) => { + $crate::serial::_info(format_args!($($arg)*)); + }; +} + +#[macro_export] +macro_rules! serial_info { + () => ($crate::_serial_info!("\n")); + ($fmt:expr) => ($crate::_serial_info!(concat!($fmt, "\n"))); + ($fmt:expr, $($arg:tt)*) => ($crate::_serial_info!( + concat!($fmt, "\n"), $($arg)*)); +} + +/// Prints to the host through the serial interface. +#[macro_export] +macro_rules! _serial_error { + ($($arg:tt)*) => { + $crate::serial::_error(format_args!($($arg)*)); + }; +} + +#[macro_export] +macro_rules! serial_error { + () => ($crate::_serial_error!("\n")); + ($fmt:expr) => ($crate::_serial_error!(concat!($fmt, "\n"))); + ($fmt:expr, $($arg:tt)*) => ($crate::_serial_error!( + concat!($fmt, "\n"), $($arg)*)); +} + +/// Prints to the host through the serial interface. +#[macro_export] +macro_rules! _serial_debug { + ($($arg:tt)*) => { + $crate::serial::_debug(format_args!($($arg)*)); + }; +} + +#[macro_export] +macro_rules! serial_debug { + () => ($crate::_serial_debug!("\n")); + ($fmt:expr) => ($crate::_serial_debug!(concat!($fmt, "\n"))); + ($fmt:expr, $($arg:tt)*) => ($crate::_serial_debug!( + concat!($fmt, "\n"), $($arg)*)); +} + +#[doc(hidden)] +pub fn _print(args: ::core::fmt::Arguments) { + SERIAL + .lock() + .write_fmt(args) + .expect("Printing to serial failed"); +} + +#[macro_export] +macro_rules! serial_print { + ($($arg:tt)*) => { + $crate::serial::_print(format_args!($($arg)*)); + }; +} + +#[macro_export] +macro_rules! serial_println { + () => ($crate::serial_print!("\n")); + ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n"))); + ($fmt:expr, $($arg:tt)*) => ($crate::serial_print!( + concat!($fmt, "\n"), $($arg)*)); +} diff --git a/src/trap.rs b/src/trap.rs new file mode 100644 index 0000000..6ecf627 --- /dev/null +++ b/src/trap.rs @@ -0,0 +1,177 @@ +use core::arch::asm; + +use hal_core::page::Vaddr; +use once_cell::unsync::OnceCell; + +use crate::{alloc::Locked, KERNEL_STACK_END}; + +#[derive(Debug)] +pub struct Scheduler { + tasks: [Task; 3], + current: usize, +} + +pub static SCHEDULER: Locked> = Locked::new(OnceCell::new()); + +#[derive(Debug, Default)] +#[repr(align(8))] +pub struct TrapFrame { + ra: u64, + sp: u64, + t0: u64, + t1: u64, + t2: u64, + t3: u64, + t4: u64, + t5: u64, + t6: u64, + s0: u64, + s1: u64, + s2: u64, + s3: u64, + s4: u64, + s5: u64, + s6: u64, + s7: u64, + s8: u64, + s9: u64, + s10: u64, + s11: u64, + a0: u64, + a1: u64, + a2: u64, + a3: u64, + a4: u64, + a5: u64, + a6: u64, + a7: u64, + kernel_sp: usize, +} + +#[derive(Debug)] +pub struct Task { + pub trap_frame: TrapFrame, + addr: Vaddr, + pub pc: Vaddr, +} + +impl Task { + pub fn new(addr: Vaddr, tid: u64) -> Self { + let kernel_sp = unsafe { KERNEL_STACK_END }; + let trap_frame = TrapFrame { + kernel_sp, + a0: tid, + ..TrapFrame::default() + }; + Self { + trap_frame, + addr, + pc: addr, + } + } +} + +impl Scheduler { + #[inline(always)] + pub fn new(tasks: [Task; 3]) -> Self { + Self { tasks, current: 0 } + } + + pub fn current(&self) -> usize { + self.current + } + + #[inline(always)] + pub fn task(&self, tid: usize) -> &Task { + self.tasks.get(tid).expect("Invalid task index") + } + + pub fn save_state(&mut self, addr: u64) { + let state = self + .tasks + .get_mut(self.current) + .expect("Invalid task index"); + state.pc = Vaddr::new(addr); + } + + #[inline(always)] + pub fn next(&mut self) -> (usize, &Task) { + self.current = (self.current + 1) % self.tasks.len(); + (self.current, &self.tasks[self.current]) + } +} + +#[naked] +#[no_mangle] +pub unsafe fn save_cpu_registers() { + asm!( + "sd sp, 8(a0)", + "sd t0, 16(a0)", + "sd t1, 24(a0)", + "sd t2, 32(a0)", + "sd t3, 40(a0)", + "sd t4, 48(a0)", + "sd t5, 56(a0)", + "sd t6, 64(a0)", + "sd s0, 72(a0)", + "sd s1, 80(a0)", + "sd s2, 88(a0)", + "sd s3, 96(a0)", + "sd s4, 104(a0)", + "sd s5, 112(a0)", + "sd s6, 120(a0)", + "sd s7, 128(a0)", + "sd s8, 136(a0)", + "sd s9, 144(a0)", + "sd s10, 152(a0)", + "sd s11, 160(a0)", + "sd a1, 176(a0)", + "sd a2, 184(a0)", + "sd a3, 192(a0)", + "sd a4, 200(a0)", + "sd a5, 208(a0)", + "sd a6, 216(a0)", + "sd a7, 224(a0)", + "csrr t0, mscratch", + "sd t0, 168(a0)", + "mv t0, zero", + "ret", + options(noreturn) + ) +} + +#[naked] +#[no_mangle] +pub unsafe fn restore_cpu_registers() { + asm!( + "ld sp, 8(a0)", + "ld t0, 16(a0)", + "ld t1, 24(a0)", + "ld t2, 32(a0)", + "ld t3, 40(a0)", + "ld t4, 48(a0)", + "ld t5, 56(a0)", + "ld t6, 64(a0)", + "ld s0, 72(a0)", + "ld s1, 80(a0)", + "ld s2, 88(a0)", + "ld s3, 96(a0)", + "ld s4, 104(a0)", + "ld s5, 112(a0)", + "ld s6, 120(a0)", + "ld s7, 128(a0)", + "ld s8, 136(a0)", + "ld s9, 144(a0)", + "ld s10, 152(a0)", + "ld s11, 160(a0)", + "ld a1, 176(a0)", + "ld a2, 184(a0)", + "ld a3, 192(a0)", + "ld a4, 200(a0)", + "ld a5, 208(a0)", + "ld a6, 216(a0)", + "ld a7, 224(a0)", + "ret", + options(noreturn) + ) +} diff --git a/usercode/.cargo/config.toml b/usercode/.cargo/config.toml new file mode 100644 index 0000000..d77dc66 --- /dev/null +++ b/usercode/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "riscv-pathos.json" diff --git a/usercode/.gitignore b/usercode/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/usercode/.gitignore @@ -0,0 +1 @@ +/target diff --git a/usercode/Cargo.lock b/usercode/Cargo.lock new file mode 100644 index 0000000..52a89ee --- /dev/null +++ b/usercode/Cargo.lock @@ -0,0 +1,25 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "usercode" +version = "0.1.0" +dependencies = [ + "rand", +] diff --git a/usercode/Cargo.toml b/usercode/Cargo.toml new file mode 100644 index 0000000..cd86f71 --- /dev/null +++ b/usercode/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "usercode" +version = "0.1.0" +edition = "2021" + +[dependencies] +rand = { version = "0.8.5", default-features = false, features = ["small_rng"] } + +[profile.release] +strip = "debuginfo" + +[[bin]] +name = "usercode" +harness = false +test = false diff --git a/usercode/README.md b/usercode/README.md new file mode 100644 index 0000000..7929bb3 --- /dev/null +++ b/usercode/README.md @@ -0,0 +1,19 @@ +riscv64-linux-as -march=rv64gc -mabi=lp64d -o rt.o -c rt.s +riscv64-linux-ld app.o rt.o --no-dynamic-linker -m elf64lriscv -nostdlib -s -o app +riscv64-linux-objdump -D target/riscv64gc-unknown-none-elf/release/usercode -j .text | grep -C 10 _start + +rustflags = "-C link-arg=rt.o" + +```rs + // let mut small_rng: SmallRng = SmallRng::seed_from_u64(12345); + // let mut serial: Serial = Serial(UART_MMIO_ADDR); + + loop { + // let num = small_rng.next_u32(); + // serial.write_fmt(format_args!("{}", num)).expect("nzzzzz"); + // if num < 10 { + // serial.write_str(".").expect("noooooooo"); + // } + // unsafe { asm!("ebreak") } + } +``` \ No newline at end of file diff --git a/usercode/justfile b/usercode/justfile new file mode 100644 index 0000000..aece438 --- /dev/null +++ b/usercode/justfile @@ -0,0 +1,17 @@ +validate-target: + @ rustc --print cfg --target riscv-pathos.json + +build: + @ cargo build --release + +dump: + @ riscv64-linux-objdump -D target/riscv-pathos/release/usercode -j .text + +dump-all: + @ riscv64-linux-objdump -D target/riscv-pathos/release/usercode + +readelf: + @ riscv64-linux-readelf -a target/riscv-pathos/release/usercode + +copy: + @ cp target/riscv-pathos/release/usercode ../src/app \ No newline at end of file diff --git a/usercode/riscv-pathos.json b/usercode/riscv-pathos.json new file mode 100644 index 0000000..0af4394 --- /dev/null +++ b/usercode/riscv-pathos.json @@ -0,0 +1,30 @@ +{ + "arch": "riscv64", + "code-model": "medium", + "cpu": "generic-rv64", + "crt-objects-fallback": "false", + "data-layout": "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128", + "eh-frame-header": false, + "emit-debug-gdb-scripts": false, + "features": "+m,+a,+f,+d", + "linker": "rust-lld", + "linker-flavor": "gnu-lld", + "llvm-abiname": "lp64d", + "llvm-target": "riscv64", + "max-atomic-width": 64, + "panic-strategy": "abort", + "relocation-model": "pic", + "supported-sanitizers": [ + "kernel-address" + ], + "target-pointer-width": "64", + "pre-link-args": { + "gnu-lld": [ + "/home/jlkiri/dev/pathos/path0/path0.o", + "-T/home/jlkiri/dev/pathos/path0/default.ld", + "--no-dynamic-linker", + "--nostdlib", + "--pie" + ] + } +} \ No newline at end of file diff --git a/usercode/src/main.rs b/usercode/src/main.rs new file mode 100644 index 0000000..f24d552 --- /dev/null +++ b/usercode/src/main.rs @@ -0,0 +1,51 @@ +#![no_std] +#![no_main] + +mod print; + +use core::{fmt::Write, panic::PanicInfo}; + +use print::Serial; + +#[no_mangle] +fn main(pid: u8) -> core::result::Result<(), core::fmt::Error> { + let mut serial = Serial::new(pid); + serial.print_pid(); + writeln!(serial, "Greetings from userspace!")?; + writeln!( + serial, + "This program prints its TID (task ID) in an endless loop!" + )?; + + let mut total = 0; + loop { + total = add(total, 1); + write_pid_if_divisible_by(pid, total, 854884, &mut serial)?; + } + + Ok(()) +} + +#[inline(never)] +fn add(a: u32, b: u32) -> u32 { + a + b +} + +#[inline(never)] +fn write_pid_if_divisible_by( + pid: u8, + num: u32, + divisor: u32, + serial: &mut Serial, +) -> core::result::Result<(), core::fmt::Error> { + if num % divisor == 0 { + serial.write_char(char::from_digit(pid as u32, 10).unwrap())?; + } + Ok(()) +} + +#[panic_handler] +#[no_mangle] +fn panic(_info: &PanicInfo) -> ! { + loop {} +} diff --git a/usercode/src/print.rs b/usercode/src/print.rs new file mode 100644 index 0000000..c1c663e --- /dev/null +++ b/usercode/src/print.rs @@ -0,0 +1,32 @@ +use core::{fmt, ptr}; + +type MmioAddr = usize; +type PID = u8; + +const UART_MMIO_ADDR: MmioAddr = 0x10_0000_0000; + +pub struct Serial(MmioAddr, PID); + +impl fmt::Write for Serial { + fn write_str(&mut self, s: &str) -> fmt::Result { + for byte in s.bytes() { + unsafe { ptr::write_volatile(self.0 as *mut u8, byte) } + } + Ok(()) + } +} + +impl Serial { + pub fn new(pid: u8) -> Self { + Serial(UART_MMIO_ADDR, pid) + } + + pub fn print_pid(&self) { + unsafe { ptr::write_volatile(self.0 as *mut u8, self.1 + 48) } + unsafe { ptr::write_volatile(self.0 as *mut u8, ' ' as u8) } + unsafe { ptr::write_volatile(self.0 as *mut u8, ':' as u8) } + unsafe { ptr::write_volatile(self.0 as *mut u8, ':' as u8) } + unsafe { ptr::write_volatile(self.0 as *mut u8, ':' as u8) } + unsafe { ptr::write_volatile(self.0 as *mut u8, ' ' as u8) } + } +}