From 70f0b05f554111bb33d77109db999129bec66530 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:37:46 +0000 Subject: [PATCH 01/42] wip: added rbtree. scheduler rewrite start. --- Cargo.lock | 75 ++- Cargo.toml | 2 + machine/arm/src/sched.rs | 2 +- machine/testing/src/sched.rs | 2 +- macros/src/lib.rs | 12 + macros/src/tree.rs | 99 ++++ src/lib.rs | 2 +- src/mem.rs | 93 +-- src/mem/alloc.rs | 2 + src/mem/array.rs | 262 +++++++-- src/mem/rbtree.rs | 1061 ++++++++++++++++++++++++++++++++++ src/mem/traits.rs | 24 + src/mem/view.rs | 63 ++ src/sched.rs | 71 ++- src/sched/rt.rs | 42 ++ src/sched/task.rs | 8 +- src/sched/thread.rs | 216 ++++--- src/syscalls/tasks.rs | 3 +- src/utils.rs | 4 + 19 files changed, 1808 insertions(+), 235 deletions(-) create mode 100644 macros/src/tree.rs create mode 100644 src/mem/rbtree.rs create mode 100644 src/mem/traits.rs create mode 100644 src/mem/view.rs create mode 100644 src/sched/rt.rs diff --git a/Cargo.lock b/Cargo.lock index bdc12ce..713355d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -876,6 +876,36 @@ dependencies = [ "syn", ] +[[package]] +name = "kani" +version = "0.67.0" +source = "git+https://github.com/model-checking/kani#ac1f0a1c03fcbb805002154689ba15184e2f36b7" +dependencies = [ + "kani_core", + "kani_macros", +] + +[[package]] +name = "kani_core" +version = "0.67.0" +source = "git+https://github.com/model-checking/kani#ac1f0a1c03fcbb805002154689ba15184e2f36b7" +dependencies = [ + "kani_macros", +] + +[[package]] +name = "kani_macros" +version = "0.67.0" +source = "git+https://github.com/model-checking/kani#ac1f0a1c03fcbb805002154689ba15184e2f36b7" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "strum 0.27.2", + "strum_macros 0.27.2", + "syn", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1048,6 +1078,7 @@ dependencies = [ "hal-select", "hal-testing", "interface", + "kani", "macros", "quote", "rand", @@ -1135,6 +1166,28 @@ dependencies = [ "syn", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.105" @@ -1204,7 +1257,7 @@ dependencies = [ "itertools 0.13.0", "lru", "paste", - "strum", + "strum 0.26.3", "unicode-segmentation", "unicode-truncate", "unicode-width 0.2.0", @@ -1481,9 +1534,15 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "strum_macros", + "strum_macros 0.26.4", ] +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" + [[package]] name = "strum_macros" version = "0.26.4" @@ -1497,6 +1556,18 @@ dependencies = [ "syn", ] +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "syn" version = "2.0.114" diff --git a/Cargo.toml b/Cargo.toml index 3305670..0d02d35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,8 @@ envparse = "0.1.0" # This is a host-compatible HAL which will be used for running tests and verification on the host. hal-testing = { path = "machine/testing", features = [] } +[target.'cfg(kani_ra)'.dependencies] +kani = { git = "https://github.com/model-checking/kani" } [features] default = [] diff --git a/machine/arm/src/sched.rs b/machine/arm/src/sched.rs index 18b463f..3026071 100644 --- a/machine/arm/src/sched.rs +++ b/machine/arm/src/sched.rs @@ -55,7 +55,7 @@ impl Add for StackPtr { } /// A stack on arm is 4 byte aligned and grows downwards. -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct ArmStack { /// The top of the stack (highest address). /// Safety: NonNull can safely be covariant over u32. diff --git a/machine/testing/src/sched.rs b/machine/testing/src/sched.rs index 7bf874d..9715b16 100644 --- a/machine/testing/src/sched.rs +++ b/machine/testing/src/sched.rs @@ -5,7 +5,7 @@ use hal_api::{ stack::{StackDescriptor, Stacklike}, }; -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct TestingStack {} impl Stacklike for TestingStack { diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 903945a..c871c86 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -3,6 +3,18 @@ use syn::parse_macro_input; use proc_macro2::TokenStream; +mod tree; + +#[proc_macro_derive(TaggedLinks, attributes(rbtree))] +pub fn derive_tagged_links(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = syn::parse_macro_input!(input as syn::DeriveInput); + + match tree::derive_tagged_links(&input) { + Ok(tokens) => tokens, + Err(e) => e.to_compile_error(), + }.into() +} + #[proc_macro_attribute] pub fn service( attr: proc_macro::TokenStream, diff --git a/macros/src/tree.rs b/macros/src/tree.rs new file mode 100644 index 0000000..f6c3b39 --- /dev/null +++ b/macros/src/tree.rs @@ -0,0 +1,99 @@ +use quote::quote; +use syn::{ + spanned::Spanned, Data, DeriveInput, Error, Fields, Path, +}; + +pub fn derive_tagged_links(input: &DeriveInput) -> syn::Result { + let fields = match &input.data { + Data::Struct(ds) => match &ds.fields { + Fields::Named(named) => &named.named, + _ => { + return Err(Error::new( + ds.fields.span(), + "TaggedLinks only supports structs with named fields", + )) + } + }, + _ => { + return Err(Error::new( + input.span(), + "TaggedLinks can only be derived for structs", + )) + } + }; + + let rbtree_impls = impl_rbtree(input, fields)?; + + Ok(quote! { + #rbtree_impls + }) +} + +fn impl_rbtree(input: &DeriveInput, fields: &syn::punctuated::Punctuated) -> syn::Result { + let struct_ident = &input.ident; + let generics = &input.generics; + + let mut impls = Vec::new(); + + for field in fields { + let Some(field_ident) = field.ident.clone() else { continue }; + + if let (Some(tag_path), Some(idx_path)) = find_rbtree(&field.attrs)? { + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let impl_block = quote! { + impl #impl_generics crate::mem::rbtree::Linkable<#tag_path, #idx_path> for #struct_ident #ty_generics #where_clause { + #[inline] + fn links(&self) -> &crate::mem::rbtree::Links<#tag_path, #idx_path> { + &self.#field_ident + } + #[inline] + fn links_mut(&mut self) -> &mut crate::mem::rbtree::Links<#tag_path, #idx_path> { + &mut self.#field_ident + } + } + }; + + impls.push(impl_block); + } + } + + if impls.is_empty() { + return Err(Error::new( + input.span(), + "No fields found with #[rbtree(tag = ..., idx = ...)] attribute", + )); + } + + Ok(quote! { #(#impls)* }) +} + +fn find_rbtree(attrs: &[syn::Attribute]) -> syn::Result<(Option, Option)> { + for attr in attrs { + if !attr.path().is_ident("rbtree") { + continue; + } + + let mut tag: Option = None; + let mut idx: Option = None; + + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("tag") { + let value = meta.value()?; // expects '=' + let p: Path = value.parse()?; + tag = Some(p); + Ok(()) + } else if meta.path.is_ident("idx") { + let value = meta.value()?; // expects '=' + let p: Path = value.parse()?; + idx = Some(p); + Ok(()) + } else { + Err(meta.error("expected `tag = SomePath` or `idx = SomePath`")) + } + })?; + + return Ok((tag, idx)); + } + Ok((None, None)) +} diff --git a/src/lib.rs b/src/lib.rs index 844751b..b41219c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ pub mod print; pub mod sched; pub mod sync; pub mod syscalls; -pub mod time; +//pub mod time; pub mod uspace; use hal::Machinelike; diff --git a/src/mem.rs b/src/mem.rs index b1ec789..ad27833 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -11,6 +11,9 @@ pub mod boxed; pub mod heap; pub mod pool; pub mod queue; +pub mod rbtree; +pub mod traits; +pub mod view; /// The possible types of memory. Which is compatible with the multiboot2 memory map. /// Link: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html @@ -90,92 +93,4 @@ pub fn align_up(size: usize) -> usize { let align = align_of::(); (size + align - 1) & !(align - 1) -} - -// VERIFICATION ------------------------------------------------------------------------------------------------------- -#[cfg(kani)] -mod verification { - use crate::MemMapEntry; - - use super::*; - use kani::Arbitrary; - - impl Arbitrary for MemMapEntry { - fn any() -> Self { - let size = size_of::() as u32; - let length = kani::any(); - let addr = kani::any(); - - kani::assume( - length < alloc::MAX_ADDR as u64 - && length > alloc::BestFitAllocator::MIN_RANGE_SIZE as u64, - ); - kani::assume(addr < alloc::MAX_ADDR as u64 - length && addr > 0); - - MemMapEntry { - size, - addr, - length, - ty: kani::any(), - } - } - - fn any_array() -> [Self; MAX_ARRAY_LENGTH] { - [(); MAX_ARRAY_LENGTH].map(|_| Self::any()) - } - } - - fn mock_ptr_write(dst: *mut T, src: T) { - // Just a noop - } - - #[kani::proof] - #[kani::stub(core::ptr::write, mock_ptr_write)] - fn proof_init_allocator_good() { - let mmap: [MemMapEntry; _] = kani::any(); - - kani::assume(mmap.len() > 0 && mmap.len() <= 8); - for entry in mmap.iter() { - // Ensure aligned. - kani::assume(entry.addr % align_of::() as u64 == 0); - // Ensure valid range. - kani::assume(entry.addr > 0); - kani::assume(entry.length > 0); - - // Ensure non overlapping entries - for other in mmap.iter() { - if entry.addr != other.addr { - kani::assume( - entry.addr + entry.length <= other.addr - || other.addr + other.length <= entry.addr, - ); - } - } - } - - let mmap_len = mmap.len(); - - let boot_info = BootInfo { - implementer: core::ptr::null(), - variant: core::ptr::null(), - mmap, - mmap_len, - }; - - assert!(init_memory(&boot_info).is_ok()); - } - - #[kani::proof] - fn check_align_up() { - let size = kani::any(); - kani::assume(size > 0); - let align = align_up(size); - assert_ne!(align, 0); - - if align != usize::MAX { - assert_eq!(align % align_of::(), 0); - assert!(align >= size); - } - } -} -// END VERIFICATION +} \ No newline at end of file diff --git a/src/mem/alloc.rs b/src/mem/alloc.rs index d02c2fd..415bccc 100644 --- a/src/mem/alloc.rs +++ b/src/mem/alloc.rs @@ -45,6 +45,8 @@ pub struct BestFitAllocator { /// Implementation of the BestFitAllocator. impl BestFitAllocator { + pub const MIN_RANGE_SIZE: usize = size_of::() + Self::align_up() + 1; + /// Creates a new BestFitAllocator. /// /// Returns the new BestFitAllocator. diff --git a/src/mem/array.rs b/src/mem/array.rs index e7274db..4e40954 100644 --- a/src/mem/array.rs +++ b/src/mem/array.rs @@ -1,18 +1,26 @@ //! This module implements static and dynamic arrays for in-kernel use. use super::boxed::Box; -use crate::utils::KernelError; +use crate::{ + mem::traits::{Get, GetMut, ToIndex}, + utils::KernelError, +}; use core::{borrow::Borrow, mem::MaybeUninit}; +use std::{ + ops::{Index, IndexMut}, +}; /// This is a fixed-size map that can store up to N consecutive elements. #[derive(Debug)] -pub struct IndexMap + Default, V, const N: usize> { +pub struct IndexMap +{ data: [Option; N], phantom: core::marker::PhantomData, } #[allow(dead_code)] -impl + Default, V, const N: usize> IndexMap { +impl IndexMap +{ /// Create a new IndexMap. /// /// Returns a new IndexMap. @@ -23,47 +31,17 @@ impl + Default, V, const N: usize> IndexMap { } } - /// Get the element at the given index. - /// - /// `index` - The index to get the element from. - /// - /// Returns `Some(&T)` if the index is in-bounds, otherwise `None`. - pub fn get(&self, index: &K) -> Option<&V> { - let index = *index.borrow(); - - if index < N { - self.data[index].as_ref() - } else { - None - } - } - - /// Get a mutable reference to the element at the given index. - /// - /// `index` - The index to get the element from. - /// - /// Returns `Some(&mut T)` if the index is in-bounds, otherwise `None`. - pub fn get_mut(&mut self, index: &K) -> Option<&mut V> { - let index = *index.borrow(); - - if index < N { - self.data[index].as_mut() - } else { - None - } - } - /// Insert a value at the given index. /// /// `index` - The index to insert the value at. /// `value` - The value to insert. /// /// Returns `Ok(())` if the index was in-bounds, otherwise `Err(KernelError::OutOfMemory)`. - pub fn insert(&mut self, index: &K, value: V) -> Result<(), KernelError> { - let index = *index.borrow(); + pub fn insert(&mut self, idx: &K, value: V) -> Result<(), KernelError> { + let idx = K::to_index(Some(idx)); - if index < N { - self.data[index] = Some(value); + if idx < N { + self.data[idx] = Some(value); Ok(()) } else { Err(KernelError::OutOfMemory) @@ -91,11 +69,11 @@ impl + Default, V, const N: usize> IndexMap { /// `index` - The index to remove the value from. /// /// Returns the value if it was removed, otherwise `None`. - pub fn remove(&mut self, index: &K) -> Option { - let index = *index.borrow(); + pub fn remove(&mut self, idx: &K) -> Option { + let idx = K::to_index(Some(idx)); - if index < N { - self.data[index].take() + if idx < N { + self.data[idx].take() } else { None } @@ -113,8 +91,8 @@ impl + Default, V, const N: usize> IndexMap { /// `index` - The index to start the iterator from. /// /// Returns an iterator over the elements in the map. - pub fn iter_from_cycle(&self, index: &K) -> impl Iterator> { - self.data.iter().cycle().skip(index.borrow() + 1) + pub fn iter_from_cycle(&self, idx: Option<&K>) -> impl Iterator> { + self.data.iter().cycle().skip(K::to_index(idx) + 1) } /// Get the next index that contains a value (this will cycle). @@ -122,13 +100,11 @@ impl + Default, V, const N: usize> IndexMap { /// `index` - The index to start the search from. /// /// Returns the next index (potentially < index) that contains a value, otherwise `None`. - pub fn next(&self, index: Option<&K>) -> Option { - let default = K::default(); - let index = index.unwrap_or(&default); - - for (i, elem) in self.iter_from_cycle(index).enumerate() { + pub fn next(&self, idx: Option<&K>) -> Option { + for (i, elem) in self.iter_from_cycle(idx).enumerate() { if elem.is_some() { - return Some((index.borrow() + i + 1) % N); + let idx = K::to_index(idx); + return Some((idx + i + 1) % N); } } @@ -146,6 +122,88 @@ impl + Default, V, const N: usize> IndexMap { } } +impl Index for IndexMap +{ + type Output = V; + + fn index(&self, index: K) -> &Self::Output { + self.get(&index).unwrap() + } +} + +impl IndexMut for IndexMap +{ + fn index_mut(&mut self, index: K) -> &mut Self::Output { + self.get_mut(&index).unwrap() + } +} + +impl Get for IndexMap +{ + type Output = V; + + fn get>(&self, index: Q) -> Option<&Self::Output> { + let idx = K::to_index(Some(index.borrow())); + if idx < N { + self.data[idx].as_ref() + } else { + None + } + } +} + +impl GetMut for IndexMap { + fn get_mut>(&mut self, index: Q) -> Option<&mut Self::Output> { + let idx = K::to_index(Some(index.borrow())); + if idx < N { + self.data[idx].as_mut() + } else { + None + } + } + + fn get2_mut>(&mut self, index1: Q, index2: Q) -> (Option<&mut Self::Output>, Option<&mut Self::Output>) { + let index1 = K::to_index(Some(index1.borrow())); + let index2 = K::to_index(Some(index2.borrow())); + + if index1 == index2 { + debug_assert!(false, "get2_mut called with identical indices"); + return (None, None); + } + + let ptr1 = &mut self.data[index1] as *mut Option; + let ptr2 = &mut self.data[index2] as *mut Option; + + // Safety: the elements at index1 and index2 are nowhere else borrowed mutably by function contract. + // And they are disjoint because of the check above. + unsafe { ((*ptr1).as_mut(), (*ptr2).as_mut()) } + } + + fn get3_mut>( + &mut self, + index1: Q, + index2: Q, + index3: Q, + ) -> (Option<&mut Self::Output>, Option<&mut Self::Output>, Option<&mut Self::Output>) { + let index1 = K::to_index(Some(index1.borrow())); + let index2 = K::to_index(Some(index2.borrow())); + let index3 = K::to_index(Some(index3.borrow())); + + if index1 == index2 || index1 == index3 || index2 == index3 { + debug_assert!(false, "get3_mut called with identical indices"); + return (None, None, None); + } + + let ptr1 = &mut self.data[index1] as *mut Option; + let ptr2 = &mut self.data[index2] as *mut Option; + let ptr3 = &mut self.data[index3] as *mut Option; + + // Safety: the elements at index1, index2 and index3 are nowhere else borrowed mutably by function contract. + // And they are disjoint because of the check above. + unsafe { ((*ptr1).as_mut(), (*ptr2).as_mut(), (*ptr3).as_mut()) } + } +} + /// This is a vector that can store up to N elements inline and will allocate on the heap if more are needed. #[derive(Debug)] pub struct Vec { @@ -403,6 +461,66 @@ impl Vec { } } + fn at_mut_unchecked(&mut self, index: usize) -> *mut T { + if index < N { + // Safety: the elements until self.len are initialized. + // The element at index is nowhere else borrowed mutably by function contract. + self.data[index].as_mut_ptr() + } else { + let index = index - N; + // Safety: the elements until self.len - N are initialized. + // The element at index is nowhere else borrowed mutably by function contract. + self.extra[index].as_mut_ptr() + } + } + + /// Get disjoint mutable references to the values at the given indices. + /// + /// `index1` - The first index. + /// `index2` - The second index. + /// + /// Returns `Some(&mut T, &mut T)` if the indices are in-bounds and disjoint, otherwise `None`. + pub fn at2_mut(&mut self, index1: usize, index2: usize) -> (Option<&mut T>, Option<&mut T>) { + if index1 == index2 { + debug_assert!(false, "at2_mut called with identical indices"); + return (None, None); + } + + let ptr1 = self.at_mut_unchecked(index1); + let ptr2 = self.at_mut_unchecked(index2); + + // Safety: the elements at index1 and index2 are nowhere else borrowed mutably by function contract. + // And they are disjoint because of the check above. + unsafe { (Some(&mut *ptr1), Some(&mut *ptr2)) } + } + + /// Get disjoint mutable references to the values at the given indices. + /// + /// `index1` - The first index. + /// `index2` - The second index. + /// `index3` - The third index. + /// + /// Returns `Some(&mut T, &mut T, &mut T)` if the indices are in-bounds and disjoint, otherwise `None`. + pub fn at3_mut( + &mut self, + index1: usize, + index2: usize, + index3: usize, + ) -> (Option<&mut T>, Option<&mut T>, Option<&mut T>) { + if index1 == index2 || index1 == index3 || index2 == index3 { + debug_assert!(false, "at3_mut called with identical indices"); + return (None, None, None); + } + + let ptr1 = self.at_mut_unchecked(index1); + let ptr2 = self.at_mut_unchecked(index2); + let ptr3 = self.at_mut_unchecked(index3); + + // Safety: the elements at index1, index2 and index3 are nowhere else borrowed mutably by function contract. + // And they are disjoint because of the check above. + unsafe { (Some(&mut *ptr1), Some(&mut *ptr2), Some(&mut *ptr3)) } + } + /// Swap the values at the given indices. /// /// `a` - The first index. @@ -465,7 +583,47 @@ impl Drop for Vec { } } -#[cfg(kani)] -mod verification { - use super::*; +impl Index for Vec { + type Output = T; + + fn index(&self, index: usize) -> &Self::Output { + self.at(index).unwrap() + } +} + +impl IndexMut for Vec { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.at_mut(index).unwrap() + } +} + +impl Get for Vec { + type Output = T; + + fn get>(&self, index: Q) -> Option<&Self::Output> { + self.at(*index.borrow()) + } +} + +impl GetMut for Vec { + fn get_mut>(&mut self, index: Q) -> Option<&mut Self::Output> { + self.at_mut(*index.borrow()) + } + + fn get2_mut>( + &mut self, + index1: Q, + index2: Q, + ) -> (Option<&mut Self::Output>, Option<&mut Self::Output>) { + self.at2_mut(*index1.borrow(), *index2.borrow()) + } + + fn get3_mut>( + &mut self, + index1: Q, + index2: Q, + index3: Q, + ) -> (Option<&mut Self::Output>, Option<&mut Self::Output>, Option<&mut Self::Output>) { + self.at3_mut(*index1.borrow(), *index2.borrow(), *index3.borrow()) + } } diff --git a/src/mem/rbtree.rs b/src/mem/rbtree.rs new file mode 100644 index 0000000..2f7a5f0 --- /dev/null +++ b/src/mem/rbtree.rs @@ -0,0 +1,1061 @@ +use std::{marker::PhantomData}; + +use crate::mem::traits::{Get, GetMut}; + +#[allow(dead_code)] +pub struct RbTree { + root: Option, + min: Option, + _tag: PhantomData, +} + +#[allow(dead_code)] +pub trait Linkable { + fn links(&self) -> &Links; + fn links_mut(&mut self) -> &mut Links; +} + +pub trait Compare { + fn cmp(&self, other: &Self) -> core::cmp::Ordering; +} + +#[allow(dead_code)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Links { + parent: Option, + left: Option, + right: Option, + color: Color, + _tag: PhantomData, +} + +#[allow(dead_code)] +impl Links { + pub fn new() -> Self { + Self { + parent: None, + left: None, + right: None, + color: Color::Red, + _tag: PhantomData, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum Color { + Red, + Black, +} + +#[allow(dead_code)] +impl RbTree +{ + pub fn new() -> Self { + Self { + root: None, + min: None, + _tag: PhantomData, + } + } + + pub fn insert + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + where >::Output: Linkable + Compare,{ + let mut last = None; + + { + let node = storage.get(id).ok_or(())?; + let mut current = self.root; + + while let Some(current_id) = current { + last = current; + let current_node = storage.get(current_id).ok_or(())?; + let go_left = node.cmp(current_node) == core::cmp::Ordering::Less; + + current = if go_left { + current_node.links().left + } else { + current_node.links().right + }; + } + } + + { + let node = storage.get_mut(id).ok_or(())?.links_mut(); + node.parent = last; + node.left = None; + node.right = None; + node.color = Color::Red; + } + + match last { + None => self.root = Some(id), + Some(last_id) => { + if let (Some(node), Some(last)) = storage.get2_mut(id, last_id) { + if node.cmp(last) == core::cmp::Ordering::Less { + last.links_mut().left = Some(id); + } else { + last.links_mut().right = Some(id); + } + } + } + } + + if let Some(min_id) = self.min { + let node = storage.get(id).ok_or(())?; + let min_node = storage.get(min_id).ok_or(())?; + if node.cmp(min_node) == core::cmp::Ordering::Less { + self.min = Some(id); + } + } else { + self.min = Some(id); + } + + self.insert_fixup(id, storage) + } + + pub fn remove + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + where >::Output: Linkable + Compare { + let (node_left, node_right, node_parent, node_is_red) = { + let node = storage.get(id).ok_or(())?; + ( + node.links().left, + node.links().right, + node.links().parent, + matches!(node.links().color, Color::Red), + ) + }; + + let mut succ_was_red = node_is_red; + let child: Option; + let child_parent: Option; + + if node_left.is_none() { + child = node_right; + child_parent = node_parent; + + self.transplant(id, node_right, storage)?; + } else if node_right.is_none() { + child = node_left; + child_parent = node_parent; + + self.transplant(id, node_left, storage)?; + } else { + let right_id = node_right.ok_or(())?; + let succ = self.minimum(right_id, storage)?; + let succ_right = storage.get(succ).and_then(|n| n.links().right); + let succ_parent = storage.get(succ).and_then(|n| n.links().parent); + + succ_was_red = storage + .get(succ) + .map_or(false, |n| matches!(n.links().color, Color::Red)); + child = succ_right; + + if succ_parent == Some(id) { + child_parent = Some(succ); + } else { + self.transplant(succ, succ_right, storage)?; + + if let (Some(succ_node), Some(right_node)) = storage.get2_mut(succ, right_id) { + succ_node.links_mut().right = Some(right_id); + right_node.links_mut().parent = Some(succ); + } else { + return Err(()); + } + + child_parent = succ_parent; + } + + self.transplant(id, Some(succ), storage)?; + + let left_id = node_left.ok_or(())?; + + if let (Some(succ_node), Some(left_node)) = storage.get2_mut(succ, left_id) { + succ_node.links_mut().left = Some(left_id); + left_node.links_mut().parent = Some(succ); + } else { + return Err(()); + } + + if let Some(succ_node) = storage.get_mut(succ) { + succ_node.links_mut().color = if node_is_red { + Color::Red + } else { + Color::Black + }; + } else { + return Err(()); + } + } + + if !succ_was_red { + self.delete_fixup(child, child_parent, storage)?; + } + + if self.min == Some(id) { + self.min = match self.root { + Some(root_id) => Some(self.minimum(root_id, storage)?), + None => None, + }; + } + + Ok(()) + } + + pub fn min(&self) -> Option { + self.min + } + + fn insert_fixup + GetMut>(&mut self, mut id: T, storage: &mut S) -> Result<(), ()> + where >::Output: Linkable + Compare, { + while let Some(parent) = storage.get(id).and_then(|n| n.links().parent) + && storage + .get(parent) + .map_or(false, |n| matches!(n.links().color, Color::Red)) + { + let grandparent = storage + .get(parent) + .and_then(|n| n.links().parent) + .ok_or(())?; + + // Is left child node + if storage + .get(grandparent) + .map_or(false, |n| n.links().left == Some(parent)) + { + // Uncle node must be the right child node + let uncle = storage.get(grandparent).and_then(|n| n.links().right); + + if let Some(uncle_id) = uncle + && storage + .get(uncle_id) + .map_or(false, |n| matches!(n.links().color, Color::Red)) + { + // Parent and uncle nodes are red + if let (Some(parent_node), Some(uncle_node), Some(grandparent_node)) = + storage.get3_mut(parent, uncle_id, grandparent) + { + parent_node.links_mut().color = Color::Black; + uncle_node.links_mut().color = Color::Black; + grandparent_node.links_mut().color = Color::Red; + } + id = grandparent; + } else { + // Uncle node is black + if storage + .get(parent) + .map_or(false, |n| n.links().right == Some(id)) + { + let old_parent = parent; + self.rotate_left(parent, id, storage)?; + id = old_parent; + } + + let parent = storage.get(id).and_then(|n| n.links().parent).ok_or(())?; + let grandparent = storage + .get(parent) + .and_then(|n| n.links().parent) + .ok_or(())?; + + if let (Some(parent_node), Some(grandparent_node)) = + storage.get2_mut(parent, grandparent) + { + parent_node.links_mut().color = Color::Black; + grandparent_node.links_mut().color = Color::Red; + } + self.rotate_right(grandparent, parent, storage)?; + break; + } + } else { + // Uncle node must be the left child + let uncle = storage.get(grandparent).and_then(|n| n.links().left); + + if let Some(uncle_id) = uncle + && storage + .get(uncle_id) + .map_or(false, |n| matches!(n.links().color, Color::Red)) + { + // Parent and uncle nodes are red + if let (Some(parent_node), Some(uncle_node), Some(grandparent_node)) = + storage.get3_mut(parent, uncle_id, grandparent) + { + parent_node.links_mut().color = Color::Black; + uncle_node.links_mut().color = Color::Black; + grandparent_node.links_mut().color = Color::Red; + } + id = grandparent; + } else { + // Uncle node is black + if storage + .get(parent) + .map_or(false, |n| n.links().left == Some(id)) + { + let old_parent = parent; + self.rotate_right(parent, id, storage)?; + id = old_parent; + } + + let parent = storage.get(id).and_then(|n| n.links().parent).ok_or(())?; + let grandparent = storage + .get(parent) + .and_then(|n| n.links().parent) + .ok_or(())?; + + if let (Some(parent_node), Some(grandparent_node)) = + storage.get2_mut(parent, grandparent) + { + parent_node.links_mut().color = Color::Black; + grandparent_node.links_mut().color = Color::Red; + } + self.rotate_left(grandparent, parent, storage)?; + break; + } + } + } + + if let Some(root_id) = self.root { + if let Some(root_node) = storage.get_mut(root_id) { + root_node.links_mut().color = Color::Black; + } + } + + Ok(()) + } + + fn delete_fixup + GetMut>( + &mut self, + mut id: Option, + mut parent: Option, + storage: &mut S, + ) -> Result<(), ()> + where >::Output: Linkable + Compare, { + let is_red = |node_id: Option, storage: &S| -> bool { + node_id + .and_then(|id| storage.get(id)) + .map_or(false, |n| matches!(n.links().color, Color::Red)) + }; + + let is_black = |node_id: Option, storage: &S| -> bool { !is_red(node_id, storage) }; + + while id != self.root && is_black(id, storage) { + let parent_id = parent.ok_or(())?; + + let is_left_child = storage + .get(parent_id) + .map_or(false, |n| n.links().left == id); + + if is_left_child { + let mut sibling_opt = storage.get(parent_id).and_then(|n| n.links().right); + + if is_red(sibling_opt, storage) { + let sibling_id = sibling_opt.ok_or(())?; + // Color sibling node black and parent node red, rotate + if let (Some(sib), Some(par)) = storage.get2_mut(sibling_id, parent_id) { + sib.links_mut().color = Color::Black; + par.links_mut().color = Color::Red; + } else { + return Err(()); + } + self.rotate_left(parent_id, sibling_id, storage)?; + sibling_opt = storage.get(parent_id).and_then(|n| n.links().right); + } + + // Sibling node is black + let sibling_id = sibling_opt.ok_or(())?; + let sib_left = storage.get(sibling_id).and_then(|n| n.links().left); + let sib_right = storage.get(sibling_id).and_then(|n| n.links().right); + + if is_black(sib_left, storage) && is_black(sib_right, storage) { + // Color sibling node red and move up + if let Some(sib) = storage.get_mut(sibling_id) { + sib.links_mut().color = Color::Red; + } else { + return Err(()); + } + id = Some(parent_id); + parent = storage.get(parent_id).and_then(|n| n.links().parent); + } else { + // Sibling's left node is red + if is_black(sib_right, storage) { + let sib_left_id = sib_left.ok_or(())?; + if let (Some(sib), Some(left)) = storage.get2_mut(sibling_id, sib_left_id) { + sib.links_mut().color = Color::Red; + left.links_mut().color = Color::Black; + } else { + return Err(()); + } + self.rotate_right(sibling_id, sib_left_id, storage)?; + sibling_opt = storage.get(parent_id).and_then(|n| n.links().right); + } + + // Sibling's right child node is red + let sibling_id = sibling_opt.ok_or(())?; + let parent_is_red = storage + .get(parent_id) + .map_or(false, |n| matches!(n.links().color, Color::Red)); + + if let Some(sib) = storage.get_mut(sibling_id) { + sib.links_mut().color = if parent_is_red { + Color::Red + } else { + Color::Black + }; + } + if let Some(par) = storage.get_mut(parent_id) { + par.links_mut().color = Color::Black; + } + + let sib_right = storage.get(sibling_id).and_then(|n| n.links().right); + if let Some(sib_right_id) = sib_right { + if let Some(right) = storage.get_mut(sib_right_id) { + right.links_mut().color = Color::Black; + } + } + + self.rotate_left(parent_id, sibling_id, storage)?; + id = self.root; + break; + } + } else { + let mut sibling_opt = storage.get(parent_id).and_then(|n| n.links().left); + + if is_red(sibling_opt, storage) { + let sibling_id = sibling_opt.ok_or(())?; + if let (Some(sib), Some(par)) = storage.get2_mut(sibling_id, parent_id) { + sib.links_mut().color = Color::Black; + par.links_mut().color = Color::Red; + } else { + return Err(()); + } + self.rotate_right(parent_id, sibling_id, storage)?; + sibling_opt = storage.get(parent_id).and_then(|n| n.links().left); + } + + // Sibling node is black + let sibling_id = sibling_opt.ok_or(())?; + let sib_left = storage.get(sibling_id).and_then(|n| n.links().left); + let sib_right = storage.get(sibling_id).and_then(|n| n.links().right); + + if is_black(sib_left, storage) && is_black(sib_right, storage) { + if let Some(sib) = storage.get_mut(sibling_id) { + sib.links_mut().color = Color::Red; + } else { + return Err(()); + } + id = Some(parent_id); + parent = storage.get(parent_id).and_then(|n| n.links().parent); + } else { + // Sibling's right node is red + if is_black(sib_left, storage) { + let sib_right_id = sib_right.ok_or(())?; + if let (Some(sib), Some(right)) = storage.get2_mut(sibling_id, sib_right_id) + { + sib.links_mut().color = Color::Red; + right.links_mut().color = Color::Black; + } else { + return Err(()); + } + self.rotate_left(sibling_id, sib_right_id, storage)?; + sibling_opt = storage.get(parent_id).and_then(|n| n.links().left); + } + + // Sibling's left child node is red + let sibling_id = sibling_opt.ok_or(())?; + let parent_is_red = storage + .get(parent_id) + .map_or(false, |n| matches!(n.links().color, Color::Red)); + + if let Some(sib) = storage.get_mut(sibling_id) { + sib.links_mut().color = if parent_is_red { + Color::Red + } else { + Color::Black + }; + } + if let Some(par) = storage.get_mut(parent_id) { + par.links_mut().color = Color::Black; + } + + let sib_left = storage.get(sibling_id).and_then(|n| n.links().left); + if let Some(sib_left_id) = sib_left { + if let Some(left) = storage.get_mut(sib_left_id) { + left.links_mut().color = Color::Black; + } + } + + self.rotate_right(parent_id, sibling_id, storage)?; + id = self.root; + break; + } + } + } + + // Color the root node black + if let Some(id) = id { + if let Some(node) = storage.get_mut(id) { + node.links_mut().color = Color::Black; + } + } + + Ok(()) + } + + fn minimum>(&self, mut id: T, storage: &S) -> Result + where >::Output: Linkable + Compare, { + loop { + let left = storage.get(id).ok_or(())?.links().left; + match left { + Some(left_id) => id = left_id, + None => return Ok(id), + } + } + } + + fn transplant + GetMut>(&mut self, u: T, v: Option, storage: &mut S) -> Result<(), ()> + where >::Output: Linkable + Compare, { + let u_parent = storage.get(u).and_then(|n| n.links().parent); + + match u_parent { + None => self.root = v, + Some(parent_id) => { + if let Some(parent_node) = storage.get_mut(parent_id) { + if parent_node.links().left == Some(u) { + parent_node.links_mut().left = v; + } else { + parent_node.links_mut().right = v; + } + } else { + return Err(()); + } + } + } + + if let Some(v_id) = v { + if let Some(v_node) = storage.get_mut(v_id) { + v_node.links_mut().parent = u_parent; + } else { + return Err(()); + } + } + + Ok(()) + } + + fn rotate_right + GetMut>(&mut self, pivot: T, left: T, storage: &mut S) -> Result<(), ()> + where >::Output: Linkable + Compare, { + if pivot == left { + return Err(()); + } + + let (right, parent) = + if let (Some(pivot_node), Some(left_node)) = storage.get2_mut(pivot, left) { + // Add left child's right subtree as pivot's left subtree + pivot_node.links_mut().left = left_node.links().right; + + // Add pivot's parent as left child's parent + left_node.links_mut().parent = pivot_node.links().parent; + + let old_right = left_node.links().right; + + // Set pivot as the right child of left child + left_node.links_mut().right = Some(pivot); + + let old_parent = pivot_node.links().parent; + + // Set pivot's parent to left child + pivot_node.links_mut().parent = Some(left); + + (old_right, old_parent) + } else { + return Err(()); + }; + + if let Some(right_id) = right { + if let Some(right_node) = storage.get_mut(right_id) { + right_node.links_mut().parent = Some(pivot); + } + } + + match parent { + None => self.root = Some(left), + Some(parent_id) => { + if let Some(parent_node) = storage.get_mut(parent_id) { + if parent_node.links().left == Some(pivot) { + parent_node.links_mut().left = Some(left); + } else { + parent_node.links_mut().right = Some(left); + } + } else { + return Err(()); + } + } + } + + Ok(()) + } + + fn rotate_left + GetMut>(&mut self, pivot: T, right: T, storage: &mut S) -> Result<(), ()> + where >::Output: Linkable + Compare, { + if pivot == right { + return Err(()); + } + + let (left, parent) = + if let (Some(pivot_node), Some(right_node)) = storage.get2_mut(pivot, right) { + // Add right child's left subtree as pivot's right subtree + pivot_node.links_mut().right = right_node.links().left; + + // Add pivot's parent as right child's parent + right_node.links_mut().parent = pivot_node.links().parent; + + let old_left = right_node.links().left; + + // Set pivot as the left child of right child + right_node.links_mut().left = Some(pivot); + + let old_parent = pivot_node.links().parent; + + // Set pivot's parent to right child + pivot_node.links_mut().parent = Some(right); + + (old_left, old_parent) + } else { + return Err(()); + }; + + if let Some(left_id) = left { + if let Some(left_node) = storage.get_mut(left_id) { + left_node.links_mut().parent = Some(pivot); + } + } + + match parent { + None => self.root = Some(right), + Some(parent_id) => { + if let Some(parent_node) = storage.get_mut(parent_id) { + if parent_node.links().left == Some(pivot) { + parent_node.links_mut().left = Some(right); + } else { + parent_node.links_mut().right = Some(right); + } + } else { + return Err(()); + } + } + } + Ok(()) + } +} + +// TESTING ------------------------------------------------------------------------------------------------------------ + +#[cfg(test)] +mod tests { + use super::*; + use crate::mem::traits::{Get, GetMut}; + use std::borrow::Borrow; + use std::collections::HashSet; + + struct Tree; + + struct Node { + key: i32, + links: Links, + } + + impl Node { + fn new(key: i32) -> Self { + Self { + key, + links: Links::new(), + } + } + } + + impl Compare for Node { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.key.cmp(&other.key) + } + } + + impl Linkable for Node { + fn links(&self) -> &Links { + &self.links + } + + fn links_mut(&mut self) -> &mut Links { + &mut self.links + } + } + + struct NodeStore { + nodes: Vec, + } + + impl NodeStore { + fn new(keys: &[i32]) -> Self { + Self { + nodes: keys.iter().copied().map(Node::new).collect(), + } + } + } + + impl Get for NodeStore { + type Output = Node; + + fn get>(&self, index: K) -> Option<&Self::Output> { + self.nodes.get(*index.borrow()) + } + } + + impl GetMut for NodeStore { + fn get_mut>(&mut self, index: K) -> Option<&mut Self::Output> { + self.nodes.get_mut(*index.borrow()) + } + + fn get2_mut>( + &mut self, + index1: K, + index2: K, + ) -> (Option<&mut Self::Output>, Option<&mut Self::Output>) { + if *index1.borrow() == *index2.borrow() { + return (None, None); + } + + let ptr = self.nodes.as_ptr(); + + return unsafe { + ( + Some(&mut *(ptr.add(*index1.borrow()) as *mut Self::Output)), + Some(&mut *(ptr.add(*index2.borrow()) as *mut Self::Output)), + ) + }; + } + + fn get3_mut>( + &mut self, + index1: K, + index2: K, + index3: K, + ) -> ( + Option<&mut Self::Output>, + Option<&mut Self::Output>, + Option<&mut Self::Output>, + ) { + if *index1.borrow() == *index2.borrow() + || *index1.borrow() == *index3.borrow() + || *index2.borrow() == *index3.borrow() + { + return (None, None, None); + } + + let ptr = self.nodes.as_ptr(); + return unsafe { + ( + Some(&mut *(ptr.add(*index1.borrow()) as *mut Self::Output)), + Some(&mut *(ptr.add(*index2.borrow()) as *mut Self::Output)), + Some(&mut *(ptr.add(*index3.borrow()) as *mut Self::Output)), + ) + }; + } + } + + fn validate_tree(tree: &RbTree, store: &NodeStore, expected: &[i32]) { + let mut visited = HashSet::new(); + + if let Some(root_id) = tree.root { + let root = store.get(root_id).expect("root missing from store"); + assert!(matches!(root.links().color, Color::Black)); + assert_eq!(root.links().parent, None); + } + + let (count, _) = validate_node(tree.root, store, &mut visited, expected); + assert_eq!(count, expected.len()); + + if !expected.is_empty() { + let min = tree_min_key(tree, store).expect("non-empty tree must contain a min."); + assert_eq!(min, expected[0]); + } + } + + fn tree_min_key(tree: &RbTree, store: &NodeStore) -> Option { + tree.min().map(|id| store.get(id).expect("min missing").key) + } + + fn validate_node( + id: Option, + store: &NodeStore, + visited: &mut HashSet, + expected: &[i32], + ) -> (usize, usize) { + let Some(id) = id else { + return (0, 1); + }; + + assert!(visited.insert(id)); + + let node = store.get(id).expect("node missing from store"); + + let left = node.links().left; + let right = node.links().right; + + if matches!(node.links().color, Color::Red) { + if let Some(left_id) = left { + let left_node = store.get(left_id).expect("left missing"); + assert!(matches!(left_node.links().color, Color::Black)); + } + if let Some(right_id) = right { + let right_node = store.get(right_id).expect("right missing"); + assert!(matches!(right_node.links().color, Color::Black)); + } + } + + if let Some(left_id) = left { + let left_node = store.get(left_id).expect("left missing"); + assert_eq!(left_node.links().parent, Some(id)); + } + if let Some(right_id) = right { + let right_node = store.get(right_id).expect("right missing"); + assert_eq!(right_node.links().parent, Some(id)); + } + + let (left_count, left_bh) = validate_node(left, store, visited, &expected); + assert_eq!( + node.key, expected[left_count], + "expected key {}, found {}", + expected[left_count], node.key + ); + let (right_count, right_bh) = + validate_node(right, store, visited, &expected[1 + left_count..]); + + assert_eq!( + left_bh, right_bh, + "black height mismatch at node with key {}", + node.key + ); + + let self_bh = if matches!(node.links().color, Color::Black) { + left_bh + 1 + } else { + left_bh + }; + + (1 + left_count + right_count, self_bh) + } + + fn lcg(seed: &mut u64) -> u64 { + *seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1); + *seed + } + + fn shuffle(ids: &mut [usize]) { + let mut seed = 0x6b8b_4567_9a1c_def0u64; + for i in (1..ids.len()).rev() { + let j = (lcg(&mut seed) % (i as u64 + 1)) as usize; + ids.swap(i, j); + } + } + + #[test] + fn insert_validates() { + let keys: Vec = (0..200).collect(); + let mut store = NodeStore::new(&keys); + let mut tree = RbTree::new(); + let mut order: Vec = (0..keys.len()).collect(); + + shuffle(&mut order); + for id in order { + tree.insert(id, &mut store).unwrap(); + } + + validate_tree(&tree, &store, &keys); + } + + #[test] + fn min_updates_on_insert_and_remove() { + let keys = vec![10, 5, 15, 3, 7, 12, 18, 1, 6]; + let mut store = NodeStore::new(&keys); + let mut tree = RbTree::new(); + + for id in 0..keys.len() { + tree.insert(id, &mut store).unwrap(); + } + + let mut sorted_keys = keys.clone(); + sorted_keys.sort(); + + validate_tree(&tree, &store, &sorted_keys); + assert_eq!(tree_min_key(&tree, &store), Some(1)); + + // Remove index 7 (key=1) + tree.remove(7, &mut store).unwrap(); + sorted_keys.retain(|&x| x != 1); + validate_tree(&tree, &store, &sorted_keys); + assert_eq!(tree_min_key(&tree, &store), Some(3)); + + // Remove index 8 (key=6) + tree.remove(8, &mut store).unwrap(); + sorted_keys.retain(|&x| x != 6); + validate_tree(&tree, &store, &sorted_keys); + assert_eq!(tree_min_key(&tree, &store), Some(3)); + + // Remove index 3 (key=3) + tree.remove(3, &mut store).unwrap(); + sorted_keys.retain(|&x| x != 3); + validate_tree(&tree, &store, &sorted_keys); + assert_eq!(tree_min_key(&tree, &store), Some(5)); + } + + #[test] + fn remove_leaf_one_child_two_children() { + let keys = vec![10, 5, 15, 3, 7, 12, 18, 1, 6]; + let mut store = NodeStore::new(&keys); + let mut tree = RbTree::new(); + + for id in 0..keys.len() { + tree.insert(id, &mut store).unwrap(); + } + + let mut sorted_keys = keys.clone(); + sorted_keys.sort(); + validate_tree(&tree, &store, &sorted_keys); + + // Remove node at index 4 (key=7) + tree.remove(4, &mut store).unwrap(); + sorted_keys.retain(|&x| x != 7); + validate_tree(&tree, &store, &sorted_keys); + + // Remove node at index 3 (key=3) + tree.remove(3, &mut store).unwrap(); + sorted_keys.retain(|&x| x != 3); + validate_tree(&tree, &store, &sorted_keys); + + // Remove node at index 7 (key=1) + tree.remove(7, &mut store).unwrap(); + sorted_keys.retain(|&x| x != 1); + validate_tree(&tree, &store, &sorted_keys); + } + + #[test] + fn remove_root_with_two_children() { + let keys = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; + let mut store = NodeStore::new(&keys); + let mut tree = RbTree::new(); + + for id in 0..keys.len() { + tree.insert(id, &mut store).unwrap(); + } + + let mut sorted_keys: Vec = keys.to_vec(); + sorted_keys.sort(); + validate_tree(&tree, &store, &sorted_keys); + + let root_id = tree.root.expect("root missing"); + let root_key = store.get(root_id).expect("root missing").key; + + tree.remove(root_id, &mut store).unwrap(); + sorted_keys.retain(|&x| x != root_key); + validate_tree(&tree, &store, &sorted_keys); + } + + #[test] + fn remove_all_nodes() { + let keys: Vec = (0..128).collect(); + let mut store = NodeStore::new(&keys); + let mut tree = RbTree::new(); + let mut order: Vec = (0..keys.len()).collect(); + shuffle(&mut order); + + for id in &order { + tree.insert(*id, &mut store).unwrap(); + } + + let mut remaining_keys = keys.clone(); + validate_tree(&tree, &store, &remaining_keys); + + for id in order { + let removed_key = keys[id]; + tree.remove(id, &mut store).unwrap(); + remaining_keys.retain(|&k| k != removed_key); + validate_tree(&tree, &store, &remaining_keys); + } + + assert_eq!(tree.root, None); + } + + #[test] + fn interleaved_operations() { + let keys: Vec = (0..100).collect(); + let mut store = NodeStore::new(&keys); + let mut tree = RbTree::new(); + let mut order: Vec = (0..keys.len()).collect(); + shuffle(&mut order); + + // Build initial tree with 50 nodes + let mut active_keys: Vec = Vec::new(); + for id in order.iter().take(50) { + tree.insert(*id, &mut store).unwrap(); + active_keys.push(keys[*id]); + } + active_keys.sort(); + validate_tree(&tree, &store, &active_keys); + + // Alternate: remove oldest, insert new + for i in 0..50 { + let removed_key = keys[order[i]]; + tree.remove(order[i], &mut store).unwrap(); + active_keys.retain(|&k| k != removed_key); + validate_tree(&tree, &store, &active_keys); + + tree.insert(order[50 + i], &mut store).unwrap(); + active_keys.push(keys[order[50 + i]]); + active_keys.sort(); + validate_tree(&tree, &store, &active_keys); + } + } + + #[test] + fn stress_test() { + let keys: Vec = (0..500).collect(); + let mut store = NodeStore::new(&keys); + let mut tree = RbTree::new(); + let mut order: Vec = (0..keys.len()).collect(); + shuffle(&mut order); + + let mut seed = 0x6b8b_4567_9a1c_def0u64; + let mut active_nodes = Vec::new(); + let mut available_nodes = order.clone(); + + for _ in 0..10000 { + let do_insert = if active_nodes.is_empty() { + true + } else if available_nodes.is_empty() { + false + } else { + (lcg(&mut seed) % 10) < 7 + }; + + if do_insert { + let idx = (lcg(&mut seed) as usize) % available_nodes.len(); + let node_id = available_nodes.swap_remove(idx); + tree.insert(node_id, &mut store).unwrap(); + active_nodes.push(node_id); + } else { + let idx = (lcg(&mut seed) as usize) % active_nodes.len(); + let node_id = active_nodes.swap_remove(idx); + tree.remove(node_id, &mut store).unwrap(); + available_nodes.push(node_id); + } + + let mut expected_keys: Vec = active_nodes.iter().map(|&id| keys[id]).collect(); + expected_keys.sort(); + validate_tree(&tree, &store, &expected_keys); + } + + let mut expected_keys: Vec = active_nodes.iter().map(|&id| keys[id]).collect(); + expected_keys.sort(); + validate_tree(&tree, &store, &expected_keys); + } +} + +// END TESTING diff --git a/src/mem/traits.rs b/src/mem/traits.rs new file mode 100644 index 0000000..cc02d85 --- /dev/null +++ b/src/mem/traits.rs @@ -0,0 +1,24 @@ +use core::borrow::Borrow; + +pub trait Get { + type Output: ?Sized; + + fn get>(&self, index: K) -> Option<&Self::Output>; +} + +pub trait GetMut: Get { + fn get_mut>(&mut self, index: K) -> Option<&mut Self::Output>; + + // Getting multiple disjoint mutable references at once + fn get2_mut>(&mut self, index1: K, index2: K) -> (Option<&mut Self::Output>, Option<&mut Self::Output>); + fn get3_mut>(&mut self, index1: K, index2: K, index3: K) -> (Option<&mut Self::Output>, Option<&mut Self::Output>, Option<&mut Self::Output>); +} + +pub trait ToIndex { + fn to_index>(index: Option) -> usize; +} + +pub trait Project

{ + fn project(&self) -> Option<&P>; + fn project_mut(&mut self) -> Option<&mut P>; +} \ No newline at end of file diff --git a/src/mem/view.rs b/src/mem/view.rs new file mode 100644 index 0000000..c8bd4bc --- /dev/null +++ b/src/mem/view.rs @@ -0,0 +1,63 @@ +use core::borrow::Borrow; +use std::marker::PhantomData; + +use crate::mem::traits::{Get, GetMut, Project, ToIndex}; + +pub struct ViewMut<'a, K: ?Sized + ToIndex, P, S: GetMut> +where + S::Output: Project

, +{ + data: &'a mut S, + _k: PhantomData, + _proj: PhantomData

, +} + +impl<'a, K: ?Sized + ToIndex, P, S: GetMut> ViewMut<'a, K, P, S> +where + S::Output: Project

, +{ + pub fn new(data: &'a mut S) -> Self { + Self { + data, + _k: PhantomData, + _proj: PhantomData, + } + } +} + +impl<'a, K: ?Sized + ToIndex, P, S: GetMut> Get for ViewMut<'a, K, P, S> +where + S::Output: Project

, +{ + type Output = P; + + fn get>(&self, idx: Q) -> Option<&P> { + self.data.get(idx).and_then(Project::project) + } +} + +impl<'a, K: ?Sized + ToIndex, P, S: GetMut> GetMut for ViewMut<'a, K, P, S> +where + S::Output: Project

, +{ + fn get_mut>(&mut self, idx: Q) -> Option<&mut P> { + self.data.get_mut(idx).and_then(Project::project_mut) + } + + fn get2_mut>(&mut self, idx1: Q, idx2: Q) -> (Option<&mut P>, Option<&mut P>) { + let (a, b) = self.data.get2_mut(idx1, idx2); + ( + a.and_then(Project::project_mut), + b.and_then(Project::project_mut), + ) + } + + fn get3_mut>(&mut self, idx1: Q, idx2: Q, idx3: Q) -> (Option<&mut P>, Option<&mut P>, Option<&mut P>) { + let (a, b, c) = self.data.get3_mut(idx1, idx2, idx3); + ( + a.and_then(Project::project_mut), + b.and_then(Project::project_mut), + c.and_then(Project::project_mut), + ) + } +} \ No newline at end of file diff --git a/src/sched.rs b/src/sched.rs index 70ae413..72ce054 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -1,18 +1,83 @@ //! This module provides access to the scheduler. -pub mod scheduler; +pub mod rt; +//pub mod scheduler; pub mod task; pub mod thread; use hal::Schedable; -use crate::utils::KernelError; +use crate::mem::{array::IndexMap, rbtree::RbTree, view::ViewMut}; + +type ThreadMap = IndexMap; + +pub struct Scheduler { + threads: ThreadMap, + rt_scheduler: rt::Scheduler, + + wakeup: RbTree, + + last_tick: u64, +} + +impl Scheduler { + pub fn new() -> Self { + Self { + threads: IndexMap::new(), + rt_scheduler: rt::Scheduler::new(), + wakeup: RbTree::new(), + last_tick: 0, + } + } + + pub fn enqueue(&mut self, thread: thread::Thread) { + let uid = thread.uid(); + let rt = thread.rt_server().is_some(); + self.threads.insert(&thread.uid(), thread); + + if rt { + let mut view = ViewMut::>::new( + &mut self.threads, + ); + self.rt_scheduler.enqueue(uid, &mut view); + } + } + + pub fn do_sched(&mut self, now: u64, old: Option) -> Option { + let dt = now - self.last_tick; + self.last_tick = now; + + if let Some(old) = old { + let mut view = rt::ServerView::::new(&mut self.threads); + // If this is not a real-time thread, this will just do nothing. + self.rt_scheduler.put(old, dt, &mut view); + + // TODO: thread is still enqueued. Dequeue if blocked or sleeping and put to the respective tree/list. + // If it exited remove it completely. + } + + let mut view = rt::ServerView::::new(&mut self.threads); + self.rt_scheduler.pick(now, &mut view) + } + + pub fn dequeue(&mut self, uid: thread::UId) -> Option { + let mut view = rt::ServerView::::new(&mut self.threads); + // If this is not a real-time thread, this will just do nothing. + self.rt_scheduler.dequeue(uid, &mut view); + + self.threads.remove(&uid) + } +} /// Reschedule the tasks. pub fn reschedule() { hal::Machine::trigger_reschedule(); } +/* + + + /// Create a new task. /// /// `desc` - The task descriptor. @@ -54,3 +119,5 @@ pub fn tick_scheduler() -> bool { scheduler::SCHEDULER.lock().tick() } + + */ diff --git a/src/sched/rt.rs b/src/sched/rt.rs new file mode 100644 index 0000000..c6c6b00 --- /dev/null +++ b/src/sched/rt.rs @@ -0,0 +1,42 @@ +use crate::{mem::{rbtree::RbTree, traits::{Get, GetMut}, view::ViewMut}, sched::{ThreadMap, thread::{self}}}; + +pub struct Scheduler { + edf: RbTree, +} + +pub type ServerView<'a, const N: usize> = ViewMut<'a, thread::UId, thread::RtServer, ThreadMap>; + +impl Scheduler { + pub fn new() -> Self { + Self { + edf: RbTree::new(), + } + } + + pub fn enqueue(&mut self, uid: thread::UId, storage: &mut ServerView) { + self.edf.insert(uid, storage); + } + + pub fn put(&mut self, uid: thread::UId, dt: u64, storage: &mut ServerView) { + if let Some(server) = storage.get_mut(uid) { + server.consume(dt); + } + } + + pub fn pick(&mut self, now: u64, storage: &mut ServerView) -> Option { + let id = self.edf.min()?; + + if storage.get(id)?.budget() == 0 { + self.edf.remove(id, storage); + storage.get_mut(id)?.replenish(now); + self.edf.insert(id, storage); + } + + // Insert updated the min cache. + self.edf.min() + } + + pub fn dequeue(&mut self, uid: thread::UId, storage: &mut ServerView) { + self.edf.remove(uid, storage); + } +} \ No newline at end of file diff --git a/src/sched/task.rs b/src/sched/task.rs index 7f6deb8..9f5388b 100644 --- a/src/sched/task.rs +++ b/src/sched/task.rs @@ -53,6 +53,8 @@ impl From for usize { } } + + /// Descibes a task. pub struct TaskDescriptor { /// The size of the memory that the task requires. @@ -68,8 +70,6 @@ pub struct Task { memory: TaskMemory, /// The counter for the thread ids. tid_cntr: usize, - /// The threads associated with the task. - threads: mem::array::Vec, } impl Task { @@ -80,13 +80,11 @@ impl Task { /// Returns a new task if the task was created successfully, or an error if the task could not be created. pub fn new(memory_size: usize, id: TaskId) -> Result { let memory = TaskMemory::new(memory_size)?; - let threads = mem::array::Vec::new(); Ok(Self { id, memory, tid_cntr: 0, - threads, }) } @@ -187,3 +185,5 @@ impl Drop for TaskMemory { unsafe { mem::free(self.begin, self.size) }; } } + + diff --git a/src/sched/thread.rs b/src/sched/thread.rs index 647d5b9..7a814b4 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -4,18 +4,19 @@ use core::{borrow::Borrow, ffi::c_void}; use hal::Stack; use hal::stack::Stacklike; +use macros::TaggedLinks; -use crate::{mem::array::IndexMap, sched::task::TaskId, utils::KernelError}; +use crate::{mem::{rbtree::{self, Compare}, traits::{Project, ToIndex}}, sched::task::TaskId, utils::KernelError}; /// Id of a task. This is only unique within a Task. #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] -pub struct ThreadId { +pub struct Id { id: usize, owner: TaskId, } #[allow(dead_code)] -impl ThreadId { +impl Id { pub fn new(id: usize, owner: TaskId) -> Self { Self { id, owner } } @@ -28,78 +29,72 @@ impl ThreadId { self.owner } - pub fn get_uid(&self, uid: usize) -> ThreadUId { - ThreadUId { uid, tid: *self } + pub fn get_uid(&self, uid: usize) -> UId { + UId { uid, tid: *self } } } /// Unique identifier for a thread. Build from TaskId and ThreadId. #[derive(Clone, Copy, Debug)] #[allow(dead_code)] -pub struct ThreadUId { +pub struct UId { uid: usize, - tid: ThreadId, + tid: Id, } #[allow(dead_code)] -impl ThreadUId { - pub fn tid(&self) -> ThreadId { +impl UId { + pub fn tid(&self) -> Id { self.tid } } -impl PartialEq for ThreadUId { +impl PartialEq for UId { fn eq(&self, other: &Self) -> bool { self.uid == other.uid } } -impl Eq for ThreadUId {} +impl Eq for UId {} -impl Borrow for ThreadUId { - fn borrow(&self) -> &usize { - &self.uid +impl Into for UId { + fn into(self) -> usize { + self.uid } } -impl Default for ThreadUId { +impl Default for UId { fn default() -> Self { Self { uid: 0, - tid: ThreadId::new(0, TaskId::User(0)), + tid: Id::new(0, TaskId::User(0)), } } } -impl PartialOrd for ThreadUId { +impl PartialOrd for UId { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for ThreadUId { +impl Ord for UId { fn cmp(&self, other: &Self) -> core::cmp::Ordering { self.uid.cmp(&other.uid) } } +impl ToIndex for UId { + fn to_index>(idx: Option) -> usize { + idx.as_ref().map_or(0, |k| k.borrow().uid) + } +} + // ------------------------------------------------------------------------- -pub struct ThreadDescriptor { - pub tid: ThreadId, +pub struct Descriptor { + pub tid: Id, pub stack: Stack, - pub timing: Timing, -} - -/// The timing information for a thread. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Timing { - /// The period of the thread after which it should run again. - pub period: usize, - /// The deadline of the thread. - pub deadline: usize, - /// The execution time of the thread. (How much cpu time it needs) - pub exec_time: usize, } /// The state of a thread. @@ -114,22 +109,98 @@ pub enum RunState { Waits, } -#[derive(Debug)] -pub struct ThreadState { +#[derive(Debug, Clone, Copy)] +pub struct State { run_state: RunState, stack: Stack, } +#[derive(Debug, Clone, Copy)] +#[derive(TaggedLinks)] +pub struct RtServer { + budget: u64, + total_budget: u64, + + reservation: u64, + deadline: u64, + + // Back-reference to the thread uid. + uid: UId, + + /// Real-time tree links for the server. + #[rbtree(tag = RtTree, idx = UId)] + _rt_links: rbtree::Links, +} + +impl RtServer { + pub fn new(budget: u64, reservation: u64, deadline: u64, uid: UId) -> Self { + Self { + budget, + total_budget: budget, + reservation, + deadline, + uid, + _rt_links: rbtree::Links::new(), + } + } + + pub fn budget(&self) -> u64 { + self.budget + } + + pub fn replenish(&mut self, now: u64) { + let next = self.deadline + self.reservation; + self.deadline = next.max(now + self.reservation); + self.budget = self.total_budget; + } + + pub fn consume(&mut self, dt: u64) { + if self.budget >= dt { + self.budget -= dt; + } else { + self.budget = 0; + } + } + + pub fn deadline(&self) -> u64 { + self.deadline + } + + pub fn uid(&self) -> UId { + self.uid + } +} + +impl Compare for RtServer { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + let ord = self.deadline.cmp(&other.deadline); + + if ord == core::cmp::Ordering::Equal { + self.uid.cmp(&other.uid) + } else { + ord + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct WakupTree; +#[derive(Debug, Clone, Copy)] +pub struct RtTree; + /// The struct representing a thread. -#[derive(Debug)] -#[allow(dead_code)] +#[derive(Debug, Clone, Copy)] +#[derive(TaggedLinks)] pub struct Thread { /// The current state of the thread. - state: ThreadState, - /// The timing constraints of the thread. - timing: Timing, + state: State, /// The unique identifier of the thread. - tuid: ThreadUId, + uid: UId, + /// If the thread is real-time, its contains a constant bandwidth server. + rt_server: Option, + /// Wakup tree links for the thread. + #[rbtree(tag = WakupTree, idx = UId)] + _wakeup_links: rbtree::Links, } #[allow(dead_code)] @@ -137,74 +208,55 @@ impl Thread { /// Create a new thread. /// /// `stack` - The stack of the thread. - /// `timing` - The timing constraints of the thread. /// /// Returns a new thread. - fn new(tuid: ThreadUId, stack: Stack, timing: Timing) -> Self { + fn new(uid: UId, stack: Stack) -> Self { Self { - state: ThreadState { + state: State { run_state: RunState::Ready, stack, }, - timing, - tuid, + uid, + rt_server: None, + _wakeup_links: rbtree::Links::new(), } } - pub fn update_sp(&mut self, sp: *mut c_void) -> Result<(), KernelError> { - let sp = self.state.stack.create_sp(sp)?; + pub fn save_ctx(&mut self, ctx: *mut c_void) -> Result<(), KernelError> { + let sp = self.state.stack.create_sp(ctx)?; self.state.stack.set_sp(sp); Ok(()) } - pub fn update_run_state(&mut self, state: RunState) { + pub fn set_run_state(&mut self, state: RunState) { self.state.run_state = state; } - pub fn timing(&self) -> &Timing { - &self.timing + pub fn rt_server(&self) -> Option<&RtServer> { + self.rt_server.as_ref() } - pub fn sp(&self) -> *mut c_void { + pub fn ctx(&self) -> *mut c_void { self.state.stack.sp() } - pub fn tuid(&self) -> ThreadUId { - self.tuid + pub fn uid(&self) -> UId { + self.uid } } -#[derive(Debug)] -pub struct ThreadMap { - map: IndexMap, -} - -#[allow(dead_code)] -impl ThreadMap { - pub const fn new() -> Self { - Self { - map: IndexMap::new(), - } - } - - pub fn create(&mut self, desc: ThreadDescriptor) -> Result { - let idx = self.map.find_empty().ok_or(KernelError::OutOfMemory)?; - let tuid = desc.tid.get_uid(idx); - let thread = Thread::new(tuid, desc.stack, desc.timing); - - self.map.insert(&tuid, thread)?; - Ok(tuid) - } - - pub fn get_mut(&mut self, id: &ThreadUId) -> Option<&mut Thread> { - self.map.get_mut(id) +impl Compare for Thread { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.uid.cmp(&other.uid) } +} - pub fn get(&self, id: &ThreadUId) -> Option<&Thread> { - self.map.get(id) +impl Project for Thread { + fn project(&self) -> Option<&RtServer> { + self.rt_server.as_ref() } - pub fn remove(&mut self, id: &ThreadUId) -> Option { - self.map.remove(id) + fn project_mut(&mut self) -> Option<&mut RtServer> { + self.rt_server.as_mut() } -} +} \ No newline at end of file diff --git a/src/syscalls/tasks.rs b/src/syscalls/tasks.rs index 7bd7b5a..2a6d68d 100644 --- a/src/syscalls/tasks.rs +++ b/src/syscalls/tasks.rs @@ -2,6 +2,7 @@ use core::ffi::c_int; +/* use crate::sched; use macros::syscall_handler; @@ -29,4 +30,4 @@ fn syscall_exec(entry: usize) -> c_int { .and_then(|task| sched::create_thread(task, entry, None, timing)) .map(|_| 0) .unwrap_or(-1) -} +}*/ diff --git a/src/utils.rs b/src/utils.rs index 8d4144f..9fa7644 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,6 +2,8 @@ #![cfg_attr(feature = "nightly", feature(likely_unlikely))] use core::fmt::Debug; +use core::ptr::NonNull; +use core::mem::offset_of; /// These two definitions are copied from https://github.com/rust-lang/hashbrown #[cfg(not(feature = "nightly"))] @@ -11,6 +13,8 @@ pub(crate) use core::convert::{identity as likely, identity as unlikely}; #[cfg(feature = "nightly")] pub(crate) use core::hint::{likely, unlikely}; + + /// This is a macro that is used to panic when a bug is detected. /// It is similar to the BUG() macro in the Linux kernel. Link: [https://www.kernel.org/]() #[macro_export] From 4c4f8656b152eb43df2a8ed525437545e63e08f1 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Sat, 7 Mar 2026 15:23:32 +0000 Subject: [PATCH 02/42] remove me --- .cargo/config.toml | 4 +- Cargo.lock | 1 + Cargo.toml | 2 + machine/api/src/lib.rs | 2 +- machine/api/src/mem.rs | 48 +++++++ machine/api/src/{ => mem}/stack.rs | 7 +- machine/arm/src/sched.rs | 3 + macros/src/tree.rs | 6 +- src/dispatch.rs | 14 ++ src/{sched => dispatch}/task.rs | 101 +++++-------- src/lib.rs | 4 +- src/mem.rs | 12 +- src/mem/pfa.rs | 57 ++++++++ src/mem/pfa/bitset.rs | 219 +++++++++++++++++++++++++++++ src/mem/vmm.rs | 66 +++++++++ src/mem/vmm/nommu.rs | 87 ++++++++++++ src/sched.rs | 6 +- src/sched/rt.rs | 2 +- src/sched/thread.rs | 2 +- src/types.rs | 8 ++ src/{mem => types}/array.rs | 27 ++-- src/{mem => types}/boxed.rs | 46 +----- src/{mem => types}/heap.rs | 0 src/{mem => types}/pool.rs | 0 src/{mem => types}/queue.rs | 0 src/{mem => types}/rbtree.rs | 6 +- src/{mem => types}/traits.rs | 0 src/{mem => types}/view.rs | 4 +- src/utils.rs | 2 + 29 files changed, 591 insertions(+), 145 deletions(-) create mode 100644 machine/api/src/mem.rs rename machine/api/src/{ => mem}/stack.rs (94%) create mode 100644 src/dispatch.rs rename src/{sched => dispatch}/task.rs (64%) create mode 100644 src/mem/pfa.rs create mode 100644 src/mem/pfa/bitset.rs create mode 100644 src/mem/vmm.rs create mode 100644 src/mem/vmm/nommu.rs create mode 100644 src/types.rs rename src/{mem => types}/array.rs (97%) rename src/{mem => types}/boxed.rs (85%) rename src/{mem => types}/heap.rs (100%) rename src/{mem => types}/pool.rs (100%) rename src/{mem => types}/queue.rs (100%) rename src/{mem => types}/rbtree.rs (99%) rename src/{mem => types}/traits.rs (100%) rename src/{mem => types}/view.rs (94%) diff --git a/.cargo/config.toml b/.cargo/config.toml index b73ed2c..0a1e750 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -7,10 +7,10 @@ xtask = "--config xtasks/.cargo/config.toml run -p xtask --release --" [build] target = "host-tuple" -[target] - [target.'cfg(target_os = "none")'] rustflags = ["-C", "link-arg=--entry=main",] +[target] + [target.thumbv7em-none-eabi] rustflags = ["-C", "relocation-model=ropi-rwpi"] diff --git a/Cargo.lock b/Cargo.lock index 713355d..bc2decf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1072,6 +1072,7 @@ name = "osiris" version = "0.1.0" dependencies = [ "bindgen 0.69.5", + "bitflags", "cbindgen", "cfg_aliases", "envparse", diff --git a/Cargo.toml b/Cargo.toml index 0d02d35..61afd64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,10 +22,12 @@ hal = { package = "hal-select", path = "machine/select" } macros = { path = "macros" } interface = { path = "interface" } envparse = "0.1.0" +bitflags = "2.10.0" [dev-dependencies] # This is a host-compatible HAL which will be used for running tests and verification on the host. hal-testing = { path = "machine/testing", features = [] } +rand = "0.8.5" [target.'cfg(kani_ra)'.dependencies] kani = { git = "https://github.com/model-checking/kani" } diff --git a/machine/api/src/lib.rs b/machine/api/src/lib.rs index a6330a6..36860bc 100644 --- a/machine/api/src/lib.rs +++ b/machine/api/src/lib.rs @@ -2,7 +2,7 @@ use core::{fmt::Display, ops::Range}; -pub mod stack; +pub mod mem; #[derive(Default, Debug, PartialEq, Eq, Clone)] pub enum Error { diff --git a/machine/api/src/mem.rs b/machine/api/src/mem.rs new file mode 100644 index 0000000..10c8b07 --- /dev/null +++ b/machine/api/src/mem.rs @@ -0,0 +1,48 @@ + +pub mod stack; + +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] +pub struct PhysAddr(usize); + +impl PhysAddr { + #[inline] + pub fn new(addr: usize) -> Self { + Self(addr) + } + + #[inline] + pub fn as_usize(&self) -> usize { + self.0 + } +} + +impl From for usize { + #[inline] + fn from(addr: PhysAddr) -> Self { + addr.0 + } +} + +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] +pub struct VirtAddr(usize); + +impl VirtAddr { + #[inline] + pub fn new(addr: usize) -> Self { + Self(addr) + } + + #[inline] + pub fn as_usize(&self) -> usize { + self.0 + } +} + +impl From for usize { + #[inline] + fn from(addr: VirtAddr) -> Self { + addr.0 + } +} \ No newline at end of file diff --git a/machine/api/src/stack.rs b/machine/api/src/mem/stack.rs similarity index 94% rename from machine/api/src/stack.rs rename to machine/api/src/mem/stack.rs index db226a0..4291378 100644 --- a/machine/api/src/stack.rs +++ b/machine/api/src/mem/stack.rs @@ -1,9 +1,8 @@ -use core::{ffi::c_void, num::NonZero, ptr::NonNull}; - -use crate::Result; +use core::{ffi::c_void, num::NonZero}; +use crate::{Result, mem::PhysAddr}; pub struct StackDescriptor { - pub top: NonNull, + pub top: PhysAddr, pub size: NonZero, pub entry: extern "C" fn(), pub fin: Option !>, diff --git a/machine/arm/src/sched.rs b/machine/arm/src/sched.rs index 3026071..19578fa 100644 --- a/machine/arm/src/sched.rs +++ b/machine/arm/src/sched.rs @@ -202,6 +202,9 @@ impl hal_api::stack::Stacklike for ArmStack { fin, } = desc; + // We expect a PhysAddr, which can be converted to a ptr on nommu. + let top = NonNull::new(top as *mut u32).ok_or(hal_api::Error::InvalidAddress)?; + let mut stack = Self { top, sp: StackPtr { offset: 0 }, diff --git a/macros/src/tree.rs b/macros/src/tree.rs index f6c3b39..d62d8f6 100644 --- a/macros/src/tree.rs +++ b/macros/src/tree.rs @@ -42,13 +42,13 @@ fn impl_rbtree(input: &DeriveInput, fields: &syn::punctuated::Punctuated for #struct_ident #ty_generics #where_clause { + impl #impl_generics crate::types::rbtree::Linkable<#tag_path, #idx_path> for #struct_ident #ty_generics #where_clause { #[inline] - fn links(&self) -> &crate::mem::rbtree::Links<#tag_path, #idx_path> { + fn links(&self) -> &crate::types::rbtree::Links<#tag_path, #idx_path> { &self.#field_ident } #[inline] - fn links_mut(&mut self) -> &mut crate::mem::rbtree::Links<#tag_path, #idx_path> { + fn links_mut(&mut self) -> &mut crate::types::rbtree::Links<#tag_path, #idx_path> { &mut self.#field_ident } } diff --git a/src/dispatch.rs b/src/dispatch.rs new file mode 100644 index 0000000..a985fef --- /dev/null +++ b/src/dispatch.rs @@ -0,0 +1,14 @@ +//! This is the owner of all Tasks. It takes care of context switching between them. +//! The idea is that the Schedulers selects one of its threads to run, and then the Dipatcher takes care of context-switching to the associated Task. (e.g. setting up the address space) +//! If the thread is part of the same task as the currently running one, the Dispatcher does effectively nothing. +//! +//! + +mod task; + +use crate::types::array::IndexMap; + +/* +struct Dispatcher { + tasks: IndexMap, +}*/ \ No newline at end of file diff --git a/src/sched/task.rs b/src/dispatch/task.rs similarity index 64% rename from src/sched/task.rs rename to src/dispatch/task.rs index 9f5388b..b3f3d2c 100644 --- a/src/sched/task.rs +++ b/src/dispatch/task.rs @@ -2,74 +2,43 @@ use core::num::NonZero; use core::ops::Range; use core::ptr::NonNull; +use std::borrow::Borrow; -use hal::Stack; +use hal::{Stack, stack}; use hal::stack::Stacklike; -use crate::mem; +use crate::{mem, sched}; use crate::mem::alloc::{Allocator, BestFitAllocator}; -use crate::sched::thread::{ThreadDescriptor, ThreadId, Timing}; +use crate::mem::vmm::{AddressSpace, AddressSpacelike, Region}; +use crate::types::traits::ToIndex; use crate::utils::KernelError; /// Id of a task. This is unique across all tasks. -#[repr(u16)] #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] -pub enum TaskId { - // Task with normal user privileges in user mode. - User(usize), - // Task with kernel privileges in user mode. - Kernel(usize), +pub struct UId { + uid: usize, } -#[allow(dead_code)] -impl TaskId { - /// Check if the task is a user task. - pub fn is_user(&self) -> bool { - matches!(self, TaskId::User(_)) - } - - /// Check if the task is a kernel task. - pub fn is_kernel(&self) -> bool { - matches!(self, TaskId::Kernel(_)) - } - - pub fn new_user(id: usize) -> Self { - TaskId::User(id) - } - - pub fn new_kernel(id: usize) -> Self { - TaskId::Kernel(id) +impl ToIndex for UId { + fn to_index>(idx: Option) -> usize { + idx.as_ref().map_or(0, |uid| uid.borrow().uid) } } -impl From for usize { - fn from(val: TaskId) -> Self { - match val { - TaskId::User(id) => id, - TaskId::Kernel(id) => id, - } - } -} - - - -/// Descibes a task. -pub struct TaskDescriptor { - /// The size of the memory that the task requires. - pub mem_size: usize, +pub struct Attributes { + reserved: Option>, } /// The struct representing a task. -#[derive(Debug)] pub struct Task { /// The unique identifier of the task. - pub id: TaskId, - /// The memory of the task. - memory: TaskMemory, + pub id: UId, /// The counter for the thread ids. tid_cntr: usize, + /// Sets up the memory for the task. + address_space: mem::vmm::AddressSpace, } impl Task { @@ -78,45 +47,51 @@ impl Task { /// `memory_size` - The size of the memory that the task requires. /// /// Returns a new task if the task was created successfully, or an error if the task could not be created. - pub fn new(memory_size: usize, id: TaskId) -> Result { - let memory = TaskMemory::new(memory_size)?; - + pub fn new(id: UId, attrs: &Attributes) -> Result { Ok(Self { id, - memory, + address_space: AddressSpace::new(), tid_cntr: 0, }) } - fn allocate_tid(&mut self) -> ThreadId { + fn allocate_tid(&mut self) -> sched::thread::Id { let tid = self.tid_cntr; self.tid_cntr += 1; - ThreadId::new(tid, self.id) + sched::thread::Id::new(tid, self.id) + } + + pub fn allocate(&mut self, size: usize, align: usize) -> Result { + self.address_space.map(size, align) } pub fn create_thread( &mut self, entry: extern "C" fn(), fin: Option !>, - timing: Timing, ) -> Result { - // Safe unwrap because stack size is non zero. - // TODO: Make this configurable - let stack_size = NonZero::new(4096usize).unwrap(); - // TODO: Revert if error occurs - let stack_mem = self.memory.malloc(stack_size.into(), align_of::())?; - let stack_top = unsafe { stack_mem.byte_add(stack_size.get()) }; + + // Create the stack for the thread. + let size = 1 * mem::pfa::PAGE_SIZE; // TODO: Make this configurable + let start = self.address_space.end() - size; + let region = mem::vmm::Region::new( + start, + size, + mem::vmm::Backing::Uninit, + mem::vmm::Perms::Read | mem::vmm::Perms::Write, + ); + let stack_pa = self.address_space.map(region)?; let stack = hal::stack::StackDescriptor { - top: stack_top, - size: stack_size, + top: stack_pa, + // Safe unwrap because stack size is non zero. + size: NonZero::new(size).unwrap(), entry, fin, }; let stack = unsafe { Stack::new(stack) }?; - let tid = self.allocate_tid(); // TODO: Revert if error occurs @@ -185,5 +160,3 @@ impl Drop for TaskMemory { unsafe { mem::free(self.begin, self.size) }; } } - - diff --git a/src/lib.rs b/src/lib.rs index b41219c..eeb2000 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,10 @@ mod macros; mod utils; mod faults; mod mem; +mod types; pub mod print; -pub mod sched; +//pub mod sched; +mod dispatch; pub mod sync; pub mod syscalls; //pub mod time; diff --git a/src/mem.rs b/src/mem.rs index ad27833..ff9973a 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -6,14 +6,8 @@ use alloc::Allocator; use core::ptr::NonNull; pub mod alloc; -pub mod array; -pub mod boxed; -pub mod heap; -pub mod pool; -pub mod queue; -pub mod rbtree; -pub mod traits; -pub mod view; +pub mod vmm; +pub mod pfa; /// The possible types of memory. Which is compatible with the multiboot2 memory map. /// Link: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html @@ -42,6 +36,8 @@ static GLOBAL_ALLOCATOR: SpinLocked = /// /// Returns an error if the memory allocator could not be initialized. pub fn init_memory(boot_info: &BootInfo) -> Result<(), utils::KernelError> { + pfa::init_pfa(0x20000000)?; // TODO: Get this from the DeviceTree. + let mut allocator = GLOBAL_ALLOCATOR.lock(); for entry in boot_info.mmap.iter().take(boot_info.mmap_len as usize) { diff --git a/src/mem/pfa.rs b/src/mem/pfa.rs new file mode 100644 index 0000000..9c24df8 --- /dev/null +++ b/src/mem/pfa.rs @@ -0,0 +1,57 @@ +// The top level page frame allocator. + +use crate::sync::spinlock::SpinLocked; +use crate::types::boxed::Box; +use crate::utils::KernelError; + +use interface::PhysAddr; +use core::pin::Pin; + +mod bitset; + +/// Page size constant (typically 4KB) +pub const PAGE_SIZE: usize = 4096; + +const PAGE_CNT: usize = 1024; // TODO: This should be determined by the DeviceTree. + +type AllocatorType = bitset::Allocator; + +static PFA: SpinLocked>>> = SpinLocked::new(None); + +/// This trait abstracts over different page frame allocator implementations. +trait Allocator { + /// Returns an initializer function that can be used to create an instance of the allocator. + /// The initializer function takes a physical address and the amount of pages needed. + /// + /// Safety: + /// + /// - The returned function must only be called with a useable and valid physical address. + fn initializer() -> unsafe fn(PhysAddr, usize) -> Result>, KernelError>; + + fn alloc(&mut self, page_count: usize) -> Option; + fn free(&mut self, addr: PhysAddr, page_count: usize); +} + +pub fn init_pfa(addr: PhysAddr) -> Result<(), KernelError> { + let mut pfa = PFA.lock(); + if pfa.is_some() { + return Err(KernelError::CustomError("Page frame allocator is already initialized")); + } + + let initializer = AllocatorType::initializer(); + *pfa = Some(unsafe { initializer(addr, PAGE_CNT)? }); + + Ok(()) +} + +pub fn alloc_page(page_count: usize) -> Option { + let mut pfa = PFA.lock(); + pfa.as_mut()?.alloc(page_count) +} + +pub fn free_page(addr: PhysAddr, page_count: usize) { + let mut pfa = PFA.lock(); + if let Some(pfa) = pfa.as_mut() { + pfa.free(addr, page_count); + } +} \ No newline at end of file diff --git a/src/mem/pfa/bitset.rs b/src/mem/pfa/bitset.rs new file mode 100644 index 0000000..d9ab9e3 --- /dev/null +++ b/src/mem/pfa/bitset.rs @@ -0,0 +1,219 @@ +use core::pin::Pin; +use core::ptr::NonNull; + +use crate::{ + types::boxed::{self, Box}, + utils::KernelError, +}; + +use interface::PhysAddr; + +pub struct Allocator { + begin: PhysAddr, + l1: [usize; N], +} + +impl Allocator { + const BITS_PER_WORD: usize = usize::BITS as usize; + + pub fn new(begin: PhysAddr) -> Option { + if !begin.is_multiple_of(super::PAGE_SIZE) { + return None; + } + + if begin > PhysAddr::MAX - (N * super::PAGE_SIZE * usize::BITS as usize) { + return None; + } + + Some(Self { + begin, + l1: [!0; N], // All bits are set to 1, meaning all pages are free. + }) + } +} + +impl super::Allocator for Allocator { + fn initializer() -> unsafe fn(PhysAddr, usize) -> Result>, KernelError> { + |addr: PhysAddr, pcnt: usize| -> Result>, KernelError> { + if pcnt > N { + todo!("Runtime page frame allocator for more than {} pages", N) + } + + if !addr.is_multiple_of(core::mem::align_of::()) { + return Err(KernelError::InvalidAlign); + } + + let ptr = NonNull::new(addr as *mut Self).ok_or(KernelError::InvalidAddress)?; + + // Safety: Ptr is properly aligned and non-null. The validity of the memory at that address is valid by the call contract. + Ok(Pin::new(unsafe { boxed::Box::from_raw(ptr) })) + } + } + + fn alloc(&mut self, page_count: usize) -> Option { + // If a bit is 1 the page is free. If a bit is 0 the page is allocated. + let mut start = 0; + let mut len = 0usize; + + let rem = page_count.saturating_sub(Self::BITS_PER_WORD); + let mask = (!0usize).unbounded_shl((Self::BITS_PER_WORD.saturating_sub(page_count)) as u32); + + for idx in 0..N { + if self.l1[idx] == 0 { + len = 0; + continue; + } + + let mut byte = self.l1[idx]; + + let mut shift = if len > 0 { + 0usize + } else { + byte.leading_zeros() as usize + }; + + byte <<= shift; + + while shift < Self::BITS_PER_WORD { + // Make the mask smaller if we already have some contiguous bits. + let mask = if rem.saturating_sub(len) == 0 { + mask << (len - rem) + } else { + mask + }; + + // We shifted byte to MSB, mask is already aligned to the left. + // We compare them via and and shift to the right to shift out extra bits from the mask that would overflow into the next word. + let mut found = (byte & mask) >> shift; + + // We also need to shift the mask to the right so that we can compare mask and found. + if found == (mask >> shift) { + if len == 0 { + start = idx * Self::BITS_PER_WORD + shift; + } + + // Shift completely to the right. + found >>= found.trailing_zeros(); + + // As all found bits are now on the right we can just count them to get the amount we found. + len += found.trailing_ones() as usize; + // Continue to the next word if we haven't found enough bits yet. + break; + } else { + len = 0; + } + + shift += 1; + byte <<= 1; + } + + if len >= page_count { + // Mark the allocated pages as used. + let mut idx = start / Self::BITS_PER_WORD; + + // Mark all bits in the first word as used. + { + let skip = start % Self::BITS_PER_WORD; + let rem = len.min(Self::BITS_PER_WORD) - skip; + + self.l1[idx] &= !((!0usize).unbounded_shl((Self::BITS_PER_WORD - rem) as u32) >> skip); + + if len <= rem { + return Some(start); + } + + len -= rem; + idx += 1; + } + + // Mark all bits in the middle words as used. + { + let mid_cnt = len / Self::BITS_PER_WORD; + + for i in 0..mid_cnt { + self.l1[idx + i] = 0; + } + + idx += mid_cnt; + } + + // Mark the remaining bits in the last word as used. + self.l1[idx] &= !((!0usize).unbounded_shl((Self::BITS_PER_WORD - (len % Self::BITS_PER_WORD)) as u32)); + return Some(self.begin + (start * super::PAGE_SIZE)); + } + } + + None + } + + fn free(&mut self, addr: PhysAddr, page_count: usize) { + if !addr.is_multiple_of(super::PAGE_SIZE) { + panic!("Address must be page aligned"); + } + + let mut idx = (addr - self.begin) / super::PAGE_SIZE / Self::BITS_PER_WORD; + let mut bit_idx = ((addr - self.begin) / super::PAGE_SIZE) % Self::BITS_PER_WORD; + + // TODO: slow + for _ in 0..page_count { + self.l1[idx] |= 1 << (Self::BITS_PER_WORD - 1 - bit_idx); + + bit_idx += 1; + + if bit_idx == Self::BITS_PER_WORD { + bit_idx = 0; + idx += 1; + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_random_pattern() { + const ITARATIONS: usize = 1000; + + for i in 0..ITARATIONS { + const N: usize = 1024; + const BITS: usize = Allocator::::BITS_PER_WORD; + const ALLOC_SIZE: usize = 100; + + let mut allocator = Allocator::::new(0x0).unwrap(); + + // Generate a random bit pattern. + for i in 0..N { + let is_zero = rand::random::(); + + if is_zero { + allocator.l1[i / BITS] &= !(1 << ((BITS - 1) - (i % BITS))); + } + } + + // Place a run of ALLOC_SIZE contiguous bits set to 1 at a random position. + let start = rand::random::() % (N - ALLOC_SIZE); + for i in start..(start + ALLOC_SIZE) { + allocator.l1[i / BITS] |= 1 << ((BITS - 1) - (i % BITS)); + } + + let pre = allocator.l1.clone(); + + let addr = super::super::Allocator::alloc(&mut allocator, ALLOC_SIZE).unwrap(); + let idx = addr / super::super::PAGE_SIZE; + + // Check that the bits in returned addresses is all ones in pre. + for i in 0..ALLOC_SIZE { + let bit = (pre[(idx + i) / BITS] >> ((BITS - 1) - ((idx + i) % BITS))) & 1; + assert_eq!(bit, 1, "Bit at index {} is not set", idx + i); + } + + // Check that the bits in returned addresses is all zeros in allocator.l1. + for i in 0..ALLOC_SIZE { + let bit = (allocator.l1[(idx + i) / BITS] >> ((BITS - 1) - ((idx + i) % BITS))) & 1; + assert_eq!(bit, 0, "Bit at index {} is not cleared", idx + i); + } + } + } +} \ No newline at end of file diff --git a/src/mem/vmm.rs b/src/mem/vmm.rs new file mode 100644 index 0000000..7565658 --- /dev/null +++ b/src/mem/vmm.rs @@ -0,0 +1,66 @@ +use core::ops::Range; + +use crate::{utils::KernelError}; + +use interface::{PhysAddr, VirtAddr}; + +mod nommu; + +pub type AddressSpace = nommu::AddressSpace; + +bitflags::bitflags! { + #[derive(Clone, Copy)] + pub struct Perms: u8 { + const Read = 0b0001; + const Write = 0b0010; + const Exec = 0b0100; + } +} + +#[derive(Clone)] +pub enum Backing { + Zeroed, + Uninit, + Anon(PhysAddr), +} + +#[derive(Clone)] +pub struct Region { + range: Range, + backing: Backing, + perms: Perms, +} + +impl Region { + pub fn new(start: VirtAddr, len: usize, backing: Backing, perms: Perms) -> Self { + Self { + range: start..start.saturating_add(len), + backing, + perms, + } + } + + pub fn start(&self) -> VirtAddr { + self.range.start + } + + pub fn len(&self) -> usize { + self.range.end.saturating_sub(self.range.start) + } + + pub fn contains(&self, addr: VirtAddr) -> bool { + self.range.contains(&addr) + } +} + +pub trait AddressSpacelike { + // Size is the amount of pages in the address space. On nommu systems this will be reserved. + fn new(pages: usize) -> Result where Self: Sized; + fn map(&mut self, region: Region) -> Result; + fn unmap(&mut self, region: &Region) -> Result<(), KernelError>; + fn protect(&mut self, region: &Region, perms: Perms) -> Result<(), KernelError>; + fn virt_to_phys(&self, addr: VirtAddr) -> Option; + fn phys_to_virt(&self, addr: PhysAddr) -> Option; + fn end(&self) -> VirtAddr; + fn activate(&self) -> Result<(), KernelError>; +} \ No newline at end of file diff --git a/src/mem/vmm/nommu.rs b/src/mem/vmm/nommu.rs new file mode 100644 index 0000000..69603cd --- /dev/null +++ b/src/mem/vmm/nommu.rs @@ -0,0 +1,87 @@ +use core::ptr::copy_nonoverlapping; +use std::num::NonZero; + +use crate::{ + mem::{ + pfa, vmm, + }, + utils::KernelError, +}; + +use interface::{PhysAddr, VirtAddr}; + +pub struct AddressSpace { + begin: VirtAddr, + size: usize, +} + +impl vmm::AddressSpacelike for AddressSpace { + fn new(size: usize) -> Result { + let pg_cnt = size.div_ceil(pfa::PAGE_SIZE); + let begin = pfa::alloc_page(pg_cnt).ok_or(KernelError::OutOfMemory)?; + + Ok(Self { + begin, + size: pg_cnt * pfa::PAGE_SIZE, + }) + } + + fn map(&mut self, region: vmm::Region) -> Result { + if region.start() + region.len() > self.size { + return Err(KernelError::OutOfMemory); + } + + if let Some(test) = NonZero::new(region.start()) { + test. + } + + + match region.backing { + vmm::Backing::Anon(phys) => { + unsafe { + copy_nonoverlapping( + phys as *const u8, + (self.begin + region.start()) as *mut u8, + region.len(), + ) + }; + Ok(self.begin + region.start()) + }, + vmm::Backing::Zeroed => { + unsafe { + core::ptr::write_bytes( + (self.begin + region.start()) as *mut u8, + 0, + region.len(), + ) + }; + Ok(self.begin + region.start()) + }, + vmm::Backing::Uninit => Ok(self.begin + region.start()), + } + } + + fn unmap(&mut self, _region: &vmm::Region) -> Result<(), KernelError> { + Ok(()) + } + + fn protect(&mut self, _region: &vmm::Region, _perms: vmm::Perms) -> Result<(), KernelError> { + Ok(()) + } + + fn phys_to_virt(&self, addr: PhysAddr) -> Option { + addr.checked_sub(self.begin) + } + + fn virt_to_phys(&self, addr: VirtAddr) -> Option { + self.begin.checked_add(addr) + } + + fn end(&self) -> VirtAddr { + self.size + } + + fn activate(&self) -> Result<(), KernelError> { + Ok(()) + } +} diff --git a/src/sched.rs b/src/sched.rs index 72ce054..9edca37 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -1,13 +1,11 @@ //! This module provides access to the scheduler. pub mod rt; -//pub mod scheduler; -pub mod task; -pub mod thread; +//pub mod thread; use hal::Schedable; -use crate::mem::{array::IndexMap, rbtree::RbTree, view::ViewMut}; +use crate::types::{array::IndexMap, rbtree::RbTree, view::ViewMut}; type ThreadMap = IndexMap; diff --git a/src/sched/rt.rs b/src/sched/rt.rs index c6c6b00..95dfb62 100644 --- a/src/sched/rt.rs +++ b/src/sched/rt.rs @@ -1,4 +1,4 @@ -use crate::{mem::{rbtree::RbTree, traits::{Get, GetMut}, view::ViewMut}, sched::{ThreadMap, thread::{self}}}; +use crate::{types::{rbtree::RbTree, traits::{Get, GetMut}, view::ViewMut}, sched::{ThreadMap, thread::{self}}}; pub struct Scheduler { edf: RbTree, diff --git a/src/sched/thread.rs b/src/sched/thread.rs index 7a814b4..f988b97 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -6,7 +6,7 @@ use hal::Stack; use hal::stack::Stacklike; use macros::TaggedLinks; -use crate::{mem::{rbtree::{self, Compare}, traits::{Project, ToIndex}}, sched::task::TaskId, utils::KernelError}; +use crate::{types::{rbtree::{self, Compare}, traits::{Project, ToIndex}}, sched::task::TaskId, utils::KernelError}; /// Id of a task. This is only unique within a Task. #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..26df432 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,8 @@ + +pub mod boxed; +pub mod array; +pub mod heap; +pub mod pool; +pub mod rbtree; +pub mod traits; +pub mod view; \ No newline at end of file diff --git a/src/mem/array.rs b/src/types/array.rs similarity index 97% rename from src/mem/array.rs rename to src/types/array.rs index 4e40954..3e50514 100644 --- a/src/mem/array.rs +++ b/src/types/array.rs @@ -1,12 +1,14 @@ //! This module implements static and dynamic arrays for in-kernel use. -use super::boxed::Box; -use crate::{ - mem::traits::{Get, GetMut, ToIndex}, - utils::KernelError, +use super::{ + traits::{Get, GetMut, ToIndex}, + boxed::Box, }; + +use crate::utils::KernelError; + use core::{borrow::Borrow, mem::MaybeUninit}; -use std::{ +use core::{ ops::{Index, IndexMut}, }; @@ -171,12 +173,17 @@ impl GetMut for IndexMap { return (None, None); } - let ptr1 = &mut self.data[index1] as *mut Option; - let ptr2 = &mut self.data[index2] as *mut Option; + let (left, right) = self.data.split_at_mut(index1.max(index2)); - // Safety: the elements at index1 and index2 are nowhere else borrowed mutably by function contract. - // And they are disjoint because of the check above. - unsafe { ((*ptr1).as_mut(), (*ptr2).as_mut()) } + if index1 < index2 { + let elem1 = left[index1].as_mut(); + let elem2 = right[0].as_mut(); + (elem1, elem2) + } else { + let elem1 = right[0].as_mut(); + let elem2 = left[index2].as_mut(); + (elem1, elem2) + } } fn get3_mut>( diff --git a/src/mem/boxed.rs b/src/types/boxed.rs similarity index 85% rename from src/mem/boxed.rs rename to src/types/boxed.rs index c2af5d7..3e2277a 100644 --- a/src/mem/boxed.rs +++ b/src/types/boxed.rs @@ -1,6 +1,6 @@ //! This module provides a simple heap-allocated memory block for in-kernel use. -use super::{free, malloc}; +use crate::mem; use crate::utils::KernelError; use core::{ mem::{MaybeUninit, forget}, @@ -28,7 +28,7 @@ impl Box<[T]> { return Ok(Self::new_slice_empty()); } - if let Some(ptr) = malloc(size_of::() * len, align_of::()) { + if let Some(ptr) = mem::malloc(size_of::() * len, align_of::()) { let ptr = slice_from_raw_parts_mut(ptr.as_ptr().cast(), len); Ok(Self { ptr: unsafe { NonNull::new_unchecked(ptr) }, @@ -54,7 +54,7 @@ impl Box<[T]> { /// /// Returns a new heap-allocated slice with the given length or an error if the allocation failed. pub fn new_slice_uninit(len: usize) -> Result]>, KernelError> { - if let Some(ptr) = malloc( + if let Some(ptr) = mem::malloc( size_of::>() * len, align_of::>(), ) { @@ -76,7 +76,7 @@ impl Box { /// /// Returns a new heap-allocated value or `None` if the allocation failed. pub fn new(value: T) -> Option { - if let Some(ptr) = malloc(size_of::(), align_of::()) { + if let Some(ptr) = mem::malloc(size_of::(), align_of::()) { unsafe { write(ptr.as_ptr().cast(), value); } @@ -139,7 +139,7 @@ impl Drop for Box { } drop_in_place(self.ptr.as_ptr()); - free(self.ptr.cast(), size); + mem::free(self.ptr.cast(), size); } } } @@ -239,39 +239,3 @@ impl AsMut for Box { self.as_mut() } } - -#[cfg(kani)] -mod verification { - use crate::mem::alloc; - - use super::*; - - /* - fn alloc_range(length: usize) -> Option> { - let alloc_range = std::alloc::Layout::from_size_align(length, align_of::()).unwrap(); - let ptr = unsafe { std::alloc::alloc(alloc_range) }; - - if ptr.is_null() || ((ptr as usize) >= isize::MAX as usize - length) { - None - } else { - Some(ptr as usize..ptr as usize + length) - } - } - - #[kani::proof] - fn proof_new_slice_zero() { - let mut allocator = alloc::BestFitAllocator::new(); - allocator - - let len = kani::any(); - kani::assume(len < alloc::MAX_ADDR); - - let b = Box::::new_slice_zeroed(len); - - let index = kani::any(); - kani::assume(index < len); - - assert!(b[index] == 0); - } - */ -} diff --git a/src/mem/heap.rs b/src/types/heap.rs similarity index 100% rename from src/mem/heap.rs rename to src/types/heap.rs diff --git a/src/mem/pool.rs b/src/types/pool.rs similarity index 100% rename from src/mem/pool.rs rename to src/types/pool.rs diff --git a/src/mem/queue.rs b/src/types/queue.rs similarity index 100% rename from src/mem/queue.rs rename to src/types/queue.rs diff --git a/src/mem/rbtree.rs b/src/types/rbtree.rs similarity index 99% rename from src/mem/rbtree.rs rename to src/types/rbtree.rs index 2f7a5f0..ac64a17 100644 --- a/src/mem/rbtree.rs +++ b/src/types/rbtree.rs @@ -1,6 +1,6 @@ -use std::{marker::PhantomData}; +use core::{marker::PhantomData}; -use crate::mem::traits::{Get, GetMut}; +use super::traits::{Get, GetMut}; #[allow(dead_code)] pub struct RbTree { @@ -652,7 +652,7 @@ impl RbTree #[cfg(test)] mod tests { use super::*; - use crate::mem::traits::{Get, GetMut}; + use super::{Get, GetMut}; use std::borrow::Borrow; use std::collections::HashSet; diff --git a/src/mem/traits.rs b/src/types/traits.rs similarity index 100% rename from src/mem/traits.rs rename to src/types/traits.rs diff --git a/src/mem/view.rs b/src/types/view.rs similarity index 94% rename from src/mem/view.rs rename to src/types/view.rs index c8bd4bc..c07de3a 100644 --- a/src/mem/view.rs +++ b/src/types/view.rs @@ -1,7 +1,7 @@ use core::borrow::Borrow; -use std::marker::PhantomData; +use core::marker::PhantomData; -use crate::mem::traits::{Get, GetMut, Project, ToIndex}; +use super::traits::{Get, GetMut, Project, ToIndex}; pub struct ViewMut<'a, K: ?Sized + ToIndex, P, S: GetMut> where diff --git a/src/utils.rs b/src/utils.rs index 9fa7644..c77239a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -58,6 +58,7 @@ pub enum KernelError { InvalidAddress, InvalidArgument, HalError(hal::Error), + CustomError(&'static str), } /// Debug msg implementation for KernelError. @@ -70,6 +71,7 @@ impl Debug for KernelError { KernelError::InvalidAddress => write!(f, "Invalid address"), KernelError::InvalidArgument => write!(f, "Invalid argument"), KernelError::HalError(e) => write!(f, "{e} (in HAL)"), + KernelError::CustomError(msg) => write!(f, "{}", msg), } } } From 36927b9de0a7d3f52cb54eb0a2b8dce03b58879e Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Wed, 25 Mar 2026 19:08:03 +0000 Subject: [PATCH 03/42] more scheduler rewrite --- machine/api/src/lib.rs | 5 +- machine/api/src/mem.rs | 67 +++++++++++- machine/api/src/{mem => }/stack.rs | 11 +- machine/arm/src/sched.rs | 8 +- machine/testing/src/sched.rs | 4 +- options.toml | 6 ++ src/dispatch.rs | 14 --- src/dispatch/task.rs | 162 ---------------------------- src/idle.rs | 17 +++ src/lib.rs | 17 +-- src/mem.rs | 37 ++++--- src/mem/pfa.rs | 5 +- src/mem/pfa/bitset.rs | 14 +-- src/mem/vmm.rs | 6 +- src/mem/vmm/nommu.rs | 47 ++++---- src/sched.rs | 165 ++++++++++++++++++++--------- src/sched/dispch.rs | 7 ++ src/sched/rt.rs | 6 +- src/sched/task.rs | 129 ++++++++++++++++++++++ src/sched/thread.rs | 49 +++++---- src/sync/atomic.rs | 117 +++++++++++++++++++- src/time.rs | 38 ++----- src/types/rbtree.rs | 2 +- 23 files changed, 584 insertions(+), 349 deletions(-) rename machine/api/src/{mem => }/stack.rs (89%) delete mode 100644 src/dispatch.rs delete mode 100644 src/dispatch/task.rs create mode 100644 src/idle.rs create mode 100644 src/sched/dispch.rs create mode 100644 src/sched/task.rs diff --git a/machine/api/src/lib.rs b/machine/api/src/lib.rs index 36860bc..4480adb 100644 --- a/machine/api/src/lib.rs +++ b/machine/api/src/lib.rs @@ -3,6 +3,7 @@ use core::{fmt::Display, ops::Range}; pub mod mem; +pub mod stack; #[derive(Default, Debug, PartialEq, Eq, Clone)] pub enum Error { @@ -10,6 +11,7 @@ pub enum Error { Generic, OutOfMemory(usize), OutOfBoundsPtr(usize, Range), + InvalidAddress(usize), } pub enum Fault { @@ -30,7 +32,8 @@ impl Display for Error { "Pointer {:p} out of bounds (expected in {:p}..{:p})", *ptr as *const u8, range.start as *const u8, range.end as *const u8 ) - } + }, + Error::InvalidAddress(addr) => write!(f, "Invalid address {:p}", *addr as *const u8), } } } diff --git a/machine/api/src/mem.rs b/machine/api/src/mem.rs index 10c8b07..b919bfe 100644 --- a/machine/api/src/mem.rs +++ b/machine/api/src/mem.rs @@ -1,11 +1,12 @@ - -pub mod stack; +use core::ops::{Add, Sub, Div, Rem}; #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] pub struct PhysAddr(usize); impl PhysAddr { + pub const MAX: Self = Self(usize::MAX); + #[inline] pub fn new(addr: usize) -> Self { Self(addr) @@ -15,6 +16,58 @@ impl PhysAddr { pub fn as_usize(&self) -> usize { self.0 } + + pub fn as_mut_ptr(&self) -> *mut T { + self.0 as *mut T + } + + pub fn checked_add(&self, other: usize) -> Option { + self.0.checked_add(other).map(Self) + } + + pub fn checked_sub(&self, other: usize) -> Option { + self.0.checked_sub(other).map(Self) + } + + pub fn is_multiple_of(&self, align: usize) -> bool { + self.0.is_multiple_of(align) + } +} + +impl Add for PhysAddr { + type Output = Self; + + #[inline] + fn add(self, rhs: usize) -> Self::Output { + Self(self.0 + rhs) + } +} + +impl Sub for PhysAddr { + type Output = Self; + + #[inline] + fn sub(self, rhs: usize) -> Self::Output { + Self(self.0 - rhs) + } +} + +impl Div for PhysAddr { + type Output = Self; + + #[inline] + fn div(self, rhs: usize) -> Self::Output { + Self(self.0 / rhs) + } +} + +impl Rem for PhysAddr { + type Output = Self; + + #[inline] + fn rem(self, rhs: usize) -> Self::Output { + Self(self.0 % rhs) + } } impl From for usize { @@ -38,6 +91,16 @@ impl VirtAddr { pub fn as_usize(&self) -> usize { self.0 } + + #[inline] + pub fn saturating_add(&self, other: usize) -> Self { + Self(self.0.saturating_add(other)) + } + + #[inline] + pub fn saturating_sub(&self, other: usize) -> Self { + Self(self.0.saturating_sub(other)) + } } impl From for usize { diff --git a/machine/api/src/mem/stack.rs b/machine/api/src/stack.rs similarity index 89% rename from machine/api/src/mem/stack.rs rename to machine/api/src/stack.rs index 4291378..11604f1 100644 --- a/machine/api/src/mem/stack.rs +++ b/machine/api/src/stack.rs @@ -1,18 +1,21 @@ use core::{ffi::c_void, num::NonZero}; use crate::{Result, mem::PhysAddr}; -pub struct StackDescriptor { +pub type EntryFn = extern "C" fn(); +pub type FinFn = extern "C" fn() -> !; + +pub struct Descriptor { pub top: PhysAddr, pub size: NonZero, - pub entry: extern "C" fn(), - pub fin: Option !>, + pub entry: EntryFn, + pub fin: Option, } pub trait Stacklike { type ElemSize: Copy; type StackPtr; - unsafe fn new(desc: StackDescriptor) -> Result + unsafe fn new(desc: Descriptor) -> Result where Self: Sized; diff --git a/machine/arm/src/sched.rs b/machine/arm/src/sched.rs index 19578fa..eb3f274 100644 --- a/machine/arm/src/sched.rs +++ b/machine/arm/src/sched.rs @@ -7,7 +7,7 @@ use core::{ ptr::NonNull, }; -use hal_api::{Result, stack::StackDescriptor}; +use hal_api::{Result, stack::Descriptor}; use crate::print::println; @@ -191,11 +191,11 @@ impl hal_api::stack::Stacklike for ArmStack { type ElemSize = u32; type StackPtr = StackPtr; - unsafe fn new(desc: StackDescriptor) -> Result + unsafe fn new(desc: Descriptor) -> Result where Self: Sized, { - let StackDescriptor { + let Descriptor { top, size, entry, @@ -203,7 +203,7 @@ impl hal_api::stack::Stacklike for ArmStack { } = desc; // We expect a PhysAddr, which can be converted to a ptr on nommu. - let top = NonNull::new(top as *mut u32).ok_or(hal_api::Error::InvalidAddress)?; + let top = NonNull::new(top.as_mut_ptr::()).ok_or(hal_api::Error::InvalidAddress(top.as_usize()))?; let mut stack = Self { top, diff --git a/machine/testing/src/sched.rs b/machine/testing/src/sched.rs index 9715b16..ced7100 100644 --- a/machine/testing/src/sched.rs +++ b/machine/testing/src/sched.rs @@ -2,7 +2,7 @@ use std::ffi::c_void; use hal_api::{ Result, - stack::{StackDescriptor, Stacklike}, + stack::{Descriptor, Stacklike}, }; #[derive(Debug, Clone, Copy)] @@ -12,7 +12,7 @@ impl Stacklike for TestingStack { type ElemSize = usize; type StackPtr = *mut c_void; - unsafe fn new(_desc: StackDescriptor) -> Result + unsafe fn new(_desc: Descriptor) -> Result where Self: Sized, { diff --git a/options.toml b/options.toml index d595d37..eedf288 100644 --- a/options.toml +++ b/options.toml @@ -26,6 +26,12 @@ description = "Enables the Floating Point Unit (FPU). This is required for appli type = "Boolean" default = false +[stackpages] +name = "Stack Pages" +description = "Number of pages to allocate for the kernel stack." +type = { type = "Integer", min = 1 } +default = 4 + [tuning.appmemsize] name = "Application Memory Size" description = "Sets the size of the initial memory region for the init application. This memory is used for the heap and stack." diff --git a/src/dispatch.rs b/src/dispatch.rs deleted file mode 100644 index a985fef..0000000 --- a/src/dispatch.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! This is the owner of all Tasks. It takes care of context switching between them. -//! The idea is that the Schedulers selects one of its threads to run, and then the Dipatcher takes care of context-switching to the associated Task. (e.g. setting up the address space) -//! If the thread is part of the same task as the currently running one, the Dispatcher does effectively nothing. -//! -//! - -mod task; - -use crate::types::array::IndexMap; - -/* -struct Dispatcher { - tasks: IndexMap, -}*/ \ No newline at end of file diff --git a/src/dispatch/task.rs b/src/dispatch/task.rs deleted file mode 100644 index b3f3d2c..0000000 --- a/src/dispatch/task.rs +++ /dev/null @@ -1,162 +0,0 @@ -//! This module provides the basic task and thread structures for the scheduler. -use core::num::NonZero; -use core::ops::Range; -use core::ptr::NonNull; -use std::borrow::Borrow; - -use hal::{Stack, stack}; - -use hal::stack::Stacklike; - -use crate::{mem, sched}; - -use crate::mem::alloc::{Allocator, BestFitAllocator}; -use crate::mem::vmm::{AddressSpace, AddressSpacelike, Region}; -use crate::types::traits::ToIndex; -use crate::utils::KernelError; - -/// Id of a task. This is unique across all tasks. -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] -pub struct UId { - uid: usize, -} - -impl ToIndex for UId { - fn to_index>(idx: Option) -> usize { - idx.as_ref().map_or(0, |uid| uid.borrow().uid) - } -} - -pub struct Attributes { - reserved: Option>, -} - -/// The struct representing a task. -pub struct Task { - /// The unique identifier of the task. - pub id: UId, - /// The counter for the thread ids. - tid_cntr: usize, - /// Sets up the memory for the task. - address_space: mem::vmm::AddressSpace, -} - -impl Task { - /// Create a new task. - /// - /// `memory_size` - The size of the memory that the task requires. - /// - /// Returns a new task if the task was created successfully, or an error if the task could not be created. - pub fn new(id: UId, attrs: &Attributes) -> Result { - Ok(Self { - id, - address_space: AddressSpace::new(), - tid_cntr: 0, - }) - } - - fn allocate_tid(&mut self) -> sched::thread::Id { - let tid = self.tid_cntr; - self.tid_cntr += 1; - - sched::thread::Id::new(tid, self.id) - } - - pub fn allocate(&mut self, size: usize, align: usize) -> Result { - self.address_space.map(size, align) - } - - pub fn create_thread( - &mut self, - entry: extern "C" fn(), - fin: Option !>, - ) -> Result { - - // Create the stack for the thread. - let size = 1 * mem::pfa::PAGE_SIZE; // TODO: Make this configurable - let start = self.address_space.end() - size; - let region = mem::vmm::Region::new( - start, - size, - mem::vmm::Backing::Uninit, - mem::vmm::Perms::Read | mem::vmm::Perms::Write, - ); - let stack_pa = self.address_space.map(region)?; - - let stack = hal::stack::StackDescriptor { - top: stack_pa, - // Safe unwrap because stack size is non zero. - size: NonZero::new(size).unwrap(), - entry, - fin, - }; - - let stack = unsafe { Stack::new(stack) }?; - let tid = self.allocate_tid(); - - // TODO: Revert if error occurs - self.register_thread(tid)?; - - Ok(ThreadDescriptor { tid, stack, timing }) - } - - /// Register a thread with the task. - /// - /// `thread_id` - The id of the thread to register. - /// - /// Returns `Ok(())` if the thread was registered successfully, or an error if the thread could not be registered. TODO: Check if the thread is using the same memory as the task. - fn register_thread(&mut self, thread_id: ThreadId) -> Result<(), KernelError> { - self.threads.push(thread_id) - } -} - -/// The memory of a task. -#[derive(Debug)] -pub struct TaskMemory { - /// The beginning of the memory. - begin: NonNull, - /// The size of the memory. - size: usize, - - /// The allocator for the task's memory. - alloc: BestFitAllocator, -} - -#[allow(dead_code)] -impl TaskMemory { - /// Create a new task memory. - /// - /// `size` - The size of the memory. - /// - /// Returns a new task memory if the memory was created successfully, or an error if the memory could not be created. - pub fn new(size: usize) -> Result { - let begin = mem::malloc(size, align_of::()).ok_or(KernelError::OutOfMemory)?; - - let mut alloc = BestFitAllocator::new(); - let range = Range { - start: begin.as_ptr() as usize, - end: begin.as_ptr() as usize + size, - }; - - if let Err(e) = unsafe { alloc.add_range(range) } { - unsafe { mem::free(begin, size) }; - return Err(e); - } - - Ok(Self { begin, size, alloc }) - } - - pub fn malloc(&mut self, size: usize, align: usize) -> Result, KernelError> { - self.alloc.malloc(size, align) - } - - pub fn free(&mut self, ptr: NonNull, size: usize) { - unsafe { self.alloc.free(ptr, size) } - } -} - -impl Drop for TaskMemory { - fn drop(&mut self) { - unsafe { mem::free(self.begin, self.size) }; - } -} diff --git a/src/idle.rs b/src/idle.rs new file mode 100644 index 0000000..c3b4060 --- /dev/null +++ b/src/idle.rs @@ -0,0 +1,17 @@ +use crate::sched; + +extern "C" fn entry() { + loop { + hal::asm::wfi!(); + } +} + +pub fn init() { + let attrs = sched::thread::Attributes { + entry: entry, + fin: None, + }; + if let Err(e) = sched::create_thread(sched::task::KERNEL_TASK, &attrs) { + panic!("[Idle] Error: failed to create idle thread. Error: {e:?}"); + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index eeb2000..13fbdf8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,12 +10,13 @@ mod utils; mod faults; mod mem; mod types; +mod idle; + pub mod print; -//pub mod sched; -mod dispatch; +pub mod sched; pub mod sync; pub mod syscalls; -//pub mod time; +pub mod time; pub mod uspace; use hal::Machinelike; @@ -32,6 +33,7 @@ include!(concat!(env!("OUT_DIR"), "/syscalls_export.rs")); /// The `boot_info` pointer must be valid and point to a properly initialized `BootInfo` structure. #[unsafe(no_mangle)] pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { + hal::asm::disable_interrupts!(); // Initialize basic hardware and the logging system. hal::Machine::init(); hal::Machine::bench_start(); @@ -46,9 +48,10 @@ pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { print::print_header(); // Initialize the memory allocator. - if let Err(e) = mem::init_memory(boot_info) { - panic!("[Kernel] Error: failed to initialize memory allocator. Error: {e:?}"); - } + let kaddr_space = mem::init_memory(boot_info); + + sched::init(kaddr_space); + idle::init(); let (cyc, ns) = hal::Machine::bench_end(); kprintln!( @@ -62,6 +65,8 @@ pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { panic!("[Kernel] Error: failed to start init application. Error: {e:?}"); } + hal::asm::enable_interrupts!(); + loop { hal::asm::nop!(); } diff --git a/src/mem.rs b/src/mem.rs index ff9973a..fb96d5f 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -1,14 +1,18 @@ //! This module provides access to the global memory allocator. +use crate::mem::vmm::{AddressSpacelike, Backing, Perms, Region}; use crate::sync::spinlock::SpinLocked; -use crate::{BootInfo, utils}; +use crate::{BootInfo, sched, utils}; use alloc::Allocator; +use hal::mem::{PhysAddr, VirtAddr}; use core::ptr::NonNull; pub mod alloc; pub mod vmm; pub mod pfa; +pub const BITS_PER_PTR: usize = core::mem::size_of::() * 8; + /// The possible types of memory. Which is compatible with the multiboot2 memory map. /// Link: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html #[repr(C)] @@ -35,22 +39,31 @@ static GLOBAL_ALLOCATOR: SpinLocked = /// `boot_info` - The boot information. This contains the memory map. /// /// Returns an error if the memory allocator could not be initialized. -pub fn init_memory(boot_info: &BootInfo) -> Result<(), utils::KernelError> { - pfa::init_pfa(0x20000000)?; // TODO: Get this from the DeviceTree. +pub fn init_memory(boot_info: &BootInfo) -> vmm::AddressSpace { + if let Err(e) = pfa::init_pfa(PhysAddr::new(0x20000000)) { // TODO: Get this from the DeviceTree. + panic!("[Kernel] Error: failed to initialize PFA. Error: {e:?}"); + } + + // TODO: Configure. + let pgs = 4; + + let kaddr_space = vmm::AddressSpace::new(pgs).unwrap_or_else(|e| { + panic!("[Kernel] Error: failed to create kernel address space."); + }); + + let begin = kaddr_space.map(Region::new(VirtAddr::new(0), len, Backing::Zeroed, Perms::all())).unwrap_or_else(|e| { + panic!("[Kernel] Error: failed to map kernel address space."); + }); let mut allocator = GLOBAL_ALLOCATOR.lock(); - for entry in boot_info.mmap.iter().take(boot_info.mmap_len as usize) { - // We only add available memory to the allocator. - if entry.ty == MemoryTypes::Available as u32 { - let range = entry.addr as usize..(entry.addr + entry.length) as usize; - unsafe { - allocator.add_range(range)?; - } - } + let range = begin.as_usize()..(begin.as_usize() + pgs * vmm::PAGE_SIZE); + + if let Err(e) = unsafe { allocator.add_range(range) } { + panic!("[Kernel] Error: failed to add range to allocator."); } - Ok(()) + kaddr_space } /// Allocate a memory block. Normally Box or SizedPool should be used instead of this function. diff --git a/src/mem/pfa.rs b/src/mem/pfa.rs index 9c24df8..c62b8c1 100644 --- a/src/mem/pfa.rs +++ b/src/mem/pfa.rs @@ -1,10 +1,11 @@ // The top level page frame allocator. +use hal::mem::PhysAddr; + use crate::sync::spinlock::SpinLocked; use crate::types::boxed::Box; use crate::utils::KernelError; -use interface::PhysAddr; use core::pin::Pin; mod bitset; @@ -12,7 +13,7 @@ mod bitset; /// Page size constant (typically 4KB) pub const PAGE_SIZE: usize = 4096; -const PAGE_CNT: usize = 1024; // TODO: This should be determined by the DeviceTree. +const PAGE_CNT: usize = 100; // TODO: This should be determined by the DeviceTree. type AllocatorType = bitset::Allocator; diff --git a/src/mem/pfa/bitset.rs b/src/mem/pfa/bitset.rs index d9ab9e3..23ef7f7 100644 --- a/src/mem/pfa/bitset.rs +++ b/src/mem/pfa/bitset.rs @@ -1,13 +1,13 @@ use core::pin::Pin; use core::ptr::NonNull; +use hal::mem::PhysAddr; + use crate::{ types::boxed::{self, Box}, utils::KernelError, }; -use interface::PhysAddr; - pub struct Allocator { begin: PhysAddr, l1: [usize; N], @@ -43,7 +43,7 @@ impl super::Allocator for Allocator { return Err(KernelError::InvalidAlign); } - let ptr = NonNull::new(addr as *mut Self).ok_or(KernelError::InvalidAddress)?; + let ptr = NonNull::new(addr.as_mut_ptr::()).ok_or(KernelError::InvalidAddress)?; // Safety: Ptr is properly aligned and non-null. The validity of the memory at that address is valid by the call contract. Ok(Pin::new(unsafe { boxed::Box::from_raw(ptr) })) @@ -119,7 +119,7 @@ impl super::Allocator for Allocator { self.l1[idx] &= !((!0usize).unbounded_shl((Self::BITS_PER_WORD - rem) as u32) >> skip); if len <= rem { - return Some(start); + return Some(PhysAddr::new(start)); } len -= rem; @@ -151,8 +151,8 @@ impl super::Allocator for Allocator { panic!("Address must be page aligned"); } - let mut idx = (addr - self.begin) / super::PAGE_SIZE / Self::BITS_PER_WORD; - let mut bit_idx = ((addr - self.begin) / super::PAGE_SIZE) % Self::BITS_PER_WORD; + let mut idx = (addr.as_usize() - self.begin.as_usize()) / super::PAGE_SIZE / Self::BITS_PER_WORD; + let mut bit_idx = ((addr.as_usize() - self.begin.as_usize()) / super::PAGE_SIZE) % Self::BITS_PER_WORD; // TODO: slow for _ in 0..page_count { @@ -181,7 +181,7 @@ mod tests { const BITS: usize = Allocator::::BITS_PER_WORD; const ALLOC_SIZE: usize = 100; - let mut allocator = Allocator::::new(0x0).unwrap(); + let mut allocator = Allocator::::new(PhysAddr::new(0x0)).unwrap(); // Generate a random bit pattern. for i in 0..N { diff --git a/src/mem/vmm.rs b/src/mem/vmm.rs index 7565658..1f8aba9 100644 --- a/src/mem/vmm.rs +++ b/src/mem/vmm.rs @@ -1,8 +1,8 @@ use core::ops::Range; -use crate::{utils::KernelError}; +use hal::mem::{PhysAddr, VirtAddr}; -use interface::{PhysAddr, VirtAddr}; +use crate::{utils::KernelError}; mod nommu; @@ -45,7 +45,7 @@ impl Region { } pub fn len(&self) -> usize { - self.range.end.saturating_sub(self.range.start) + self.range.end.saturating_sub(self.range.start.into()).into() } pub fn contains(&self, addr: VirtAddr) -> bool { diff --git a/src/mem/vmm/nommu.rs b/src/mem/vmm/nommu.rs index 69603cd..ff561b2 100644 --- a/src/mem/vmm/nommu.rs +++ b/src/mem/vmm/nommu.rs @@ -1,5 +1,6 @@ use core::ptr::copy_nonoverlapping; -use std::num::NonZero; + +use hal::mem::{PhysAddr, VirtAddr}; use crate::{ mem::{ @@ -8,57 +9,56 @@ use crate::{ utils::KernelError, }; -use interface::{PhysAddr, VirtAddr}; - pub struct AddressSpace { - begin: VirtAddr, - size: usize, + begin: PhysAddr, + end: PhysAddr, } impl vmm::AddressSpacelike for AddressSpace { fn new(size: usize) -> Result { let pg_cnt = size.div_ceil(pfa::PAGE_SIZE); let begin = pfa::alloc_page(pg_cnt).ok_or(KernelError::OutOfMemory)?; + let end = begin.checked_add(pg_cnt * pfa::PAGE_SIZE).ok_or(KernelError::OutOfMemory)?; Ok(Self { begin, - size: pg_cnt * pfa::PAGE_SIZE, + end, }) } fn map(&mut self, region: vmm::Region) -> Result { - if region.start() + region.len() > self.size { - return Err(KernelError::OutOfMemory); - } - - if let Some(test) = NonZero::new(region.start()) { - test. - } - + // Do both checks in one statement. + let phys = self.virt_to_phys(region.start()).and_then(|phys| { + if phys > self.end { + None + } else { + Some(phys) + } + }).ok_or(KernelError::InvalidArgument)?; match region.backing { vmm::Backing::Anon(phys) => { unsafe { copy_nonoverlapping( - phys as *const u8, - (self.begin + region.start()) as *mut u8, + phys.as_mut_ptr::(), + phys.as_mut_ptr::(), region.len(), ) }; - Ok(self.begin + region.start()) }, vmm::Backing::Zeroed => { unsafe { core::ptr::write_bytes( - (self.begin + region.start()) as *mut u8, + phys.as_mut_ptr::(), 0, region.len(), ) }; - Ok(self.begin + region.start()) }, - vmm::Backing::Uninit => Ok(self.begin + region.start()), + vmm::Backing::Uninit => {}, } + + Ok(phys) } fn unmap(&mut self, _region: &vmm::Region) -> Result<(), KernelError> { @@ -70,15 +70,16 @@ impl vmm::AddressSpacelike for AddressSpace { } fn phys_to_virt(&self, addr: PhysAddr) -> Option { - addr.checked_sub(self.begin) + addr.checked_sub(self.begin.as_usize()).map(|phys| VirtAddr::new(phys.as_usize())) } fn virt_to_phys(&self, addr: VirtAddr) -> Option { - self.begin.checked_add(addr) + self.begin.checked_add(addr.as_usize()) } fn end(&self) -> VirtAddr { - self.size + // This should always succeed. + self.phys_to_virt(self.end).unwrap() } fn activate(&self) -> Result<(), KernelError> { diff --git a/src/sched.rs b/src/sched.rs index 9edca37..076b107 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -1,47 +1,80 @@ //! This module provides access to the scheduler. +mod dispch; pub mod rt; -//pub mod thread; +pub mod task; +pub mod thread; + +use core::ffi::c_void; use hal::Schedable; -use crate::types::{array::IndexMap, rbtree::RbTree, view::ViewMut}; +use crate::{ + sync::spinlock::SpinLocked, types::{ + array::IndexMap, + rbtree::RbTree, + traits::{Get, GetMut}, + view::ViewMut, + }, utils::KernelError +}; type ThreadMap = IndexMap; +type TaskMap = IndexMap; + +static SCHED: SpinLocked> = SpinLocked::new(Scheduler::new()); pub struct Scheduler { threads: ThreadMap, + tasks: TaskMap, + id_gen: usize, + rt_scheduler: rt::Scheduler, wakeup: RbTree, + current: thread::UId, last_tick: u64, + next_tick: u64, } impl Scheduler { - pub fn new() -> Self { + pub const fn new() -> Self { Self { threads: IndexMap::new(), + tasks: IndexMap::new(), + id_gen: 1, rt_scheduler: rt::Scheduler::new(), wakeup: RbTree::new(), + current: thread::IDLE_THREAD, last_tick: 0, + next_tick: 0, } } - pub fn enqueue(&mut self, thread: thread::Thread) { - let uid = thread.uid(); - let rt = thread.rt_server().is_some(); - self.threads.insert(&thread.uid(), thread); + fn land(&mut self, ctx: *mut c_void) -> Result<(), KernelError> { + // A thread must not disappear while it is running. + let current = self.threads.get_mut(self.current).ok_or(KernelError::InvalidArgument)?; + // The context pointer must not be bogus after a sched_enter. + current.save_ctx(ctx) + } + + pub fn enqueue(&mut self, uid: thread::UId) -> Result<(), KernelError> { + let thread = self.threads.get(uid).ok_or(KernelError::InvalidArgument)?; - if rt { - let mut view = ViewMut::>::new( - &mut self.threads, - ); + if thread.rt_server().is_some() { + let mut view = + ViewMut::>::new(&mut self.threads); self.rt_scheduler.enqueue(uid, &mut view); } + + Ok(()) } - pub fn do_sched(&mut self, now: u64, old: Option) -> Option { + pub fn do_sched( + &mut self, + now: u64, + old: Option, + ) -> Option<(*mut c_void, &mut task::Task)> { let dt = now - self.last_tick; self.last_tick = now; @@ -55,7 +88,14 @@ impl Scheduler { } let mut view = rt::ServerView::::new(&mut self.threads); - self.rt_scheduler.pick(now, &mut view) + let (new, budget) = self.rt_scheduler.pick(now, &mut view)?; + + let ctx = self.threads.get(new)?.ctx(); + let task = self.tasks.get_mut(self.threads.get(new)?.task_id())?; + + self.current = new; + self.next_tick = now + budget; + Some((ctx, task)) } pub fn dequeue(&mut self, uid: thread::UId) -> Option { @@ -65,57 +105,78 @@ impl Scheduler { self.threads.remove(&uid) } -} -/// Reschedule the tasks. -pub fn reschedule() { - hal::Machine::trigger_reschedule(); -} + pub fn create_task(&mut self, task: &task::Attributes) -> Result { + let uid = task::UId::new(self.id_gen).ok_or(KernelError::InvalidArgument)?; + self.id_gen += 1; -/* + self.tasks.insert(&uid, task::Task::new(uid, task)?); + Ok(uid) + } + pub fn create_thread(&mut self, task: task::UId, attrs: &thread::Attributes) -> Result { + let task = self.tasks.get_mut(task).ok_or(KernelError::InvalidArgument)?; + let thread = task.create_thread(self.id_gen, attrs)?; + let uid = thread.uid(); + self.id_gen += 1; + Ok(uid) + } +} +pub fn init(kaddr_space: mem::vmm::AddressSpace) { + let mut sched = SCHED.lock(); + let uid = task::KERNEL_TASK; + sched.tasks.insert(&uid, task::Task::from_addr_space(uid, kaddr_space)); +} -/// Create a new task. -/// -/// `desc` - The task descriptor. -/// `main_desc` - The main thread descriptor. -/// `main_timing` - The timing information for the main thread. -/// -/// Returns the task ID if the task was created successfully, or an error if the task could not be created. -pub fn create_task(desc: task::TaskDescriptor) -> Result { - enable_scheduler(false); - let res = scheduler::SCHEDULER.lock().create_task(desc); - enable_scheduler(true); +pub fn needs_reschedule(now: u64) -> bool { + let sched = SCHED.lock(); + now >= sched.next_tick +} - res +pub fn create_task(attrs: &task::Attributes) -> Result { + SCHED.lock().create_task(attrs) } -pub fn create_thread( - task_id: task::TaskId, - entry: extern "C" fn(), - fin: Option !>, - timing: thread::Timing, -) -> Result { - enable_scheduler(false); - let res = scheduler::SCHEDULER - .lock() - .create_thread(entry, fin, timing, task_id); - enable_scheduler(true); - - res +pub fn create_thread(task: task::UId, attrs: &thread::Attributes) -> Result { + SCHED.lock().create_thread(task, attrs) } -pub fn enable_scheduler(enable: bool) { - scheduler::set_enabled(enable); +/// Reschedule the tasks. +pub fn reschedule() { + hal::Machine::trigger_reschedule(); } -pub fn tick_scheduler() -> bool { - if !scheduler::enabled() { - return false; +/// cbindgen:ignore +/// cbindgen:no-export +#[unsafe(no_mangle)] +pub extern "C" fn sched_enter(ctx: *mut c_void) -> *mut c_void { + let mut sched = SCHED.lock(); + let mut broken = false; + let old = sched.current; + + if sched.land(ctx).is_err() { + if sched.current == thread::IDLE_THREAD { + BUG!("failed to land the idle thread. something is horribly broken."); + } + + // If we cannot reasonably land. We dequeue the thread. + sched.dequeue(old); + // TODO: Warn + sched.current = thread::IDLE_THREAD; + broken = true; } - scheduler::SCHEDULER.lock().tick() -} + let now = 0; - */ + if let Some((ctx, task)) = sched.do_sched(now, Some(old)) { + if task.id != old.owner() { + dispch::prepare(task); + } + ctx + } else if broken { + BUG!("failed to reschedule after a failed landing. something is horribly broken."); + } else { + ctx + } +} diff --git a/src/sched/dispch.rs b/src/sched/dispch.rs new file mode 100644 index 0000000..0f781b6 --- /dev/null +++ b/src/sched/dispch.rs @@ -0,0 +1,7 @@ +use super::task::Task; + +pub fn prepare(task: &mut Task) { + if task.id.is_kernel() { + // Change task priv. level in HAL. + } +} \ No newline at end of file diff --git a/src/sched/rt.rs b/src/sched/rt.rs index 95dfb62..b5a66b9 100644 --- a/src/sched/rt.rs +++ b/src/sched/rt.rs @@ -7,7 +7,7 @@ pub struct Scheduler { pub type ServerView<'a, const N: usize> = ViewMut<'a, thread::UId, thread::RtServer, ThreadMap>; impl Scheduler { - pub fn new() -> Self { + pub const fn new() -> Self { Self { edf: RbTree::new(), } @@ -23,7 +23,7 @@ impl Scheduler { } } - pub fn pick(&mut self, now: u64, storage: &mut ServerView) -> Option { + pub fn pick(&mut self, now: u64, storage: &mut ServerView) -> Option<(thread::UId, u64)> { let id = self.edf.min()?; if storage.get(id)?.budget() == 0 { @@ -33,7 +33,7 @@ impl Scheduler { } // Insert updated the min cache. - self.edf.min() + self.edf.min().and_then(|id| storage.get(id).map(|s| (id, s.budget()))) } pub fn dequeue(&mut self, uid: thread::UId, storage: &mut ServerView) { diff --git a/src/sched/task.rs b/src/sched/task.rs new file mode 100644 index 0000000..06bb8d9 --- /dev/null +++ b/src/sched/task.rs @@ -0,0 +1,129 @@ +//! This module provides the basic task and thread structures for the scheduler. +use core::num::NonZero; +use core::borrow::Borrow; + +use envparse::parse_env; +use hal::{Stack}; + +use hal::stack::{Stacklike}; + +use crate::sched::thread; +use crate::{mem, sched}; + +use crate::mem::vmm::{AddressSpacelike}; +use crate::types::traits::ToIndex; +use crate::utils::KernelError; + +pub struct Defaults { + pub stack_pages: usize, +} + +const DEFAULTS: Defaults = Defaults { + stack_pages: parse_env!("OSIRIS_STACKPAGES" as usize), +}; + +pub const KERNEL_TASK: UId = UId { uid: 0 }; + +/// Id of a task. This is unique across all tasks. +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] +pub struct UId { + uid: usize, +} + +impl UId { + pub fn new(uid: usize) -> Option { + if uid == 0 { + None + } else { + Some(Self { uid }) + } + } + + pub fn is_kernel(&self) -> bool { + self.uid == 0 + } +} + +impl ToIndex for UId { + fn to_index>(idx: Option) -> usize { + idx.as_ref().map_or(0, |uid| uid.borrow().uid) + } +} + +pub struct Attributes { + pub resrv_pgs: Option>, +} + +/// The struct representing a task. +pub struct Task { + /// The unique identifier of the task. + pub id: UId, + /// The counter for the thread ids. + tid_cntr: usize, + /// Sets up the memory for the task. + address_space: mem::vmm::AddressSpace, +} + +impl Task { + pub fn new(id: UId, attrs: &Attributes) -> Result { + // TODO: On MMU systems, the resrv_pgs attribute will be ignored, as memory will not be reserved. + let resrv_pgs = attrs.resrv_pgs.ok_or(KernelError::OutOfMemory)?; + let address_space = mem::vmm::AddressSpace::new(resrv_pgs.get())?; + + Ok(Self { + id, + address_space, + tid_cntr: 0, + }) + } + + pub fn from_addr_space(id: UId, address_space: mem::vmm::AddressSpace) -> Self { + Self { + id, + address_space, + tid_cntr: 0, + } + } + + fn allocate_tid(&mut self) -> sched::thread::Id { + let tid = self.tid_cntr; + self.tid_cntr += 1; + + sched::thread::Id::new(tid, self.id) + } + + fn allocate_stack( + &mut self, + attrs: &thread::Attributes, + ) -> Result { + let size = DEFAULTS.stack_pages * mem::pfa::PAGE_SIZE; + let start = self.address_space.end().saturating_sub(size); + let region = mem::vmm::Region::new( + start, + size, + mem::vmm::Backing::Uninit, + mem::vmm::Perms::Read | mem::vmm::Perms::Write, + ); + let pa = self.address_space.map(region)?; + + Ok(hal::stack::Descriptor { + top: pa + size, + size: NonZero::new(size).unwrap(), + entry: attrs.entry, + fin: attrs.fin, + }) + } + + pub fn create_thread( + &mut self, + uid: usize, + attrs: &thread::Attributes, + ) -> Result { + let stack = self.allocate_stack(attrs)?; + + let stack = unsafe { Stack::new(stack) }?; + let tid = self.allocate_tid(); + + Ok(sched::thread::Thread::new(tid.get_uid(uid), stack)) + } +} diff --git a/src/sched/thread.rs b/src/sched/thread.rs index f988b97..63c608a 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -2,22 +2,28 @@ use core::{borrow::Borrow, ffi::c_void}; -use hal::Stack; -use hal::stack::Stacklike; +use hal::{Stack, stack::EntryFn}; +use hal::stack::{FinFn, Stacklike}; use macros::TaggedLinks; -use crate::{types::{rbtree::{self, Compare}, traits::{Project, ToIndex}}, sched::task::TaskId, utils::KernelError}; +use crate::sched::task::{self, KERNEL_TASK}; +use crate::{types::{rbtree::{self, Compare}, traits::{Project, ToIndex}}, utils::KernelError}; + +pub const IDLE_THREAD: UId = UId { + uid: 0, + tid: Id { id: 0, owner: KERNEL_TASK }, +}; /// Id of a task. This is only unique within a Task. #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] pub struct Id { id: usize, - owner: TaskId, + owner: task::UId, } #[allow(dead_code)] impl Id { - pub fn new(id: usize, owner: TaskId) -> Self { + pub fn new(id: usize, owner: task::UId) -> Self { Self { id, owner } } @@ -25,7 +31,7 @@ impl Id { self.id } - pub fn owner(&self) -> TaskId { + pub fn owner(&self) -> task::UId { self.owner } @@ -38,7 +44,9 @@ impl Id { #[derive(Clone, Copy, Debug)] #[allow(dead_code)] pub struct UId { + /// A globally unique identifier for the thread. uid: usize, + /// The task-local identifier for the thread. tid: Id, } @@ -47,6 +55,10 @@ impl UId { pub fn tid(&self) -> Id { self.tid } + + pub fn owner(&self) -> task::UId { + self.tid.owner + } } impl PartialEq for UId { @@ -63,15 +75,6 @@ impl Into for UId { } } -impl Default for UId { - fn default() -> Self { - Self { - uid: 0, - tid: Id::new(0, TaskId::User(0)), - } - } -} - impl PartialOrd for UId { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -92,11 +95,6 @@ impl ToIndex for UId { // ------------------------------------------------------------------------- -pub struct Descriptor { - pub tid: Id, - pub stack: Stack, -} - /// The state of a thread. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[allow(dead_code)] @@ -188,6 +186,11 @@ pub struct WakupTree; #[derive(Debug, Clone, Copy)] pub struct RtTree; +pub struct Attributes { + pub entry: EntryFn, + pub fin: Option, +} + /// The struct representing a thread. #[derive(Debug, Clone, Copy)] #[derive(TaggedLinks)] @@ -210,7 +213,7 @@ impl Thread { /// `stack` - The stack of the thread. /// /// Returns a new thread. - fn new(uid: UId, stack: Stack) -> Self { + pub fn new(uid: UId, stack: Stack) -> Self { Self { state: State { run_state: RunState::Ready, @@ -243,6 +246,10 @@ impl Thread { pub fn uid(&self) -> UId { self.uid } + + pub fn task_id(&self) -> task::UId { + self.uid.tid().owner + } } impl Compare for Thread { diff --git a/src/sync/atomic.rs b/src/sync/atomic.rs index 487125c..dec9f98 100644 --- a/src/sync/atomic.rs +++ b/src/sync/atomic.rs @@ -11,10 +11,10 @@ compile_error!( ); // ----------------------------AtomicU8---------------------------- -#[cfg(all(feature = "no-atomic-cas"))] +#[cfg(any(feature = "no-atomic-cas", not(target_has_atomic = "64")))] pub use core::sync::atomic::Ordering; -#[cfg(all(feature = "no-atomic-cas"))] +#[cfg(any(feature = "no-atomic-cas", not(target_has_atomic = "64")))] use core::cell::UnsafeCell; #[cfg(all(feature = "no-atomic-cas"))] @@ -106,3 +106,116 @@ impl AtomicBool { todo!("Implement atomic compare_exchange for bool"); } } + +// ----------------------------AtomicU64---------------------------- +#[allow(unused_imports)] +#[cfg(target_has_atomic = "64")] +pub use core::sync::atomic::AtomicU64; + +#[cfg(not(target_has_atomic = "64"))] +/// An atomic `u64` implemented by disabling interrupts around each operation. +pub struct AtomicU64 { + value: UnsafeCell, +} + +#[cfg(not(target_has_atomic = "64"))] +unsafe impl Sync for AtomicU64 {} + +#[cfg(not(target_has_atomic = "64"))] +impl AtomicU64 { + /// Creates a new atomic u64. + pub const fn new(value: u64) -> Self { + Self { + value: UnsafeCell::new(value), + } + } + + #[inline(always)] + fn with_interrupts_disabled(f: impl FnOnce() -> T) -> T { + let were_enabled = hal::asm::are_interrupts_enabled(); + if were_enabled { + hal::asm::disable_interrupts(); + } + + let result = f(); + + if were_enabled { + hal::asm::enable_interrupts(); + } + + result + } + + /// Loads the value. + pub fn load(&self, _: Ordering) -> u64 { + Self::with_interrupts_disabled(|| { + // SAFETY: Interrupts are disabled, so this read is exclusive with writes. + unsafe { *self.value.get() } + }) + } + + /// Stores a value. + pub fn store(&self, value: u64, _: Ordering) { + Self::with_interrupts_disabled(|| { + // SAFETY: Interrupts are disabled, so this write is exclusive with other access. + unsafe { + *self.value.get() = value; + } + }); + } + + /// Compares the value and exchanges it. + pub fn compare_exchange( + &self, + current: u64, + new: u64, + _: Ordering, + _: Ordering, + ) -> Result { + Self::with_interrupts_disabled(|| { + // SAFETY: Interrupts are disabled, so this read-modify-write is exclusive. + unsafe { + let value = self.value.get(); + if *value == current { + *value = new; + Ok(current) + } else { + Err(*value) + } + } + }) + } + + /// Fetches and adds, returning the previous value. + pub fn fetch_add(&self, value: u64, _: Ordering) -> u64 { + Self::with_interrupts_disabled(|| { + // SAFETY: Interrupts are disabled, so this read-modify-write is exclusive. + unsafe { + let ptr = self.value.get(); + let old = *ptr; + *ptr = old.wrapping_add(value); + old + } + }) + } + + /// Fetches a value, applies the function and writes it back atomically. + pub fn fetch_update(&self, _: Ordering, _: Ordering, mut f: F) -> Result + where + F: FnMut(u64) -> Option, + { + Self::with_interrupts_disabled(|| { + // SAFETY: Interrupts are disabled, so this read-modify-write is exclusive. + unsafe { + let ptr = self.value.get(); + let old = *ptr; + if let Some(new) = f(old) { + *ptr = new; + Ok(old) + } else { + Err(old) + } + } + }) + } +} diff --git a/src/time.rs b/src/time.rs index f8a2fc4..25c73eb 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,15 +1,13 @@ -use crate::{sched, sync::spinlock::SpinLocked}; -use hal::Schedable; +use core::sync::atomic::Ordering; + +use crate::sched; +use crate::sync::atomic::AtomicU64; // This variable is only allowed to be modified by the systick handler. -static TIME: SpinLocked = SpinLocked::new(0); +static TIME: AtomicU64 = AtomicU64::new(0); fn tick() { - // Increment the global time counter. - { - let mut time = TIME.lock(); - *time += 1; - } + TIME.fetch_add(1, Ordering::Release); } /* @@ -18,32 +16,16 @@ fn tick() { */ #[allow(dead_code)] pub fn time() -> u64 { - if !hal::asm::are_interrupts_enabled() { - // If interrupts are disabled, we can just read the time. - return *TIME.lock(); - } else { - let time; - // We need to disable interrupts to ensure that systick is always able to lock the time. - hal::asm::disable_interrupts(); - // Return the current time. - { - time = *TIME.lock(); - } - hal::asm::enable_interrupts(); - // Now systick can be called again. - time - } + TIME.load(Ordering::Acquire) } /// cbindgen:ignore /// cbindgen:no-export #[unsafe(no_mangle)] pub extern "C" fn systick_hndlr() { - tick(); - - let resched = { sched::tick_scheduler() }; + let time = TIME.fetch_add(1, Ordering::Release) + 1; - if resched { - hal::Machine::trigger_reschedule(); + if sched::needs_reschedule(time) { + sched::reschedule(); } } diff --git a/src/types/rbtree.rs b/src/types/rbtree.rs index ac64a17..969df19 100644 --- a/src/types/rbtree.rs +++ b/src/types/rbtree.rs @@ -51,7 +51,7 @@ enum Color { #[allow(dead_code)] impl RbTree { - pub fn new() -> Self { + pub const fn new() -> Self { Self { root: None, min: None, From 4a906a200217b28d70dca036654ffdf3004c078c Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Wed, 25 Mar 2026 19:25:02 +0000 Subject: [PATCH 04/42] more scheduler rewrite --- presets/stm32l4r5zi_def.toml | 1 + src/idle.rs | 4 ++-- src/lib.rs | 11 +++++++++-- src/mem.rs | 7 ++++--- src/sched.rs | 2 +- src/uspace.rs | 10 +++++++--- 6 files changed, 24 insertions(+), 11 deletions(-) diff --git a/presets/stm32l4r5zi_def.toml b/presets/stm32l4r5zi_def.toml index c5f9166..9edf803 100644 --- a/presets/stm32l4r5zi_def.toml +++ b/presets/stm32l4r5zi_def.toml @@ -11,6 +11,7 @@ OSIRIS_DEBUG_RUNTIMESYMBOLS = "false" # Tuning parameters OSIRIS_TUNING_ENABLEFPU = "false" +OSIRIS_STACKPAGES = "4" OSIRIS_TUNING_APPSTACKSIZE = "2048" OSIRIS_TUNING_APPMEMSIZE = "8192" diff --git a/src/idle.rs b/src/idle.rs index c3b4060..aa62b17 100644 --- a/src/idle.rs +++ b/src/idle.rs @@ -2,13 +2,13 @@ use crate::sched; extern "C" fn entry() { loop { - hal::asm::wfi!(); + hal::asm::nop!(); } } pub fn init() { let attrs = sched::thread::Attributes { - entry: entry, + entry, fin: None, }; if let Err(e) = sched::create_thread(sched::task::KERNEL_TASK, &attrs) { diff --git a/src/lib.rs b/src/lib.rs index 13fbdf8..21efb1d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,7 @@ include!(concat!(env!("OUT_DIR"), "/syscalls_export.rs")); /// The `boot_info` pointer must be valid and point to a properly initialized `BootInfo` structure. #[unsafe(no_mangle)] pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { - hal::asm::disable_interrupts!(); + hal::asm::disable_interrupts(); // Initialize basic hardware and the logging system. hal::Machine::init(); hal::Machine::bench_start(); @@ -50,9 +50,16 @@ pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { // Initialize the memory allocator. let kaddr_space = mem::init_memory(boot_info); + kprintln!("[Kernel] Memory initialized."); + sched::init(kaddr_space); + + kprintln!("[Kernel] Scheduler initialized."); + idle::init(); + kprintln!("[Kernel] Idle thread initialized."); + let (cyc, ns) = hal::Machine::bench_end(); kprintln!( "[Osiris] Kernel init took {} cycles taking {} ms", @@ -65,7 +72,7 @@ pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { panic!("[Kernel] Error: failed to start init application. Error: {e:?}"); } - hal::asm::enable_interrupts!(); + hal::asm::enable_interrupts(); loop { hal::asm::nop!(); diff --git a/src/mem.rs b/src/mem.rs index fb96d5f..b720828 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -1,5 +1,6 @@ //! This module provides access to the global memory allocator. +use crate::mem::pfa::PAGE_SIZE; use crate::mem::vmm::{AddressSpacelike, Backing, Perms, Region}; use crate::sync::spinlock::SpinLocked; use crate::{BootInfo, sched, utils}; @@ -47,17 +48,17 @@ pub fn init_memory(boot_info: &BootInfo) -> vmm::AddressSpace { // TODO: Configure. let pgs = 4; - let kaddr_space = vmm::AddressSpace::new(pgs).unwrap_or_else(|e| { + let mut kaddr_space = vmm::AddressSpace::new(pgs).unwrap_or_else(|e| { panic!("[Kernel] Error: failed to create kernel address space."); }); - let begin = kaddr_space.map(Region::new(VirtAddr::new(0), len, Backing::Zeroed, Perms::all())).unwrap_or_else(|e| { + let begin = kaddr_space.map(Region::new(VirtAddr::new(0), pgs * PAGE_SIZE, Backing::Zeroed, Perms::all())).unwrap_or_else(|e| { panic!("[Kernel] Error: failed to map kernel address space."); }); let mut allocator = GLOBAL_ALLOCATOR.lock(); - let range = begin.as_usize()..(begin.as_usize() + pgs * vmm::PAGE_SIZE); + let range = begin.as_usize()..(begin.as_usize() + pgs * PAGE_SIZE); if let Err(e) = unsafe { allocator.add_range(range) } { panic!("[Kernel] Error: failed to add range to allocator."); diff --git a/src/sched.rs b/src/sched.rs index 076b107..c9acae7 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -10,7 +10,7 @@ use core::ffi::c_void; use hal::Schedable; use crate::{ - sync::spinlock::SpinLocked, types::{ + mem, sync::spinlock::SpinLocked, types::{ array::IndexMap, rbtree::RbTree, traits::{Get, GetMut}, diff --git a/src/uspace.rs b/src/uspace.rs index c0d1d8e..14dcfd9 100644 --- a/src/uspace.rs +++ b/src/uspace.rs @@ -2,6 +2,8 @@ use ::core::mem::transmute; +use crate::sched; + pub fn init_app(boot_info: &crate::BootInfo) -> Result<(), crate::utils::KernelError> { let len = boot_info.args.init.len; @@ -15,8 +17,10 @@ pub fn init_app(boot_info: &crate::BootInfo) -> Result<(), crate::utils::KernelE ) }; - // We don't expect coming back from the init program. - // But for future user mode support the init program will be run by the scheduler, thus we leave a result as a return value here. - entry(); + let attrs = sched::thread::Attributes { + entry, + fin: None, + }; + sched::create_thread(sched::task::KERNEL_TASK, &attrs)?; Ok(()) } From 7710beab2cae2548381cb73b3a71fdf62b3708df Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:19:09 +0000 Subject: [PATCH 05/42] should work --- Cargo.toml | 1 + machine/api/src/lib.rs | 5 + machine/api/src/mem.rs | 27 +- machine/arm/common/CMakeLists.txt | 6 - machine/arm/common/crt0.S | 7 - machine/arm/src/lib.rs | 12 + machine/arm/stm32l4xx/CMakeLists.txt | 5 - machine/arm/stm32l4xx/interface/clock.c | 130 +++++ machine/arm/stm32l4xx/interface/export.h | 7 + machine/arm/stm32l4xx/interface/lib.c | 8 +- machine/arm/stm32l4xx/interface/sched.c | 3 +- machine/arm/stm32l4xx/link.ld | 18 +- machine/testing/src/lib.rs | 12 + macros/src/lib.rs | 2 +- macros/src/tree.rs | 43 +- options.toml | 2 +- presets/stm32l4r5zi_def.toml | 7 +- presets/testing_def.toml | 1 + src/idle.rs | 2 +- src/lib.rs | 27 +- src/macros.rs | 8 + src/mem.rs | 36 +- src/mem/alloc.rs | 583 +------------------ src/mem/alloc/bestfit.rs | 705 +++++++++++++++++++++++ src/mem/pfa/bitset.rs | 15 +- src/mem/vmm.rs | 23 +- src/mem/vmm/nommu.rs | 54 +- src/sched.rs | 128 ++-- src/sched/rr.rs | 48 ++ src/sched/task.rs | 14 +- src/sched/thread.rs | 8 + src/sync/atomic.rs | 46 +- src/time.rs | 30 +- src/types.rs | 1 + src/types/list.rs | 302 ++++++++++ src/uspace.rs | 5 +- src/utils.rs | 6 +- 37 files changed, 1552 insertions(+), 785 deletions(-) create mode 100644 machine/arm/stm32l4xx/interface/clock.c create mode 100644 src/mem/alloc/bestfit.rs create mode 100644 src/sched/rr.rs create mode 100644 src/types/list.rs diff --git a/Cargo.toml b/Cargo.toml index 61afd64..ccc5109 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = ["examples/*", "xtasks", "xtasks/crates/*"] +default-members = ["."] [workspace.dependencies] interface = { path = "interface" } diff --git a/machine/api/src/lib.rs b/machine/api/src/lib.rs index 4480adb..f4ebc52 100644 --- a/machine/api/src/lib.rs +++ b/machine/api/src/lib.rs @@ -47,6 +47,11 @@ pub trait Machinelike { fn bench_start(); fn bench_end() -> (u32, f32); + fn monotonic_now() -> u64; + fn monotonic_freq() -> u64; + // Returns the frequency of the machine's systick timer in Hz. + fn systick_freq() -> u64; + type ExcepBacktrace: Display; type ExcepStackFrame: Display; fn backtrace(initial_fp: *const usize, stack_ptr: *const usize) -> Self::ExcepBacktrace; diff --git a/machine/api/src/mem.rs b/machine/api/src/mem.rs index b919bfe..4aa98d6 100644 --- a/machine/api/src/mem.rs +++ b/machine/api/src/mem.rs @@ -1,7 +1,7 @@ -use core::ops::{Add, Sub, Div, Rem}; +use core::{fmt::Display, ops::{Add, Div, Rem, Sub}, ptr::NonNull}; #[repr(transparent)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] pub struct PhysAddr(usize); impl PhysAddr { @@ -32,6 +32,29 @@ impl PhysAddr { pub fn is_multiple_of(&self, align: usize) -> bool { self.0.is_multiple_of(align) } + + pub fn diff(&self, other: Self) -> usize { + if self.0 >= other.0 { + // Cannot underflow because of the check above. + self.0.checked_sub(other.0).unwrap() + } else { + // Cannot underflow because of the check above. + other.0.checked_sub(self.0).unwrap() + } + } +} + +impl From> for PhysAddr { + #[inline] + fn from(ptr: NonNull) -> Self { + Self(ptr.as_ptr() as usize) + } +} + +impl Display for PhysAddr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "0x{:x}", self.0) + } } impl Add for PhysAddr { diff --git a/machine/arm/common/CMakeLists.txt b/machine/arm/common/CMakeLists.txt index c9111b7..f426b3a 100644 --- a/machine/arm/common/CMakeLists.txt +++ b/machine/arm/common/CMakeLists.txt @@ -28,14 +28,8 @@ foreach(var ${_cache_vars}) endif() endforeach() -# This sets up PIC for .data/.bss access by making all accesses relative to r9. -# r9 is initialized in crt0.S to point to the base of the .data section. -# We need this because the offset between .text and .data is not known at compile time. (relocatable) -add_compile_options(-msingle-pic-base -mpic-register=r9 -mno-pic-data-is-text-relative) - set_property(SOURCE ivt.S APPEND PROPERTY COMPILE_OPTIONS "-x" "assembler-with-cpp") set_property(SOURCE irq.S APPEND PROPERTY COMPILE_OPTIONS "-x" "assembler-with-cpp") -set_property(SOURCE crt0.S APPEND PROPERTY COMPILE_OPTIONS "-fno-pic") add_library(common STATIC ivt.S diff --git a/machine/arm/common/crt0.S b/machine/arm/common/crt0.S index 215c85e..e26deb6 100644 --- a/machine/arm/common/crt0.S +++ b/machine/arm/common/crt0.S @@ -10,13 +10,6 @@ .thumb_func .global bootstrap bootstrap: - /* - * Initialize r9 to point to the start of the .data section. - * This is needed because all .data/.bss accesses are relative to r9. - * We need this because the offset between .text and .data is not known at compile time (relocatable). - */ - ldr r9, =__data_start - @ Copy initialized data from flash to RAM. ldr r0, =__data ldr r1, =__data_start diff --git a/machine/arm/src/lib.rs b/machine/arm/src/lib.rs index 389d14b..31666f5 100644 --- a/machine/arm/src/lib.rs +++ b/machine/arm/src/lib.rs @@ -65,6 +65,18 @@ impl hal_api::Machinelike for ArmMachine { (cycles as u32, ns) } + fn monotonic_now() -> u64 { + unsafe { bindings::monotonic_now() } + } + + fn monotonic_freq() -> u64 { + unsafe { bindings::monotonic_freq() } + } + + fn systick_freq() -> u64 { + unsafe { bindings::systick_freq() } + } + type ExcepBacktrace = excep::ExcepBacktrace; type ExcepStackFrame = excep::ExcepStackFrame; diff --git a/machine/arm/stm32l4xx/CMakeLists.txt b/machine/arm/stm32l4xx/CMakeLists.txt index 569d51b..3e11634 100644 --- a/machine/arm/stm32l4xx/CMakeLists.txt +++ b/machine/arm/stm32l4xx/CMakeLists.txt @@ -30,11 +30,6 @@ foreach(var ${_cache_vars}) endif() endforeach() -# This sets up PIC for .data/.bss access by making all accesses relative to r9. -# r9 is initialized in crt0.S to point to the base of the .data section. -# We need this because the offset between .text and .data is not known at compile time. (relocatable) -add_compile_options(-msingle-pic-base -mpic-register=r9 -mno-pic-data-is-text-relative) - # this will compile our variant_stm32l4xx library add_subdirectory(${OSIRIS_ARM_STM32L4XX_VARIANT}) diff --git a/machine/arm/stm32l4xx/interface/clock.c b/machine/arm/stm32l4xx/interface/clock.c new file mode 100644 index 0000000..ed3acac --- /dev/null +++ b/machine/arm/stm32l4xx/interface/clock.c @@ -0,0 +1,130 @@ +#include "lib.h" +#include + +static volatile uint64_t monotonic_hi = 0; + +static void init_monotonic_timer(void) +{ + const uint32_t target_hz = 1000000U; + uint32_t tim_clk = HAL_RCC_GetPCLK1Freq(); + + monotonic_hi = 0; + + // If APB1 prescaler is not 1, timer clocks run at 2x PCLK1. + if ((RCC->CFGR & RCC_CFGR_PPRE1) != RCC_CFGR_PPRE1_DIV1) { + tim_clk *= 2U; + } + + const uint32_t prescaler = (tim_clk / target_hz) - 1U; + + __HAL_RCC_TIM2_CLK_ENABLE(); + __HAL_RCC_TIM2_FORCE_RESET(); + __HAL_RCC_TIM2_RELEASE_RESET(); + + HAL_NVIC_DisableIRQ(TIM2_IRQn); + NVIC_ClearPendingIRQ(TIM2_IRQn); + + // URS ensures update flags/interrupts are only from real overflows. + TIM2->CR1 = TIM_CR1_URS; + TIM2->PSC = prescaler; + TIM2->ARR = 0xFFFFFFFFU; + TIM2->CNT = 0; + TIM2->EGR = TIM_EGR_UG; + + // Clear pending flags and enable update interrupt for wrap extension. + TIM2->SR = 0; + TIM2->DIER = TIM_DIER_UIE; + + HAL_NVIC_SetPriority(TIM2_IRQn, 15, 0); + HAL_NVIC_EnableIRQ(TIM2_IRQn); + + TIM2->CR1 |= TIM_CR1_CEN; + + // Clear any latent startup update state before first read. + TIM2->SR = 0; + NVIC_ClearPendingIRQ(TIM2_IRQn); +} + +void tim2_hndlr(void) +{ + if ((TIM2->SR & TIM_SR_UIF) != 0U) { + TIM2->SR &= ~TIM_SR_UIF; + monotonic_hi += (1ULL << 32); + } +} + +void SystemClock_Config(void) +{ + RCC_OscInitTypeDef RCC_OscInitStruct = {0}; + RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; + + /* 80 MHz on STM32L4+ => Range 1 normal mode, not boost */ + __HAL_RCC_PWR_CLK_ENABLE(); + + if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK) { + while (1) {} + } + + /* HSI16 -> PLL -> 80 MHz SYSCLK */ + RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; + RCC_OscInitStruct.HSIState = RCC_HSI_ON; + RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; + + RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; + RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; + RCC_OscInitStruct.PLL.PLLM = 1; + RCC_OscInitStruct.PLL.PLLN = 10; + RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2; + RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7; // arbitrary unless you use PLLP + RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; // arbitrary unless you use PLLQ + + if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { + while (1) {} + } + + RCC_ClkInitStruct.ClockType = + RCC_CLOCKTYPE_SYSCLK | + RCC_CLOCKTYPE_HCLK | + RCC_CLOCKTYPE_PCLK1 | + RCC_CLOCKTYPE_PCLK2; + + RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; + RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; + RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; + RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; + + if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) { + while (1) {} + } + + SystemCoreClockUpdate(); + init_monotonic_timer(); +} + +unsigned long long monotonic_now(void) +{ + uint64_t hi_1; + uint64_t hi_2; + uint32_t lo; + uint32_t sr; + + // Retry if the overflow IRQ updates the high word while sampling. + do { + hi_1 = monotonic_hi; + lo = TIM2->CNT; + sr = TIM2->SR; + hi_2 = monotonic_hi; + } while (hi_1 != hi_2); + + // If overflow is pending but IRQ has not run yet, include that wrap. + if ((sr & TIM_SR_UIF) != 0U && lo < 0x80000000U) { + hi_1 += (1ULL << 32); + } + + return hi_1 | (uint64_t)lo; +} + +unsigned long long monotonic_freq(void) +{ + return 1000000ULL; +} \ No newline at end of file diff --git a/machine/arm/stm32l4xx/interface/export.h b/machine/arm/stm32l4xx/interface/export.h index ce3e398..9d5910b 100644 --- a/machine/arm/stm32l4xx/interface/export.h +++ b/machine/arm/stm32l4xx/interface/export.h @@ -1,6 +1,7 @@ #pragma once // lib.c +unsigned long long systick_freq(void); void init_hal(void); // uart.c @@ -16,3 +17,9 @@ void dwt_reset(void); long dwt_read(void); float dwt_read_ns(void); float dwt_cycles_to_ns(long cycles); + +// clock.c +void SystemClock_Config(void); + +unsigned long long monotonic_now(void); +unsigned long long monotonic_freq(void); diff --git a/machine/arm/stm32l4xx/interface/lib.c b/machine/arm/stm32l4xx/interface/lib.c index 188119c..3c7d723 100644 --- a/machine/arm/stm32l4xx/interface/lib.c +++ b/machine/arm/stm32l4xx/interface/lib.c @@ -15,11 +15,14 @@ static void enable_faults(void) { } static void init_systick(void) { - HAL_SYSTICK_Config(SystemCoreClock / - 10); // Configure SysTick to interrupt every 1 ms + HAL_SYSTICK_Config(SystemCoreClock / 1000); // Configure SysTick to interrupt every 1 ms HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK); } +unsigned long long systick_freq(void) { + return 1000; +} + void init_hal(void) { #if OSIRIS_TUNING_ENABLEFPU init_fpu(); @@ -28,6 +31,7 @@ void init_hal(void) { enable_faults(); + SystemClock_Config(); init_systick(); } diff --git a/machine/arm/stm32l4xx/interface/sched.c b/machine/arm/stm32l4xx/interface/sched.c index a301d50..8e90155 100644 --- a/machine/arm/stm32l4xx/interface/sched.c +++ b/machine/arm/stm32l4xx/interface/sched.c @@ -6,4 +6,5 @@ void reschedule(void) { SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk; // Trigger PendSV exception __ISB(); __DSB(); -} \ No newline at end of file +} + diff --git a/machine/arm/stm32l4xx/link.ld b/machine/arm/stm32l4xx/link.ld index c964b11..b3e0ee8 100644 --- a/machine/arm/stm32l4xx/link.ld +++ b/machine/arm/stm32l4xx/link.ld @@ -39,15 +39,6 @@ SECTIONS KEEP(*(.ivt.ext)); } > FLASH :text - .stack (NOLOAD) : - { - . = ALIGN(4); - __stack_start = .; - . = . + __stack_size; - . = ALIGN(4); - __stack_top = .; - } > RAM - .text : { *(.text .text.* .gnu.linkonce.t*) @@ -130,6 +121,15 @@ SECTIONS __bss_end = .; } > RAM :data + .stack (NOLOAD) : + { + . = ALIGN(4); + __stack_start = .; + . = . + __stack_size; + . = ALIGN(4); + __stack_top = .; + } > RAM + /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) } } diff --git a/machine/testing/src/lib.rs b/machine/testing/src/lib.rs index 4236f4f..7c8dc6a 100644 --- a/machine/testing/src/lib.rs +++ b/machine/testing/src/lib.rs @@ -26,6 +26,18 @@ impl hal_api::Machinelike for TestingMachine { (0, 0.0) } + fn monotonic_now() -> u64 { + 0 + } + + fn monotonic_freq() -> u64 { + 0 + } + + fn systick_freq() -> u64 { + 0 + } + type ExcepBacktrace = String; type ExcepStackFrame = String; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index c871c86..178322c 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -5,7 +5,7 @@ use proc_macro2::TokenStream; mod tree; -#[proc_macro_derive(TaggedLinks, attributes(rbtree))] +#[proc_macro_derive(TaggedLinks, attributes(rbtree, list))] pub fn derive_tagged_links(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = syn::parse_macro_input!(input as syn::DeriveInput); diff --git a/macros/src/tree.rs b/macros/src/tree.rs index d62d8f6..9a37950 100644 --- a/macros/src/tree.rs +++ b/macros/src/tree.rs @@ -23,9 +23,11 @@ pub fn derive_tagged_links(input: &DeriveInput) -> syn::Result) -> syn::Result { + let struct_ident = &input.ident; + let generics = &input.generics; + + let mut impls = Vec::new(); + + for field in fields { + let Some(field_ident) = field.ident.clone() else { continue }; + + if let (Some(tag_path), Some(idx_path)) = find_tagged(&field.attrs, "list")? { + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let impl_block = quote! { + impl #impl_generics crate::types::list::Linkable<#tag_path, #idx_path> for #struct_ident #ty_generics #where_clause { + #[inline] + fn links(&self) -> &crate::types::list::Links<#tag_path, #idx_path> { + &self.#field_ident + } + #[inline] + fn links_mut(&mut self) -> &mut crate::types::list::Links<#tag_path, #idx_path> { + &mut self.#field_ident + } + } + }; + + impls.push(impl_block); + } } Ok(quote! { #(#impls)* }) } -fn find_rbtree(attrs: &[syn::Attribute]) -> syn::Result<(Option, Option)> { +fn find_tagged(attrs: &[syn::Attribute], attr_name: &str) -> syn::Result<(Option, Option)> { for attr in attrs { - if !attr.path().is_ident("rbtree") { + if !attr.path().is_ident(attr_name) { continue; } diff --git a/options.toml b/options.toml index eedf288..ca15e12 100644 --- a/options.toml +++ b/options.toml @@ -30,7 +30,7 @@ default = false name = "Stack Pages" description = "Number of pages to allocate for the kernel stack." type = { type = "Integer", min = 1 } -default = 4 +default = 1 [tuning.appmemsize] name = "Application Memory Size" diff --git a/presets/stm32l4r5zi_def.toml b/presets/stm32l4r5zi_def.toml index 9edf803..e637fc4 100644 --- a/presets/stm32l4r5zi_def.toml +++ b/presets/stm32l4r5zi_def.toml @@ -11,7 +11,7 @@ OSIRIS_DEBUG_RUNTIMESYMBOLS = "false" # Tuning parameters OSIRIS_TUNING_ENABLEFPU = "false" -OSIRIS_STACKPAGES = "4" +OSIRIS_STACKPAGES = "1" OSIRIS_TUNING_APPSTACKSIZE = "2048" OSIRIS_TUNING_APPMEMSIZE = "8192" @@ -22,7 +22,4 @@ target = "thumbv7em-none-eabi" [target.'cfg(target_os = "none")'] rustflags = [ "-C", "link-arg=--entry=main", -] - -[target.thumbv7em-none-eabi] -rustflags = ["-C", "relocation-model=ropi-rwpi"] \ No newline at end of file +] \ No newline at end of file diff --git a/presets/testing_def.toml b/presets/testing_def.toml index 1ed804d..59b9c04 100644 --- a/presets/testing_def.toml +++ b/presets/testing_def.toml @@ -1,6 +1,7 @@ # This is the default configuration for running tests and verification. [env] +OSIRIS_STACKPAGES = "1" [build] target = "host-tuple" \ No newline at end of file diff --git a/src/idle.rs b/src/idle.rs index aa62b17..7c79b37 100644 --- a/src/idle.rs +++ b/src/idle.rs @@ -12,6 +12,6 @@ pub fn init() { fin: None, }; if let Err(e) = sched::create_thread(sched::task::KERNEL_TASK, &attrs) { - panic!("[Idle] Error: failed to create idle thread. Error: {e:?}"); + panic!("failed to create idle thread. Error: {e:?}"); } } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 21efb1d..ea3823b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,13 +33,12 @@ include!(concat!(env!("OUT_DIR"), "/syscalls_export.rs")); /// The `boot_info` pointer must be valid and point to a properly initialized `BootInfo` structure. #[unsafe(no_mangle)] pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { - hal::asm::disable_interrupts(); // Initialize basic hardware and the logging system. hal::Machine::init(); hal::Machine::bench_start(); if boot_info.is_null() || !boot_info.is_aligned() { - panic!("[Kernel] Error: boot_info pointer is null or unaligned."); + panic!("boot_info pointer is null or unaligned."); } // Safety: We trust the bootloader to provide a valid boot_info structure. @@ -50,31 +49,29 @@ pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { // Initialize the memory allocator. let kaddr_space = mem::init_memory(boot_info); - kprintln!("[Kernel] Memory initialized."); + kprintln!("Memory initialized."); - sched::init(kaddr_space); + if let Err(e) = sched::init(kaddr_space) { + panic!("failed to initialize scheduler. Error: {e:?}"); + } - kprintln!("[Kernel] Scheduler initialized."); + kprintln!("Scheduler initialized."); idle::init(); - kprintln!("[Kernel] Idle thread initialized."); + kprintln!("Idle thread initialized."); let (cyc, ns) = hal::Machine::bench_end(); kprintln!( - "[Osiris] Kernel init took {} cycles taking {} ms", - cyc, - ns as u32 / 1000000 + "Kernel init took {} cycles.", cyc ); // Start the init application. if let Err(e) = uspace::init_app(boot_info) { - panic!("[Kernel] Error: failed to start init application. Error: {e:?}"); + panic!("failed to start init application. Error: {e:?}"); } + + sched::enable(); - hal::asm::enable_interrupts(); - - loop { - hal::asm::nop!(); - } + loop {} } diff --git a/src/macros.rs b/src/macros.rs index 7c3a2e0..9d9d06f 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -30,7 +30,15 @@ macro_rules! kprintln { ($($arg:tt)*) => ({ use core::fmt::Write; use $crate::print::Printer; + let mut printer = Printer; + const MICROS_PER_SEC: u64 = 1000000; + let hz = $crate::time::mono_freq(); + let secs = $crate::time::mono_now() / hz; + let rem = $crate::time::mono_now() % hz; + let frac = (rem * MICROS_PER_SEC) / hz; + + write!(&mut printer, "[{}.{:06}] ", secs, frac).unwrap(); printer.write_fmt(format_args!($($arg)*)).unwrap(); printer.write_str("\n").unwrap(); }); diff --git a/src/mem.rs b/src/mem.rs index b720828..5a346bc 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -3,7 +3,7 @@ use crate::mem::pfa::PAGE_SIZE; use crate::mem::vmm::{AddressSpacelike, Backing, Perms, Region}; use crate::sync::spinlock::SpinLocked; -use crate::{BootInfo, sched, utils}; +use crate::BootInfo; use alloc::Allocator; use hal::mem::{PhysAddr, VirtAddr}; use core::ptr::NonNull; @@ -31,9 +31,13 @@ enum MemoryTypes { BadMemory = 5, } +unsafe extern "C" { + unsafe static __stack_top: u8; +} + /// The global memory allocator. -static GLOBAL_ALLOCATOR: SpinLocked = - SpinLocked::new(alloc::BestFitAllocator::new()); +static GLOBAL_ALLOCATOR: SpinLocked = + SpinLocked::new(alloc::bestfit::BestFitAllocator::new()); /// Initialize the memory allocator. /// @@ -41,27 +45,29 @@ static GLOBAL_ALLOCATOR: SpinLocked = /// /// Returns an error if the memory allocator could not be initialized. pub fn init_memory(boot_info: &BootInfo) -> vmm::AddressSpace { - if let Err(e) = pfa::init_pfa(PhysAddr::new(0x20000000)) { // TODO: Get this from the DeviceTree. - panic!("[Kernel] Error: failed to initialize PFA. Error: {e:?}"); + let stack_top = &raw const __stack_top as usize; + if let Err(e) = pfa::init_pfa(PhysAddr::new(stack_top)) { // TODO: Get this from the DeviceTree. + panic!("failed to initialize PFA. Error: {e:?}"); } // TODO: Configure. - let pgs = 4; + let pgs = 10; let mut kaddr_space = vmm::AddressSpace::new(pgs).unwrap_or_else(|e| { - panic!("[Kernel] Error: failed to create kernel address space."); + panic!("failed to create kernel address space. Error: {e:?}"); }); - let begin = kaddr_space.map(Region::new(VirtAddr::new(0), pgs * PAGE_SIZE, Backing::Zeroed, Perms::all())).unwrap_or_else(|e| { - panic!("[Kernel] Error: failed to map kernel address space."); + let begin = kaddr_space.map(Region::new(None, 2 * PAGE_SIZE, Backing::Zeroed, Perms::all())).unwrap_or_else(|e| { + panic!("failed to map kernel address space. Error: {e:?}"); }); - let mut allocator = GLOBAL_ALLOCATOR.lock(); - - let range = begin.as_usize()..(begin.as_usize() + pgs * PAGE_SIZE); + { + let mut allocator = GLOBAL_ALLOCATOR.lock(); - if let Err(e) = unsafe { allocator.add_range(range) } { - panic!("[Kernel] Error: failed to add range to allocator."); + let range = begin..(begin + pgs * PAGE_SIZE); + if let Err(e) = unsafe { allocator.add_range(&range) } { + panic!("failed to add range to allocator. Error: {e:?}"); + } } kaddr_space @@ -75,7 +81,7 @@ pub fn init_memory(boot_info: &BootInfo) -> vmm::AddressSpace { /// Returns a pointer to the allocated memory block if the allocation was successful, or `None` if the allocation failed. pub fn malloc(size: usize, align: usize) -> Option> { let mut allocator = GLOBAL_ALLOCATOR.lock(); - allocator.malloc(size, align).ok() + allocator.malloc(size, align, None).ok() } /// Free a memory block. diff --git a/src/mem/alloc.rs b/src/mem/alloc.rs index 415bccc..85d8830 100644 --- a/src/mem/alloc.rs +++ b/src/mem/alloc.rs @@ -1,9 +1,13 @@ //! This module provides a simple allocator. //! One implementation is the BestFitAllocator, which uses the best fit strategy. -use core::{ops::Range, ptr::NonNull}; +use core::ptr::NonNull; -use crate::{BUG_ON, utils}; +use hal::mem::PhysAddr; + +use crate::utils; + +pub mod bestfit; #[cfg(target_pointer_width = "64")] pub const MAX_ADDR: usize = 2_usize.pow(48); @@ -21,580 +25,7 @@ pub const MAX_ADDR: usize = usize::MAX; /// Each range added to the allocator must be valid for the whole lifetime of the allocator and must not overlap with any other range. /// The lifetime of any allocation is only valid as long as the allocator is valid. (A pointer must not be used after the allocator is dropped.) pub trait Allocator { - fn malloc(&mut self, size: usize, align: usize) -> Result, utils::KernelError>; + fn malloc(&mut self, size: usize, align: usize, request: Option) -> Result, utils::KernelError>; unsafe fn free(&mut self, ptr: NonNull, size: usize); } -/// The metadata that is before any block in the BestFitAllocator. -struct BestFitMeta { - /// The size of the block in bytes. - size: usize, - /// The pointer to the next free block. This is `None` if the block is allocated. - next: Option>, -} - -/// This is an allocator implementation that uses the best fit strategy. -/// That does mean, when we allocate a block, we try to find the smallest block that fits the requested size. -/// Blocks are stored in a singly linked list. The important part is that the linked list is stored in-line with the memory blocks. -/// This means that every block has a header that contains the size of the block and a pointer to the next block. -#[derive(Debug)] -pub struct BestFitAllocator { - /// Head of the free block list. - head: Option>, -} - -/// Implementation of the BestFitAllocator. -impl BestFitAllocator { - pub const MIN_RANGE_SIZE: usize = size_of::() + Self::align_up() + 1; - - /// Creates a new BestFitAllocator. - /// - /// Returns the new BestFitAllocator. - pub const fn new() -> Self { - Self { head: None } - } - - /// Adds a range of memory to the allocator. - /// - /// `range` - The range of memory to add. - /// - /// Returns `Ok(())` if the range was added successfully, otherwise an error. - /// - /// # Safety - /// - /// The range must be valid, 128bit aligned and must not overlapping with any other current or future range. - /// The range must also be at least as large as `MIN_RANGE_SIZE`. - /// Also the range must stay valid, for the whole lifetime of the allocator. Also the lifetime of any allocation is only valid as long as the allocator is valid. - pub unsafe fn add_range(&mut self, range: Range) -> Result<(), utils::KernelError> { - let ptr = range.start; - - // Check if the pointer is 128bit aligned. - if !ptr.is_multiple_of(align_of::()) { - return Err(utils::KernelError::InvalidAlign); - } - - if ptr == 0 { - return Err(utils::KernelError::InvalidAddress); - } - - debug_assert!(range.end > range.start); - debug_assert!(range.end - range.start > size_of::() + Self::align_up()); - debug_assert!(range.end <= isize::MAX as usize); - - // The user pointer is the pointer to the user memory. So we need to add the size of the meta data and possibly add padding. - let user_pointer = ptr + size_of::() + Self::align_up(); - - // Set the current head as the next block, so we can add the new block to the head. - let meta = BestFitMeta { - size: range.end - user_pointer, - next: self.head, - }; - - // Write the header to the memory. - unsafe { core::ptr::write(ptr as *mut BestFitMeta, meta) }; - - // Set the head to the new block. - self.head = Some(unsafe { NonNull::new_unchecked(ptr as *mut u8) }); - Ok(()) - } - - /// Calculates the padding required to align the block. Note: We only align to 128bit. - /// - /// Returns the padding in bytes. - const fn align_up() -> usize { - let meta = size_of::(); - let align = align_of::(); - // Calculate the padding required to align the block. - (align - (meta % align)) % align - } - - /// Selects the best fit block for the given size. - /// - /// `size` - The size of the block. - /// - /// Returns the control pointer to the block and the control pointer to the previous block. - fn select_block( - &mut self, - size: usize, - ) -> Result<(NonNull, Option>), utils::KernelError> { - let mut best_fit = Err(utils::KernelError::OutOfMemory); - let mut best_fit_size = usize::MAX; - - let mut current = self.head; - let mut prev = None; - - // Iterate over all blocks and find the best fit. - while let Some(ptr) = current { - // Get the metadata of the block. - let meta = unsafe { ptr.cast::().as_ref() }; - - // Check if the block is big enough and smaller than the current best fit. - if meta.size >= size && meta.size <= best_fit_size { - best_fit = Ok((ptr, prev)); - best_fit_size = meta.size; - } - - // Move to the next block. - prev = current; - current = meta.next; - } - - best_fit - } - - /// Calculates the user pointer from the control pointer. - /// - /// `ptr` - The control pointer. - /// - /// Returns the user pointer. - /// - /// # Safety - /// - /// The ptr must be a valid control pointer. Note: After the allocator which allocated the pointer is dropped, the control pointer is always considered invalid. - unsafe fn user_ptr(ptr: NonNull) -> NonNull { - debug_assert!( - (ptr.as_ptr() as usize) - <= isize::MAX as usize - size_of::() - Self::align_up() - ); - unsafe { ptr.byte_add(size_of::() + Self::align_up()) } - } - - /// Calculates the control pointer from the user pointer. - /// - /// `ptr` - The user pointer. - /// - /// Returns the control pointer. - /// - /// # Safety - /// - /// The ptr must be a valid user pointer. Note: After the allocator which allocated the pointer is dropped, the user pointer is always considered invalid. - unsafe fn control_ptr(ptr: NonNull) -> NonNull { - debug_assert!((ptr.as_ptr() as usize) > size_of::() + Self::align_up()); - unsafe { ptr.byte_sub(size_of::() + Self::align_up()) } - } -} - -/// Implementation of the Allocator trait for BestFitAllocator. -impl Allocator for BestFitAllocator { - /// Allocates a block of memory with the given size and alignment. Note: This function will always yield an invalid align for align > 128bit. - /// - /// `size` - The size of the block. - /// `align` - The alignment of the block. - /// - /// Returns the user pointer to the block if successful, otherwise an error. - fn malloc(&mut self, size: usize, align: usize) -> Result, utils::KernelError> { - // Check if the alignment is valid. - if align > align_of::() { - return Err(utils::KernelError::InvalidAlign); - } - - // Check if the size is valid. - if size == 0 { - return Err(utils::KernelError::InvalidSize); - } - - // For some cfg this warning is correct. But for others its not. - #[allow(clippy::absurd_extreme_comparisons)] - if size >= MAX_ADDR { - return Err(utils::KernelError::InvalidSize); - } - - // Align the size. - let aligned_size = super::align_up(size); - debug_assert!(aligned_size >= size); - debug_assert!(aligned_size <= isize::MAX as usize); - - // Find the best fit block. - let (split, block, prev) = match self.select_block(aligned_size) { - Ok((block, prev)) => { - // Get the metadata of the block. - let meta = unsafe { block.cast::().as_mut() }; - - // Calculate the amount of bytes until the beginning of the possibly next metadata. - let min = aligned_size.saturating_add(size_of::() + Self::align_up()); - - debug_assert!( - (block.as_ptr() as usize) - <= isize::MAX as usize - - meta.size - - size_of::() - - Self::align_up() - ); - - debug_assert!( - meta.size < isize::MAX as usize - size_of::() - Self::align_up() - ); - - // If the block is big enough to split. Then it also needs to be big enough to store the metadata + align of the next block. - if meta.size > min { - // Calculate the remaining size of the block and thus the next metadata. - let remaining_meta = BestFitMeta { - size: meta.size - min, - next: meta.next, - }; - - // Shrink the current block to the requested aligned_size + padding (which is not available to the user). - meta.size = aligned_size; - - // Calculate the pointer to the next metadata. - let ptr = unsafe { Self::user_ptr(block).byte_add(aligned_size) }; - - unsafe { - // Write the new metadata to the memory. - ptr.cast::().write(remaining_meta); - } - - // If there is a previous block, we insert the new block after it. Otherwise we set it as the new head. - if let Some(prev) = prev { - let prev_meta = unsafe { prev.cast::().as_mut() }; - prev_meta.next = Some(ptr); - } else { - self.head = Some(ptr); - } - - // The next block of an allocated block is always None. - meta.next = None; - - (true, block, prev) - } else { - (false, block, prev) - } - } - Err(_) => { - let (block, prev) = self.select_block(size)?; - (false, block, prev) - } - }; - - if !split { - // Get the metadata of the block. - let meta = unsafe { block.cast::().as_mut() }; - - if let Some(prev) = prev { - let prev_meta = unsafe { prev.cast::().as_mut() }; - // If there is a previous block, we remove the current block from the list. Ie. we set the next block of the previous block to the next block of the current block. - prev_meta.next = meta.next; - } else { - // If there is no previous block, we set the next block as the new head. - self.head = meta.next; - } - - // The next block of an allocated block is always None. - meta.next = None; - } - - // Return the user pointer. - Ok(unsafe { Self::user_ptr(block).cast() }) - } - - /// Frees a block of memory. - /// - /// `ptr` - The pointer to the block. - /// `size` - The size of the block. (This is used to check if the size of the block is correct.) - unsafe fn free(&mut self, ptr: NonNull, size: usize) { - let block = unsafe { Self::control_ptr(ptr.cast()) }; - let meta = unsafe { block.cast::().as_mut() }; - - // The next block of a free block is always the current head. We essentially insert the block at the beginning of the list. - meta.next = self.head; - - // Check if the size of the block is correct. - BUG_ON!(meta.size != super::align_up(size), "Invalid size in free()"); - - // Set the size of the block. - meta.size = size; - - // Set the block as the new head. - self.head = Some(block); - } -} - -// TESTING ------------------------------------------------------------------------------------------------------------ - -#[cfg(test)] -mod tests { - use super::*; - - fn verify_block(user_ptr: NonNull, size: usize, next: Option>) { - let control_ptr = unsafe { BestFitAllocator::control_ptr(user_ptr) }; - let meta = unsafe { control_ptr.cast::().as_ref() }; - - assert!(meta.size >= size); - assert_eq!(meta.next, next); - } - - fn verify_ptrs_not_overlaping(ptrs: &[(NonNull, usize)]) { - for (i, (ptr1, size1)) in ptrs.iter().enumerate() { - for (j, (ptr2, size2)) in ptrs.iter().enumerate() { - if i == j { - continue; - } - - let begin1 = ptr1.as_ptr() as usize; - let end1 = begin1 + size1; - let begin2 = ptr2.as_ptr() as usize; - let end2 = begin2 + size2; - - assert!(end1 <= begin2 || end2 <= begin1); - assert!(begin1 != begin2); - assert!(end1 != end2); - assert!(*size1 > 0); - assert!(*size2 > 0); - assert!(end1 > begin1); - assert!(end2 > begin2); - } - } - } - - fn alloc_range(length: usize) -> Range { - let alloc_range = std::alloc::Layout::from_size_align(length, align_of::()).unwrap(); - let ptr = unsafe { std::alloc::alloc(alloc_range) }; - ptr as usize..ptr as usize + length - } - - #[test] - fn allocate_one() { - let mut allocator = BestFitAllocator::new(); - - let range = alloc_range(4096); - unsafe { - allocator.add_range(range).unwrap(); - } - - let ptr = allocator.malloc(128, 1).unwrap(); - - verify_block(ptr, 128, None); - } - - #[test] - fn alloc_alot() { - let mut allocator = BestFitAllocator::new(); - const CNT: usize = 100; - const SIZE: usize = 128; - - let range = alloc_range(SIZE * CNT * 2); - unsafe { - allocator.add_range(range).unwrap(); - } - - let mut ptrs = Vec::new(); - for _ in 0..CNT { - let ptr = allocator.malloc(SIZE, 1).unwrap(); - verify_block(ptr, SIZE, None); - ptrs.push((ptr, SIZE)); - } - - verify_ptrs_not_overlaping(ptrs.as_slice()); - } - - #[test] - fn alloc_exact() { - let mut allocator = BestFitAllocator::new(); - const CNT: usize = 10; - const SIZE: usize = 128; - - let range = - alloc_range((SIZE + size_of::() + BestFitAllocator::align_up()) * CNT); - unsafe { - allocator.add_range(range).unwrap(); - } - - let mut ptrs = Vec::new(); - for _ in 0..CNT { - let ptr = allocator.malloc(SIZE, 1).unwrap(); - verify_block(ptr, SIZE, None); - ptrs.push((ptr, SIZE)); - } - - verify_ptrs_not_overlaping(ptrs.as_slice()); - } - - #[test] - fn alloc_oom() { - let mut allocator = BestFitAllocator::new(); - const CNT: usize = 10; - const SIZE: usize = 128; - - let range = - alloc_range((SIZE + size_of::() + BestFitAllocator::align_up()) * CNT - 1); - unsafe { - allocator.add_range(range).unwrap(); - } - - let mut ptrs = Vec::new(); - for _ in 0..CNT - 1 { - let ptr = allocator.malloc(SIZE, 1).unwrap(); - verify_block(ptr, SIZE, None); - ptrs.push(ptr); - } - - let ptr = allocator.malloc::(SIZE, 1); - assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); - } - - #[test] - fn alloc_no_oom_through_free() { - let mut allocator = BestFitAllocator::new(); - const SIZE: usize = 128; - - let range = alloc_range(SIZE + size_of::() + BestFitAllocator::align_up()); - unsafe { - allocator.add_range(range).unwrap(); - } - - let ptr = allocator.malloc(SIZE, 1).unwrap(); - verify_block(ptr, SIZE, None); - - unsafe { - allocator.free(ptr, SIZE); - } - - let ptr = allocator.malloc(SIZE, 1).unwrap(); - verify_block(ptr, SIZE, None); - } - - #[test] - fn multi_range_alloc() { - let mut allocator = BestFitAllocator::new(); - const CNT: usize = 10; - const SIZE: usize = 128; - - let mut ranges = Vec::new(); - for _ in 0..CNT { - let range = alloc_range(SIZE + size_of::() + BestFitAllocator::align_up()); - unsafe { - allocator.add_range(range.clone()).unwrap(); - } - ranges.push(range); - } - - let mut ptrs = Vec::new(); - for _ in 0..CNT { - let ptr = allocator.malloc(SIZE, 1).unwrap(); - verify_block(ptr, SIZE, None); - ptrs.push((ptr, SIZE)); - } - - verify_ptrs_not_overlaping(ptrs.as_slice()); - } - - #[test] - fn multi_range_no_oom_through_free() { - // This function allocates multiple ranges and then frees one of them randomly. And only then there is no oom. - let mut allocator = BestFitAllocator::new(); - - const CNT: usize = 10; - const SIZE: usize = 128; - - let mut ranges = Vec::new(); - for _ in 0..CNT { - let range = alloc_range(SIZE + size_of::() + BestFitAllocator::align_up()); - unsafe { - allocator.add_range(range.clone()).unwrap(); - } - ranges.push(range); - } - - let mut ptrs = Vec::new(); - - let ptr = allocator.malloc::(SIZE, 1).unwrap(); - - for _ in 0..CNT - 1 { - let ptr = allocator.malloc(SIZE, 1).unwrap(); - verify_block(ptr, SIZE, None); - ptrs.push((ptr, SIZE)); - } - - unsafe { - allocator.free(ptr, SIZE); - } - - let ptr = allocator.malloc(SIZE, 1).unwrap(); - ptrs.push((ptr, SIZE)); - - verify_ptrs_not_overlaping(ptrs.as_slice()); - } - - #[test] - fn multi_range_oom() { - // This function allocates multiple ranges and then frees one of them randomly. And only then there is no oom. - let mut allocator = BestFitAllocator::new(); - - const CNT: usize = 10; - const SIZE: usize = 128; - - let mut ranges = Vec::new(); - for _ in 0..CNT { - let range = alloc_range(SIZE + size_of::() + BestFitAllocator::align_up()); - unsafe { - allocator.add_range(range.clone()).unwrap(); - } - ranges.push(range); - } - - let mut ptrs = Vec::new(); - - for _ in 0..CNT { - let ptr = allocator.malloc(SIZE, 1).unwrap(); - verify_block(ptr, SIZE, None); - ptrs.push((ptr, SIZE)); - } - - let ptr = allocator.malloc::(SIZE, 1); - assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); - - verify_ptrs_not_overlaping(ptrs.as_slice()); - } -} - -// END TESTING -------------------------------------------------------------------------------------------------------- - -// VERIFICATION ------------------------------------------------------------------------------------------------------- -#[cfg(kani)] -mod verification { - use super::*; - use core::{alloc::Layout, ptr}; - - fn verify_block(user_ptr: NonNull, size: usize, next: Option>) { - let control_ptr = unsafe { BestFitAllocator::control_ptr(user_ptr) }; - let meta = unsafe { control_ptr.cast::().as_ref() }; - - assert!(meta.size >= size); - assert_eq!(meta.next, next); - } - - fn alloc_range(length: usize) -> Option> { - let alloc_range = std::alloc::Layout::from_size_align(length, align_of::()).unwrap(); - let ptr = unsafe { std::alloc::alloc(alloc_range) }; - - if ptr.is_null() || ((ptr as usize) >= isize::MAX as usize - length) { - None - } else { - Some(ptr as usize..ptr as usize + length) - } - } - - #[kani::proof] - #[kani::unwind(2)] - fn allocate_one() { - let mut allocator = BestFitAllocator::new(); - - let size: usize = kani::any(); - kani::assume(size < MAX_ADDR - size_of::() - BestFitAllocator::align_up()); - kani::assume(size > 0); - let larger_size: usize = kani::any_where(|&x| { - x > size + size_of::() + BestFitAllocator::align_up() && x < MAX_ADDR - }); - - if let Some(range) = alloc_range(larger_size) { - unsafe { - assert_eq!(allocator.add_range(range), Ok(())); - } - - let ptr = allocator.malloc(size, 1).unwrap(); - - verify_block(ptr, size, None); - } - } -} -// END VERIFICATION --------------------------------------------------------------------------------------------------- diff --git a/src/mem/alloc/bestfit.rs b/src/mem/alloc/bestfit.rs new file mode 100644 index 0000000..a14c251 --- /dev/null +++ b/src/mem/alloc/bestfit.rs @@ -0,0 +1,705 @@ +use core::{ops::Range, ptr::NonNull}; + +use hal::mem::PhysAddr; + +use crate::utils::{self, KernelError}; + +/// The metadata that is before any block in the BestFitAllocator. +struct BestFitMeta { + /// The size of the block in bytes. + size: usize, + /// The pointer to the next free block. This is `None` if the block is allocated. + next: Option>, +} + +/// This is an allocator implementation that uses the best fit strategy. +/// That does mean, when we allocate a block, we try to find the smallest block that fits the requested size. +/// Blocks are stored in a singly linked list. The important part is that the linked list is stored in-line with the memory blocks. +/// This means that every block has a header that contains the size of the block and a pointer to the next block. +#[derive(Debug)] +pub struct BestFitAllocator { + /// Head of the free block list. + head: Option>, +} + +/// Implementation of the BestFitAllocator. +impl BestFitAllocator { + pub const MIN_RANGE_SIZE: usize = size_of::() + Self::align_up() + 1; + + /// Creates a new BestFitAllocator. + /// + /// Returns the new BestFitAllocator. + pub const fn new() -> Self { + Self { head: None } + } + + /// Adds a range of memory to the allocator. + /// + /// `range` - The range of memory to add. + /// + /// Returns `Ok(())` if the range was added successfully, otherwise an error. + /// + /// # Safety + /// + /// The range must be valid, 128bit aligned and must not overlapping with any other current or future range. + /// The range must also be at least as large as `MIN_RANGE_SIZE`. + /// Also the range must stay valid, for the whole lifetime of the allocator. Also the lifetime of any allocation is only valid as long as the allocator is valid. + pub unsafe fn add_range(&mut self, range: &Range) -> Result<(), utils::KernelError> { + let ptr = range.start; + + // Check if the pointer is 128bit aligned. + if !ptr.is_multiple_of(align_of::()) { + return Err(utils::KernelError::InvalidAlign); + } + + debug_assert!(range.end > range.start); + debug_assert!(range.end.diff(range.start) > size_of::() + Self::align_up()); + debug_assert!(range.end.as_usize() <= isize::MAX as usize); + + // The user pointer is the pointer to the user memory. So we need to add the size of the meta data and possibly add padding. + let user_pointer = ptr + size_of::() + Self::align_up(); + + // Set the current head as the next block, so we can add the new block to the head. + let meta = BestFitMeta { + size: range.end.diff(user_pointer), + next: self.head, + }; + + // Write the header to the memory. + unsafe { core::ptr::write(ptr.as_mut_ptr::(), meta) }; + + // Set the head to the new block. + self.head = Some(unsafe { NonNull::new_unchecked(ptr.as_mut_ptr::()) }); + Ok(()) + } + + /// Calculates the padding required to align the block. Note: We only align to 128bit. + /// + /// Returns the padding in bytes. + const fn align_up() -> usize { + let meta = size_of::(); + let align = align_of::(); + // Calculate the padding required to align the block. + (align - (meta % align)) % align + } + + /// Selects the best fit block for the given size. + /// + /// `size` - The size of the block. + /// + /// Returns the control pointer to the block and the control pointer to the previous block. + fn select_block( + &mut self, + size: usize, + requested: Option, + ) -> Result<(NonNull, Option>), utils::KernelError> { + let mut best_fit = Err(utils::KernelError::OutOfMemory); + let mut best_fit_size = usize::MAX; + + let mut current = self.head; + let mut prev = None; + + if let Some(requested) = requested { + while let Some(ptr) = current { + // Get the metadata of the block. + let meta = unsafe { ptr.cast::().as_ref() }; + + if unsafe { Self::contains(meta, requested, size) } { + return Ok((ptr, prev)); + } + + // Move to the next block. + prev = current; + current = meta.next; + } + } + + // Iterate over all blocks and find the best fit. + while let Some(ptr) = current { + // Get the metadata of the block. + let meta = unsafe { ptr.cast::().as_ref() }; + + // Check if the block is big enough and smaller than the current best fit. + if meta.size >= size && meta.size <= best_fit_size { + best_fit = Ok((ptr, prev)); + best_fit_size = meta.size; + } + + // Move to the next block. + prev = current; + current = meta.next; + } + + best_fit + } + + /// Calculates the user pointer from the control pointer. + /// + /// `ptr` - The control pointer. + /// + /// Returns the user pointer. + /// + /// # Safety + /// + /// The ptr must be a valid control pointer. Note: After the allocator which allocated the pointer is dropped, the control pointer is always considered invalid. + unsafe fn user_ptr(ptr: NonNull) -> NonNull { + debug_assert!( + (ptr.as_ptr() as usize) + <= isize::MAX as usize - size_of::() - Self::align_up() + ); + unsafe { ptr.byte_add(size_of::() + Self::align_up()) } + } + + /// Calculates the control pointer from the user pointer. + /// + /// `ptr` - The user pointer. + /// + /// Returns the control pointer. + /// + /// # Safety + /// + /// The ptr must be a valid user pointer. Note: After the allocator which allocated the pointer is dropped, the user pointer is always considered invalid. + unsafe fn control_ptr(ptr: NonNull) -> NonNull { + debug_assert!((ptr.as_ptr() as usize) > size_of::() + Self::align_up()); + unsafe { ptr.byte_sub(size_of::() + Self::align_up()) } + } + + unsafe fn contains(meta: &BestFitMeta, target: PhysAddr, size: usize) -> bool { + let begin = unsafe { Self::user_ptr(NonNull::new_unchecked(meta as *const BestFitMeta as *mut u8)) }; + debug_assert!(size > 0); + + if target >= begin.into() { + if let Some(target) = target.checked_add(size) { + if target > (unsafe { begin.add(meta.size) }).into() { + return false; + } + } else { + return false; + } + return true; + } + false + } +} + +/// Implementation of the Allocator trait for BestFitAllocator. +impl super::Allocator for BestFitAllocator { + /// Allocates a block of memory with the given size and alignment. Note: This function will always yield an invalid align for align > 128bit. + /// + /// `size` - The size of the block. + /// `align` - The alignment of the block. + /// + /// Returns the user pointer to the block if successful, otherwise an error. + fn malloc(&mut self, size: usize, align: usize, request: Option) -> Result, utils::KernelError> { + // Check if the alignment is valid. + if align > align_of::() { + return Err(utils::KernelError::InvalidAlign); + } + + if let Some(request) = request { + if !request.is_multiple_of(align) { + return Err(utils::KernelError::InvalidAlign); + } + } + + // Check if the size is valid. + if size == 0 { + return Err(utils::KernelError::InvalidSize); + } + + // For some cfg this warning is correct. But for others its not. + #[allow(clippy::absurd_extreme_comparisons)] + if size >= super::MAX_ADDR { + return Err(utils::KernelError::InvalidSize); + } + + // Align the size. + let aligned_size = super::super::align_up(size); + debug_assert!(aligned_size >= size); + debug_assert!(aligned_size <= isize::MAX as usize); + + // Find the best fit block. + let (split, block, prev) = match self.select_block(aligned_size, request) { + Ok((block, prev)) => { + // Get the metadata of the block. + let meta = unsafe { block.cast::().as_mut() }; + + // If we requested a specific address. The size must be extended by the offset from block start to the requested address. + let aligned_size = if let Some(request) = request { + aligned_size + request.diff(unsafe { Self::user_ptr(block) }.into()) + } else { + aligned_size + }; + + // Calculate the amount of bytes until the beginning of the possibly next metadata. + let min = aligned_size.saturating_add(size_of::() + Self::align_up()); + + debug_assert!( + (block.as_ptr() as usize) + <= isize::MAX as usize + - meta.size + - size_of::() + - Self::align_up() + ); + + debug_assert!( + meta.size < isize::MAX as usize - size_of::() - Self::align_up() + ); + + // If the block is big enough to split. Then it also needs to be big enough to store the metadata + align of the next block. + if meta.size > min { + // Calculate the remaining size of the block and thus the next metadata. + let remaining_meta = BestFitMeta { + size: meta.size - min, + next: meta.next, + }; + + // Shrink the current block to the requested aligned_size + padding (which is not available to the user). + meta.size = aligned_size; + + // Calculate the pointer to the next metadata. + let ptr = unsafe { Self::user_ptr(block).byte_add(aligned_size) }; + + unsafe { + // Write the new metadata to the memory. + ptr.cast::().write(remaining_meta); + } + + // If there is a previous block, we insert the new block after it. Otherwise we set it as the new head. + if let Some(prev) = prev { + let prev_meta = unsafe { prev.cast::().as_mut() }; + prev_meta.next = Some(ptr); + } else { + self.head = Some(ptr); + } + + // The next block of an allocated block is always None. + meta.next = None; + + (true, block, prev) + } else { + (false, block, prev) + } + } + Err(_) => { + let (block, prev) = self.select_block(size, request)?; + (false, block, prev) + } + }; + + if !split { + // Get the metadata of the block. + let meta = unsafe { block.cast::().as_mut() }; + + if let Some(prev) = prev { + let prev_meta = unsafe { prev.cast::().as_mut() }; + // If there is a previous block, we remove the current block from the list. Ie. we set the next block of the previous block to the next block of the current block. + prev_meta.next = meta.next; + } else { + // If there is no previous block, we set the next block as the new head. + self.head = meta.next; + } + + // The next block of an allocated block is always None. + meta.next = None; + } + + if let Some(request) = request { + debug_assert!(unsafe { Self::contains(block.cast::().as_ref(), request, size) }); + } + + // Return the user pointer. + Ok(unsafe { Self::user_ptr(block).cast() }) + } + + /// Frees a block of memory. + /// + /// `ptr` - The pointer to the block. + /// `size` - The size of the block. (This is used to check if the size of the block is correct.) + unsafe fn free(&mut self, ptr: NonNull, size: usize) { + let block = unsafe { Self::control_ptr(ptr.cast()) }; + let meta = unsafe { block.cast::().as_mut() }; + + // The next block of a free block is always the current head. We essentially insert the block at the beginning of the list. + meta.next = self.head; + + // Check if the size of the block is correct. + BUG_ON!(meta.size != super::super::align_up(size), "Invalid size in free()"); + + // Set the size of the block. + meta.size = size; + + // Set the block as the new head. + self.head = Some(block); + } +} + +// TESTING ------------------------------------------------------------------------------------------------------------ + +#[cfg(test)] +mod tests { + use super::*; + use super::super::*; + + fn verify_block(user_ptr: NonNull, size: usize, next: Option>) { + let control_ptr = unsafe { BestFitAllocator::control_ptr(user_ptr) }; + let meta = unsafe { control_ptr.cast::().as_ref() }; + + assert!(meta.size >= size); + assert_eq!(meta.next, next); + } + + fn verify_ptrs_not_overlaping(ptrs: &[(NonNull, usize)]) { + for (i, (ptr1, size1)) in ptrs.iter().enumerate() { + for (j, (ptr2, size2)) in ptrs.iter().enumerate() { + if i == j { + continue; + } + + let begin1 = ptr1.as_ptr() as usize; + let end1 = begin1 + size1; + let begin2 = ptr2.as_ptr() as usize; + let end2 = begin2 + size2; + + assert!(end1 <= begin2 || end2 <= begin1); + assert!(begin1 != begin2); + assert!(end1 != end2); + assert!(*size1 > 0); + assert!(*size2 > 0); + assert!(end1 > begin1); + assert!(end2 > begin2); + } + } + } + + fn alloc_range(length: usize) -> Range { + let alloc_range = std::alloc::Layout::from_size_align(length, align_of::()).unwrap(); + let ptr = unsafe { std::alloc::alloc(alloc_range) }; + PhysAddr::new(ptr as usize)..PhysAddr::new(ptr as usize + length) + } + + #[test] + fn allocate_one() { + let mut allocator = BestFitAllocator::new(); + + let range = alloc_range(4096); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let ptr = allocator.malloc(128, 1, None).unwrap(); + + verify_block(ptr, 128, None); + } + + #[test] + fn alloc_request() { + let mut allocator = BestFitAllocator::new(); + + let range = alloc_range(4096); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let request = range.start + 128; + let ptr = allocator.malloc::(128, 1, Some(request)).unwrap(); + + // Check that the returned pointer contains the requested address. + let meta = unsafe { BestFitAllocator::control_ptr(ptr).cast::().as_ref() }; + assert!(unsafe { BestFitAllocator::contains(meta, request, 128) }); + } + + #[test] + fn alloc_request_to_big() { + let mut allocator = BestFitAllocator::new(); + + let range = alloc_range(4096); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let request = range.start + 4096; + let ptr = allocator.malloc::(128, 1, Some(request)); + + assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + } + + #[test] + fn alloc_request_not_aligned() { + let mut allocator = BestFitAllocator::new(); + + let range = alloc_range(4096); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let request = range.start + 127; + let ptr = allocator.malloc::(128, 8, Some(request)); + + assert!(ptr.is_err_and(|e| e == utils::KernelError::InvalidAlign)); + } + + #[test] + fn alloc_request_not_available() { + let mut allocator = BestFitAllocator::new(); + + let range = alloc_range(4096); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let request = range.start + 128; + let ptr = allocator.malloc::(128, 1, Some(request)).unwrap(); + verify_block(ptr, 128, None); + + let ptr = allocator.malloc::(128, 1, Some(request)); + assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + } + + #[test] + fn alloc_request_out_of_range() { + let mut allocator = BestFitAllocator::new(); + + let range = alloc_range(4096); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let request = range.end + 128; + let ptr = allocator.malloc::(128, 1, Some(request)); + + assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + } + + #[test] + fn alloc_alot() { + let mut allocator = BestFitAllocator::new(); + const CNT: usize = 100; + const SIZE: usize = 128; + + let range = alloc_range(SIZE * CNT * 2); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let mut ptrs = Vec::new(); + for _ in 0..CNT { + let ptr = allocator.malloc(SIZE, 1, None).unwrap(); + verify_block(ptr, SIZE, None); + ptrs.push((ptr, SIZE)); + } + + verify_ptrs_not_overlaping(ptrs.as_slice()); + } + + #[test] + fn alloc_exact() { + let mut allocator = BestFitAllocator::new(); + const CNT: usize = 10; + const SIZE: usize = 128; + + let range = + alloc_range((SIZE + size_of::() + BestFitAllocator::align_up()) * CNT); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let mut ptrs = Vec::new(); + for _ in 0..CNT { + let ptr = allocator.malloc(SIZE, 1, None).unwrap(); + verify_block(ptr, SIZE, None); + ptrs.push((ptr, SIZE)); + } + + verify_ptrs_not_overlaping(ptrs.as_slice()); + } + + #[test] + fn alloc_oom() { + let mut allocator = BestFitAllocator::new(); + const CNT: usize = 10; + const SIZE: usize = 128; + + let range = + alloc_range((SIZE + size_of::() + BestFitAllocator::align_up()) * CNT - 1); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let mut ptrs = Vec::new(); + for _ in 0..CNT - 1 { + let ptr = allocator.malloc(SIZE, 1, None).unwrap(); + verify_block(ptr, SIZE, None); + ptrs.push(ptr); + } + + let ptr = allocator.malloc::(SIZE, 1, None); + assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + } + + #[test] + fn alloc_no_oom_through_free() { + let mut allocator = BestFitAllocator::new(); + const SIZE: usize = 128; + + let range = alloc_range(SIZE + size_of::() + BestFitAllocator::align_up()); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let ptr = allocator.malloc(SIZE, 1, None).unwrap(); + verify_block(ptr, SIZE, None); + + unsafe { + allocator.free(ptr, SIZE); + } + + let ptr = allocator.malloc(SIZE, 1, None).unwrap(); + verify_block(ptr, SIZE, None); + } + + #[test] + fn multi_range_alloc() { + let mut allocator = BestFitAllocator::new(); + const CNT: usize = 10; + const SIZE: usize = 128; + + let mut ranges = Vec::new(); + for _ in 0..CNT { + let range = alloc_range(SIZE + size_of::() + BestFitAllocator::align_up()); + unsafe { + allocator.add_range(&range).unwrap(); + } + ranges.push(range); + } + + let mut ptrs = Vec::new(); + for _ in 0..CNT { + let ptr = allocator.malloc(SIZE, 1, None).unwrap(); + verify_block(ptr, SIZE, None); + ptrs.push((ptr, SIZE)); + } + + verify_ptrs_not_overlaping(ptrs.as_slice()); + } + + #[test] + fn multi_range_no_oom_through_free() { + // This function allocates multiple ranges and then frees one of them randomly. And only then there is no oom. + let mut allocator = BestFitAllocator::new(); + + const CNT: usize = 10; + const SIZE: usize = 128; + + let mut ranges = Vec::new(); + for _ in 0..CNT { + let range = alloc_range(SIZE + size_of::() + BestFitAllocator::align_up()); + unsafe { + allocator.add_range(&range).unwrap(); + } + ranges.push(range); + } + + let mut ptrs = Vec::new(); + + let ptr = allocator.malloc::(SIZE, 1, None).unwrap(); + + for _ in 0..CNT - 1 { + let ptr = allocator.malloc(SIZE, 1, None).unwrap(); + verify_block(ptr, SIZE, None); + ptrs.push((ptr, SIZE)); + } + + unsafe { + allocator.free(ptr, SIZE); + } + + let ptr = allocator.malloc(SIZE, 1, None).unwrap(); + ptrs.push((ptr, SIZE)); + + verify_ptrs_not_overlaping(ptrs.as_slice()); + } + + #[test] + fn multi_range_oom() { + // This function allocates multiple ranges and then frees one of them randomly. And only then there is no oom. + let mut allocator = BestFitAllocator::new(); + + const CNT: usize = 10; + const SIZE: usize = 128; + + let mut ranges = Vec::new(); + for _ in 0..CNT { + let range = alloc_range(SIZE + size_of::() + BestFitAllocator::align_up()); + unsafe { + allocator.add_range(&range).unwrap(); + } + ranges.push(range); + } + + let mut ptrs = Vec::new(); + + for _ in 0..CNT { + let ptr = allocator.malloc(SIZE, 1, None).unwrap(); + verify_block(ptr, SIZE, None); + ptrs.push((ptr, SIZE)); + } + + let ptr = allocator.malloc::(SIZE, 1, None); + assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + + verify_ptrs_not_overlaping(ptrs.as_slice()); + } +} + +// END TESTING -------------------------------------------------------------------------------------------------------- + +// VERIFICATION ------------------------------------------------------------------------------------------------------- +#[cfg(kani)] +mod verification { + use super::*; + use core::{alloc::Layout, ptr}; + + fn verify_block(user_ptr: NonNull, size: usize, next: Option>) { + let control_ptr = unsafe { BestFitAllocator::control_ptr(user_ptr) }; + let meta = unsafe { control_ptr.cast::().as_ref() }; + + assert!(meta.size >= size); + assert_eq!(meta.next, next); + } + + fn alloc_range(length: usize) -> Option> { + let alloc_range = std::alloc::Layout::from_size_align(length, align_of::()).unwrap(); + let ptr = unsafe { std::alloc::alloc(alloc_range) }; + + if ptr.is_null() || ((ptr as usize) >= isize::MAX as usize - length) { + None + } else { + Some(ptr as usize..ptr as usize + length) + } + } + + #[kani::proof] + #[kani::unwind(2)] + fn allocate_one() { + let mut allocator = BestFitAllocator::new(); + + let size: usize = kani::any(); + kani::assume(size < MAX_ADDR - size_of::() - BestFitAllocator::align_up()); + kani::assume(size > 0); + let larger_size: usize = kani::any_where(|&x| { + x > size + size_of::() + BestFitAllocator::align_up() && x < MAX_ADDR + }); + + if let Some(range) = alloc_range(larger_size) { + unsafe { + assert_eq!(allocator.add_range(range), Ok(())); + } + + let ptr = allocator.malloc(size, 1, None).unwrap(); + + verify_block(ptr, size, None); + } + } +} +// END VERIFICATION --------------------------------------------------------------------------------------------------- diff --git a/src/mem/pfa/bitset.rs b/src/mem/pfa/bitset.rs index 23ef7f7..391ab9a 100644 --- a/src/mem/pfa/bitset.rs +++ b/src/mem/pfa/bitset.rs @@ -43,7 +43,16 @@ impl super::Allocator for Allocator { return Err(KernelError::InvalidAlign); } - let ptr = NonNull::new(addr.as_mut_ptr::()).ok_or(KernelError::InvalidAddress)?; + let ptr = NonNull::new(addr.as_mut_ptr::()).ok_or(KernelError::InvalidAddress(addr))?; + // Align this up to PAGE_SIZE + let begin = addr + size_of::(); + let begin = if begin.is_multiple_of(super::PAGE_SIZE) { + begin + } else { + PhysAddr::new((begin.as_usize() + super::PAGE_SIZE - 1) & !(super::PAGE_SIZE - 1)) + }; + // TODO: Subtract the needed pages from the available + unsafe { core::ptr::write(ptr.as_ptr(), Self::new(begin).ok_or(KernelError::InvalidAddress(begin))?) }; // Safety: Ptr is properly aligned and non-null. The validity of the memory at that address is valid by the call contract. Ok(Pin::new(unsafe { boxed::Box::from_raw(ptr) })) @@ -119,7 +128,7 @@ impl super::Allocator for Allocator { self.l1[idx] &= !((!0usize).unbounded_shl((Self::BITS_PER_WORD - rem) as u32) >> skip); if len <= rem { - return Some(PhysAddr::new(start)); + return Some(self.begin + (start * super::PAGE_SIZE)); } len -= rem; @@ -201,7 +210,7 @@ mod tests { let pre = allocator.l1.clone(); let addr = super::super::Allocator::alloc(&mut allocator, ALLOC_SIZE).unwrap(); - let idx = addr / super::super::PAGE_SIZE; + let idx = addr.as_usize() / super::super::PAGE_SIZE; // Check that the bits in returned addresses is all ones in pre. for i in 0..ALLOC_SIZE { diff --git a/src/mem/vmm.rs b/src/mem/vmm.rs index 1f8aba9..ee86228 100644 --- a/src/mem/vmm.rs +++ b/src/mem/vmm.rs @@ -1,5 +1,3 @@ -use core::ops::Range; - use hal::mem::{PhysAddr, VirtAddr}; use crate::{utils::KernelError}; @@ -26,30 +24,39 @@ pub enum Backing { #[derive(Clone)] pub struct Region { - range: Range, + start: Option, + len: usize, backing: Backing, perms: Perms, } impl Region { - pub fn new(start: VirtAddr, len: usize, backing: Backing, perms: Perms) -> Self { + /// Creates a new region. + /// + /// - `start` is the starting virtual address of the region. If `None`, the system will choose a suitable address. + /// - `len` is the length of the region in bytes. + /// - `backing` is the backing type of the region, which determines how the region is initialized and where its contents come from. + /// - `perms` is the permissions of the region, which determines how the region can be accessed. + /// + pub fn new(start: Option, len: usize, backing: Backing, perms: Perms) -> Self { Self { - range: start..start.saturating_add(len), + start, + len, backing, perms, } } pub fn start(&self) -> VirtAddr { - self.range.start + self.start.unwrap_or_else(|| VirtAddr::new(0)) } pub fn len(&self) -> usize { - self.range.end.saturating_sub(self.range.start.into()).into() + self.len } pub fn contains(&self, addr: VirtAddr) -> bool { - self.range.contains(&addr) + self.start().saturating_add(self.len()) > addr && addr >= self.start() } } diff --git a/src/mem/vmm/nommu.rs b/src/mem/vmm/nommu.rs index ff561b2..8927471 100644 --- a/src/mem/vmm/nommu.rs +++ b/src/mem/vmm/nommu.rs @@ -4,6 +4,7 @@ use hal::mem::{PhysAddr, VirtAddr}; use crate::{ mem::{ + alloc::{Allocator, bestfit}, pfa, vmm, }, utils::KernelError, @@ -12,53 +13,45 @@ use crate::{ pub struct AddressSpace { begin: PhysAddr, end: PhysAddr, + allocator: bestfit::BestFitAllocator, } impl vmm::AddressSpacelike for AddressSpace { - fn new(size: usize) -> Result { - let pg_cnt = size.div_ceil(pfa::PAGE_SIZE); - let begin = pfa::alloc_page(pg_cnt).ok_or(KernelError::OutOfMemory)?; - let end = begin.checked_add(pg_cnt * pfa::PAGE_SIZE).ok_or(KernelError::OutOfMemory)?; + fn new(pgs: usize) -> Result { + let begin = pfa::alloc_page(pgs).ok_or(KernelError::OutOfMemory)?; + let end = begin + .checked_add(pgs * pfa::PAGE_SIZE) + .ok_or(KernelError::OutOfMemory)?; + + let mut allocator = bestfit::BestFitAllocator::new(); + unsafe { allocator.add_range(&(begin..end))? }; Ok(Self { begin, end, + allocator, }) } fn map(&mut self, region: vmm::Region) -> Result { - // Do both checks in one statement. - let phys = self.virt_to_phys(region.start()).and_then(|phys| { - if phys > self.end { - None - } else { - Some(phys) - } - }).ok_or(KernelError::InvalidArgument)?; + let req = region.start.and_then(|virt| self.virt_to_phys(virt)); + // TODO: per page align + let align = core::mem::align_of::(); + let start = self.allocator.malloc::(region.len(), align, req)?; match region.backing { vmm::Backing::Anon(phys) => { unsafe { - copy_nonoverlapping( - phys.as_mut_ptr::(), - phys.as_mut_ptr::(), - region.len(), - ) + copy_nonoverlapping(phys.as_mut_ptr::(), start.as_ptr(), region.len()) }; - }, + } vmm::Backing::Zeroed => { - unsafe { - core::ptr::write_bytes( - phys.as_mut_ptr::(), - 0, - region.len(), - ) - }; - }, - vmm::Backing::Uninit => {}, + unsafe { core::ptr::write_bytes(start.as_ptr(), 0, region.len()) }; + } + vmm::Backing::Uninit => {} } - Ok(phys) + Ok(start.into()) } fn unmap(&mut self, _region: &vmm::Region) -> Result<(), KernelError> { @@ -70,11 +63,12 @@ impl vmm::AddressSpacelike for AddressSpace { } fn phys_to_virt(&self, addr: PhysAddr) -> Option { - addr.checked_sub(self.begin.as_usize()).map(|phys| VirtAddr::new(phys.as_usize())) + addr.checked_sub(self.begin.as_usize()) + .map(|phys| VirtAddr::new(phys.as_usize())) } fn virt_to_phys(&self, addr: VirtAddr) -> Option { - self.begin.checked_add(addr.as_usize()) + self.begin.checked_add(addr.as_usize()) } fn end(&self) -> VirtAddr { diff --git a/src/sched.rs b/src/sched.rs index c9acae7..510ff80 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -2,15 +2,16 @@ mod dispch; pub mod rt; +pub mod rr; pub mod task; pub mod thread; -use core::ffi::c_void; +use core::{ffi::c_void, sync::atomic::{AtomicBool, Ordering}}; use hal::Schedable; use crate::{ - mem, sync::spinlock::SpinLocked, types::{ + mem, sync::{atomic::AtomicU64, spinlock::SpinLocked}, time::{self, tick}, types::{ array::IndexMap, rbtree::RbTree, traits::{Get, GetMut}, @@ -23,18 +24,21 @@ type TaskMap = IndexMap; static SCHED: SpinLocked> = SpinLocked::new(Scheduler::new()); +static DISABLED: AtomicBool = AtomicBool::new(true); +static NEXT_TICK: AtomicU64 = AtomicU64::new(0); + pub struct Scheduler { threads: ThreadMap, tasks: TaskMap, id_gen: usize, rt_scheduler: rt::Scheduler, + rr_scheduler: rr::Scheduler, wakeup: RbTree, - current: thread::UId, + current: Option, last_tick: u64, - next_tick: u64, } impl Scheduler { @@ -44,18 +48,20 @@ impl Scheduler { tasks: IndexMap::new(), id_gen: 1, rt_scheduler: rt::Scheduler::new(), + rr_scheduler: rr::Scheduler::new(), wakeup: RbTree::new(), - current: thread::IDLE_THREAD, + current: None, last_tick: 0, - next_tick: 0, } } fn land(&mut self, ctx: *mut c_void) -> Result<(), KernelError> { - // A thread must not disappear while it is running. - let current = self.threads.get_mut(self.current).ok_or(KernelError::InvalidArgument)?; - // The context pointer must not be bogus after a sched_enter. - current.save_ctx(ctx) + if let Some(current) = self.current { + let thread = self.threads.get_mut(current).ok_or(KernelError::InvalidArgument)?; + return thread.save_ctx(ctx); + } + + Ok(()) } pub fn enqueue(&mut self, uid: thread::UId) -> Result<(), KernelError> { @@ -65,8 +71,12 @@ impl Scheduler { let mut view = ViewMut::>::new(&mut self.threads); self.rt_scheduler.enqueue(uid, &mut view); + } else { + self.rr_scheduler.enqueue(uid, &mut self.threads)?; } + // A new thread was added -> Trigger a reschedule. + NEXT_TICK.store(tick(), Ordering::Release); Ok(()) } @@ -82,19 +92,40 @@ impl Scheduler { let mut view = rt::ServerView::::new(&mut self.threads); // If this is not a real-time thread, this will just do nothing. self.rt_scheduler.put(old, dt, &mut view); + // If this is not a round-robin thread, this will just do nothing. + self.rr_scheduler.put(old, dt); // TODO: thread is still enqueued. Dequeue if blocked or sleeping and put to the respective tree/list. // If it exited remove it completely. } let mut view = rt::ServerView::::new(&mut self.threads); - let (new, budget) = self.rt_scheduler.pick(now, &mut view)?; + + let (new, budget) = if let Some((new, budget)) = self.rt_scheduler.pick(now, &mut view) { + (new, budget) + } else if let Some((new, budget)) = self.rr_scheduler.pick(&mut self.threads) { + (new, budget) + } else { + // No thread to run. Run the idle thread. + (thread::IDLE_THREAD, u64::MAX) + }; let ctx = self.threads.get(new)?.ctx(); let task = self.tasks.get_mut(self.threads.get(new)?.task_id())?; - self.current = new; - self.next_tick = now + budget; + self.current = Some(new); + + // Only store next_tick if now + budget is smaller than the current next tick. + let next_tick = now + budget; + let mut old_tick = NEXT_TICK.load(Ordering::Acquire); + + while NEXT_TICK.compare_exchange(old_tick, next_tick, Ordering::Release, Ordering::Acquire).is_err() { + old_tick = NEXT_TICK.load(Ordering::Acquire); + if next_tick >= old_tick { + break; + } + } + Some((ctx, task)) } @@ -110,7 +141,7 @@ impl Scheduler { let uid = task::UId::new(self.id_gen).ok_or(KernelError::InvalidArgument)?; self.id_gen += 1; - self.tasks.insert(&uid, task::Task::new(uid, task)?); + self.tasks.insert(&uid, task::Task::new(uid, task)?)?; Ok(uid) } @@ -118,20 +149,18 @@ impl Scheduler { let task = self.tasks.get_mut(task).ok_or(KernelError::InvalidArgument)?; let thread = task.create_thread(self.id_gen, attrs)?; let uid = thread.uid(); + + self.threads.insert(&uid, thread)?; + self.id_gen += 1; Ok(uid) } } -pub fn init(kaddr_space: mem::vmm::AddressSpace) { +pub fn init(kaddr_space: mem::vmm::AddressSpace) -> Result<(), KernelError> { let mut sched = SCHED.lock(); let uid = task::KERNEL_TASK; - sched.tasks.insert(&uid, task::Task::from_addr_space(uid, kaddr_space)); -} - -pub fn needs_reschedule(now: u64) -> bool { - let sched = SCHED.lock(); - now >= sched.next_tick + sched.tasks.insert(&uid, task::Task::from_addr_space(uid, kaddr_space)?) } pub fn create_task(attrs: &task::Attributes) -> Result { @@ -139,7 +168,30 @@ pub fn create_task(attrs: &task::Attributes) -> Result { } pub fn create_thread(task: task::UId, attrs: &thread::Attributes) -> Result { - SCHED.lock().create_thread(task, attrs) + let mut sched = SCHED.lock(); + sched.create_thread(task, attrs) +} + +pub fn enqueue(uid: thread::UId) -> Result<(), KernelError> { + SCHED.lock().enqueue(uid) +} + +pub fn needs_reschedule(now: u64) -> bool { + if DISABLED.load(Ordering::Acquire) { + return false; + } + + now >= NEXT_TICK.load(Ordering::Acquire) +} + +#[inline] +pub fn disable() { + DISABLED.store(true, Ordering::Release); +} + +#[inline] +pub fn enable() { + DISABLED.store(false, Ordering::Release); } /// Reschedule the tasks. @@ -156,23 +208,25 @@ pub extern "C" fn sched_enter(ctx: *mut c_void) -> *mut c_void { let old = sched.current; if sched.land(ctx).is_err() { - if sched.current == thread::IDLE_THREAD { - BUG!("failed to land the idle thread. something is horribly broken."); - } - - // If we cannot reasonably land. We dequeue the thread. - sched.dequeue(old); - // TODO: Warn - sched.current = thread::IDLE_THREAD; - broken = true; + sched.current.inspect(|uid| { + if *uid == thread::IDLE_THREAD { + BUG!("failed to land the idle thread. something is horribly broken."); + } + + // If we cannot reasonably land. We dequeue the thread. + sched.dequeue(*uid); + // TODO: Warn + sched.current = None; + broken = true; + }); } - let now = 0; - - if let Some((ctx, task)) = sched.do_sched(now, Some(old)) { - if task.id != old.owner() { - dispch::prepare(task); - } + if let Some((ctx, task)) = sched.do_sched(time::tick(), old) { + if let Some(old) = old + && task.id != old.owner() { + dispch::prepare(task); + } + ctx } else if broken { BUG!("failed to reschedule after a failed landing. something is horribly broken."); diff --git a/src/sched/rr.rs b/src/sched/rr.rs new file mode 100644 index 0000000..c4613f2 --- /dev/null +++ b/src/sched/rr.rs @@ -0,0 +1,48 @@ +use crate::{ + sched::{ + thread::{self}, + }, + types::{ + list::List, + }, +}; + +pub struct Scheduler { + queue: List, + + current: Option, + current_left: u64, + quantum: u64, +} + +impl Scheduler { + pub const fn new() -> Self { + // TODO: Make quantum configurable. + Self { queue: List::new(), current: None, current_left: 0, quantum: 1000 } + } + + pub fn enqueue(&mut self, uid: thread::UId, storage: &mut super::ThreadMap) -> Result<(), crate::utils::KernelError> { + self.queue.push_back(uid, storage).map_err(|_| crate::utils::KernelError::InvalidArgument) + } + + pub fn put(&mut self, uid: thread::UId, dt: u64) { + if let Some(current) = self.current { + if current == uid { + self.current_left = self.current_left.saturating_sub(dt); + } + } + } + + pub fn pick(&mut self, storage: &mut super::ThreadMap) -> Option<(thread::UId, u64)> { + if self.current_left == 0 { + if let Some(current) = self.current { + self.queue.push_back(current, storage); + } + + self.current = self.queue.pop_front(storage).ok().flatten(); + self.current_left = self.quantum; + } + + self.current.map(|id| (id, self.current_left)) + } +} diff --git a/src/sched/task.rs b/src/sched/task.rs index 06bb8d9..9527f81 100644 --- a/src/sched/task.rs +++ b/src/sched/task.rs @@ -69,7 +69,10 @@ impl Task { // TODO: On MMU systems, the resrv_pgs attribute will be ignored, as memory will not be reserved. let resrv_pgs = attrs.resrv_pgs.ok_or(KernelError::OutOfMemory)?; let address_space = mem::vmm::AddressSpace::new(resrv_pgs.get())?; + Self::from_addr_space(id, address_space) + } + pub fn from_addr_space(id: UId, address_space: mem::vmm::AddressSpace) -> Result { Ok(Self { id, address_space, @@ -77,14 +80,6 @@ impl Task { }) } - pub fn from_addr_space(id: UId, address_space: mem::vmm::AddressSpace) -> Self { - Self { - id, - address_space, - tid_cntr: 0, - } - } - fn allocate_tid(&mut self) -> sched::thread::Id { let tid = self.tid_cntr; self.tid_cntr += 1; @@ -97,9 +92,8 @@ impl Task { attrs: &thread::Attributes, ) -> Result { let size = DEFAULTS.stack_pages * mem::pfa::PAGE_SIZE; - let start = self.address_space.end().saturating_sub(size); let region = mem::vmm::Region::new( - start, + None, size, mem::vmm::Backing::Uninit, mem::vmm::Perms::Read | mem::vmm::Perms::Write, diff --git a/src/sched/thread.rs b/src/sched/thread.rs index 63c608a..66f4ec5 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -7,6 +7,7 @@ use hal::stack::{FinFn, Stacklike}; use macros::TaggedLinks; use crate::sched::task::{self, KERNEL_TASK}; +use crate::types::list; use crate::{types::{rbtree::{self, Compare}, traits::{Project, ToIndex}}, utils::KernelError}; pub const IDLE_THREAD: UId = UId { @@ -186,6 +187,9 @@ pub struct WakupTree; #[derive(Debug, Clone, Copy)] pub struct RtTree; +#[derive(Debug, Clone, Copy)] +pub struct RRList; + pub struct Attributes { pub entry: EntryFn, pub fin: Option, @@ -204,6 +208,9 @@ pub struct Thread { /// Wakup tree links for the thread. #[rbtree(tag = WakupTree, idx = UId)] _wakeup_links: rbtree::Links, + + #[list(tag = RRList, idx = UId)] + rr_links: list::Links, } #[allow(dead_code)] @@ -222,6 +229,7 @@ impl Thread { uid, rt_server: None, _wakeup_links: rbtree::Links::new(), + rr_links: list::Links::new(), } } diff --git a/src/sync/atomic.rs b/src/sync/atomic.rs index dec9f98..e3a4bde 100644 --- a/src/sync/atomic.rs +++ b/src/sync/atomic.rs @@ -10,10 +10,26 @@ compile_error!( "The `atomic-cas` feature requires the target to have atomic operations on at least 8-bit integers." ); -// ----------------------------AtomicU8---------------------------- -#[cfg(any(feature = "no-atomic-cas", not(target_has_atomic = "64")))] +#[allow(unused_imports)] pub use core::sync::atomic::Ordering; +#[inline(always)] +pub fn irq_free(f: impl FnOnce() -> T) -> T { + let enabled = hal::asm::are_interrupts_enabled(); + if enabled { + hal::asm::disable_interrupts(); + } + + let result = f(); + + if enabled { + hal::asm::enable_interrupts(); + } + + result +} + +// ----------------------------AtomicU8---------------------------- #[cfg(any(feature = "no-atomic-cas", not(target_has_atomic = "64")))] use core::cell::UnsafeCell; @@ -130,25 +146,9 @@ impl AtomicU64 { } } - #[inline(always)] - fn with_interrupts_disabled(f: impl FnOnce() -> T) -> T { - let were_enabled = hal::asm::are_interrupts_enabled(); - if were_enabled { - hal::asm::disable_interrupts(); - } - - let result = f(); - - if were_enabled { - hal::asm::enable_interrupts(); - } - - result - } - /// Loads the value. pub fn load(&self, _: Ordering) -> u64 { - Self::with_interrupts_disabled(|| { + irq_free(|| { // SAFETY: Interrupts are disabled, so this read is exclusive with writes. unsafe { *self.value.get() } }) @@ -156,7 +156,7 @@ impl AtomicU64 { /// Stores a value. pub fn store(&self, value: u64, _: Ordering) { - Self::with_interrupts_disabled(|| { + irq_free(|| { // SAFETY: Interrupts are disabled, so this write is exclusive with other access. unsafe { *self.value.get() = value; @@ -172,7 +172,7 @@ impl AtomicU64 { _: Ordering, _: Ordering, ) -> Result { - Self::with_interrupts_disabled(|| { + irq_free(|| { // SAFETY: Interrupts are disabled, so this read-modify-write is exclusive. unsafe { let value = self.value.get(); @@ -188,7 +188,7 @@ impl AtomicU64 { /// Fetches and adds, returning the previous value. pub fn fetch_add(&self, value: u64, _: Ordering) -> u64 { - Self::with_interrupts_disabled(|| { + irq_free(|| { // SAFETY: Interrupts are disabled, so this read-modify-write is exclusive. unsafe { let ptr = self.value.get(); @@ -204,7 +204,7 @@ impl AtomicU64 { where F: FnMut(u64) -> Option, { - Self::with_interrupts_disabled(|| { + irq_free(|| { // SAFETY: Interrupts are disabled, so this read-modify-write is exclusive. unsafe { let ptr = self.value.get(); diff --git a/src/time.rs b/src/time.rs index 25c73eb..44cc3a5 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,31 +1,29 @@ -use core::sync::atomic::Ordering; +use hal::Machinelike; -use crate::sched; -use crate::sync::atomic::AtomicU64; +use crate::{sched, sync}; -// This variable is only allowed to be modified by the systick handler. -static TIME: AtomicU64 = AtomicU64::new(0); +static TICKS: sync::atomic::AtomicU64 = sync::atomic::AtomicU64::new(0); -fn tick() { - TIME.fetch_add(1, Ordering::Release); +pub fn tick() -> u64 { + TICKS.load(sync::atomic::Ordering::Acquire) } -/* - * Returns the current time in milliseconds after boot. - * - */ -#[allow(dead_code)] -pub fn time() -> u64 { - TIME.load(Ordering::Acquire) +pub fn mono_now() -> u64 { + // TODO: This will break on SMP systems without native u64 atomic store. + sync::atomic::irq_free(|| hal::Machine::monotonic_now() ) +} + +pub fn mono_freq() -> u64 { + hal::Machine::monotonic_freq() } /// cbindgen:ignore /// cbindgen:no-export #[unsafe(no_mangle)] pub extern "C" fn systick_hndlr() { - let time = TIME.fetch_add(1, Ordering::Release) + 1; + let tick = TICKS.fetch_add(1, sync::atomic::Ordering::Release) + 1; - if sched::needs_reschedule(time) { + if sched::needs_reschedule(tick) { sched::reschedule(); } } diff --git a/src/types.rs b/src/types.rs index 26df432..746bf6d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -4,5 +4,6 @@ pub mod array; pub mod heap; pub mod pool; pub mod rbtree; +pub mod list; pub mod traits; pub mod view; \ No newline at end of file diff --git a/src/types/list.rs b/src/types/list.rs new file mode 100644 index 0000000..d3c9779 --- /dev/null +++ b/src/types/list.rs @@ -0,0 +1,302 @@ +use core::marker::PhantomData; + +use super::traits::{Get, GetMut}; + +#[allow(dead_code)] +pub struct List { + head: Option, + tail: Option, + len: usize, + _tag: PhantomData, +} + +#[allow(dead_code)] +pub trait Linkable { + fn links(&self) -> &Links; + fn links_mut(&mut self) -> &mut Links; +} + +#[allow(dead_code)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Links { + prev: Option, + next: Option, + _tag: PhantomData, +} + +#[allow(dead_code)] +impl Links { + pub const fn new() -> Self { + Self { + prev: None, + next: None, + _tag: PhantomData, + } + } +} + +#[allow(dead_code)] +impl List { + pub const fn new() -> Self { + Self { + head: None, + tail: None, + len: 0, + _tag: PhantomData, + } + } + + pub fn head(&self) -> Option { + self.head + } + + pub fn tail(&self) -> Option { + self.tail + } + + pub fn len(&self) -> usize { + self.len + } + + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + pub fn push_front + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + where + >::Output: Linkable, + { + self.detach_links(id, storage)?; + + match self.head { + Some(old_head) => { + let (new_node, old_head_node) = storage.get2_mut(id, old_head); + let (new_node, old_head_node) = (new_node.ok_or(())?, old_head_node.ok_or(())?); + + new_node.links_mut().prev = None; + new_node.links_mut().next = Some(old_head); + + old_head_node.links_mut().prev = Some(id); + } + None => { + let new_node = storage.get_mut(id).ok_or(())?; + new_node.links_mut().prev = None; + new_node.links_mut().next = None; + self.tail = Some(id); + } + } + + self.head = Some(id); + self.len += 1; + Ok(()) + } + + pub fn push_back + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + where + >::Output: Linkable, + { + self.detach_links(id, storage)?; + + match self.tail { + Some(old_tail) => { + let (new_node, old_tail_node) = storage.get2_mut(id, old_tail); + let (new_node, old_tail_node) = (new_node.ok_or(())?, old_tail_node.ok_or(())?); + + new_node.links_mut().next = None; + new_node.links_mut().prev = Some(old_tail); + + old_tail_node.links_mut().next = Some(id); + } + None => { + let new_node = storage.get_mut(id).ok_or(())?; + new_node.links_mut().next = None; + new_node.links_mut().prev = None; + self.head = Some(id); + } + } + + self.tail = Some(id); + self.len += 1; + Ok(()) + } + + pub fn pop_front + GetMut>(&mut self, storage: &mut S) -> Result, ()> + where + >::Output: Linkable, + { + let Some(id) = self.head else { + return Ok(None); + }; + + self.remove(id, storage)?; + Ok(Some(id)) + } + + pub fn pop_back + GetMut>(&mut self, storage: &mut S) -> Result, ()> + where + >::Output: Linkable, + { + let Some(id) = self.tail else { + return Ok(None); + }; + + self.remove(id, storage)?; + Ok(Some(id)) + } + + pub fn remove + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + where + >::Output: Linkable, + { + let (prev, next, linked) = { + let node = storage.get(id).ok_or(())?; + let links = node.links(); + let linked = self.head == Some(id) + || self.tail == Some(id) + || links.prev.is_some() + || links.next.is_some(); + (links.prev, links.next, linked) + }; + + if !linked { + return Err(()); + } + + if let Some(prev_id) = prev { + let prev_node = storage.get_mut(prev_id).ok_or(())?; + prev_node.links_mut().next = next; + } else { + self.head = next; + } + + if let Some(next_id) = next { + let next_node = storage.get_mut(next_id).ok_or(())?; + next_node.links_mut().prev = prev; + } else { + self.tail = prev; + } + + let node = storage.get_mut(id).ok_or(())?; + node.links_mut().prev = None; + node.links_mut().next = None; + + self.len = self.len.saturating_sub(1); + Ok(()) + } + + fn detach_links + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + where + >::Output: Linkable, + { + let node = storage.get_mut(id).ok_or(())?; + node.links_mut().prev = None; + node.links_mut().next = None; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use core::borrow::Borrow; + + use super::{Linkable, Links, List}; + use crate::types::{array::IndexMap, traits::{Get, ToIndex}}; + + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + struct Id(usize); + + impl ToIndex for Id { + fn to_index>(idx: Option) -> usize { + idx.as_ref().map_or(0, |k| k.borrow().0) + } + } + + #[derive(Clone, Copy)] + struct TestTag; + + struct Node { + links: Links, + } + + impl Node { + fn new() -> Self { + Self { + links: Links::new(), + } + } + } + + impl Linkable for Node { + fn links(&self) -> &Links { + &self.links + } + + fn links_mut(&mut self) -> &mut Links { + &mut self.links + } + } + + fn storage() -> IndexMap { + let mut map = IndexMap::new(); + for i in 0..4 { + map.insert(&Id(i), Node::new()).unwrap(); + } + map + } + + #[test] + fn push_front_and_remove() { + let mut s = storage(); + let mut list = List::::new(); + + list.push_front(Id(1), &mut s).unwrap(); + list.push_front(Id(2), &mut s).unwrap(); + list.push_front(Id(3), &mut s).unwrap(); + + assert_eq!(list.head(), Some(Id(3))); + assert_eq!(list.tail(), Some(Id(1))); + assert_eq!(list.len(), 3); + + list.remove(Id(2), &mut s).unwrap(); + assert_eq!(list.head(), Some(Id(3))); + assert_eq!(list.tail(), Some(Id(1))); + assert_eq!(list.len(), 2); + + let n3 = s.get(Id(3)).unwrap(); + let n1 = s.get(Id(1)).unwrap(); + assert_eq!(n3.links().next, Some(Id(1))); + assert_eq!(n1.links().prev, Some(Id(3))); + } + + #[test] + fn pop_back_ordered() { + let mut s = storage(); + let mut list = List::::new(); + + list.push_back(Id(1), &mut s).unwrap(); + list.push_back(Id(2), &mut s).unwrap(); + list.push_back(Id(3), &mut s).unwrap(); + + assert_eq!(list.pop_back(&mut s).unwrap(), Some(Id(3))); + assert_eq!(list.pop_back(&mut s).unwrap(), Some(Id(2))); + assert_eq!(list.pop_back(&mut s).unwrap(), Some(Id(1))); + assert_eq!(list.pop_back(&mut s).unwrap(), None); + assert!(list.is_empty()); + } + + #[test] + fn pop_front_ordered() { + let mut s = storage(); + let mut list = List::::new(); + + list.push_back(Id(1), &mut s).unwrap(); + list.push_back(Id(2), &mut s).unwrap(); + list.push_back(Id(3), &mut s).unwrap(); + + assert_eq!(list.pop_front(&mut s).unwrap(), Some(Id(1))); + assert_eq!(list.pop_front(&mut s).unwrap(), Some(Id(2))); + assert_eq!(list.pop_front(&mut s).unwrap(), Some(Id(3))); + assert_eq!(list.pop_front(&mut s).unwrap(), None); + assert!(list.is_empty()); + } +} \ No newline at end of file diff --git a/src/uspace.rs b/src/uspace.rs index 14dcfd9..febcf01 100644 --- a/src/uspace.rs +++ b/src/uspace.rs @@ -21,6 +21,7 @@ pub fn init_app(boot_info: &crate::BootInfo) -> Result<(), crate::utils::KernelE entry, fin: None, }; - sched::create_thread(sched::task::KERNEL_TASK, &attrs)?; - Ok(()) + let uid = sched::create_thread(sched::task::KERNEL_TASK, &attrs)?; + + sched::enqueue(uid) } diff --git a/src/utils.rs b/src/utils.rs index c77239a..0f1bd71 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -13,6 +13,8 @@ pub(crate) use core::convert::{identity as likely, identity as unlikely}; #[cfg(feature = "nightly")] pub(crate) use core::hint::{likely, unlikely}; +use hal::mem::PhysAddr; + /// This is a macro that is used to panic when a bug is detected. @@ -55,7 +57,7 @@ pub enum KernelError { /// The kernel is out of memory. OutOfMemory, InvalidSize, - InvalidAddress, + InvalidAddress(PhysAddr), InvalidArgument, HalError(hal::Error), CustomError(&'static str), @@ -68,7 +70,7 @@ impl Debug for KernelError { KernelError::InvalidAlign => write!(f, "Invalid alignment"), KernelError::OutOfMemory => write!(f, "Out of memory"), KernelError::InvalidSize => write!(f, "Invalid size"), - KernelError::InvalidAddress => write!(f, "Invalid address"), + KernelError::InvalidAddress(addr) => write!(f, "Invalid address ({})", addr), KernelError::InvalidArgument => write!(f, "Invalid argument"), KernelError::HalError(e) => write!(f, "{e} (in HAL)"), KernelError::CustomError(msg) => write!(f, "{}", msg), From dfae0f80083641063f7c65e833c66c37c607f1d4 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Thu, 26 Mar 2026 20:25:29 +0000 Subject: [PATCH 06/42] make things more sane --- .cargo/config.toml | 18 ++++++-------- .gitignore | 1 + build.rs | 7 ------ machine/arm/Cargo.toml | 3 --- machine/arm/build.rs | 5 ++-- machine/select/Cargo.toml | 1 - machine/select/build.rs | 7 ------ presets/stm32l4r5zi_def.toml | 7 +----- xtasks/crates/config/src/file.rs | 9 +++++++ xtasks/crates/config/src/lib.rs | 40 ++++-------------------------- xtasks/crates/config/src/main.rs | 15 +++++------ xtasks/crates/injector/src/main.rs | 2 +- 12 files changed, 35 insertions(+), 80 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 0a1e750..dfc05b1 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,16 +1,12 @@ +include = [ + "../config.toml" +] [alias] xtask = "--config xtasks/.cargo/config.toml run -p xtask --release --" -[env] - -[build] -target = "host-tuple" - [target.'cfg(target_os = "none")'] -rustflags = ["-C", "link-arg=--entry=main",] - -[target] - -[target.thumbv7em-none-eabi] -rustflags = ["-C", "relocation-model=ropi-rwpi"] +rustflags = [ + "-C", "link-arg=--entry=main", + "-C", "link-arg=-Tlink.ld", +] \ No newline at end of file diff --git a/.gitignore b/.gitignore index aeeb6ba..5902a98 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ symbols.map compile_commands.json .cache/ *.img +config.toml diff --git a/build.rs b/build.rs index a571440..582e86e 100644 --- a/build.rs +++ b/build.rs @@ -19,13 +19,6 @@ fn main() { generate_syscall_map("src/syscalls").expect("Failed to generate syscall map."); generate_syscalls_export("src/syscalls").expect("Failed to generate syscall exports."); - // Get linker script from environment variable - if let Ok(linker_script) = std::env::var("DEP_HAL_LINKER_SCRIPT") { - println!("cargo::rustc-link-arg=-T{linker_script}"); - } else { - println!("cargo::warning=LD_SCRIPT_PATH environment variable not set."); - } - cfg_aliases! { freestanding: { all(not(test), not(doctest), not(doc), not(kani), any(target_os = "none", target_os = "unknown")) }, } diff --git a/machine/arm/Cargo.toml b/machine/arm/Cargo.toml index 6aef529..9e5a441 100644 --- a/machine/arm/Cargo.toml +++ b/machine/arm/Cargo.toml @@ -5,9 +5,6 @@ rust-version = "1.85.0" authors = ["Thomas Wachter"] edition = "2024" build = "build.rs" -# Through this we can access env variables set by the build script through DEP_HAL_ -# It has nothing to do with native libraries. -links = "halarm" [lib] crate-type = ["rlib"] diff --git a/machine/arm/build.rs b/machine/arm/build.rs index 3ff8569..0a12b84 100644 --- a/machine/arm/build.rs +++ b/machine/arm/build.rs @@ -337,7 +337,8 @@ fn workspace_dir() -> PathBuf { /// /// Exits with error code 1 if any critical build step fails fn main() { - let out = env::var("OUT_DIR").unwrap_or("src".to_string()); + let out = env::var("OUT_DIR").unwrap(); + println!("cargo::rustc-link-search={out}"); let hal = fail_on_error(env::var("OSIRIS_ARM_HAL").with_context( || "OSIRIS_ARM_HAL environment variable not set. Please set it to the path of the ARM HAL.", @@ -363,7 +364,7 @@ fn main() { let libhal = libhal_config.build(); println!("cargo::rustc-link-search=native={}", libhal.display()); - println!("cargo::metadata=linker-script={out}/link.ld"); + println!("cargo::rerun-if-changed={out}/link.ld"); // Extract compile commands for HAL let hal_cc = build_dir.join("compile_commands.json"); diff --git a/machine/select/Cargo.toml b/machine/select/Cargo.toml index 4d068cc..1bb1694 100644 --- a/machine/select/Cargo.toml +++ b/machine/select/Cargo.toml @@ -2,7 +2,6 @@ name = "hal-select" version = "0.1.0" edition = "2024" -links = "hal" [dependencies] hal-api = { path = "../api" } diff --git a/machine/select/build.rs b/machine/select/build.rs index d7c04ac..78d1124 100644 --- a/machine/select/build.rs +++ b/machine/select/build.rs @@ -8,13 +8,6 @@ fn main() { } } - // Pass linker script to top level - if let Ok(linker_script) = std::env::var("DEP_HALARM_LINKER_SCRIPT") { - println!("cargo::metadata=linker-script={linker_script}"); - } else { - println!("cargo::warning=LD_SCRIPT_PATH environment variable not set."); - } - cfg_aliases! { freestanding: { all(not(test), not(doctest), not(doc), not(kani), any(target_os = "none", target_os = "unknown")) }, } diff --git a/presets/stm32l4r5zi_def.toml b/presets/stm32l4r5zi_def.toml index e637fc4..8751d75 100644 --- a/presets/stm32l4r5zi_def.toml +++ b/presets/stm32l4r5zi_def.toml @@ -17,9 +17,4 @@ OSIRIS_TUNING_APPSTACKSIZE = "2048" OSIRIS_TUNING_APPMEMSIZE = "8192" [build] -target = "thumbv7em-none-eabi" - -[target.'cfg(target_os = "none")'] -rustflags = [ - "-C", "link-arg=--entry=main", -] \ No newline at end of file +target = "thumbv7em-none-eabi" \ No newline at end of file diff --git a/xtasks/crates/config/src/file.rs b/xtasks/crates/config/src/file.rs index 0b97663..abc5270 100644 --- a/xtasks/crates/config/src/file.rs +++ b/xtasks/crates/config/src/file.rs @@ -20,6 +20,15 @@ pub fn load_file(path: &Path) -> Result { }) } +pub fn create_if_not_exists(path: &Path) -> Result<()> { + if !path.exists() { + std::fs::write(path, "") + .with_context(|| format!("failed to create file {}", path.display()))?; + } + + Ok(()) +} + pub fn load_files(root: &Path, filename: &str) -> Vec> { let mut files = Vec::new(); diff --git a/xtasks/crates/config/src/lib.rs b/xtasks/crates/config/src/lib.rs index 60172ee..65f431e 100644 --- a/xtasks/crates/config/src/lib.rs +++ b/xtasks/crates/config/src/lib.rs @@ -21,8 +21,7 @@ mod toml_patch; pub mod types; pub mod ui; -use anyhow::anyhow; -use toml_edit::{DocumentMut, ImDocument, Item, Table}; +use toml_edit::{DocumentMut, ImDocument}; pub fn load_config(root: &Path, filename: &str) -> ConfigNode { let files = file::load_files(root, filename); @@ -116,6 +115,7 @@ pub fn load_state<'node>( } pub fn load_toml_mut(toml: &Path) -> Result { + file::create_if_not_exists(&toml)?; let File { path, content } = file::load_file(&toml)?; let path = path.to_string_lossy(); @@ -151,41 +151,11 @@ pub fn load_toml(toml: &Path) -> Result, Error> { Ok(doc) } -#[rustversion::since(1.94)] -compile_error!("config-includes are stable since Rust 1.94; fix the TODOs below."); - pub fn apply_preset(config: &mut DocumentMut, preset: &ImDocument) -> Result<(), Error> { - for (key, value) in preset.iter() { - // We override with a depth of zero or one granularity. - - // TODO: Until we have config-includes stabilized, we skip alias sections. - if key == "alias" { - continue; - } - - match value { - Item::Table(src) => { - let dst = config.entry(key).or_insert(Item::Table(Table::new())); - - if let Item::Table(dst) = dst { - dst.clear(); + config.clear(); - for (key, value) in src.iter() { - dst.insert(key, value.clone()); - } - } else { - return Err(anyhow!( - "type mismatch when applying preset key '{}': expected table, found {}", - key, - dst.type_name() - ) - .into()); - } - } - _ => { - config.insert(key, value.clone()); - } - } + for (key, value) in preset.iter() { + config.insert(key, value.clone()); } Ok(()) diff --git a/xtasks/crates/config/src/main.rs b/xtasks/crates/config/src/main.rs index 85320c1..37ebaf6 100644 --- a/xtasks/crates/config/src/main.rs +++ b/xtasks/crates/config/src/main.rs @@ -58,7 +58,9 @@ pub fn main() { } fn ask_confirmation(prompt: &str) -> bool { - print!("{} (y/N): ", prompt); + print!("{}\n\n(y/N): ", + prompt + ); if let Err(_) = std::io::Write::flush(&mut std::io::stdout()) { return false; @@ -79,14 +81,13 @@ fn run_load_preset(preset_name: &str, no_confirm: bool, current_dir: &Path) -> R let preset_path = PathBuf::from("presets").join(format!("{preset_name}.toml")); let preset = config::load_toml(&preset_path)?; - let config_path = current_dir.join(".cargo/config.toml"); + let config_path = current_dir.join("config.toml"); let mut config = config::load_toml_mut(&config_path)?; // Ask for confirmation if !no_confirm - && !ask_confirmation(&format!( - "Are you sure you want to apply the preset '{preset_name}' to {}?\nThis will overwrite all existing configuration options.", + && !ask_confirmation(&format!("\nApply preset '{preset_name}' to '{}'?\nThis overwrites all existing configuration options.", config_path.display() )) { @@ -111,14 +112,14 @@ fn run_clean(no_confirm: bool, current_dir: &Path) -> Result<(), Error> { // Ask for confirmation if !no_confirm && !ask_confirmation( - "Are you sure you want to remove all configuration options from .cargo/config.toml?", + "Are you sure you want to remove all configuration options from config.toml?", ) { log::info!("Abort."); return Ok(()); } - let config_path = current_dir.join(".cargo/config.toml"); + let config_path = current_dir.join("config.toml"); let mut config = config::load_toml_mut(&config_path)?; @@ -142,7 +143,7 @@ fn run_clean(no_confirm: bool, current_dir: &Path) -> Result<(), Error> { } fn run_ui(current_dir: &Path) { - let config_path = current_dir.join(".cargo/config.toml"); + let config_path = current_dir.join("config.toml"); let node = config::load_config(¤t_dir, "options.toml"); diff --git a/xtasks/crates/injector/src/main.rs b/xtasks/crates/injector/src/main.rs index 973b8c7..3fafe36 100644 --- a/xtasks/crates/injector/src/main.rs +++ b/xtasks/crates/injector/src/main.rs @@ -104,7 +104,7 @@ fn inject(elf: &PathBuf) -> Result<(), String> { } fn get_target_from_cargo_config(manifest_dir: &PathBuf) -> Option { - let cargo_config = manifest_dir.join(".cargo").join("config.toml"); + let cargo_config = manifest_dir.join("config.toml"); if !cargo_config.exists() { return None; From b998d8e038d51a0f69415a43173795f95684da53 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Thu, 26 Mar 2026 21:28:03 +0000 Subject: [PATCH 07/42] sanify --- Cargo.lock | 11 - Cargo.toml | 5 +- examples/hello-world/Cargo.toml | 13 +- examples/hello-world/src/main.rs | 11 +- interface/Cargo.lock | 473 ----------------------------- interface/Cargo.toml | 12 - interface/build.rs | 45 --- interface/include/bindings.h | 76 ----- interface/src/lib.rs | 60 ---- justfile | 1 - machine/arm/common/CMakeLists.txt | 1 - machine/arm/common/crt0.S | 2 +- machine/arm/common/entry.c | 50 --- machine/arm/stm32l4xx/r5zi/lib.c | 30 -- macros/src/lib.rs | 27 ++ src/lib.rs | 31 +- src/main.rs | 20 +- src/mem.rs | 5 +- src/sched/thread.rs | 2 +- src/syscalls/file.rs | 4 +- src/uspace.rs | 23 +- xtasks/crates/pack/Cargo.toml | 1 - xtasks/crates/pack/src/bootinfo.rs | 67 ---- xtasks/crates/pack/src/main.rs | 1 - xtasks/crates/pack/src/pack.rs | 4 +- 25 files changed, 87 insertions(+), 888 deletions(-) delete mode 100644 interface/Cargo.lock delete mode 100644 interface/Cargo.toml delete mode 100644 interface/build.rs delete mode 100644 interface/include/bindings.h delete mode 100644 interface/src/lib.rs delete mode 100644 machine/arm/common/entry.c delete mode 100644 xtasks/crates/pack/src/bootinfo.rs diff --git a/Cargo.lock b/Cargo.lock index bc2decf..292bc70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -813,15 +813,6 @@ dependencies = [ "syn", ] -[[package]] -name = "interface" -version = "0.1.0" -dependencies = [ - "bytemuck", - "cbindgen", - "cfg_aliases", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1078,7 +1069,6 @@ dependencies = [ "envparse", "hal-select", "hal-testing", - "interface", "kani", "macros", "quote", @@ -1097,7 +1087,6 @@ dependencies = [ "clap", "crc-fast", "elf", - "interface", "log", "logging", "tempfile", diff --git a/Cargo.toml b/Cargo.toml index ccc5109..f201c3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,8 @@ members = ["examples/*", "xtasks", "xtasks/crates/*"] default-members = ["."] [workspace.dependencies] -interface = { path = "interface" } logging = { path = "xtasks/logging" } +osiris = { path = "." } [package] name = "osiris" @@ -20,8 +20,7 @@ path = "src/main.rs" [dependencies] hal = { package = "hal-select", path = "machine/select" } -macros = { path = "macros" } -interface = { path = "interface" } +proc_macros = { package = "macros", path = "macros" } envparse = "0.1.0" bitflags = "2.10.0" diff --git a/examples/hello-world/Cargo.toml b/examples/hello-world/Cargo.toml index 8f7248e..1bd3456 100644 --- a/examples/hello-world/Cargo.toml +++ b/examples/hello-world/Cargo.toml @@ -4,7 +4,18 @@ version = "0.1.0" edition = "2024" [dependencies] -osiris = { path = "../../" } +osiris = { workspace = true } [build-dependencies] cfg_aliases = "0.2.1" + +[profile.dev] +panic = "abort" +strip = false +opt-level = 2 + +[profile.release] +panic = "abort" +opt-level = "z" +codegen-units = 1 +lto = true diff --git a/examples/hello-world/src/main.rs b/examples/hello-world/src/main.rs index 351dd79..d78020d 100644 --- a/examples/hello-world/src/main.rs +++ b/examples/hello-world/src/main.rs @@ -1,13 +1,10 @@ #![no_std] #![no_main] -#[unsafe(no_mangle)] -extern "C" fn main() { - osiris::syscall_print(0, "Hello World!".as_bytes().as_ptr(), 12); -} +use osiris::app_main; -#[cfg(freestanding)] -#[panic_handler] -fn panic(_info: &core::panic::PanicInfo) -> ! { +#[app_main] +fn main() { + osiris::syscall_print(0, "Hello World!".as_bytes().as_ptr(), 12); loop {} } diff --git a/interface/Cargo.lock b/interface/Cargo.lock deleted file mode 100644 index adeff39..0000000 --- a/interface/Cargo.lock +++ /dev/null @@ -1,473 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys", -] - -[[package]] -name = "bitflags" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" - -[[package]] -name = "bytemuck" -version = "1.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "cbindgen" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" -dependencies = [ - "clap", - "heck", - "indexmap", - "log", - "proc-macro2", - "quote", - "serde", - "serde_json", - "syn", - "tempfile", - "toml", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "clap" -version = "4.5.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" -dependencies = [ - "clap_builder", -] - -[[package]] -name = "clap_builder" -version = "4.5.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_lex" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" - -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "indexmap" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "interface" -version = "0.1.0" -dependencies = [ - "bytemuck", - "cbindgen", - "cfg_aliases", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itoa" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" - -[[package]] -name = "libc" -version = "0.2.180" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "proc-macro2" -version = "1.0.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "rustix" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "syn" -version = "2.0.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" -dependencies = [ - "fastrand", - "getrandom", - "once_cell", - "rustix", - "windows-sys", -] - -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", - "winnow", -] - -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - -[[package]] -name = "unicode-ident" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "winnow" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "zmij" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" diff --git a/interface/Cargo.toml b/interface/Cargo.toml deleted file mode 100644 index a435764..0000000 --- a/interface/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "interface" -version = "0.1.0" -edition = "2024" - -[dependencies] -bytemuck = { version = "1.24.0", features = ["derive"] } - - -[build-dependencies] -cfg_aliases = "0.2.1" -cbindgen = "0.28.0" diff --git a/interface/build.rs b/interface/build.rs deleted file mode 100644 index 2dd23a2..0000000 --- a/interface/build.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::env; - -use cfg_aliases::cfg_aliases; - -fn main() { - cfg_aliases! { - freestanding: { all(not(test), not(doctest), not(doc), not(kani), any(target_os = "none", target_os = "unknown")) }, - } - - generate_c_api(); -} - -fn generate_c_api() { - let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - - let config: cbindgen::Config = cbindgen::Config { - no_includes: true, - includes: vec![ - "stdint.h".to_string(), - "stdbool.h".to_string(), - "stdarg.h".to_string(), - ], - layout: cbindgen::LayoutConfig { - packed: Some("__attribute__((packed))".to_string()), - ..Default::default() - }, - language: cbindgen::Language::C, - cpp_compat: false, - ..Default::default() - }; - - cbindgen::Builder::new() - .with_crate(crate_dir) - .with_config(config) - .generate() - .map_or_else( - |error| match error { - cbindgen::Error::ParseSyntaxError { .. } => {} - e => panic!("{e:?}"), - }, - |bindings| { - bindings.write_to_file("include/bindings.h"); - }, - ); -} diff --git a/interface/include/bindings.h b/interface/include/bindings.h deleted file mode 100644 index dd7b765..0000000 --- a/interface/include/bindings.h +++ /dev/null @@ -1,76 +0,0 @@ -#include "stdint.h" -#include "stdbool.h" -#include "stdarg.h" - -#define BOOT_INFO_MAGIC 221566477 - -/** - * The memory map entry type. - * - * This structure shall be compatible with the multiboot_memory_map_t struct at - * Link: [https://www.gnu.org/software/grub/manual/multiboot/multiboot.html]() - */ -typedef struct __attribute__((packed)) MemMapEntry { - /** - * The size of the entry. - */ - uint32_t size; - /** - * The base address of the memory region. - */ - uint64_t addr; - /** - * The length of the memory region. - */ - uint64_t length; - /** - * The type of the memory region. - */ - uint32_t ty; -} MemMapEntry; - -typedef struct InitDescriptor { - /** - * Pointer to the start of the binary of the init program. - */ - uint64_t begin; - /** - * Length of the binary of the init program. - */ - uint64_t len; - uint64_t entry_offset; -} InitDescriptor; - -typedef struct Args { - struct InitDescriptor init; -} Args; - -/** - * The boot information structure. - */ -typedef struct BootInfo { - /** - * The magic number that indicates valid boot information. - */ - uint32_t magic; - /** - * The version of the boot information structure. - */ - uint32_t version; - /** - * The implementer of the processor. - * The variant of the processor. - * The memory map. - */ - struct MemMapEntry mmap[8]; - /** - * The length of the memory map. - */ - uint64_t mmap_len; - /** - * The command line arguments. - */ - struct Args args; -} BootInfo; - -extern void kernel_init(const struct BootInfo *boot_info); diff --git a/interface/src/lib.rs b/interface/src/lib.rs deleted file mode 100644 index 1a334a8..0000000 --- a/interface/src/lib.rs +++ /dev/null @@ -1,60 +0,0 @@ -#![cfg_attr(freestanding, no_std)] - -/// The memory map entry type. -/// -/// This structure shall be compatible with the multiboot_memory_map_t struct at -/// Link: [https://www.gnu.org/software/grub/manual/multiboot/multiboot.html]() -#[repr(packed, C)] -#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] -pub struct MemMapEntry { - /// The size of the entry. - pub size: u32, - /// The base address of the memory region. - pub addr: u64, - /// The length of the memory region. - pub length: u64, - /// The type of the memory region. - pub ty: u32, -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] -pub struct InitDescriptor { - /// Pointer to the start of the binary of the init program. - pub begin: u64, - /// Length of the binary of the init program. - pub len: u64, - pub entry_offset: u64, -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] -pub struct Args { - pub init: InitDescriptor, -} - -pub const BOOT_INFO_MAGIC: u32 = 0xD34D60D; - -/// The boot information structure. -#[repr(C)] -#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] -pub struct BootInfo { - /// The magic number that indicates valid boot information. - pub magic: u32, - /// The version of the boot information structure. - pub version: u32, - /// The implementer of the processor. - //pub implementer: u64, - /// The variant of the processor. - //pub variant: u64, - /// The memory map. - pub mmap: [MemMapEntry; 8], - /// The length of the memory map. - pub mmap_len: u64, - /// The command line arguments. - pub args: Args, -} - -unsafe extern "C" { - pub fn kernel_init(boot_info: *const BootInfo) -> !; -} diff --git a/justfile b/justfile index c1d3dd4..b14c0c4 100644 --- a/justfile +++ b/justfile @@ -10,7 +10,6 @@ pack *args: example name *args: (build args) cargo build -p {{name}} {{args}} - cargo xtask pack --output {{name}}.bin --init examples/{{name}} {{args}} fmt *args: cargo fmt {{args}} diff --git a/machine/arm/common/CMakeLists.txt b/machine/arm/common/CMakeLists.txt index f426b3a..0346c0d 100644 --- a/machine/arm/common/CMakeLists.txt +++ b/machine/arm/common/CMakeLists.txt @@ -33,7 +33,6 @@ set_property(SOURCE irq.S APPEND PROPERTY COMPILE_OPTIONS "-x" "assembler-with-c add_library(common STATIC ivt.S - entry.c syscall.c irq.S crt0.S diff --git a/machine/arm/common/crt0.S b/machine/arm/common/crt0.S index e26deb6..ff93ecd 100644 --- a/machine/arm/common/crt0.S +++ b/machine/arm/common/crt0.S @@ -28,7 +28,7 @@ bootstrap: strlt r3, [r1], #4 blt 2b @ Call the pre_init function. - bl pre_init + bl kernel_init @ If main returns, loop forever. hang: b hang \ No newline at end of file diff --git a/machine/arm/common/entry.c b/machine/arm/common/entry.c deleted file mode 100644 index 52dad42..0000000 --- a/machine/arm/common/entry.c +++ /dev/null @@ -1,50 +0,0 @@ - -#include -#include "mem.h" - -#include - -typedef void (*func_t)(void); - -extern func_t __init_array_start; -extern func_t __init_array_end; -extern func_t __fini_array_start; -extern func_t __fini_array_end; - -extern void pre_init(void) __attribute__((noreturn)); -extern void init_mem_maps(BootInfo *boot_info); - -__attribute__((section(".bootinfo"), used, aligned(4))) -static BootInfo _boot_info = { - .magic = BOOT_INFO_MAGIC, - .version = 1, - .mmap = {0}, - .mmap_len = 0, - .args = {.init = {0}}, -}; - -void call_constructors(void) -{ - for (func_t *func = &__init_array_start; func < &__init_array_end; func++) - { - (*func)(); - } -} - -void call_destructors(void) -{ - for (func_t *func = &__fini_array_start; func < &__fini_array_end; func++) - { - (*func)(); - } -} - -void pre_init(void) -{ - // Init memory maps, etc. - init_mem_maps(&_boot_info); - - // Boot! - kernel_init(&_boot_info); - unreachable(); -} diff --git a/machine/arm/stm32l4xx/r5zi/lib.c b/machine/arm/stm32l4xx/r5zi/lib.c index 877422d..83c8135 100644 --- a/machine/arm/stm32l4xx/r5zi/lib.c +++ b/machine/arm/stm32l4xx/r5zi/lib.c @@ -1,6 +1,4 @@ #include - -#include #include /* @@ -197,31 +195,3 @@ const uintptr_t vector_table_ext[] __attribute__((section(".ivt.ext"))) = { (uintptr_t)&gfxmmu_hndlr, (uintptr_t)&dmamux1_ovr_hndlr, }; - -void init_mem_maps(BootInfo *boot_info) { - boot_info->mmap_len = 3; - - // SRAM1 - boot_info->mmap[0] = (MemMapEntry){ - .size = sizeof(MemMapEntry), - .addr = 0x20000000, - .length = 0x30000, - .ty = 1, - }; - - // SRAM2 - boot_info->mmap[1] = (MemMapEntry){ - .size = sizeof(MemMapEntry), - .addr = 0x20030000, - .length = 0x10000, - .ty = 1, - }; - - // SRAM3 - boot_info->mmap[2] = (MemMapEntry){ - .size = sizeof(MemMapEntry), - .addr = 0x20040000, - .length = 0x60000, - .ty = 1, - }; -} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 178322c..2387d03 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -15,6 +15,33 @@ pub fn derive_tagged_links(input: proc_macro::TokenStream) -> proc_macro::TokenS }.into() } +#[proc_macro_attribute] +pub fn app_main(input: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let item = syn::parse_macro_input!(item as syn::ItemFn); + let block = &item.block; + + let expanded = quote::quote! { + #[unsafe(no_mangle)] + #[unsafe(naked)] + extern "C" fn main() { + osiris::hal::asm::startup_trampoline!(); + } + + #[cfg(freestanding)] + #[panic_handler] + fn panic(info: &core::panic::PanicInfo) -> ! { + osiris::panic(info); + } + + #[unsafe(no_mangle)] + pub extern "C" fn app_main() -> () { + #block + } + }; + + expanded.into() +} + #[proc_macro_attribute] pub fn service( attr: proc_macro::TokenStream, diff --git a/src/lib.rs b/src/lib.rs index ea3823b..c2de536 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,9 +20,11 @@ pub mod time; pub mod uspace; use hal::Machinelike; -use interface::BootInfo; include!(concat!(env!("OUT_DIR"), "/syscalls_export.rs")); +pub use hal; +pub use proc_macros::app_main; + /// The kernel initialization function. /// /// `boot_info` - The boot information. @@ -32,22 +34,15 @@ include!(concat!(env!("OUT_DIR"), "/syscalls_export.rs")); /// This function must be called only once during the kernel startup. /// The `boot_info` pointer must be valid and point to a properly initialized `BootInfo` structure. #[unsafe(no_mangle)] -pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { +pub unsafe extern "C" fn kernel_init() -> ! { // Initialize basic hardware and the logging system. hal::Machine::init(); hal::Machine::bench_start(); - if boot_info.is_null() || !boot_info.is_aligned() { - panic!("boot_info pointer is null or unaligned."); - } - - // Safety: We trust the bootloader to provide a valid boot_info structure. - let boot_info = unsafe { &*boot_info }; - print::print_header(); // Initialize the memory allocator. - let kaddr_space = mem::init_memory(boot_info); + let kaddr_space = mem::init_memory(); kprintln!("Memory initialized."); @@ -67,7 +62,7 @@ pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { ); // Start the init application. - if let Err(e) = uspace::init_app(boot_info) { + if let Err(e) = uspace::init_app() { panic!("failed to start init application. Error: {e:?}"); } @@ -75,3 +70,17 @@ pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { loop {} } + +pub fn panic(info: &core::panic::PanicInfo) -> ! { + kprintln!("**************************** PANIC ****************************"); + kprintln!(""); + kprintln!("Message: {}", info.message()); + + if let Some(location) = info.location() { + kprintln!("Location: {}:{}", location.file(), location.line()); + } + + kprintln!("**************************** PANIC ****************************"); + + hal::Machine::panic_handler(info); +} diff --git a/src/main.rs b/src/main.rs index ae72d07..3191b0f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,23 +7,17 @@ pub extern "C" fn main() -> ! { hal::asm::startup_trampoline!(); } +#[unsafe(no_mangle)] +pub extern "C" fn app_main() -> ! { + osiris::syscall_print(0, "Hello World!".as_bytes().as_ptr(), 12); + loop {} +} + /// The panic handler. #[cfg(freestanding)] #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { - use hal::Machinelike; - - osiris::kprintln!("**************************** PANIC ****************************"); - osiris::kprintln!(""); - osiris::kprintln!("Message: {}", info.message()); - - if let Some(location) = info.location() { - osiris::kprintln!("Location: {}:{}", location.file(), location.line()); - } - - osiris::kprintln!("**************************** PANIC ****************************"); - - hal::Machine::panic_handler(info); + osiris::panic(info); } #[cfg(not(freestanding))] diff --git a/src/mem.rs b/src/mem.rs index 5a346bc..56a5074 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -3,9 +3,8 @@ use crate::mem::pfa::PAGE_SIZE; use crate::mem::vmm::{AddressSpacelike, Backing, Perms, Region}; use crate::sync::spinlock::SpinLocked; -use crate::BootInfo; use alloc::Allocator; -use hal::mem::{PhysAddr, VirtAddr}; +use hal::mem::{PhysAddr}; use core::ptr::NonNull; pub mod alloc; @@ -44,7 +43,7 @@ static GLOBAL_ALLOCATOR: SpinLocked = /// `boot_info` - The boot information. This contains the memory map. /// /// Returns an error if the memory allocator could not be initialized. -pub fn init_memory(boot_info: &BootInfo) -> vmm::AddressSpace { +pub fn init_memory() -> vmm::AddressSpace { let stack_top = &raw const __stack_top as usize; if let Err(e) = pfa::init_pfa(PhysAddr::new(stack_top)) { // TODO: Get this from the DeviceTree. panic!("failed to initialize PFA. Error: {e:?}"); diff --git a/src/sched/thread.rs b/src/sched/thread.rs index 66f4ec5..75a1890 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -4,7 +4,7 @@ use core::{borrow::Borrow, ffi::c_void}; use hal::{Stack, stack::EntryFn}; use hal::stack::{FinFn, Stacklike}; -use macros::TaggedLinks; +use proc_macros::TaggedLinks; use crate::sched::task::{self, KERNEL_TASK}; use crate::types::list; diff --git a/src/syscalls/file.rs b/src/syscalls/file.rs index 65131b9..957a765 100644 --- a/src/syscalls/file.rs +++ b/src/syscalls/file.rs @@ -1,7 +1,5 @@ use core::{ffi::c_int, str}; - -use crate::kprintln; -use macros::syscall_handler; +use proc_macros::syscall_handler; #[syscall_handler(num = 0)] fn syscall_print(fd: usize, buf: *const u8, len: usize) -> c_int { diff --git a/src/uspace.rs b/src/uspace.rs index febcf01..d6799de 100644 --- a/src/uspace.rs +++ b/src/uspace.rs @@ -1,24 +1,19 @@ //! This module provides access to userspace structures and services. -use ::core::mem::transmute; - use crate::sched; -pub fn init_app(boot_info: &crate::BootInfo) -> Result<(), crate::utils::KernelError> { - let len = boot_info.args.init.len; - - if len == 0 { - return Err(crate::utils::KernelError::InvalidArgument); - } +unsafe extern "C" { + /// The entry point for the userspace application. + fn app_main() -> (); +} - let entry = unsafe { - transmute::( - boot_info.args.init.begin as usize + boot_info.args.init.entry_offset as usize, - ) - }; +extern "C" fn app_main_entry() { + unsafe { app_main() } +} +pub fn init_app() -> Result<(), crate::utils::KernelError> { let attrs = sched::thread::Attributes { - entry, + entry: app_main_entry, fin: None, }; let uid = sched::create_thread(sched::task::KERNEL_TASK, &attrs)?; diff --git a/xtasks/crates/pack/Cargo.toml b/xtasks/crates/pack/Cargo.toml index 7ca2656..e898333 100644 --- a/xtasks/crates/pack/Cargo.toml +++ b/xtasks/crates/pack/Cargo.toml @@ -10,7 +10,6 @@ clap = { version = "4.5.47", features = ["derive"] } crc-fast = "1.8.0" elf = "0.8.0" log = "0.4.27" -interface = { workspace = true } bytemuck = { version = "1.24.0", features = ["derive"] } tempfile = "3.23.0" cargo_metadata = "0.23.1" diff --git a/xtasks/crates/pack/src/bootinfo.rs b/xtasks/crates/pack/src/bootinfo.rs deleted file mode 100644 index 8f9fcf3..0000000 --- a/xtasks/crates/pack/src/bootinfo.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::image; - -pub struct BootInfo { - inner: Vec, -} - -impl BootInfo { - pub fn new(img_paddr: usize, section: &image::Section) -> Self { - let boot_info = interface::BootInfo { - magic: interface::BOOT_INFO_MAGIC, - version: 1, - mmap: [interface::MemMapEntry { - size: 0, - addr: 0, - length: 0, - ty: 0, - }; 8], - mmap_len: 0, - args: interface::Args { - init: interface::InitDescriptor { - begin: (img_paddr + section.offset()) as u64, - len: section.size() as u64, - entry_offset: section.entry_offset() as u64, - }, - }, - }; - - let boot_info_bytes = bytemuck::bytes_of(&boot_info); - - Self { - inner: boot_info_bytes.to_vec(), - } - } - - pub fn inner(&self) -> &Vec { - &self.inner - } -} - -// Tests for bootinfo -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_bootinfo_fields() { - let boot_info = BootInfo::new( - 0x4000, - &image::Section::from_parts(0x4000, 0x2000, 0x100, 0x1000, 0, false), - ); - - // Deserialize back to struct for comparison - assert_eq!( - boot_info.inner().len(), - std::mem::size_of::() - ); - - let reconstructed: interface::BootInfo = - unsafe { std::ptr::read(boot_info.inner().as_ptr() as *const interface::BootInfo) }; - - assert_eq!(reconstructed.magic, interface::BOOT_INFO_MAGIC); - assert_eq!(reconstructed.version, 1); - assert_eq!(reconstructed.args.init.begin, 0x4000 + 0x4000); - assert_eq!(reconstructed.args.init.len, 0x2000); - assert_eq!(reconstructed.args.init.entry_offset, 0x100); - } -} diff --git a/xtasks/crates/pack/src/main.rs b/xtasks/crates/pack/src/main.rs index 5e985a8..f84c7a7 100644 --- a/xtasks/crates/pack/src/main.rs +++ b/xtasks/crates/pack/src/main.rs @@ -2,7 +2,6 @@ use std::path::PathBuf; use clap::Parser; -mod bootinfo; mod elf; mod image; mod pack; diff --git a/xtasks/crates/pack/src/pack.rs b/xtasks/crates/pack/src/pack.rs index e4cf12f..6cab370 100644 --- a/xtasks/crates/pack/src/pack.rs +++ b/xtasks/crates/pack/src/pack.rs @@ -4,7 +4,6 @@ use anyhow::{Result, anyhow, bail}; use cargo_metadata::MetadataCommand; use crate::{ - bootinfo, elf::ElfInfo, image::{self}, }; @@ -126,8 +125,7 @@ pub fn pack(init_info: &ElfInfo, kernel_info: &mut ElfInfo, out: &Path) -> Resul let init_section = img.add_elf(init_info, image::SectionDescripter::Loadable(None))?; // Patch bootinfo into kernel. - let boot_info = bootinfo::BootInfo::new(img.paddr(), &init_section); - kernel_info.patch_section(".bootinfo", 0, boot_info.inner())?; + //kernel_info.patch_section(".bootinfo", 0, boot_info.inner())?; // Update kernel in image. img.update(kernel_info, 0)?; From 48e123f8a2e36fea95a593b22d341a59a88bca16 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Thu, 26 Mar 2026 21:37:03 +0000 Subject: [PATCH 08/42] sanify --- machine/arm/build.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/machine/arm/build.rs b/machine/arm/build.rs index 0a12b84..57df5f2 100644 --- a/machine/arm/build.rs +++ b/machine/arm/build.rs @@ -303,13 +303,12 @@ fn merge_compile_commands(files: &[String]) -> String { /// # Returns /// /// PathBuf pointing to the workspace root directory -fn workspace_dir() -> PathBuf { +fn workspace_dir() -> Option { let output = Command::new("cargo") .args(["locate-project", "--workspace", "--message-format=plain"]) - .output() - .expect("failed to run cargo locate-project"); + .output().ok()?; let path = String::from_utf8(output.stdout).expect("utf8"); - PathBuf::from(path.trim()).parent().unwrap().to_path_buf() + Some(PathBuf::from(path.trim()).parent()?.to_path_buf()) } /// Main build script entry point. @@ -388,8 +387,10 @@ fn main() { // Merge and export compile_commands.json for IDE integration let merged = merge_compile_commands(&[hal_cc, common_cc]); - let project_root = workspace_dir(); - let out_file = project_root.join("compile_commands.json"); - - fs::write(out_file, merged).expect("write merged compile_commands.json"); + if let Some(project_root) = workspace_dir() { + let out_file = project_root.join("compile_commands.json"); + fs::write(out_file, merged).expect("write merged compile_commands.json"); + } else { + println!("cargo::warning=Could not determine workspace root, skipping compile_commands.json generation."); + } } From 208ffddcf97147057b09fe7784d70dba43adf270 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Thu, 26 Mar 2026 21:51:54 +0000 Subject: [PATCH 09/42] remove old options --- options.toml | 14 +------------- presets/stm32l4r5zi_def.toml | 3 --- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/options.toml b/options.toml index ca15e12..a5a7941 100644 --- a/options.toml +++ b/options.toml @@ -30,16 +30,4 @@ default = false name = "Stack Pages" description = "Number of pages to allocate for the kernel stack." type = { type = "Integer", min = 1 } -default = 1 - -[tuning.appmemsize] -name = "Application Memory Size" -description = "Sets the size of the initial memory region for the init application. This memory is used for the heap and stack." -type = { type = "Integer", min = 0 } -default = 8192 - -[tuning.appstacksize] -name = "Application Stack Size" -description = "Sets the size of the stack for the init application. This must be less than the application memory size." -type = { type = "Integer", min = 0 } -default = 2048 \ No newline at end of file +default = 1 \ No newline at end of file diff --git a/presets/stm32l4r5zi_def.toml b/presets/stm32l4r5zi_def.toml index 8751d75..fc6a853 100644 --- a/presets/stm32l4r5zi_def.toml +++ b/presets/stm32l4r5zi_def.toml @@ -13,8 +13,5 @@ OSIRIS_DEBUG_RUNTIMESYMBOLS = "false" OSIRIS_TUNING_ENABLEFPU = "false" OSIRIS_STACKPAGES = "1" -OSIRIS_TUNING_APPSTACKSIZE = "2048" -OSIRIS_TUNING_APPMEMSIZE = "8192" - [build] target = "thumbv7em-none-eabi" \ No newline at end of file From 36103ed45d34d9e2841a451090851c247587e1a4 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Sat, 28 Mar 2026 13:57:48 +0000 Subject: [PATCH 10/42] fix lots of stuff --- build.rs | 109 +----------- examples/hello-world/src/main.rs | 10 +- machine/api/src/mem.rs | 16 +- machine/arm/src/asm.rs | 75 ++++++-- machine/arm/src/lib.rs | 6 +- machine/arm/src/panic.rs | 7 +- machine/testing/src/asm.rs | 6 +- src/error.rs | 178 +++++++++++++++++++ src/idle.rs | 15 +- src/lib.rs | 28 ++- src/main.rs | 1 - src/mem.rs | 25 +-- src/mem/alloc.rs | 4 +- src/mem/alloc/bestfit.rs | 38 +++-- src/mem/pfa.rs | 8 +- src/mem/pfa/bitset.rs | 13 +- src/mem/vmm.rs | 12 +- src/mem/vmm/nommu.rs | 19 +-- src/sched.rs | 282 ++++++++++++++++++++----------- src/sched/rr.rs | 35 ++-- src/sched/rt.rs | 15 +- src/sched/scheduler.rs | 206 ---------------------- src/sched/task.rs | 23 ++- src/sched/thread.rs | 116 ++++++++++--- src/sync/atomic.rs | 11 +- src/syscalls.rs | 4 +- src/syscalls/sched.rs | 29 ++++ src/syscalls/tasks.rs | 33 ---- src/types/array.rs | 34 ++-- src/types/boxed.rs | 12 +- src/types/heap.rs | 5 +- src/types/list.rs | 52 +++++- src/types/rbtree.rs | 31 ++++ src/uapi.rs | 2 + src/uapi/print.rs | 24 +++ src/uapi/sched.rs | 8 + src/uspace.rs | 16 +- src/utils.rs | 85 ---------- 38 files changed, 850 insertions(+), 743 deletions(-) create mode 100644 src/error.rs delete mode 100644 src/sched/scheduler.rs create mode 100644 src/syscalls/sched.rs delete mode 100644 src/syscalls/tasks.rs create mode 100644 src/uapi.rs create mode 100644 src/uapi/print.rs create mode 100644 src/uapi/sched.rs delete mode 100644 src/utils.rs diff --git a/build.rs b/build.rs index 582e86e..ff9b3cf 100644 --- a/build.rs +++ b/build.rs @@ -5,9 +5,8 @@ extern crate syn; extern crate walkdir; use cfg_aliases::cfg_aliases; -use quote::ToTokens; use std::io::Write; -use syn::{Attribute, FnArg, LitInt, punctuated::Punctuated, token::Comma}; +use syn::{Attribute, LitInt}; use walkdir::WalkDir; extern crate cbindgen; @@ -17,62 +16,12 @@ fn main() { println!("cargo::rerun-if-changed=build.rs"); generate_syscall_map("src/syscalls").expect("Failed to generate syscall map."); - generate_syscalls_export("src/syscalls").expect("Failed to generate syscall exports."); cfg_aliases! { freestanding: { all(not(test), not(doctest), not(doc), not(kani), any(target_os = "none", target_os = "unknown")) }, } } -fn generate_syscalls_export>(root: P) -> Result<(), std::io::Error> { - let syscalls = collect_syscalls_export(root); - - let out_dir = std::env::var("OUT_DIR").unwrap(); - let out_path = Path::new(&out_dir).join("syscalls_export.rs"); - let mut file = File::create(out_path)?; - - writeln!(file, "// This file is @generated by build.rs. Do not edit!")?; - - for (name, (number, inputs)) in &syscalls { - let mut args = &inputs.iter().fold("".to_owned(), |acc, arg| { - acc + "," + &arg.into_token_stream().to_string() - })[..]; - if !args.is_empty() { - args = &args[1..]; - } - let names = get_arg_names(args); - writeln!(file)?; - writeln!(file, "pub fn {name}({args}) {{")?; - writeln!(file, " hal::asm::syscall!({number}{names});")?; - writeln!(file, "}}")?; - } - - Ok(()) -} - -fn get_arg_names(args: &str) -> String { - if args.is_empty() { - return "".to_string(); - } - let mut in_arg_name = true; - - ", ".to_owned() - + &args.chars().fold("".to_owned(), |mut acc, char| { - if char.eq(&' ') { - in_arg_name = false; - return acc; - } - if char.eq(&',') { - in_arg_name = true; - return acc + ", "; - } - if in_arg_name { - acc.push(char); - } - acc - }) -} - fn generate_syscall_map>(root: P) -> Result<(), std::io::Error> { let syscalls = collect_syscalls(root); @@ -188,59 +137,3 @@ fn collect_syscalls>(root: P) -> HashMap { syscalls } - -type SyscallDataExport = (u16, Punctuated); - -fn collect_syscalls_export>(root: P) -> HashMap { - let mut syscalls = HashMap::new(); - let mut numbers = HashMap::new(); - - for entry in WalkDir::new(&root) { - let entry = match entry { - Ok(entry) => entry, - Err(_) => continue, - }; - - if entry.file_type().is_file() { - let path = entry.path(); - - println!("Processing file: {}", path.display()); - - let contents = match std::fs::read_to_string(path) { - Ok(contents) => contents, - Err(_) => continue, - }; - - let file = match syn::parse_file(&contents) { - Ok(file) => file, - Err(_) => continue, - }; - - for item in file.items { - let item = match item { - syn::Item::Fn(item) => item, - _ => continue, - }; - - let name = item.sig.ident.to_string(); - - if let Some(num) = is_syscall(&item.attrs, &name) { - if syscalls.contains_key(&name) { - println!("cargo:warning=Duplicate syscall handler: {name}"); - continue; - } - - if numbers.contains_key(&num) { - println!("cargo:warning=Duplicate syscall number: {num} for {name}"); - continue; - } - - syscalls.insert(name.clone(), (num, item.sig.inputs)); - numbers.insert(num, name); - } - } - } - } - - syscalls -} diff --git a/examples/hello-world/src/main.rs b/examples/hello-world/src/main.rs index d78020d..2702505 100644 --- a/examples/hello-world/src/main.rs +++ b/examples/hello-world/src/main.rs @@ -5,6 +5,12 @@ use osiris::app_main; #[app_main] fn main() { - osiris::syscall_print(0, "Hello World!".as_bytes().as_ptr(), 12); - loop {} + osiris::uprintln!("Hello World!"); + let mut tick = 0; + + loop { + osiris::uprintln!("Tick: {}", tick); + tick += 1; + osiris::uapi::sched::sleep_for(1000); + } } diff --git a/machine/api/src/mem.rs b/machine/api/src/mem.rs index 4aa98d6..d3a6223 100644 --- a/machine/api/src/mem.rs +++ b/machine/api/src/mem.rs @@ -1,7 +1,7 @@ -use core::{fmt::Display, ops::{Add, Div, Rem, Sub}, ptr::NonNull}; +use core::{fmt::{Display, LowerHex, UpperHex}, ops::{Add, Div, Rem, Sub}, ptr::NonNull}; #[repr(transparent)] -#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] pub struct PhysAddr(usize); impl PhysAddr { @@ -100,6 +100,18 @@ impl From for usize { } } +impl LowerHex for PhysAddr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:x}", self.0) + } +} + +impl UpperHex for PhysAddr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:X}", self.0) + } +} + #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] pub struct VirtAddr(usize); diff --git a/machine/arm/src/asm.rs b/machine/arm/src/asm.rs index 9f12f75..b69e90e 100644 --- a/machine/arm/src/asm.rs +++ b/machine/arm/src/asm.rs @@ -22,31 +22,57 @@ macro_rules! __macro_syscall { ($num:expr) => { use core::arch::asm; unsafe { - asm!("svc {0}", const $num); + asm!("svc {num}", num = const $num, clobber_abi("C")); } }; ($num:expr, $arg0:expr) => { use core::arch::asm; unsafe { - asm!("mov r0, {0}", "svc {1}", in(reg)$arg0, const $num); + asm!( + "svc {num}", + in("r0") $arg0, + num = const $num, + clobber_abi("C") + ); } }; ($num:expr, $arg0:expr, $arg1:expr) => { use core::arch::asm; unsafe { - asm!("mov r0, {0}", "mov r1, {1}", "svc {2}", in(reg)$arg0, in(reg)$arg1, const $num); + asm!( + "svc {num}", + in("r0") $arg0, + in("r1") $arg1, + num = const $num, + clobber_abi("C") + ); } }; ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => { use core::arch::asm; unsafe { - asm!("mov r0, {0}", "mov r1, {1}", "mov r2, {2}", "svc {3}", in(reg)$arg0, in(reg)$arg1, in(reg)$arg2, const $num); + asm!( + "svc {num}", + in("r0") $arg0, + in("r1") $arg1, + in("r2") $arg2, + num = const $num, + clobber_abi("C") + ); } }; ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => { use core::arch::asm; unsafe { - asm!("mov r0, {0}", "mov r1, {1}", "mov r2, {2}", "mov r3, {3}", "svc {4}", in(reg)$arg0, in(reg)$arg1, in(reg)$arg2, in(reg)$arg3, const $num); + asm!( + "svc {num}", + in("r0") $arg0, + in("r1") $arg1, + in("r2") $arg2, + in("r3") $arg3, + num = const $num, + clobber_abi("C") + ); } }; } @@ -65,17 +91,28 @@ pub use crate::__macro_syscall as syscall; #[cfg(not(feature = "host"))] #[inline(always)] -pub fn disable_interrupts() { +pub fn disable_irq_save() -> usize { use core::arch::asm; - use core::sync::atomic::compiler_fence; - unsafe { asm!("cpsid i", options(nomem, nostack, preserves_flags)) }; - compiler_fence(core::sync::atomic::Ordering::SeqCst); + let old: usize; + + unsafe { + asm!( + "mrs {old}, primask", + "cpsid i", + "isb", + old = out(reg) old, + options(nostack, preserves_flags) + ); + } + old } #[cfg(feature = "host")] #[inline(always)] -pub fn disable_interrupts() {} +pub fn disable_irq_save() -> usize { + 0 +} #[cfg(not(feature = "host"))] #[inline(always)] @@ -97,17 +134,23 @@ pub fn are_interrupts_enabled() -> bool { #[cfg(not(feature = "host"))] #[inline(always)] -pub fn enable_interrupts() { +pub fn enable_irq_restr(state: usize) { use core::arch::asm; - use core::sync::atomic::compiler_fence; - - unsafe { asm!("cpsie i", options(nomem, nostack, preserves_flags)) }; - compiler_fence(core::sync::atomic::Ordering::SeqCst); + + unsafe { + asm!( + "dsb", + "msr primask, {state}", + "isb", + state = in(reg) state, + options(nostack, preserves_flags) + ); + } } #[cfg(feature = "host")] #[inline(always)] -pub fn enable_interrupts() {} +pub fn enable_irq_restr(state: usize) {} #[cfg(not(feature = "host"))] #[macro_export] diff --git a/machine/arm/src/lib.rs b/machine/arm/src/lib.rs index 31666f5..00fb103 100644 --- a/machine/arm/src/lib.rs +++ b/machine/arm/src/lib.rs @@ -40,14 +40,14 @@ impl hal_api::Machinelike for ArmMachine { fn print(s: &str) -> Result<()> { use crate::asm; - asm::disable_interrupts(); + let state = asm::disable_irq_save(); if (unsafe { bindings::write_debug_uart(s.as_ptr() as *const c_char, s.len() as i32) } != 0) { - asm::enable_interrupts(); + asm::enable_irq_restr(state); Ok(()) } else { - asm::enable_interrupts(); + asm::enable_irq_restr(state); Err(hal_api::Error::default()) } } diff --git a/machine/arm/src/panic.rs b/machine/arm/src/panic.rs index 3d6dfbc..9025b8a 100644 --- a/machine/arm/src/panic.rs +++ b/machine/arm/src/panic.rs @@ -6,9 +6,6 @@ use core::panic::PanicInfo; use crate::asm; pub fn panic_handler(_info: &PanicInfo) -> ! { - asm::disable_interrupts(); - - loop { - core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); - } + asm::disable_irq_save(); + loop {} } diff --git a/machine/testing/src/asm.rs b/machine/testing/src/asm.rs index 9322be2..a303684 100644 --- a/machine/testing/src/asm.rs +++ b/machine/testing/src/asm.rs @@ -21,7 +21,9 @@ macro_rules! __macro_syscall { pub use crate::__macro_syscall as syscall; #[inline(always)] -pub fn disable_interrupts() {} +pub fn disable_irq_save() -> usize { + 0 +} #[inline(always)] pub fn are_interrupts_enabled() -> bool { @@ -29,7 +31,7 @@ pub fn are_interrupts_enabled() -> bool { } #[inline(always)] -pub fn enable_interrupts() {} +pub fn enable_irq_restr(state: usize) {} #[macro_export] macro_rules! __macro_startup_trampoline { diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..f9edb31 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,178 @@ +//! Utility functions and definitions for the kernel. +#![cfg_attr(feature = "nightly", feature(likely_unlikely))] + +use core::fmt::Display; +use hal::mem::PhysAddr; +use core::fmt::Debug; + +/// These two definitions are copied from https://github.com/rust-lang/hashbrown +#[cfg(not(feature = "nightly"))] +#[allow(unused_imports)] +pub(crate) use core::convert::{identity as likely, identity as unlikely}; + +#[cfg(feature = "nightly")] +pub(crate) use core::hint::{likely, unlikely}; + +pub type Result = core::result::Result; + +/// This is a macro that is used to panic when a bug is detected. +/// It is similar to the BUG() macro in the Linux kernel. Link: [https://www.kernel.org/]() +#[macro_export] +macro_rules! bug { + () => { + panic!("BUG at {}:{}", file!(), line!()); + }; + ($fmt:literal $(, $arg:expr)* $(,)?) => {{ + panic!(concat!("BUG at {}:{}: ", $fmt), file!(), line!() $(, $arg)*); + }}; +} + +#[macro_export] +macro_rules! warn { + () => { + kprintln!("WARN at {}:{}", file!(), line!()); + }; + ($fmt:literal $(, $arg:expr)* $(,)?) => {{ + kprintln!(concat!("WARN at {}:{}: ", $fmt), file!(), line!() $(, $arg)*); + }}; +} + +/// This is a macro that is used to panic when a condition is true. +/// It is similar to the BUG_ON() macro in the Linux kernel. Link: [https://www.kernel.org/]() +macro_rules! bug_on { + ($cond:expr) => {{ + let cond = $cond; + #[allow(unused_unsafe)] + if unsafe { $crate::error::unlikely(cond) } { + panic!("BUG({}) at {}:{}", stringify!($cond), file!(), line!()); + } + }}; + ($cond:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {{ + let cond = $cond; + #[allow(unused_unsafe)] + if unsafe { $crate::error::unlikely(cond) } { + panic!(concat!("BUG({}) at {}:{}: ", $fmt), stringify!($cond), file!(), line!() $(, $arg)*); + } + }}; +} + +macro_rules! warn_on { + ($cond:expr) => {{ + let cond = $cond; + #[allow(unused_unsafe)] + if unsafe { $crate::error::unlikely(cond) } { + kprintln!("WARN({}) at {}:{}", stringify!($cond), file!(), line!()); + } + }}; + ($cond:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {{ + let cond = $cond; + #[allow(unused_unsafe)] + if unsafe { $crate::error::unlikely(cond) } { + kprintln!(concat!("WARN({}) at {}:{}: ", $fmt), stringify!($cond), file!(), line!() $(, $arg)*); + } + }}; +} + +macro_rules! kerr { + ($kind:ident) => { + $crate::error::Error::new($crate::error::Kind::$kind) + }; + ($kind:expr, $msg:expr) => { + use $crate::error::Error; + #[cfg(feature = "error-msg")] + { + Error::new($crate::error::Kind::$kind).with_msg($msg) + } + #[cfg(not(feature = "error-msg"))] + { + Error::new($crate::error::Kind::$kind) + } + }; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Kind { + InvalidAlign, + OutOfMemory, + InvalidSize, + InvalidAddress(PhysAddr), + InvalidArgument, + NotFound, + Hal(hal::Error), +} + +impl Display for Kind { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Kind::InvalidAlign => write!(f, "Invalid alignment"), + Kind::OutOfMemory => write!(f, "Out of memory"), + Kind::InvalidSize => write!(f, "Invalid size"), + Kind::InvalidAddress(addr) => write!(f, "Invalid address: {addr:#x}"), + Kind::InvalidArgument => write!(f, "Invalid argument"), + Kind::NotFound => write!(f, "Not found"), + Kind::Hal(e) => write!(f, "HAL error: {e:?}"), + } + } +} + +pub struct Error { + pub kind: Kind, + #[cfg(feature = "error-msg")] + msg: Option<&'static str>, +} + +impl Error { + pub fn new(kind: Kind) -> Self { + #[cfg(feature = "error-msg")] + { + Self { kind, msg: None } + } + #[cfg(not(feature = "error-msg"))] + { + Self { kind } + } + } + + #[cfg(feature = "error-msg")] + pub fn with_msg(mut self, msg: &'static str) -> Self { + self.msg = Some(msg); + self + } +} + +impl Debug for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + #[cfg(feature = "error-msg")] + { + match self.msg { + Some(msg) => write!(f, "{}: {}", self.kind, msg), + None => write!(f, "{}", self.kind), + } + } + #[cfg(not(feature = "error-msg"))] + { + write!(f, "{}", self.kind) + } + } +} + +impl Display for Error { + #[cfg(not(feature = "error-msg"))] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.kind) + } + + #[cfg(feature = "error-msg")] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self.msg { + Some(msg) => write!(f, "{}: {}", self.kind, msg), + None => write!(f, "{}", self.kind), + } + } +} + +impl From for Error { + fn from(e: hal::Error) -> Self { + Self::new(Kind::Hal(e)) + } +} \ No newline at end of file diff --git a/src/idle.rs b/src/idle.rs index 7c79b37..70633a7 100644 --- a/src/idle.rs +++ b/src/idle.rs @@ -7,11 +7,10 @@ extern "C" fn entry() { } pub fn init() { - let attrs = sched::thread::Attributes { - entry, - fin: None, - }; - if let Err(e) = sched::create_thread(sched::task::KERNEL_TASK, &attrs) { - panic!("failed to create idle thread. Error: {e:?}"); - } -} \ No newline at end of file + let attrs = sched::thread::Attributes { entry, fin: None }; + sched::with(|sched| { + if let Err(e) = sched.create_thread(sched::task::KERNEL_TASK, &attrs) { + panic!("failed to create idle thread. Error: {}", e); + } + }); +} diff --git a/src/lib.rs b/src/lib.rs index c2de536..b027a6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,21 +6,22 @@ #[macro_use] mod macros; #[macro_use] -mod utils; +mod error; mod faults; mod mem; mod types; mod idle; +mod uspace; +mod print; -pub mod print; -pub mod sched; -pub mod sync; -pub mod syscalls; -pub mod time; -pub mod uspace; +mod sched; +mod sync; +mod syscalls; +mod time; + +pub mod uapi; use hal::Machinelike; -include!(concat!(env!("OUT_DIR"), "/syscalls_export.rs")); pub use hal; pub use proc_macros::app_main; @@ -43,17 +44,12 @@ pub unsafe extern "C" fn kernel_init() -> ! { // Initialize the memory allocator. let kaddr_space = mem::init_memory(); - kprintln!("Memory initialized."); - if let Err(e) = sched::init(kaddr_space) { - panic!("failed to initialize scheduler. Error: {e:?}"); - } - + sched::init(kaddr_space); kprintln!("Scheduler initialized."); idle::init(); - kprintln!("Idle thread initialized."); let (cyc, ns) = hal::Machine::bench_end(); @@ -62,9 +58,7 @@ pub unsafe extern "C" fn kernel_init() -> ! { ); // Start the init application. - if let Err(e) = uspace::init_app() { - panic!("failed to start init application. Error: {e:?}"); - } + uspace::init_app(); sched::enable(); diff --git a/src/main.rs b/src/main.rs index 3191b0f..5ed2555 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,6 @@ pub extern "C" fn main() -> ! { #[unsafe(no_mangle)] pub extern "C" fn app_main() -> ! { - osiris::syscall_print(0, "Hello World!".as_bytes().as_ptr(), 12); loop {} } diff --git a/src/mem.rs b/src/mem.rs index 56a5074..f543db7 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -13,23 +13,6 @@ pub mod pfa; pub const BITS_PER_PTR: usize = core::mem::size_of::() * 8; -/// The possible types of memory. Which is compatible with the multiboot2 memory map. -/// Link: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html -#[repr(C)] -#[allow(unused)] -enum MemoryTypes { - /// Memory that is available for use. - Available = 1, - /// Memory that is reserved for the system. - Reserved = 2, - /// Memory that is reclaimable after ACPI tables are read. - ACPIReclaimable = 3, - /// ACPI Non-volatile-sleeping memory. - Nvs = 4, - /// Memory that is bad. - BadMemory = 5, -} - unsafe extern "C" { unsafe static __stack_top: u8; } @@ -46,18 +29,18 @@ static GLOBAL_ALLOCATOR: SpinLocked = pub fn init_memory() -> vmm::AddressSpace { let stack_top = &raw const __stack_top as usize; if let Err(e) = pfa::init_pfa(PhysAddr::new(stack_top)) { // TODO: Get this from the DeviceTree. - panic!("failed to initialize PFA. Error: {e:?}"); + panic!("failed to initialize PFA. Error: {e}"); } // TODO: Configure. let pgs = 10; let mut kaddr_space = vmm::AddressSpace::new(pgs).unwrap_or_else(|e| { - panic!("failed to create kernel address space. Error: {e:?}"); + panic!("failed to create kernel address space. Error: {e}"); }); let begin = kaddr_space.map(Region::new(None, 2 * PAGE_SIZE, Backing::Zeroed, Perms::all())).unwrap_or_else(|e| { - panic!("failed to map kernel address space. Error: {e:?}"); + panic!("failed to map kernel address space. Error: {e}"); }); { @@ -65,7 +48,7 @@ pub fn init_memory() -> vmm::AddressSpace { let range = begin..(begin + pgs * PAGE_SIZE); if let Err(e) = unsafe { allocator.add_range(&range) } { - panic!("failed to add range to allocator. Error: {e:?}"); + panic!("failed to add range to allocator. Error: {e}"); } } diff --git a/src/mem/alloc.rs b/src/mem/alloc.rs index 85d8830..c7a7831 100644 --- a/src/mem/alloc.rs +++ b/src/mem/alloc.rs @@ -5,7 +5,7 @@ use core::ptr::NonNull; use hal::mem::PhysAddr; -use crate::utils; +use crate::error::Result; pub mod bestfit; @@ -25,7 +25,7 @@ pub const MAX_ADDR: usize = usize::MAX; /// Each range added to the allocator must be valid for the whole lifetime of the allocator and must not overlap with any other range. /// The lifetime of any allocation is only valid as long as the allocator is valid. (A pointer must not be used after the allocator is dropped.) pub trait Allocator { - fn malloc(&mut self, size: usize, align: usize, request: Option) -> Result, utils::KernelError>; + fn malloc(&mut self, size: usize, align: usize, request: Option) -> Result>; unsafe fn free(&mut self, ptr: NonNull, size: usize); } diff --git a/src/mem/alloc/bestfit.rs b/src/mem/alloc/bestfit.rs index a14c251..9c91fa1 100644 --- a/src/mem/alloc/bestfit.rs +++ b/src/mem/alloc/bestfit.rs @@ -2,7 +2,7 @@ use core::{ops::Range, ptr::NonNull}; use hal::mem::PhysAddr; -use crate::utils::{self, KernelError}; +use crate::error::Result; /// The metadata that is before any block in the BestFitAllocator. struct BestFitMeta { @@ -44,13 +44,13 @@ impl BestFitAllocator { /// The range must be valid, 128bit aligned and must not overlapping with any other current or future range. /// The range must also be at least as large as `MIN_RANGE_SIZE`. /// Also the range must stay valid, for the whole lifetime of the allocator. Also the lifetime of any allocation is only valid as long as the allocator is valid. - pub unsafe fn add_range(&mut self, range: &Range) -> Result<(), utils::KernelError> { + pub unsafe fn add_range(&mut self, range: &Range) -> Result<()> { let ptr = range.start; // Check if the pointer is 128bit aligned. if !ptr.is_multiple_of(align_of::()) { - return Err(utils::KernelError::InvalidAlign); - } + return Err(kerr!(InvalidArgument)); + } debug_assert!(range.end > range.start); debug_assert!(range.end.diff(range.start) > size_of::() + Self::align_up()); @@ -92,8 +92,8 @@ impl BestFitAllocator { &mut self, size: usize, requested: Option, - ) -> Result<(NonNull, Option>), utils::KernelError> { - let mut best_fit = Err(utils::KernelError::OutOfMemory); + ) -> Result<(NonNull, Option>)> { + let mut best_fit = Err(kerr!(OutOfMemory)); let mut best_fit_size = usize::MAX; let mut current = self.head; @@ -190,27 +190,27 @@ impl super::Allocator for BestFitAllocator { /// `align` - The alignment of the block. /// /// Returns the user pointer to the block if successful, otherwise an error. - fn malloc(&mut self, size: usize, align: usize, request: Option) -> Result, utils::KernelError> { + fn malloc(&mut self, size: usize, align: usize, request: Option) -> Result> { // Check if the alignment is valid. if align > align_of::() { - return Err(utils::KernelError::InvalidAlign); + return Err(kerr!(InvalidArgument)); } if let Some(request) = request { if !request.is_multiple_of(align) { - return Err(utils::KernelError::InvalidAlign); + return Err(kerr!(InvalidArgument)); } } // Check if the size is valid. if size == 0 { - return Err(utils::KernelError::InvalidSize); + return Err(kerr!(InvalidArgument)); } // For some cfg this warning is correct. But for others its not. #[allow(clippy::absurd_extreme_comparisons)] if size >= super::MAX_ADDR { - return Err(utils::KernelError::InvalidSize); + return Err(kerr!(InvalidArgument)); } // Align the size. @@ -324,7 +324,7 @@ impl super::Allocator for BestFitAllocator { meta.next = self.head; // Check if the size of the block is correct. - BUG_ON!(meta.size != super::super::align_up(size), "Invalid size in free()"); + bug_on!(meta.size != super::super::align_up(size), "Invalid size in free()"); // Set the size of the block. meta.size = size; @@ -338,6 +338,8 @@ impl super::Allocator for BestFitAllocator { #[cfg(test)] mod tests { + use crate::error::Kind; + use super::*; use super::super::*; @@ -421,7 +423,7 @@ mod tests { let request = range.start + 4096; let ptr = allocator.malloc::(128, 1, Some(request)); - assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + assert!(ptr.is_err_and(|e| e.kind == Kind::OutOfMemory)); } #[test] @@ -436,7 +438,7 @@ mod tests { let request = range.start + 127; let ptr = allocator.malloc::(128, 8, Some(request)); - assert!(ptr.is_err_and(|e| e == utils::KernelError::InvalidAlign)); + assert!(ptr.is_err_and(|e| e.kind == Kind::InvalidAlign)); } #[test] @@ -453,7 +455,7 @@ mod tests { verify_block(ptr, 128, None); let ptr = allocator.malloc::(128, 1, Some(request)); - assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + assert!(ptr.is_err_and(|e| e.kind == Kind::OutOfMemory)); } #[test] @@ -468,7 +470,7 @@ mod tests { let request = range.end + 128; let ptr = allocator.malloc::(128, 1, Some(request)); - assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + assert!(ptr.is_err_and(|e| e.kind == Kind::OutOfMemory)); } #[test] @@ -534,7 +536,7 @@ mod tests { } let ptr = allocator.malloc::(SIZE, 1, None); - assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + assert!(ptr.is_err_and(|e| e.kind == Kind::OutOfMemory)); } #[test] @@ -646,7 +648,7 @@ mod tests { } let ptr = allocator.malloc::(SIZE, 1, None); - assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + assert!(ptr.is_err_and(|e| e.kind == Kind::OutOfMemory)); verify_ptrs_not_overlaping(ptrs.as_slice()); } diff --git a/src/mem/pfa.rs b/src/mem/pfa.rs index c62b8c1..e20f212 100644 --- a/src/mem/pfa.rs +++ b/src/mem/pfa.rs @@ -2,9 +2,9 @@ use hal::mem::PhysAddr; +use crate::error::Result; use crate::sync::spinlock::SpinLocked; use crate::types::boxed::Box; -use crate::utils::KernelError; use core::pin::Pin; @@ -27,16 +27,16 @@ trait Allocator { /// Safety: /// /// - The returned function must only be called with a useable and valid physical address. - fn initializer() -> unsafe fn(PhysAddr, usize) -> Result>, KernelError>; + fn initializer() -> unsafe fn(PhysAddr, usize) -> Result>>; fn alloc(&mut self, page_count: usize) -> Option; fn free(&mut self, addr: PhysAddr, page_count: usize); } -pub fn init_pfa(addr: PhysAddr) -> Result<(), KernelError> { +pub fn init_pfa(addr: PhysAddr) -> Result<()> { let mut pfa = PFA.lock(); if pfa.is_some() { - return Err(KernelError::CustomError("Page frame allocator is already initialized")); + return Err(kerr!(InvalidArgument)); } let initializer = AllocatorType::initializer(); diff --git a/src/mem/pfa/bitset.rs b/src/mem/pfa/bitset.rs index 391ab9a..7dc5692 100644 --- a/src/mem/pfa/bitset.rs +++ b/src/mem/pfa/bitset.rs @@ -4,8 +4,7 @@ use core::ptr::NonNull; use hal::mem::PhysAddr; use crate::{ - types::boxed::{self, Box}, - utils::KernelError, + error::Result, types::boxed::{self, Box} }; pub struct Allocator { @@ -33,17 +32,17 @@ impl Allocator { } impl super::Allocator for Allocator { - fn initializer() -> unsafe fn(PhysAddr, usize) -> Result>, KernelError> { - |addr: PhysAddr, pcnt: usize| -> Result>, KernelError> { + fn initializer() -> unsafe fn(PhysAddr, usize) -> Result>> { + |addr: PhysAddr, pcnt: usize| -> Result>> { if pcnt > N { todo!("Runtime page frame allocator for more than {} pages", N) } if !addr.is_multiple_of(core::mem::align_of::()) { - return Err(KernelError::InvalidAlign); + return Err(kerr!(InvalidArgument)); } - let ptr = NonNull::new(addr.as_mut_ptr::()).ok_or(KernelError::InvalidAddress(addr))?; + let ptr = NonNull::new(addr.as_mut_ptr::()).ok_or(kerr!(InvalidArgument))?; // Align this up to PAGE_SIZE let begin = addr + size_of::(); let begin = if begin.is_multiple_of(super::PAGE_SIZE) { @@ -52,7 +51,7 @@ impl super::Allocator for Allocator { PhysAddr::new((begin.as_usize() + super::PAGE_SIZE - 1) & !(super::PAGE_SIZE - 1)) }; // TODO: Subtract the needed pages from the available - unsafe { core::ptr::write(ptr.as_ptr(), Self::new(begin).ok_or(KernelError::InvalidAddress(begin))?) }; + unsafe { core::ptr::write(ptr.as_ptr(), Self::new(begin).ok_or(kerr!(InvalidArgument))?) }; // Safety: Ptr is properly aligned and non-null. The validity of the memory at that address is valid by the call contract. Ok(Pin::new(unsafe { boxed::Box::from_raw(ptr) })) diff --git a/src/mem/vmm.rs b/src/mem/vmm.rs index ee86228..6ddc7e9 100644 --- a/src/mem/vmm.rs +++ b/src/mem/vmm.rs @@ -1,6 +1,6 @@ use hal::mem::{PhysAddr, VirtAddr}; -use crate::{utils::KernelError}; +use crate::error::Result; mod nommu; @@ -62,12 +62,12 @@ impl Region { pub trait AddressSpacelike { // Size is the amount of pages in the address space. On nommu systems this will be reserved. - fn new(pages: usize) -> Result where Self: Sized; - fn map(&mut self, region: Region) -> Result; - fn unmap(&mut self, region: &Region) -> Result<(), KernelError>; - fn protect(&mut self, region: &Region, perms: Perms) -> Result<(), KernelError>; + fn new(pages: usize) -> Result where Self: Sized; + fn map(&mut self, region: Region) -> Result; + fn unmap(&mut self, region: &Region) -> Result<()>; + fn protect(&mut self, region: &Region, perms: Perms) -> Result<()>; fn virt_to_phys(&self, addr: VirtAddr) -> Option; fn phys_to_virt(&self, addr: PhysAddr) -> Option; fn end(&self) -> VirtAddr; - fn activate(&self) -> Result<(), KernelError>; + fn activate(&self) -> Result<()>; } \ No newline at end of file diff --git a/src/mem/vmm/nommu.rs b/src/mem/vmm/nommu.rs index 8927471..f169c14 100644 --- a/src/mem/vmm/nommu.rs +++ b/src/mem/vmm/nommu.rs @@ -3,11 +3,10 @@ use core::ptr::copy_nonoverlapping; use hal::mem::{PhysAddr, VirtAddr}; use crate::{ - mem::{ + error::Result, mem::{ alloc::{Allocator, bestfit}, pfa, vmm, - }, - utils::KernelError, + } }; pub struct AddressSpace { @@ -17,11 +16,11 @@ pub struct AddressSpace { } impl vmm::AddressSpacelike for AddressSpace { - fn new(pgs: usize) -> Result { - let begin = pfa::alloc_page(pgs).ok_or(KernelError::OutOfMemory)?; + fn new(pgs: usize) -> Result { + let begin = pfa::alloc_page(pgs).ok_or(kerr!(OutOfMemory))?; let end = begin .checked_add(pgs * pfa::PAGE_SIZE) - .ok_or(KernelError::OutOfMemory)?; + .ok_or(kerr!(OutOfMemory))?; let mut allocator = bestfit::BestFitAllocator::new(); unsafe { allocator.add_range(&(begin..end))? }; @@ -33,7 +32,7 @@ impl vmm::AddressSpacelike for AddressSpace { }) } - fn map(&mut self, region: vmm::Region) -> Result { + fn map(&mut self, region: vmm::Region) -> Result { let req = region.start.and_then(|virt| self.virt_to_phys(virt)); // TODO: per page align let align = core::mem::align_of::(); @@ -54,11 +53,11 @@ impl vmm::AddressSpacelike for AddressSpace { Ok(start.into()) } - fn unmap(&mut self, _region: &vmm::Region) -> Result<(), KernelError> { + fn unmap(&mut self, _region: &vmm::Region) -> Result<()> { Ok(()) } - fn protect(&mut self, _region: &vmm::Region, _perms: vmm::Perms) -> Result<(), KernelError> { + fn protect(&mut self, _region: &vmm::Region, _perms: vmm::Perms) -> Result<()> { Ok(()) } @@ -76,7 +75,7 @@ impl vmm::AddressSpacelike for AddressSpace { self.phys_to_virt(self.end).unwrap() } - fn activate(&self) -> Result<(), KernelError> { + fn activate(&self) -> Result<()> { Ok(()) } } diff --git a/src/sched.rs b/src/sched.rs index 510ff80..39583d8 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -1,32 +1,44 @@ //! This module provides access to the scheduler. mod dispch; -pub mod rt; pub mod rr; +pub mod rt; pub mod task; pub mod thread; -use core::{ffi::c_void, sync::atomic::{AtomicBool, Ordering}}; +use core::{ + ffi::c_void, + sync::atomic::{AtomicBool, Ordering}, +}; use hal::Schedable; use crate::{ - mem, sync::{atomic::AtomicU64, spinlock::SpinLocked}, time::{self, tick}, types::{ + error::Result, + mem, + sched::thread::Waiter, + sync::{self, atomic::AtomicU64, spinlock::SpinLocked}, + time::{self}, + types::{ array::IndexMap, rbtree::RbTree, - traits::{Get, GetMut}, + traits::{Get, GetMut, Project}, view::ViewMut, - }, utils::KernelError + }, }; type ThreadMap = IndexMap; type TaskMap = IndexMap; -static SCHED: SpinLocked> = SpinLocked::new(Scheduler::new()); +type GlobalScheduler = Scheduler<32>; + +static SCHED: SpinLocked = SpinLocked::new(GlobalScheduler::new()); static DISABLED: AtomicBool = AtomicBool::new(true); static NEXT_TICK: AtomicU64 = AtomicU64::new(0); +type WaiterView<'a, const N: usize> = ViewMut<'a, thread::UId, thread::Waiter, ThreadMap>; + pub struct Scheduler { threads: ThreadMap, tasks: TaskMap, @@ -55,98 +67,186 @@ impl Scheduler { } } - fn land(&mut self, ctx: *mut c_void) -> Result<(), KernelError> { + fn land(&mut self, ctx: *mut c_void) { if let Some(current) = self.current { - let thread = self.threads.get_mut(current).ok_or(KernelError::InvalidArgument)?; - return thread.save_ctx(ctx); + let mut kill = None; + if let Some(thread) = self.threads.get_mut(current) { + if thread.save_ctx(ctx).is_err() { + warn!( + "failed to save context (SP: {:x}) of thread {}.", + ctx as usize, current + ); + kill = Some(thread.task_id()); + } + } else { + bug!("failed to land thread {}. Does not exist.", current); + } + + if let Some(task_id) = kill { + self.dequeue(current); + self.current = None; + self.kill_task(task_id); + } } + } - Ok(()) + fn schedule_resched(now: u64, next: u64) { + let old = NEXT_TICK.load(Ordering::Acquire); + + if old > now && old <= next { + return; + } + + NEXT_TICK.store(next, Ordering::Release); } - pub fn enqueue(&mut self, uid: thread::UId) -> Result<(), KernelError> { - let thread = self.threads.get(uid).ok_or(KernelError::InvalidArgument)?; + pub fn enqueue(&mut self, now: u64, uid: thread::UId) -> Result<()> { + let thread = self.threads.get(uid).ok_or(kerr!(InvalidArgument))?; if thread.rt_server().is_some() { - let mut view = - ViewMut::>::new(&mut self.threads); - self.rt_scheduler.enqueue(uid, &mut view); + let mut view = rt::ServerView::::new(&mut self.threads); + self.rt_scheduler.enqueue(uid, now, &mut view); } else { self.rr_scheduler.enqueue(uid, &mut self.threads)?; } - - // A new thread was added -> Trigger a reschedule. - NEXT_TICK.store(tick(), Ordering::Release); + reschedule(); Ok(()) } - pub fn do_sched( - &mut self, - now: u64, - old: Option, - ) -> Option<(*mut c_void, &mut task::Task)> { + fn do_wakeups(&mut self, now: u64) { + while let Some(uid) = self.wakeup.min() { + { + let mut view = WaiterView::::new(&mut self.threads); + let waiter = view.get(uid).expect("THIS IS A BUG!"); + + if waiter.until() > now { + Self::schedule_resched(now, waiter.until()); + break; + } + + self.wakeup.remove(uid, &mut view); + } + + self.enqueue(now, uid); + } + } + + pub fn do_sched(&mut self, now: u64) -> Option<(*mut c_void, &mut task::Task)> { let dt = now - self.last_tick; self.last_tick = now; - if let Some(old) = old { + if let Some(old) = self.current { let mut view = rt::ServerView::::new(&mut self.threads); - // If this is not a real-time thread, this will just do nothing. self.rt_scheduler.put(old, dt, &mut view); - // If this is not a round-robin thread, this will just do nothing. self.rr_scheduler.put(old, dt); - - // TODO: thread is still enqueued. Dequeue if blocked or sleeping and put to the respective tree/list. - // If it exited remove it completely. } + self.do_wakeups(now); + let mut view = rt::ServerView::::new(&mut self.threads); - let (new, budget) = if let Some((new, budget)) = self.rt_scheduler.pick(now, &mut view) { - (new, budget) - } else if let Some((new, budget)) = self.rr_scheduler.pick(&mut self.threads) { - (new, budget) - } else { - // No thread to run. Run the idle thread. - (thread::IDLE_THREAD, u64::MAX) - }; + let (new, budget) = self + .rt_scheduler + .pick(now, &mut view) + .or_else(|| self.rr_scheduler.pick(&mut self.threads)) + .unwrap_or((thread::IDLE_THREAD, 1000)); let ctx = self.threads.get(new)?.ctx(); let task = self.tasks.get_mut(self.threads.get(new)?.task_id())?; self.current = Some(new); + let next = now.saturating_add(budget); - // Only store next_tick if now + budget is smaller than the current next tick. - let next_tick = now + budget; - let mut old_tick = NEXT_TICK.load(Ordering::Acquire); + Self::schedule_resched(now, next); + Some((ctx, task)) + } - while NEXT_TICK.compare_exchange(old_tick, next_tick, Ordering::Release, Ordering::Acquire).is_err() { - old_tick = NEXT_TICK.load(Ordering::Acquire); - if next_tick >= old_tick { - break; - } + pub fn sleep_until(&mut self, until: u64, now: u64) -> Result<()> { + if until <= now { + return Ok(()); } + let uid = self.current.ok_or(kerr!(InvalidArgument))?; - Some((ctx, task)) + if let Some(thread) = self.threads.get_mut(uid) { + thread.set_waiter(Some(Waiter::new(until, uid))); + } else { + bug!( + "failed to put current thread {} to sleep. Does not exist.", + uid + ); + } + + if self + .wakeup + .insert(uid, &mut WaiterView::::new(&mut self.threads)) + .is_err() + { + bug!("failed to insert thread {} into wakeup tree.", uid); + } + + + + self.dequeue(uid); + reschedule(); + Ok(()) + } + + pub fn kick(&mut self, uid: thread::UId) -> Result<()> { + let thread = self.threads.get_mut(uid).ok_or(kerr!(InvalidArgument))?; + if let Some(waiter) = Project::::project_mut(thread) { + waiter.set_until(0); + } + Ok(()) } - pub fn dequeue(&mut self, uid: thread::UId) -> Option { + pub fn dequeue(&mut self, uid: thread::UId) { let mut view = rt::ServerView::::new(&mut self.threads); - // If this is not a real-time thread, this will just do nothing. self.rt_scheduler.dequeue(uid, &mut view); - - self.threads.remove(&uid) + self.rr_scheduler.dequeue(uid, &mut self.threads); } - pub fn create_task(&mut self, task: &task::Attributes) -> Result { - let uid = task::UId::new(self.id_gen).ok_or(KernelError::InvalidArgument)?; + pub fn create_task(&mut self, task: &task::Attributes) -> Result { + let uid = task::UId::new(self.id_gen).ok_or(kerr!(InvalidArgument))?; self.id_gen += 1; self.tasks.insert(&uid, task::Task::new(uid, task)?)?; Ok(uid) } - pub fn create_thread(&mut self, task: task::UId, attrs: &thread::Attributes) -> Result { - let task = self.tasks.get_mut(task).ok_or(KernelError::InvalidArgument)?; + pub fn kill_task(&mut self, uid: task::UId) -> Result<()> { + let task_id = self.tasks.get(uid).ok_or(kerr!(InvalidArgument))?.id; + self.tasks.remove(&uid).ok_or(kerr!(InvalidArgument))?; + + let begin = match self.threads.next(None) { + Some(i) => i, + None => return Ok(()), + }; + let mut i = begin; + + while i != begin { + i = (i + 1) % N; + + let mut id = None; + if let Some(thread) = self.threads.at_cont(i) { + if thread.task_id() == task_id { + id = Some(thread.uid()); + } + } + + if let Some(id) = id { + self.dequeue(id); + } + } + + Ok(()) + } + + pub fn create_thread( + &mut self, + task: task::UId, + attrs: &thread::Attributes, + ) -> Result { + let task = self.tasks.get_mut(task).ok_or(kerr!(InvalidArgument))?; let thread = task.create_thread(self.id_gen, attrs)?; let uid = thread.uid(); @@ -157,23 +257,24 @@ impl Scheduler { } } -pub fn init(kaddr_space: mem::vmm::AddressSpace) -> Result<(), KernelError> { - let mut sched = SCHED.lock(); - let uid = task::KERNEL_TASK; - sched.tasks.insert(&uid, task::Task::from_addr_space(uid, kaddr_space)?) +pub fn with T>(f: F) -> T { + sync::atomic::irq_free(|| { + let mut sched = SCHED.lock(); + f(&mut sched) + }) } -pub fn create_task(attrs: &task::Attributes) -> Result { - SCHED.lock().create_task(attrs) -} - -pub fn create_thread(task: task::UId, attrs: &thread::Attributes) -> Result { - let mut sched = SCHED.lock(); - sched.create_thread(task, attrs) -} - -pub fn enqueue(uid: thread::UId) -> Result<(), KernelError> { - SCHED.lock().enqueue(uid) +pub fn init(kaddr_space: mem::vmm::AddressSpace) { + with(|sched| { + let uid = task::KERNEL_TASK; + if let Ok(task) = task::Task::from_addr_space(uid, kaddr_space) { + if sched.tasks.insert(&uid, task).is_err() { + panic!("failed to create kernel task."); + } + } else { + panic!("failed to create kernel address space."); + } + }) } pub fn needs_reschedule(now: u64) -> bool { @@ -196,41 +297,30 @@ pub fn enable() { /// Reschedule the tasks. pub fn reschedule() { + if DISABLED.load(Ordering::Acquire) { + return; + } + hal::Machine::trigger_reschedule(); } /// cbindgen:ignore /// cbindgen:no-export #[unsafe(no_mangle)] -pub extern "C" fn sched_enter(ctx: *mut c_void) -> *mut c_void { - let mut sched = SCHED.lock(); - let mut broken = false; - let old = sched.current; - - if sched.land(ctx).is_err() { - sched.current.inspect(|uid| { - if *uid == thread::IDLE_THREAD { - BUG!("failed to land the idle thread. something is horribly broken."); - } +pub extern "C" fn sched_enter(mut ctx: *mut c_void) -> *mut c_void { + with(|sched| { + let old = sched.current.map(|c| c.owner()); + sched.land(ctx); - // If we cannot reasonably land. We dequeue the thread. - sched.dequeue(*uid); - // TODO: Warn - sched.current = None; - broken = true; - }); - } - - if let Some((ctx, task)) = sched.do_sched(time::tick(), old) { - if let Some(old) = old - && task.id != old.owner() { + if let Some((new, task)) = sched.do_sched(time::tick()) { + if old != Some(task.id) { dispch::prepare(task); } - - ctx - } else if broken { - BUG!("failed to reschedule after a failed landing. something is horribly broken."); - } else { + ctx = new; + } else { + bug!("failed to schedule a thread. No threads available."); + } + ctx - } + }) } diff --git a/src/sched/rr.rs b/src/sched/rr.rs index c4613f2..ee9f877 100644 --- a/src/sched/rr.rs +++ b/src/sched/rr.rs @@ -1,10 +1,5 @@ use crate::{ - sched::{ - thread::{self}, - }, - types::{ - list::List, - }, + error::Result, sched::thread::{self}, types::list::List }; pub struct Scheduler { @@ -21,8 +16,8 @@ impl Scheduler { Self { queue: List::new(), current: None, current_left: 0, quantum: 1000 } } - pub fn enqueue(&mut self, uid: thread::UId, storage: &mut super::ThreadMap) -> Result<(), crate::utils::KernelError> { - self.queue.push_back(uid, storage).map_err(|_| crate::utils::KernelError::InvalidArgument) + pub fn enqueue(&mut self, uid: thread::UId, storage: &mut super::ThreadMap) -> Result<()> { + self.queue.push_back(uid, storage).map_err(|_| kerr!(InvalidArgument)) } pub fn put(&mut self, uid: thread::UId, dt: u64) { @@ -34,15 +29,29 @@ impl Scheduler { } pub fn pick(&mut self, storage: &mut super::ThreadMap) -> Option<(thread::UId, u64)> { - if self.current_left == 0 { - if let Some(current) = self.current { + match self.current { + Some(current) if self.current_left > 0 => return Some((current, self.current_left)), + Some(current) => { + self.queue.pop_front(storage); self.queue.push_back(current, storage); - } - self.current = self.queue.pop_front(storage).ok().flatten(); - self.current_left = self.quantum; + self.current = self.queue.head(); + self.current_left = self.quantum; + } + None => { + self.current = self.queue.head(); + self.current_left = self.quantum; + } } self.current.map(|id| (id, self.current_left)) } + + pub fn dequeue(&mut self, uid: thread::UId, storage: &mut super::ThreadMap) { + self.queue.remove(uid, storage); + + if self.current == Some(uid) { + self.current = None; + } + } } diff --git a/src/sched/rt.rs b/src/sched/rt.rs index b5a66b9..7c3405b 100644 --- a/src/sched/rt.rs +++ b/src/sched/rt.rs @@ -13,13 +13,20 @@ impl Scheduler { } } - pub fn enqueue(&mut self, uid: thread::UId, storage: &mut ServerView) { - self.edf.insert(uid, storage); + pub fn enqueue(&mut self, uid: thread::UId, now: u64, storage: &mut ServerView) { + if let Some(server) = storage.get_mut(uid) { + server.replenish(now); + self.edf.insert(uid, storage); + } } pub fn put(&mut self, uid: thread::UId, dt: u64, storage: &mut ServerView) { - if let Some(server) = storage.get_mut(uid) { - server.consume(dt); + if Some(uid) == self.edf.min() { + if let Some(server) = storage.get_mut(uid) { + server.consume(dt); + } else { + bug!("thread {} not found in storage", uid); + } } } diff --git a/src/sched/scheduler.rs b/src/sched/scheduler.rs deleted file mode 100644 index aded20b..0000000 --- a/src/sched/scheduler.rs +++ /dev/null @@ -1,206 +0,0 @@ -//! The scheduler module is responsible for managing the tasks and threads in the system. -//! It provides the necessary functions to create tasks and threads, and to switch between them. - -use core::{ffi::c_void, sync::atomic::AtomicBool}; - -use super::task::{Task, TaskId}; -use crate::{ - mem::{self, array::IndexMap, heap::BinaryHeap, queue::Queue}, - sched::{ - task::TaskDescriptor, - thread::{RunState, ThreadMap, ThreadUId, Timing}, - }, - sync::spinlock::SpinLocked, - utils, -}; - -/// The global scheduler instance. -pub static SCHEDULER: SpinLocked = SpinLocked::new(Scheduler::new()); -static SCHEDULER_ENABLED: AtomicBool = AtomicBool::new(false); - -/// The scheduler struct. It keeps track of the tasks and threads in the system. -/// This scheduler is a simple Rate Monotonic Scheduler (RMS) implementation. -#[derive(Debug)] -pub struct Scheduler { - /// The current running thread. - current: Option, - /// Fast interval store. This gets updated every time a new thread is selected. - current_interval: usize, - /// Stores the tasks in the system. - user_tasks: IndexMap, - /// Stores the threads in the system. - threads: ThreadMap<8>, - /// The priority queue that yields the next thread to run. - queue: BinaryHeap<(usize, ThreadUId), 32>, - /// The callbacks queue that stores the threads that need to be fired in the future. - callbacks: Queue<(ThreadUId, usize), 32>, - /// The progression of the time interval of the scheduler. - time: usize, -} - -impl Scheduler { - /// Create a new scheduler instance. - pub const fn new() -> Self { - Self { - current: None, - current_interval: 0, - user_tasks: IndexMap::new(), - threads: ThreadMap::new(), - queue: BinaryHeap::new(), - callbacks: Queue::new(), - time: 0, - } - } - - pub fn create_task(&mut self, desc: TaskDescriptor) -> Result { - let size = mem::align_up(desc.mem_size); - let idx = self - .user_tasks - .find_empty() - .ok_or(utils::KernelError::OutOfMemory)?; - let task_id = TaskId::new_user(idx); - - let task = Task::new(size, task_id)?; - self.user_tasks.insert(&idx, task)?; - Ok(task_id) - } - - pub fn create_thread( - &mut self, - entry: extern "C" fn(), - fin: Option !>, - timing: Timing, - task_id: TaskId, - ) -> Result { - let task_idx: usize = task_id.into(); - - if let Some(task) = self.user_tasks.get_mut(&task_idx) { - let desc = task.create_thread(entry, fin, timing)?; - let id = self.threads.create(desc)?; - self.queue.push((timing.period, id))?; - Ok(id) - } else { - Err(utils::KernelError::InvalidArgument) - } - } - - /// Updates the current thread context with the given context. - /// - /// `ctx` - The new context to update the current thread with. - fn update_current_ctx(&mut self, ctx: *mut c_void) { - if let Some(id) = self.current - && let Some(thread) = self.threads.get_mut(&id) - { - thread - .update_sp(ctx) - .expect("Failed to update thread context"); - } - } - - /// Selects a new thread to run, sets the previous thread as ready, and sets the new thread as runs. - /// The old thread will be added to the queue to be fired in the next period. - /// The new thread will be selected based on the priority queue. - /// - /// Returns the context of the new thread to run, or `None` if no thread is available. - fn select_new_thread(&mut self) -> Option<*mut c_void> { - if let Some(id) = self.queue.pop().map(|(_, id)| id) { - // Set the previous thread as ready. And add a callback from now. - if let Some(id) = self.current - && let Some(thread) = self.threads.get_mut(&id) - { - thread.update_run_state(RunState::Ready); - // The delay that is already in the queue. - let delay = self.callbacks.back().map(|(_, delay)| *delay).unwrap_or(0); - // Check if the period is already passed. - if thread.timing().period > (self.time + delay) { - // Add the callback to the queue. If it fails, we can't do much. - let _ = self - .callbacks - .push_back((id, thread.timing().period - (self.time + delay))); - } else { - // If the period is already passed, add it to the queue immediately. - let _ = self.queue.push((thread.timing().exec_time, id)); - } - } - - if let Some(thread) = self.threads.get_mut(&id) { - thread.update_run_state(RunState::Runs); - - // Set the new thread as the current one. - self.current_interval = thread.timing().exec_time; - self.current = Some(id); - - // Return the new thread context. - return Some(thread.sp()); - } - } - - None - } - - /// Fires the thread if necessary. - /// - /// Returns `true` if a thread was fired, otherwise `false`. - fn fire_thread_if_necessary(&mut self) -> bool { - let mut found = false; - while let Some((id, cnt)) = self.callbacks.front().cloned() { - // If the delay is 0, we can fire the thread. - if cnt - 1 == 0 { - self.callbacks.pop_front(); - if let Some(thread) = self.threads.get_mut(&id) { - thread.update_run_state(RunState::Ready); - - let _ = self.queue.push((thread.timing().exec_time, id)); - found = true; - } - } else { - // If the delay is not 0, we need to update the delay and reinsert it. - let _ = self.callbacks.insert(0, (id, cnt - 1)); - break; - } - } - - found - } - - /// Ticks the scheduler. This function is called every time the system timer ticks. - pub fn tick(&mut self) -> bool { - self.time += 1; - - // If a thread was fired, we need to reschedule. - if self.fire_thread_if_necessary() { - return true; - } - - // If the current thread is done, we need to reschedule. - if self.time >= self.current_interval { - self.time = 0; - return true; - } - - false - } -} - -pub fn enabled() -> bool { - SCHEDULER_ENABLED.load(core::sync::atomic::Ordering::Acquire) -} - -pub fn set_enabled(enabled: bool) { - SCHEDULER_ENABLED.store(enabled, core::sync::atomic::Ordering::Release); -} - -/// cbindgen:ignore -/// cbindgen:no-export -#[unsafe(no_mangle)] -pub extern "C" fn sched_enter(ctx: *mut c_void) -> *mut c_void { - { - let mut scheduler = SCHEDULER.lock(); - - // Update the current context. - scheduler.update_current_ctx(ctx); - - // Select a new thread to run, if available. - scheduler.select_new_thread().unwrap_or(ctx) - } -} diff --git a/src/sched/task.rs b/src/sched/task.rs index 9527f81..52b0345 100644 --- a/src/sched/task.rs +++ b/src/sched/task.rs @@ -1,4 +1,5 @@ //! This module provides the basic task and thread structures for the scheduler. +use core::fmt::Display; use core::num::NonZero; use core::borrow::Borrow; @@ -7,12 +8,12 @@ use hal::{Stack}; use hal::stack::{Stacklike}; +use crate::error::Result; use crate::sched::thread; use crate::{mem, sched}; use crate::mem::vmm::{AddressSpacelike}; use crate::types::traits::ToIndex; -use crate::utils::KernelError; pub struct Defaults { pub stack_pages: usize, @@ -50,6 +51,12 @@ impl ToIndex for UId { } } +impl Display for UId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Task-{}", self.uid) + } +} + pub struct Attributes { pub resrv_pgs: Option>, } @@ -65,14 +72,14 @@ pub struct Task { } impl Task { - pub fn new(id: UId, attrs: &Attributes) -> Result { + pub fn new(id: UId, attrs: &Attributes) -> Result { // TODO: On MMU systems, the resrv_pgs attribute will be ignored, as memory will not be reserved. - let resrv_pgs = attrs.resrv_pgs.ok_or(KernelError::OutOfMemory)?; + let resrv_pgs = attrs.resrv_pgs.ok_or(kerr!(InvalidArgument))?; let address_space = mem::vmm::AddressSpace::new(resrv_pgs.get())?; Self::from_addr_space(id, address_space) } - pub fn from_addr_space(id: UId, address_space: mem::vmm::AddressSpace) -> Result { + pub fn from_addr_space(id: UId, address_space: mem::vmm::AddressSpace) -> Result { Ok(Self { id, address_space, @@ -90,7 +97,7 @@ impl Task { fn allocate_stack( &mut self, attrs: &thread::Attributes, - ) -> Result { + ) -> Result { let size = DEFAULTS.stack_pages * mem::pfa::PAGE_SIZE; let region = mem::vmm::Region::new( None, @@ -112,7 +119,7 @@ impl Task { &mut self, uid: usize, attrs: &thread::Attributes, - ) -> Result { + ) -> Result { let stack = self.allocate_stack(attrs)?; let stack = unsafe { Stack::new(stack) }?; @@ -120,4 +127,8 @@ impl Task { Ok(sched::thread::Thread::new(tid.get_uid(uid), stack)) } + + pub fn tid_cntr(&self) -> usize { + self.tid_cntr + } } diff --git a/src/sched/thread.rs b/src/sched/thread.rs index 75a1890..86af4aa 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -1,17 +1,20 @@ // ----------------------------------- Identifiers ----------------------------------- +use core::fmt::Display; use core::{borrow::Borrow, ffi::c_void}; use hal::{Stack, stack::EntryFn}; use hal::stack::{FinFn, Stacklike}; use proc_macros::TaggedLinks; +use crate::error::Result; use crate::sched::task::{self, KERNEL_TASK}; +use crate::time::tick; use crate::types::list; -use crate::{types::{rbtree::{self, Compare}, traits::{Project, ToIndex}}, utils::KernelError}; +use crate::{types::{rbtree::{self, Compare}, traits::{Project, ToIndex}}}; pub const IDLE_THREAD: UId = UId { - uid: 0, + uid: 1, tid: Id { id: 0, owner: KERNEL_TASK }, }; @@ -94,6 +97,12 @@ impl ToIndex for UId { } } +impl Display for UId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "T{}-{}", self.tid.owner(), self.tid.as_usize()) + } +} + // ------------------------------------------------------------------------- /// The state of a thread. @@ -118,9 +127,8 @@ pub struct State { #[derive(TaggedLinks)] pub struct RtServer { budget: u64, - total_budget: u64, - - reservation: u64, + budget_left: u64, + period: u64, deadline: u64, // Back-reference to the thread uid. @@ -132,32 +140,35 @@ pub struct RtServer { } impl RtServer { - pub fn new(budget: u64, reservation: u64, deadline: u64, uid: UId) -> Self { + pub fn new(budget: u64, period: u64, uid: UId) -> Self { Self { budget, - total_budget: budget, - reservation, - deadline, + budget_left: budget, + period, + deadline: tick() + period, uid, _rt_links: rbtree::Links::new(), } } + pub fn budget_left(&self) -> u64 { + self.budget_left + } + pub fn budget(&self) -> u64 { self.budget } pub fn replenish(&mut self, now: u64) { - let next = self.deadline + self.reservation; - self.deadline = next.max(now + self.reservation); - self.budget = self.total_budget; + self.deadline += self.period; + self.budget_left = self.budget; } pub fn consume(&mut self, dt: u64) { - if self.budget >= dt { - self.budget -= dt; + if self.budget_left >= dt { + self.budget_left -= dt; } else { - self.budget = 0; + self.budget_left = 0; } } @@ -182,6 +193,46 @@ impl Compare for RtServer { } } +#[derive(Debug, Clone, Copy)] +#[derive(TaggedLinks)] +pub struct Waiter { + /// The time when the Thread will be awakened. + until: u64, + + // Back-reference to the thread uid. + uid: UId, + /// Wakup tree links for the thread. + #[rbtree(tag = WakupTree, idx = UId)] + _wakeup_links: rbtree::Links, +} + +impl Waiter { + pub fn new(until: u64, uid: UId) -> Self { + Self { + until, + uid, + _wakeup_links: rbtree::Links::new(), + } + } + + pub fn until(&self) -> u64 { + self.until + } + + pub fn set_until(&mut self, until: u64) { + self.until = until; + } +} + +impl Compare for Waiter { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + match self.until.cmp(&other.until) { + core::cmp::Ordering::Equal => self.uid.cmp(&other.uid), + ord => ord, + } + } +} + #[derive(Debug, Clone, Copy)] pub struct WakupTree; #[derive(Debug, Clone, Copy)] @@ -205,9 +256,8 @@ pub struct Thread { uid: UId, /// If the thread is real-time, its contains a constant bandwidth server. rt_server: Option, - /// Wakup tree links for the thread. - #[rbtree(tag = WakupTree, idx = UId)] - _wakeup_links: rbtree::Links, + + waiter: Option, #[list(tag = RRList, idx = UId)] rr_links: list::Links, @@ -228,12 +278,20 @@ impl Thread { }, uid, rt_server: None, - _wakeup_links: rbtree::Links::new(), + waiter: None, rr_links: list::Links::new(), } } - pub fn save_ctx(&mut self, ctx: *mut c_void) -> Result<(), KernelError> { + pub fn set_waiter(&mut self, waiter: Option) { + self.waiter = waiter; + } + + pub fn waiter(&self) -> Option<&Waiter> { + self.waiter.as_ref() + } + + pub fn save_ctx(&mut self, ctx: *mut c_void) -> Result<()> { let sp = self.state.stack.create_sp(ctx)?; self.state.stack.set_sp(sp); Ok(()) @@ -260,12 +318,6 @@ impl Thread { } } -impl Compare for Thread { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.uid.cmp(&other.uid) - } -} - impl Project for Thread { fn project(&self) -> Option<&RtServer> { self.rt_server.as_ref() @@ -274,4 +326,14 @@ impl Project for Thread { fn project_mut(&mut self) -> Option<&mut RtServer> { self.rt_server.as_mut() } -} \ No newline at end of file +} + +impl Project for Thread { + fn project(&self) -> Option<&Waiter> { + self.waiter.as_ref() + } + + fn project_mut(&mut self) -> Option<&mut Waiter> { + self.waiter.as_mut() + } +} diff --git a/src/sync/atomic.rs b/src/sync/atomic.rs index e3a4bde..a1f54be 100644 --- a/src/sync/atomic.rs +++ b/src/sync/atomic.rs @@ -15,16 +15,9 @@ pub use core::sync::atomic::Ordering; #[inline(always)] pub fn irq_free(f: impl FnOnce() -> T) -> T { - let enabled = hal::asm::are_interrupts_enabled(); - if enabled { - hal::asm::disable_interrupts(); - } - + let state = hal::asm::disable_irq_save(); let result = f(); - - if enabled { - hal::asm::enable_interrupts(); - } + hal::asm::enable_irq_restr(state); result } diff --git a/src/syscalls.rs b/src/syscalls.rs index eda3f92..c573d2f 100644 --- a/src/syscalls.rs +++ b/src/syscalls.rs @@ -3,11 +3,11 @@ use core::ffi::{c_int, c_uint}; mod file; -mod tasks; +mod sched; // We need to import everything so that the macro is able to find the entry functions. use file::*; -use tasks::*; +use sched::*; #[unsafe(no_mangle)] pub extern "C" fn handle_syscall(number: usize, args: *const c_uint) -> c_int { diff --git a/src/syscalls/sched.rs b/src/syscalls/sched.rs new file mode 100644 index 0000000..38688dd --- /dev/null +++ b/src/syscalls/sched.rs @@ -0,0 +1,29 @@ +//! This module provides task management related syscalls. + +use core::ffi::c_int; + +use proc_macros::syscall_handler; + +use crate::{sched, time}; + +#[syscall_handler(num = 1)] +fn sleep(until_hi: u32, until_lo: u32) -> c_int { + let until = ((until_hi as u64) << 32) | (until_lo as u64); + sched::with(|sched| { + sched.sleep_until(until, time::tick()); + }); + 0 +} + +#[syscall_handler(num = 2)] +fn sleep_for(duration_hi: u32, duration_lo: u32) -> c_int { + let duration = ((duration_hi as u64) << 32) | (duration_lo as u64); + sched::with(|sched| { + let now = time::tick(); + if sched.sleep_until(now + duration, now).is_err() { + panic!("failed to sleep for duration: {duration}"); + } + }); + 0 +} + diff --git a/src/syscalls/tasks.rs b/src/syscalls/tasks.rs deleted file mode 100644 index 2a6d68d..0000000 --- a/src/syscalls/tasks.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! This module provides task management related syscalls. - -use core::ffi::c_int; - -/* -use crate::sched; -use macros::syscall_handler; - -/// Syscall handler: reschedule. -/// This syscall is used to request a reschedule. -/// -/// No arguments are passed to this syscall. -#[syscall_handler(num = 1)] -fn syscall_reschedule() -> c_int { - sched::reschedule(); - 0 -} - -#[syscall_handler(num = 2)] -fn syscall_exec(entry: usize) -> c_int { - let entry: extern "C" fn() -> () = unsafe { core::mem::transmute(entry) }; - - let timing = sched::thread::Timing { - period: 8, - deadline: 8, - exec_time: 2, - }; - - sched::create_task(sched::task::TaskDescriptor { mem_size: 0 }) - .and_then(|task| sched::create_thread(task, entry, None, timing)) - .map(|_| 0) - .unwrap_or(-1) -}*/ diff --git a/src/types/array.rs b/src/types/array.rs index 3e50514..67a9cba 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -1,12 +1,12 @@ //! This module implements static and dynamic arrays for in-kernel use. +use crate::error::Result; + use super::{ traits::{Get, GetMut, ToIndex}, boxed::Box, }; -use crate::utils::KernelError; - use core::{borrow::Borrow, mem::MaybeUninit}; use core::{ ops::{Index, IndexMut}, @@ -39,14 +39,14 @@ impl IndexMap /// `value` - The value to insert. /// /// Returns `Ok(())` if the index was in-bounds, otherwise `Err(KernelError::OutOfMemory)`. - pub fn insert(&mut self, idx: &K, value: V) -> Result<(), KernelError> { + pub fn insert(&mut self, idx: &K, value: V) -> Result<()> { let idx = K::to_index(Some(idx)); if idx < N { self.data[idx] = Some(value); Ok(()) } else { - Err(KernelError::OutOfMemory) + Err(kerr!(OutOfMemory)) } } @@ -55,7 +55,7 @@ impl IndexMap /// `value` - The value to insert. /// /// Returns `Ok(index)` if the value was inserted, otherwise `Err(KernelError::OutOfMemory)`. - pub fn insert_next(&mut self, value: V) -> Result { + pub fn insert_next(&mut self, value: V) -> Result { for (i, slot) in self.data.iter_mut().enumerate() { if slot.is_none() { *slot = Some(value); @@ -63,7 +63,7 @@ impl IndexMap } } - Err(KernelError::OutOfMemory) + Err(kerr!(OutOfMemory)) } /// Remove the value at the given index. @@ -113,6 +113,14 @@ impl IndexMap None } + pub fn at_cont(&self, idx: usize) -> Option<&V> { + if idx < N { + self.data[idx].as_ref() + } else { + None + } + } + pub fn find_empty(&self) -> Option { for (i, slot) in self.data.iter().enumerate() { if slot.is_none() { @@ -237,7 +245,7 @@ impl Vec { /// `additional` - The additional space to reserve. /// /// Returns `Ok(())` if the space was reserved, otherwise `Err(KernelError::OutOfMemory)`. - pub fn reserve(&mut self, additional: usize) -> Result<(), KernelError> { + pub fn reserve(&mut self, additional: usize) -> Result<()> { let len_extra = self.extra.len(); // Check if we have enough space in the inline storage. @@ -250,7 +258,7 @@ impl Vec { let mut new_extra = Box::new_slice_uninit(grow)?; // Check that the new extra storage has the requested length. - BUG_ON!(new_extra.len() != grow); + bug_on!(new_extra.len() != grow); // Copy the old extra storage into the new one. new_extra[..len_extra].copy_from_slice(&self.extra); @@ -265,7 +273,7 @@ impl Vec { /// `total_capacity` - The total space to be reserved. /// /// Returns `Ok(())` if the space was reserved, otherwise `Err(KernelError::OutOfMemory)`. - pub fn reserve_total_capacity(&mut self, total_capacity: usize) -> Result<(), KernelError> { + pub fn reserve_total_capacity(&mut self, total_capacity: usize) -> Result<()> { // Check if we already have enough space if self.capacity() >= total_capacity { return Ok(()); @@ -276,7 +284,7 @@ impl Vec { let mut new_extra = Box::new_slice_uninit(new_out_of_line_cap)?; // Check that the new extra storage has the requested length. - BUG_ON!(new_extra.len() != new_out_of_line_cap); + bug_on!(new_extra.len() != new_out_of_line_cap); let curr_out_of_line_size = self.extra.len(); // Copy the old extra storage into the new one. @@ -293,7 +301,7 @@ impl Vec { /// `value` - The value to initialize the elements in the Vec with. /// /// Returns the new Vec or `Err(KernelError::OutOfMemory)` if the allocation failed. - pub fn new_init(length: usize, value: T) -> Result { + pub fn new_init(length: usize, value: T) -> Result { let mut vec = Self::new(); // Check if we can fit all elements in the inline storage. @@ -329,7 +337,7 @@ impl Vec { /// `value` - The value to push. /// /// Returns `Ok(())` if the value was pushed, otherwise `Err(KernelError::OutOfMemory)`. - pub fn push(&mut self, value: T) -> Result<(), KernelError> { + pub fn push(&mut self, value: T) -> Result<()> { // Check if we have enough space in the inline storage. if self.len < N { // Push the value into the inline storage. @@ -350,7 +358,7 @@ impl Vec { let grow = (len_extra + 1) * 2; let mut new_extra = Box::new_slice_uninit(grow)?; - BUG_ON!(new_extra.len() != grow); + bug_on!(new_extra.len() != grow); // Copy the old extra storage into the new one. new_extra[..len_extra].copy_from_slice(&self.extra); diff --git a/src/types/boxed.rs b/src/types/boxed.rs index 3e2277a..0f23e56 100644 --- a/src/types/boxed.rs +++ b/src/types/boxed.rs @@ -1,7 +1,7 @@ //! This module provides a simple heap-allocated memory block for in-kernel use. -use crate::mem; -use crate::utils::KernelError; +use crate::{error::Result, mem}; + use core::{ mem::{MaybeUninit, forget}, ops::{Deref, DerefMut, Index, IndexMut, Range, RangeFrom, RangeTo}, @@ -23,7 +23,7 @@ impl Box<[T]> { /// `len` - The length of the slice. /// /// Returns a new heap-allocated slice with the given length or an error if the allocation failed. - pub fn new_slice_zeroed(len: usize) -> Result { + pub fn new_slice_zeroed(len: usize) -> Result { if len == 0 { return Ok(Self::new_slice_empty()); } @@ -34,7 +34,7 @@ impl Box<[T]> { ptr: unsafe { NonNull::new_unchecked(ptr) }, }) } else { - Err(KernelError::OutOfMemory) + Err(kerr!(OutOfMemory)) } } @@ -53,7 +53,7 @@ impl Box<[T]> { /// `len` - The length of the slice. /// /// Returns a new heap-allocated slice with the given length or an error if the allocation failed. - pub fn new_slice_uninit(len: usize) -> Result]>, KernelError> { + pub fn new_slice_uninit(len: usize) -> Result]>> { if let Some(ptr) = mem::malloc( size_of::>() * len, align_of::>(), @@ -63,7 +63,7 @@ impl Box<[T]> { ptr: unsafe { NonNull::new_unchecked(ptr) }, }) } else { - Err(KernelError::OutOfMemory) + Err(kerr!(OutOfMemory)) } } } diff --git a/src/types/heap.rs b/src/types/heap.rs index b3b0af3..aa0b579 100644 --- a/src/types/heap.rs +++ b/src/types/heap.rs @@ -1,7 +1,8 @@ //! This module provides a binary heap implementation. +use crate::error::Result; + use super::array::Vec; -use crate::utils::KernelError; /// An array-based binary heap, with N elements stored inline. #[derive(Debug)] @@ -20,7 +21,7 @@ impl BinaryHeap { /// `value` - The value to push onto the binary heap. /// /// Returns `Ok(())` if the value was pushed onto the binary heap, or an error if the heap cannot be extended (e.g. OOM). - pub fn push(&mut self, value: T) -> Result<(), KernelError> { + pub fn push(&mut self, value: T) -> Result<()> { self.vec.push(value)?; self.sift_up(self.len() - 1); Ok(()) diff --git a/src/types/list.rs b/src/types/list.rs index d3c9779..bb64980 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -188,9 +188,23 @@ impl List { where >::Output: Linkable, { - let node = storage.get_mut(id).ok_or(())?; - node.links_mut().prev = None; - node.links_mut().next = None; + let linked = { + let node = storage.get(id).ok_or(())?; + let links = node.links(); + self.head == Some(id) + || self.tail == Some(id) + || links.prev.is_some() + || links.next.is_some() + }; + + if linked { + self.remove(id, storage)?; + } else { + let node = storage.get_mut(id).ok_or(())?; + node.links_mut().prev = None; + node.links_mut().next = None; + } + Ok(()) } } @@ -239,7 +253,7 @@ mod tests { fn storage() -> IndexMap { let mut map = IndexMap::new(); for i in 0..4 { - map.insert(&Id(i), Node::new()).unwrap(); + assert!(map.insert(&Id(i), Node::new()).is_ok()); } map } @@ -268,6 +282,36 @@ mod tests { assert_eq!(n1.links().prev, Some(Id(3))); } + #[test] + fn push_back_and_remove() { + let mut s = storage(); + let mut list = List::::new(); + + list.push_back(Id(1), &mut s).unwrap(); + list.remove(Id(1), &mut s); + + assert_eq!(list.head(), None); + assert_eq!(list.tail(), None); + assert_eq!(list.len(), 0); + } + + #[test] + fn push_back_same_id_reinserts() { + let mut s = storage(); + let mut list = List::::new(); + + list.push_back(Id(1), &mut s).unwrap(); + list.push_back(Id(1), &mut s).unwrap(); + + assert_eq!(list.head(), Some(Id(1))); + assert_eq!(list.tail(), Some(Id(1))); + assert_eq!(list.len(), 1); + + let n1 = s.get(Id(1)).unwrap(); + assert_eq!(n1.links().prev, None); + assert_eq!(n1.links().next, None); + } + #[test] fn pop_back_ordered() { let mut s = storage(); diff --git a/src/types/rbtree.rs b/src/types/rbtree.rs index 969df19..887e386 100644 --- a/src/types/rbtree.rs +++ b/src/types/rbtree.rs @@ -61,6 +61,19 @@ impl RbTree pub fn insert + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> where >::Output: Linkable + Compare,{ + let already_linked = { + let node = storage.get(id).ok_or(())?; + let links = node.links(); + self.root == Some(id) + || links.parent.is_some() + || links.left.is_some() + || links.right.is_some() + }; + + if already_linked { + self.remove(id, storage)?; + } + let mut last = None; { @@ -871,6 +884,24 @@ mod tests { validate_tree(&tree, &store, &keys); } + #[test] + fn reinsert_same_id_is_stable() { + let keys = vec![10, 5, 15]; + let mut store = NodeStore::new(&keys); + let mut tree = RbTree::new(); + + tree.insert(0, &mut store).unwrap(); + tree.insert(1, &mut store).unwrap(); + tree.insert(2, &mut store).unwrap(); + + // Reinsert existing node id. This should not create duplicate structural links. + tree.insert(1, &mut store).unwrap(); + + let mut expected = keys.clone(); + expected.sort(); + validate_tree(&tree, &store, &expected); + } + #[test] fn min_updates_on_insert_and_remove() { let keys = vec![10, 5, 15, 3, 7, 12, 18, 1, 6]; diff --git a/src/uapi.rs b/src/uapi.rs new file mode 100644 index 0000000..bfc9b49 --- /dev/null +++ b/src/uapi.rs @@ -0,0 +1,2 @@ +pub mod print; +pub mod sched; \ No newline at end of file diff --git a/src/uapi/print.rs b/src/uapi/print.rs new file mode 100644 index 0000000..d148107 --- /dev/null +++ b/src/uapi/print.rs @@ -0,0 +1,24 @@ +use core::fmt::{self, Write}; + +use hal::Machinelike; + +#[macro_export] +macro_rules! uprintln { + ($($arg:tt)*) => ({ + use core::fmt::Write; + use osiris::uapi::print::Printer; + + let mut printer = Printer; + printer.write_fmt(format_args!($($arg)*)).unwrap(); + printer.write_str("\n").unwrap(); + }); +} + +pub struct Printer; + +impl Write for Printer { + fn write_str(&mut self, s: &str) -> fmt::Result { + hal::Machine::print(s).map_err(|_| fmt::Error)?; + Ok(()) + } +} \ No newline at end of file diff --git a/src/uapi/sched.rs b/src/uapi/sched.rs new file mode 100644 index 0000000..43df677 --- /dev/null +++ b/src/uapi/sched.rs @@ -0,0 +1,8 @@ + +pub fn sleep(until: u64) { + hal::asm::syscall!(1, (until >> 32) as u32, until as u32); +} + +pub fn sleep_for(duration: u64) { + hal::asm::syscall!(2, (duration >> 32) as u32, duration as u32); +} \ No newline at end of file diff --git a/src/uspace.rs b/src/uspace.rs index d6799de..bc40be2 100644 --- a/src/uspace.rs +++ b/src/uspace.rs @@ -1,6 +1,6 @@ //! This module provides access to userspace structures and services. -use crate::sched; +use crate::{sched, time}; unsafe extern "C" { /// The entry point for the userspace application. @@ -11,12 +11,18 @@ extern "C" fn app_main_entry() { unsafe { app_main() } } -pub fn init_app() -> Result<(), crate::utils::KernelError> { +pub fn init_app() { let attrs = sched::thread::Attributes { entry: app_main_entry, fin: None, }; - let uid = sched::create_thread(sched::task::KERNEL_TASK, &attrs)?; - - sched::enqueue(uid) + sched::with(|sched| { + if let Ok(uid) = sched.create_thread(sched::task::KERNEL_TASK, &attrs) { + if sched.enqueue(time::tick(), uid).is_err() { + panic!("failed to enqueue init thread."); + } + } else { + panic!("failed to create init task."); + } + }) } diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index 0f1bd71..0000000 --- a/src/utils.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! Utility functions and definitions for the kernel. -#![cfg_attr(feature = "nightly", feature(likely_unlikely))] - -use core::fmt::Debug; -use core::ptr::NonNull; -use core::mem::offset_of; - -/// These two definitions are copied from https://github.com/rust-lang/hashbrown -#[cfg(not(feature = "nightly"))] -#[allow(unused_imports)] -pub(crate) use core::convert::{identity as likely, identity as unlikely}; - -#[cfg(feature = "nightly")] -pub(crate) use core::hint::{likely, unlikely}; - -use hal::mem::PhysAddr; - - - -/// This is a macro that is used to panic when a bug is detected. -/// It is similar to the BUG() macro in the Linux kernel. Link: [https://www.kernel.org/]() -#[macro_export] -macro_rules! BUG { - () => { - panic!("BUG triggered at {}:{}", file!(), line!()); - }; - ($msg:expr) => { - panic!("BUG triggered: {} at {}:{}", $msg, file!(), line!()); - }; -} - -/// This is a macro that is used to panic when a condition is true. -/// It is similar to the BUG_ON() macro in the Linux kernel. Link: [https://www.kernel.org/]() -#[macro_export] -macro_rules! BUG_ON { - ($cond:expr) => {{ - let cond = $cond; - #[allow(unused_unsafe)] - if unsafe { $crate::utils::unlikely(cond) } { - BUG!(); - } - }}; - ($cond:expr, $msg:expr) => {{ - let cond = $cond; - #[allow(unused_unsafe)] - if unsafe { $crate::utils::unlikely(cond) } { - BUG!($msg); - } - }}; -} - -/// The error type that is returned when an error in the kernel occurs. -#[derive(PartialEq, Eq, Clone)] -pub enum KernelError { - /// The alignment is invalid. - InvalidAlign, - /// The kernel is out of memory. - OutOfMemory, - InvalidSize, - InvalidAddress(PhysAddr), - InvalidArgument, - HalError(hal::Error), - CustomError(&'static str), -} - -/// Debug msg implementation for KernelError. -impl Debug for KernelError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - KernelError::InvalidAlign => write!(f, "Invalid alignment"), - KernelError::OutOfMemory => write!(f, "Out of memory"), - KernelError::InvalidSize => write!(f, "Invalid size"), - KernelError::InvalidAddress(addr) => write!(f, "Invalid address ({})", addr), - KernelError::InvalidArgument => write!(f, "Invalid argument"), - KernelError::HalError(e) => write!(f, "{e} (in HAL)"), - KernelError::CustomError(msg) => write!(f, "{}", msg), - } - } -} - -impl From for KernelError { - fn from(err: hal::Error) -> Self { - KernelError::HalError(err) - } -} From 3ca219725ae0aae472021c13fc615b6041abee63 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Sat, 28 Mar 2026 17:17:06 +0000 Subject: [PATCH 11/42] cleanup and fixes. --- machine/arm/src/asm.rs | 117 +++++++++++++++----------- machine/testing/src/asm.rs | 10 +-- macros/src/lib.rs | 50 ------------ src/mem/alloc/bestfit.rs | 14 ++-- src/sched.rs | 93 ++++++++++++++------- src/syscalls/sched.rs | 6 +- src/types/list.rs | 49 +++++++---- src/types/rbtree.rs | 163 +++++++++++++++++++++++++------------ src/types/view.rs | 5 ++ src/uapi/sched.rs | 10 +-- 10 files changed, 302 insertions(+), 215 deletions(-) diff --git a/machine/arm/src/asm.rs b/machine/arm/src/asm.rs index b69e90e..1ba1ebb 100644 --- a/machine/arm/src/asm.rs +++ b/machine/arm/src/asm.rs @@ -20,59 +20,84 @@ pub use crate::__macro_nop as nop; #[macro_export] macro_rules! __macro_syscall { ($num:expr) => { - use core::arch::asm; - unsafe { - asm!("svc {num}", num = const $num, clobber_abi("C")); + { + use core::arch::asm; + let ret: isize; + unsafe { + asm!( + "svc {num}", + lateout("r0") ret, + num = const $num, + clobber_abi("C") + ); + } + ret } }; ($num:expr, $arg0:expr) => { - use core::arch::asm; - unsafe { - asm!( - "svc {num}", - in("r0") $arg0, - num = const $num, - clobber_abi("C") - ); + { + use core::arch::asm; + let ret: isize; + unsafe { + asm!( + "svc {num}", + inlateout("r0") $arg0 => ret, + num = const $num, + clobber_abi("C") + ); + } + ret } }; ($num:expr, $arg0:expr, $arg1:expr) => { - use core::arch::asm; - unsafe { - asm!( - "svc {num}", - in("r0") $arg0, - in("r1") $arg1, - num = const $num, - clobber_abi("C") - ); + { + use core::arch::asm; + let ret: isize; + unsafe { + asm!( + "svc {num}", + inlateout("r0") $arg0 => ret, + in("r1") $arg1, + num = const $num, + clobber_abi("C") + ); + } + ret } }; ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => { - use core::arch::asm; - unsafe { - asm!( - "svc {num}", - in("r0") $arg0, - in("r1") $arg1, - in("r2") $arg2, - num = const $num, - clobber_abi("C") - ); + { + use core::arch::asm; + let ret: isize; + unsafe { + asm!( + "svc {num}", + inlateout("r0") $arg0 => ret, + in("r1") $arg1, + in("r2") $arg2, + num = const $num, + clobber_abi("C") + ); + } + ret } }; ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => { - use core::arch::asm; - unsafe { - asm!( - "svc {num}", - in("r0") $arg0, - in("r1") $arg1, - in("r2") $arg2, - in("r3") $arg3, - num = const $num, - clobber_abi("C") - ); + { + use core::arch::asm; + let ret: isize; + unsafe { + asm!( + "svc {num}", + inlateout("r0") $arg0 => ret, + in("r1") $arg1, + in("r2") $arg2, + in("r3") $arg3, + num = const $num, + clobber_abi("C") + ); + } + ret } }; } @@ -80,11 +105,11 @@ macro_rules! __macro_syscall { #[cfg(feature = "host")] #[macro_export] macro_rules! __macro_syscall { - ($num:expr) => {{}}; - ($num:expr, $arg0:expr) => {{}}; - ($num:expr, $arg0:expr, $arg1:expr) => {{}}; - ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => {{}}; - ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {{}}; + ($num:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr, $arg1:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {{ 0isize }}; } pub use crate::__macro_syscall as syscall; diff --git a/machine/testing/src/asm.rs b/machine/testing/src/asm.rs index a303684..fbd7dd5 100644 --- a/machine/testing/src/asm.rs +++ b/machine/testing/src/asm.rs @@ -11,11 +11,11 @@ pub use crate::__macro_nop as nop; /// Macro for doing a system call. #[macro_export] macro_rules! __macro_syscall { - ($num:expr) => {{}}; - ($num:expr, $arg0:expr) => {{}}; - ($num:expr, $arg0:expr, $arg1:expr) => {{}}; - ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => {{}}; - ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {{}}; + ($num:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr, $arg1:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {{ 0isize }}; } pub use crate::__macro_syscall as syscall; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 2387d03..b2084a6 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -42,56 +42,6 @@ pub fn app_main(input: proc_macro::TokenStream, item: proc_macro::TokenStream) - expanded.into() } -#[proc_macro_attribute] -pub fn service( - attr: proc_macro::TokenStream, - item: proc_macro::TokenStream, -) -> proc_macro::TokenStream { - // This macro should be used to annotate a service struct. - let item = syn::parse_macro_input!(item as syn::ItemStruct); - - let service_name = item.ident.clone(); - - let mut mem_size: usize = 0; - let mut stack_size: usize = 0; - - let parser = syn::meta::parser(|meta| { - if meta.path.is_ident("mem_size") { - mem_size = meta.value()?.parse::()?.base10_parse()?; - Ok(()) - } else if meta.path.is_ident("stack_size") { - stack_size = meta.value()?.parse::()?.base10_parse()?; - Ok(()) - } else { - Err(meta.error("unknown attribute")) - } - }); - - parse_macro_input!(attr with parser); - - let mem_size_ident = format_ident!("TASK_{}_MEM_SIZE", service_name.to_string().to_uppercase()); - let stack_size_ident = format_ident!( - "TASK_{}_STACK_SIZE", - service_name.to_string().to_uppercase() - ); - - let expanded = quote::quote! { - const #mem_size_ident: usize = #mem_size; - const #stack_size_ident: usize = #stack_size; - #item - - impl #service_name { - pub fn task_desc() -> crate::sched::task::TaskDescriptor { - crate::sched::task::TaskDescriptor { - mem_size: #mem_size_ident, - } - } - } - }; - - expanded.into() -} - const SYSCALL_MAX_ARGS: usize = 4; fn is_return_type_register_sized_check( diff --git a/src/mem/alloc/bestfit.rs b/src/mem/alloc/bestfit.rs index 9c91fa1..00db5a7 100644 --- a/src/mem/alloc/bestfit.rs +++ b/src/mem/alloc/bestfit.rs @@ -24,7 +24,7 @@ pub struct BestFitAllocator { /// Implementation of the BestFitAllocator. impl BestFitAllocator { - pub const MIN_RANGE_SIZE: usize = size_of::() + Self::align_up() + 1; + pub const MIN_RANGE_SIZE: usize = size_of::() + Self::align_up() + 1; /// Creates a new BestFitAllocator. /// @@ -50,7 +50,11 @@ impl BestFitAllocator { // Check if the pointer is 128bit aligned. if !ptr.is_multiple_of(align_of::()) { return Err(kerr!(InvalidArgument)); - } + } + + if range.end.diff(range.start) < Self::MIN_RANGE_SIZE { + return Err(kerr!(InvalidArgument)); + } debug_assert!(range.end > range.start); debug_assert!(range.end.diff(range.start) > size_of::() + Self::align_up()); @@ -192,13 +196,13 @@ impl super::Allocator for BestFitAllocator { /// Returns the user pointer to the block if successful, otherwise an error. fn malloc(&mut self, size: usize, align: usize, request: Option) -> Result> { // Check if the alignment is valid. - if align > align_of::() { - return Err(kerr!(InvalidArgument)); + if align == 0 || align > align_of::() { + return Err(kerr!(InvalidAlign)); } if let Some(request) = request { if !request.is_multiple_of(align) { - return Err(kerr!(InvalidArgument)); + return Err(kerr!(InvalidAlign)); } } diff --git a/src/sched.rs b/src/sched.rs index 39583d8..e3cd1e5 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -22,7 +22,7 @@ use crate::{ types::{ array::IndexMap, rbtree::RbTree, - traits::{Get, GetMut, Project}, + traits::{Get, GetMut}, view::ViewMut, }, }; @@ -85,11 +85,15 @@ impl Scheduler { if let Some(task_id) = kill { self.dequeue(current); self.current = None; - self.kill_task(task_id); + if self.kill_task(task_id).is_err() { + // Should not be possible. The thread exists, so the task must exist. + bug!("failed to kill task {}", task_id); + } } } } + /// Triggers a reschedule at *latest* when we hit timepoint `next`. fn schedule_resched(now: u64, next: u64) { let old = NEXT_TICK.load(Ordering::Acquire); @@ -107,7 +111,12 @@ impl Scheduler { let mut view = rt::ServerView::::new(&mut self.threads); self.rt_scheduler.enqueue(uid, now, &mut view); } else { - self.rr_scheduler.enqueue(uid, &mut self.threads)?; + if self.rr_scheduler.enqueue(uid, &mut self.threads).is_err() { + // This should not be possible. + // - Thread is in the thread list. + // - Thread is not linked into a different list. + bug!("failed to enqueue thread {} into RR scheduler.", uid); + } } reschedule(); Ok(()) @@ -115,19 +124,27 @@ impl Scheduler { fn do_wakeups(&mut self, now: u64) { while let Some(uid) = self.wakeup.min() { - { - let mut view = WaiterView::::new(&mut self.threads); + let mut done = false; + WaiterView::::with(&mut self.threads, |view| { let waiter = view.get(uid).expect("THIS IS A BUG!"); - if waiter.until() > now { Self::schedule_resched(now, waiter.until()); - break; + done = true; + return; + } + + if let Err(_) = self.wakeup.remove(uid, view) { + bug!("failed to remove thread {} from wakeup tree.", uid); } + }); - self.wakeup.remove(uid, &mut view); + if done { + break; } - self.enqueue(now, uid); + if self.enqueue(now, uid).is_err() { + bug!("failed to enqueue thread {} after wakeup.", uid); + } } } @@ -136,28 +153,35 @@ impl Scheduler { self.last_tick = now; if let Some(old) = self.current { - let mut view = rt::ServerView::::new(&mut self.threads); - self.rt_scheduler.put(old, dt, &mut view); + rt::ServerView::::with(&mut self.threads, |view| { + self.rt_scheduler.put(old, dt, view); + }); self.rr_scheduler.put(old, dt); } self.do_wakeups(now); - let mut view = rt::ServerView::::new(&mut self.threads); + let pick = + rt::ServerView::::with(&mut self.threads, |view| self.rt_scheduler.pick(now, view)); + let pick = pick.or_else(|| self.rr_scheduler.pick(&mut self.threads)); + let (new, budget) = pick.unwrap_or((thread::IDLE_THREAD, 1000)); - let (new, budget) = self - .rt_scheduler - .pick(now, &mut view) - .or_else(|| self.rr_scheduler.pick(&mut self.threads)) - .unwrap_or((thread::IDLE_THREAD, 1000)); + // At this point, the task/thread must exist. Everything else is a bug. + let (ctx, task_id) = if let Some(thread) = self.threads.get(new) { + (thread.ctx(), thread.task_id()) + } else { + bug!("failed to pick thread {}. Does not exist.", new); + }; - let ctx = self.threads.get(new)?.ctx(); - let task = self.tasks.get_mut(self.threads.get(new)?.task_id())?; + let task = if let Some(task) = self.tasks.get_mut(task_id) { + task + } else { + bug!("failed to get task {}. Does not exist.", task_id); + }; + // We don't need to resched if the thread has budget. self.current = Some(new); - let next = now.saturating_add(budget); - - Self::schedule_resched(now, next); + Self::schedule_resched(now, now.saturating_add(budget)); Some((ctx, task)) } @@ -170,6 +194,7 @@ impl Scheduler { if let Some(thread) = self.threads.get_mut(uid) { thread.set_waiter(Some(Waiter::new(until, uid))); } else { + // This should not be possible. The thread must exist since it's the current thread. bug!( "failed to put current thread {} to sleep. Does not exist.", uid @@ -181,27 +206,33 @@ impl Scheduler { .insert(uid, &mut WaiterView::::new(&mut self.threads)) .is_err() { + // This should not be possible. The thread exists. bug!("failed to insert thread {} into wakeup tree.", uid); } - - self.dequeue(uid); reschedule(); Ok(()) } pub fn kick(&mut self, uid: thread::UId) -> Result<()> { - let thread = self.threads.get_mut(uid).ok_or(kerr!(InvalidArgument))?; - if let Some(waiter) = Project::::project_mut(thread) { - waiter.set_until(0); - } - Ok(()) + WaiterView::::with(&mut self.threads, |view| { + self.wakeup.remove(uid, view)?; + let thread = view.get_mut(uid).unwrap_or_else(|| { + bug!("failed to get thread {} from wakeup tree.", uid); + }); + thread.set_until(0); + self.wakeup.insert(uid, view).unwrap_or_else(|_| { + bug!("failed to re-insert thread {} into wakeup tree.", uid); + }); + Ok(()) + }) } pub fn dequeue(&mut self, uid: thread::UId) { - let mut view = rt::ServerView::::new(&mut self.threads); - self.rt_scheduler.dequeue(uid, &mut view); + rt::ServerView::::with(&mut self.threads, |view| { + self.rt_scheduler.dequeue(uid, view); + }); self.rr_scheduler.dequeue(uid, &mut self.threads); } diff --git a/src/syscalls/sched.rs b/src/syscalls/sched.rs index 38688dd..2b7d434 100644 --- a/src/syscalls/sched.rs +++ b/src/syscalls/sched.rs @@ -10,7 +10,9 @@ use crate::{sched, time}; fn sleep(until_hi: u32, until_lo: u32) -> c_int { let until = ((until_hi as u64) << 32) | (until_lo as u64); sched::with(|sched| { - sched.sleep_until(until, time::tick()); + if sched.sleep_until(until, time::tick()).is_err() { + bug!("no current thread set."); + } }); 0 } @@ -21,7 +23,7 @@ fn sleep_for(duration_hi: u32, duration_lo: u32) -> c_int { sched::with(|sched| { let now = time::tick(); if sched.sleep_until(now + duration, now).is_err() { - panic!("failed to sleep for duration: {duration}"); + bug!("no current thread set."); } }); 0 diff --git a/src/types/list.rs b/src/types/list.rs index bb64980..7156d29 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -1,5 +1,7 @@ use core::marker::PhantomData; +use crate::error::Result; + use super::traits::{Get, GetMut}; #[allow(dead_code)] @@ -62,7 +64,7 @@ impl List { self.len == 0 } - pub fn push_front + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + pub fn push_front + GetMut>(&mut self, id: T, storage: &mut S) -> Result<()> where >::Output: Linkable, { @@ -71,7 +73,9 @@ impl List { match self.head { Some(old_head) => { let (new_node, old_head_node) = storage.get2_mut(id, old_head); - let (new_node, old_head_node) = (new_node.ok_or(())?, old_head_node.ok_or(())?); + let (new_node, old_head_node) = (new_node.ok_or(kerr!(NotFound))?, old_head_node.unwrap_or_else(|| { + bug!("node linked from list does not exist in storage."); + })); new_node.links_mut().prev = None; new_node.links_mut().next = Some(old_head); @@ -79,7 +83,7 @@ impl List { old_head_node.links_mut().prev = Some(id); } None => { - let new_node = storage.get_mut(id).ok_or(())?; + let new_node = storage.get_mut(id).ok_or(kerr!(NotFound))?; new_node.links_mut().prev = None; new_node.links_mut().next = None; self.tail = Some(id); @@ -91,7 +95,10 @@ impl List { Ok(()) } - pub fn push_back + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + /// Pushes `id` to the back of the list. If `id` is already in the list, it is moved to the back. + /// + /// Errors if `id` does not exist in `storage` or if the node corresponding to `id` is linked but not in the list. + pub fn push_back + GetMut>(&mut self, id: T, storage: &mut S) -> Result<()> where >::Output: Linkable, { @@ -100,7 +107,9 @@ impl List { match self.tail { Some(old_tail) => { let (new_node, old_tail_node) = storage.get2_mut(id, old_tail); - let (new_node, old_tail_node) = (new_node.ok_or(())?, old_tail_node.ok_or(())?); + let (new_node, old_tail_node) = (new_node.ok_or(kerr!(NotFound))?, old_tail_node.unwrap_or_else(|| { + bug!("node linked from list does not exist in storage."); + })); new_node.links_mut().next = None; new_node.links_mut().prev = Some(old_tail); @@ -108,7 +117,7 @@ impl List { old_tail_node.links_mut().next = Some(id); } None => { - let new_node = storage.get_mut(id).ok_or(())?; + let new_node = storage.get_mut(id).ok_or(kerr!(NotFound))?; new_node.links_mut().next = None; new_node.links_mut().prev = None; self.head = Some(id); @@ -120,7 +129,7 @@ impl List { Ok(()) } - pub fn pop_front + GetMut>(&mut self, storage: &mut S) -> Result, ()> + pub fn pop_front + GetMut>(&mut self, storage: &mut S) -> Result> where >::Output: Linkable, { @@ -132,7 +141,7 @@ impl List { Ok(Some(id)) } - pub fn pop_back + GetMut>(&mut self, storage: &mut S) -> Result, ()> + pub fn pop_back + GetMut>(&mut self, storage: &mut S) -> Result> where >::Output: Linkable, { @@ -144,12 +153,13 @@ impl List { Ok(Some(id)) } - pub fn remove + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + /// Removes `id` from the list. Errors if `id` does not exist in `storage` or if the node corresponding to `id` is not linked. + pub fn remove + GetMut>(&mut self, id: T, storage: &mut S) -> Result<()> where >::Output: Linkable, { let (prev, next, linked) = { - let node = storage.get(id).ok_or(())?; + let node = storage.get(id).ok_or(kerr!(NotFound))?; let links = node.links(); let linked = self.head == Some(id) || self.tail == Some(id) @@ -159,24 +169,28 @@ impl List { }; if !linked { - return Err(()); + return Err(kerr!(NotFound)); } if let Some(prev_id) = prev { - let prev_node = storage.get_mut(prev_id).ok_or(())?; + let prev_node = storage.get_mut(prev_id).unwrap_or_else(|| { + bug!("node linked from list does not exist in storage."); + }); prev_node.links_mut().next = next; } else { self.head = next; } if let Some(next_id) = next { - let next_node = storage.get_mut(next_id).ok_or(())?; + let next_node = storage.get_mut(next_id).unwrap_or_else(|| { + bug!("node linked from list does not exist in storage."); + }); next_node.links_mut().prev = prev; } else { self.tail = prev; } - let node = storage.get_mut(id).ok_or(())?; + let node = storage.get_mut(id).ok_or(kerr!(NotFound))?; node.links_mut().prev = None; node.links_mut().next = None; @@ -184,12 +198,13 @@ impl List { Ok(()) } - fn detach_links + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + /// Detaches `id` from any list it is currently in. If `id` is not in any list but is linked, the links are cleared. + fn detach_links + GetMut>(&mut self, id: T, storage: &mut S) -> Result<()> where >::Output: Linkable, { let linked = { - let node = storage.get(id).ok_or(())?; + let node = storage.get(id).ok_or(kerr!(NotFound))?; let links = node.links(); self.head == Some(id) || self.tail == Some(id) @@ -200,7 +215,7 @@ impl List { if linked { self.remove(id, storage)?; } else { - let node = storage.get_mut(id).ok_or(())?; + let node = storage.get_mut(id).ok_or(kerr!(NotFound))?; node.links_mut().prev = None; node.links_mut().next = None; } diff --git a/src/types/rbtree.rs b/src/types/rbtree.rs index 887e386..842ffe2 100644 --- a/src/types/rbtree.rs +++ b/src/types/rbtree.rs @@ -1,5 +1,7 @@ use core::{marker::PhantomData}; +use crate::error::Result; + use super::traits::{Get, GetMut}; #[allow(dead_code)] @@ -59,10 +61,11 @@ impl RbTree } } - pub fn insert + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + /// Inserts `id` into the tree. If `id` already exists in the tree, it is first removed and then re-inserted. Errors if `id` does not exist in `storage`. + pub fn insert + GetMut>(&mut self, id: T, storage: &mut S) -> Result<()> where >::Output: Linkable + Compare,{ let already_linked = { - let node = storage.get(id).ok_or(())?; + let node = storage.get(id).ok_or(kerr!(NotFound))?; let links = node.links(); self.root == Some(id) || links.parent.is_some() @@ -77,12 +80,14 @@ impl RbTree let mut last = None; { - let node = storage.get(id).ok_or(())?; + let node = storage.get(id).ok_or(kerr!(NotFound))?; let mut current = self.root; while let Some(current_id) = current { last = current; - let current_node = storage.get(current_id).ok_or(())?; + let current_node = storage.get(current_id).unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); let go_left = node.cmp(current_node) == core::cmp::Ordering::Less; current = if go_left { @@ -94,7 +99,7 @@ impl RbTree } { - let node = storage.get_mut(id).ok_or(())?.links_mut(); + let node = storage.get_mut(id).ok_or(kerr!(NotFound))?.links_mut(); node.parent = last; node.left = None; node.right = None; @@ -115,8 +120,10 @@ impl RbTree } if let Some(min_id) = self.min { - let node = storage.get(id).ok_or(())?; - let min_node = storage.get(min_id).ok_or(())?; + let node = storage.get(id).ok_or(kerr!(NotFound))?; + let min_node = storage.get(min_id).unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); if node.cmp(min_node) == core::cmp::Ordering::Less { self.min = Some(id); } @@ -127,18 +134,27 @@ impl RbTree self.insert_fixup(id, storage) } - pub fn remove + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + pub fn remove + GetMut>(&mut self, id: T, storage: &mut S) -> Result<()> where >::Output: Linkable + Compare { - let (node_left, node_right, node_parent, node_is_red) = { - let node = storage.get(id).ok_or(())?; + let (node_left, node_right, node_parent, node_is_red, linked) = { + let node = storage.get(id).ok_or(kerr!(NotFound))?; + let links = node.links(); ( - node.links().left, - node.links().right, - node.links().parent, - matches!(node.links().color, Color::Red), + links.left, + links.right, + links.parent, + matches!(links.color, Color::Red), + self.root == Some(id) + || links.parent.is_some() + || links.left.is_some() + || links.right.is_some(), ) }; + if !linked { + return Err(kerr!(NotFound)); + } + let mut succ_was_red = node_is_red; let child: Option; let child_parent: Option; @@ -154,7 +170,9 @@ impl RbTree self.transplant(id, node_left, storage)?; } else { - let right_id = node_right.ok_or(())?; + let right_id = node_right.unwrap_or_else(|| { + bug!("node's right child is None, but it is not None according to previous get."); + }); let succ = self.minimum(right_id, storage)?; let succ_right = storage.get(succ).and_then(|n| n.links().right); let succ_parent = storage.get(succ).and_then(|n| n.links().parent); @@ -173,7 +191,7 @@ impl RbTree succ_node.links_mut().right = Some(right_id); right_node.links_mut().parent = Some(succ); } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } child_parent = succ_parent; @@ -181,13 +199,15 @@ impl RbTree self.transplant(id, Some(succ), storage)?; - let left_id = node_left.ok_or(())?; + let left_id = node_left.unwrap_or_else(|| { + bug!("node's left child is None, but it is not None according to previous get."); + }); if let (Some(succ_node), Some(left_node)) = storage.get2_mut(succ, left_id) { succ_node.links_mut().left = Some(left_id); left_node.links_mut().parent = Some(succ); } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } if let Some(succ_node) = storage.get_mut(succ) { @@ -197,7 +217,7 @@ impl RbTree Color::Black }; } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } } @@ -205,6 +225,17 @@ impl RbTree self.delete_fixup(child, child_parent, storage)?; } + // Fully detach the removed node so stale links cannot be observed on reinsertion. + if let Some(node) = storage.get_mut(id) { + let links = node.links_mut(); + links.parent = None; + links.left = None; + links.right = None; + links.color = Color::Red; + } else { + bug!("node linked from tree does not exist in storage."); + } + if self.min == Some(id) { self.min = match self.root { Some(root_id) => Some(self.minimum(root_id, storage)?), @@ -219,7 +250,7 @@ impl RbTree self.min } - fn insert_fixup + GetMut>(&mut self, mut id: T, storage: &mut S) -> Result<(), ()> + fn insert_fixup + GetMut>(&mut self, mut id: T, storage: &mut S) -> Result<()> where >::Output: Linkable + Compare, { while let Some(parent) = storage.get(id).and_then(|n| n.links().parent) && storage @@ -229,7 +260,9 @@ impl RbTree let grandparent = storage .get(parent) .and_then(|n| n.links().parent) - .ok_or(())?; + .unwrap_or_else(|| { + bug!("node linked from tree does not have a parent."); + }); // Is left child node if storage @@ -264,11 +297,13 @@ impl RbTree id = old_parent; } - let parent = storage.get(id).and_then(|n| n.links().parent).ok_or(())?; + let parent = storage.get(id).and_then(|n| n.links().parent).ok_or(kerr!(NotFound))?; let grandparent = storage .get(parent) .and_then(|n| n.links().parent) - .ok_or(())?; + .unwrap_or_else(|| { + bug!("node linked from tree does not have a parent."); + }); if let (Some(parent_node), Some(grandparent_node)) = storage.get2_mut(parent, grandparent) @@ -308,11 +343,13 @@ impl RbTree id = old_parent; } - let parent = storage.get(id).and_then(|n| n.links().parent).ok_or(())?; + let parent = storage.get(id).and_then(|n| n.links().parent).ok_or(kerr!(NotFound))?; let grandparent = storage .get(parent) .and_then(|n| n.links().parent) - .ok_or(())?; + .unwrap_or_else(|| { + bug!("node linked from tree does not have a parent."); + }); if let (Some(parent_node), Some(grandparent_node)) = storage.get2_mut(parent, grandparent) @@ -340,7 +377,7 @@ impl RbTree mut id: Option, mut parent: Option, storage: &mut S, - ) -> Result<(), ()> + ) -> Result<()> where >::Output: Linkable + Compare, { let is_red = |node_id: Option, storage: &S| -> bool { node_id @@ -351,7 +388,9 @@ impl RbTree let is_black = |node_id: Option, storage: &S| -> bool { !is_red(node_id, storage) }; while id != self.root && is_black(id, storage) { - let parent_id = parent.ok_or(())?; + let parent_id = parent.unwrap_or_else(|| { + bug!("node linked from tree does not have a parent."); + }); let is_left_child = storage .get(parent_id) @@ -361,20 +400,24 @@ impl RbTree let mut sibling_opt = storage.get(parent_id).and_then(|n| n.links().right); if is_red(sibling_opt, storage) { - let sibling_id = sibling_opt.ok_or(())?; + let sibling_id = sibling_opt.unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); // Color sibling node black and parent node red, rotate if let (Some(sib), Some(par)) = storage.get2_mut(sibling_id, parent_id) { sib.links_mut().color = Color::Black; par.links_mut().color = Color::Red; } else { - return Err(()); + return Err(kerr!(NotFound)); } self.rotate_left(parent_id, sibling_id, storage)?; sibling_opt = storage.get(parent_id).and_then(|n| n.links().right); } // Sibling node is black - let sibling_id = sibling_opt.ok_or(())?; + let sibling_id = sibling_opt.unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); let sib_left = storage.get(sibling_id).and_then(|n| n.links().left); let sib_right = storage.get(sibling_id).and_then(|n| n.links().right); @@ -383,26 +426,30 @@ impl RbTree if let Some(sib) = storage.get_mut(sibling_id) { sib.links_mut().color = Color::Red; } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } id = Some(parent_id); parent = storage.get(parent_id).and_then(|n| n.links().parent); } else { // Sibling's left node is red if is_black(sib_right, storage) { - let sib_left_id = sib_left.ok_or(())?; + let sib_left_id = sib_left.unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); if let (Some(sib), Some(left)) = storage.get2_mut(sibling_id, sib_left_id) { sib.links_mut().color = Color::Red; left.links_mut().color = Color::Black; } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } self.rotate_right(sibling_id, sib_left_id, storage)?; sibling_opt = storage.get(parent_id).and_then(|n| n.links().right); } // Sibling's right child node is red - let sibling_id = sibling_opt.ok_or(())?; + let sibling_id = sibling_opt.unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); let parent_is_red = storage .get(parent_id) .map_or(false, |n| matches!(n.links().color, Color::Red)); @@ -433,19 +480,23 @@ impl RbTree let mut sibling_opt = storage.get(parent_id).and_then(|n| n.links().left); if is_red(sibling_opt, storage) { - let sibling_id = sibling_opt.ok_or(())?; + let sibling_id = sibling_opt.unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); if let (Some(sib), Some(par)) = storage.get2_mut(sibling_id, parent_id) { sib.links_mut().color = Color::Black; par.links_mut().color = Color::Red; } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } self.rotate_right(parent_id, sibling_id, storage)?; sibling_opt = storage.get(parent_id).and_then(|n| n.links().left); } // Sibling node is black - let sibling_id = sibling_opt.ok_or(())?; + let sibling_id = sibling_opt.unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); let sib_left = storage.get(sibling_id).and_then(|n| n.links().left); let sib_right = storage.get(sibling_id).and_then(|n| n.links().right); @@ -453,27 +504,31 @@ impl RbTree if let Some(sib) = storage.get_mut(sibling_id) { sib.links_mut().color = Color::Red; } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } id = Some(parent_id); parent = storage.get(parent_id).and_then(|n| n.links().parent); } else { // Sibling's right node is red if is_black(sib_left, storage) { - let sib_right_id = sib_right.ok_or(())?; + let sib_right_id = sib_right.unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); if let (Some(sib), Some(right)) = storage.get2_mut(sibling_id, sib_right_id) { sib.links_mut().color = Color::Red; right.links_mut().color = Color::Black; } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } self.rotate_left(sibling_id, sib_right_id, storage)?; sibling_opt = storage.get(parent_id).and_then(|n| n.links().left); } // Sibling's left child node is red - let sibling_id = sibling_opt.ok_or(())?; + let sibling_id = sibling_opt.unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); let parent_is_red = storage .get(parent_id) .map_or(false, |n| matches!(n.links().color, Color::Red)); @@ -513,10 +568,10 @@ impl RbTree Ok(()) } - fn minimum>(&self, mut id: T, storage: &S) -> Result + fn minimum>(&self, mut id: T, storage: &S) -> Result where >::Output: Linkable + Compare, { loop { - let left = storage.get(id).ok_or(())?.links().left; + let left = storage.get(id).ok_or(kerr!(NotFound))?.links().left; match left { Some(left_id) => id = left_id, None => return Ok(id), @@ -524,7 +579,7 @@ impl RbTree } } - fn transplant + GetMut>(&mut self, u: T, v: Option, storage: &mut S) -> Result<(), ()> + fn transplant + GetMut>(&mut self, u: T, v: Option, storage: &mut S) -> Result<()> where >::Output: Linkable + Compare, { let u_parent = storage.get(u).and_then(|n| n.links().parent); @@ -538,7 +593,7 @@ impl RbTree parent_node.links_mut().right = v; } } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } } } @@ -547,17 +602,17 @@ impl RbTree if let Some(v_node) = storage.get_mut(v_id) { v_node.links_mut().parent = u_parent; } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } } Ok(()) } - fn rotate_right + GetMut>(&mut self, pivot: T, left: T, storage: &mut S) -> Result<(), ()> + fn rotate_right + GetMut>(&mut self, pivot: T, left: T, storage: &mut S) -> Result<()> where >::Output: Linkable + Compare, { if pivot == left { - return Err(()); + return Err(kerr!(NotFound)); } let (right, parent) = @@ -580,7 +635,7 @@ impl RbTree (old_right, old_parent) } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); }; if let Some(right_id) = right { @@ -599,7 +654,7 @@ impl RbTree parent_node.links_mut().right = Some(left); } } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } } } @@ -607,10 +662,10 @@ impl RbTree Ok(()) } - fn rotate_left + GetMut>(&mut self, pivot: T, right: T, storage: &mut S) -> Result<(), ()> + fn rotate_left + GetMut>(&mut self, pivot: T, right: T, storage: &mut S) -> Result<()> where >::Output: Linkable + Compare, { if pivot == right { - return Err(()); + return Err(kerr!(NotFound)); } let (left, parent) = @@ -633,7 +688,7 @@ impl RbTree (old_left, old_parent) } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); }; if let Some(left_id) = left { @@ -652,7 +707,7 @@ impl RbTree parent_node.links_mut().right = Some(right); } } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } } } diff --git a/src/types/view.rs b/src/types/view.rs index c07de3a..de56096 100644 --- a/src/types/view.rs +++ b/src/types/view.rs @@ -23,6 +23,11 @@ where _proj: PhantomData, } } + + pub fn with R, R>(data: &'a mut S, f: F) -> R { + let mut view = Self::new(data); + f(&mut view) + } } impl<'a, K: ?Sized + ToIndex, P, S: GetMut> Get for ViewMut<'a, K, P, S> diff --git a/src/uapi/sched.rs b/src/uapi/sched.rs index 43df677..db6bddd 100644 --- a/src/uapi/sched.rs +++ b/src/uapi/sched.rs @@ -1,8 +1,8 @@ -pub fn sleep(until: u64) { - hal::asm::syscall!(1, (until >> 32) as u32, until as u32); +pub fn sleep(until: u64) -> isize { + hal::asm::syscall!(1, (until >> 32) as u32, until as u32) } - -pub fn sleep_for(duration: u64) { - hal::asm::syscall!(2, (duration >> 32) as u32, duration as u32); + +pub fn sleep_for(duration: u64) -> isize { + hal::asm::syscall!(2, (duration >> 32) as u32, duration as u32) } \ No newline at end of file From 24c0556447ae3ab0b50f35b3269b07ef0a6a2503 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Sat, 28 Mar 2026 17:43:33 +0000 Subject: [PATCH 12/42] spawn thread syscall --- examples/hello-world/src/main.rs | 12 ++++++++++++ src/idle.rs | 2 +- src/sched.rs | 7 ++++++- src/sched/thread.rs | 4 ++++ src/syscalls/sched.rs | 19 +++++++++++++++++++ src/uapi/sched.rs | 6 ++++++ src/uspace.rs | 2 +- 7 files changed, 49 insertions(+), 3 deletions(-) diff --git a/examples/hello-world/src/main.rs b/examples/hello-world/src/main.rs index 2702505..70500bb 100644 --- a/examples/hello-world/src/main.rs +++ b/examples/hello-world/src/main.rs @@ -3,10 +3,22 @@ use osiris::app_main; +extern "C" fn second_thread() { + osiris::uprintln!("Hello from the second thread!"); + + let mut tick = 0; + loop { + osiris::uprintln!("Second thread tick: {}", tick); + tick += 1; + osiris::uapi::sched::sleep_for(1500); + } +} + #[app_main] fn main() { osiris::uprintln!("Hello World!"); let mut tick = 0; + osiris::uapi::sched::spawn_thread(second_thread); loop { osiris::uprintln!("Tick: {}", tick); diff --git a/src/idle.rs b/src/idle.rs index 70633a7..479f407 100644 --- a/src/idle.rs +++ b/src/idle.rs @@ -9,7 +9,7 @@ extern "C" fn entry() { pub fn init() { let attrs = sched::thread::Attributes { entry, fin: None }; sched::with(|sched| { - if let Err(e) = sched.create_thread(sched::task::KERNEL_TASK, &attrs) { + if let Err(e) = sched.create_thread(Some(sched::task::KERNEL_TASK), &attrs) { panic!("failed to create idle thread. Error: {}", e); } }); diff --git a/src/sched.rs b/src/sched.rs index e3cd1e5..e063b48 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -274,10 +274,15 @@ impl Scheduler { pub fn create_thread( &mut self, - task: task::UId, + task: Option, attrs: &thread::Attributes, ) -> Result { + let task = match task { + Some(t) => t, + None => self.current.ok_or(kerr!(InvalidArgument))?.owner() + }; let task = self.tasks.get_mut(task).ok_or(kerr!(InvalidArgument))?; + let thread = task.create_thread(self.id_gen, attrs)?; let uid = thread.uid(); diff --git a/src/sched/thread.rs b/src/sched/thread.rs index 86af4aa..7cc628d 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -60,6 +60,10 @@ impl UId { self.tid } + pub fn as_usize(&self) -> usize { + self.uid + } + pub fn owner(&self) -> task::UId { self.tid.owner } diff --git a/src/syscalls/sched.rs b/src/syscalls/sched.rs index 2b7d434..b7b1ffd 100644 --- a/src/syscalls/sched.rs +++ b/src/syscalls/sched.rs @@ -29,3 +29,22 @@ fn sleep_for(duration_hi: u32, duration_lo: u32) -> c_int { 0 } +#[syscall_handler(num = 3)] +fn spawn_thread(func_ptr: usize) -> c_int { + sched::with(|sched| { + let attrs = sched::thread::Attributes { + entry: unsafe { core::mem::transmute(func_ptr) }, + fin: None, + }; + match sched.create_thread(None, &attrs) { + Ok(uid) => { + if sched.enqueue(time::tick(), uid).is_err() { + panic!("failed to enqueue thread."); + } + uid.as_usize() as c_int + } + Err(_) => -1, + } + }) +} + diff --git a/src/uapi/sched.rs b/src/uapi/sched.rs index db6bddd..5437a2b 100644 --- a/src/uapi/sched.rs +++ b/src/uapi/sched.rs @@ -1,3 +1,5 @@ +use hal::stack::EntryFn; + pub fn sleep(until: u64) -> isize { hal::asm::syscall!(1, (until >> 32) as u32, until as u32) @@ -5,4 +7,8 @@ pub fn sleep(until: u64) -> isize { pub fn sleep_for(duration: u64) -> isize { hal::asm::syscall!(2, (duration >> 32) as u32, duration as u32) +} + +pub fn spawn_thread(func_ptr: EntryFn) -> isize { + hal::asm::syscall!(3, func_ptr as u32) } \ No newline at end of file diff --git a/src/uspace.rs b/src/uspace.rs index bc40be2..2f11b34 100644 --- a/src/uspace.rs +++ b/src/uspace.rs @@ -17,7 +17,7 @@ pub fn init_app() { fin: None, }; sched::with(|sched| { - if let Ok(uid) = sched.create_thread(sched::task::KERNEL_TASK, &attrs) { + if let Ok(uid) = sched.create_thread(Some(sched::task::KERNEL_TASK), &attrs) { if sched.enqueue(time::tick(), uid).is_err() { panic!("failed to enqueue init thread."); } From b057d9c20b60e16a2f1c00d0490313c06adfb132 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Mon, 30 Mar 2026 18:19:13 +0000 Subject: [PATCH 13/42] wip --- Cargo.lock | 75 ++++++++++++-- Cargo.toml | 3 + build.rs | 45 ++++----- examples/hello-world/Cargo.toml | 2 +- examples/hello-world/src/main.rs | 17 +++- machine/arm/Cargo.toml | 1 + machine/arm/src/crit.rs | 14 +++ machine/arm/src/excep.rs | 27 ++++++ machine/arm/src/lib.rs | 1 + macros/src/lib.rs | 161 +++---------------------------- macros/src/logging.rs | 24 +++++ macros/src/syscall.rs | 146 ++++++++++++++++++++++++++++ src/error.rs | 3 +- src/lib.rs | 5 +- src/macros.rs | 48 ++++++--- src/mem/alloc/bestfit.rs | 2 +- src/sched.rs | 52 +++++----- src/sched/task.rs | 50 ++++++---- src/sched/thread.rs | 50 +++++++--- src/sync/spinlock.rs | 4 +- src/syscalls.rs | 3 +- src/syscalls/sched.rs | 17 +++- src/types/array.rs | 46 +++++---- src/types/boxed.rs | 2 +- src/types/heap.rs | 2 +- src/types/list.rs | 6 +- src/types/queue.rs | 2 +- src/types/rbtree.rs | 9 +- src/types/traits.rs | 6 ++ src/uapi/sched.rs | 12 +++ 30 files changed, 542 insertions(+), 293 deletions(-) create mode 100644 machine/arm/src/crit.rs create mode 100644 macros/src/logging.rs create mode 100644 macros/src/syscall.rs diff --git a/Cargo.lock b/Cargo.lock index 292bc70..efd0c21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,7 +95,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags", + "bitflags 2.10.0", "cexpr", "clang-sys", "itertools 0.12.1", @@ -118,7 +118,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags", + "bitflags 2.10.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -132,6 +132,12 @@ dependencies = [ "syn", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.10.0" @@ -412,13 +418,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossterm" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags", + "bitflags 2.10.0", "crossterm_winapi", "libc", "mio 0.8.11", @@ -434,7 +446,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags", + "bitflags 2.10.0", "crossterm_winapi", "mio 1.1.1", "parking_lot", @@ -514,6 +526,48 @@ dependencies = [ "syn", ] +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.17", +] + +[[package]] +name = "defmt-rtt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" +dependencies = [ + "critical-section", + "defmt", +] + [[package]] name = "digest" version = "0.10.7" @@ -695,6 +749,7 @@ dependencies = [ "bindgen 0.72.1", "cbindgen", "cmake", + "critical-section", "hal-api", "serde_json", ] @@ -1063,9 +1118,11 @@ name = "osiris" version = "0.1.0" dependencies = [ "bindgen 0.69.5", - "bitflags", + "bitflags 2.10.0", "cbindgen", "cfg_aliases", + "defmt", + "defmt-rtt", "envparse", "hal-select", "hal-testing", @@ -1238,7 +1295,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags", + "bitflags 2.10.0", "cassowary", "compact_str", "crossterm 0.28.1", @@ -1259,7 +1316,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.10.0", ] [[package]] @@ -1317,7 +1374,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -1330,7 +1387,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.11.0", diff --git a/Cargo.toml b/Cargo.toml index f201c3a..ffb6158 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,8 @@ hal = { package = "hal-select", path = "machine/select" } proc_macros = { package = "macros", path = "macros" } envparse = "0.1.0" bitflags = "2.10.0" +defmt = { version = "1.0", optional = true } +defmt-rtt = { version = "1.0", optional = true } [dev-dependencies] # This is a host-compatible HAL which will be used for running tests and verification on the host. @@ -37,6 +39,7 @@ default = [] nightly = [] no-atomic-cas = [] multi-core = [] +defmt = ["dep:defmt", "dep:defmt-rtt"] [build-dependencies] cbindgen = "0.28.0" diff --git a/build.rs b/build.rs index ff9b3cf..ed12097 100644 --- a/build.rs +++ b/build.rs @@ -5,6 +5,7 @@ extern crate syn; extern crate walkdir; use cfg_aliases::cfg_aliases; +use quote::format_ident; use std::io::Write; use syn::{Attribute, LitInt}; use walkdir::WalkDir; @@ -14,35 +15,37 @@ extern crate cbindgen; fn main() { println!("cargo::rerun-if-changed=src"); println!("cargo::rerun-if-changed=build.rs"); + let out_dir = std::env::var("OUT_DIR").unwrap(); - generate_syscall_map("src/syscalls").expect("Failed to generate syscall map."); + if gen_syscall_match(Path::new("src/syscalls"), Path::new(&out_dir)).is_err() { + panic!("Failed to generate syscall match statement."); + } cfg_aliases! { freestanding: { all(not(test), not(doctest), not(doc), not(kani), any(target_os = "none", target_os = "unknown")) }, } } -fn generate_syscall_map>(root: P) -> Result<(), std::io::Error> { - let syscalls = collect_syscalls(root); - - let out_dir = std::env::var("OUT_DIR").unwrap(); - let out_path = Path::new(&out_dir).join("syscall_dispatcher.in"); - let mut file = File::create(out_path)?; - - writeln!(file, "// This file is @generated by build.rs. Do not edit!")?; - writeln!(file)?; - writeln!(file, "match number {{")?; +fn gen_syscall_match(root: &Path, out: &Path) -> Result<(), std::io::Error> { + let syscalls = find_syscalls(root); + let mut file = File::create(out.join("syscall_match.in"))?; - for (name, number) in &syscalls { - writeln!(file, " {number} => entry_{name}(args),")?; - } + let arms = syscalls.iter().map(|(name, number)| { + let entry = format_ident!("entry_{}", name); + quote::quote! { + #number => #entry(args), + } + }); - writeln!( - file, - " _ => panic!(\"Unknown syscall number: {{}}\", number)," - )?; - writeln!(file, "}}")?; + let syscall_match = quote::quote! { + // This match statement is @generated by build.rs. Do not edit. + match number { + #(#arms)* + _ => panic!("Unknown syscall number: {}", number), + } + }; + writeln!(file, "{syscall_match}")?; Ok(()) } @@ -82,9 +85,7 @@ fn is_syscall(attrs: &[Attribute], name: &str) -> Option { None } -type SyscallData = u16; - -fn collect_syscalls>(root: P) -> HashMap { +fn find_syscalls(root: &Path) -> HashMap { let mut syscalls = HashMap::new(); let mut numbers = HashMap::new(); diff --git a/examples/hello-world/Cargo.toml b/examples/hello-world/Cargo.toml index 1bd3456..81645ee 100644 --- a/examples/hello-world/Cargo.toml +++ b/examples/hello-world/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -osiris = { workspace = true } +osiris = { workspace = true, features = ["defmt"] } [build-dependencies] cfg_aliases = "0.2.1" diff --git a/examples/hello-world/src/main.rs b/examples/hello-world/src/main.rs index 70500bb..baa7bb3 100644 --- a/examples/hello-world/src/main.rs +++ b/examples/hello-world/src/main.rs @@ -7,11 +7,24 @@ extern "C" fn second_thread() { osiris::uprintln!("Hello from the second thread!"); let mut tick = 0; - loop { + for i in 0..5 { osiris::uprintln!("Second thread tick: {}", tick); tick += 1; osiris::uapi::sched::sleep_for(1500); } + + osiris::uapi::sched::exit(0); + osiris::uprintln!("This will never be printed."); +} + +extern "C" fn generator_thread() { + + let mut cnt = 0; + loop { + osiris::uapi::sched::yield_thread(); + osiris::uprintln!("Number: {}", cnt); + cnt += 1; + } } #[app_main] @@ -19,7 +32,7 @@ fn main() { osiris::uprintln!("Hello World!"); let mut tick = 0; osiris::uapi::sched::spawn_thread(second_thread); - + osiris::uapi::sched::spawn_thread(generator_thread); loop { osiris::uprintln!("Tick: {}", tick); tick += 1; diff --git a/machine/arm/Cargo.toml b/machine/arm/Cargo.toml index 9e5a441..9fb4753 100644 --- a/machine/arm/Cargo.toml +++ b/machine/arm/Cargo.toml @@ -11,6 +11,7 @@ crate-type = ["rlib"] [dependencies] hal-api = { path = "../api" } +critical-section = { version = "1.2", features = ["restore-state-usize"] } [build-dependencies] cbindgen = "0.28.0" diff --git a/machine/arm/src/crit.rs b/machine/arm/src/crit.rs new file mode 100644 index 0000000..134f2f5 --- /dev/null +++ b/machine/arm/src/crit.rs @@ -0,0 +1,14 @@ +use critical_section::RawRestoreState; + +struct CriticalSection; +critical_section::set_impl!(CriticalSection); + +unsafe impl critical_section::Impl for CriticalSection { + unsafe fn acquire() -> RawRestoreState { + crate::asm::disable_irq_save() + } + + unsafe fn release(token: RawRestoreState) { + crate::asm::enable_irq_restr(token); + } +} \ No newline at end of file diff --git a/machine/arm/src/excep.rs b/machine/arm/src/excep.rs index 4e50bbc..ab51268 100644 --- a/machine/arm/src/excep.rs +++ b/machine/arm/src/excep.rs @@ -1,4 +1,5 @@ use core::fmt::Display; +use core::mem::align_of; #[repr(C)] pub struct ExcepStackFrame { @@ -43,6 +44,11 @@ impl Display for ExcepStackFrame { const BACKTRACE_MAX_FRAMES: usize = 20; +#[inline] +fn is_call_aligned(ptr: *const usize) -> bool { + (ptr as usize).is_multiple_of(align_of::()) +} + #[repr(C)] pub struct ExcepBacktrace { stack_frame: ExcepStackFrame, @@ -79,6 +85,11 @@ impl Display for ExcepBacktrace { writeln!(f, "0: 0x{:08x}", self.stack_frame.pc)?; } + if fp.is_null() || !is_call_aligned(fp) { + writeln!(f, "", fp as usize)?; + return writeln!(f); + } + for i in 1..BACKTRACE_MAX_FRAMES { // Read the return address from the stack. let ret_addr = unsafe { fp.add(1).read_volatile() }; @@ -89,6 +100,9 @@ impl Display for ExcepBacktrace { break; } + // Return addresses in Thumb mode carry bit0 = 1. + let ret_addr = ret_addr & !1; + // Print the return address. if let Some(symbol) = crate::debug::find_nearest_symbol(ret_addr) { writeln!(f, "{i}: {symbol} (0x{ret_addr:08x})")?; @@ -101,6 +115,19 @@ impl Display for ExcepBacktrace { break; } + let fp_addr = fp as usize; + let next_fp_addr = next_fp; + + if next_fp_addr <= fp_addr { + writeln!(f, "")?; + break; + } + + if !is_call_aligned(next_fp_addr as *const usize) { + writeln!(f, "")?; + break; + } + // Move to the next frame. fp = next_fp as *const usize; diff --git a/machine/arm/src/lib.rs b/machine/arm/src/lib.rs index 00fb103..46ba0b9 100644 --- a/machine/arm/src/lib.rs +++ b/machine/arm/src/lib.rs @@ -10,6 +10,7 @@ pub mod excep; pub mod panic; pub mod sched; +mod crit; mod print; mod bindings { diff --git a/macros/src/lib.rs b/macros/src/lib.rs index b2084a6..49f1b4a 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,9 +1,8 @@ -use quote::{ToTokens, format_ident}; use syn::parse_macro_input; -use proc_macro2::TokenStream; - mod tree; +mod syscall; +mod logging; #[proc_macro_derive(TaggedLinks, attributes(rbtree, list))] pub fn derive_tagged_links(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -15,6 +14,16 @@ pub fn derive_tagged_links(input: proc_macro::TokenStream) -> proc_macro::TokenS }.into() } +#[proc_macro_attribute] +pub fn fmt(args: proc_macro::TokenStream, input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = syn::parse_macro_input!(input as syn::DeriveInput); + + match logging::derive_fmt(&input) { + Ok(tokens) => tokens, + Err(e) => e.to_compile_error(), + }.into() +} + #[proc_macro_attribute] pub fn app_main(input: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { let item = syn::parse_macro_input!(item as syn::ItemFn); @@ -42,74 +51,6 @@ pub fn app_main(input: proc_macro::TokenStream, item: proc_macro::TokenStream) - expanded.into() } -const SYSCALL_MAX_ARGS: usize = 4; - -fn is_return_type_register_sized_check( - item: &syn::ItemFn, -) -> Result { - let ret_ty = match &item.sig.output { - syn::ReturnType::Default => { - // no "-> Type" present - return Err(syn::Error::new_spanned( - &item.sig.output, - "syscall_handler: missing return type; expected a register‐sized type", - )); - } - syn::ReturnType::Type(_, ty) => (*ty).clone(), - }; - - Ok(quote::quote! { - const _: () = { - if core::mem::size_of::<#ret_ty>() > core::mem::size_of::() { - panic!("syscall_handler: the return type is bigger than usize. return type must fit in a register."); - } - }; - }) -} - -fn check_and_collect_argument_types(item: &syn::ItemFn) -> Result, syn::Error> { - let types: Vec> = item - .sig - .inputs - .iter() - .map(|arg| { - if let syn::FnArg::Typed(pat_type) = arg { - Ok((*pat_type.ty).clone()) - } else { - Err(syn::Error::new( - item.sig.ident.span(), - format!( - "argument {} is invalid. expected a typed argument.\n", - arg.to_token_stream() - ), - )) - } - }) - .collect(); - - let concat_errors: Vec<_> = types - .iter() - .filter_map(|arg0: &std::result::Result| Result::err(arg0.clone())) - .collect(); - - if !concat_errors.is_empty() { - return Err(syn::Error::new( - item.sig.ident.span(), - format!( - "syscall_handler: function {} has invalid arguments: {}", - item.sig.ident, - concat_errors - .iter() - .map(|e| e.to_string()) - .collect::>() - .join(", ") - ), - )); - } - - Ok(types.into_iter().map(Result::unwrap).collect()) -} - #[proc_macro_attribute] pub fn syscall_handler( attr: proc_macro::TokenStream, @@ -129,83 +70,7 @@ pub fn syscall_handler( parse_macro_input!(attr with parser); let item = syn::parse_macro_input!(item as syn::ItemFn); - syscall_handler_fn(&item).into() + syscall::syscall_handler_fn(&item).into() } -fn syscall_handler_fn(item: &syn::ItemFn) -> TokenStream { - let name = item.sig.ident.to_string().to_uppercase(); - let num_args = item.sig.inputs.len(); - - // Check if the function has a valid signature. So args <= 4 and return type is u32. - if num_args > SYSCALL_MAX_ARGS { - return syn::Error::new( - item.sig.ident.span(), - format!( - "syscall_handler: function {name} has too many arguments (max is {SYSCALL_MAX_ARGS})" - ), - ) - .to_compile_error(); - } - - let ret_check = match is_return_type_register_sized_check(item) { - Ok(check) => check, - Err(e) => return e.to_compile_error(), - }; - let types = match check_and_collect_argument_types(item) { - Ok(types) => { - if types.len() > SYSCALL_MAX_ARGS { - return syn::Error::new( - item.sig.ident.span(), - format!( - "syscall_handler: function {name} has too many arguments (max is {SYSCALL_MAX_ARGS})" - ), - ) - .to_compile_error(); - } - types - } - Err(e) => return e.to_compile_error(), - }; - - // Check if each argument type is valid and fits in a register. - let size_checks: Vec = types.iter().map(|ty| { - quote::quote! { - const _: () = { - if core::mem::size_of::<#ty>() > core::mem::size_of::() { - panic!("syscall_handler: an argument type is bigger than usize. arguments must fit in a register."); - } - }; - } - }).collect(); - - let unpack = types.iter().enumerate().map(|(i, ty)| { - quote::quote! { - unsafe { *(args.add(#i) as *const #ty) } - } - }); - - let wrapper_name = format_ident!("entry_{}", item.sig.ident.clone()); - let func_name = item.sig.ident.clone(); - - let call = quote::quote! { - #func_name( #(#unpack),* ) - }; - - let wrapper = quote::quote! { - #[unsafe(no_mangle)] - pub extern "C" fn #wrapper_name(svc_args: *const core::ffi::c_uint) -> core::ffi::c_int { - // This function needs to extract the arguments from the pointer and call the original function by passing the arguments as actual different parameters. - let args = unsafe { svc_args as *const usize }; - // Call the original function with the extracted arguments. - #call - } - }; - - quote::quote! { - #wrapper - #item - #ret_check - #(#size_checks)* - } -} diff --git a/macros/src/logging.rs b/macros/src/logging.rs new file mode 100644 index 0000000..9e857fe --- /dev/null +++ b/macros/src/logging.rs @@ -0,0 +1,24 @@ +use syn::{DeriveInput, ItemFn}; + +pub fn derive_fmt(input: &DeriveInput) -> syn::Result { + // Check if the env variable "OSIRIS_DEBUG_DEFMT" is set. If it is, generate a defmt::Format implementation. Otherwise, generate a Debug implementation. + if std::env::var("OSIRIS_DEBUG_DEFMT").is_ok() { + Ok(derive_fmt_defmt(input)) + } else { + Ok(derive_fmt_debug(input)) + } +} + +fn derive_fmt_defmt(input: &DeriveInput) -> proc_macro2::TokenStream { + quote::quote! { + #[derive(defmt::Format)] + #input + } +} + +fn derive_fmt_debug(input: &DeriveInput) -> proc_macro2::TokenStream { + quote::quote! { + #[derive(core::fmt::Debug)] + #input + } +} \ No newline at end of file diff --git a/macros/src/syscall.rs b/macros/src/syscall.rs new file mode 100644 index 0000000..45c071c --- /dev/null +++ b/macros/src/syscall.rs @@ -0,0 +1,146 @@ +use quote::{ToTokens, format_ident}; +use proc_macro2::TokenStream; + +pub const MAX_ARGS: usize = 4; + +pub fn valid_ret_type_check(item: &syn::ItemFn) -> Result { + let ret_ty = match &item.sig.output { + syn::ReturnType::Default => { + // no "-> Type" present + return Err(syn::Error::new_spanned( + &item.sig.output, + "syscall_handler: missing return type; expected a register‐sized type", + )); + } + syn::ReturnType::Type(_, ty) => (*ty).clone(), + }; + + Ok(quote::quote! { + const _: () = { + if core::mem::size_of::<#ret_ty>() > core::mem::size_of::() { + panic!("syscall_handler: the return type is bigger than usize. return type must fit in a register."); + } + }; + }) +} + +pub fn valid_arg_types_check(item: &syn::ItemFn) -> Result, syn::Error> { + let types: Vec> = item + .sig + .inputs + .iter() + .map(|arg| { + if let syn::FnArg::Typed(pat_type) = arg { + Ok((*pat_type.ty).clone()) + } else { + Err(syn::Error::new( + item.sig.ident.span(), + format!( + "argument {} is invalid. expected a typed argument.\n", + arg.to_token_stream() + ), + )) + } + }) + .collect(); + + let concat_errors: Vec<_> = types + .iter() + .filter_map(|arg0: &std::result::Result| Result::err(arg0.clone())) + .collect(); + + if !concat_errors.is_empty() { + return Err(syn::Error::new( + item.sig.ident.span(), + format!( + "syscall_handler: function {} has invalid arguments: {}", + item.sig.ident, + concat_errors + .iter() + .map(|e| e.to_string()) + .collect::>() + .join(", ") + ), + )); + } + + Ok(types.into_iter().map(Result::unwrap).collect()) +} + +pub fn syscall_handler_fn(item: &syn::ItemFn) -> TokenStream { + let name = item.sig.ident.to_string().to_uppercase(); + let num_args = item.sig.inputs.len(); + + // Check if the function has a valid signature. So args <= 4 and return type is u32. + if num_args > MAX_ARGS { + return syn::Error::new( + item.sig.ident.span(), + format!( + "syscall_handler: function {name} has too many arguments (max is {MAX_ARGS})", + ), + ) + .to_compile_error(); + } + + let ret_check = match valid_ret_type_check(item) { + Ok(check) => check, + Err(e) => return e.to_compile_error(), + }; + + let types = match valid_arg_types_check(item) { + Ok(types) => { + if types.len() > MAX_ARGS { + return syn::Error::new( + item.sig.ident.span(), + format!( + "syscall_handler: function {name} has too many arguments (max is {MAX_ARGS})", + ), + ) + .to_compile_error(); + } + types + } + Err(e) => return e.to_compile_error(), + }; + + // Check if each argument type is valid and fits in a register. + let size_checks: Vec = types.iter().map(|ty| { + quote::quote! { + const _: () = { + if core::mem::size_of::<#ty>() > core::mem::size_of::() { + panic!("syscall_handler: an argument type is bigger than usize. arguments must fit in a register."); + } + }; + } + }).collect(); + + let unpack = types.iter().enumerate().map(|(i, ty)| { + quote::quote! { + unsafe { *(args.add(#i) as *const #ty) } + } + }); + + let wrapper_name = format_ident!("entry_{}", item.sig.ident.clone()); + let func_name = item.sig.ident.clone(); + + let call = quote::quote! { + #func_name( #(#unpack),* ) + }; + + let wrapper = quote::quote! { + #[unsafe(no_mangle)] + pub extern "C" fn #wrapper_name(svc_args: *const core::ffi::c_uint) -> core::ffi::c_int { + // This function needs to extract the arguments from the pointer and call the original function by passing the arguments as actual different parameters. + let args = unsafe { svc_args as *const usize }; + // Call the original function with the extracted arguments. + #call + } + }; + + quote::quote! { + #wrapper + #item + #ret_check + #(#size_checks)* + } +} diff --git a/src/error.rs b/src/error.rs index f9edb31..cc48035 100644 --- a/src/error.rs +++ b/src/error.rs @@ -90,7 +90,8 @@ macro_rules! kerr { }; } -#[derive(Debug, Clone, PartialEq, Eq)] +#[proc_macros::fmt] +#[derive(Clone, PartialEq, Eq)] pub enum Kind { InvalidAlign, OutOfMemory, diff --git a/src/lib.rs b/src/lib.rs index b027a6d..dff34a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,12 +28,9 @@ pub use proc_macros::app_main; /// The kernel initialization function. /// -/// `boot_info` - The boot information. -/// /// # Safety /// /// This function must be called only once during the kernel startup. -/// The `boot_info` pointer must be valid and point to a properly initialized `BootInfo` structure. #[unsafe(no_mangle)] pub unsafe extern "C" fn kernel_init() -> ! { // Initialize basic hardware and the logging system. @@ -42,6 +39,8 @@ pub unsafe extern "C" fn kernel_init() -> ! { print::print_header(); + error!("Hello World!"); + // Initialize the memory allocator. let kaddr_space = mem::init_memory(); kprintln!("Memory initialized."); diff --git a/src/macros.rs b/src/macros.rs index 9d9d06f..0ddf819 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,27 +1,43 @@ //! Macros for kernel development. +use defmt_rtt as _; + + +#[macro_export] +macro_rules! debug { + ($fmt:literal $(, $arg:expr)* $(,)?) => { + #[cfg(feature = "defmt")] + defmt::debug!($fmt $(, $arg)*); + }; +} + +#[macro_export] +macro_rules! trace { + ($fmt:literal $(, $arg:expr)* $(,)?) => { + #[cfg(feature = "defmt")] + defmt::trace!($fmt $(, $arg)*); + }; +} -/// Creates a slice from the raw arguments. #[macro_export] -macro_rules! args_from_raw { - ($argc:expr, $argv:expr) => {{ - let argc = $argc; - let argv = $argv; - - if argc == 0 || argv.is_null() { - &[] - } else { - unsafe { core::slice::from_raw_parts(argv, argc) } - } - }}; +macro_rules! info { + ($fmt:literal $(, $arg:expr)* $(,)?) => { + #[cfg(feature = "defmt")] + defmt::info!($fmt $(, $arg)*); + }; +} + +#[macro_export] +macro_rules! error { + ($fmt:literal $(, $arg:expr)* $(,)?) => { + #[cfg(feature = "defmt")] + defmt::error!($fmt $(, $arg)*); + }; } #[macro_export] macro_rules! kprint { ($($arg:tt)*) => ({ - use core::fmt::Write; - use $crate::print::Printer; - let mut printer = Printer; - printer.write_fmt(format_args!($($arg)*)).unwrap(); + }); } diff --git a/src/mem/alloc/bestfit.rs b/src/mem/alloc/bestfit.rs index 00db5a7..4732a85 100644 --- a/src/mem/alloc/bestfit.rs +++ b/src/mem/alloc/bestfit.rs @@ -16,7 +16,7 @@ struct BestFitMeta { /// That does mean, when we allocate a block, we try to find the smallest block that fits the requested size. /// Blocks are stored in a singly linked list. The important part is that the linked list is stored in-line with the memory blocks. /// This means that every block has a header that contains the size of the block and a pointer to the next block. -#[derive(Debug)] +#[proc_macros::fmt] pub struct BestFitAllocator { /// Head of the free block list. head: Option>, diff --git a/src/sched.rs b/src/sched.rs index e063b48..4001fa1 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -112,7 +112,7 @@ impl Scheduler { self.rt_scheduler.enqueue(uid, now, &mut view); } else { if self.rr_scheduler.enqueue(uid, &mut self.threads).is_err() { - // This should not be possible. + // This should not be possible. // - Thread is in the thread list. // - Thread is not linked into a different list. bug!("failed to enqueue thread {} into RR scheduler.", uid); @@ -245,30 +245,22 @@ impl Scheduler { } pub fn kill_task(&mut self, uid: task::UId) -> Result<()> { - let task_id = self.tasks.get(uid).ok_or(kerr!(InvalidArgument))?.id; - self.tasks.remove(&uid).ok_or(kerr!(InvalidArgument))?; - - let begin = match self.threads.next(None) { - Some(i) => i, - None => return Ok(()), - }; - let mut i = begin; - - while i != begin { - i = (i + 1) % N; + let task = self.tasks.get_mut(uid).ok_or(kerr!(InvalidArgument))?; - let mut id = None; - if let Some(thread) = self.threads.at_cont(i) { - if thread.task_id() == task_id { - id = Some(thread.uid()); - } - } + while let Some(id) = task.threads().head() { + // Borrow checker... + rt::ServerView::::with(&mut self.threads, |view| { + self.rt_scheduler.dequeue(id, view); + }); + self.rr_scheduler.dequeue(id, &mut self.threads); - if let Some(id) = id { - self.dequeue(id); + if task.threads_mut().remove(id, &mut self.threads).is_err() { + // This should not be possible. The thread ID is from the thread list of the task, so it must exist. + bug!("failed to remove thread {} from task {}.", id, uid); } } + self.tasks.remove(&uid).ok_or(kerr!(InvalidArgument))?; Ok(()) } @@ -279,18 +271,26 @@ impl Scheduler { ) -> Result { let task = match task { Some(t) => t, - None => self.current.ok_or(kerr!(InvalidArgument))?.owner() + None => self.current.ok_or(kerr!(InvalidArgument))?.owner(), }; let task = self.tasks.get_mut(task).ok_or(kerr!(InvalidArgument))?; - - let thread = task.create_thread(self.id_gen, attrs)?; - let uid = thread.uid(); - - self.threads.insert(&uid, thread)?; + let uid = task.create_thread(self.id_gen, attrs, &mut self.threads)?; self.id_gen += 1; Ok(uid) } + + pub fn kill_thread(&mut self, uid: Option) -> Result<()> { + let uid = uid.unwrap_or(self.current.ok_or(kerr!(InvalidArgument))?); + self.dequeue(uid); + self.threads.remove(&uid).ok_or(kerr!(InvalidArgument))?; + + if Some(uid) == self.current { + self.current = None; + reschedule(); + } + Ok(()) + } } pub fn with T>(f: F) -> T { diff --git a/src/sched/task.rs b/src/sched/task.rs index 52b0345..c1e094a 100644 --- a/src/sched/task.rs +++ b/src/sched/task.rs @@ -1,19 +1,20 @@ //! This module provides the basic task and thread structures for the scheduler. +use core::borrow::Borrow; use core::fmt::Display; use core::num::NonZero; -use core::borrow::Borrow; use envparse::parse_env; -use hal::{Stack}; +use hal::Stack; -use hal::stack::{Stacklike}; +use hal::stack::Stacklike; use crate::error::Result; -use crate::sched::thread; +use crate::sched::{GlobalScheduler, ThreadMap, thread}; +use crate::types::list; use crate::{mem, sched}; -use crate::mem::vmm::{AddressSpacelike}; -use crate::types::traits::ToIndex; +use crate::mem::vmm::AddressSpacelike; +use crate::types::traits::{Get, GetMut, ToIndex}; pub struct Defaults { pub stack_pages: usize, @@ -26,18 +27,15 @@ const DEFAULTS: Defaults = Defaults { pub const KERNEL_TASK: UId = UId { uid: 0 }; /// Id of a task. This is unique across all tasks. -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] +#[proc_macros::fmt] +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] pub struct UId { uid: usize, } impl UId { pub fn new(uid: usize) -> Option { - if uid == 0 { - None - } else { - Some(Self { uid }) - } + if uid == 0 { None } else { Some(Self { uid }) } } pub fn is_kernel(&self) -> bool { @@ -53,7 +51,7 @@ impl ToIndex for UId { impl Display for UId { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "Task-{}", self.uid) + write!(f, "{}", self.uid) } } @@ -69,6 +67,8 @@ pub struct Task { tid_cntr: usize, /// Sets up the memory for the task. address_space: mem::vmm::AddressSpace, + /// The threads belonging to this task. + threads: list::List, } impl Task { @@ -84,6 +84,7 @@ impl Task { id, address_space, tid_cntr: 0, + threads: list::List::new(), }) } @@ -94,10 +95,7 @@ impl Task { sched::thread::Id::new(tid, self.id) } - fn allocate_stack( - &mut self, - attrs: &thread::Attributes, - ) -> Result { + fn allocate_stack(&mut self, attrs: &thread::Attributes) -> Result { let size = DEFAULTS.stack_pages * mem::pfa::PAGE_SIZE; let region = mem::vmm::Region::new( None, @@ -115,20 +113,32 @@ impl Task { }) } - pub fn create_thread( + pub fn create_thread( &mut self, uid: usize, attrs: &thread::Attributes, - ) -> Result { + storage: &mut ThreadMap, + ) -> Result { let stack = self.allocate_stack(attrs)?; let stack = unsafe { Stack::new(stack) }?; let tid = self.allocate_tid(); + let new = sched::thread::Thread::new(tid.get_uid(uid), stack); + storage.insert(&tid.get_uid(uid), new); + self.threads.push_back(tid.get_uid(uid), storage); - Ok(sched::thread::Thread::new(tid.get_uid(uid), stack)) + Ok(tid.get_uid(uid)) } pub fn tid_cntr(&self) -> usize { self.tid_cntr } + + pub fn threads_mut(&mut self) -> &mut list::List { + &mut self.threads + } + + pub fn threads(&self) -> &list::List { + &self.threads + } } diff --git a/src/sched/thread.rs b/src/sched/thread.rs index 7cc628d..a83203d 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -19,7 +19,8 @@ pub const IDLE_THREAD: UId = UId { }; /// Id of a task. This is only unique within a Task. -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] +#[proc_macros::fmt] +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] pub struct Id { id: usize, owner: task::UId, @@ -45,7 +46,8 @@ impl Id { } /// Unique identifier for a thread. Build from TaskId and ThreadId. -#[derive(Clone, Copy, Debug)] +#[proc_macros::fmt] +#[derive(Clone, Copy)] #[allow(dead_code)] pub struct UId { /// A globally unique identifier for the thread. @@ -65,7 +67,7 @@ impl UId { } pub fn owner(&self) -> task::UId { - self.tid.owner + self.tid.owner() } } @@ -103,14 +105,15 @@ impl ToIndex for UId { impl Display for UId { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "T{}-{}", self.tid.owner(), self.tid.as_usize()) + write!(f, "{}-{}", self.tid.owner(), self.tid.as_usize()) } } // ------------------------------------------------------------------------- /// The state of a thread. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[proc_macros::fmt] +#[derive(Clone, Copy, PartialEq, Eq)] #[allow(dead_code)] pub enum RunState { /// The thread is currently using the cpu. @@ -121,13 +124,15 @@ pub enum RunState { Waits, } -#[derive(Debug, Clone, Copy)] +#[proc_macros::fmt] +#[derive(Clone, Copy)] pub struct State { run_state: RunState, stack: Stack, } -#[derive(Debug, Clone, Copy)] +#[proc_macros::fmt] +#[derive(Clone, Copy)] #[derive(TaggedLinks)] pub struct RtServer { budget: u64, @@ -197,7 +202,8 @@ impl Compare for RtServer { } } -#[derive(Debug, Clone, Copy)] +#[proc_macros::fmt] +#[derive(Clone, Copy)] #[derive(TaggedLinks)] pub struct Waiter { /// The time when the Thread will be awakened. @@ -237,21 +243,29 @@ impl Compare for Waiter { } } -#[derive(Debug, Clone, Copy)] +#[proc_macros::fmt] +#[derive(Clone, Copy)] pub struct WakupTree; -#[derive(Debug, Clone, Copy)] +#[proc_macros::fmt] +#[derive(Clone, Copy)] pub struct RtTree; -#[derive(Debug, Clone, Copy)] +#[proc_macros::fmt] +#[derive(Clone, Copy)] pub struct RRList; +#[proc_macros::fmt] +#[derive(Clone, Copy)] +pub struct ThreadList; + pub struct Attributes { pub entry: EntryFn, pub fin: Option, } /// The struct representing a thread. -#[derive(Debug, Clone, Copy)] +#[proc_macros::fmt] +#[derive(Clone, Copy)] #[derive(TaggedLinks)] pub struct Thread { /// The current state of the thread. @@ -265,6 +279,9 @@ pub struct Thread { #[list(tag = RRList, idx = UId)] rr_links: list::Links, + + #[list(tag = ThreadList, idx = UId)] + thread_links: list::Links, } #[allow(dead_code)] @@ -284,6 +301,7 @@ impl Thread { rt_server: None, waiter: None, rr_links: list::Links::new(), + thread_links: list::Links::new(), } } @@ -318,7 +336,13 @@ impl Thread { } pub fn task_id(&self) -> task::UId { - self.uid.tid().owner + self.uid.tid().owner() + } +} + +impl PartialEq for Thread { + fn eq(&self, other: &Self) -> bool { + self.uid == other.uid } } diff --git a/src/sync/spinlock.rs b/src/sync/spinlock.rs index 7e1b2de..14178cf 100644 --- a/src/sync/spinlock.rs +++ b/src/sync/spinlock.rs @@ -6,7 +6,7 @@ use core::sync::atomic::AtomicBool; use core::sync::atomic::Ordering; /// A mutual exclusion primitive, facilitating busy-waiting. -#[derive(Debug)] +#[proc_macros::fmt] pub struct SpinLock { lock: AtomicBool, } @@ -56,7 +56,7 @@ impl SpinLock { } /// A guard that releases the SpinLock when dropped. -#[derive(Debug)] +#[proc_macros::fmt] pub struct SpinLockGuard<'a, T: ?Sized> { lock: &'a SpinLock, value: NonNull, diff --git a/src/syscalls.rs b/src/syscalls.rs index c573d2f..d692f26 100644 --- a/src/syscalls.rs +++ b/src/syscalls.rs @@ -11,7 +11,8 @@ use sched::*; #[unsafe(no_mangle)] pub extern "C" fn handle_syscall(number: usize, args: *const c_uint) -> c_int { + let number = number as u16; // All functions that are annotated with the #[syscall_handler(num = X)] macro are syscalls. // build.rs will generate a match statement that matches the syscall number to the function which is then included here. - include!(concat!(env!("OUT_DIR"), "/syscall_dispatcher.in")) + include!(concat!(env!("OUT_DIR"), "/syscall_match.in")) } diff --git a/src/syscalls/sched.rs b/src/syscalls/sched.rs index b7b1ffd..1972b31 100644 --- a/src/syscalls/sched.rs +++ b/src/syscalls/sched.rs @@ -39,7 +39,7 @@ fn spawn_thread(func_ptr: usize) -> c_int { match sched.create_thread(None, &attrs) { Ok(uid) => { if sched.enqueue(time::tick(), uid).is_err() { - panic!("failed to enqueue thread."); + bug!("failed to enqueue thread."); } uid.as_usize() as c_int } @@ -48,3 +48,18 @@ fn spawn_thread(func_ptr: usize) -> c_int { }) } +#[syscall_handler(num = 4)] +fn exit(code: usize) -> c_int { + sched::with(|sched| { + if sched.kill_thread(None).is_err() { + bug!("failed to terminate thread."); + } + }); + 0 +} + +#[syscall_handler(num = 5)] +fn kick_thread(uid: usize) -> c_int { + 0 +} + diff --git a/src/types/array.rs b/src/types/array.rs index 67a9cba..15eae5b 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -13,7 +13,7 @@ use core::{ }; /// This is a fixed-size map that can store up to N consecutive elements. -#[derive(Debug)] +#[proc_macros::fmt] pub struct IndexMap { data: [Option; N], @@ -113,7 +113,7 @@ impl IndexMap None } - pub fn at_cont(&self, idx: usize) -> Option<&V> { + pub fn raw_at(&self, idx: usize) -> Option<&V> { if idx < N { self.data[idx].as_ref() } else { @@ -121,6 +121,14 @@ impl IndexMap } } + pub fn raw_at_mut(&mut self, idx: usize) -> Option<&mut V> { + if idx < N { + self.data[idx].as_mut() + } else { + None + } + } + pub fn find_empty(&self) -> Option { for (i, slot) in self.data.iter().enumerate() { if slot.is_none() { @@ -137,14 +145,14 @@ impl Index for IndexMap type Output = V; fn index(&self, index: K) -> &Self::Output { - self.get(&index).unwrap() + self.get::(index).unwrap() } } impl IndexMut for IndexMap { fn index_mut(&mut self, index: K) -> &mut Self::Output { - self.get_mut(&index).unwrap() + self.get_mut::(index).unwrap() } } @@ -173,23 +181,23 @@ impl GetMut for IndexMap { } fn get2_mut>(&mut self, index1: Q, index2: Q) -> (Option<&mut Self::Output>, Option<&mut Self::Output>) { - let index1 = K::to_index(Some(index1.borrow())); - let index2 = K::to_index(Some(index2.borrow())); + let idx1 = K::to_index(Some(index1.borrow())); + let idx2 = K::to_index(Some(index2.borrow())); - if index1 == index2 { + if idx1 == idx2 { debug_assert!(false, "get2_mut called with identical indices"); return (None, None); } - let (left, right) = self.data.split_at_mut(index1.max(index2)); + let (left, right) = self.data.split_at_mut(idx1.max(idx2)); - if index1 < index2 { - let elem1 = left[index1].as_mut(); + if idx1 < idx2 { + let elem1 = left[idx1].as_mut(); let elem2 = right[0].as_mut(); (elem1, elem2) } else { let elem1 = right[0].as_mut(); - let elem2 = left[index2].as_mut(); + let elem2 = left[idx2].as_mut(); (elem1, elem2) } } @@ -200,18 +208,18 @@ impl GetMut for IndexMap { index2: Q, index3: Q, ) -> (Option<&mut Self::Output>, Option<&mut Self::Output>, Option<&mut Self::Output>) { - let index1 = K::to_index(Some(index1.borrow())); - let index2 = K::to_index(Some(index2.borrow())); - let index3 = K::to_index(Some(index3.borrow())); + let idx1 = K::to_index(Some(index1.borrow())); + let idx2 = K::to_index(Some(index2.borrow())); + let idx3 = K::to_index(Some(index3.borrow())); - if index1 == index2 || index1 == index3 || index2 == index3 { + if idx1 == idx2 || idx1 == idx3 || idx2 == idx3 { debug_assert!(false, "get3_mut called with identical indices"); return (None, None, None); } - let ptr1 = &mut self.data[index1] as *mut Option; - let ptr2 = &mut self.data[index2] as *mut Option; - let ptr3 = &mut self.data[index3] as *mut Option; + let ptr1 = &mut self.data[idx1] as *mut Option; + let ptr2 = &mut self.data[idx2] as *mut Option; + let ptr3 = &mut self.data[idx3] as *mut Option; // Safety: the elements at index1, index2 and index3 are nowhere else borrowed mutably by function contract. // And they are disjoint because of the check above. @@ -220,7 +228,7 @@ impl GetMut for IndexMap { } /// This is a vector that can store up to N elements inline and will allocate on the heap if more are needed. -#[derive(Debug)] +#[proc_macros::fmt] pub struct Vec { len: usize, data: [MaybeUninit; N], diff --git a/src/types/boxed.rs b/src/types/boxed.rs index 0f23e56..012245b 100644 --- a/src/types/boxed.rs +++ b/src/types/boxed.rs @@ -9,7 +9,7 @@ use core::{ }; /// A heap-allocated memory block. -#[derive(Debug)] +#[proc_macros::fmt] pub struct Box { /// Pointer to the heap-allocated memory. /// This is uniquely owned, so no covariance issues. diff --git a/src/types/heap.rs b/src/types/heap.rs index aa0b579..a25b1f8 100644 --- a/src/types/heap.rs +++ b/src/types/heap.rs @@ -5,7 +5,7 @@ use crate::error::Result; use super::array::Vec; /// An array-based binary heap, with N elements stored inline. -#[derive(Debug)] +#[proc_macros::fmt] pub struct BinaryHeap { vec: Vec, } diff --git a/src/types/list.rs b/src/types/list.rs index 7156d29..be84923 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -19,7 +19,8 @@ pub trait Linkable { } #[allow(dead_code)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[proc_macros::fmt] +#[derive(Clone, Copy, PartialEq, Eq)] pub struct Links { prev: Option, next: Option, @@ -231,7 +232,8 @@ mod tests { use super::{Linkable, Links, List}; use crate::types::{array::IndexMap, traits::{Get, ToIndex}}; - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + #[proc_macros::fmt] + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] struct Id(usize); impl ToIndex for Id { diff --git a/src/types/queue.rs b/src/types/queue.rs index 4e2d159..fbd0ba0 100644 --- a/src/types/queue.rs +++ b/src/types/queue.rs @@ -5,7 +5,7 @@ use super::boxed::Box; use crate::utils::KernelError; /// A ring-buffer based queue, with N elements stored inline. TODO: Make this growable. -#[derive(Debug)] +#[proc_macros::fmt] pub struct Queue { data: Vec, len: usize, diff --git a/src/types/rbtree.rs b/src/types/rbtree.rs index 842ffe2..3a5b0f0 100644 --- a/src/types/rbtree.rs +++ b/src/types/rbtree.rs @@ -22,7 +22,8 @@ pub trait Compare { } #[allow(dead_code)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[proc_macros::fmt] +#[derive(Clone, Copy, PartialEq, Eq)] pub struct Links { parent: Option, left: Option, @@ -44,7 +45,8 @@ impl Links { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[proc_macros::fmt] +#[derive(Clone, Copy, PartialEq, Eq)] enum Color { Red, Black, @@ -62,7 +64,8 @@ impl RbTree } /// Inserts `id` into the tree. If `id` already exists in the tree, it is first removed and then re-inserted. Errors if `id` does not exist in `storage`. - pub fn insert + GetMut>(&mut self, id: T, storage: &mut S) -> Result<()> + pub fn insert< + S: Get + GetMut>(&mut self, id: T, storage: &mut S) -> Result<()> where >::Output: Linkable + Compare,{ let already_linked = { let node = storage.get(id).ok_or(kerr!(NotFound))?; diff --git a/src/types/traits.rs b/src/types/traits.rs index cc02d85..aa8c951 100644 --- a/src/types/traits.rs +++ b/src/types/traits.rs @@ -18,6 +18,12 @@ pub trait ToIndex { fn to_index>(index: Option) -> usize; } +impl ToIndex for usize { + fn to_index>(index: Option) -> usize { + index.map_or(0, |i| *i.borrow()) + } +} + pub trait Project

{ fn project(&self) -> Option<&P>; fn project_mut(&mut self) -> Option<&mut P>; diff --git a/src/uapi/sched.rs b/src/uapi/sched.rs index 5437a2b..540060a 100644 --- a/src/uapi/sched.rs +++ b/src/uapi/sched.rs @@ -9,6 +9,18 @@ pub fn sleep_for(duration: u64) -> isize { hal::asm::syscall!(2, (duration >> 32) as u32, duration as u32) } +pub fn yield_thread() -> isize { + let until = u64::MAX; + hal::asm::syscall!(1, (until >> 32) as u32, until as u32) +} + pub fn spawn_thread(func_ptr: EntryFn) -> isize { hal::asm::syscall!(3, func_ptr as u32) +} + +pub fn exit(code: usize) -> ! { + hal::asm::syscall!(4, code as u32); + loop { + hal::asm::nop!(); + } } \ No newline at end of file From e1dc7becd9013071a735ceafb2edf7eefc0a472e Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Tue, 31 Mar 2026 18:35:45 +0000 Subject: [PATCH 14/42] fixed rt sched. --- examples/hello-world/Cargo.toml | 2 +- examples/hello-world/src/main.rs | 24 +++------ src/idle.rs | 2 +- src/macros.rs | 2 - src/sched.rs | 93 ++++++++++++++++++++++---------- src/sched/rr.rs | 8 +-- src/sched/rt.rs | 22 ++++---- src/sched/task.rs | 10 ++-- src/sched/thread.rs | 78 +++++++++++++++++---------- src/syscalls/sched.rs | 11 +++- src/uapi.rs | 3 +- src/uapi/sched.rs | 18 +++++-- src/uapi/time.rs | 9 ++++ src/uspace.rs | 2 + 14 files changed, 178 insertions(+), 106 deletions(-) create mode 100644 src/uapi/time.rs diff --git a/examples/hello-world/Cargo.toml b/examples/hello-world/Cargo.toml index 81645ee..1bd3456 100644 --- a/examples/hello-world/Cargo.toml +++ b/examples/hello-world/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -osiris = { workspace = true, features = ["defmt"] } +osiris = { workspace = true } [build-dependencies] cfg_aliases = "0.2.1" diff --git a/examples/hello-world/src/main.rs b/examples/hello-world/src/main.rs index baa7bb3..4bad2e0 100644 --- a/examples/hello-world/src/main.rs +++ b/examples/hello-world/src/main.rs @@ -4,26 +4,13 @@ use osiris::app_main; extern "C" fn second_thread() { - osiris::uprintln!("Hello from the second thread!"); - - let mut tick = 0; - for i in 0..5 { - osiris::uprintln!("Second thread tick: {}", tick); - tick += 1; - osiris::uapi::sched::sleep_for(1500); - } - - osiris::uapi::sched::exit(0); - osiris::uprintln!("This will never be printed."); -} - -extern "C" fn generator_thread() { - + let mut time = osiris::uapi::time::tick(); let mut cnt = 0; loop { - osiris::uapi::sched::yield_thread(); + time += 100; osiris::uprintln!("Number: {}", cnt); cnt += 1; + osiris::uapi::sched::sleep(time); } } @@ -31,8 +18,9 @@ extern "C" fn generator_thread() { fn main() { osiris::uprintln!("Hello World!"); let mut tick = 0; - osiris::uapi::sched::spawn_thread(second_thread); - osiris::uapi::sched::spawn_thread(generator_thread); + let attrs = osiris::uapi::sched::RtAttrs { deadline: 100, period: 100, budget: 100 }; + + osiris::uapi::sched::spawn_thread(second_thread, Some(attrs)); loop { osiris::uprintln!("Tick: {}", tick); tick += 1; diff --git a/src/idle.rs b/src/idle.rs index 479f407..940605e 100644 --- a/src/idle.rs +++ b/src/idle.rs @@ -7,7 +7,7 @@ extern "C" fn entry() { } pub fn init() { - let attrs = sched::thread::Attributes { entry, fin: None }; + let attrs = sched::thread::Attributes { entry, fin: None, attrs: None }; sched::with(|sched| { if let Err(e) = sched.create_thread(Some(sched::task::KERNEL_TASK), &attrs) { panic!("failed to create idle thread. Error: {}", e); diff --git a/src/macros.rs b/src/macros.rs index 0ddf819..78ddb33 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,6 +1,4 @@ //! Macros for kernel development. -use defmt_rtt as _; - #[macro_export] macro_rules! debug { diff --git a/src/sched.rs b/src/sched.rs index 4001fa1..4e5f130 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -94,7 +94,7 @@ impl Scheduler { } /// Triggers a reschedule at *latest* when we hit timepoint `next`. - fn schedule_resched(now: u64, next: u64) { + fn next_resched(now: u64, next: u64) { let old = NEXT_TICK.load(Ordering::Acquire); if old > now && old <= next { @@ -126,15 +126,18 @@ impl Scheduler { while let Some(uid) = self.wakeup.min() { let mut done = false; WaiterView::::with(&mut self.threads, |view| { - let waiter = view.get(uid).expect("THIS IS A BUG!"); - if waiter.until() > now { - Self::schedule_resched(now, waiter.until()); - done = true; - return; - } - - if let Err(_) = self.wakeup.remove(uid, view) { - bug!("failed to remove thread {} from wakeup tree.", uid); + if let Some(waiter) = view.get(uid) { + if waiter.until() > now { + Self::next_resched(now, waiter.until()); + done = true; + return; + } + + if let Err(_) = self.wakeup.remove(uid, view) { + bug!("failed to remove thread {} from wakeup tree.", uid); + } + } else { + bug!("failed to get thread {} from wakeup tree.", uid); } }); @@ -148,40 +151,57 @@ impl Scheduler { } } - pub fn do_sched(&mut self, now: u64) -> Option<(*mut c_void, &mut task::Task)> { + /// Syncs the new state after the last do_sched call to the scheduler, and returns whether we need to immediately reschedule. + fn sync_to_sched(&mut self, now: u64) -> bool { let dt = now - self.last_tick; self.last_tick = now; if let Some(old) = self.current { - rt::ServerView::::with(&mut self.threads, |view| { - self.rt_scheduler.put(old, dt, view); + let throttle = rt::ServerView::::with(&mut self.threads, |view| { + self.rt_scheduler.put(old, dt, view) }); - self.rr_scheduler.put(old, dt); + + if let Some(throttle) = throttle { + self.sleep_until(throttle, now); + return true; + } + + self.rr_scheduler.put(old, dt as u32); } self.do_wakeups(now); + false + } + + fn select_next(&mut self) -> (thread::UId, u32) { + rt::ServerView::::with(&mut self.threads, |view| self.rt_scheduler.pick(view)) + .or_else(|| self.rr_scheduler.pick(&mut self.threads)) + .unwrap_or((thread::IDLE_THREAD, 1000)) + } + + pub fn do_sched(&mut self, now: u64) -> Option<(*mut c_void, &mut task::Task)> { + // Sync the new state to the scheduler. + if self.sync_to_sched(now) { + // Trigger reschedule after interrupts are enabled. + return None; + } - let pick = - rt::ServerView::::with(&mut self.threads, |view| self.rt_scheduler.pick(now, view)); - let pick = pick.or_else(|| self.rr_scheduler.pick(&mut self.threads)); - let (new, budget) = pick.unwrap_or((thread::IDLE_THREAD, 1000)); + // Pick the next thread to run. + let (new, budget) = self.select_next(); // At this point, the task/thread must exist. Everything else is a bug. - let (ctx, task_id) = if let Some(thread) = self.threads.get(new) { - (thread.ctx(), thread.task_id()) - } else { + let Some(thread) = self.threads.get(new) else { bug!("failed to pick thread {}. Does not exist.", new); }; + let (ctx, task_id) = (thread.ctx(), thread.task_id()); - let task = if let Some(task) = self.tasks.get_mut(task_id) { - task - } else { + let Some(task) = self.tasks.get_mut(task_id) else { bug!("failed to get task {}. Does not exist.", task_id); }; // We don't need to resched if the thread has budget. self.current = Some(new); - Self::schedule_resched(now, now.saturating_add(budget)); + Self::next_resched(now, now.saturating_add(budget as u64)); Some((ctx, task)) } @@ -253,11 +273,23 @@ impl Scheduler { self.rt_scheduler.dequeue(id, view); }); self.rr_scheduler.dequeue(id, &mut self.threads); + self.wakeup + .remove(id, &mut WaiterView::::new(&mut self.threads)); if task.threads_mut().remove(id, &mut self.threads).is_err() { // This should not be possible. The thread ID is from the thread list of the task, so it must exist. bug!("failed to remove thread {} from task {}.", id, uid); } + + if self.threads.remove(&id).is_none() { + // This should not be possible. The thread ID is from the thread list of the task, so it must exist. + bug!("failed to remove thread {} from thread list.", id); + } + + if Some(id) == self.current { + self.current = None; + reschedule(); + } } self.tasks.remove(&uid).ok_or(kerr!(InvalidArgument))?; @@ -283,6 +315,15 @@ impl Scheduler { pub fn kill_thread(&mut self, uid: Option) -> Result<()> { let uid = uid.unwrap_or(self.current.ok_or(kerr!(InvalidArgument))?); self.dequeue(uid); + self.wakeup + .remove(uid, &mut WaiterView::::new(&mut self.threads)); + + self.tasks + .get_mut(uid.tid().owner()) + .ok_or(kerr!(InvalidArgument))? + .threads_mut() + .remove(uid, &mut self.threads)?; + self.threads.remove(&uid).ok_or(kerr!(InvalidArgument))?; if Some(uid) == self.current { @@ -353,8 +394,6 @@ pub extern "C" fn sched_enter(mut ctx: *mut c_void) -> *mut c_void { dispch::prepare(task); } ctx = new; - } else { - bug!("failed to schedule a thread. No threads available."); } ctx diff --git a/src/sched/rr.rs b/src/sched/rr.rs index ee9f877..1e966c9 100644 --- a/src/sched/rr.rs +++ b/src/sched/rr.rs @@ -6,8 +6,8 @@ pub struct Scheduler { queue: List, current: Option, - current_left: u64, - quantum: u64, + current_left: u32, + quantum: u32, } impl Scheduler { @@ -20,7 +20,7 @@ impl Scheduler { self.queue.push_back(uid, storage).map_err(|_| kerr!(InvalidArgument)) } - pub fn put(&mut self, uid: thread::UId, dt: u64) { + pub fn put(&mut self, uid: thread::UId, dt: u32) { if let Some(current) = self.current { if current == uid { self.current_left = self.current_left.saturating_sub(dt); @@ -28,7 +28,7 @@ impl Scheduler { } } - pub fn pick(&mut self, storage: &mut super::ThreadMap) -> Option<(thread::UId, u64)> { + pub fn pick(&mut self, storage: &mut super::ThreadMap) -> Option<(thread::UId, u32)> { match self.current { Some(current) if self.current_left > 0 => return Some((current, self.current_left)), Some(current) => { diff --git a/src/sched/rt.rs b/src/sched/rt.rs index 7c3405b..33799a6 100644 --- a/src/sched/rt.rs +++ b/src/sched/rt.rs @@ -15,31 +15,27 @@ impl Scheduler { pub fn enqueue(&mut self, uid: thread::UId, now: u64, storage: &mut ServerView) { if let Some(server) = storage.get_mut(uid) { - server.replenish(now); + // Threads are only enqueued when they are runnable. + server.on_wakeup(now); self.edf.insert(uid, storage); } } - pub fn put(&mut self, uid: thread::UId, dt: u64, storage: &mut ServerView) { + /// This should be called on each do_schedule call, to update the internal scheduler state. + /// If this function returns Some(u64) it means the current thread has exhausted its budget and should be throttled until the returned timestamp. + pub fn put(&mut self, uid: thread::UId, dt: u64, storage: &mut ServerView) -> Option { if Some(uid) == self.edf.min() { if let Some(server) = storage.get_mut(uid) { - server.consume(dt); + return server.consume(dt); } else { bug!("thread {} not found in storage", uid); } } - } - pub fn pick(&mut self, now: u64, storage: &mut ServerView) -> Option<(thread::UId, u64)> { - let id = self.edf.min()?; - - if storage.get(id)?.budget() == 0 { - self.edf.remove(id, storage); - storage.get_mut(id)?.replenish(now); - self.edf.insert(id, storage); - } + None + } - // Insert updated the min cache. + pub fn pick(&mut self, storage: &mut ServerView) -> Option<(thread::UId, u32)> { self.edf.min().and_then(|id| storage.get(id).map(|s| (id, s.budget()))) } diff --git a/src/sched/task.rs b/src/sched/task.rs index c1e094a..3c74835 100644 --- a/src/sched/task.rs +++ b/src/sched/task.rs @@ -9,12 +9,12 @@ use hal::Stack; use hal::stack::Stacklike; use crate::error::Result; -use crate::sched::{GlobalScheduler, ThreadMap, thread}; +use crate::sched::{ThreadMap, thread}; use crate::types::list; use crate::{mem, sched}; use crate::mem::vmm::AddressSpacelike; -use crate::types::traits::{Get, GetMut, ToIndex}; +use crate::types::traits::ToIndex; pub struct Defaults { pub stack_pages: usize, @@ -123,9 +123,9 @@ impl Task { let stack = unsafe { Stack::new(stack) }?; let tid = self.allocate_tid(); - let new = sched::thread::Thread::new(tid.get_uid(uid), stack); - storage.insert(&tid.get_uid(uid), new); - self.threads.push_back(tid.get_uid(uid), storage); + let new = sched::thread::Thread::new(tid.get_uid(uid), stack, attrs.attrs); + storage.insert(&tid.get_uid(uid), new)?; + self.threads.push_back(tid.get_uid(uid), storage)?; Ok(tid.get_uid(uid)) } diff --git a/src/sched/thread.rs b/src/sched/thread.rs index a83203d..80d01f1 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -3,19 +3,25 @@ use core::fmt::Display; use core::{borrow::Borrow, ffi::c_void}; -use hal::{Stack, stack::EntryFn}; use hal::stack::{FinFn, Stacklike}; +use hal::{Stack, stack::EntryFn}; use proc_macros::TaggedLinks; use crate::error::Result; use crate::sched::task::{self, KERNEL_TASK}; -use crate::time::tick; use crate::types::list; -use crate::{types::{rbtree::{self, Compare}, traits::{Project, ToIndex}}}; +use crate::types::{ + rbtree::{self, Compare}, + traits::{Project, ToIndex}, +}; +use crate::uapi; pub const IDLE_THREAD: UId = UId { uid: 1, - tid: Id { id: 0, owner: KERNEL_TASK }, + tid: Id { + id: 0, + owner: KERNEL_TASK, + }, }; /// Id of a task. This is only unique within a Task. @@ -132,12 +138,11 @@ pub struct State { } #[proc_macros::fmt] -#[derive(Clone, Copy)] -#[derive(TaggedLinks)] +#[derive(Clone, Copy, TaggedLinks)] pub struct RtServer { - budget: u64, - budget_left: u64, - period: u64, + budget: u32, + budget_left: u32, + period: u32, deadline: u64, // Back-reference to the thread uid. @@ -149,36 +154,50 @@ pub struct RtServer { } impl RtServer { - pub fn new(budget: u64, period: u64, uid: UId) -> Self { + pub fn new(budget: u32, period: u32, deadline: u64, uid: UId) -> Self { Self { budget, budget_left: budget, period, - deadline: tick() + period, + deadline, uid, _rt_links: rbtree::Links::new(), } } - pub fn budget_left(&self) -> u64 { + pub fn budget_left(&self) -> u32 { self.budget_left } - pub fn budget(&self) -> u64 { + pub fn budget(&self) -> u32 { self.budget } - pub fn replenish(&mut self, now: u64) { - self.deadline += self.period; - self.budget_left = self.budget; + fn violates_sched(&self, now: u64) -> bool { + self.budget_left as u64 * self.period as u64 + > self.budget as u64 * (self.deadline.saturating_sub(now)) } - pub fn consume(&mut self, dt: u64) { - if self.budget_left >= dt { - self.budget_left -= dt; - } else { - self.budget_left = 0; + pub fn on_wakeup(&mut self, now: u64) { + if self.deadline <= now || self.violates_sched(now) { + self.deadline = now + self.period as u64; + self.budget_left = self.budget; + } + } + + pub fn replenish(&mut self) { + self.deadline = self.deadline + self.period as u64; + self.budget_left += self.budget; + } + + pub fn consume(&mut self, dt: u64) -> Option { + self.budget_left = self.budget_left.saturating_sub(dt as u32); + + if self.budget_left == 0 { + return Some(self.deadline); } + + None } pub fn deadline(&self) -> u64 { @@ -203,8 +222,7 @@ impl Compare for RtServer { } #[proc_macros::fmt] -#[derive(Clone, Copy)] -#[derive(TaggedLinks)] +#[derive(Clone, Copy, TaggedLinks)] pub struct Waiter { /// The time when the Thread will be awakened. until: u64, @@ -261,12 +279,12 @@ pub struct ThreadList; pub struct Attributes { pub entry: EntryFn, pub fin: Option, + pub attrs: Option, } /// The struct representing a thread. #[proc_macros::fmt] -#[derive(Clone, Copy)] -#[derive(TaggedLinks)] +#[derive(Clone, Copy, TaggedLinks)] pub struct Thread { /// The current state of the thread. state: State, @@ -291,14 +309,16 @@ impl Thread { /// `stack` - The stack of the thread. /// /// Returns a new thread. - pub fn new(uid: UId, stack: Stack) -> Self { + pub fn new(uid: UId, stack: Stack, rtattrs: Option) -> Self { + let server = + rtattrs.map(|attrs| RtServer::new(attrs.budget, attrs.period, attrs.deadline, uid)); Self { state: State { run_state: RunState::Ready, stack, }, uid, - rt_server: None, + rt_server: server, waiter: None, rr_links: list::Links::new(), thread_links: list::Links::new(), @@ -348,7 +368,7 @@ impl PartialEq for Thread { impl Project for Thread { fn project(&self) -> Option<&RtServer> { - self.rt_server.as_ref() + self.rt_server.as_ref() } fn project_mut(&mut self) -> Option<&mut RtServer> { @@ -358,7 +378,7 @@ impl Project for Thread { impl Project for Thread { fn project(&self) -> Option<&Waiter> { - self.waiter.as_ref() + self.waiter.as_ref() } fn project_mut(&mut self) -> Option<&mut Waiter> { diff --git a/src/syscalls/sched.rs b/src/syscalls/sched.rs index 1972b31..258574e 100644 --- a/src/syscalls/sched.rs +++ b/src/syscalls/sched.rs @@ -4,7 +4,7 @@ use core::ffi::c_int; use proc_macros::syscall_handler; -use crate::{sched, time}; +use crate::{sched, time, uapi::sched::RtAttrs}; #[syscall_handler(num = 1)] fn sleep(until_hi: u32, until_lo: u32) -> c_int { @@ -30,11 +30,18 @@ fn sleep_for(duration_hi: u32, duration_lo: u32) -> c_int { } #[syscall_handler(num = 3)] -fn spawn_thread(func_ptr: usize) -> c_int { +fn spawn_thread(func_ptr: usize, attrs: *const RtAttrs) -> c_int { sched::with(|sched| { + let attrs = if attrs.is_null() { + None + } else { + Some(unsafe { *attrs }) + }; + let attrs = sched::thread::Attributes { entry: unsafe { core::mem::transmute(func_ptr) }, fin: None, + attrs, }; match sched.create_thread(None, &attrs) { Ok(uid) => { diff --git a/src/uapi.rs b/src/uapi.rs index bfc9b49..60a2b0e 100644 --- a/src/uapi.rs +++ b/src/uapi.rs @@ -1,2 +1,3 @@ pub mod print; -pub mod sched; \ No newline at end of file +pub mod sched; +pub mod time; \ No newline at end of file diff --git a/src/uapi/sched.rs b/src/uapi/sched.rs index 540060a..ef39306 100644 --- a/src/uapi/sched.rs +++ b/src/uapi/sched.rs @@ -1,6 +1,5 @@ use hal::stack::EntryFn; - pub fn sleep(until: u64) -> isize { hal::asm::syscall!(1, (until >> 32) as u32, until as u32) } @@ -14,8 +13,21 @@ pub fn yield_thread() -> isize { hal::asm::syscall!(1, (until >> 32) as u32, until as u32) } -pub fn spawn_thread(func_ptr: EntryFn) -> isize { - hal::asm::syscall!(3, func_ptr as u32) +#[repr(C)] +#[derive(Clone, Copy)] +pub struct RtAttrs { + pub deadline: u64, + pub period: u32, + pub budget: u32, +} + +pub fn spawn_thread(func_ptr: EntryFn, attrs: Option) -> isize { + let attr_ptr = if let Some(attrs) = attrs { + &attrs as *const RtAttrs as usize + } else { + 0 + }; + hal::asm::syscall!(3, func_ptr as u32, attr_ptr) } pub fn exit(code: usize) -> ! { diff --git a/src/uapi/time.rs b/src/uapi/time.rs new file mode 100644 index 0000000..1b63ebb --- /dev/null +++ b/src/uapi/time.rs @@ -0,0 +1,9 @@ +use crate::time; + +pub fn mono_now() -> u64 { + time::mono_now() +} + +pub fn tick() -> u64 { + time::tick() +} \ No newline at end of file diff --git a/src/uspace.rs b/src/uspace.rs index 2f11b34..91ae66b 100644 --- a/src/uspace.rs +++ b/src/uspace.rs @@ -15,7 +15,9 @@ pub fn init_app() { let attrs = sched::thread::Attributes { entry: app_main_entry, fin: None, + attrs: None, }; + sched::with(|sched| { if let Ok(uid) = sched.create_thread(Some(sched::task::KERNEL_TASK), &attrs) { if sched.enqueue(time::tick(), uid).is_err() { From b2aaba7a02633fc2c7628e0b2506d8455ddd4e1d Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Wed, 1 Apr 2026 15:42:57 +0000 Subject: [PATCH 15/42] fixed bug. --- machine/arm/stm32l4xx/interface/clock.c | 2 +- machine/arm/stm32l4xx/interface/lib.c | 2 +- machine/arm/stm32l4xx/interface/lib.h | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/machine/arm/stm32l4xx/interface/clock.c b/machine/arm/stm32l4xx/interface/clock.c index ed3acac..4110c3c 100644 --- a/machine/arm/stm32l4xx/interface/clock.c +++ b/machine/arm/stm32l4xx/interface/clock.c @@ -53,7 +53,7 @@ void tim2_hndlr(void) } } -void SystemClock_Config(void) +void init_clock_cfg(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; diff --git a/machine/arm/stm32l4xx/interface/lib.c b/machine/arm/stm32l4xx/interface/lib.c index 3c7d723..41319fa 100644 --- a/machine/arm/stm32l4xx/interface/lib.c +++ b/machine/arm/stm32l4xx/interface/lib.c @@ -31,7 +31,7 @@ void init_hal(void) { enable_faults(); - SystemClock_Config(); + init_clock_cfg(); init_systick(); } diff --git a/machine/arm/stm32l4xx/interface/lib.h b/machine/arm/stm32l4xx/interface/lib.h index 7b9637e..c66f171 100644 --- a/machine/arm/stm32l4xx/interface/lib.h +++ b/machine/arm/stm32l4xx/interface/lib.h @@ -1 +1,3 @@ -#pragma once \ No newline at end of file +#pragma once + +void init_clock_cfg(void); \ No newline at end of file From fc8e077095f54432719c23d3a823b300e1fea4de Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:37:46 +0000 Subject: [PATCH 16/42] wip: added rbtree. scheduler rewrite start. --- Cargo.lock | 75 ++- Cargo.toml | 2 + machine/arm/src/sched.rs | 2 +- machine/testing/src/sched.rs | 2 +- macros/src/lib.rs | 12 + macros/src/tree.rs | 99 ++++ src/lib.rs | 2 +- src/mem.rs | 61 +- src/mem/alloc.rs | 3 +- src/mem/array.rs | 262 +++++++-- src/mem/rbtree.rs | 1061 ++++++++++++++++++++++++++++++++++ src/mem/traits.rs | 24 + src/mem/view.rs | 63 ++ src/sched.rs | 71 ++- src/sched/rt.rs | 42 ++ src/sched/task.rs | 8 +- src/sched/thread.rs | 216 ++++--- src/syscalls/tasks.rs | 3 +- src/utils.rs | 4 + 19 files changed, 1807 insertions(+), 205 deletions(-) create mode 100644 macros/src/tree.rs create mode 100644 src/mem/rbtree.rs create mode 100644 src/mem/traits.rs create mode 100644 src/mem/view.rs create mode 100644 src/sched/rt.rs diff --git a/Cargo.lock b/Cargo.lock index 933791c..9f3703b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -894,6 +894,36 @@ dependencies = [ "syn", ] +[[package]] +name = "kani" +version = "0.67.0" +source = "git+https://github.com/model-checking/kani#ac1f0a1c03fcbb805002154689ba15184e2f36b7" +dependencies = [ + "kani_core", + "kani_macros", +] + +[[package]] +name = "kani_core" +version = "0.67.0" +source = "git+https://github.com/model-checking/kani#ac1f0a1c03fcbb805002154689ba15184e2f36b7" +dependencies = [ + "kani_macros", +] + +[[package]] +name = "kani_macros" +version = "0.67.0" +source = "git+https://github.com/model-checking/kani#ac1f0a1c03fcbb805002154689ba15184e2f36b7" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "strum 0.27.2", + "strum_macros 0.27.2", + "syn", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1067,6 +1097,7 @@ dependencies = [ "hal-select", "hal-testing", "interface", + "kani", "macros", "quote", "rand", @@ -1154,6 +1185,28 @@ dependencies = [ "syn", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.105" @@ -1223,7 +1276,7 @@ dependencies = [ "itertools 0.13.0", "lru", "paste", - "strum", + "strum 0.26.3", "unicode-segmentation", "unicode-truncate", "unicode-width 0.2.0", @@ -1500,9 +1553,15 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "strum_macros", + "strum_macros 0.26.4", ] +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" + [[package]] name = "strum_macros" version = "0.26.4" @@ -1516,6 +1575,18 @@ dependencies = [ "syn", ] +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "syn" version = "2.0.114" diff --git a/Cargo.toml b/Cargo.toml index cb3fff1..38c8641 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,8 @@ envparse = "0.1.0" # This is a host-compatible HAL which will be used for running tests and verification on the host. hal-testing = { path = "machine/testing", features = [] } +[target.'cfg(kani_ra)'.dependencies] +kani = { git = "https://github.com/model-checking/kani" } [features] default = [] diff --git a/machine/arm/src/sched.rs b/machine/arm/src/sched.rs index 18b463f..3026071 100644 --- a/machine/arm/src/sched.rs +++ b/machine/arm/src/sched.rs @@ -55,7 +55,7 @@ impl Add for StackPtr { } /// A stack on arm is 4 byte aligned and grows downwards. -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct ArmStack { /// The top of the stack (highest address). /// Safety: NonNull can safely be covariant over u32. diff --git a/machine/testing/src/sched.rs b/machine/testing/src/sched.rs index 7bf874d..9715b16 100644 --- a/machine/testing/src/sched.rs +++ b/machine/testing/src/sched.rs @@ -5,7 +5,7 @@ use hal_api::{ stack::{StackDescriptor, Stacklike}, }; -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct TestingStack {} impl Stacklike for TestingStack { diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 903945a..c871c86 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -3,6 +3,18 @@ use syn::parse_macro_input; use proc_macro2::TokenStream; +mod tree; + +#[proc_macro_derive(TaggedLinks, attributes(rbtree))] +pub fn derive_tagged_links(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = syn::parse_macro_input!(input as syn::DeriveInput); + + match tree::derive_tagged_links(&input) { + Ok(tokens) => tokens, + Err(e) => e.to_compile_error(), + }.into() +} + #[proc_macro_attribute] pub fn service( attr: proc_macro::TokenStream, diff --git a/macros/src/tree.rs b/macros/src/tree.rs new file mode 100644 index 0000000..f6c3b39 --- /dev/null +++ b/macros/src/tree.rs @@ -0,0 +1,99 @@ +use quote::quote; +use syn::{ + spanned::Spanned, Data, DeriveInput, Error, Fields, Path, +}; + +pub fn derive_tagged_links(input: &DeriveInput) -> syn::Result { + let fields = match &input.data { + Data::Struct(ds) => match &ds.fields { + Fields::Named(named) => &named.named, + _ => { + return Err(Error::new( + ds.fields.span(), + "TaggedLinks only supports structs with named fields", + )) + } + }, + _ => { + return Err(Error::new( + input.span(), + "TaggedLinks can only be derived for structs", + )) + } + }; + + let rbtree_impls = impl_rbtree(input, fields)?; + + Ok(quote! { + #rbtree_impls + }) +} + +fn impl_rbtree(input: &DeriveInput, fields: &syn::punctuated::Punctuated) -> syn::Result { + let struct_ident = &input.ident; + let generics = &input.generics; + + let mut impls = Vec::new(); + + for field in fields { + let Some(field_ident) = field.ident.clone() else { continue }; + + if let (Some(tag_path), Some(idx_path)) = find_rbtree(&field.attrs)? { + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let impl_block = quote! { + impl #impl_generics crate::mem::rbtree::Linkable<#tag_path, #idx_path> for #struct_ident #ty_generics #where_clause { + #[inline] + fn links(&self) -> &crate::mem::rbtree::Links<#tag_path, #idx_path> { + &self.#field_ident + } + #[inline] + fn links_mut(&mut self) -> &mut crate::mem::rbtree::Links<#tag_path, #idx_path> { + &mut self.#field_ident + } + } + }; + + impls.push(impl_block); + } + } + + if impls.is_empty() { + return Err(Error::new( + input.span(), + "No fields found with #[rbtree(tag = ..., idx = ...)] attribute", + )); + } + + Ok(quote! { #(#impls)* }) +} + +fn find_rbtree(attrs: &[syn::Attribute]) -> syn::Result<(Option, Option)> { + for attr in attrs { + if !attr.path().is_ident("rbtree") { + continue; + } + + let mut tag: Option = None; + let mut idx: Option = None; + + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("tag") { + let value = meta.value()?; // expects '=' + let p: Path = value.parse()?; + tag = Some(p); + Ok(()) + } else if meta.path.is_ident("idx") { + let value = meta.value()?; // expects '=' + let p: Path = value.parse()?; + idx = Some(p); + Ok(()) + } else { + Err(meta.error("expected `tag = SomePath` or `idx = SomePath`")) + } + })?; + + return Ok((tag, idx)); + } + Ok((None, None)) +} diff --git a/src/lib.rs b/src/lib.rs index cb40e60..287fd3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ pub mod print; pub mod sched; pub mod sync; pub mod syscalls; -pub mod time; +//pub mod time; pub mod uspace; use hal::Machinelike; diff --git a/src/mem.rs b/src/mem.rs index 897e031..6865d6b 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -11,6 +11,9 @@ pub mod boxed; pub mod heap; pub mod pool; pub mod queue; +pub mod rbtree; +pub mod traits; +pub mod view; /// The possible types of memory. Which is compatible with the multiboot2 memory map. /// Link: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html @@ -88,61 +91,3 @@ pub fn align_up(size: usize) -> usize { let align = align_of::(); (size + align - 1) & !(align - 1) } - -// VERIFICATION ----------------------------------------------------------------------------------- -#[cfg(kani)] -mod verification { - use super::*; - use crate::mem::alloc::MAX_ADDR; - - fn mock_ptr_write(dst: *mut T, src: T) { - // noop - } - - #[kani::proof] - #[kani::stub(core::ptr::write, mock_ptr_write)] - fn proof_init_allocator_good() { - const MAX_REGIONS: usize = 8; - let regions: [(&str, usize, usize); MAX_REGIONS] = - core::array::from_fn(|i| ("dummy", kani::any(), kani::any())); - - // contrain all regions - for &(_, base, size) in regions.iter() { - kani::assume(base % align_of::() == 0); - kani::assume(base > 0); - kani::assume(size > 0); - kani::assume(size < alloc::MAX_ADDR && size > alloc::BestFitAllocator::MIN_RANGE_SIZE); - kani::assume(base < alloc::MAX_ADDR - size); - } - - // for any i, j, i != j as indices into the memory regions the following should hold - let i: usize = kani::any(); - let j: usize = kani::any(); - kani::assume(i < MAX_REGIONS); - kani::assume(j < MAX_REGIONS); - kani::assume(i != j); - - // non-overlapping regions - let (_, base_i, size_i) = regions[i]; - let (_, base_j, size_j) = regions[j]; - kani::assume(base_i + size_i <= base_j || base_j + size_j <= base_i); - - // verify memory init - assert!(init_memory(®ions).is_ok()); - } - - #[kani::proof] - fn check_align_up() { - let size = kani::any(); - kani::assume(size > 0); - - let align = align_up(size); - assert_ne!(align, 0); - - if align != usize::MAX { - assert_eq!(align % align_of::(), 0); - assert!(align >= size); - } - } -} -// END VERIFICATION diff --git a/src/mem/alloc.rs b/src/mem/alloc.rs index 710ad03..415bccc 100644 --- a/src/mem/alloc.rs +++ b/src/mem/alloc.rs @@ -45,7 +45,8 @@ pub struct BestFitAllocator { /// Implementation of the BestFitAllocator. impl BestFitAllocator { - pub const MIN_RANGE_SIZE: usize = size_of::() + Self::align_up() + 1; + pub const MIN_RANGE_SIZE: usize = size_of::() + Self::align_up() + 1; + /// Creates a new BestFitAllocator. /// /// Returns the new BestFitAllocator. diff --git a/src/mem/array.rs b/src/mem/array.rs index e7274db..4e40954 100644 --- a/src/mem/array.rs +++ b/src/mem/array.rs @@ -1,18 +1,26 @@ //! This module implements static and dynamic arrays for in-kernel use. use super::boxed::Box; -use crate::utils::KernelError; +use crate::{ + mem::traits::{Get, GetMut, ToIndex}, + utils::KernelError, +}; use core::{borrow::Borrow, mem::MaybeUninit}; +use std::{ + ops::{Index, IndexMut}, +}; /// This is a fixed-size map that can store up to N consecutive elements. #[derive(Debug)] -pub struct IndexMap + Default, V, const N: usize> { +pub struct IndexMap +{ data: [Option; N], phantom: core::marker::PhantomData, } #[allow(dead_code)] -impl + Default, V, const N: usize> IndexMap { +impl IndexMap +{ /// Create a new IndexMap. /// /// Returns a new IndexMap. @@ -23,47 +31,17 @@ impl + Default, V, const N: usize> IndexMap { } } - /// Get the element at the given index. - /// - /// `index` - The index to get the element from. - /// - /// Returns `Some(&T)` if the index is in-bounds, otherwise `None`. - pub fn get(&self, index: &K) -> Option<&V> { - let index = *index.borrow(); - - if index < N { - self.data[index].as_ref() - } else { - None - } - } - - /// Get a mutable reference to the element at the given index. - /// - /// `index` - The index to get the element from. - /// - /// Returns `Some(&mut T)` if the index is in-bounds, otherwise `None`. - pub fn get_mut(&mut self, index: &K) -> Option<&mut V> { - let index = *index.borrow(); - - if index < N { - self.data[index].as_mut() - } else { - None - } - } - /// Insert a value at the given index. /// /// `index` - The index to insert the value at. /// `value` - The value to insert. /// /// Returns `Ok(())` if the index was in-bounds, otherwise `Err(KernelError::OutOfMemory)`. - pub fn insert(&mut self, index: &K, value: V) -> Result<(), KernelError> { - let index = *index.borrow(); + pub fn insert(&mut self, idx: &K, value: V) -> Result<(), KernelError> { + let idx = K::to_index(Some(idx)); - if index < N { - self.data[index] = Some(value); + if idx < N { + self.data[idx] = Some(value); Ok(()) } else { Err(KernelError::OutOfMemory) @@ -91,11 +69,11 @@ impl + Default, V, const N: usize> IndexMap { /// `index` - The index to remove the value from. /// /// Returns the value if it was removed, otherwise `None`. - pub fn remove(&mut self, index: &K) -> Option { - let index = *index.borrow(); + pub fn remove(&mut self, idx: &K) -> Option { + let idx = K::to_index(Some(idx)); - if index < N { - self.data[index].take() + if idx < N { + self.data[idx].take() } else { None } @@ -113,8 +91,8 @@ impl + Default, V, const N: usize> IndexMap { /// `index` - The index to start the iterator from. /// /// Returns an iterator over the elements in the map. - pub fn iter_from_cycle(&self, index: &K) -> impl Iterator> { - self.data.iter().cycle().skip(index.borrow() + 1) + pub fn iter_from_cycle(&self, idx: Option<&K>) -> impl Iterator> { + self.data.iter().cycle().skip(K::to_index(idx) + 1) } /// Get the next index that contains a value (this will cycle). @@ -122,13 +100,11 @@ impl + Default, V, const N: usize> IndexMap { /// `index` - The index to start the search from. /// /// Returns the next index (potentially < index) that contains a value, otherwise `None`. - pub fn next(&self, index: Option<&K>) -> Option { - let default = K::default(); - let index = index.unwrap_or(&default); - - for (i, elem) in self.iter_from_cycle(index).enumerate() { + pub fn next(&self, idx: Option<&K>) -> Option { + for (i, elem) in self.iter_from_cycle(idx).enumerate() { if elem.is_some() { - return Some((index.borrow() + i + 1) % N); + let idx = K::to_index(idx); + return Some((idx + i + 1) % N); } } @@ -146,6 +122,88 @@ impl + Default, V, const N: usize> IndexMap { } } +impl Index for IndexMap +{ + type Output = V; + + fn index(&self, index: K) -> &Self::Output { + self.get(&index).unwrap() + } +} + +impl IndexMut for IndexMap +{ + fn index_mut(&mut self, index: K) -> &mut Self::Output { + self.get_mut(&index).unwrap() + } +} + +impl Get for IndexMap +{ + type Output = V; + + fn get>(&self, index: Q) -> Option<&Self::Output> { + let idx = K::to_index(Some(index.borrow())); + if idx < N { + self.data[idx].as_ref() + } else { + None + } + } +} + +impl GetMut for IndexMap { + fn get_mut>(&mut self, index: Q) -> Option<&mut Self::Output> { + let idx = K::to_index(Some(index.borrow())); + if idx < N { + self.data[idx].as_mut() + } else { + None + } + } + + fn get2_mut>(&mut self, index1: Q, index2: Q) -> (Option<&mut Self::Output>, Option<&mut Self::Output>) { + let index1 = K::to_index(Some(index1.borrow())); + let index2 = K::to_index(Some(index2.borrow())); + + if index1 == index2 { + debug_assert!(false, "get2_mut called with identical indices"); + return (None, None); + } + + let ptr1 = &mut self.data[index1] as *mut Option; + let ptr2 = &mut self.data[index2] as *mut Option; + + // Safety: the elements at index1 and index2 are nowhere else borrowed mutably by function contract. + // And they are disjoint because of the check above. + unsafe { ((*ptr1).as_mut(), (*ptr2).as_mut()) } + } + + fn get3_mut>( + &mut self, + index1: Q, + index2: Q, + index3: Q, + ) -> (Option<&mut Self::Output>, Option<&mut Self::Output>, Option<&mut Self::Output>) { + let index1 = K::to_index(Some(index1.borrow())); + let index2 = K::to_index(Some(index2.borrow())); + let index3 = K::to_index(Some(index3.borrow())); + + if index1 == index2 || index1 == index3 || index2 == index3 { + debug_assert!(false, "get3_mut called with identical indices"); + return (None, None, None); + } + + let ptr1 = &mut self.data[index1] as *mut Option; + let ptr2 = &mut self.data[index2] as *mut Option; + let ptr3 = &mut self.data[index3] as *mut Option; + + // Safety: the elements at index1, index2 and index3 are nowhere else borrowed mutably by function contract. + // And they are disjoint because of the check above. + unsafe { ((*ptr1).as_mut(), (*ptr2).as_mut(), (*ptr3).as_mut()) } + } +} + /// This is a vector that can store up to N elements inline and will allocate on the heap if more are needed. #[derive(Debug)] pub struct Vec { @@ -403,6 +461,66 @@ impl Vec { } } + fn at_mut_unchecked(&mut self, index: usize) -> *mut T { + if index < N { + // Safety: the elements until self.len are initialized. + // The element at index is nowhere else borrowed mutably by function contract. + self.data[index].as_mut_ptr() + } else { + let index = index - N; + // Safety: the elements until self.len - N are initialized. + // The element at index is nowhere else borrowed mutably by function contract. + self.extra[index].as_mut_ptr() + } + } + + /// Get disjoint mutable references to the values at the given indices. + /// + /// `index1` - The first index. + /// `index2` - The second index. + /// + /// Returns `Some(&mut T, &mut T)` if the indices are in-bounds and disjoint, otherwise `None`. + pub fn at2_mut(&mut self, index1: usize, index2: usize) -> (Option<&mut T>, Option<&mut T>) { + if index1 == index2 { + debug_assert!(false, "at2_mut called with identical indices"); + return (None, None); + } + + let ptr1 = self.at_mut_unchecked(index1); + let ptr2 = self.at_mut_unchecked(index2); + + // Safety: the elements at index1 and index2 are nowhere else borrowed mutably by function contract. + // And they are disjoint because of the check above. + unsafe { (Some(&mut *ptr1), Some(&mut *ptr2)) } + } + + /// Get disjoint mutable references to the values at the given indices. + /// + /// `index1` - The first index. + /// `index2` - The second index. + /// `index3` - The third index. + /// + /// Returns `Some(&mut T, &mut T, &mut T)` if the indices are in-bounds and disjoint, otherwise `None`. + pub fn at3_mut( + &mut self, + index1: usize, + index2: usize, + index3: usize, + ) -> (Option<&mut T>, Option<&mut T>, Option<&mut T>) { + if index1 == index2 || index1 == index3 || index2 == index3 { + debug_assert!(false, "at3_mut called with identical indices"); + return (None, None, None); + } + + let ptr1 = self.at_mut_unchecked(index1); + let ptr2 = self.at_mut_unchecked(index2); + let ptr3 = self.at_mut_unchecked(index3); + + // Safety: the elements at index1, index2 and index3 are nowhere else borrowed mutably by function contract. + // And they are disjoint because of the check above. + unsafe { (Some(&mut *ptr1), Some(&mut *ptr2), Some(&mut *ptr3)) } + } + /// Swap the values at the given indices. /// /// `a` - The first index. @@ -465,7 +583,47 @@ impl Drop for Vec { } } -#[cfg(kani)] -mod verification { - use super::*; +impl Index for Vec { + type Output = T; + + fn index(&self, index: usize) -> &Self::Output { + self.at(index).unwrap() + } +} + +impl IndexMut for Vec { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.at_mut(index).unwrap() + } +} + +impl Get for Vec { + type Output = T; + + fn get>(&self, index: Q) -> Option<&Self::Output> { + self.at(*index.borrow()) + } +} + +impl GetMut for Vec { + fn get_mut>(&mut self, index: Q) -> Option<&mut Self::Output> { + self.at_mut(*index.borrow()) + } + + fn get2_mut>( + &mut self, + index1: Q, + index2: Q, + ) -> (Option<&mut Self::Output>, Option<&mut Self::Output>) { + self.at2_mut(*index1.borrow(), *index2.borrow()) + } + + fn get3_mut>( + &mut self, + index1: Q, + index2: Q, + index3: Q, + ) -> (Option<&mut Self::Output>, Option<&mut Self::Output>, Option<&mut Self::Output>) { + self.at3_mut(*index1.borrow(), *index2.borrow(), *index3.borrow()) + } } diff --git a/src/mem/rbtree.rs b/src/mem/rbtree.rs new file mode 100644 index 0000000..2f7a5f0 --- /dev/null +++ b/src/mem/rbtree.rs @@ -0,0 +1,1061 @@ +use std::{marker::PhantomData}; + +use crate::mem::traits::{Get, GetMut}; + +#[allow(dead_code)] +pub struct RbTree { + root: Option, + min: Option, + _tag: PhantomData, +} + +#[allow(dead_code)] +pub trait Linkable { + fn links(&self) -> &Links; + fn links_mut(&mut self) -> &mut Links; +} + +pub trait Compare { + fn cmp(&self, other: &Self) -> core::cmp::Ordering; +} + +#[allow(dead_code)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Links { + parent: Option, + left: Option, + right: Option, + color: Color, + _tag: PhantomData, +} + +#[allow(dead_code)] +impl Links { + pub fn new() -> Self { + Self { + parent: None, + left: None, + right: None, + color: Color::Red, + _tag: PhantomData, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum Color { + Red, + Black, +} + +#[allow(dead_code)] +impl RbTree +{ + pub fn new() -> Self { + Self { + root: None, + min: None, + _tag: PhantomData, + } + } + + pub fn insert + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + where >::Output: Linkable + Compare,{ + let mut last = None; + + { + let node = storage.get(id).ok_or(())?; + let mut current = self.root; + + while let Some(current_id) = current { + last = current; + let current_node = storage.get(current_id).ok_or(())?; + let go_left = node.cmp(current_node) == core::cmp::Ordering::Less; + + current = if go_left { + current_node.links().left + } else { + current_node.links().right + }; + } + } + + { + let node = storage.get_mut(id).ok_or(())?.links_mut(); + node.parent = last; + node.left = None; + node.right = None; + node.color = Color::Red; + } + + match last { + None => self.root = Some(id), + Some(last_id) => { + if let (Some(node), Some(last)) = storage.get2_mut(id, last_id) { + if node.cmp(last) == core::cmp::Ordering::Less { + last.links_mut().left = Some(id); + } else { + last.links_mut().right = Some(id); + } + } + } + } + + if let Some(min_id) = self.min { + let node = storage.get(id).ok_or(())?; + let min_node = storage.get(min_id).ok_or(())?; + if node.cmp(min_node) == core::cmp::Ordering::Less { + self.min = Some(id); + } + } else { + self.min = Some(id); + } + + self.insert_fixup(id, storage) + } + + pub fn remove + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + where >::Output: Linkable + Compare { + let (node_left, node_right, node_parent, node_is_red) = { + let node = storage.get(id).ok_or(())?; + ( + node.links().left, + node.links().right, + node.links().parent, + matches!(node.links().color, Color::Red), + ) + }; + + let mut succ_was_red = node_is_red; + let child: Option; + let child_parent: Option; + + if node_left.is_none() { + child = node_right; + child_parent = node_parent; + + self.transplant(id, node_right, storage)?; + } else if node_right.is_none() { + child = node_left; + child_parent = node_parent; + + self.transplant(id, node_left, storage)?; + } else { + let right_id = node_right.ok_or(())?; + let succ = self.minimum(right_id, storage)?; + let succ_right = storage.get(succ).and_then(|n| n.links().right); + let succ_parent = storage.get(succ).and_then(|n| n.links().parent); + + succ_was_red = storage + .get(succ) + .map_or(false, |n| matches!(n.links().color, Color::Red)); + child = succ_right; + + if succ_parent == Some(id) { + child_parent = Some(succ); + } else { + self.transplant(succ, succ_right, storage)?; + + if let (Some(succ_node), Some(right_node)) = storage.get2_mut(succ, right_id) { + succ_node.links_mut().right = Some(right_id); + right_node.links_mut().parent = Some(succ); + } else { + return Err(()); + } + + child_parent = succ_parent; + } + + self.transplant(id, Some(succ), storage)?; + + let left_id = node_left.ok_or(())?; + + if let (Some(succ_node), Some(left_node)) = storage.get2_mut(succ, left_id) { + succ_node.links_mut().left = Some(left_id); + left_node.links_mut().parent = Some(succ); + } else { + return Err(()); + } + + if let Some(succ_node) = storage.get_mut(succ) { + succ_node.links_mut().color = if node_is_red { + Color::Red + } else { + Color::Black + }; + } else { + return Err(()); + } + } + + if !succ_was_red { + self.delete_fixup(child, child_parent, storage)?; + } + + if self.min == Some(id) { + self.min = match self.root { + Some(root_id) => Some(self.minimum(root_id, storage)?), + None => None, + }; + } + + Ok(()) + } + + pub fn min(&self) -> Option { + self.min + } + + fn insert_fixup + GetMut>(&mut self, mut id: T, storage: &mut S) -> Result<(), ()> + where >::Output: Linkable + Compare, { + while let Some(parent) = storage.get(id).and_then(|n| n.links().parent) + && storage + .get(parent) + .map_or(false, |n| matches!(n.links().color, Color::Red)) + { + let grandparent = storage + .get(parent) + .and_then(|n| n.links().parent) + .ok_or(())?; + + // Is left child node + if storage + .get(grandparent) + .map_or(false, |n| n.links().left == Some(parent)) + { + // Uncle node must be the right child node + let uncle = storage.get(grandparent).and_then(|n| n.links().right); + + if let Some(uncle_id) = uncle + && storage + .get(uncle_id) + .map_or(false, |n| matches!(n.links().color, Color::Red)) + { + // Parent and uncle nodes are red + if let (Some(parent_node), Some(uncle_node), Some(grandparent_node)) = + storage.get3_mut(parent, uncle_id, grandparent) + { + parent_node.links_mut().color = Color::Black; + uncle_node.links_mut().color = Color::Black; + grandparent_node.links_mut().color = Color::Red; + } + id = grandparent; + } else { + // Uncle node is black + if storage + .get(parent) + .map_or(false, |n| n.links().right == Some(id)) + { + let old_parent = parent; + self.rotate_left(parent, id, storage)?; + id = old_parent; + } + + let parent = storage.get(id).and_then(|n| n.links().parent).ok_or(())?; + let grandparent = storage + .get(parent) + .and_then(|n| n.links().parent) + .ok_or(())?; + + if let (Some(parent_node), Some(grandparent_node)) = + storage.get2_mut(parent, grandparent) + { + parent_node.links_mut().color = Color::Black; + grandparent_node.links_mut().color = Color::Red; + } + self.rotate_right(grandparent, parent, storage)?; + break; + } + } else { + // Uncle node must be the left child + let uncle = storage.get(grandparent).and_then(|n| n.links().left); + + if let Some(uncle_id) = uncle + && storage + .get(uncle_id) + .map_or(false, |n| matches!(n.links().color, Color::Red)) + { + // Parent and uncle nodes are red + if let (Some(parent_node), Some(uncle_node), Some(grandparent_node)) = + storage.get3_mut(parent, uncle_id, grandparent) + { + parent_node.links_mut().color = Color::Black; + uncle_node.links_mut().color = Color::Black; + grandparent_node.links_mut().color = Color::Red; + } + id = grandparent; + } else { + // Uncle node is black + if storage + .get(parent) + .map_or(false, |n| n.links().left == Some(id)) + { + let old_parent = parent; + self.rotate_right(parent, id, storage)?; + id = old_parent; + } + + let parent = storage.get(id).and_then(|n| n.links().parent).ok_or(())?; + let grandparent = storage + .get(parent) + .and_then(|n| n.links().parent) + .ok_or(())?; + + if let (Some(parent_node), Some(grandparent_node)) = + storage.get2_mut(parent, grandparent) + { + parent_node.links_mut().color = Color::Black; + grandparent_node.links_mut().color = Color::Red; + } + self.rotate_left(grandparent, parent, storage)?; + break; + } + } + } + + if let Some(root_id) = self.root { + if let Some(root_node) = storage.get_mut(root_id) { + root_node.links_mut().color = Color::Black; + } + } + + Ok(()) + } + + fn delete_fixup + GetMut>( + &mut self, + mut id: Option, + mut parent: Option, + storage: &mut S, + ) -> Result<(), ()> + where >::Output: Linkable + Compare, { + let is_red = |node_id: Option, storage: &S| -> bool { + node_id + .and_then(|id| storage.get(id)) + .map_or(false, |n| matches!(n.links().color, Color::Red)) + }; + + let is_black = |node_id: Option, storage: &S| -> bool { !is_red(node_id, storage) }; + + while id != self.root && is_black(id, storage) { + let parent_id = parent.ok_or(())?; + + let is_left_child = storage + .get(parent_id) + .map_or(false, |n| n.links().left == id); + + if is_left_child { + let mut sibling_opt = storage.get(parent_id).and_then(|n| n.links().right); + + if is_red(sibling_opt, storage) { + let sibling_id = sibling_opt.ok_or(())?; + // Color sibling node black and parent node red, rotate + if let (Some(sib), Some(par)) = storage.get2_mut(sibling_id, parent_id) { + sib.links_mut().color = Color::Black; + par.links_mut().color = Color::Red; + } else { + return Err(()); + } + self.rotate_left(parent_id, sibling_id, storage)?; + sibling_opt = storage.get(parent_id).and_then(|n| n.links().right); + } + + // Sibling node is black + let sibling_id = sibling_opt.ok_or(())?; + let sib_left = storage.get(sibling_id).and_then(|n| n.links().left); + let sib_right = storage.get(sibling_id).and_then(|n| n.links().right); + + if is_black(sib_left, storage) && is_black(sib_right, storage) { + // Color sibling node red and move up + if let Some(sib) = storage.get_mut(sibling_id) { + sib.links_mut().color = Color::Red; + } else { + return Err(()); + } + id = Some(parent_id); + parent = storage.get(parent_id).and_then(|n| n.links().parent); + } else { + // Sibling's left node is red + if is_black(sib_right, storage) { + let sib_left_id = sib_left.ok_or(())?; + if let (Some(sib), Some(left)) = storage.get2_mut(sibling_id, sib_left_id) { + sib.links_mut().color = Color::Red; + left.links_mut().color = Color::Black; + } else { + return Err(()); + } + self.rotate_right(sibling_id, sib_left_id, storage)?; + sibling_opt = storage.get(parent_id).and_then(|n| n.links().right); + } + + // Sibling's right child node is red + let sibling_id = sibling_opt.ok_or(())?; + let parent_is_red = storage + .get(parent_id) + .map_or(false, |n| matches!(n.links().color, Color::Red)); + + if let Some(sib) = storage.get_mut(sibling_id) { + sib.links_mut().color = if parent_is_red { + Color::Red + } else { + Color::Black + }; + } + if let Some(par) = storage.get_mut(parent_id) { + par.links_mut().color = Color::Black; + } + + let sib_right = storage.get(sibling_id).and_then(|n| n.links().right); + if let Some(sib_right_id) = sib_right { + if let Some(right) = storage.get_mut(sib_right_id) { + right.links_mut().color = Color::Black; + } + } + + self.rotate_left(parent_id, sibling_id, storage)?; + id = self.root; + break; + } + } else { + let mut sibling_opt = storage.get(parent_id).and_then(|n| n.links().left); + + if is_red(sibling_opt, storage) { + let sibling_id = sibling_opt.ok_or(())?; + if let (Some(sib), Some(par)) = storage.get2_mut(sibling_id, parent_id) { + sib.links_mut().color = Color::Black; + par.links_mut().color = Color::Red; + } else { + return Err(()); + } + self.rotate_right(parent_id, sibling_id, storage)?; + sibling_opt = storage.get(parent_id).and_then(|n| n.links().left); + } + + // Sibling node is black + let sibling_id = sibling_opt.ok_or(())?; + let sib_left = storage.get(sibling_id).and_then(|n| n.links().left); + let sib_right = storage.get(sibling_id).and_then(|n| n.links().right); + + if is_black(sib_left, storage) && is_black(sib_right, storage) { + if let Some(sib) = storage.get_mut(sibling_id) { + sib.links_mut().color = Color::Red; + } else { + return Err(()); + } + id = Some(parent_id); + parent = storage.get(parent_id).and_then(|n| n.links().parent); + } else { + // Sibling's right node is red + if is_black(sib_left, storage) { + let sib_right_id = sib_right.ok_or(())?; + if let (Some(sib), Some(right)) = storage.get2_mut(sibling_id, sib_right_id) + { + sib.links_mut().color = Color::Red; + right.links_mut().color = Color::Black; + } else { + return Err(()); + } + self.rotate_left(sibling_id, sib_right_id, storage)?; + sibling_opt = storage.get(parent_id).and_then(|n| n.links().left); + } + + // Sibling's left child node is red + let sibling_id = sibling_opt.ok_or(())?; + let parent_is_red = storage + .get(parent_id) + .map_or(false, |n| matches!(n.links().color, Color::Red)); + + if let Some(sib) = storage.get_mut(sibling_id) { + sib.links_mut().color = if parent_is_red { + Color::Red + } else { + Color::Black + }; + } + if let Some(par) = storage.get_mut(parent_id) { + par.links_mut().color = Color::Black; + } + + let sib_left = storage.get(sibling_id).and_then(|n| n.links().left); + if let Some(sib_left_id) = sib_left { + if let Some(left) = storage.get_mut(sib_left_id) { + left.links_mut().color = Color::Black; + } + } + + self.rotate_right(parent_id, sibling_id, storage)?; + id = self.root; + break; + } + } + } + + // Color the root node black + if let Some(id) = id { + if let Some(node) = storage.get_mut(id) { + node.links_mut().color = Color::Black; + } + } + + Ok(()) + } + + fn minimum>(&self, mut id: T, storage: &S) -> Result + where >::Output: Linkable + Compare, { + loop { + let left = storage.get(id).ok_or(())?.links().left; + match left { + Some(left_id) => id = left_id, + None => return Ok(id), + } + } + } + + fn transplant + GetMut>(&mut self, u: T, v: Option, storage: &mut S) -> Result<(), ()> + where >::Output: Linkable + Compare, { + let u_parent = storage.get(u).and_then(|n| n.links().parent); + + match u_parent { + None => self.root = v, + Some(parent_id) => { + if let Some(parent_node) = storage.get_mut(parent_id) { + if parent_node.links().left == Some(u) { + parent_node.links_mut().left = v; + } else { + parent_node.links_mut().right = v; + } + } else { + return Err(()); + } + } + } + + if let Some(v_id) = v { + if let Some(v_node) = storage.get_mut(v_id) { + v_node.links_mut().parent = u_parent; + } else { + return Err(()); + } + } + + Ok(()) + } + + fn rotate_right + GetMut>(&mut self, pivot: T, left: T, storage: &mut S) -> Result<(), ()> + where >::Output: Linkable + Compare, { + if pivot == left { + return Err(()); + } + + let (right, parent) = + if let (Some(pivot_node), Some(left_node)) = storage.get2_mut(pivot, left) { + // Add left child's right subtree as pivot's left subtree + pivot_node.links_mut().left = left_node.links().right; + + // Add pivot's parent as left child's parent + left_node.links_mut().parent = pivot_node.links().parent; + + let old_right = left_node.links().right; + + // Set pivot as the right child of left child + left_node.links_mut().right = Some(pivot); + + let old_parent = pivot_node.links().parent; + + // Set pivot's parent to left child + pivot_node.links_mut().parent = Some(left); + + (old_right, old_parent) + } else { + return Err(()); + }; + + if let Some(right_id) = right { + if let Some(right_node) = storage.get_mut(right_id) { + right_node.links_mut().parent = Some(pivot); + } + } + + match parent { + None => self.root = Some(left), + Some(parent_id) => { + if let Some(parent_node) = storage.get_mut(parent_id) { + if parent_node.links().left == Some(pivot) { + parent_node.links_mut().left = Some(left); + } else { + parent_node.links_mut().right = Some(left); + } + } else { + return Err(()); + } + } + } + + Ok(()) + } + + fn rotate_left + GetMut>(&mut self, pivot: T, right: T, storage: &mut S) -> Result<(), ()> + where >::Output: Linkable + Compare, { + if pivot == right { + return Err(()); + } + + let (left, parent) = + if let (Some(pivot_node), Some(right_node)) = storage.get2_mut(pivot, right) { + // Add right child's left subtree as pivot's right subtree + pivot_node.links_mut().right = right_node.links().left; + + // Add pivot's parent as right child's parent + right_node.links_mut().parent = pivot_node.links().parent; + + let old_left = right_node.links().left; + + // Set pivot as the left child of right child + right_node.links_mut().left = Some(pivot); + + let old_parent = pivot_node.links().parent; + + // Set pivot's parent to right child + pivot_node.links_mut().parent = Some(right); + + (old_left, old_parent) + } else { + return Err(()); + }; + + if let Some(left_id) = left { + if let Some(left_node) = storage.get_mut(left_id) { + left_node.links_mut().parent = Some(pivot); + } + } + + match parent { + None => self.root = Some(right), + Some(parent_id) => { + if let Some(parent_node) = storage.get_mut(parent_id) { + if parent_node.links().left == Some(pivot) { + parent_node.links_mut().left = Some(right); + } else { + parent_node.links_mut().right = Some(right); + } + } else { + return Err(()); + } + } + } + Ok(()) + } +} + +// TESTING ------------------------------------------------------------------------------------------------------------ + +#[cfg(test)] +mod tests { + use super::*; + use crate::mem::traits::{Get, GetMut}; + use std::borrow::Borrow; + use std::collections::HashSet; + + struct Tree; + + struct Node { + key: i32, + links: Links, + } + + impl Node { + fn new(key: i32) -> Self { + Self { + key, + links: Links::new(), + } + } + } + + impl Compare for Node { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.key.cmp(&other.key) + } + } + + impl Linkable for Node { + fn links(&self) -> &Links { + &self.links + } + + fn links_mut(&mut self) -> &mut Links { + &mut self.links + } + } + + struct NodeStore { + nodes: Vec, + } + + impl NodeStore { + fn new(keys: &[i32]) -> Self { + Self { + nodes: keys.iter().copied().map(Node::new).collect(), + } + } + } + + impl Get for NodeStore { + type Output = Node; + + fn get>(&self, index: K) -> Option<&Self::Output> { + self.nodes.get(*index.borrow()) + } + } + + impl GetMut for NodeStore { + fn get_mut>(&mut self, index: K) -> Option<&mut Self::Output> { + self.nodes.get_mut(*index.borrow()) + } + + fn get2_mut>( + &mut self, + index1: K, + index2: K, + ) -> (Option<&mut Self::Output>, Option<&mut Self::Output>) { + if *index1.borrow() == *index2.borrow() { + return (None, None); + } + + let ptr = self.nodes.as_ptr(); + + return unsafe { + ( + Some(&mut *(ptr.add(*index1.borrow()) as *mut Self::Output)), + Some(&mut *(ptr.add(*index2.borrow()) as *mut Self::Output)), + ) + }; + } + + fn get3_mut>( + &mut self, + index1: K, + index2: K, + index3: K, + ) -> ( + Option<&mut Self::Output>, + Option<&mut Self::Output>, + Option<&mut Self::Output>, + ) { + if *index1.borrow() == *index2.borrow() + || *index1.borrow() == *index3.borrow() + || *index2.borrow() == *index3.borrow() + { + return (None, None, None); + } + + let ptr = self.nodes.as_ptr(); + return unsafe { + ( + Some(&mut *(ptr.add(*index1.borrow()) as *mut Self::Output)), + Some(&mut *(ptr.add(*index2.borrow()) as *mut Self::Output)), + Some(&mut *(ptr.add(*index3.borrow()) as *mut Self::Output)), + ) + }; + } + } + + fn validate_tree(tree: &RbTree, store: &NodeStore, expected: &[i32]) { + let mut visited = HashSet::new(); + + if let Some(root_id) = tree.root { + let root = store.get(root_id).expect("root missing from store"); + assert!(matches!(root.links().color, Color::Black)); + assert_eq!(root.links().parent, None); + } + + let (count, _) = validate_node(tree.root, store, &mut visited, expected); + assert_eq!(count, expected.len()); + + if !expected.is_empty() { + let min = tree_min_key(tree, store).expect("non-empty tree must contain a min."); + assert_eq!(min, expected[0]); + } + } + + fn tree_min_key(tree: &RbTree, store: &NodeStore) -> Option { + tree.min().map(|id| store.get(id).expect("min missing").key) + } + + fn validate_node( + id: Option, + store: &NodeStore, + visited: &mut HashSet, + expected: &[i32], + ) -> (usize, usize) { + let Some(id) = id else { + return (0, 1); + }; + + assert!(visited.insert(id)); + + let node = store.get(id).expect("node missing from store"); + + let left = node.links().left; + let right = node.links().right; + + if matches!(node.links().color, Color::Red) { + if let Some(left_id) = left { + let left_node = store.get(left_id).expect("left missing"); + assert!(matches!(left_node.links().color, Color::Black)); + } + if let Some(right_id) = right { + let right_node = store.get(right_id).expect("right missing"); + assert!(matches!(right_node.links().color, Color::Black)); + } + } + + if let Some(left_id) = left { + let left_node = store.get(left_id).expect("left missing"); + assert_eq!(left_node.links().parent, Some(id)); + } + if let Some(right_id) = right { + let right_node = store.get(right_id).expect("right missing"); + assert_eq!(right_node.links().parent, Some(id)); + } + + let (left_count, left_bh) = validate_node(left, store, visited, &expected); + assert_eq!( + node.key, expected[left_count], + "expected key {}, found {}", + expected[left_count], node.key + ); + let (right_count, right_bh) = + validate_node(right, store, visited, &expected[1 + left_count..]); + + assert_eq!( + left_bh, right_bh, + "black height mismatch at node with key {}", + node.key + ); + + let self_bh = if matches!(node.links().color, Color::Black) { + left_bh + 1 + } else { + left_bh + }; + + (1 + left_count + right_count, self_bh) + } + + fn lcg(seed: &mut u64) -> u64 { + *seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1); + *seed + } + + fn shuffle(ids: &mut [usize]) { + let mut seed = 0x6b8b_4567_9a1c_def0u64; + for i in (1..ids.len()).rev() { + let j = (lcg(&mut seed) % (i as u64 + 1)) as usize; + ids.swap(i, j); + } + } + + #[test] + fn insert_validates() { + let keys: Vec = (0..200).collect(); + let mut store = NodeStore::new(&keys); + let mut tree = RbTree::new(); + let mut order: Vec = (0..keys.len()).collect(); + + shuffle(&mut order); + for id in order { + tree.insert(id, &mut store).unwrap(); + } + + validate_tree(&tree, &store, &keys); + } + + #[test] + fn min_updates_on_insert_and_remove() { + let keys = vec![10, 5, 15, 3, 7, 12, 18, 1, 6]; + let mut store = NodeStore::new(&keys); + let mut tree = RbTree::new(); + + for id in 0..keys.len() { + tree.insert(id, &mut store).unwrap(); + } + + let mut sorted_keys = keys.clone(); + sorted_keys.sort(); + + validate_tree(&tree, &store, &sorted_keys); + assert_eq!(tree_min_key(&tree, &store), Some(1)); + + // Remove index 7 (key=1) + tree.remove(7, &mut store).unwrap(); + sorted_keys.retain(|&x| x != 1); + validate_tree(&tree, &store, &sorted_keys); + assert_eq!(tree_min_key(&tree, &store), Some(3)); + + // Remove index 8 (key=6) + tree.remove(8, &mut store).unwrap(); + sorted_keys.retain(|&x| x != 6); + validate_tree(&tree, &store, &sorted_keys); + assert_eq!(tree_min_key(&tree, &store), Some(3)); + + // Remove index 3 (key=3) + tree.remove(3, &mut store).unwrap(); + sorted_keys.retain(|&x| x != 3); + validate_tree(&tree, &store, &sorted_keys); + assert_eq!(tree_min_key(&tree, &store), Some(5)); + } + + #[test] + fn remove_leaf_one_child_two_children() { + let keys = vec![10, 5, 15, 3, 7, 12, 18, 1, 6]; + let mut store = NodeStore::new(&keys); + let mut tree = RbTree::new(); + + for id in 0..keys.len() { + tree.insert(id, &mut store).unwrap(); + } + + let mut sorted_keys = keys.clone(); + sorted_keys.sort(); + validate_tree(&tree, &store, &sorted_keys); + + // Remove node at index 4 (key=7) + tree.remove(4, &mut store).unwrap(); + sorted_keys.retain(|&x| x != 7); + validate_tree(&tree, &store, &sorted_keys); + + // Remove node at index 3 (key=3) + tree.remove(3, &mut store).unwrap(); + sorted_keys.retain(|&x| x != 3); + validate_tree(&tree, &store, &sorted_keys); + + // Remove node at index 7 (key=1) + tree.remove(7, &mut store).unwrap(); + sorted_keys.retain(|&x| x != 1); + validate_tree(&tree, &store, &sorted_keys); + } + + #[test] + fn remove_root_with_two_children() { + let keys = [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]; + let mut store = NodeStore::new(&keys); + let mut tree = RbTree::new(); + + for id in 0..keys.len() { + tree.insert(id, &mut store).unwrap(); + } + + let mut sorted_keys: Vec = keys.to_vec(); + sorted_keys.sort(); + validate_tree(&tree, &store, &sorted_keys); + + let root_id = tree.root.expect("root missing"); + let root_key = store.get(root_id).expect("root missing").key; + + tree.remove(root_id, &mut store).unwrap(); + sorted_keys.retain(|&x| x != root_key); + validate_tree(&tree, &store, &sorted_keys); + } + + #[test] + fn remove_all_nodes() { + let keys: Vec = (0..128).collect(); + let mut store = NodeStore::new(&keys); + let mut tree = RbTree::new(); + let mut order: Vec = (0..keys.len()).collect(); + shuffle(&mut order); + + for id in &order { + tree.insert(*id, &mut store).unwrap(); + } + + let mut remaining_keys = keys.clone(); + validate_tree(&tree, &store, &remaining_keys); + + for id in order { + let removed_key = keys[id]; + tree.remove(id, &mut store).unwrap(); + remaining_keys.retain(|&k| k != removed_key); + validate_tree(&tree, &store, &remaining_keys); + } + + assert_eq!(tree.root, None); + } + + #[test] + fn interleaved_operations() { + let keys: Vec = (0..100).collect(); + let mut store = NodeStore::new(&keys); + let mut tree = RbTree::new(); + let mut order: Vec = (0..keys.len()).collect(); + shuffle(&mut order); + + // Build initial tree with 50 nodes + let mut active_keys: Vec = Vec::new(); + for id in order.iter().take(50) { + tree.insert(*id, &mut store).unwrap(); + active_keys.push(keys[*id]); + } + active_keys.sort(); + validate_tree(&tree, &store, &active_keys); + + // Alternate: remove oldest, insert new + for i in 0..50 { + let removed_key = keys[order[i]]; + tree.remove(order[i], &mut store).unwrap(); + active_keys.retain(|&k| k != removed_key); + validate_tree(&tree, &store, &active_keys); + + tree.insert(order[50 + i], &mut store).unwrap(); + active_keys.push(keys[order[50 + i]]); + active_keys.sort(); + validate_tree(&tree, &store, &active_keys); + } + } + + #[test] + fn stress_test() { + let keys: Vec = (0..500).collect(); + let mut store = NodeStore::new(&keys); + let mut tree = RbTree::new(); + let mut order: Vec = (0..keys.len()).collect(); + shuffle(&mut order); + + let mut seed = 0x6b8b_4567_9a1c_def0u64; + let mut active_nodes = Vec::new(); + let mut available_nodes = order.clone(); + + for _ in 0..10000 { + let do_insert = if active_nodes.is_empty() { + true + } else if available_nodes.is_empty() { + false + } else { + (lcg(&mut seed) % 10) < 7 + }; + + if do_insert { + let idx = (lcg(&mut seed) as usize) % available_nodes.len(); + let node_id = available_nodes.swap_remove(idx); + tree.insert(node_id, &mut store).unwrap(); + active_nodes.push(node_id); + } else { + let idx = (lcg(&mut seed) as usize) % active_nodes.len(); + let node_id = active_nodes.swap_remove(idx); + tree.remove(node_id, &mut store).unwrap(); + available_nodes.push(node_id); + } + + let mut expected_keys: Vec = active_nodes.iter().map(|&id| keys[id]).collect(); + expected_keys.sort(); + validate_tree(&tree, &store, &expected_keys); + } + + let mut expected_keys: Vec = active_nodes.iter().map(|&id| keys[id]).collect(); + expected_keys.sort(); + validate_tree(&tree, &store, &expected_keys); + } +} + +// END TESTING diff --git a/src/mem/traits.rs b/src/mem/traits.rs new file mode 100644 index 0000000..cc02d85 --- /dev/null +++ b/src/mem/traits.rs @@ -0,0 +1,24 @@ +use core::borrow::Borrow; + +pub trait Get { + type Output: ?Sized; + + fn get>(&self, index: K) -> Option<&Self::Output>; +} + +pub trait GetMut: Get { + fn get_mut>(&mut self, index: K) -> Option<&mut Self::Output>; + + // Getting multiple disjoint mutable references at once + fn get2_mut>(&mut self, index1: K, index2: K) -> (Option<&mut Self::Output>, Option<&mut Self::Output>); + fn get3_mut>(&mut self, index1: K, index2: K, index3: K) -> (Option<&mut Self::Output>, Option<&mut Self::Output>, Option<&mut Self::Output>); +} + +pub trait ToIndex { + fn to_index>(index: Option) -> usize; +} + +pub trait Project

{ + fn project(&self) -> Option<&P>; + fn project_mut(&mut self) -> Option<&mut P>; +} \ No newline at end of file diff --git a/src/mem/view.rs b/src/mem/view.rs new file mode 100644 index 0000000..c8bd4bc --- /dev/null +++ b/src/mem/view.rs @@ -0,0 +1,63 @@ +use core::borrow::Borrow; +use std::marker::PhantomData; + +use crate::mem::traits::{Get, GetMut, Project, ToIndex}; + +pub struct ViewMut<'a, K: ?Sized + ToIndex, P, S: GetMut> +where + S::Output: Project

, +{ + data: &'a mut S, + _k: PhantomData, + _proj: PhantomData

, +} + +impl<'a, K: ?Sized + ToIndex, P, S: GetMut> ViewMut<'a, K, P, S> +where + S::Output: Project

, +{ + pub fn new(data: &'a mut S) -> Self { + Self { + data, + _k: PhantomData, + _proj: PhantomData, + } + } +} + +impl<'a, K: ?Sized + ToIndex, P, S: GetMut> Get for ViewMut<'a, K, P, S> +where + S::Output: Project

, +{ + type Output = P; + + fn get>(&self, idx: Q) -> Option<&P> { + self.data.get(idx).and_then(Project::project) + } +} + +impl<'a, K: ?Sized + ToIndex, P, S: GetMut> GetMut for ViewMut<'a, K, P, S> +where + S::Output: Project

, +{ + fn get_mut>(&mut self, idx: Q) -> Option<&mut P> { + self.data.get_mut(idx).and_then(Project::project_mut) + } + + fn get2_mut>(&mut self, idx1: Q, idx2: Q) -> (Option<&mut P>, Option<&mut P>) { + let (a, b) = self.data.get2_mut(idx1, idx2); + ( + a.and_then(Project::project_mut), + b.and_then(Project::project_mut), + ) + } + + fn get3_mut>(&mut self, idx1: Q, idx2: Q, idx3: Q) -> (Option<&mut P>, Option<&mut P>, Option<&mut P>) { + let (a, b, c) = self.data.get3_mut(idx1, idx2, idx3); + ( + a.and_then(Project::project_mut), + b.and_then(Project::project_mut), + c.and_then(Project::project_mut), + ) + } +} \ No newline at end of file diff --git a/src/sched.rs b/src/sched.rs index 70ae413..72ce054 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -1,18 +1,83 @@ //! This module provides access to the scheduler. -pub mod scheduler; +pub mod rt; +//pub mod scheduler; pub mod task; pub mod thread; use hal::Schedable; -use crate::utils::KernelError; +use crate::mem::{array::IndexMap, rbtree::RbTree, view::ViewMut}; + +type ThreadMap = IndexMap; + +pub struct Scheduler { + threads: ThreadMap, + rt_scheduler: rt::Scheduler, + + wakeup: RbTree, + + last_tick: u64, +} + +impl Scheduler { + pub fn new() -> Self { + Self { + threads: IndexMap::new(), + rt_scheduler: rt::Scheduler::new(), + wakeup: RbTree::new(), + last_tick: 0, + } + } + + pub fn enqueue(&mut self, thread: thread::Thread) { + let uid = thread.uid(); + let rt = thread.rt_server().is_some(); + self.threads.insert(&thread.uid(), thread); + + if rt { + let mut view = ViewMut::>::new( + &mut self.threads, + ); + self.rt_scheduler.enqueue(uid, &mut view); + } + } + + pub fn do_sched(&mut self, now: u64, old: Option) -> Option { + let dt = now - self.last_tick; + self.last_tick = now; + + if let Some(old) = old { + let mut view = rt::ServerView::::new(&mut self.threads); + // If this is not a real-time thread, this will just do nothing. + self.rt_scheduler.put(old, dt, &mut view); + + // TODO: thread is still enqueued. Dequeue if blocked or sleeping and put to the respective tree/list. + // If it exited remove it completely. + } + + let mut view = rt::ServerView::::new(&mut self.threads); + self.rt_scheduler.pick(now, &mut view) + } + + pub fn dequeue(&mut self, uid: thread::UId) -> Option { + let mut view = rt::ServerView::::new(&mut self.threads); + // If this is not a real-time thread, this will just do nothing. + self.rt_scheduler.dequeue(uid, &mut view); + + self.threads.remove(&uid) + } +} /// Reschedule the tasks. pub fn reschedule() { hal::Machine::trigger_reschedule(); } +/* + + + /// Create a new task. /// /// `desc` - The task descriptor. @@ -54,3 +119,5 @@ pub fn tick_scheduler() -> bool { scheduler::SCHEDULER.lock().tick() } + + */ diff --git a/src/sched/rt.rs b/src/sched/rt.rs new file mode 100644 index 0000000..c6c6b00 --- /dev/null +++ b/src/sched/rt.rs @@ -0,0 +1,42 @@ +use crate::{mem::{rbtree::RbTree, traits::{Get, GetMut}, view::ViewMut}, sched::{ThreadMap, thread::{self}}}; + +pub struct Scheduler { + edf: RbTree, +} + +pub type ServerView<'a, const N: usize> = ViewMut<'a, thread::UId, thread::RtServer, ThreadMap>; + +impl Scheduler { + pub fn new() -> Self { + Self { + edf: RbTree::new(), + } + } + + pub fn enqueue(&mut self, uid: thread::UId, storage: &mut ServerView) { + self.edf.insert(uid, storage); + } + + pub fn put(&mut self, uid: thread::UId, dt: u64, storage: &mut ServerView) { + if let Some(server) = storage.get_mut(uid) { + server.consume(dt); + } + } + + pub fn pick(&mut self, now: u64, storage: &mut ServerView) -> Option { + let id = self.edf.min()?; + + if storage.get(id)?.budget() == 0 { + self.edf.remove(id, storage); + storage.get_mut(id)?.replenish(now); + self.edf.insert(id, storage); + } + + // Insert updated the min cache. + self.edf.min() + } + + pub fn dequeue(&mut self, uid: thread::UId, storage: &mut ServerView) { + self.edf.remove(uid, storage); + } +} \ No newline at end of file diff --git a/src/sched/task.rs b/src/sched/task.rs index 7f6deb8..9f5388b 100644 --- a/src/sched/task.rs +++ b/src/sched/task.rs @@ -53,6 +53,8 @@ impl From for usize { } } + + /// Descibes a task. pub struct TaskDescriptor { /// The size of the memory that the task requires. @@ -68,8 +70,6 @@ pub struct Task { memory: TaskMemory, /// The counter for the thread ids. tid_cntr: usize, - /// The threads associated with the task. - threads: mem::array::Vec, } impl Task { @@ -80,13 +80,11 @@ impl Task { /// Returns a new task if the task was created successfully, or an error if the task could not be created. pub fn new(memory_size: usize, id: TaskId) -> Result { let memory = TaskMemory::new(memory_size)?; - let threads = mem::array::Vec::new(); Ok(Self { id, memory, tid_cntr: 0, - threads, }) } @@ -187,3 +185,5 @@ impl Drop for TaskMemory { unsafe { mem::free(self.begin, self.size) }; } } + + diff --git a/src/sched/thread.rs b/src/sched/thread.rs index 647d5b9..7a814b4 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -4,18 +4,19 @@ use core::{borrow::Borrow, ffi::c_void}; use hal::Stack; use hal::stack::Stacklike; +use macros::TaggedLinks; -use crate::{mem::array::IndexMap, sched::task::TaskId, utils::KernelError}; +use crate::{mem::{rbtree::{self, Compare}, traits::{Project, ToIndex}}, sched::task::TaskId, utils::KernelError}; /// Id of a task. This is only unique within a Task. #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] -pub struct ThreadId { +pub struct Id { id: usize, owner: TaskId, } #[allow(dead_code)] -impl ThreadId { +impl Id { pub fn new(id: usize, owner: TaskId) -> Self { Self { id, owner } } @@ -28,78 +29,72 @@ impl ThreadId { self.owner } - pub fn get_uid(&self, uid: usize) -> ThreadUId { - ThreadUId { uid, tid: *self } + pub fn get_uid(&self, uid: usize) -> UId { + UId { uid, tid: *self } } } /// Unique identifier for a thread. Build from TaskId and ThreadId. #[derive(Clone, Copy, Debug)] #[allow(dead_code)] -pub struct ThreadUId { +pub struct UId { uid: usize, - tid: ThreadId, + tid: Id, } #[allow(dead_code)] -impl ThreadUId { - pub fn tid(&self) -> ThreadId { +impl UId { + pub fn tid(&self) -> Id { self.tid } } -impl PartialEq for ThreadUId { +impl PartialEq for UId { fn eq(&self, other: &Self) -> bool { self.uid == other.uid } } -impl Eq for ThreadUId {} +impl Eq for UId {} -impl Borrow for ThreadUId { - fn borrow(&self) -> &usize { - &self.uid +impl Into for UId { + fn into(self) -> usize { + self.uid } } -impl Default for ThreadUId { +impl Default for UId { fn default() -> Self { Self { uid: 0, - tid: ThreadId::new(0, TaskId::User(0)), + tid: Id::new(0, TaskId::User(0)), } } } -impl PartialOrd for ThreadUId { +impl PartialOrd for UId { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for ThreadUId { +impl Ord for UId { fn cmp(&self, other: &Self) -> core::cmp::Ordering { self.uid.cmp(&other.uid) } } +impl ToIndex for UId { + fn to_index>(idx: Option) -> usize { + idx.as_ref().map_or(0, |k| k.borrow().uid) + } +} + // ------------------------------------------------------------------------- -pub struct ThreadDescriptor { - pub tid: ThreadId, +pub struct Descriptor { + pub tid: Id, pub stack: Stack, - pub timing: Timing, -} - -/// The timing information for a thread. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Timing { - /// The period of the thread after which it should run again. - pub period: usize, - /// The deadline of the thread. - pub deadline: usize, - /// The execution time of the thread. (How much cpu time it needs) - pub exec_time: usize, } /// The state of a thread. @@ -114,22 +109,98 @@ pub enum RunState { Waits, } -#[derive(Debug)] -pub struct ThreadState { +#[derive(Debug, Clone, Copy)] +pub struct State { run_state: RunState, stack: Stack, } +#[derive(Debug, Clone, Copy)] +#[derive(TaggedLinks)] +pub struct RtServer { + budget: u64, + total_budget: u64, + + reservation: u64, + deadline: u64, + + // Back-reference to the thread uid. + uid: UId, + + /// Real-time tree links for the server. + #[rbtree(tag = RtTree, idx = UId)] + _rt_links: rbtree::Links, +} + +impl RtServer { + pub fn new(budget: u64, reservation: u64, deadline: u64, uid: UId) -> Self { + Self { + budget, + total_budget: budget, + reservation, + deadline, + uid, + _rt_links: rbtree::Links::new(), + } + } + + pub fn budget(&self) -> u64 { + self.budget + } + + pub fn replenish(&mut self, now: u64) { + let next = self.deadline + self.reservation; + self.deadline = next.max(now + self.reservation); + self.budget = self.total_budget; + } + + pub fn consume(&mut self, dt: u64) { + if self.budget >= dt { + self.budget -= dt; + } else { + self.budget = 0; + } + } + + pub fn deadline(&self) -> u64 { + self.deadline + } + + pub fn uid(&self) -> UId { + self.uid + } +} + +impl Compare for RtServer { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + let ord = self.deadline.cmp(&other.deadline); + + if ord == core::cmp::Ordering::Equal { + self.uid.cmp(&other.uid) + } else { + ord + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct WakupTree; +#[derive(Debug, Clone, Copy)] +pub struct RtTree; + /// The struct representing a thread. -#[derive(Debug)] -#[allow(dead_code)] +#[derive(Debug, Clone, Copy)] +#[derive(TaggedLinks)] pub struct Thread { /// The current state of the thread. - state: ThreadState, - /// The timing constraints of the thread. - timing: Timing, + state: State, /// The unique identifier of the thread. - tuid: ThreadUId, + uid: UId, + /// If the thread is real-time, its contains a constant bandwidth server. + rt_server: Option, + /// Wakup tree links for the thread. + #[rbtree(tag = WakupTree, idx = UId)] + _wakeup_links: rbtree::Links, } #[allow(dead_code)] @@ -137,74 +208,55 @@ impl Thread { /// Create a new thread. /// /// `stack` - The stack of the thread. - /// `timing` - The timing constraints of the thread. /// /// Returns a new thread. - fn new(tuid: ThreadUId, stack: Stack, timing: Timing) -> Self { + fn new(uid: UId, stack: Stack) -> Self { Self { - state: ThreadState { + state: State { run_state: RunState::Ready, stack, }, - timing, - tuid, + uid, + rt_server: None, + _wakeup_links: rbtree::Links::new(), } } - pub fn update_sp(&mut self, sp: *mut c_void) -> Result<(), KernelError> { - let sp = self.state.stack.create_sp(sp)?; + pub fn save_ctx(&mut self, ctx: *mut c_void) -> Result<(), KernelError> { + let sp = self.state.stack.create_sp(ctx)?; self.state.stack.set_sp(sp); Ok(()) } - pub fn update_run_state(&mut self, state: RunState) { + pub fn set_run_state(&mut self, state: RunState) { self.state.run_state = state; } - pub fn timing(&self) -> &Timing { - &self.timing + pub fn rt_server(&self) -> Option<&RtServer> { + self.rt_server.as_ref() } - pub fn sp(&self) -> *mut c_void { + pub fn ctx(&self) -> *mut c_void { self.state.stack.sp() } - pub fn tuid(&self) -> ThreadUId { - self.tuid + pub fn uid(&self) -> UId { + self.uid } } -#[derive(Debug)] -pub struct ThreadMap { - map: IndexMap, -} - -#[allow(dead_code)] -impl ThreadMap { - pub const fn new() -> Self { - Self { - map: IndexMap::new(), - } - } - - pub fn create(&mut self, desc: ThreadDescriptor) -> Result { - let idx = self.map.find_empty().ok_or(KernelError::OutOfMemory)?; - let tuid = desc.tid.get_uid(idx); - let thread = Thread::new(tuid, desc.stack, desc.timing); - - self.map.insert(&tuid, thread)?; - Ok(tuid) - } - - pub fn get_mut(&mut self, id: &ThreadUId) -> Option<&mut Thread> { - self.map.get_mut(id) +impl Compare for Thread { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.uid.cmp(&other.uid) } +} - pub fn get(&self, id: &ThreadUId) -> Option<&Thread> { - self.map.get(id) +impl Project for Thread { + fn project(&self) -> Option<&RtServer> { + self.rt_server.as_ref() } - pub fn remove(&mut self, id: &ThreadUId) -> Option { - self.map.remove(id) + fn project_mut(&mut self) -> Option<&mut RtServer> { + self.rt_server.as_mut() } -} +} \ No newline at end of file diff --git a/src/syscalls/tasks.rs b/src/syscalls/tasks.rs index 7bd7b5a..2a6d68d 100644 --- a/src/syscalls/tasks.rs +++ b/src/syscalls/tasks.rs @@ -2,6 +2,7 @@ use core::ffi::c_int; +/* use crate::sched; use macros::syscall_handler; @@ -29,4 +30,4 @@ fn syscall_exec(entry: usize) -> c_int { .and_then(|task| sched::create_thread(task, entry, None, timing)) .map(|_| 0) .unwrap_or(-1) -} +}*/ diff --git a/src/utils.rs b/src/utils.rs index 8d4144f..9fa7644 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,6 +2,8 @@ #![cfg_attr(feature = "nightly", feature(likely_unlikely))] use core::fmt::Debug; +use core::ptr::NonNull; +use core::mem::offset_of; /// These two definitions are copied from https://github.com/rust-lang/hashbrown #[cfg(not(feature = "nightly"))] @@ -11,6 +13,8 @@ pub(crate) use core::convert::{identity as likely, identity as unlikely}; #[cfg(feature = "nightly")] pub(crate) use core::hint::{likely, unlikely}; + + /// This is a macro that is used to panic when a bug is detected. /// It is similar to the BUG() macro in the Linux kernel. Link: [https://www.kernel.org/]() #[macro_export] From bae03f3f66b8fb5521604e9be30902839209078a Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Sat, 7 Mar 2026 15:23:32 +0000 Subject: [PATCH 17/42] remove me --- .cargo/config.toml | 4 +- Cargo.lock | 1 + Cargo.toml | 2 + config.toml | 10 ++ machine/api/src/lib.rs | 2 +- machine/api/src/mem.rs | 48 +++++++ machine/api/src/{ => mem}/stack.rs | 7 +- machine/arm/src/sched.rs | 3 + macros/src/tree.rs | 6 +- src/dispatch.rs | 14 ++ src/{sched => dispatch}/task.rs | 101 +++++-------- src/lib.rs | 4 +- src/mem.rs | 14 +- src/mem/pfa.rs | 57 ++++++++ src/mem/pfa/bitset.rs | 219 +++++++++++++++++++++++++++++ src/mem/vmm.rs | 66 +++++++++ src/mem/vmm/nommu.rs | 87 ++++++++++++ src/sched.rs | 6 +- src/sched/rt.rs | 2 +- src/sched/thread.rs | 2 +- src/types.rs | 8 ++ src/{mem => types}/array.rs | 27 ++-- src/{mem => types}/boxed.rs | 46 +----- src/{mem => types}/heap.rs | 0 src/{mem => types}/pool.rs | 0 src/{mem => types}/queue.rs | 0 src/{mem => types}/rbtree.rs | 6 +- src/{mem => types}/traits.rs | 0 src/{mem => types}/view.rs | 4 +- src/utils.rs | 2 + 30 files changed, 602 insertions(+), 146 deletions(-) create mode 100644 config.toml create mode 100644 machine/api/src/mem.rs rename machine/api/src/{ => mem}/stack.rs (94%) create mode 100644 src/dispatch.rs rename src/{sched => dispatch}/task.rs (64%) create mode 100644 src/mem/pfa.rs create mode 100644 src/mem/pfa/bitset.rs create mode 100644 src/mem/vmm.rs create mode 100644 src/mem/vmm/nommu.rs create mode 100644 src/types.rs rename src/{mem => types}/array.rs (97%) rename src/{mem => types}/boxed.rs (85%) rename src/{mem => types}/heap.rs (100%) rename src/{mem => types}/pool.rs (100%) rename src/{mem => types}/queue.rs (100%) rename src/{mem => types}/rbtree.rs (99%) rename src/{mem => types}/traits.rs (100%) rename src/{mem => types}/view.rs (94%) diff --git a/.cargo/config.toml b/.cargo/config.toml index b73ed2c..0a1e750 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -7,10 +7,10 @@ xtask = "--config xtasks/.cargo/config.toml run -p xtask --release --" [build] target = "host-tuple" -[target] - [target.'cfg(target_os = "none")'] rustflags = ["-C", "link-arg=--entry=main",] +[target] + [target.thumbv7em-none-eabi] rustflags = ["-C", "relocation-model=ropi-rwpi"] diff --git a/Cargo.lock b/Cargo.lock index 9f3703b..d1469e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1090,6 +1090,7 @@ name = "osiris" version = "0.1.0" dependencies = [ "bindgen 0.69.5", + "bitflags", "cbindgen", "cfg_aliases", "dtgen", diff --git a/Cargo.toml b/Cargo.toml index 38c8641..b6d40be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,10 +22,12 @@ hal = { package = "hal-select", path = "machine/select" } macros = { path = "macros" } interface = { path = "interface" } envparse = "0.1.0" +bitflags = "2.10.0" [dev-dependencies] # This is a host-compatible HAL which will be used for running tests and verification on the host. hal-testing = { path = "machine/testing", features = [] } +rand = "0.8.5" [target.'cfg(kani_ra)'.dependencies] kani = { git = "https://github.com/model-checking/kani" } diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..519b669 --- /dev/null +++ b/config.toml @@ -0,0 +1,10 @@ +[env] +OSIRIS_ARM_HAL = "stm32l4xx" +OSIRIS_ARM_STM32L4XX_VARIANT = "r5zi" +OSIRIS_DEBUG_UART = "UART5" +OSIRIS_DEBUG_RUNTIMESYMBOLS = "false" +OSIRIS_TUNING_ENABLEFPU = "false" +OSIRIS_STACKPAGES = "1" + +[build] +target = "thumbv7em-none-eabi" diff --git a/machine/api/src/lib.rs b/machine/api/src/lib.rs index a6330a6..36860bc 100644 --- a/machine/api/src/lib.rs +++ b/machine/api/src/lib.rs @@ -2,7 +2,7 @@ use core::{fmt::Display, ops::Range}; -pub mod stack; +pub mod mem; #[derive(Default, Debug, PartialEq, Eq, Clone)] pub enum Error { diff --git a/machine/api/src/mem.rs b/machine/api/src/mem.rs new file mode 100644 index 0000000..10c8b07 --- /dev/null +++ b/machine/api/src/mem.rs @@ -0,0 +1,48 @@ + +pub mod stack; + +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] +pub struct PhysAddr(usize); + +impl PhysAddr { + #[inline] + pub fn new(addr: usize) -> Self { + Self(addr) + } + + #[inline] + pub fn as_usize(&self) -> usize { + self.0 + } +} + +impl From for usize { + #[inline] + fn from(addr: PhysAddr) -> Self { + addr.0 + } +} + +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] +pub struct VirtAddr(usize); + +impl VirtAddr { + #[inline] + pub fn new(addr: usize) -> Self { + Self(addr) + } + + #[inline] + pub fn as_usize(&self) -> usize { + self.0 + } +} + +impl From for usize { + #[inline] + fn from(addr: VirtAddr) -> Self { + addr.0 + } +} \ No newline at end of file diff --git a/machine/api/src/stack.rs b/machine/api/src/mem/stack.rs similarity index 94% rename from machine/api/src/stack.rs rename to machine/api/src/mem/stack.rs index db226a0..4291378 100644 --- a/machine/api/src/stack.rs +++ b/machine/api/src/mem/stack.rs @@ -1,9 +1,8 @@ -use core::{ffi::c_void, num::NonZero, ptr::NonNull}; - -use crate::Result; +use core::{ffi::c_void, num::NonZero}; +use crate::{Result, mem::PhysAddr}; pub struct StackDescriptor { - pub top: NonNull, + pub top: PhysAddr, pub size: NonZero, pub entry: extern "C" fn(), pub fin: Option !>, diff --git a/machine/arm/src/sched.rs b/machine/arm/src/sched.rs index 3026071..19578fa 100644 --- a/machine/arm/src/sched.rs +++ b/machine/arm/src/sched.rs @@ -202,6 +202,9 @@ impl hal_api::stack::Stacklike for ArmStack { fin, } = desc; + // We expect a PhysAddr, which can be converted to a ptr on nommu. + let top = NonNull::new(top as *mut u32).ok_or(hal_api::Error::InvalidAddress)?; + let mut stack = Self { top, sp: StackPtr { offset: 0 }, diff --git a/macros/src/tree.rs b/macros/src/tree.rs index f6c3b39..d62d8f6 100644 --- a/macros/src/tree.rs +++ b/macros/src/tree.rs @@ -42,13 +42,13 @@ fn impl_rbtree(input: &DeriveInput, fields: &syn::punctuated::Punctuated for #struct_ident #ty_generics #where_clause { + impl #impl_generics crate::types::rbtree::Linkable<#tag_path, #idx_path> for #struct_ident #ty_generics #where_clause { #[inline] - fn links(&self) -> &crate::mem::rbtree::Links<#tag_path, #idx_path> { + fn links(&self) -> &crate::types::rbtree::Links<#tag_path, #idx_path> { &self.#field_ident } #[inline] - fn links_mut(&mut self) -> &mut crate::mem::rbtree::Links<#tag_path, #idx_path> { + fn links_mut(&mut self) -> &mut crate::types::rbtree::Links<#tag_path, #idx_path> { &mut self.#field_ident } } diff --git a/src/dispatch.rs b/src/dispatch.rs new file mode 100644 index 0000000..a985fef --- /dev/null +++ b/src/dispatch.rs @@ -0,0 +1,14 @@ +//! This is the owner of all Tasks. It takes care of context switching between them. +//! The idea is that the Schedulers selects one of its threads to run, and then the Dipatcher takes care of context-switching to the associated Task. (e.g. setting up the address space) +//! If the thread is part of the same task as the currently running one, the Dispatcher does effectively nothing. +//! +//! + +mod task; + +use crate::types::array::IndexMap; + +/* +struct Dispatcher { + tasks: IndexMap, +}*/ \ No newline at end of file diff --git a/src/sched/task.rs b/src/dispatch/task.rs similarity index 64% rename from src/sched/task.rs rename to src/dispatch/task.rs index 9f5388b..b3f3d2c 100644 --- a/src/sched/task.rs +++ b/src/dispatch/task.rs @@ -2,74 +2,43 @@ use core::num::NonZero; use core::ops::Range; use core::ptr::NonNull; +use std::borrow::Borrow; -use hal::Stack; +use hal::{Stack, stack}; use hal::stack::Stacklike; -use crate::mem; +use crate::{mem, sched}; use crate::mem::alloc::{Allocator, BestFitAllocator}; -use crate::sched::thread::{ThreadDescriptor, ThreadId, Timing}; +use crate::mem::vmm::{AddressSpace, AddressSpacelike, Region}; +use crate::types::traits::ToIndex; use crate::utils::KernelError; /// Id of a task. This is unique across all tasks. -#[repr(u16)] #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] -pub enum TaskId { - // Task with normal user privileges in user mode. - User(usize), - // Task with kernel privileges in user mode. - Kernel(usize), +pub struct UId { + uid: usize, } -#[allow(dead_code)] -impl TaskId { - /// Check if the task is a user task. - pub fn is_user(&self) -> bool { - matches!(self, TaskId::User(_)) - } - - /// Check if the task is a kernel task. - pub fn is_kernel(&self) -> bool { - matches!(self, TaskId::Kernel(_)) - } - - pub fn new_user(id: usize) -> Self { - TaskId::User(id) - } - - pub fn new_kernel(id: usize) -> Self { - TaskId::Kernel(id) +impl ToIndex for UId { + fn to_index>(idx: Option) -> usize { + idx.as_ref().map_or(0, |uid| uid.borrow().uid) } } -impl From for usize { - fn from(val: TaskId) -> Self { - match val { - TaskId::User(id) => id, - TaskId::Kernel(id) => id, - } - } -} - - - -/// Descibes a task. -pub struct TaskDescriptor { - /// The size of the memory that the task requires. - pub mem_size: usize, +pub struct Attributes { + reserved: Option>, } /// The struct representing a task. -#[derive(Debug)] pub struct Task { /// The unique identifier of the task. - pub id: TaskId, - /// The memory of the task. - memory: TaskMemory, + pub id: UId, /// The counter for the thread ids. tid_cntr: usize, + /// Sets up the memory for the task. + address_space: mem::vmm::AddressSpace, } impl Task { @@ -78,45 +47,51 @@ impl Task { /// `memory_size` - The size of the memory that the task requires. /// /// Returns a new task if the task was created successfully, or an error if the task could not be created. - pub fn new(memory_size: usize, id: TaskId) -> Result { - let memory = TaskMemory::new(memory_size)?; - + pub fn new(id: UId, attrs: &Attributes) -> Result { Ok(Self { id, - memory, + address_space: AddressSpace::new(), tid_cntr: 0, }) } - fn allocate_tid(&mut self) -> ThreadId { + fn allocate_tid(&mut self) -> sched::thread::Id { let tid = self.tid_cntr; self.tid_cntr += 1; - ThreadId::new(tid, self.id) + sched::thread::Id::new(tid, self.id) + } + + pub fn allocate(&mut self, size: usize, align: usize) -> Result { + self.address_space.map(size, align) } pub fn create_thread( &mut self, entry: extern "C" fn(), fin: Option !>, - timing: Timing, ) -> Result { - // Safe unwrap because stack size is non zero. - // TODO: Make this configurable - let stack_size = NonZero::new(4096usize).unwrap(); - // TODO: Revert if error occurs - let stack_mem = self.memory.malloc(stack_size.into(), align_of::())?; - let stack_top = unsafe { stack_mem.byte_add(stack_size.get()) }; + + // Create the stack for the thread. + let size = 1 * mem::pfa::PAGE_SIZE; // TODO: Make this configurable + let start = self.address_space.end() - size; + let region = mem::vmm::Region::new( + start, + size, + mem::vmm::Backing::Uninit, + mem::vmm::Perms::Read | mem::vmm::Perms::Write, + ); + let stack_pa = self.address_space.map(region)?; let stack = hal::stack::StackDescriptor { - top: stack_top, - size: stack_size, + top: stack_pa, + // Safe unwrap because stack size is non zero. + size: NonZero::new(size).unwrap(), entry, fin, }; let stack = unsafe { Stack::new(stack) }?; - let tid = self.allocate_tid(); // TODO: Revert if error occurs @@ -185,5 +160,3 @@ impl Drop for TaskMemory { unsafe { mem::free(self.begin, self.size) }; } } - - diff --git a/src/lib.rs b/src/lib.rs index 287fd3d..2c023a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,10 @@ mod macros; mod utils; mod faults; mod mem; +mod types; pub mod print; -pub mod sched; +//pub mod sched; +mod dispatch; pub mod sync; pub mod syscalls; //pub mod time; diff --git a/src/mem.rs b/src/mem.rs index 6865d6b..f6c98b1 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -6,14 +6,8 @@ use alloc::Allocator; use core::ptr::NonNull; pub mod alloc; -pub mod array; -pub mod boxed; -pub mod heap; -pub mod pool; -pub mod queue; -pub mod rbtree; -pub mod traits; -pub mod view; +pub mod vmm; +pub mod pfa; /// The possible types of memory. Which is compatible with the multiboot2 memory map. /// Link: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html @@ -41,7 +35,9 @@ static GLOBAL_ALLOCATOR: SpinLocked = /// `regions` - The memory node module of device tree codegen file. /// /// Returns an error if the memory allocator could not be initialized. -pub fn init_memory(regions: &[(&str, usize, usize)]) -> Result<(), utils::KernelError> { +pub fn init_memory(boot_info: &BootInfo) -> Result<(), utils::KernelError> { + pfa::init_pfa(0x20000000)?; // TODO: Get this from the DeviceTree. + let mut allocator = GLOBAL_ALLOCATOR.lock(); for &(_, base, size) in regions { diff --git a/src/mem/pfa.rs b/src/mem/pfa.rs new file mode 100644 index 0000000..9c24df8 --- /dev/null +++ b/src/mem/pfa.rs @@ -0,0 +1,57 @@ +// The top level page frame allocator. + +use crate::sync::spinlock::SpinLocked; +use crate::types::boxed::Box; +use crate::utils::KernelError; + +use interface::PhysAddr; +use core::pin::Pin; + +mod bitset; + +/// Page size constant (typically 4KB) +pub const PAGE_SIZE: usize = 4096; + +const PAGE_CNT: usize = 1024; // TODO: This should be determined by the DeviceTree. + +type AllocatorType = bitset::Allocator; + +static PFA: SpinLocked>>> = SpinLocked::new(None); + +/// This trait abstracts over different page frame allocator implementations. +trait Allocator { + /// Returns an initializer function that can be used to create an instance of the allocator. + /// The initializer function takes a physical address and the amount of pages needed. + /// + /// Safety: + /// + /// - The returned function must only be called with a useable and valid physical address. + fn initializer() -> unsafe fn(PhysAddr, usize) -> Result>, KernelError>; + + fn alloc(&mut self, page_count: usize) -> Option; + fn free(&mut self, addr: PhysAddr, page_count: usize); +} + +pub fn init_pfa(addr: PhysAddr) -> Result<(), KernelError> { + let mut pfa = PFA.lock(); + if pfa.is_some() { + return Err(KernelError::CustomError("Page frame allocator is already initialized")); + } + + let initializer = AllocatorType::initializer(); + *pfa = Some(unsafe { initializer(addr, PAGE_CNT)? }); + + Ok(()) +} + +pub fn alloc_page(page_count: usize) -> Option { + let mut pfa = PFA.lock(); + pfa.as_mut()?.alloc(page_count) +} + +pub fn free_page(addr: PhysAddr, page_count: usize) { + let mut pfa = PFA.lock(); + if let Some(pfa) = pfa.as_mut() { + pfa.free(addr, page_count); + } +} \ No newline at end of file diff --git a/src/mem/pfa/bitset.rs b/src/mem/pfa/bitset.rs new file mode 100644 index 0000000..d9ab9e3 --- /dev/null +++ b/src/mem/pfa/bitset.rs @@ -0,0 +1,219 @@ +use core::pin::Pin; +use core::ptr::NonNull; + +use crate::{ + types::boxed::{self, Box}, + utils::KernelError, +}; + +use interface::PhysAddr; + +pub struct Allocator { + begin: PhysAddr, + l1: [usize; N], +} + +impl Allocator { + const BITS_PER_WORD: usize = usize::BITS as usize; + + pub fn new(begin: PhysAddr) -> Option { + if !begin.is_multiple_of(super::PAGE_SIZE) { + return None; + } + + if begin > PhysAddr::MAX - (N * super::PAGE_SIZE * usize::BITS as usize) { + return None; + } + + Some(Self { + begin, + l1: [!0; N], // All bits are set to 1, meaning all pages are free. + }) + } +} + +impl super::Allocator for Allocator { + fn initializer() -> unsafe fn(PhysAddr, usize) -> Result>, KernelError> { + |addr: PhysAddr, pcnt: usize| -> Result>, KernelError> { + if pcnt > N { + todo!("Runtime page frame allocator for more than {} pages", N) + } + + if !addr.is_multiple_of(core::mem::align_of::()) { + return Err(KernelError::InvalidAlign); + } + + let ptr = NonNull::new(addr as *mut Self).ok_or(KernelError::InvalidAddress)?; + + // Safety: Ptr is properly aligned and non-null. The validity of the memory at that address is valid by the call contract. + Ok(Pin::new(unsafe { boxed::Box::from_raw(ptr) })) + } + } + + fn alloc(&mut self, page_count: usize) -> Option { + // If a bit is 1 the page is free. If a bit is 0 the page is allocated. + let mut start = 0; + let mut len = 0usize; + + let rem = page_count.saturating_sub(Self::BITS_PER_WORD); + let mask = (!0usize).unbounded_shl((Self::BITS_PER_WORD.saturating_sub(page_count)) as u32); + + for idx in 0..N { + if self.l1[idx] == 0 { + len = 0; + continue; + } + + let mut byte = self.l1[idx]; + + let mut shift = if len > 0 { + 0usize + } else { + byte.leading_zeros() as usize + }; + + byte <<= shift; + + while shift < Self::BITS_PER_WORD { + // Make the mask smaller if we already have some contiguous bits. + let mask = if rem.saturating_sub(len) == 0 { + mask << (len - rem) + } else { + mask + }; + + // We shifted byte to MSB, mask is already aligned to the left. + // We compare them via and and shift to the right to shift out extra bits from the mask that would overflow into the next word. + let mut found = (byte & mask) >> shift; + + // We also need to shift the mask to the right so that we can compare mask and found. + if found == (mask >> shift) { + if len == 0 { + start = idx * Self::BITS_PER_WORD + shift; + } + + // Shift completely to the right. + found >>= found.trailing_zeros(); + + // As all found bits are now on the right we can just count them to get the amount we found. + len += found.trailing_ones() as usize; + // Continue to the next word if we haven't found enough bits yet. + break; + } else { + len = 0; + } + + shift += 1; + byte <<= 1; + } + + if len >= page_count { + // Mark the allocated pages as used. + let mut idx = start / Self::BITS_PER_WORD; + + // Mark all bits in the first word as used. + { + let skip = start % Self::BITS_PER_WORD; + let rem = len.min(Self::BITS_PER_WORD) - skip; + + self.l1[idx] &= !((!0usize).unbounded_shl((Self::BITS_PER_WORD - rem) as u32) >> skip); + + if len <= rem { + return Some(start); + } + + len -= rem; + idx += 1; + } + + // Mark all bits in the middle words as used. + { + let mid_cnt = len / Self::BITS_PER_WORD; + + for i in 0..mid_cnt { + self.l1[idx + i] = 0; + } + + idx += mid_cnt; + } + + // Mark the remaining bits in the last word as used. + self.l1[idx] &= !((!0usize).unbounded_shl((Self::BITS_PER_WORD - (len % Self::BITS_PER_WORD)) as u32)); + return Some(self.begin + (start * super::PAGE_SIZE)); + } + } + + None + } + + fn free(&mut self, addr: PhysAddr, page_count: usize) { + if !addr.is_multiple_of(super::PAGE_SIZE) { + panic!("Address must be page aligned"); + } + + let mut idx = (addr - self.begin) / super::PAGE_SIZE / Self::BITS_PER_WORD; + let mut bit_idx = ((addr - self.begin) / super::PAGE_SIZE) % Self::BITS_PER_WORD; + + // TODO: slow + for _ in 0..page_count { + self.l1[idx] |= 1 << (Self::BITS_PER_WORD - 1 - bit_idx); + + bit_idx += 1; + + if bit_idx == Self::BITS_PER_WORD { + bit_idx = 0; + idx += 1; + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_random_pattern() { + const ITARATIONS: usize = 1000; + + for i in 0..ITARATIONS { + const N: usize = 1024; + const BITS: usize = Allocator::::BITS_PER_WORD; + const ALLOC_SIZE: usize = 100; + + let mut allocator = Allocator::::new(0x0).unwrap(); + + // Generate a random bit pattern. + for i in 0..N { + let is_zero = rand::random::(); + + if is_zero { + allocator.l1[i / BITS] &= !(1 << ((BITS - 1) - (i % BITS))); + } + } + + // Place a run of ALLOC_SIZE contiguous bits set to 1 at a random position. + let start = rand::random::() % (N - ALLOC_SIZE); + for i in start..(start + ALLOC_SIZE) { + allocator.l1[i / BITS] |= 1 << ((BITS - 1) - (i % BITS)); + } + + let pre = allocator.l1.clone(); + + let addr = super::super::Allocator::alloc(&mut allocator, ALLOC_SIZE).unwrap(); + let idx = addr / super::super::PAGE_SIZE; + + // Check that the bits in returned addresses is all ones in pre. + for i in 0..ALLOC_SIZE { + let bit = (pre[(idx + i) / BITS] >> ((BITS - 1) - ((idx + i) % BITS))) & 1; + assert_eq!(bit, 1, "Bit at index {} is not set", idx + i); + } + + // Check that the bits in returned addresses is all zeros in allocator.l1. + for i in 0..ALLOC_SIZE { + let bit = (allocator.l1[(idx + i) / BITS] >> ((BITS - 1) - ((idx + i) % BITS))) & 1; + assert_eq!(bit, 0, "Bit at index {} is not cleared", idx + i); + } + } + } +} \ No newline at end of file diff --git a/src/mem/vmm.rs b/src/mem/vmm.rs new file mode 100644 index 0000000..7565658 --- /dev/null +++ b/src/mem/vmm.rs @@ -0,0 +1,66 @@ +use core::ops::Range; + +use crate::{utils::KernelError}; + +use interface::{PhysAddr, VirtAddr}; + +mod nommu; + +pub type AddressSpace = nommu::AddressSpace; + +bitflags::bitflags! { + #[derive(Clone, Copy)] + pub struct Perms: u8 { + const Read = 0b0001; + const Write = 0b0010; + const Exec = 0b0100; + } +} + +#[derive(Clone)] +pub enum Backing { + Zeroed, + Uninit, + Anon(PhysAddr), +} + +#[derive(Clone)] +pub struct Region { + range: Range, + backing: Backing, + perms: Perms, +} + +impl Region { + pub fn new(start: VirtAddr, len: usize, backing: Backing, perms: Perms) -> Self { + Self { + range: start..start.saturating_add(len), + backing, + perms, + } + } + + pub fn start(&self) -> VirtAddr { + self.range.start + } + + pub fn len(&self) -> usize { + self.range.end.saturating_sub(self.range.start) + } + + pub fn contains(&self, addr: VirtAddr) -> bool { + self.range.contains(&addr) + } +} + +pub trait AddressSpacelike { + // Size is the amount of pages in the address space. On nommu systems this will be reserved. + fn new(pages: usize) -> Result where Self: Sized; + fn map(&mut self, region: Region) -> Result; + fn unmap(&mut self, region: &Region) -> Result<(), KernelError>; + fn protect(&mut self, region: &Region, perms: Perms) -> Result<(), KernelError>; + fn virt_to_phys(&self, addr: VirtAddr) -> Option; + fn phys_to_virt(&self, addr: PhysAddr) -> Option; + fn end(&self) -> VirtAddr; + fn activate(&self) -> Result<(), KernelError>; +} \ No newline at end of file diff --git a/src/mem/vmm/nommu.rs b/src/mem/vmm/nommu.rs new file mode 100644 index 0000000..69603cd --- /dev/null +++ b/src/mem/vmm/nommu.rs @@ -0,0 +1,87 @@ +use core::ptr::copy_nonoverlapping; +use std::num::NonZero; + +use crate::{ + mem::{ + pfa, vmm, + }, + utils::KernelError, +}; + +use interface::{PhysAddr, VirtAddr}; + +pub struct AddressSpace { + begin: VirtAddr, + size: usize, +} + +impl vmm::AddressSpacelike for AddressSpace { + fn new(size: usize) -> Result { + let pg_cnt = size.div_ceil(pfa::PAGE_SIZE); + let begin = pfa::alloc_page(pg_cnt).ok_or(KernelError::OutOfMemory)?; + + Ok(Self { + begin, + size: pg_cnt * pfa::PAGE_SIZE, + }) + } + + fn map(&mut self, region: vmm::Region) -> Result { + if region.start() + region.len() > self.size { + return Err(KernelError::OutOfMemory); + } + + if let Some(test) = NonZero::new(region.start()) { + test. + } + + + match region.backing { + vmm::Backing::Anon(phys) => { + unsafe { + copy_nonoverlapping( + phys as *const u8, + (self.begin + region.start()) as *mut u8, + region.len(), + ) + }; + Ok(self.begin + region.start()) + }, + vmm::Backing::Zeroed => { + unsafe { + core::ptr::write_bytes( + (self.begin + region.start()) as *mut u8, + 0, + region.len(), + ) + }; + Ok(self.begin + region.start()) + }, + vmm::Backing::Uninit => Ok(self.begin + region.start()), + } + } + + fn unmap(&mut self, _region: &vmm::Region) -> Result<(), KernelError> { + Ok(()) + } + + fn protect(&mut self, _region: &vmm::Region, _perms: vmm::Perms) -> Result<(), KernelError> { + Ok(()) + } + + fn phys_to_virt(&self, addr: PhysAddr) -> Option { + addr.checked_sub(self.begin) + } + + fn virt_to_phys(&self, addr: VirtAddr) -> Option { + self.begin.checked_add(addr) + } + + fn end(&self) -> VirtAddr { + self.size + } + + fn activate(&self) -> Result<(), KernelError> { + Ok(()) + } +} diff --git a/src/sched.rs b/src/sched.rs index 72ce054..9edca37 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -1,13 +1,11 @@ //! This module provides access to the scheduler. pub mod rt; -//pub mod scheduler; -pub mod task; -pub mod thread; +//pub mod thread; use hal::Schedable; -use crate::mem::{array::IndexMap, rbtree::RbTree, view::ViewMut}; +use crate::types::{array::IndexMap, rbtree::RbTree, view::ViewMut}; type ThreadMap = IndexMap; diff --git a/src/sched/rt.rs b/src/sched/rt.rs index c6c6b00..95dfb62 100644 --- a/src/sched/rt.rs +++ b/src/sched/rt.rs @@ -1,4 +1,4 @@ -use crate::{mem::{rbtree::RbTree, traits::{Get, GetMut}, view::ViewMut}, sched::{ThreadMap, thread::{self}}}; +use crate::{types::{rbtree::RbTree, traits::{Get, GetMut}, view::ViewMut}, sched::{ThreadMap, thread::{self}}}; pub struct Scheduler { edf: RbTree, diff --git a/src/sched/thread.rs b/src/sched/thread.rs index 7a814b4..f988b97 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -6,7 +6,7 @@ use hal::Stack; use hal::stack::Stacklike; use macros::TaggedLinks; -use crate::{mem::{rbtree::{self, Compare}, traits::{Project, ToIndex}}, sched::task::TaskId, utils::KernelError}; +use crate::{types::{rbtree::{self, Compare}, traits::{Project, ToIndex}}, sched::task::TaskId, utils::KernelError}; /// Id of a task. This is only unique within a Task. #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..26df432 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,8 @@ + +pub mod boxed; +pub mod array; +pub mod heap; +pub mod pool; +pub mod rbtree; +pub mod traits; +pub mod view; \ No newline at end of file diff --git a/src/mem/array.rs b/src/types/array.rs similarity index 97% rename from src/mem/array.rs rename to src/types/array.rs index 4e40954..3e50514 100644 --- a/src/mem/array.rs +++ b/src/types/array.rs @@ -1,12 +1,14 @@ //! This module implements static and dynamic arrays for in-kernel use. -use super::boxed::Box; -use crate::{ - mem::traits::{Get, GetMut, ToIndex}, - utils::KernelError, +use super::{ + traits::{Get, GetMut, ToIndex}, + boxed::Box, }; + +use crate::utils::KernelError; + use core::{borrow::Borrow, mem::MaybeUninit}; -use std::{ +use core::{ ops::{Index, IndexMut}, }; @@ -171,12 +173,17 @@ impl GetMut for IndexMap { return (None, None); } - let ptr1 = &mut self.data[index1] as *mut Option; - let ptr2 = &mut self.data[index2] as *mut Option; + let (left, right) = self.data.split_at_mut(index1.max(index2)); - // Safety: the elements at index1 and index2 are nowhere else borrowed mutably by function contract. - // And they are disjoint because of the check above. - unsafe { ((*ptr1).as_mut(), (*ptr2).as_mut()) } + if index1 < index2 { + let elem1 = left[index1].as_mut(); + let elem2 = right[0].as_mut(); + (elem1, elem2) + } else { + let elem1 = right[0].as_mut(); + let elem2 = left[index2].as_mut(); + (elem1, elem2) + } } fn get3_mut>( diff --git a/src/mem/boxed.rs b/src/types/boxed.rs similarity index 85% rename from src/mem/boxed.rs rename to src/types/boxed.rs index c2af5d7..3e2277a 100644 --- a/src/mem/boxed.rs +++ b/src/types/boxed.rs @@ -1,6 +1,6 @@ //! This module provides a simple heap-allocated memory block for in-kernel use. -use super::{free, malloc}; +use crate::mem; use crate::utils::KernelError; use core::{ mem::{MaybeUninit, forget}, @@ -28,7 +28,7 @@ impl Box<[T]> { return Ok(Self::new_slice_empty()); } - if let Some(ptr) = malloc(size_of::() * len, align_of::()) { + if let Some(ptr) = mem::malloc(size_of::() * len, align_of::()) { let ptr = slice_from_raw_parts_mut(ptr.as_ptr().cast(), len); Ok(Self { ptr: unsafe { NonNull::new_unchecked(ptr) }, @@ -54,7 +54,7 @@ impl Box<[T]> { /// /// Returns a new heap-allocated slice with the given length or an error if the allocation failed. pub fn new_slice_uninit(len: usize) -> Result]>, KernelError> { - if let Some(ptr) = malloc( + if let Some(ptr) = mem::malloc( size_of::>() * len, align_of::>(), ) { @@ -76,7 +76,7 @@ impl Box { /// /// Returns a new heap-allocated value or `None` if the allocation failed. pub fn new(value: T) -> Option { - if let Some(ptr) = malloc(size_of::(), align_of::()) { + if let Some(ptr) = mem::malloc(size_of::(), align_of::()) { unsafe { write(ptr.as_ptr().cast(), value); } @@ -139,7 +139,7 @@ impl Drop for Box { } drop_in_place(self.ptr.as_ptr()); - free(self.ptr.cast(), size); + mem::free(self.ptr.cast(), size); } } } @@ -239,39 +239,3 @@ impl AsMut for Box { self.as_mut() } } - -#[cfg(kani)] -mod verification { - use crate::mem::alloc; - - use super::*; - - /* - fn alloc_range(length: usize) -> Option> { - let alloc_range = std::alloc::Layout::from_size_align(length, align_of::()).unwrap(); - let ptr = unsafe { std::alloc::alloc(alloc_range) }; - - if ptr.is_null() || ((ptr as usize) >= isize::MAX as usize - length) { - None - } else { - Some(ptr as usize..ptr as usize + length) - } - } - - #[kani::proof] - fn proof_new_slice_zero() { - let mut allocator = alloc::BestFitAllocator::new(); - allocator - - let len = kani::any(); - kani::assume(len < alloc::MAX_ADDR); - - let b = Box::::new_slice_zeroed(len); - - let index = kani::any(); - kani::assume(index < len); - - assert!(b[index] == 0); - } - */ -} diff --git a/src/mem/heap.rs b/src/types/heap.rs similarity index 100% rename from src/mem/heap.rs rename to src/types/heap.rs diff --git a/src/mem/pool.rs b/src/types/pool.rs similarity index 100% rename from src/mem/pool.rs rename to src/types/pool.rs diff --git a/src/mem/queue.rs b/src/types/queue.rs similarity index 100% rename from src/mem/queue.rs rename to src/types/queue.rs diff --git a/src/mem/rbtree.rs b/src/types/rbtree.rs similarity index 99% rename from src/mem/rbtree.rs rename to src/types/rbtree.rs index 2f7a5f0..ac64a17 100644 --- a/src/mem/rbtree.rs +++ b/src/types/rbtree.rs @@ -1,6 +1,6 @@ -use std::{marker::PhantomData}; +use core::{marker::PhantomData}; -use crate::mem::traits::{Get, GetMut}; +use super::traits::{Get, GetMut}; #[allow(dead_code)] pub struct RbTree { @@ -652,7 +652,7 @@ impl RbTree #[cfg(test)] mod tests { use super::*; - use crate::mem::traits::{Get, GetMut}; + use super::{Get, GetMut}; use std::borrow::Borrow; use std::collections::HashSet; diff --git a/src/mem/traits.rs b/src/types/traits.rs similarity index 100% rename from src/mem/traits.rs rename to src/types/traits.rs diff --git a/src/mem/view.rs b/src/types/view.rs similarity index 94% rename from src/mem/view.rs rename to src/types/view.rs index c8bd4bc..c07de3a 100644 --- a/src/mem/view.rs +++ b/src/types/view.rs @@ -1,7 +1,7 @@ use core::borrow::Borrow; -use std::marker::PhantomData; +use core::marker::PhantomData; -use crate::mem::traits::{Get, GetMut, Project, ToIndex}; +use super::traits::{Get, GetMut, Project, ToIndex}; pub struct ViewMut<'a, K: ?Sized + ToIndex, P, S: GetMut> where diff --git a/src/utils.rs b/src/utils.rs index 9fa7644..c77239a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -58,6 +58,7 @@ pub enum KernelError { InvalidAddress, InvalidArgument, HalError(hal::Error), + CustomError(&'static str), } /// Debug msg implementation for KernelError. @@ -70,6 +71,7 @@ impl Debug for KernelError { KernelError::InvalidAddress => write!(f, "Invalid address"), KernelError::InvalidArgument => write!(f, "Invalid argument"), KernelError::HalError(e) => write!(f, "{e} (in HAL)"), + KernelError::CustomError(msg) => write!(f, "{}", msg), } } } From b74275188d9f23fc0211c8c30dcb7414c0db5953 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Wed, 25 Mar 2026 19:08:03 +0000 Subject: [PATCH 18/42] more scheduler rewrite --- machine/api/src/lib.rs | 5 +- machine/api/src/mem.rs | 67 +++++++++++- machine/api/src/{mem => }/stack.rs | 11 +- machine/arm/src/sched.rs | 8 +- machine/testing/src/sched.rs | 4 +- options.toml | 6 ++ src/dispatch.rs | 14 --- src/dispatch/task.rs | 162 ---------------------------- src/idle.rs | 17 +++ src/lib.rs | 17 +-- src/mem.rs | 34 ++++-- src/mem/pfa.rs | 5 +- src/mem/pfa/bitset.rs | 14 +-- src/mem/vmm.rs | 6 +- src/mem/vmm/nommu.rs | 47 ++++---- src/sched.rs | 165 ++++++++++++++++++++--------- src/sched/dispch.rs | 7 ++ src/sched/rt.rs | 6 +- src/sched/task.rs | 129 ++++++++++++++++++++++ src/sched/thread.rs | 49 +++++---- src/sync/atomic.rs | 117 +++++++++++++++++++- src/time.rs | 38 ++----- src/types/rbtree.rs | 2 +- 23 files changed, 584 insertions(+), 346 deletions(-) rename machine/api/src/{mem => }/stack.rs (89%) delete mode 100644 src/dispatch.rs delete mode 100644 src/dispatch/task.rs create mode 100644 src/idle.rs create mode 100644 src/sched/dispch.rs create mode 100644 src/sched/task.rs diff --git a/machine/api/src/lib.rs b/machine/api/src/lib.rs index 36860bc..4480adb 100644 --- a/machine/api/src/lib.rs +++ b/machine/api/src/lib.rs @@ -3,6 +3,7 @@ use core::{fmt::Display, ops::Range}; pub mod mem; +pub mod stack; #[derive(Default, Debug, PartialEq, Eq, Clone)] pub enum Error { @@ -10,6 +11,7 @@ pub enum Error { Generic, OutOfMemory(usize), OutOfBoundsPtr(usize, Range), + InvalidAddress(usize), } pub enum Fault { @@ -30,7 +32,8 @@ impl Display for Error { "Pointer {:p} out of bounds (expected in {:p}..{:p})", *ptr as *const u8, range.start as *const u8, range.end as *const u8 ) - } + }, + Error::InvalidAddress(addr) => write!(f, "Invalid address {:p}", *addr as *const u8), } } } diff --git a/machine/api/src/mem.rs b/machine/api/src/mem.rs index 10c8b07..b919bfe 100644 --- a/machine/api/src/mem.rs +++ b/machine/api/src/mem.rs @@ -1,11 +1,12 @@ - -pub mod stack; +use core::ops::{Add, Sub, Div, Rem}; #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] pub struct PhysAddr(usize); impl PhysAddr { + pub const MAX: Self = Self(usize::MAX); + #[inline] pub fn new(addr: usize) -> Self { Self(addr) @@ -15,6 +16,58 @@ impl PhysAddr { pub fn as_usize(&self) -> usize { self.0 } + + pub fn as_mut_ptr(&self) -> *mut T { + self.0 as *mut T + } + + pub fn checked_add(&self, other: usize) -> Option { + self.0.checked_add(other).map(Self) + } + + pub fn checked_sub(&self, other: usize) -> Option { + self.0.checked_sub(other).map(Self) + } + + pub fn is_multiple_of(&self, align: usize) -> bool { + self.0.is_multiple_of(align) + } +} + +impl Add for PhysAddr { + type Output = Self; + + #[inline] + fn add(self, rhs: usize) -> Self::Output { + Self(self.0 + rhs) + } +} + +impl Sub for PhysAddr { + type Output = Self; + + #[inline] + fn sub(self, rhs: usize) -> Self::Output { + Self(self.0 - rhs) + } +} + +impl Div for PhysAddr { + type Output = Self; + + #[inline] + fn div(self, rhs: usize) -> Self::Output { + Self(self.0 / rhs) + } +} + +impl Rem for PhysAddr { + type Output = Self; + + #[inline] + fn rem(self, rhs: usize) -> Self::Output { + Self(self.0 % rhs) + } } impl From for usize { @@ -38,6 +91,16 @@ impl VirtAddr { pub fn as_usize(&self) -> usize { self.0 } + + #[inline] + pub fn saturating_add(&self, other: usize) -> Self { + Self(self.0.saturating_add(other)) + } + + #[inline] + pub fn saturating_sub(&self, other: usize) -> Self { + Self(self.0.saturating_sub(other)) + } } impl From for usize { diff --git a/machine/api/src/mem/stack.rs b/machine/api/src/stack.rs similarity index 89% rename from machine/api/src/mem/stack.rs rename to machine/api/src/stack.rs index 4291378..11604f1 100644 --- a/machine/api/src/mem/stack.rs +++ b/machine/api/src/stack.rs @@ -1,18 +1,21 @@ use core::{ffi::c_void, num::NonZero}; use crate::{Result, mem::PhysAddr}; -pub struct StackDescriptor { +pub type EntryFn = extern "C" fn(); +pub type FinFn = extern "C" fn() -> !; + +pub struct Descriptor { pub top: PhysAddr, pub size: NonZero, - pub entry: extern "C" fn(), - pub fin: Option !>, + pub entry: EntryFn, + pub fin: Option, } pub trait Stacklike { type ElemSize: Copy; type StackPtr; - unsafe fn new(desc: StackDescriptor) -> Result + unsafe fn new(desc: Descriptor) -> Result where Self: Sized; diff --git a/machine/arm/src/sched.rs b/machine/arm/src/sched.rs index 19578fa..eb3f274 100644 --- a/machine/arm/src/sched.rs +++ b/machine/arm/src/sched.rs @@ -7,7 +7,7 @@ use core::{ ptr::NonNull, }; -use hal_api::{Result, stack::StackDescriptor}; +use hal_api::{Result, stack::Descriptor}; use crate::print::println; @@ -191,11 +191,11 @@ impl hal_api::stack::Stacklike for ArmStack { type ElemSize = u32; type StackPtr = StackPtr; - unsafe fn new(desc: StackDescriptor) -> Result + unsafe fn new(desc: Descriptor) -> Result where Self: Sized, { - let StackDescriptor { + let Descriptor { top, size, entry, @@ -203,7 +203,7 @@ impl hal_api::stack::Stacklike for ArmStack { } = desc; // We expect a PhysAddr, which can be converted to a ptr on nommu. - let top = NonNull::new(top as *mut u32).ok_or(hal_api::Error::InvalidAddress)?; + let top = NonNull::new(top.as_mut_ptr::()).ok_or(hal_api::Error::InvalidAddress(top.as_usize()))?; let mut stack = Self { top, diff --git a/machine/testing/src/sched.rs b/machine/testing/src/sched.rs index 9715b16..ced7100 100644 --- a/machine/testing/src/sched.rs +++ b/machine/testing/src/sched.rs @@ -2,7 +2,7 @@ use std::ffi::c_void; use hal_api::{ Result, - stack::{StackDescriptor, Stacklike}, + stack::{Descriptor, Stacklike}, }; #[derive(Debug, Clone, Copy)] @@ -12,7 +12,7 @@ impl Stacklike for TestingStack { type ElemSize = usize; type StackPtr = *mut c_void; - unsafe fn new(_desc: StackDescriptor) -> Result + unsafe fn new(_desc: Descriptor) -> Result where Self: Sized, { diff --git a/options.toml b/options.toml index d7bf1ce..7427cea 100644 --- a/options.toml +++ b/options.toml @@ -26,6 +26,12 @@ description = "Enables the Floating Point Unit (FPU). This is required for appli type = "Boolean" default = false +[stackpages] +name = "Stack Pages" +description = "Number of pages to allocate for the kernel stack." +type = { type = "Integer", min = 1 } +default = 4 + [tuning.appmemsize] name = "Application Memory Size" description = "Sets the size of the initial memory region for the init application. This memory is used for the heap and stack." diff --git a/src/dispatch.rs b/src/dispatch.rs deleted file mode 100644 index a985fef..0000000 --- a/src/dispatch.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! This is the owner of all Tasks. It takes care of context switching between them. -//! The idea is that the Schedulers selects one of its threads to run, and then the Dipatcher takes care of context-switching to the associated Task. (e.g. setting up the address space) -//! If the thread is part of the same task as the currently running one, the Dispatcher does effectively nothing. -//! -//! - -mod task; - -use crate::types::array::IndexMap; - -/* -struct Dispatcher { - tasks: IndexMap, -}*/ \ No newline at end of file diff --git a/src/dispatch/task.rs b/src/dispatch/task.rs deleted file mode 100644 index b3f3d2c..0000000 --- a/src/dispatch/task.rs +++ /dev/null @@ -1,162 +0,0 @@ -//! This module provides the basic task and thread structures for the scheduler. -use core::num::NonZero; -use core::ops::Range; -use core::ptr::NonNull; -use std::borrow::Borrow; - -use hal::{Stack, stack}; - -use hal::stack::Stacklike; - -use crate::{mem, sched}; - -use crate::mem::alloc::{Allocator, BestFitAllocator}; -use crate::mem::vmm::{AddressSpace, AddressSpacelike, Region}; -use crate::types::traits::ToIndex; -use crate::utils::KernelError; - -/// Id of a task. This is unique across all tasks. -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] -pub struct UId { - uid: usize, -} - -impl ToIndex for UId { - fn to_index>(idx: Option) -> usize { - idx.as_ref().map_or(0, |uid| uid.borrow().uid) - } -} - -pub struct Attributes { - reserved: Option>, -} - -/// The struct representing a task. -pub struct Task { - /// The unique identifier of the task. - pub id: UId, - /// The counter for the thread ids. - tid_cntr: usize, - /// Sets up the memory for the task. - address_space: mem::vmm::AddressSpace, -} - -impl Task { - /// Create a new task. - /// - /// `memory_size` - The size of the memory that the task requires. - /// - /// Returns a new task if the task was created successfully, or an error if the task could not be created. - pub fn new(id: UId, attrs: &Attributes) -> Result { - Ok(Self { - id, - address_space: AddressSpace::new(), - tid_cntr: 0, - }) - } - - fn allocate_tid(&mut self) -> sched::thread::Id { - let tid = self.tid_cntr; - self.tid_cntr += 1; - - sched::thread::Id::new(tid, self.id) - } - - pub fn allocate(&mut self, size: usize, align: usize) -> Result { - self.address_space.map(size, align) - } - - pub fn create_thread( - &mut self, - entry: extern "C" fn(), - fin: Option !>, - ) -> Result { - - // Create the stack for the thread. - let size = 1 * mem::pfa::PAGE_SIZE; // TODO: Make this configurable - let start = self.address_space.end() - size; - let region = mem::vmm::Region::new( - start, - size, - mem::vmm::Backing::Uninit, - mem::vmm::Perms::Read | mem::vmm::Perms::Write, - ); - let stack_pa = self.address_space.map(region)?; - - let stack = hal::stack::StackDescriptor { - top: stack_pa, - // Safe unwrap because stack size is non zero. - size: NonZero::new(size).unwrap(), - entry, - fin, - }; - - let stack = unsafe { Stack::new(stack) }?; - let tid = self.allocate_tid(); - - // TODO: Revert if error occurs - self.register_thread(tid)?; - - Ok(ThreadDescriptor { tid, stack, timing }) - } - - /// Register a thread with the task. - /// - /// `thread_id` - The id of the thread to register. - /// - /// Returns `Ok(())` if the thread was registered successfully, or an error if the thread could not be registered. TODO: Check if the thread is using the same memory as the task. - fn register_thread(&mut self, thread_id: ThreadId) -> Result<(), KernelError> { - self.threads.push(thread_id) - } -} - -/// The memory of a task. -#[derive(Debug)] -pub struct TaskMemory { - /// The beginning of the memory. - begin: NonNull, - /// The size of the memory. - size: usize, - - /// The allocator for the task's memory. - alloc: BestFitAllocator, -} - -#[allow(dead_code)] -impl TaskMemory { - /// Create a new task memory. - /// - /// `size` - The size of the memory. - /// - /// Returns a new task memory if the memory was created successfully, or an error if the memory could not be created. - pub fn new(size: usize) -> Result { - let begin = mem::malloc(size, align_of::()).ok_or(KernelError::OutOfMemory)?; - - let mut alloc = BestFitAllocator::new(); - let range = Range { - start: begin.as_ptr() as usize, - end: begin.as_ptr() as usize + size, - }; - - if let Err(e) = unsafe { alloc.add_range(range) } { - unsafe { mem::free(begin, size) }; - return Err(e); - } - - Ok(Self { begin, size, alloc }) - } - - pub fn malloc(&mut self, size: usize, align: usize) -> Result, KernelError> { - self.alloc.malloc(size, align) - } - - pub fn free(&mut self, ptr: NonNull, size: usize) { - unsafe { self.alloc.free(ptr, size) } - } -} - -impl Drop for TaskMemory { - fn drop(&mut self) { - unsafe { mem::free(self.begin, self.size) }; - } -} diff --git a/src/idle.rs b/src/idle.rs new file mode 100644 index 0000000..c3b4060 --- /dev/null +++ b/src/idle.rs @@ -0,0 +1,17 @@ +use crate::sched; + +extern "C" fn entry() { + loop { + hal::asm::wfi!(); + } +} + +pub fn init() { + let attrs = sched::thread::Attributes { + entry: entry, + fin: None, + }; + if let Err(e) = sched::create_thread(sched::task::KERNEL_TASK, &attrs) { + panic!("[Idle] Error: failed to create idle thread. Error: {e:?}"); + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 2c023a0..f273848 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,12 +10,13 @@ mod utils; mod faults; mod mem; mod types; +mod idle; + pub mod print; -//pub mod sched; -mod dispatch; +pub mod sched; pub mod sync; pub mod syscalls; -//pub mod time; +pub mod time; pub mod uspace; use hal::Machinelike; @@ -33,6 +34,7 @@ include!(concat!(env!("OUT_DIR"), "/device_tree.rs")); /// The `boot_info` pointer must be valid and point to a properly initialized `BootInfo` structure. #[unsafe(no_mangle)] pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { + hal::asm::disable_interrupts!(); // Initialize basic hardware and the logging system. hal::Machine::init(); hal::Machine::bench_start(); @@ -47,9 +49,10 @@ pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { print::print_header(); // Initialize the memory allocator. - if let Err(e) = mem::init_memory(&device_tree::memory::REGIONS) { - panic!("[Kernel] Error: failed to initialize memory allocator. Error: {e:?}"); - } + let kaddr_space = mem::init_memory(boot_info); + + sched::init(kaddr_space); + idle::init(); let (cyc, ns) = hal::Machine::bench_end(); kprintln!( @@ -63,6 +66,8 @@ pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { panic!("[Kernel] Error: failed to start init application. Error: {e:?}"); } + hal::asm::enable_interrupts!(); + loop { hal::asm::nop!(); } diff --git a/src/mem.rs b/src/mem.rs index f6c98b1..f45db97 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -1,14 +1,18 @@ //! This module provides access to the global memory allocator. +use crate::mem::vmm::{AddressSpacelike, Backing, Perms, Region}; use crate::sync::spinlock::SpinLocked; -use crate::{BootInfo, utils}; +use crate::{BootInfo, sched, utils}; use alloc::Allocator; +use hal::mem::{PhysAddr, VirtAddr}; use core::ptr::NonNull; pub mod alloc; pub mod vmm; pub mod pfa; +pub const BITS_PER_PTR: usize = core::mem::size_of::() * 8; + /// The possible types of memory. Which is compatible with the multiboot2 memory map. /// Link: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html #[repr(C)] @@ -35,19 +39,31 @@ static GLOBAL_ALLOCATOR: SpinLocked = /// `regions` - The memory node module of device tree codegen file. /// /// Returns an error if the memory allocator could not be initialized. -pub fn init_memory(boot_info: &BootInfo) -> Result<(), utils::KernelError> { - pfa::init_pfa(0x20000000)?; // TODO: Get this from the DeviceTree. +pub fn init_memory(boot_info: &BootInfo) -> vmm::AddressSpace { + if let Err(e) = pfa::init_pfa(PhysAddr::new(0x20000000)) { // TODO: Get this from the DeviceTree. + panic!("[Kernel] Error: failed to initialize PFA. Error: {e:?}"); + } + + // TODO: Configure. + let pgs = 4; + + let kaddr_space = vmm::AddressSpace::new(pgs).unwrap_or_else(|e| { + panic!("[Kernel] Error: failed to create kernel address space."); + }); + + let begin = kaddr_space.map(Region::new(VirtAddr::new(0), len, Backing::Zeroed, Perms::all())).unwrap_or_else(|e| { + panic!("[Kernel] Error: failed to map kernel address space."); + }); let mut allocator = GLOBAL_ALLOCATOR.lock(); - for &(_, base, size) in regions { - let range = base..base + size; - unsafe { - allocator.add_range(range)?; - } + let range = begin.as_usize()..(begin.as_usize() + pgs * vmm::PAGE_SIZE); + + if let Err(e) = unsafe { allocator.add_range(range) } { + panic!("[Kernel] Error: failed to add range to allocator."); } - Ok(()) + kaddr_space } /// Allocate a memory block. Normally Box or SizedPool should be used instead of this function. diff --git a/src/mem/pfa.rs b/src/mem/pfa.rs index 9c24df8..c62b8c1 100644 --- a/src/mem/pfa.rs +++ b/src/mem/pfa.rs @@ -1,10 +1,11 @@ // The top level page frame allocator. +use hal::mem::PhysAddr; + use crate::sync::spinlock::SpinLocked; use crate::types::boxed::Box; use crate::utils::KernelError; -use interface::PhysAddr; use core::pin::Pin; mod bitset; @@ -12,7 +13,7 @@ mod bitset; /// Page size constant (typically 4KB) pub const PAGE_SIZE: usize = 4096; -const PAGE_CNT: usize = 1024; // TODO: This should be determined by the DeviceTree. +const PAGE_CNT: usize = 100; // TODO: This should be determined by the DeviceTree. type AllocatorType = bitset::Allocator; diff --git a/src/mem/pfa/bitset.rs b/src/mem/pfa/bitset.rs index d9ab9e3..23ef7f7 100644 --- a/src/mem/pfa/bitset.rs +++ b/src/mem/pfa/bitset.rs @@ -1,13 +1,13 @@ use core::pin::Pin; use core::ptr::NonNull; +use hal::mem::PhysAddr; + use crate::{ types::boxed::{self, Box}, utils::KernelError, }; -use interface::PhysAddr; - pub struct Allocator { begin: PhysAddr, l1: [usize; N], @@ -43,7 +43,7 @@ impl super::Allocator for Allocator { return Err(KernelError::InvalidAlign); } - let ptr = NonNull::new(addr as *mut Self).ok_or(KernelError::InvalidAddress)?; + let ptr = NonNull::new(addr.as_mut_ptr::()).ok_or(KernelError::InvalidAddress)?; // Safety: Ptr is properly aligned and non-null. The validity of the memory at that address is valid by the call contract. Ok(Pin::new(unsafe { boxed::Box::from_raw(ptr) })) @@ -119,7 +119,7 @@ impl super::Allocator for Allocator { self.l1[idx] &= !((!0usize).unbounded_shl((Self::BITS_PER_WORD - rem) as u32) >> skip); if len <= rem { - return Some(start); + return Some(PhysAddr::new(start)); } len -= rem; @@ -151,8 +151,8 @@ impl super::Allocator for Allocator { panic!("Address must be page aligned"); } - let mut idx = (addr - self.begin) / super::PAGE_SIZE / Self::BITS_PER_WORD; - let mut bit_idx = ((addr - self.begin) / super::PAGE_SIZE) % Self::BITS_PER_WORD; + let mut idx = (addr.as_usize() - self.begin.as_usize()) / super::PAGE_SIZE / Self::BITS_PER_WORD; + let mut bit_idx = ((addr.as_usize() - self.begin.as_usize()) / super::PAGE_SIZE) % Self::BITS_PER_WORD; // TODO: slow for _ in 0..page_count { @@ -181,7 +181,7 @@ mod tests { const BITS: usize = Allocator::::BITS_PER_WORD; const ALLOC_SIZE: usize = 100; - let mut allocator = Allocator::::new(0x0).unwrap(); + let mut allocator = Allocator::::new(PhysAddr::new(0x0)).unwrap(); // Generate a random bit pattern. for i in 0..N { diff --git a/src/mem/vmm.rs b/src/mem/vmm.rs index 7565658..1f8aba9 100644 --- a/src/mem/vmm.rs +++ b/src/mem/vmm.rs @@ -1,8 +1,8 @@ use core::ops::Range; -use crate::{utils::KernelError}; +use hal::mem::{PhysAddr, VirtAddr}; -use interface::{PhysAddr, VirtAddr}; +use crate::{utils::KernelError}; mod nommu; @@ -45,7 +45,7 @@ impl Region { } pub fn len(&self) -> usize { - self.range.end.saturating_sub(self.range.start) + self.range.end.saturating_sub(self.range.start.into()).into() } pub fn contains(&self, addr: VirtAddr) -> bool { diff --git a/src/mem/vmm/nommu.rs b/src/mem/vmm/nommu.rs index 69603cd..ff561b2 100644 --- a/src/mem/vmm/nommu.rs +++ b/src/mem/vmm/nommu.rs @@ -1,5 +1,6 @@ use core::ptr::copy_nonoverlapping; -use std::num::NonZero; + +use hal::mem::{PhysAddr, VirtAddr}; use crate::{ mem::{ @@ -8,57 +9,56 @@ use crate::{ utils::KernelError, }; -use interface::{PhysAddr, VirtAddr}; - pub struct AddressSpace { - begin: VirtAddr, - size: usize, + begin: PhysAddr, + end: PhysAddr, } impl vmm::AddressSpacelike for AddressSpace { fn new(size: usize) -> Result { let pg_cnt = size.div_ceil(pfa::PAGE_SIZE); let begin = pfa::alloc_page(pg_cnt).ok_or(KernelError::OutOfMemory)?; + let end = begin.checked_add(pg_cnt * pfa::PAGE_SIZE).ok_or(KernelError::OutOfMemory)?; Ok(Self { begin, - size: pg_cnt * pfa::PAGE_SIZE, + end, }) } fn map(&mut self, region: vmm::Region) -> Result { - if region.start() + region.len() > self.size { - return Err(KernelError::OutOfMemory); - } - - if let Some(test) = NonZero::new(region.start()) { - test. - } - + // Do both checks in one statement. + let phys = self.virt_to_phys(region.start()).and_then(|phys| { + if phys > self.end { + None + } else { + Some(phys) + } + }).ok_or(KernelError::InvalidArgument)?; match region.backing { vmm::Backing::Anon(phys) => { unsafe { copy_nonoverlapping( - phys as *const u8, - (self.begin + region.start()) as *mut u8, + phys.as_mut_ptr::(), + phys.as_mut_ptr::(), region.len(), ) }; - Ok(self.begin + region.start()) }, vmm::Backing::Zeroed => { unsafe { core::ptr::write_bytes( - (self.begin + region.start()) as *mut u8, + phys.as_mut_ptr::(), 0, region.len(), ) }; - Ok(self.begin + region.start()) }, - vmm::Backing::Uninit => Ok(self.begin + region.start()), + vmm::Backing::Uninit => {}, } + + Ok(phys) } fn unmap(&mut self, _region: &vmm::Region) -> Result<(), KernelError> { @@ -70,15 +70,16 @@ impl vmm::AddressSpacelike for AddressSpace { } fn phys_to_virt(&self, addr: PhysAddr) -> Option { - addr.checked_sub(self.begin) + addr.checked_sub(self.begin.as_usize()).map(|phys| VirtAddr::new(phys.as_usize())) } fn virt_to_phys(&self, addr: VirtAddr) -> Option { - self.begin.checked_add(addr) + self.begin.checked_add(addr.as_usize()) } fn end(&self) -> VirtAddr { - self.size + // This should always succeed. + self.phys_to_virt(self.end).unwrap() } fn activate(&self) -> Result<(), KernelError> { diff --git a/src/sched.rs b/src/sched.rs index 9edca37..076b107 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -1,47 +1,80 @@ //! This module provides access to the scheduler. +mod dispch; pub mod rt; -//pub mod thread; +pub mod task; +pub mod thread; + +use core::ffi::c_void; use hal::Schedable; -use crate::types::{array::IndexMap, rbtree::RbTree, view::ViewMut}; +use crate::{ + sync::spinlock::SpinLocked, types::{ + array::IndexMap, + rbtree::RbTree, + traits::{Get, GetMut}, + view::ViewMut, + }, utils::KernelError +}; type ThreadMap = IndexMap; +type TaskMap = IndexMap; + +static SCHED: SpinLocked> = SpinLocked::new(Scheduler::new()); pub struct Scheduler { threads: ThreadMap, + tasks: TaskMap, + id_gen: usize, + rt_scheduler: rt::Scheduler, wakeup: RbTree, + current: thread::UId, last_tick: u64, + next_tick: u64, } impl Scheduler { - pub fn new() -> Self { + pub const fn new() -> Self { Self { threads: IndexMap::new(), + tasks: IndexMap::new(), + id_gen: 1, rt_scheduler: rt::Scheduler::new(), wakeup: RbTree::new(), + current: thread::IDLE_THREAD, last_tick: 0, + next_tick: 0, } } - pub fn enqueue(&mut self, thread: thread::Thread) { - let uid = thread.uid(); - let rt = thread.rt_server().is_some(); - self.threads.insert(&thread.uid(), thread); + fn land(&mut self, ctx: *mut c_void) -> Result<(), KernelError> { + // A thread must not disappear while it is running. + let current = self.threads.get_mut(self.current).ok_or(KernelError::InvalidArgument)?; + // The context pointer must not be bogus after a sched_enter. + current.save_ctx(ctx) + } + + pub fn enqueue(&mut self, uid: thread::UId) -> Result<(), KernelError> { + let thread = self.threads.get(uid).ok_or(KernelError::InvalidArgument)?; - if rt { - let mut view = ViewMut::>::new( - &mut self.threads, - ); + if thread.rt_server().is_some() { + let mut view = + ViewMut::>::new(&mut self.threads); self.rt_scheduler.enqueue(uid, &mut view); } + + Ok(()) } - pub fn do_sched(&mut self, now: u64, old: Option) -> Option { + pub fn do_sched( + &mut self, + now: u64, + old: Option, + ) -> Option<(*mut c_void, &mut task::Task)> { let dt = now - self.last_tick; self.last_tick = now; @@ -55,7 +88,14 @@ impl Scheduler { } let mut view = rt::ServerView::::new(&mut self.threads); - self.rt_scheduler.pick(now, &mut view) + let (new, budget) = self.rt_scheduler.pick(now, &mut view)?; + + let ctx = self.threads.get(new)?.ctx(); + let task = self.tasks.get_mut(self.threads.get(new)?.task_id())?; + + self.current = new; + self.next_tick = now + budget; + Some((ctx, task)) } pub fn dequeue(&mut self, uid: thread::UId) -> Option { @@ -65,57 +105,78 @@ impl Scheduler { self.threads.remove(&uid) } -} -/// Reschedule the tasks. -pub fn reschedule() { - hal::Machine::trigger_reschedule(); -} + pub fn create_task(&mut self, task: &task::Attributes) -> Result { + let uid = task::UId::new(self.id_gen).ok_or(KernelError::InvalidArgument)?; + self.id_gen += 1; -/* + self.tasks.insert(&uid, task::Task::new(uid, task)?); + Ok(uid) + } + pub fn create_thread(&mut self, task: task::UId, attrs: &thread::Attributes) -> Result { + let task = self.tasks.get_mut(task).ok_or(KernelError::InvalidArgument)?; + let thread = task.create_thread(self.id_gen, attrs)?; + let uid = thread.uid(); + self.id_gen += 1; + Ok(uid) + } +} +pub fn init(kaddr_space: mem::vmm::AddressSpace) { + let mut sched = SCHED.lock(); + let uid = task::KERNEL_TASK; + sched.tasks.insert(&uid, task::Task::from_addr_space(uid, kaddr_space)); +} -/// Create a new task. -/// -/// `desc` - The task descriptor. -/// `main_desc` - The main thread descriptor. -/// `main_timing` - The timing information for the main thread. -/// -/// Returns the task ID if the task was created successfully, or an error if the task could not be created. -pub fn create_task(desc: task::TaskDescriptor) -> Result { - enable_scheduler(false); - let res = scheduler::SCHEDULER.lock().create_task(desc); - enable_scheduler(true); +pub fn needs_reschedule(now: u64) -> bool { + let sched = SCHED.lock(); + now >= sched.next_tick +} - res +pub fn create_task(attrs: &task::Attributes) -> Result { + SCHED.lock().create_task(attrs) } -pub fn create_thread( - task_id: task::TaskId, - entry: extern "C" fn(), - fin: Option !>, - timing: thread::Timing, -) -> Result { - enable_scheduler(false); - let res = scheduler::SCHEDULER - .lock() - .create_thread(entry, fin, timing, task_id); - enable_scheduler(true); - - res +pub fn create_thread(task: task::UId, attrs: &thread::Attributes) -> Result { + SCHED.lock().create_thread(task, attrs) } -pub fn enable_scheduler(enable: bool) { - scheduler::set_enabled(enable); +/// Reschedule the tasks. +pub fn reschedule() { + hal::Machine::trigger_reschedule(); } -pub fn tick_scheduler() -> bool { - if !scheduler::enabled() { - return false; +/// cbindgen:ignore +/// cbindgen:no-export +#[unsafe(no_mangle)] +pub extern "C" fn sched_enter(ctx: *mut c_void) -> *mut c_void { + let mut sched = SCHED.lock(); + let mut broken = false; + let old = sched.current; + + if sched.land(ctx).is_err() { + if sched.current == thread::IDLE_THREAD { + BUG!("failed to land the idle thread. something is horribly broken."); + } + + // If we cannot reasonably land. We dequeue the thread. + sched.dequeue(old); + // TODO: Warn + sched.current = thread::IDLE_THREAD; + broken = true; } - scheduler::SCHEDULER.lock().tick() -} + let now = 0; - */ + if let Some((ctx, task)) = sched.do_sched(now, Some(old)) { + if task.id != old.owner() { + dispch::prepare(task); + } + ctx + } else if broken { + BUG!("failed to reschedule after a failed landing. something is horribly broken."); + } else { + ctx + } +} diff --git a/src/sched/dispch.rs b/src/sched/dispch.rs new file mode 100644 index 0000000..0f781b6 --- /dev/null +++ b/src/sched/dispch.rs @@ -0,0 +1,7 @@ +use super::task::Task; + +pub fn prepare(task: &mut Task) { + if task.id.is_kernel() { + // Change task priv. level in HAL. + } +} \ No newline at end of file diff --git a/src/sched/rt.rs b/src/sched/rt.rs index 95dfb62..b5a66b9 100644 --- a/src/sched/rt.rs +++ b/src/sched/rt.rs @@ -7,7 +7,7 @@ pub struct Scheduler { pub type ServerView<'a, const N: usize> = ViewMut<'a, thread::UId, thread::RtServer, ThreadMap>; impl Scheduler { - pub fn new() -> Self { + pub const fn new() -> Self { Self { edf: RbTree::new(), } @@ -23,7 +23,7 @@ impl Scheduler { } } - pub fn pick(&mut self, now: u64, storage: &mut ServerView) -> Option { + pub fn pick(&mut self, now: u64, storage: &mut ServerView) -> Option<(thread::UId, u64)> { let id = self.edf.min()?; if storage.get(id)?.budget() == 0 { @@ -33,7 +33,7 @@ impl Scheduler { } // Insert updated the min cache. - self.edf.min() + self.edf.min().and_then(|id| storage.get(id).map(|s| (id, s.budget()))) } pub fn dequeue(&mut self, uid: thread::UId, storage: &mut ServerView) { diff --git a/src/sched/task.rs b/src/sched/task.rs new file mode 100644 index 0000000..06bb8d9 --- /dev/null +++ b/src/sched/task.rs @@ -0,0 +1,129 @@ +//! This module provides the basic task and thread structures for the scheduler. +use core::num::NonZero; +use core::borrow::Borrow; + +use envparse::parse_env; +use hal::{Stack}; + +use hal::stack::{Stacklike}; + +use crate::sched::thread; +use crate::{mem, sched}; + +use crate::mem::vmm::{AddressSpacelike}; +use crate::types::traits::ToIndex; +use crate::utils::KernelError; + +pub struct Defaults { + pub stack_pages: usize, +} + +const DEFAULTS: Defaults = Defaults { + stack_pages: parse_env!("OSIRIS_STACKPAGES" as usize), +}; + +pub const KERNEL_TASK: UId = UId { uid: 0 }; + +/// Id of a task. This is unique across all tasks. +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] +pub struct UId { + uid: usize, +} + +impl UId { + pub fn new(uid: usize) -> Option { + if uid == 0 { + None + } else { + Some(Self { uid }) + } + } + + pub fn is_kernel(&self) -> bool { + self.uid == 0 + } +} + +impl ToIndex for UId { + fn to_index>(idx: Option) -> usize { + idx.as_ref().map_or(0, |uid| uid.borrow().uid) + } +} + +pub struct Attributes { + pub resrv_pgs: Option>, +} + +/// The struct representing a task. +pub struct Task { + /// The unique identifier of the task. + pub id: UId, + /// The counter for the thread ids. + tid_cntr: usize, + /// Sets up the memory for the task. + address_space: mem::vmm::AddressSpace, +} + +impl Task { + pub fn new(id: UId, attrs: &Attributes) -> Result { + // TODO: On MMU systems, the resrv_pgs attribute will be ignored, as memory will not be reserved. + let resrv_pgs = attrs.resrv_pgs.ok_or(KernelError::OutOfMemory)?; + let address_space = mem::vmm::AddressSpace::new(resrv_pgs.get())?; + + Ok(Self { + id, + address_space, + tid_cntr: 0, + }) + } + + pub fn from_addr_space(id: UId, address_space: mem::vmm::AddressSpace) -> Self { + Self { + id, + address_space, + tid_cntr: 0, + } + } + + fn allocate_tid(&mut self) -> sched::thread::Id { + let tid = self.tid_cntr; + self.tid_cntr += 1; + + sched::thread::Id::new(tid, self.id) + } + + fn allocate_stack( + &mut self, + attrs: &thread::Attributes, + ) -> Result { + let size = DEFAULTS.stack_pages * mem::pfa::PAGE_SIZE; + let start = self.address_space.end().saturating_sub(size); + let region = mem::vmm::Region::new( + start, + size, + mem::vmm::Backing::Uninit, + mem::vmm::Perms::Read | mem::vmm::Perms::Write, + ); + let pa = self.address_space.map(region)?; + + Ok(hal::stack::Descriptor { + top: pa + size, + size: NonZero::new(size).unwrap(), + entry: attrs.entry, + fin: attrs.fin, + }) + } + + pub fn create_thread( + &mut self, + uid: usize, + attrs: &thread::Attributes, + ) -> Result { + let stack = self.allocate_stack(attrs)?; + + let stack = unsafe { Stack::new(stack) }?; + let tid = self.allocate_tid(); + + Ok(sched::thread::Thread::new(tid.get_uid(uid), stack)) + } +} diff --git a/src/sched/thread.rs b/src/sched/thread.rs index f988b97..63c608a 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -2,22 +2,28 @@ use core::{borrow::Borrow, ffi::c_void}; -use hal::Stack; -use hal::stack::Stacklike; +use hal::{Stack, stack::EntryFn}; +use hal::stack::{FinFn, Stacklike}; use macros::TaggedLinks; -use crate::{types::{rbtree::{self, Compare}, traits::{Project, ToIndex}}, sched::task::TaskId, utils::KernelError}; +use crate::sched::task::{self, KERNEL_TASK}; +use crate::{types::{rbtree::{self, Compare}, traits::{Project, ToIndex}}, utils::KernelError}; + +pub const IDLE_THREAD: UId = UId { + uid: 0, + tid: Id { id: 0, owner: KERNEL_TASK }, +}; /// Id of a task. This is only unique within a Task. #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] pub struct Id { id: usize, - owner: TaskId, + owner: task::UId, } #[allow(dead_code)] impl Id { - pub fn new(id: usize, owner: TaskId) -> Self { + pub fn new(id: usize, owner: task::UId) -> Self { Self { id, owner } } @@ -25,7 +31,7 @@ impl Id { self.id } - pub fn owner(&self) -> TaskId { + pub fn owner(&self) -> task::UId { self.owner } @@ -38,7 +44,9 @@ impl Id { #[derive(Clone, Copy, Debug)] #[allow(dead_code)] pub struct UId { + /// A globally unique identifier for the thread. uid: usize, + /// The task-local identifier for the thread. tid: Id, } @@ -47,6 +55,10 @@ impl UId { pub fn tid(&self) -> Id { self.tid } + + pub fn owner(&self) -> task::UId { + self.tid.owner + } } impl PartialEq for UId { @@ -63,15 +75,6 @@ impl Into for UId { } } -impl Default for UId { - fn default() -> Self { - Self { - uid: 0, - tid: Id::new(0, TaskId::User(0)), - } - } -} - impl PartialOrd for UId { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -92,11 +95,6 @@ impl ToIndex for UId { // ------------------------------------------------------------------------- -pub struct Descriptor { - pub tid: Id, - pub stack: Stack, -} - /// The state of a thread. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[allow(dead_code)] @@ -188,6 +186,11 @@ pub struct WakupTree; #[derive(Debug, Clone, Copy)] pub struct RtTree; +pub struct Attributes { + pub entry: EntryFn, + pub fin: Option, +} + /// The struct representing a thread. #[derive(Debug, Clone, Copy)] #[derive(TaggedLinks)] @@ -210,7 +213,7 @@ impl Thread { /// `stack` - The stack of the thread. /// /// Returns a new thread. - fn new(uid: UId, stack: Stack) -> Self { + pub fn new(uid: UId, stack: Stack) -> Self { Self { state: State { run_state: RunState::Ready, @@ -243,6 +246,10 @@ impl Thread { pub fn uid(&self) -> UId { self.uid } + + pub fn task_id(&self) -> task::UId { + self.uid.tid().owner + } } impl Compare for Thread { diff --git a/src/sync/atomic.rs b/src/sync/atomic.rs index 487125c..dec9f98 100644 --- a/src/sync/atomic.rs +++ b/src/sync/atomic.rs @@ -11,10 +11,10 @@ compile_error!( ); // ----------------------------AtomicU8---------------------------- -#[cfg(all(feature = "no-atomic-cas"))] +#[cfg(any(feature = "no-atomic-cas", not(target_has_atomic = "64")))] pub use core::sync::atomic::Ordering; -#[cfg(all(feature = "no-atomic-cas"))] +#[cfg(any(feature = "no-atomic-cas", not(target_has_atomic = "64")))] use core::cell::UnsafeCell; #[cfg(all(feature = "no-atomic-cas"))] @@ -106,3 +106,116 @@ impl AtomicBool { todo!("Implement atomic compare_exchange for bool"); } } + +// ----------------------------AtomicU64---------------------------- +#[allow(unused_imports)] +#[cfg(target_has_atomic = "64")] +pub use core::sync::atomic::AtomicU64; + +#[cfg(not(target_has_atomic = "64"))] +/// An atomic `u64` implemented by disabling interrupts around each operation. +pub struct AtomicU64 { + value: UnsafeCell, +} + +#[cfg(not(target_has_atomic = "64"))] +unsafe impl Sync for AtomicU64 {} + +#[cfg(not(target_has_atomic = "64"))] +impl AtomicU64 { + /// Creates a new atomic u64. + pub const fn new(value: u64) -> Self { + Self { + value: UnsafeCell::new(value), + } + } + + #[inline(always)] + fn with_interrupts_disabled(f: impl FnOnce() -> T) -> T { + let were_enabled = hal::asm::are_interrupts_enabled(); + if were_enabled { + hal::asm::disable_interrupts(); + } + + let result = f(); + + if were_enabled { + hal::asm::enable_interrupts(); + } + + result + } + + /// Loads the value. + pub fn load(&self, _: Ordering) -> u64 { + Self::with_interrupts_disabled(|| { + // SAFETY: Interrupts are disabled, so this read is exclusive with writes. + unsafe { *self.value.get() } + }) + } + + /// Stores a value. + pub fn store(&self, value: u64, _: Ordering) { + Self::with_interrupts_disabled(|| { + // SAFETY: Interrupts are disabled, so this write is exclusive with other access. + unsafe { + *self.value.get() = value; + } + }); + } + + /// Compares the value and exchanges it. + pub fn compare_exchange( + &self, + current: u64, + new: u64, + _: Ordering, + _: Ordering, + ) -> Result { + Self::with_interrupts_disabled(|| { + // SAFETY: Interrupts are disabled, so this read-modify-write is exclusive. + unsafe { + let value = self.value.get(); + if *value == current { + *value = new; + Ok(current) + } else { + Err(*value) + } + } + }) + } + + /// Fetches and adds, returning the previous value. + pub fn fetch_add(&self, value: u64, _: Ordering) -> u64 { + Self::with_interrupts_disabled(|| { + // SAFETY: Interrupts are disabled, so this read-modify-write is exclusive. + unsafe { + let ptr = self.value.get(); + let old = *ptr; + *ptr = old.wrapping_add(value); + old + } + }) + } + + /// Fetches a value, applies the function and writes it back atomically. + pub fn fetch_update(&self, _: Ordering, _: Ordering, mut f: F) -> Result + where + F: FnMut(u64) -> Option, + { + Self::with_interrupts_disabled(|| { + // SAFETY: Interrupts are disabled, so this read-modify-write is exclusive. + unsafe { + let ptr = self.value.get(); + let old = *ptr; + if let Some(new) = f(old) { + *ptr = new; + Ok(old) + } else { + Err(old) + } + } + }) + } +} diff --git a/src/time.rs b/src/time.rs index f8a2fc4..25c73eb 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,15 +1,13 @@ -use crate::{sched, sync::spinlock::SpinLocked}; -use hal::Schedable; +use core::sync::atomic::Ordering; + +use crate::sched; +use crate::sync::atomic::AtomicU64; // This variable is only allowed to be modified by the systick handler. -static TIME: SpinLocked = SpinLocked::new(0); +static TIME: AtomicU64 = AtomicU64::new(0); fn tick() { - // Increment the global time counter. - { - let mut time = TIME.lock(); - *time += 1; - } + TIME.fetch_add(1, Ordering::Release); } /* @@ -18,32 +16,16 @@ fn tick() { */ #[allow(dead_code)] pub fn time() -> u64 { - if !hal::asm::are_interrupts_enabled() { - // If interrupts are disabled, we can just read the time. - return *TIME.lock(); - } else { - let time; - // We need to disable interrupts to ensure that systick is always able to lock the time. - hal::asm::disable_interrupts(); - // Return the current time. - { - time = *TIME.lock(); - } - hal::asm::enable_interrupts(); - // Now systick can be called again. - time - } + TIME.load(Ordering::Acquire) } /// cbindgen:ignore /// cbindgen:no-export #[unsafe(no_mangle)] pub extern "C" fn systick_hndlr() { - tick(); - - let resched = { sched::tick_scheduler() }; + let time = TIME.fetch_add(1, Ordering::Release) + 1; - if resched { - hal::Machine::trigger_reschedule(); + if sched::needs_reschedule(time) { + sched::reschedule(); } } diff --git a/src/types/rbtree.rs b/src/types/rbtree.rs index ac64a17..969df19 100644 --- a/src/types/rbtree.rs +++ b/src/types/rbtree.rs @@ -51,7 +51,7 @@ enum Color { #[allow(dead_code)] impl RbTree { - pub fn new() -> Self { + pub const fn new() -> Self { Self { root: None, min: None, From f06e9c2df723f2c00e07323fdecdb47fcad6e003 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Wed, 25 Mar 2026 19:25:02 +0000 Subject: [PATCH 19/42] more scheduler rewrite --- presets/stm32l4r5zi_def.toml | 1 + src/idle.rs | 4 ++-- src/lib.rs | 11 +++++++++-- src/mem.rs | 7 ++++--- src/sched.rs | 2 +- src/uspace.rs | 10 +++++++--- 6 files changed, 24 insertions(+), 11 deletions(-) diff --git a/presets/stm32l4r5zi_def.toml b/presets/stm32l4r5zi_def.toml index c5f9166..9edf803 100644 --- a/presets/stm32l4r5zi_def.toml +++ b/presets/stm32l4r5zi_def.toml @@ -11,6 +11,7 @@ OSIRIS_DEBUG_RUNTIMESYMBOLS = "false" # Tuning parameters OSIRIS_TUNING_ENABLEFPU = "false" +OSIRIS_STACKPAGES = "4" OSIRIS_TUNING_APPSTACKSIZE = "2048" OSIRIS_TUNING_APPMEMSIZE = "8192" diff --git a/src/idle.rs b/src/idle.rs index c3b4060..aa62b17 100644 --- a/src/idle.rs +++ b/src/idle.rs @@ -2,13 +2,13 @@ use crate::sched; extern "C" fn entry() { loop { - hal::asm::wfi!(); + hal::asm::nop!(); } } pub fn init() { let attrs = sched::thread::Attributes { - entry: entry, + entry, fin: None, }; if let Err(e) = sched::create_thread(sched::task::KERNEL_TASK, &attrs) { diff --git a/src/lib.rs b/src/lib.rs index f273848..ac046f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,7 @@ include!(concat!(env!("OUT_DIR"), "/device_tree.rs")); /// The `boot_info` pointer must be valid and point to a properly initialized `BootInfo` structure. #[unsafe(no_mangle)] pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { - hal::asm::disable_interrupts!(); + hal::asm::disable_interrupts(); // Initialize basic hardware and the logging system. hal::Machine::init(); hal::Machine::bench_start(); @@ -51,9 +51,16 @@ pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { // Initialize the memory allocator. let kaddr_space = mem::init_memory(boot_info); + kprintln!("[Kernel] Memory initialized."); + sched::init(kaddr_space); + + kprintln!("[Kernel] Scheduler initialized."); + idle::init(); + kprintln!("[Kernel] Idle thread initialized."); + let (cyc, ns) = hal::Machine::bench_end(); kprintln!( "[Osiris] Kernel init took {} cycles taking {} ms", @@ -66,7 +73,7 @@ pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { panic!("[Kernel] Error: failed to start init application. Error: {e:?}"); } - hal::asm::enable_interrupts!(); + hal::asm::enable_interrupts(); loop { hal::asm::nop!(); diff --git a/src/mem.rs b/src/mem.rs index f45db97..530f6e5 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -1,5 +1,6 @@ //! This module provides access to the global memory allocator. +use crate::mem::pfa::PAGE_SIZE; use crate::mem::vmm::{AddressSpacelike, Backing, Perms, Region}; use crate::sync::spinlock::SpinLocked; use crate::{BootInfo, sched, utils}; @@ -47,17 +48,17 @@ pub fn init_memory(boot_info: &BootInfo) -> vmm::AddressSpace { // TODO: Configure. let pgs = 4; - let kaddr_space = vmm::AddressSpace::new(pgs).unwrap_or_else(|e| { + let mut kaddr_space = vmm::AddressSpace::new(pgs).unwrap_or_else(|e| { panic!("[Kernel] Error: failed to create kernel address space."); }); - let begin = kaddr_space.map(Region::new(VirtAddr::new(0), len, Backing::Zeroed, Perms::all())).unwrap_or_else(|e| { + let begin = kaddr_space.map(Region::new(VirtAddr::new(0), pgs * PAGE_SIZE, Backing::Zeroed, Perms::all())).unwrap_or_else(|e| { panic!("[Kernel] Error: failed to map kernel address space."); }); let mut allocator = GLOBAL_ALLOCATOR.lock(); - let range = begin.as_usize()..(begin.as_usize() + pgs * vmm::PAGE_SIZE); + let range = begin.as_usize()..(begin.as_usize() + pgs * PAGE_SIZE); if let Err(e) = unsafe { allocator.add_range(range) } { panic!("[Kernel] Error: failed to add range to allocator."); diff --git a/src/sched.rs b/src/sched.rs index 076b107..c9acae7 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -10,7 +10,7 @@ use core::ffi::c_void; use hal::Schedable; use crate::{ - sync::spinlock::SpinLocked, types::{ + mem, sync::spinlock::SpinLocked, types::{ array::IndexMap, rbtree::RbTree, traits::{Get, GetMut}, diff --git a/src/uspace.rs b/src/uspace.rs index c0d1d8e..14dcfd9 100644 --- a/src/uspace.rs +++ b/src/uspace.rs @@ -2,6 +2,8 @@ use ::core::mem::transmute; +use crate::sched; + pub fn init_app(boot_info: &crate::BootInfo) -> Result<(), crate::utils::KernelError> { let len = boot_info.args.init.len; @@ -15,8 +17,10 @@ pub fn init_app(boot_info: &crate::BootInfo) -> Result<(), crate::utils::KernelE ) }; - // We don't expect coming back from the init program. - // But for future user mode support the init program will be run by the scheduler, thus we leave a result as a return value here. - entry(); + let attrs = sched::thread::Attributes { + entry, + fin: None, + }; + sched::create_thread(sched::task::KERNEL_TASK, &attrs)?; Ok(()) } From 3f44481acbb7bd3ec8964b061a43a5800c0332f8 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:19:09 +0000 Subject: [PATCH 20/42] should work --- Cargo.toml | 1 + machine/api/src/lib.rs | 5 + machine/api/src/mem.rs | 27 +- machine/arm/common/CMakeLists.txt | 6 - machine/arm/common/crt0.S | 7 - machine/arm/src/lib.rs | 12 + machine/arm/stm32l4xx/CMakeLists.txt | 5 - machine/arm/stm32l4xx/interface/clock.c | 130 +++++ machine/arm/stm32l4xx/interface/export.h | 7 + machine/arm/stm32l4xx/interface/lib.c | 8 +- machine/arm/stm32l4xx/interface/sched.c | 3 +- machine/arm/stm32l4xx/link.ld | 18 +- machine/testing/src/lib.rs | 12 + macros/src/lib.rs | 2 +- macros/src/tree.rs | 43 +- options.toml | 2 +- presets/stm32l4r5zi_def.toml | 7 +- presets/testing_def.toml | 1 + src/idle.rs | 2 +- src/lib.rs | 27 +- src/macros.rs | 8 + src/mem.rs | 36 +- src/mem/alloc.rs | 583 +------------------ src/mem/alloc/bestfit.rs | 705 +++++++++++++++++++++++ src/mem/pfa/bitset.rs | 15 +- src/mem/vmm.rs | 23 +- src/mem/vmm/nommu.rs | 54 +- src/sched.rs | 128 ++-- src/sched/rr.rs | 48 ++ src/sched/task.rs | 14 +- src/sched/thread.rs | 8 + src/sync/atomic.rs | 46 +- src/time.rs | 30 +- src/types.rs | 1 + src/types/list.rs | 302 ++++++++++ src/uspace.rs | 5 +- src/utils.rs | 6 +- 37 files changed, 1552 insertions(+), 785 deletions(-) create mode 100644 machine/arm/stm32l4xx/interface/clock.c create mode 100644 src/mem/alloc/bestfit.rs create mode 100644 src/sched/rr.rs create mode 100644 src/types/list.rs diff --git a/Cargo.toml b/Cargo.toml index b6d40be..0cf5255 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = ["examples/*", "xtasks", "xtasks/crates/*"] +default-members = ["."] [workspace.dependencies] interface = { path = "interface" } diff --git a/machine/api/src/lib.rs b/machine/api/src/lib.rs index 4480adb..f4ebc52 100644 --- a/machine/api/src/lib.rs +++ b/machine/api/src/lib.rs @@ -47,6 +47,11 @@ pub trait Machinelike { fn bench_start(); fn bench_end() -> (u32, f32); + fn monotonic_now() -> u64; + fn monotonic_freq() -> u64; + // Returns the frequency of the machine's systick timer in Hz. + fn systick_freq() -> u64; + type ExcepBacktrace: Display; type ExcepStackFrame: Display; fn backtrace(initial_fp: *const usize, stack_ptr: *const usize) -> Self::ExcepBacktrace; diff --git a/machine/api/src/mem.rs b/machine/api/src/mem.rs index b919bfe..4aa98d6 100644 --- a/machine/api/src/mem.rs +++ b/machine/api/src/mem.rs @@ -1,7 +1,7 @@ -use core::ops::{Add, Sub, Div, Rem}; +use core::{fmt::Display, ops::{Add, Div, Rem, Sub}, ptr::NonNull}; #[repr(transparent)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] pub struct PhysAddr(usize); impl PhysAddr { @@ -32,6 +32,29 @@ impl PhysAddr { pub fn is_multiple_of(&self, align: usize) -> bool { self.0.is_multiple_of(align) } + + pub fn diff(&self, other: Self) -> usize { + if self.0 >= other.0 { + // Cannot underflow because of the check above. + self.0.checked_sub(other.0).unwrap() + } else { + // Cannot underflow because of the check above. + other.0.checked_sub(self.0).unwrap() + } + } +} + +impl From> for PhysAddr { + #[inline] + fn from(ptr: NonNull) -> Self { + Self(ptr.as_ptr() as usize) + } +} + +impl Display for PhysAddr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "0x{:x}", self.0) + } } impl Add for PhysAddr { diff --git a/machine/arm/common/CMakeLists.txt b/machine/arm/common/CMakeLists.txt index c9111b7..f426b3a 100644 --- a/machine/arm/common/CMakeLists.txt +++ b/machine/arm/common/CMakeLists.txt @@ -28,14 +28,8 @@ foreach(var ${_cache_vars}) endif() endforeach() -# This sets up PIC for .data/.bss access by making all accesses relative to r9. -# r9 is initialized in crt0.S to point to the base of the .data section. -# We need this because the offset between .text and .data is not known at compile time. (relocatable) -add_compile_options(-msingle-pic-base -mpic-register=r9 -mno-pic-data-is-text-relative) - set_property(SOURCE ivt.S APPEND PROPERTY COMPILE_OPTIONS "-x" "assembler-with-cpp") set_property(SOURCE irq.S APPEND PROPERTY COMPILE_OPTIONS "-x" "assembler-with-cpp") -set_property(SOURCE crt0.S APPEND PROPERTY COMPILE_OPTIONS "-fno-pic") add_library(common STATIC ivt.S diff --git a/machine/arm/common/crt0.S b/machine/arm/common/crt0.S index 215c85e..e26deb6 100644 --- a/machine/arm/common/crt0.S +++ b/machine/arm/common/crt0.S @@ -10,13 +10,6 @@ .thumb_func .global bootstrap bootstrap: - /* - * Initialize r9 to point to the start of the .data section. - * This is needed because all .data/.bss accesses are relative to r9. - * We need this because the offset between .text and .data is not known at compile time (relocatable). - */ - ldr r9, =__data_start - @ Copy initialized data from flash to RAM. ldr r0, =__data ldr r1, =__data_start diff --git a/machine/arm/src/lib.rs b/machine/arm/src/lib.rs index 389d14b..31666f5 100644 --- a/machine/arm/src/lib.rs +++ b/machine/arm/src/lib.rs @@ -65,6 +65,18 @@ impl hal_api::Machinelike for ArmMachine { (cycles as u32, ns) } + fn monotonic_now() -> u64 { + unsafe { bindings::monotonic_now() } + } + + fn monotonic_freq() -> u64 { + unsafe { bindings::monotonic_freq() } + } + + fn systick_freq() -> u64 { + unsafe { bindings::systick_freq() } + } + type ExcepBacktrace = excep::ExcepBacktrace; type ExcepStackFrame = excep::ExcepStackFrame; diff --git a/machine/arm/stm32l4xx/CMakeLists.txt b/machine/arm/stm32l4xx/CMakeLists.txt index 569d51b..3e11634 100644 --- a/machine/arm/stm32l4xx/CMakeLists.txt +++ b/machine/arm/stm32l4xx/CMakeLists.txt @@ -30,11 +30,6 @@ foreach(var ${_cache_vars}) endif() endforeach() -# This sets up PIC for .data/.bss access by making all accesses relative to r9. -# r9 is initialized in crt0.S to point to the base of the .data section. -# We need this because the offset between .text and .data is not known at compile time. (relocatable) -add_compile_options(-msingle-pic-base -mpic-register=r9 -mno-pic-data-is-text-relative) - # this will compile our variant_stm32l4xx library add_subdirectory(${OSIRIS_ARM_STM32L4XX_VARIANT}) diff --git a/machine/arm/stm32l4xx/interface/clock.c b/machine/arm/stm32l4xx/interface/clock.c new file mode 100644 index 0000000..ed3acac --- /dev/null +++ b/machine/arm/stm32l4xx/interface/clock.c @@ -0,0 +1,130 @@ +#include "lib.h" +#include + +static volatile uint64_t monotonic_hi = 0; + +static void init_monotonic_timer(void) +{ + const uint32_t target_hz = 1000000U; + uint32_t tim_clk = HAL_RCC_GetPCLK1Freq(); + + monotonic_hi = 0; + + // If APB1 prescaler is not 1, timer clocks run at 2x PCLK1. + if ((RCC->CFGR & RCC_CFGR_PPRE1) != RCC_CFGR_PPRE1_DIV1) { + tim_clk *= 2U; + } + + const uint32_t prescaler = (tim_clk / target_hz) - 1U; + + __HAL_RCC_TIM2_CLK_ENABLE(); + __HAL_RCC_TIM2_FORCE_RESET(); + __HAL_RCC_TIM2_RELEASE_RESET(); + + HAL_NVIC_DisableIRQ(TIM2_IRQn); + NVIC_ClearPendingIRQ(TIM2_IRQn); + + // URS ensures update flags/interrupts are only from real overflows. + TIM2->CR1 = TIM_CR1_URS; + TIM2->PSC = prescaler; + TIM2->ARR = 0xFFFFFFFFU; + TIM2->CNT = 0; + TIM2->EGR = TIM_EGR_UG; + + // Clear pending flags and enable update interrupt for wrap extension. + TIM2->SR = 0; + TIM2->DIER = TIM_DIER_UIE; + + HAL_NVIC_SetPriority(TIM2_IRQn, 15, 0); + HAL_NVIC_EnableIRQ(TIM2_IRQn); + + TIM2->CR1 |= TIM_CR1_CEN; + + // Clear any latent startup update state before first read. + TIM2->SR = 0; + NVIC_ClearPendingIRQ(TIM2_IRQn); +} + +void tim2_hndlr(void) +{ + if ((TIM2->SR & TIM_SR_UIF) != 0U) { + TIM2->SR &= ~TIM_SR_UIF; + monotonic_hi += (1ULL << 32); + } +} + +void SystemClock_Config(void) +{ + RCC_OscInitTypeDef RCC_OscInitStruct = {0}; + RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; + + /* 80 MHz on STM32L4+ => Range 1 normal mode, not boost */ + __HAL_RCC_PWR_CLK_ENABLE(); + + if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK) { + while (1) {} + } + + /* HSI16 -> PLL -> 80 MHz SYSCLK */ + RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; + RCC_OscInitStruct.HSIState = RCC_HSI_ON; + RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; + + RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; + RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; + RCC_OscInitStruct.PLL.PLLM = 1; + RCC_OscInitStruct.PLL.PLLN = 10; + RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2; + RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7; // arbitrary unless you use PLLP + RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; // arbitrary unless you use PLLQ + + if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { + while (1) {} + } + + RCC_ClkInitStruct.ClockType = + RCC_CLOCKTYPE_SYSCLK | + RCC_CLOCKTYPE_HCLK | + RCC_CLOCKTYPE_PCLK1 | + RCC_CLOCKTYPE_PCLK2; + + RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; + RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; + RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; + RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; + + if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) { + while (1) {} + } + + SystemCoreClockUpdate(); + init_monotonic_timer(); +} + +unsigned long long monotonic_now(void) +{ + uint64_t hi_1; + uint64_t hi_2; + uint32_t lo; + uint32_t sr; + + // Retry if the overflow IRQ updates the high word while sampling. + do { + hi_1 = monotonic_hi; + lo = TIM2->CNT; + sr = TIM2->SR; + hi_2 = monotonic_hi; + } while (hi_1 != hi_2); + + // If overflow is pending but IRQ has not run yet, include that wrap. + if ((sr & TIM_SR_UIF) != 0U && lo < 0x80000000U) { + hi_1 += (1ULL << 32); + } + + return hi_1 | (uint64_t)lo; +} + +unsigned long long monotonic_freq(void) +{ + return 1000000ULL; +} \ No newline at end of file diff --git a/machine/arm/stm32l4xx/interface/export.h b/machine/arm/stm32l4xx/interface/export.h index ce3e398..9d5910b 100644 --- a/machine/arm/stm32l4xx/interface/export.h +++ b/machine/arm/stm32l4xx/interface/export.h @@ -1,6 +1,7 @@ #pragma once // lib.c +unsigned long long systick_freq(void); void init_hal(void); // uart.c @@ -16,3 +17,9 @@ void dwt_reset(void); long dwt_read(void); float dwt_read_ns(void); float dwt_cycles_to_ns(long cycles); + +// clock.c +void SystemClock_Config(void); + +unsigned long long monotonic_now(void); +unsigned long long monotonic_freq(void); diff --git a/machine/arm/stm32l4xx/interface/lib.c b/machine/arm/stm32l4xx/interface/lib.c index 188119c..3c7d723 100644 --- a/machine/arm/stm32l4xx/interface/lib.c +++ b/machine/arm/stm32l4xx/interface/lib.c @@ -15,11 +15,14 @@ static void enable_faults(void) { } static void init_systick(void) { - HAL_SYSTICK_Config(SystemCoreClock / - 10); // Configure SysTick to interrupt every 1 ms + HAL_SYSTICK_Config(SystemCoreClock / 1000); // Configure SysTick to interrupt every 1 ms HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK); } +unsigned long long systick_freq(void) { + return 1000; +} + void init_hal(void) { #if OSIRIS_TUNING_ENABLEFPU init_fpu(); @@ -28,6 +31,7 @@ void init_hal(void) { enable_faults(); + SystemClock_Config(); init_systick(); } diff --git a/machine/arm/stm32l4xx/interface/sched.c b/machine/arm/stm32l4xx/interface/sched.c index a301d50..8e90155 100644 --- a/machine/arm/stm32l4xx/interface/sched.c +++ b/machine/arm/stm32l4xx/interface/sched.c @@ -6,4 +6,5 @@ void reschedule(void) { SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk; // Trigger PendSV exception __ISB(); __DSB(); -} \ No newline at end of file +} + diff --git a/machine/arm/stm32l4xx/link.ld b/machine/arm/stm32l4xx/link.ld index c964b11..b3e0ee8 100644 --- a/machine/arm/stm32l4xx/link.ld +++ b/machine/arm/stm32l4xx/link.ld @@ -39,15 +39,6 @@ SECTIONS KEEP(*(.ivt.ext)); } > FLASH :text - .stack (NOLOAD) : - { - . = ALIGN(4); - __stack_start = .; - . = . + __stack_size; - . = ALIGN(4); - __stack_top = .; - } > RAM - .text : { *(.text .text.* .gnu.linkonce.t*) @@ -130,6 +121,15 @@ SECTIONS __bss_end = .; } > RAM :data + .stack (NOLOAD) : + { + . = ALIGN(4); + __stack_start = .; + . = . + __stack_size; + . = ALIGN(4); + __stack_top = .; + } > RAM + /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) } } diff --git a/machine/testing/src/lib.rs b/machine/testing/src/lib.rs index 4236f4f..7c8dc6a 100644 --- a/machine/testing/src/lib.rs +++ b/machine/testing/src/lib.rs @@ -26,6 +26,18 @@ impl hal_api::Machinelike for TestingMachine { (0, 0.0) } + fn monotonic_now() -> u64 { + 0 + } + + fn monotonic_freq() -> u64 { + 0 + } + + fn systick_freq() -> u64 { + 0 + } + type ExcepBacktrace = String; type ExcepStackFrame = String; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index c871c86..178322c 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -5,7 +5,7 @@ use proc_macro2::TokenStream; mod tree; -#[proc_macro_derive(TaggedLinks, attributes(rbtree))] +#[proc_macro_derive(TaggedLinks, attributes(rbtree, list))] pub fn derive_tagged_links(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = syn::parse_macro_input!(input as syn::DeriveInput); diff --git a/macros/src/tree.rs b/macros/src/tree.rs index d62d8f6..9a37950 100644 --- a/macros/src/tree.rs +++ b/macros/src/tree.rs @@ -23,9 +23,11 @@ pub fn derive_tagged_links(input: &DeriveInput) -> syn::Result) -> syn::Result { + let struct_ident = &input.ident; + let generics = &input.generics; + + let mut impls = Vec::new(); + + for field in fields { + let Some(field_ident) = field.ident.clone() else { continue }; + + if let (Some(tag_path), Some(idx_path)) = find_tagged(&field.attrs, "list")? { + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let impl_block = quote! { + impl #impl_generics crate::types::list::Linkable<#tag_path, #idx_path> for #struct_ident #ty_generics #where_clause { + #[inline] + fn links(&self) -> &crate::types::list::Links<#tag_path, #idx_path> { + &self.#field_ident + } + #[inline] + fn links_mut(&mut self) -> &mut crate::types::list::Links<#tag_path, #idx_path> { + &mut self.#field_ident + } + } + }; + + impls.push(impl_block); + } } Ok(quote! { #(#impls)* }) } -fn find_rbtree(attrs: &[syn::Attribute]) -> syn::Result<(Option, Option)> { +fn find_tagged(attrs: &[syn::Attribute], attr_name: &str) -> syn::Result<(Option, Option)> { for attr in attrs { - if !attr.path().is_ident("rbtree") { + if !attr.path().is_ident(attr_name) { continue; } diff --git a/options.toml b/options.toml index 7427cea..eb66692 100644 --- a/options.toml +++ b/options.toml @@ -30,7 +30,7 @@ default = false name = "Stack Pages" description = "Number of pages to allocate for the kernel stack." type = { type = "Integer", min = 1 } -default = 4 +default = 1 [tuning.appmemsize] name = "Application Memory Size" diff --git a/presets/stm32l4r5zi_def.toml b/presets/stm32l4r5zi_def.toml index 9edf803..e637fc4 100644 --- a/presets/stm32l4r5zi_def.toml +++ b/presets/stm32l4r5zi_def.toml @@ -11,7 +11,7 @@ OSIRIS_DEBUG_RUNTIMESYMBOLS = "false" # Tuning parameters OSIRIS_TUNING_ENABLEFPU = "false" -OSIRIS_STACKPAGES = "4" +OSIRIS_STACKPAGES = "1" OSIRIS_TUNING_APPSTACKSIZE = "2048" OSIRIS_TUNING_APPMEMSIZE = "8192" @@ -22,7 +22,4 @@ target = "thumbv7em-none-eabi" [target.'cfg(target_os = "none")'] rustflags = [ "-C", "link-arg=--entry=main", -] - -[target.thumbv7em-none-eabi] -rustflags = ["-C", "relocation-model=ropi-rwpi"] \ No newline at end of file +] \ No newline at end of file diff --git a/presets/testing_def.toml b/presets/testing_def.toml index 1ed804d..59b9c04 100644 --- a/presets/testing_def.toml +++ b/presets/testing_def.toml @@ -1,6 +1,7 @@ # This is the default configuration for running tests and verification. [env] +OSIRIS_STACKPAGES = "1" [build] target = "host-tuple" \ No newline at end of file diff --git a/src/idle.rs b/src/idle.rs index aa62b17..7c79b37 100644 --- a/src/idle.rs +++ b/src/idle.rs @@ -12,6 +12,6 @@ pub fn init() { fin: None, }; if let Err(e) = sched::create_thread(sched::task::KERNEL_TASK, &attrs) { - panic!("[Idle] Error: failed to create idle thread. Error: {e:?}"); + panic!("failed to create idle thread. Error: {e:?}"); } } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index ac046f3..f033cad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,13 +34,12 @@ include!(concat!(env!("OUT_DIR"), "/device_tree.rs")); /// The `boot_info` pointer must be valid and point to a properly initialized `BootInfo` structure. #[unsafe(no_mangle)] pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { - hal::asm::disable_interrupts(); // Initialize basic hardware and the logging system. hal::Machine::init(); hal::Machine::bench_start(); if boot_info.is_null() || !boot_info.is_aligned() { - panic!("[Kernel] Error: boot_info pointer is null or unaligned."); + panic!("boot_info pointer is null or unaligned."); } // Safety: We trust the bootloader to provide a valid boot_info structure. @@ -51,31 +50,29 @@ pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { // Initialize the memory allocator. let kaddr_space = mem::init_memory(boot_info); - kprintln!("[Kernel] Memory initialized."); + kprintln!("Memory initialized."); - sched::init(kaddr_space); + if let Err(e) = sched::init(kaddr_space) { + panic!("failed to initialize scheduler. Error: {e:?}"); + } - kprintln!("[Kernel] Scheduler initialized."); + kprintln!("Scheduler initialized."); idle::init(); - kprintln!("[Kernel] Idle thread initialized."); + kprintln!("Idle thread initialized."); let (cyc, ns) = hal::Machine::bench_end(); kprintln!( - "[Osiris] Kernel init took {} cycles taking {} ms", - cyc, - ns as u32 / 1000000 + "Kernel init took {} cycles.", cyc ); // Start the init application. if let Err(e) = uspace::init_app(boot_info) { - panic!("[Kernel] Error: failed to start init application. Error: {e:?}"); + panic!("failed to start init application. Error: {e:?}"); } + + sched::enable(); - hal::asm::enable_interrupts(); - - loop { - hal::asm::nop!(); - } + loop {} } diff --git a/src/macros.rs b/src/macros.rs index 7c3a2e0..9d9d06f 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -30,7 +30,15 @@ macro_rules! kprintln { ($($arg:tt)*) => ({ use core::fmt::Write; use $crate::print::Printer; + let mut printer = Printer; + const MICROS_PER_SEC: u64 = 1000000; + let hz = $crate::time::mono_freq(); + let secs = $crate::time::mono_now() / hz; + let rem = $crate::time::mono_now() % hz; + let frac = (rem * MICROS_PER_SEC) / hz; + + write!(&mut printer, "[{}.{:06}] ", secs, frac).unwrap(); printer.write_fmt(format_args!($($arg)*)).unwrap(); printer.write_str("\n").unwrap(); }); diff --git a/src/mem.rs b/src/mem.rs index 530f6e5..a01d32e 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -3,7 +3,7 @@ use crate::mem::pfa::PAGE_SIZE; use crate::mem::vmm::{AddressSpacelike, Backing, Perms, Region}; use crate::sync::spinlock::SpinLocked; -use crate::{BootInfo, sched, utils}; +use crate::BootInfo; use alloc::Allocator; use hal::mem::{PhysAddr, VirtAddr}; use core::ptr::NonNull; @@ -31,9 +31,13 @@ enum MemoryTypes { BadMemory = 5, } +unsafe extern "C" { + unsafe static __stack_top: u8; +} + /// The global memory allocator. -static GLOBAL_ALLOCATOR: SpinLocked = - SpinLocked::new(alloc::BestFitAllocator::new()); +static GLOBAL_ALLOCATOR: SpinLocked = + SpinLocked::new(alloc::bestfit::BestFitAllocator::new()); /// Initialize the memory allocator. /// @@ -41,27 +45,29 @@ static GLOBAL_ALLOCATOR: SpinLocked = /// /// Returns an error if the memory allocator could not be initialized. pub fn init_memory(boot_info: &BootInfo) -> vmm::AddressSpace { - if let Err(e) = pfa::init_pfa(PhysAddr::new(0x20000000)) { // TODO: Get this from the DeviceTree. - panic!("[Kernel] Error: failed to initialize PFA. Error: {e:?}"); + let stack_top = &raw const __stack_top as usize; + if let Err(e) = pfa::init_pfa(PhysAddr::new(stack_top)) { // TODO: Get this from the DeviceTree. + panic!("failed to initialize PFA. Error: {e:?}"); } // TODO: Configure. - let pgs = 4; + let pgs = 10; let mut kaddr_space = vmm::AddressSpace::new(pgs).unwrap_or_else(|e| { - panic!("[Kernel] Error: failed to create kernel address space."); + panic!("failed to create kernel address space. Error: {e:?}"); }); - let begin = kaddr_space.map(Region::new(VirtAddr::new(0), pgs * PAGE_SIZE, Backing::Zeroed, Perms::all())).unwrap_or_else(|e| { - panic!("[Kernel] Error: failed to map kernel address space."); + let begin = kaddr_space.map(Region::new(None, 2 * PAGE_SIZE, Backing::Zeroed, Perms::all())).unwrap_or_else(|e| { + panic!("failed to map kernel address space. Error: {e:?}"); }); - let mut allocator = GLOBAL_ALLOCATOR.lock(); - - let range = begin.as_usize()..(begin.as_usize() + pgs * PAGE_SIZE); + { + let mut allocator = GLOBAL_ALLOCATOR.lock(); - if let Err(e) = unsafe { allocator.add_range(range) } { - panic!("[Kernel] Error: failed to add range to allocator."); + let range = begin..(begin + pgs * PAGE_SIZE); + if let Err(e) = unsafe { allocator.add_range(&range) } { + panic!("failed to add range to allocator. Error: {e:?}"); + } } kaddr_space @@ -75,7 +81,7 @@ pub fn init_memory(boot_info: &BootInfo) -> vmm::AddressSpace { /// Returns a pointer to the allocated memory block if the allocation was successful, or `None` if the allocation failed. pub fn malloc(size: usize, align: usize) -> Option> { let mut allocator = GLOBAL_ALLOCATOR.lock(); - allocator.malloc(size, align).ok() + allocator.malloc(size, align, None).ok() } /// Free a memory block. diff --git a/src/mem/alloc.rs b/src/mem/alloc.rs index 415bccc..85d8830 100644 --- a/src/mem/alloc.rs +++ b/src/mem/alloc.rs @@ -1,9 +1,13 @@ //! This module provides a simple allocator. //! One implementation is the BestFitAllocator, which uses the best fit strategy. -use core::{ops::Range, ptr::NonNull}; +use core::ptr::NonNull; -use crate::{BUG_ON, utils}; +use hal::mem::PhysAddr; + +use crate::utils; + +pub mod bestfit; #[cfg(target_pointer_width = "64")] pub const MAX_ADDR: usize = 2_usize.pow(48); @@ -21,580 +25,7 @@ pub const MAX_ADDR: usize = usize::MAX; /// Each range added to the allocator must be valid for the whole lifetime of the allocator and must not overlap with any other range. /// The lifetime of any allocation is only valid as long as the allocator is valid. (A pointer must not be used after the allocator is dropped.) pub trait Allocator { - fn malloc(&mut self, size: usize, align: usize) -> Result, utils::KernelError>; + fn malloc(&mut self, size: usize, align: usize, request: Option) -> Result, utils::KernelError>; unsafe fn free(&mut self, ptr: NonNull, size: usize); } -/// The metadata that is before any block in the BestFitAllocator. -struct BestFitMeta { - /// The size of the block in bytes. - size: usize, - /// The pointer to the next free block. This is `None` if the block is allocated. - next: Option>, -} - -/// This is an allocator implementation that uses the best fit strategy. -/// That does mean, when we allocate a block, we try to find the smallest block that fits the requested size. -/// Blocks are stored in a singly linked list. The important part is that the linked list is stored in-line with the memory blocks. -/// This means that every block has a header that contains the size of the block and a pointer to the next block. -#[derive(Debug)] -pub struct BestFitAllocator { - /// Head of the free block list. - head: Option>, -} - -/// Implementation of the BestFitAllocator. -impl BestFitAllocator { - pub const MIN_RANGE_SIZE: usize = size_of::() + Self::align_up() + 1; - - /// Creates a new BestFitAllocator. - /// - /// Returns the new BestFitAllocator. - pub const fn new() -> Self { - Self { head: None } - } - - /// Adds a range of memory to the allocator. - /// - /// `range` - The range of memory to add. - /// - /// Returns `Ok(())` if the range was added successfully, otherwise an error. - /// - /// # Safety - /// - /// The range must be valid, 128bit aligned and must not overlapping with any other current or future range. - /// The range must also be at least as large as `MIN_RANGE_SIZE`. - /// Also the range must stay valid, for the whole lifetime of the allocator. Also the lifetime of any allocation is only valid as long as the allocator is valid. - pub unsafe fn add_range(&mut self, range: Range) -> Result<(), utils::KernelError> { - let ptr = range.start; - - // Check if the pointer is 128bit aligned. - if !ptr.is_multiple_of(align_of::()) { - return Err(utils::KernelError::InvalidAlign); - } - - if ptr == 0 { - return Err(utils::KernelError::InvalidAddress); - } - - debug_assert!(range.end > range.start); - debug_assert!(range.end - range.start > size_of::() + Self::align_up()); - debug_assert!(range.end <= isize::MAX as usize); - - // The user pointer is the pointer to the user memory. So we need to add the size of the meta data and possibly add padding. - let user_pointer = ptr + size_of::() + Self::align_up(); - - // Set the current head as the next block, so we can add the new block to the head. - let meta = BestFitMeta { - size: range.end - user_pointer, - next: self.head, - }; - - // Write the header to the memory. - unsafe { core::ptr::write(ptr as *mut BestFitMeta, meta) }; - - // Set the head to the new block. - self.head = Some(unsafe { NonNull::new_unchecked(ptr as *mut u8) }); - Ok(()) - } - - /// Calculates the padding required to align the block. Note: We only align to 128bit. - /// - /// Returns the padding in bytes. - const fn align_up() -> usize { - let meta = size_of::(); - let align = align_of::(); - // Calculate the padding required to align the block. - (align - (meta % align)) % align - } - - /// Selects the best fit block for the given size. - /// - /// `size` - The size of the block. - /// - /// Returns the control pointer to the block and the control pointer to the previous block. - fn select_block( - &mut self, - size: usize, - ) -> Result<(NonNull, Option>), utils::KernelError> { - let mut best_fit = Err(utils::KernelError::OutOfMemory); - let mut best_fit_size = usize::MAX; - - let mut current = self.head; - let mut prev = None; - - // Iterate over all blocks and find the best fit. - while let Some(ptr) = current { - // Get the metadata of the block. - let meta = unsafe { ptr.cast::().as_ref() }; - - // Check if the block is big enough and smaller than the current best fit. - if meta.size >= size && meta.size <= best_fit_size { - best_fit = Ok((ptr, prev)); - best_fit_size = meta.size; - } - - // Move to the next block. - prev = current; - current = meta.next; - } - - best_fit - } - - /// Calculates the user pointer from the control pointer. - /// - /// `ptr` - The control pointer. - /// - /// Returns the user pointer. - /// - /// # Safety - /// - /// The ptr must be a valid control pointer. Note: After the allocator which allocated the pointer is dropped, the control pointer is always considered invalid. - unsafe fn user_ptr(ptr: NonNull) -> NonNull { - debug_assert!( - (ptr.as_ptr() as usize) - <= isize::MAX as usize - size_of::() - Self::align_up() - ); - unsafe { ptr.byte_add(size_of::() + Self::align_up()) } - } - - /// Calculates the control pointer from the user pointer. - /// - /// `ptr` - The user pointer. - /// - /// Returns the control pointer. - /// - /// # Safety - /// - /// The ptr must be a valid user pointer. Note: After the allocator which allocated the pointer is dropped, the user pointer is always considered invalid. - unsafe fn control_ptr(ptr: NonNull) -> NonNull { - debug_assert!((ptr.as_ptr() as usize) > size_of::() + Self::align_up()); - unsafe { ptr.byte_sub(size_of::() + Self::align_up()) } - } -} - -/// Implementation of the Allocator trait for BestFitAllocator. -impl Allocator for BestFitAllocator { - /// Allocates a block of memory with the given size and alignment. Note: This function will always yield an invalid align for align > 128bit. - /// - /// `size` - The size of the block. - /// `align` - The alignment of the block. - /// - /// Returns the user pointer to the block if successful, otherwise an error. - fn malloc(&mut self, size: usize, align: usize) -> Result, utils::KernelError> { - // Check if the alignment is valid. - if align > align_of::() { - return Err(utils::KernelError::InvalidAlign); - } - - // Check if the size is valid. - if size == 0 { - return Err(utils::KernelError::InvalidSize); - } - - // For some cfg this warning is correct. But for others its not. - #[allow(clippy::absurd_extreme_comparisons)] - if size >= MAX_ADDR { - return Err(utils::KernelError::InvalidSize); - } - - // Align the size. - let aligned_size = super::align_up(size); - debug_assert!(aligned_size >= size); - debug_assert!(aligned_size <= isize::MAX as usize); - - // Find the best fit block. - let (split, block, prev) = match self.select_block(aligned_size) { - Ok((block, prev)) => { - // Get the metadata of the block. - let meta = unsafe { block.cast::().as_mut() }; - - // Calculate the amount of bytes until the beginning of the possibly next metadata. - let min = aligned_size.saturating_add(size_of::() + Self::align_up()); - - debug_assert!( - (block.as_ptr() as usize) - <= isize::MAX as usize - - meta.size - - size_of::() - - Self::align_up() - ); - - debug_assert!( - meta.size < isize::MAX as usize - size_of::() - Self::align_up() - ); - - // If the block is big enough to split. Then it also needs to be big enough to store the metadata + align of the next block. - if meta.size > min { - // Calculate the remaining size of the block and thus the next metadata. - let remaining_meta = BestFitMeta { - size: meta.size - min, - next: meta.next, - }; - - // Shrink the current block to the requested aligned_size + padding (which is not available to the user). - meta.size = aligned_size; - - // Calculate the pointer to the next metadata. - let ptr = unsafe { Self::user_ptr(block).byte_add(aligned_size) }; - - unsafe { - // Write the new metadata to the memory. - ptr.cast::().write(remaining_meta); - } - - // If there is a previous block, we insert the new block after it. Otherwise we set it as the new head. - if let Some(prev) = prev { - let prev_meta = unsafe { prev.cast::().as_mut() }; - prev_meta.next = Some(ptr); - } else { - self.head = Some(ptr); - } - - // The next block of an allocated block is always None. - meta.next = None; - - (true, block, prev) - } else { - (false, block, prev) - } - } - Err(_) => { - let (block, prev) = self.select_block(size)?; - (false, block, prev) - } - }; - - if !split { - // Get the metadata of the block. - let meta = unsafe { block.cast::().as_mut() }; - - if let Some(prev) = prev { - let prev_meta = unsafe { prev.cast::().as_mut() }; - // If there is a previous block, we remove the current block from the list. Ie. we set the next block of the previous block to the next block of the current block. - prev_meta.next = meta.next; - } else { - // If there is no previous block, we set the next block as the new head. - self.head = meta.next; - } - - // The next block of an allocated block is always None. - meta.next = None; - } - - // Return the user pointer. - Ok(unsafe { Self::user_ptr(block).cast() }) - } - - /// Frees a block of memory. - /// - /// `ptr` - The pointer to the block. - /// `size` - The size of the block. (This is used to check if the size of the block is correct.) - unsafe fn free(&mut self, ptr: NonNull, size: usize) { - let block = unsafe { Self::control_ptr(ptr.cast()) }; - let meta = unsafe { block.cast::().as_mut() }; - - // The next block of a free block is always the current head. We essentially insert the block at the beginning of the list. - meta.next = self.head; - - // Check if the size of the block is correct. - BUG_ON!(meta.size != super::align_up(size), "Invalid size in free()"); - - // Set the size of the block. - meta.size = size; - - // Set the block as the new head. - self.head = Some(block); - } -} - -// TESTING ------------------------------------------------------------------------------------------------------------ - -#[cfg(test)] -mod tests { - use super::*; - - fn verify_block(user_ptr: NonNull, size: usize, next: Option>) { - let control_ptr = unsafe { BestFitAllocator::control_ptr(user_ptr) }; - let meta = unsafe { control_ptr.cast::().as_ref() }; - - assert!(meta.size >= size); - assert_eq!(meta.next, next); - } - - fn verify_ptrs_not_overlaping(ptrs: &[(NonNull, usize)]) { - for (i, (ptr1, size1)) in ptrs.iter().enumerate() { - for (j, (ptr2, size2)) in ptrs.iter().enumerate() { - if i == j { - continue; - } - - let begin1 = ptr1.as_ptr() as usize; - let end1 = begin1 + size1; - let begin2 = ptr2.as_ptr() as usize; - let end2 = begin2 + size2; - - assert!(end1 <= begin2 || end2 <= begin1); - assert!(begin1 != begin2); - assert!(end1 != end2); - assert!(*size1 > 0); - assert!(*size2 > 0); - assert!(end1 > begin1); - assert!(end2 > begin2); - } - } - } - - fn alloc_range(length: usize) -> Range { - let alloc_range = std::alloc::Layout::from_size_align(length, align_of::()).unwrap(); - let ptr = unsafe { std::alloc::alloc(alloc_range) }; - ptr as usize..ptr as usize + length - } - - #[test] - fn allocate_one() { - let mut allocator = BestFitAllocator::new(); - - let range = alloc_range(4096); - unsafe { - allocator.add_range(range).unwrap(); - } - - let ptr = allocator.malloc(128, 1).unwrap(); - - verify_block(ptr, 128, None); - } - - #[test] - fn alloc_alot() { - let mut allocator = BestFitAllocator::new(); - const CNT: usize = 100; - const SIZE: usize = 128; - - let range = alloc_range(SIZE * CNT * 2); - unsafe { - allocator.add_range(range).unwrap(); - } - - let mut ptrs = Vec::new(); - for _ in 0..CNT { - let ptr = allocator.malloc(SIZE, 1).unwrap(); - verify_block(ptr, SIZE, None); - ptrs.push((ptr, SIZE)); - } - - verify_ptrs_not_overlaping(ptrs.as_slice()); - } - - #[test] - fn alloc_exact() { - let mut allocator = BestFitAllocator::new(); - const CNT: usize = 10; - const SIZE: usize = 128; - - let range = - alloc_range((SIZE + size_of::() + BestFitAllocator::align_up()) * CNT); - unsafe { - allocator.add_range(range).unwrap(); - } - - let mut ptrs = Vec::new(); - for _ in 0..CNT { - let ptr = allocator.malloc(SIZE, 1).unwrap(); - verify_block(ptr, SIZE, None); - ptrs.push((ptr, SIZE)); - } - - verify_ptrs_not_overlaping(ptrs.as_slice()); - } - - #[test] - fn alloc_oom() { - let mut allocator = BestFitAllocator::new(); - const CNT: usize = 10; - const SIZE: usize = 128; - - let range = - alloc_range((SIZE + size_of::() + BestFitAllocator::align_up()) * CNT - 1); - unsafe { - allocator.add_range(range).unwrap(); - } - - let mut ptrs = Vec::new(); - for _ in 0..CNT - 1 { - let ptr = allocator.malloc(SIZE, 1).unwrap(); - verify_block(ptr, SIZE, None); - ptrs.push(ptr); - } - - let ptr = allocator.malloc::(SIZE, 1); - assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); - } - - #[test] - fn alloc_no_oom_through_free() { - let mut allocator = BestFitAllocator::new(); - const SIZE: usize = 128; - - let range = alloc_range(SIZE + size_of::() + BestFitAllocator::align_up()); - unsafe { - allocator.add_range(range).unwrap(); - } - - let ptr = allocator.malloc(SIZE, 1).unwrap(); - verify_block(ptr, SIZE, None); - - unsafe { - allocator.free(ptr, SIZE); - } - - let ptr = allocator.malloc(SIZE, 1).unwrap(); - verify_block(ptr, SIZE, None); - } - - #[test] - fn multi_range_alloc() { - let mut allocator = BestFitAllocator::new(); - const CNT: usize = 10; - const SIZE: usize = 128; - - let mut ranges = Vec::new(); - for _ in 0..CNT { - let range = alloc_range(SIZE + size_of::() + BestFitAllocator::align_up()); - unsafe { - allocator.add_range(range.clone()).unwrap(); - } - ranges.push(range); - } - - let mut ptrs = Vec::new(); - for _ in 0..CNT { - let ptr = allocator.malloc(SIZE, 1).unwrap(); - verify_block(ptr, SIZE, None); - ptrs.push((ptr, SIZE)); - } - - verify_ptrs_not_overlaping(ptrs.as_slice()); - } - - #[test] - fn multi_range_no_oom_through_free() { - // This function allocates multiple ranges and then frees one of them randomly. And only then there is no oom. - let mut allocator = BestFitAllocator::new(); - - const CNT: usize = 10; - const SIZE: usize = 128; - - let mut ranges = Vec::new(); - for _ in 0..CNT { - let range = alloc_range(SIZE + size_of::() + BestFitAllocator::align_up()); - unsafe { - allocator.add_range(range.clone()).unwrap(); - } - ranges.push(range); - } - - let mut ptrs = Vec::new(); - - let ptr = allocator.malloc::(SIZE, 1).unwrap(); - - for _ in 0..CNT - 1 { - let ptr = allocator.malloc(SIZE, 1).unwrap(); - verify_block(ptr, SIZE, None); - ptrs.push((ptr, SIZE)); - } - - unsafe { - allocator.free(ptr, SIZE); - } - - let ptr = allocator.malloc(SIZE, 1).unwrap(); - ptrs.push((ptr, SIZE)); - - verify_ptrs_not_overlaping(ptrs.as_slice()); - } - - #[test] - fn multi_range_oom() { - // This function allocates multiple ranges and then frees one of them randomly. And only then there is no oom. - let mut allocator = BestFitAllocator::new(); - - const CNT: usize = 10; - const SIZE: usize = 128; - - let mut ranges = Vec::new(); - for _ in 0..CNT { - let range = alloc_range(SIZE + size_of::() + BestFitAllocator::align_up()); - unsafe { - allocator.add_range(range.clone()).unwrap(); - } - ranges.push(range); - } - - let mut ptrs = Vec::new(); - - for _ in 0..CNT { - let ptr = allocator.malloc(SIZE, 1).unwrap(); - verify_block(ptr, SIZE, None); - ptrs.push((ptr, SIZE)); - } - - let ptr = allocator.malloc::(SIZE, 1); - assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); - - verify_ptrs_not_overlaping(ptrs.as_slice()); - } -} - -// END TESTING -------------------------------------------------------------------------------------------------------- - -// VERIFICATION ------------------------------------------------------------------------------------------------------- -#[cfg(kani)] -mod verification { - use super::*; - use core::{alloc::Layout, ptr}; - - fn verify_block(user_ptr: NonNull, size: usize, next: Option>) { - let control_ptr = unsafe { BestFitAllocator::control_ptr(user_ptr) }; - let meta = unsafe { control_ptr.cast::().as_ref() }; - - assert!(meta.size >= size); - assert_eq!(meta.next, next); - } - - fn alloc_range(length: usize) -> Option> { - let alloc_range = std::alloc::Layout::from_size_align(length, align_of::()).unwrap(); - let ptr = unsafe { std::alloc::alloc(alloc_range) }; - - if ptr.is_null() || ((ptr as usize) >= isize::MAX as usize - length) { - None - } else { - Some(ptr as usize..ptr as usize + length) - } - } - - #[kani::proof] - #[kani::unwind(2)] - fn allocate_one() { - let mut allocator = BestFitAllocator::new(); - - let size: usize = kani::any(); - kani::assume(size < MAX_ADDR - size_of::() - BestFitAllocator::align_up()); - kani::assume(size > 0); - let larger_size: usize = kani::any_where(|&x| { - x > size + size_of::() + BestFitAllocator::align_up() && x < MAX_ADDR - }); - - if let Some(range) = alloc_range(larger_size) { - unsafe { - assert_eq!(allocator.add_range(range), Ok(())); - } - - let ptr = allocator.malloc(size, 1).unwrap(); - - verify_block(ptr, size, None); - } - } -} -// END VERIFICATION --------------------------------------------------------------------------------------------------- diff --git a/src/mem/alloc/bestfit.rs b/src/mem/alloc/bestfit.rs new file mode 100644 index 0000000..a14c251 --- /dev/null +++ b/src/mem/alloc/bestfit.rs @@ -0,0 +1,705 @@ +use core::{ops::Range, ptr::NonNull}; + +use hal::mem::PhysAddr; + +use crate::utils::{self, KernelError}; + +/// The metadata that is before any block in the BestFitAllocator. +struct BestFitMeta { + /// The size of the block in bytes. + size: usize, + /// The pointer to the next free block. This is `None` if the block is allocated. + next: Option>, +} + +/// This is an allocator implementation that uses the best fit strategy. +/// That does mean, when we allocate a block, we try to find the smallest block that fits the requested size. +/// Blocks are stored in a singly linked list. The important part is that the linked list is stored in-line with the memory blocks. +/// This means that every block has a header that contains the size of the block and a pointer to the next block. +#[derive(Debug)] +pub struct BestFitAllocator { + /// Head of the free block list. + head: Option>, +} + +/// Implementation of the BestFitAllocator. +impl BestFitAllocator { + pub const MIN_RANGE_SIZE: usize = size_of::() + Self::align_up() + 1; + + /// Creates a new BestFitAllocator. + /// + /// Returns the new BestFitAllocator. + pub const fn new() -> Self { + Self { head: None } + } + + /// Adds a range of memory to the allocator. + /// + /// `range` - The range of memory to add. + /// + /// Returns `Ok(())` if the range was added successfully, otherwise an error. + /// + /// # Safety + /// + /// The range must be valid, 128bit aligned and must not overlapping with any other current or future range. + /// The range must also be at least as large as `MIN_RANGE_SIZE`. + /// Also the range must stay valid, for the whole lifetime of the allocator. Also the lifetime of any allocation is only valid as long as the allocator is valid. + pub unsafe fn add_range(&mut self, range: &Range) -> Result<(), utils::KernelError> { + let ptr = range.start; + + // Check if the pointer is 128bit aligned. + if !ptr.is_multiple_of(align_of::()) { + return Err(utils::KernelError::InvalidAlign); + } + + debug_assert!(range.end > range.start); + debug_assert!(range.end.diff(range.start) > size_of::() + Self::align_up()); + debug_assert!(range.end.as_usize() <= isize::MAX as usize); + + // The user pointer is the pointer to the user memory. So we need to add the size of the meta data and possibly add padding. + let user_pointer = ptr + size_of::() + Self::align_up(); + + // Set the current head as the next block, so we can add the new block to the head. + let meta = BestFitMeta { + size: range.end.diff(user_pointer), + next: self.head, + }; + + // Write the header to the memory. + unsafe { core::ptr::write(ptr.as_mut_ptr::(), meta) }; + + // Set the head to the new block. + self.head = Some(unsafe { NonNull::new_unchecked(ptr.as_mut_ptr::()) }); + Ok(()) + } + + /// Calculates the padding required to align the block. Note: We only align to 128bit. + /// + /// Returns the padding in bytes. + const fn align_up() -> usize { + let meta = size_of::(); + let align = align_of::(); + // Calculate the padding required to align the block. + (align - (meta % align)) % align + } + + /// Selects the best fit block for the given size. + /// + /// `size` - The size of the block. + /// + /// Returns the control pointer to the block and the control pointer to the previous block. + fn select_block( + &mut self, + size: usize, + requested: Option, + ) -> Result<(NonNull, Option>), utils::KernelError> { + let mut best_fit = Err(utils::KernelError::OutOfMemory); + let mut best_fit_size = usize::MAX; + + let mut current = self.head; + let mut prev = None; + + if let Some(requested) = requested { + while let Some(ptr) = current { + // Get the metadata of the block. + let meta = unsafe { ptr.cast::().as_ref() }; + + if unsafe { Self::contains(meta, requested, size) } { + return Ok((ptr, prev)); + } + + // Move to the next block. + prev = current; + current = meta.next; + } + } + + // Iterate over all blocks and find the best fit. + while let Some(ptr) = current { + // Get the metadata of the block. + let meta = unsafe { ptr.cast::().as_ref() }; + + // Check if the block is big enough and smaller than the current best fit. + if meta.size >= size && meta.size <= best_fit_size { + best_fit = Ok((ptr, prev)); + best_fit_size = meta.size; + } + + // Move to the next block. + prev = current; + current = meta.next; + } + + best_fit + } + + /// Calculates the user pointer from the control pointer. + /// + /// `ptr` - The control pointer. + /// + /// Returns the user pointer. + /// + /// # Safety + /// + /// The ptr must be a valid control pointer. Note: After the allocator which allocated the pointer is dropped, the control pointer is always considered invalid. + unsafe fn user_ptr(ptr: NonNull) -> NonNull { + debug_assert!( + (ptr.as_ptr() as usize) + <= isize::MAX as usize - size_of::() - Self::align_up() + ); + unsafe { ptr.byte_add(size_of::() + Self::align_up()) } + } + + /// Calculates the control pointer from the user pointer. + /// + /// `ptr` - The user pointer. + /// + /// Returns the control pointer. + /// + /// # Safety + /// + /// The ptr must be a valid user pointer. Note: After the allocator which allocated the pointer is dropped, the user pointer is always considered invalid. + unsafe fn control_ptr(ptr: NonNull) -> NonNull { + debug_assert!((ptr.as_ptr() as usize) > size_of::() + Self::align_up()); + unsafe { ptr.byte_sub(size_of::() + Self::align_up()) } + } + + unsafe fn contains(meta: &BestFitMeta, target: PhysAddr, size: usize) -> bool { + let begin = unsafe { Self::user_ptr(NonNull::new_unchecked(meta as *const BestFitMeta as *mut u8)) }; + debug_assert!(size > 0); + + if target >= begin.into() { + if let Some(target) = target.checked_add(size) { + if target > (unsafe { begin.add(meta.size) }).into() { + return false; + } + } else { + return false; + } + return true; + } + false + } +} + +/// Implementation of the Allocator trait for BestFitAllocator. +impl super::Allocator for BestFitAllocator { + /// Allocates a block of memory with the given size and alignment. Note: This function will always yield an invalid align for align > 128bit. + /// + /// `size` - The size of the block. + /// `align` - The alignment of the block. + /// + /// Returns the user pointer to the block if successful, otherwise an error. + fn malloc(&mut self, size: usize, align: usize, request: Option) -> Result, utils::KernelError> { + // Check if the alignment is valid. + if align > align_of::() { + return Err(utils::KernelError::InvalidAlign); + } + + if let Some(request) = request { + if !request.is_multiple_of(align) { + return Err(utils::KernelError::InvalidAlign); + } + } + + // Check if the size is valid. + if size == 0 { + return Err(utils::KernelError::InvalidSize); + } + + // For some cfg this warning is correct. But for others its not. + #[allow(clippy::absurd_extreme_comparisons)] + if size >= super::MAX_ADDR { + return Err(utils::KernelError::InvalidSize); + } + + // Align the size. + let aligned_size = super::super::align_up(size); + debug_assert!(aligned_size >= size); + debug_assert!(aligned_size <= isize::MAX as usize); + + // Find the best fit block. + let (split, block, prev) = match self.select_block(aligned_size, request) { + Ok((block, prev)) => { + // Get the metadata of the block. + let meta = unsafe { block.cast::().as_mut() }; + + // If we requested a specific address. The size must be extended by the offset from block start to the requested address. + let aligned_size = if let Some(request) = request { + aligned_size + request.diff(unsafe { Self::user_ptr(block) }.into()) + } else { + aligned_size + }; + + // Calculate the amount of bytes until the beginning of the possibly next metadata. + let min = aligned_size.saturating_add(size_of::() + Self::align_up()); + + debug_assert!( + (block.as_ptr() as usize) + <= isize::MAX as usize + - meta.size + - size_of::() + - Self::align_up() + ); + + debug_assert!( + meta.size < isize::MAX as usize - size_of::() - Self::align_up() + ); + + // If the block is big enough to split. Then it also needs to be big enough to store the metadata + align of the next block. + if meta.size > min { + // Calculate the remaining size of the block and thus the next metadata. + let remaining_meta = BestFitMeta { + size: meta.size - min, + next: meta.next, + }; + + // Shrink the current block to the requested aligned_size + padding (which is not available to the user). + meta.size = aligned_size; + + // Calculate the pointer to the next metadata. + let ptr = unsafe { Self::user_ptr(block).byte_add(aligned_size) }; + + unsafe { + // Write the new metadata to the memory. + ptr.cast::().write(remaining_meta); + } + + // If there is a previous block, we insert the new block after it. Otherwise we set it as the new head. + if let Some(prev) = prev { + let prev_meta = unsafe { prev.cast::().as_mut() }; + prev_meta.next = Some(ptr); + } else { + self.head = Some(ptr); + } + + // The next block of an allocated block is always None. + meta.next = None; + + (true, block, prev) + } else { + (false, block, prev) + } + } + Err(_) => { + let (block, prev) = self.select_block(size, request)?; + (false, block, prev) + } + }; + + if !split { + // Get the metadata of the block. + let meta = unsafe { block.cast::().as_mut() }; + + if let Some(prev) = prev { + let prev_meta = unsafe { prev.cast::().as_mut() }; + // If there is a previous block, we remove the current block from the list. Ie. we set the next block of the previous block to the next block of the current block. + prev_meta.next = meta.next; + } else { + // If there is no previous block, we set the next block as the new head. + self.head = meta.next; + } + + // The next block of an allocated block is always None. + meta.next = None; + } + + if let Some(request) = request { + debug_assert!(unsafe { Self::contains(block.cast::().as_ref(), request, size) }); + } + + // Return the user pointer. + Ok(unsafe { Self::user_ptr(block).cast() }) + } + + /// Frees a block of memory. + /// + /// `ptr` - The pointer to the block. + /// `size` - The size of the block. (This is used to check if the size of the block is correct.) + unsafe fn free(&mut self, ptr: NonNull, size: usize) { + let block = unsafe { Self::control_ptr(ptr.cast()) }; + let meta = unsafe { block.cast::().as_mut() }; + + // The next block of a free block is always the current head. We essentially insert the block at the beginning of the list. + meta.next = self.head; + + // Check if the size of the block is correct. + BUG_ON!(meta.size != super::super::align_up(size), "Invalid size in free()"); + + // Set the size of the block. + meta.size = size; + + // Set the block as the new head. + self.head = Some(block); + } +} + +// TESTING ------------------------------------------------------------------------------------------------------------ + +#[cfg(test)] +mod tests { + use super::*; + use super::super::*; + + fn verify_block(user_ptr: NonNull, size: usize, next: Option>) { + let control_ptr = unsafe { BestFitAllocator::control_ptr(user_ptr) }; + let meta = unsafe { control_ptr.cast::().as_ref() }; + + assert!(meta.size >= size); + assert_eq!(meta.next, next); + } + + fn verify_ptrs_not_overlaping(ptrs: &[(NonNull, usize)]) { + for (i, (ptr1, size1)) in ptrs.iter().enumerate() { + for (j, (ptr2, size2)) in ptrs.iter().enumerate() { + if i == j { + continue; + } + + let begin1 = ptr1.as_ptr() as usize; + let end1 = begin1 + size1; + let begin2 = ptr2.as_ptr() as usize; + let end2 = begin2 + size2; + + assert!(end1 <= begin2 || end2 <= begin1); + assert!(begin1 != begin2); + assert!(end1 != end2); + assert!(*size1 > 0); + assert!(*size2 > 0); + assert!(end1 > begin1); + assert!(end2 > begin2); + } + } + } + + fn alloc_range(length: usize) -> Range { + let alloc_range = std::alloc::Layout::from_size_align(length, align_of::()).unwrap(); + let ptr = unsafe { std::alloc::alloc(alloc_range) }; + PhysAddr::new(ptr as usize)..PhysAddr::new(ptr as usize + length) + } + + #[test] + fn allocate_one() { + let mut allocator = BestFitAllocator::new(); + + let range = alloc_range(4096); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let ptr = allocator.malloc(128, 1, None).unwrap(); + + verify_block(ptr, 128, None); + } + + #[test] + fn alloc_request() { + let mut allocator = BestFitAllocator::new(); + + let range = alloc_range(4096); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let request = range.start + 128; + let ptr = allocator.malloc::(128, 1, Some(request)).unwrap(); + + // Check that the returned pointer contains the requested address. + let meta = unsafe { BestFitAllocator::control_ptr(ptr).cast::().as_ref() }; + assert!(unsafe { BestFitAllocator::contains(meta, request, 128) }); + } + + #[test] + fn alloc_request_to_big() { + let mut allocator = BestFitAllocator::new(); + + let range = alloc_range(4096); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let request = range.start + 4096; + let ptr = allocator.malloc::(128, 1, Some(request)); + + assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + } + + #[test] + fn alloc_request_not_aligned() { + let mut allocator = BestFitAllocator::new(); + + let range = alloc_range(4096); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let request = range.start + 127; + let ptr = allocator.malloc::(128, 8, Some(request)); + + assert!(ptr.is_err_and(|e| e == utils::KernelError::InvalidAlign)); + } + + #[test] + fn alloc_request_not_available() { + let mut allocator = BestFitAllocator::new(); + + let range = alloc_range(4096); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let request = range.start + 128; + let ptr = allocator.malloc::(128, 1, Some(request)).unwrap(); + verify_block(ptr, 128, None); + + let ptr = allocator.malloc::(128, 1, Some(request)); + assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + } + + #[test] + fn alloc_request_out_of_range() { + let mut allocator = BestFitAllocator::new(); + + let range = alloc_range(4096); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let request = range.end + 128; + let ptr = allocator.malloc::(128, 1, Some(request)); + + assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + } + + #[test] + fn alloc_alot() { + let mut allocator = BestFitAllocator::new(); + const CNT: usize = 100; + const SIZE: usize = 128; + + let range = alloc_range(SIZE * CNT * 2); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let mut ptrs = Vec::new(); + for _ in 0..CNT { + let ptr = allocator.malloc(SIZE, 1, None).unwrap(); + verify_block(ptr, SIZE, None); + ptrs.push((ptr, SIZE)); + } + + verify_ptrs_not_overlaping(ptrs.as_slice()); + } + + #[test] + fn alloc_exact() { + let mut allocator = BestFitAllocator::new(); + const CNT: usize = 10; + const SIZE: usize = 128; + + let range = + alloc_range((SIZE + size_of::() + BestFitAllocator::align_up()) * CNT); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let mut ptrs = Vec::new(); + for _ in 0..CNT { + let ptr = allocator.malloc(SIZE, 1, None).unwrap(); + verify_block(ptr, SIZE, None); + ptrs.push((ptr, SIZE)); + } + + verify_ptrs_not_overlaping(ptrs.as_slice()); + } + + #[test] + fn alloc_oom() { + let mut allocator = BestFitAllocator::new(); + const CNT: usize = 10; + const SIZE: usize = 128; + + let range = + alloc_range((SIZE + size_of::() + BestFitAllocator::align_up()) * CNT - 1); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let mut ptrs = Vec::new(); + for _ in 0..CNT - 1 { + let ptr = allocator.malloc(SIZE, 1, None).unwrap(); + verify_block(ptr, SIZE, None); + ptrs.push(ptr); + } + + let ptr = allocator.malloc::(SIZE, 1, None); + assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + } + + #[test] + fn alloc_no_oom_through_free() { + let mut allocator = BestFitAllocator::new(); + const SIZE: usize = 128; + + let range = alloc_range(SIZE + size_of::() + BestFitAllocator::align_up()); + unsafe { + allocator.add_range(&range).unwrap(); + } + + let ptr = allocator.malloc(SIZE, 1, None).unwrap(); + verify_block(ptr, SIZE, None); + + unsafe { + allocator.free(ptr, SIZE); + } + + let ptr = allocator.malloc(SIZE, 1, None).unwrap(); + verify_block(ptr, SIZE, None); + } + + #[test] + fn multi_range_alloc() { + let mut allocator = BestFitAllocator::new(); + const CNT: usize = 10; + const SIZE: usize = 128; + + let mut ranges = Vec::new(); + for _ in 0..CNT { + let range = alloc_range(SIZE + size_of::() + BestFitAllocator::align_up()); + unsafe { + allocator.add_range(&range).unwrap(); + } + ranges.push(range); + } + + let mut ptrs = Vec::new(); + for _ in 0..CNT { + let ptr = allocator.malloc(SIZE, 1, None).unwrap(); + verify_block(ptr, SIZE, None); + ptrs.push((ptr, SIZE)); + } + + verify_ptrs_not_overlaping(ptrs.as_slice()); + } + + #[test] + fn multi_range_no_oom_through_free() { + // This function allocates multiple ranges and then frees one of them randomly. And only then there is no oom. + let mut allocator = BestFitAllocator::new(); + + const CNT: usize = 10; + const SIZE: usize = 128; + + let mut ranges = Vec::new(); + for _ in 0..CNT { + let range = alloc_range(SIZE + size_of::() + BestFitAllocator::align_up()); + unsafe { + allocator.add_range(&range).unwrap(); + } + ranges.push(range); + } + + let mut ptrs = Vec::new(); + + let ptr = allocator.malloc::(SIZE, 1, None).unwrap(); + + for _ in 0..CNT - 1 { + let ptr = allocator.malloc(SIZE, 1, None).unwrap(); + verify_block(ptr, SIZE, None); + ptrs.push((ptr, SIZE)); + } + + unsafe { + allocator.free(ptr, SIZE); + } + + let ptr = allocator.malloc(SIZE, 1, None).unwrap(); + ptrs.push((ptr, SIZE)); + + verify_ptrs_not_overlaping(ptrs.as_slice()); + } + + #[test] + fn multi_range_oom() { + // This function allocates multiple ranges and then frees one of them randomly. And only then there is no oom. + let mut allocator = BestFitAllocator::new(); + + const CNT: usize = 10; + const SIZE: usize = 128; + + let mut ranges = Vec::new(); + for _ in 0..CNT { + let range = alloc_range(SIZE + size_of::() + BestFitAllocator::align_up()); + unsafe { + allocator.add_range(&range).unwrap(); + } + ranges.push(range); + } + + let mut ptrs = Vec::new(); + + for _ in 0..CNT { + let ptr = allocator.malloc(SIZE, 1, None).unwrap(); + verify_block(ptr, SIZE, None); + ptrs.push((ptr, SIZE)); + } + + let ptr = allocator.malloc::(SIZE, 1, None); + assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + + verify_ptrs_not_overlaping(ptrs.as_slice()); + } +} + +// END TESTING -------------------------------------------------------------------------------------------------------- + +// VERIFICATION ------------------------------------------------------------------------------------------------------- +#[cfg(kani)] +mod verification { + use super::*; + use core::{alloc::Layout, ptr}; + + fn verify_block(user_ptr: NonNull, size: usize, next: Option>) { + let control_ptr = unsafe { BestFitAllocator::control_ptr(user_ptr) }; + let meta = unsafe { control_ptr.cast::().as_ref() }; + + assert!(meta.size >= size); + assert_eq!(meta.next, next); + } + + fn alloc_range(length: usize) -> Option> { + let alloc_range = std::alloc::Layout::from_size_align(length, align_of::()).unwrap(); + let ptr = unsafe { std::alloc::alloc(alloc_range) }; + + if ptr.is_null() || ((ptr as usize) >= isize::MAX as usize - length) { + None + } else { + Some(ptr as usize..ptr as usize + length) + } + } + + #[kani::proof] + #[kani::unwind(2)] + fn allocate_one() { + let mut allocator = BestFitAllocator::new(); + + let size: usize = kani::any(); + kani::assume(size < MAX_ADDR - size_of::() - BestFitAllocator::align_up()); + kani::assume(size > 0); + let larger_size: usize = kani::any_where(|&x| { + x > size + size_of::() + BestFitAllocator::align_up() && x < MAX_ADDR + }); + + if let Some(range) = alloc_range(larger_size) { + unsafe { + assert_eq!(allocator.add_range(range), Ok(())); + } + + let ptr = allocator.malloc(size, 1, None).unwrap(); + + verify_block(ptr, size, None); + } + } +} +// END VERIFICATION --------------------------------------------------------------------------------------------------- diff --git a/src/mem/pfa/bitset.rs b/src/mem/pfa/bitset.rs index 23ef7f7..391ab9a 100644 --- a/src/mem/pfa/bitset.rs +++ b/src/mem/pfa/bitset.rs @@ -43,7 +43,16 @@ impl super::Allocator for Allocator { return Err(KernelError::InvalidAlign); } - let ptr = NonNull::new(addr.as_mut_ptr::()).ok_or(KernelError::InvalidAddress)?; + let ptr = NonNull::new(addr.as_mut_ptr::()).ok_or(KernelError::InvalidAddress(addr))?; + // Align this up to PAGE_SIZE + let begin = addr + size_of::(); + let begin = if begin.is_multiple_of(super::PAGE_SIZE) { + begin + } else { + PhysAddr::new((begin.as_usize() + super::PAGE_SIZE - 1) & !(super::PAGE_SIZE - 1)) + }; + // TODO: Subtract the needed pages from the available + unsafe { core::ptr::write(ptr.as_ptr(), Self::new(begin).ok_or(KernelError::InvalidAddress(begin))?) }; // Safety: Ptr is properly aligned and non-null. The validity of the memory at that address is valid by the call contract. Ok(Pin::new(unsafe { boxed::Box::from_raw(ptr) })) @@ -119,7 +128,7 @@ impl super::Allocator for Allocator { self.l1[idx] &= !((!0usize).unbounded_shl((Self::BITS_PER_WORD - rem) as u32) >> skip); if len <= rem { - return Some(PhysAddr::new(start)); + return Some(self.begin + (start * super::PAGE_SIZE)); } len -= rem; @@ -201,7 +210,7 @@ mod tests { let pre = allocator.l1.clone(); let addr = super::super::Allocator::alloc(&mut allocator, ALLOC_SIZE).unwrap(); - let idx = addr / super::super::PAGE_SIZE; + let idx = addr.as_usize() / super::super::PAGE_SIZE; // Check that the bits in returned addresses is all ones in pre. for i in 0..ALLOC_SIZE { diff --git a/src/mem/vmm.rs b/src/mem/vmm.rs index 1f8aba9..ee86228 100644 --- a/src/mem/vmm.rs +++ b/src/mem/vmm.rs @@ -1,5 +1,3 @@ -use core::ops::Range; - use hal::mem::{PhysAddr, VirtAddr}; use crate::{utils::KernelError}; @@ -26,30 +24,39 @@ pub enum Backing { #[derive(Clone)] pub struct Region { - range: Range, + start: Option, + len: usize, backing: Backing, perms: Perms, } impl Region { - pub fn new(start: VirtAddr, len: usize, backing: Backing, perms: Perms) -> Self { + /// Creates a new region. + /// + /// - `start` is the starting virtual address of the region. If `None`, the system will choose a suitable address. + /// - `len` is the length of the region in bytes. + /// - `backing` is the backing type of the region, which determines how the region is initialized and where its contents come from. + /// - `perms` is the permissions of the region, which determines how the region can be accessed. + /// + pub fn new(start: Option, len: usize, backing: Backing, perms: Perms) -> Self { Self { - range: start..start.saturating_add(len), + start, + len, backing, perms, } } pub fn start(&self) -> VirtAddr { - self.range.start + self.start.unwrap_or_else(|| VirtAddr::new(0)) } pub fn len(&self) -> usize { - self.range.end.saturating_sub(self.range.start.into()).into() + self.len } pub fn contains(&self, addr: VirtAddr) -> bool { - self.range.contains(&addr) + self.start().saturating_add(self.len()) > addr && addr >= self.start() } } diff --git a/src/mem/vmm/nommu.rs b/src/mem/vmm/nommu.rs index ff561b2..8927471 100644 --- a/src/mem/vmm/nommu.rs +++ b/src/mem/vmm/nommu.rs @@ -4,6 +4,7 @@ use hal::mem::{PhysAddr, VirtAddr}; use crate::{ mem::{ + alloc::{Allocator, bestfit}, pfa, vmm, }, utils::KernelError, @@ -12,53 +13,45 @@ use crate::{ pub struct AddressSpace { begin: PhysAddr, end: PhysAddr, + allocator: bestfit::BestFitAllocator, } impl vmm::AddressSpacelike for AddressSpace { - fn new(size: usize) -> Result { - let pg_cnt = size.div_ceil(pfa::PAGE_SIZE); - let begin = pfa::alloc_page(pg_cnt).ok_or(KernelError::OutOfMemory)?; - let end = begin.checked_add(pg_cnt * pfa::PAGE_SIZE).ok_or(KernelError::OutOfMemory)?; + fn new(pgs: usize) -> Result { + let begin = pfa::alloc_page(pgs).ok_or(KernelError::OutOfMemory)?; + let end = begin + .checked_add(pgs * pfa::PAGE_SIZE) + .ok_or(KernelError::OutOfMemory)?; + + let mut allocator = bestfit::BestFitAllocator::new(); + unsafe { allocator.add_range(&(begin..end))? }; Ok(Self { begin, end, + allocator, }) } fn map(&mut self, region: vmm::Region) -> Result { - // Do both checks in one statement. - let phys = self.virt_to_phys(region.start()).and_then(|phys| { - if phys > self.end { - None - } else { - Some(phys) - } - }).ok_or(KernelError::InvalidArgument)?; + let req = region.start.and_then(|virt| self.virt_to_phys(virt)); + // TODO: per page align + let align = core::mem::align_of::(); + let start = self.allocator.malloc::(region.len(), align, req)?; match region.backing { vmm::Backing::Anon(phys) => { unsafe { - copy_nonoverlapping( - phys.as_mut_ptr::(), - phys.as_mut_ptr::(), - region.len(), - ) + copy_nonoverlapping(phys.as_mut_ptr::(), start.as_ptr(), region.len()) }; - }, + } vmm::Backing::Zeroed => { - unsafe { - core::ptr::write_bytes( - phys.as_mut_ptr::(), - 0, - region.len(), - ) - }; - }, - vmm::Backing::Uninit => {}, + unsafe { core::ptr::write_bytes(start.as_ptr(), 0, region.len()) }; + } + vmm::Backing::Uninit => {} } - Ok(phys) + Ok(start.into()) } fn unmap(&mut self, _region: &vmm::Region) -> Result<(), KernelError> { @@ -70,11 +63,12 @@ impl vmm::AddressSpacelike for AddressSpace { } fn phys_to_virt(&self, addr: PhysAddr) -> Option { - addr.checked_sub(self.begin.as_usize()).map(|phys| VirtAddr::new(phys.as_usize())) + addr.checked_sub(self.begin.as_usize()) + .map(|phys| VirtAddr::new(phys.as_usize())) } fn virt_to_phys(&self, addr: VirtAddr) -> Option { - self.begin.checked_add(addr.as_usize()) + self.begin.checked_add(addr.as_usize()) } fn end(&self) -> VirtAddr { diff --git a/src/sched.rs b/src/sched.rs index c9acae7..510ff80 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -2,15 +2,16 @@ mod dispch; pub mod rt; +pub mod rr; pub mod task; pub mod thread; -use core::ffi::c_void; +use core::{ffi::c_void, sync::atomic::{AtomicBool, Ordering}}; use hal::Schedable; use crate::{ - mem, sync::spinlock::SpinLocked, types::{ + mem, sync::{atomic::AtomicU64, spinlock::SpinLocked}, time::{self, tick}, types::{ array::IndexMap, rbtree::RbTree, traits::{Get, GetMut}, @@ -23,18 +24,21 @@ type TaskMap = IndexMap; static SCHED: SpinLocked> = SpinLocked::new(Scheduler::new()); +static DISABLED: AtomicBool = AtomicBool::new(true); +static NEXT_TICK: AtomicU64 = AtomicU64::new(0); + pub struct Scheduler { threads: ThreadMap, tasks: TaskMap, id_gen: usize, rt_scheduler: rt::Scheduler, + rr_scheduler: rr::Scheduler, wakeup: RbTree, - current: thread::UId, + current: Option, last_tick: u64, - next_tick: u64, } impl Scheduler { @@ -44,18 +48,20 @@ impl Scheduler { tasks: IndexMap::new(), id_gen: 1, rt_scheduler: rt::Scheduler::new(), + rr_scheduler: rr::Scheduler::new(), wakeup: RbTree::new(), - current: thread::IDLE_THREAD, + current: None, last_tick: 0, - next_tick: 0, } } fn land(&mut self, ctx: *mut c_void) -> Result<(), KernelError> { - // A thread must not disappear while it is running. - let current = self.threads.get_mut(self.current).ok_or(KernelError::InvalidArgument)?; - // The context pointer must not be bogus after a sched_enter. - current.save_ctx(ctx) + if let Some(current) = self.current { + let thread = self.threads.get_mut(current).ok_or(KernelError::InvalidArgument)?; + return thread.save_ctx(ctx); + } + + Ok(()) } pub fn enqueue(&mut self, uid: thread::UId) -> Result<(), KernelError> { @@ -65,8 +71,12 @@ impl Scheduler { let mut view = ViewMut::>::new(&mut self.threads); self.rt_scheduler.enqueue(uid, &mut view); + } else { + self.rr_scheduler.enqueue(uid, &mut self.threads)?; } + // A new thread was added -> Trigger a reschedule. + NEXT_TICK.store(tick(), Ordering::Release); Ok(()) } @@ -82,19 +92,40 @@ impl Scheduler { let mut view = rt::ServerView::::new(&mut self.threads); // If this is not a real-time thread, this will just do nothing. self.rt_scheduler.put(old, dt, &mut view); + // If this is not a round-robin thread, this will just do nothing. + self.rr_scheduler.put(old, dt); // TODO: thread is still enqueued. Dequeue if blocked or sleeping and put to the respective tree/list. // If it exited remove it completely. } let mut view = rt::ServerView::::new(&mut self.threads); - let (new, budget) = self.rt_scheduler.pick(now, &mut view)?; + + let (new, budget) = if let Some((new, budget)) = self.rt_scheduler.pick(now, &mut view) { + (new, budget) + } else if let Some((new, budget)) = self.rr_scheduler.pick(&mut self.threads) { + (new, budget) + } else { + // No thread to run. Run the idle thread. + (thread::IDLE_THREAD, u64::MAX) + }; let ctx = self.threads.get(new)?.ctx(); let task = self.tasks.get_mut(self.threads.get(new)?.task_id())?; - self.current = new; - self.next_tick = now + budget; + self.current = Some(new); + + // Only store next_tick if now + budget is smaller than the current next tick. + let next_tick = now + budget; + let mut old_tick = NEXT_TICK.load(Ordering::Acquire); + + while NEXT_TICK.compare_exchange(old_tick, next_tick, Ordering::Release, Ordering::Acquire).is_err() { + old_tick = NEXT_TICK.load(Ordering::Acquire); + if next_tick >= old_tick { + break; + } + } + Some((ctx, task)) } @@ -110,7 +141,7 @@ impl Scheduler { let uid = task::UId::new(self.id_gen).ok_or(KernelError::InvalidArgument)?; self.id_gen += 1; - self.tasks.insert(&uid, task::Task::new(uid, task)?); + self.tasks.insert(&uid, task::Task::new(uid, task)?)?; Ok(uid) } @@ -118,20 +149,18 @@ impl Scheduler { let task = self.tasks.get_mut(task).ok_or(KernelError::InvalidArgument)?; let thread = task.create_thread(self.id_gen, attrs)?; let uid = thread.uid(); + + self.threads.insert(&uid, thread)?; + self.id_gen += 1; Ok(uid) } } -pub fn init(kaddr_space: mem::vmm::AddressSpace) { +pub fn init(kaddr_space: mem::vmm::AddressSpace) -> Result<(), KernelError> { let mut sched = SCHED.lock(); let uid = task::KERNEL_TASK; - sched.tasks.insert(&uid, task::Task::from_addr_space(uid, kaddr_space)); -} - -pub fn needs_reschedule(now: u64) -> bool { - let sched = SCHED.lock(); - now >= sched.next_tick + sched.tasks.insert(&uid, task::Task::from_addr_space(uid, kaddr_space)?) } pub fn create_task(attrs: &task::Attributes) -> Result { @@ -139,7 +168,30 @@ pub fn create_task(attrs: &task::Attributes) -> Result { } pub fn create_thread(task: task::UId, attrs: &thread::Attributes) -> Result { - SCHED.lock().create_thread(task, attrs) + let mut sched = SCHED.lock(); + sched.create_thread(task, attrs) +} + +pub fn enqueue(uid: thread::UId) -> Result<(), KernelError> { + SCHED.lock().enqueue(uid) +} + +pub fn needs_reschedule(now: u64) -> bool { + if DISABLED.load(Ordering::Acquire) { + return false; + } + + now >= NEXT_TICK.load(Ordering::Acquire) +} + +#[inline] +pub fn disable() { + DISABLED.store(true, Ordering::Release); +} + +#[inline] +pub fn enable() { + DISABLED.store(false, Ordering::Release); } /// Reschedule the tasks. @@ -156,23 +208,25 @@ pub extern "C" fn sched_enter(ctx: *mut c_void) -> *mut c_void { let old = sched.current; if sched.land(ctx).is_err() { - if sched.current == thread::IDLE_THREAD { - BUG!("failed to land the idle thread. something is horribly broken."); - } - - // If we cannot reasonably land. We dequeue the thread. - sched.dequeue(old); - // TODO: Warn - sched.current = thread::IDLE_THREAD; - broken = true; + sched.current.inspect(|uid| { + if *uid == thread::IDLE_THREAD { + BUG!("failed to land the idle thread. something is horribly broken."); + } + + // If we cannot reasonably land. We dequeue the thread. + sched.dequeue(*uid); + // TODO: Warn + sched.current = None; + broken = true; + }); } - let now = 0; - - if let Some((ctx, task)) = sched.do_sched(now, Some(old)) { - if task.id != old.owner() { - dispch::prepare(task); - } + if let Some((ctx, task)) = sched.do_sched(time::tick(), old) { + if let Some(old) = old + && task.id != old.owner() { + dispch::prepare(task); + } + ctx } else if broken { BUG!("failed to reschedule after a failed landing. something is horribly broken."); diff --git a/src/sched/rr.rs b/src/sched/rr.rs new file mode 100644 index 0000000..c4613f2 --- /dev/null +++ b/src/sched/rr.rs @@ -0,0 +1,48 @@ +use crate::{ + sched::{ + thread::{self}, + }, + types::{ + list::List, + }, +}; + +pub struct Scheduler { + queue: List, + + current: Option, + current_left: u64, + quantum: u64, +} + +impl Scheduler { + pub const fn new() -> Self { + // TODO: Make quantum configurable. + Self { queue: List::new(), current: None, current_left: 0, quantum: 1000 } + } + + pub fn enqueue(&mut self, uid: thread::UId, storage: &mut super::ThreadMap) -> Result<(), crate::utils::KernelError> { + self.queue.push_back(uid, storage).map_err(|_| crate::utils::KernelError::InvalidArgument) + } + + pub fn put(&mut self, uid: thread::UId, dt: u64) { + if let Some(current) = self.current { + if current == uid { + self.current_left = self.current_left.saturating_sub(dt); + } + } + } + + pub fn pick(&mut self, storage: &mut super::ThreadMap) -> Option<(thread::UId, u64)> { + if self.current_left == 0 { + if let Some(current) = self.current { + self.queue.push_back(current, storage); + } + + self.current = self.queue.pop_front(storage).ok().flatten(); + self.current_left = self.quantum; + } + + self.current.map(|id| (id, self.current_left)) + } +} diff --git a/src/sched/task.rs b/src/sched/task.rs index 06bb8d9..9527f81 100644 --- a/src/sched/task.rs +++ b/src/sched/task.rs @@ -69,7 +69,10 @@ impl Task { // TODO: On MMU systems, the resrv_pgs attribute will be ignored, as memory will not be reserved. let resrv_pgs = attrs.resrv_pgs.ok_or(KernelError::OutOfMemory)?; let address_space = mem::vmm::AddressSpace::new(resrv_pgs.get())?; + Self::from_addr_space(id, address_space) + } + pub fn from_addr_space(id: UId, address_space: mem::vmm::AddressSpace) -> Result { Ok(Self { id, address_space, @@ -77,14 +80,6 @@ impl Task { }) } - pub fn from_addr_space(id: UId, address_space: mem::vmm::AddressSpace) -> Self { - Self { - id, - address_space, - tid_cntr: 0, - } - } - fn allocate_tid(&mut self) -> sched::thread::Id { let tid = self.tid_cntr; self.tid_cntr += 1; @@ -97,9 +92,8 @@ impl Task { attrs: &thread::Attributes, ) -> Result { let size = DEFAULTS.stack_pages * mem::pfa::PAGE_SIZE; - let start = self.address_space.end().saturating_sub(size); let region = mem::vmm::Region::new( - start, + None, size, mem::vmm::Backing::Uninit, mem::vmm::Perms::Read | mem::vmm::Perms::Write, diff --git a/src/sched/thread.rs b/src/sched/thread.rs index 63c608a..66f4ec5 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -7,6 +7,7 @@ use hal::stack::{FinFn, Stacklike}; use macros::TaggedLinks; use crate::sched::task::{self, KERNEL_TASK}; +use crate::types::list; use crate::{types::{rbtree::{self, Compare}, traits::{Project, ToIndex}}, utils::KernelError}; pub const IDLE_THREAD: UId = UId { @@ -186,6 +187,9 @@ pub struct WakupTree; #[derive(Debug, Clone, Copy)] pub struct RtTree; +#[derive(Debug, Clone, Copy)] +pub struct RRList; + pub struct Attributes { pub entry: EntryFn, pub fin: Option, @@ -204,6 +208,9 @@ pub struct Thread { /// Wakup tree links for the thread. #[rbtree(tag = WakupTree, idx = UId)] _wakeup_links: rbtree::Links, + + #[list(tag = RRList, idx = UId)] + rr_links: list::Links, } #[allow(dead_code)] @@ -222,6 +229,7 @@ impl Thread { uid, rt_server: None, _wakeup_links: rbtree::Links::new(), + rr_links: list::Links::new(), } } diff --git a/src/sync/atomic.rs b/src/sync/atomic.rs index dec9f98..e3a4bde 100644 --- a/src/sync/atomic.rs +++ b/src/sync/atomic.rs @@ -10,10 +10,26 @@ compile_error!( "The `atomic-cas` feature requires the target to have atomic operations on at least 8-bit integers." ); -// ----------------------------AtomicU8---------------------------- -#[cfg(any(feature = "no-atomic-cas", not(target_has_atomic = "64")))] +#[allow(unused_imports)] pub use core::sync::atomic::Ordering; +#[inline(always)] +pub fn irq_free(f: impl FnOnce() -> T) -> T { + let enabled = hal::asm::are_interrupts_enabled(); + if enabled { + hal::asm::disable_interrupts(); + } + + let result = f(); + + if enabled { + hal::asm::enable_interrupts(); + } + + result +} + +// ----------------------------AtomicU8---------------------------- #[cfg(any(feature = "no-atomic-cas", not(target_has_atomic = "64")))] use core::cell::UnsafeCell; @@ -130,25 +146,9 @@ impl AtomicU64 { } } - #[inline(always)] - fn with_interrupts_disabled(f: impl FnOnce() -> T) -> T { - let were_enabled = hal::asm::are_interrupts_enabled(); - if were_enabled { - hal::asm::disable_interrupts(); - } - - let result = f(); - - if were_enabled { - hal::asm::enable_interrupts(); - } - - result - } - /// Loads the value. pub fn load(&self, _: Ordering) -> u64 { - Self::with_interrupts_disabled(|| { + irq_free(|| { // SAFETY: Interrupts are disabled, so this read is exclusive with writes. unsafe { *self.value.get() } }) @@ -156,7 +156,7 @@ impl AtomicU64 { /// Stores a value. pub fn store(&self, value: u64, _: Ordering) { - Self::with_interrupts_disabled(|| { + irq_free(|| { // SAFETY: Interrupts are disabled, so this write is exclusive with other access. unsafe { *self.value.get() = value; @@ -172,7 +172,7 @@ impl AtomicU64 { _: Ordering, _: Ordering, ) -> Result { - Self::with_interrupts_disabled(|| { + irq_free(|| { // SAFETY: Interrupts are disabled, so this read-modify-write is exclusive. unsafe { let value = self.value.get(); @@ -188,7 +188,7 @@ impl AtomicU64 { /// Fetches and adds, returning the previous value. pub fn fetch_add(&self, value: u64, _: Ordering) -> u64 { - Self::with_interrupts_disabled(|| { + irq_free(|| { // SAFETY: Interrupts are disabled, so this read-modify-write is exclusive. unsafe { let ptr = self.value.get(); @@ -204,7 +204,7 @@ impl AtomicU64 { where F: FnMut(u64) -> Option, { - Self::with_interrupts_disabled(|| { + irq_free(|| { // SAFETY: Interrupts are disabled, so this read-modify-write is exclusive. unsafe { let ptr = self.value.get(); diff --git a/src/time.rs b/src/time.rs index 25c73eb..44cc3a5 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,31 +1,29 @@ -use core::sync::atomic::Ordering; +use hal::Machinelike; -use crate::sched; -use crate::sync::atomic::AtomicU64; +use crate::{sched, sync}; -// This variable is only allowed to be modified by the systick handler. -static TIME: AtomicU64 = AtomicU64::new(0); +static TICKS: sync::atomic::AtomicU64 = sync::atomic::AtomicU64::new(0); -fn tick() { - TIME.fetch_add(1, Ordering::Release); +pub fn tick() -> u64 { + TICKS.load(sync::atomic::Ordering::Acquire) } -/* - * Returns the current time in milliseconds after boot. - * - */ -#[allow(dead_code)] -pub fn time() -> u64 { - TIME.load(Ordering::Acquire) +pub fn mono_now() -> u64 { + // TODO: This will break on SMP systems without native u64 atomic store. + sync::atomic::irq_free(|| hal::Machine::monotonic_now() ) +} + +pub fn mono_freq() -> u64 { + hal::Machine::monotonic_freq() } /// cbindgen:ignore /// cbindgen:no-export #[unsafe(no_mangle)] pub extern "C" fn systick_hndlr() { - let time = TIME.fetch_add(1, Ordering::Release) + 1; + let tick = TICKS.fetch_add(1, sync::atomic::Ordering::Release) + 1; - if sched::needs_reschedule(time) { + if sched::needs_reschedule(tick) { sched::reschedule(); } } diff --git a/src/types.rs b/src/types.rs index 26df432..746bf6d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -4,5 +4,6 @@ pub mod array; pub mod heap; pub mod pool; pub mod rbtree; +pub mod list; pub mod traits; pub mod view; \ No newline at end of file diff --git a/src/types/list.rs b/src/types/list.rs new file mode 100644 index 0000000..d3c9779 --- /dev/null +++ b/src/types/list.rs @@ -0,0 +1,302 @@ +use core::marker::PhantomData; + +use super::traits::{Get, GetMut}; + +#[allow(dead_code)] +pub struct List { + head: Option, + tail: Option, + len: usize, + _tag: PhantomData, +} + +#[allow(dead_code)] +pub trait Linkable { + fn links(&self) -> &Links; + fn links_mut(&mut self) -> &mut Links; +} + +#[allow(dead_code)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Links { + prev: Option, + next: Option, + _tag: PhantomData, +} + +#[allow(dead_code)] +impl Links { + pub const fn new() -> Self { + Self { + prev: None, + next: None, + _tag: PhantomData, + } + } +} + +#[allow(dead_code)] +impl List { + pub const fn new() -> Self { + Self { + head: None, + tail: None, + len: 0, + _tag: PhantomData, + } + } + + pub fn head(&self) -> Option { + self.head + } + + pub fn tail(&self) -> Option { + self.tail + } + + pub fn len(&self) -> usize { + self.len + } + + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + pub fn push_front + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + where + >::Output: Linkable, + { + self.detach_links(id, storage)?; + + match self.head { + Some(old_head) => { + let (new_node, old_head_node) = storage.get2_mut(id, old_head); + let (new_node, old_head_node) = (new_node.ok_or(())?, old_head_node.ok_or(())?); + + new_node.links_mut().prev = None; + new_node.links_mut().next = Some(old_head); + + old_head_node.links_mut().prev = Some(id); + } + None => { + let new_node = storage.get_mut(id).ok_or(())?; + new_node.links_mut().prev = None; + new_node.links_mut().next = None; + self.tail = Some(id); + } + } + + self.head = Some(id); + self.len += 1; + Ok(()) + } + + pub fn push_back + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + where + >::Output: Linkable, + { + self.detach_links(id, storage)?; + + match self.tail { + Some(old_tail) => { + let (new_node, old_tail_node) = storage.get2_mut(id, old_tail); + let (new_node, old_tail_node) = (new_node.ok_or(())?, old_tail_node.ok_or(())?); + + new_node.links_mut().next = None; + new_node.links_mut().prev = Some(old_tail); + + old_tail_node.links_mut().next = Some(id); + } + None => { + let new_node = storage.get_mut(id).ok_or(())?; + new_node.links_mut().next = None; + new_node.links_mut().prev = None; + self.head = Some(id); + } + } + + self.tail = Some(id); + self.len += 1; + Ok(()) + } + + pub fn pop_front + GetMut>(&mut self, storage: &mut S) -> Result, ()> + where + >::Output: Linkable, + { + let Some(id) = self.head else { + return Ok(None); + }; + + self.remove(id, storage)?; + Ok(Some(id)) + } + + pub fn pop_back + GetMut>(&mut self, storage: &mut S) -> Result, ()> + where + >::Output: Linkable, + { + let Some(id) = self.tail else { + return Ok(None); + }; + + self.remove(id, storage)?; + Ok(Some(id)) + } + + pub fn remove + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + where + >::Output: Linkable, + { + let (prev, next, linked) = { + let node = storage.get(id).ok_or(())?; + let links = node.links(); + let linked = self.head == Some(id) + || self.tail == Some(id) + || links.prev.is_some() + || links.next.is_some(); + (links.prev, links.next, linked) + }; + + if !linked { + return Err(()); + } + + if let Some(prev_id) = prev { + let prev_node = storage.get_mut(prev_id).ok_or(())?; + prev_node.links_mut().next = next; + } else { + self.head = next; + } + + if let Some(next_id) = next { + let next_node = storage.get_mut(next_id).ok_or(())?; + next_node.links_mut().prev = prev; + } else { + self.tail = prev; + } + + let node = storage.get_mut(id).ok_or(())?; + node.links_mut().prev = None; + node.links_mut().next = None; + + self.len = self.len.saturating_sub(1); + Ok(()) + } + + fn detach_links + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + where + >::Output: Linkable, + { + let node = storage.get_mut(id).ok_or(())?; + node.links_mut().prev = None; + node.links_mut().next = None; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use core::borrow::Borrow; + + use super::{Linkable, Links, List}; + use crate::types::{array::IndexMap, traits::{Get, ToIndex}}; + + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + struct Id(usize); + + impl ToIndex for Id { + fn to_index>(idx: Option) -> usize { + idx.as_ref().map_or(0, |k| k.borrow().0) + } + } + + #[derive(Clone, Copy)] + struct TestTag; + + struct Node { + links: Links, + } + + impl Node { + fn new() -> Self { + Self { + links: Links::new(), + } + } + } + + impl Linkable for Node { + fn links(&self) -> &Links { + &self.links + } + + fn links_mut(&mut self) -> &mut Links { + &mut self.links + } + } + + fn storage() -> IndexMap { + let mut map = IndexMap::new(); + for i in 0..4 { + map.insert(&Id(i), Node::new()).unwrap(); + } + map + } + + #[test] + fn push_front_and_remove() { + let mut s = storage(); + let mut list = List::::new(); + + list.push_front(Id(1), &mut s).unwrap(); + list.push_front(Id(2), &mut s).unwrap(); + list.push_front(Id(3), &mut s).unwrap(); + + assert_eq!(list.head(), Some(Id(3))); + assert_eq!(list.tail(), Some(Id(1))); + assert_eq!(list.len(), 3); + + list.remove(Id(2), &mut s).unwrap(); + assert_eq!(list.head(), Some(Id(3))); + assert_eq!(list.tail(), Some(Id(1))); + assert_eq!(list.len(), 2); + + let n3 = s.get(Id(3)).unwrap(); + let n1 = s.get(Id(1)).unwrap(); + assert_eq!(n3.links().next, Some(Id(1))); + assert_eq!(n1.links().prev, Some(Id(3))); + } + + #[test] + fn pop_back_ordered() { + let mut s = storage(); + let mut list = List::::new(); + + list.push_back(Id(1), &mut s).unwrap(); + list.push_back(Id(2), &mut s).unwrap(); + list.push_back(Id(3), &mut s).unwrap(); + + assert_eq!(list.pop_back(&mut s).unwrap(), Some(Id(3))); + assert_eq!(list.pop_back(&mut s).unwrap(), Some(Id(2))); + assert_eq!(list.pop_back(&mut s).unwrap(), Some(Id(1))); + assert_eq!(list.pop_back(&mut s).unwrap(), None); + assert!(list.is_empty()); + } + + #[test] + fn pop_front_ordered() { + let mut s = storage(); + let mut list = List::::new(); + + list.push_back(Id(1), &mut s).unwrap(); + list.push_back(Id(2), &mut s).unwrap(); + list.push_back(Id(3), &mut s).unwrap(); + + assert_eq!(list.pop_front(&mut s).unwrap(), Some(Id(1))); + assert_eq!(list.pop_front(&mut s).unwrap(), Some(Id(2))); + assert_eq!(list.pop_front(&mut s).unwrap(), Some(Id(3))); + assert_eq!(list.pop_front(&mut s).unwrap(), None); + assert!(list.is_empty()); + } +} \ No newline at end of file diff --git a/src/uspace.rs b/src/uspace.rs index 14dcfd9..febcf01 100644 --- a/src/uspace.rs +++ b/src/uspace.rs @@ -21,6 +21,7 @@ pub fn init_app(boot_info: &crate::BootInfo) -> Result<(), crate::utils::KernelE entry, fin: None, }; - sched::create_thread(sched::task::KERNEL_TASK, &attrs)?; - Ok(()) + let uid = sched::create_thread(sched::task::KERNEL_TASK, &attrs)?; + + sched::enqueue(uid) } diff --git a/src/utils.rs b/src/utils.rs index c77239a..0f1bd71 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -13,6 +13,8 @@ pub(crate) use core::convert::{identity as likely, identity as unlikely}; #[cfg(feature = "nightly")] pub(crate) use core::hint::{likely, unlikely}; +use hal::mem::PhysAddr; + /// This is a macro that is used to panic when a bug is detected. @@ -55,7 +57,7 @@ pub enum KernelError { /// The kernel is out of memory. OutOfMemory, InvalidSize, - InvalidAddress, + InvalidAddress(PhysAddr), InvalidArgument, HalError(hal::Error), CustomError(&'static str), @@ -68,7 +70,7 @@ impl Debug for KernelError { KernelError::InvalidAlign => write!(f, "Invalid alignment"), KernelError::OutOfMemory => write!(f, "Out of memory"), KernelError::InvalidSize => write!(f, "Invalid size"), - KernelError::InvalidAddress => write!(f, "Invalid address"), + KernelError::InvalidAddress(addr) => write!(f, "Invalid address ({})", addr), KernelError::InvalidArgument => write!(f, "Invalid argument"), KernelError::HalError(e) => write!(f, "{e} (in HAL)"), KernelError::CustomError(msg) => write!(f, "{}", msg), From 0737bcc1f892d1c4ab3ca6a7da4703196c83bc0c Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Thu, 26 Mar 2026 20:25:29 +0000 Subject: [PATCH 21/42] make things more sane --- .cargo/config.toml | 18 ++++++-------- .gitignore | 1 + build.rs | 9 ------- machine/arm/Cargo.toml | 3 --- machine/arm/build.rs | 5 ++-- machine/select/Cargo.toml | 1 - machine/select/build.rs | 7 ------ presets/stm32l4r5zi_def.toml | 7 +----- xtasks/crates/config/src/file.rs | 9 +++++++ xtasks/crates/config/src/lib.rs | 40 ++++-------------------------- xtasks/crates/config/src/main.rs | 15 +++++------ xtasks/crates/injector/src/main.rs | 2 +- 12 files changed, 35 insertions(+), 82 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 0a1e750..dfc05b1 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,16 +1,12 @@ +include = [ + "../config.toml" +] [alias] xtask = "--config xtasks/.cargo/config.toml run -p xtask --release --" -[env] - -[build] -target = "host-tuple" - [target.'cfg(target_os = "none")'] -rustflags = ["-C", "link-arg=--entry=main",] - -[target] - -[target.thumbv7em-none-eabi] -rustflags = ["-C", "relocation-model=ropi-rwpi"] +rustflags = [ + "-C", "link-arg=--entry=main", + "-C", "link-arg=-Tlink.ld", +] \ No newline at end of file diff --git a/.gitignore b/.gitignore index aeeb6ba..5902a98 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ symbols.map compile_commands.json .cache/ *.img +config.toml diff --git a/build.rs b/build.rs index eb314c7..a1075cd 100644 --- a/build.rs +++ b/build.rs @@ -20,15 +20,6 @@ fn main() { generate_syscall_map("src/syscalls").expect("Failed to generate syscall map."); generate_syscalls_export("src/syscalls").expect("Failed to generate syscall exports."); - generate_device_tree().expect("Failed to generate device tree."); - - // Get linker script from environment variable - if let Ok(linker_script) = std::env::var("DEP_HAL_LINKER_SCRIPT") { - println!("cargo::rustc-link-arg=-T{linker_script}"); - } else { - println!("cargo::warning=LD_SCRIPT_PATH environment variable not set."); - } - cfg_aliases! { freestanding: { all(not(test), not(doctest), not(doc), not(kani), any(target_os = "none", target_os = "unknown")) }, } diff --git a/machine/arm/Cargo.toml b/machine/arm/Cargo.toml index 6aef529..9e5a441 100644 --- a/machine/arm/Cargo.toml +++ b/machine/arm/Cargo.toml @@ -5,9 +5,6 @@ rust-version = "1.85.0" authors = ["Thomas Wachter"] edition = "2024" build = "build.rs" -# Through this we can access env variables set by the build script through DEP_HAL_ -# It has nothing to do with native libraries. -links = "halarm" [lib] crate-type = ["rlib"] diff --git a/machine/arm/build.rs b/machine/arm/build.rs index 3ff8569..0a12b84 100644 --- a/machine/arm/build.rs +++ b/machine/arm/build.rs @@ -337,7 +337,8 @@ fn workspace_dir() -> PathBuf { /// /// Exits with error code 1 if any critical build step fails fn main() { - let out = env::var("OUT_DIR").unwrap_or("src".to_string()); + let out = env::var("OUT_DIR").unwrap(); + println!("cargo::rustc-link-search={out}"); let hal = fail_on_error(env::var("OSIRIS_ARM_HAL").with_context( || "OSIRIS_ARM_HAL environment variable not set. Please set it to the path of the ARM HAL.", @@ -363,7 +364,7 @@ fn main() { let libhal = libhal_config.build(); println!("cargo::rustc-link-search=native={}", libhal.display()); - println!("cargo::metadata=linker-script={out}/link.ld"); + println!("cargo::rerun-if-changed={out}/link.ld"); // Extract compile commands for HAL let hal_cc = build_dir.join("compile_commands.json"); diff --git a/machine/select/Cargo.toml b/machine/select/Cargo.toml index 4d068cc..1bb1694 100644 --- a/machine/select/Cargo.toml +++ b/machine/select/Cargo.toml @@ -2,7 +2,6 @@ name = "hal-select" version = "0.1.0" edition = "2024" -links = "hal" [dependencies] hal-api = { path = "../api" } diff --git a/machine/select/build.rs b/machine/select/build.rs index d7c04ac..78d1124 100644 --- a/machine/select/build.rs +++ b/machine/select/build.rs @@ -8,13 +8,6 @@ fn main() { } } - // Pass linker script to top level - if let Ok(linker_script) = std::env::var("DEP_HALARM_LINKER_SCRIPT") { - println!("cargo::metadata=linker-script={linker_script}"); - } else { - println!("cargo::warning=LD_SCRIPT_PATH environment variable not set."); - } - cfg_aliases! { freestanding: { all(not(test), not(doctest), not(doc), not(kani), any(target_os = "none", target_os = "unknown")) }, } diff --git a/presets/stm32l4r5zi_def.toml b/presets/stm32l4r5zi_def.toml index e637fc4..8751d75 100644 --- a/presets/stm32l4r5zi_def.toml +++ b/presets/stm32l4r5zi_def.toml @@ -17,9 +17,4 @@ OSIRIS_TUNING_APPSTACKSIZE = "2048" OSIRIS_TUNING_APPMEMSIZE = "8192" [build] -target = "thumbv7em-none-eabi" - -[target.'cfg(target_os = "none")'] -rustflags = [ - "-C", "link-arg=--entry=main", -] \ No newline at end of file +target = "thumbv7em-none-eabi" \ No newline at end of file diff --git a/xtasks/crates/config/src/file.rs b/xtasks/crates/config/src/file.rs index 0b97663..abc5270 100644 --- a/xtasks/crates/config/src/file.rs +++ b/xtasks/crates/config/src/file.rs @@ -20,6 +20,15 @@ pub fn load_file(path: &Path) -> Result { }) } +pub fn create_if_not_exists(path: &Path) -> Result<()> { + if !path.exists() { + std::fs::write(path, "") + .with_context(|| format!("failed to create file {}", path.display()))?; + } + + Ok(()) +} + pub fn load_files(root: &Path, filename: &str) -> Vec> { let mut files = Vec::new(); diff --git a/xtasks/crates/config/src/lib.rs b/xtasks/crates/config/src/lib.rs index 60172ee..65f431e 100644 --- a/xtasks/crates/config/src/lib.rs +++ b/xtasks/crates/config/src/lib.rs @@ -21,8 +21,7 @@ mod toml_patch; pub mod types; pub mod ui; -use anyhow::anyhow; -use toml_edit::{DocumentMut, ImDocument, Item, Table}; +use toml_edit::{DocumentMut, ImDocument}; pub fn load_config(root: &Path, filename: &str) -> ConfigNode { let files = file::load_files(root, filename); @@ -116,6 +115,7 @@ pub fn load_state<'node>( } pub fn load_toml_mut(toml: &Path) -> Result { + file::create_if_not_exists(&toml)?; let File { path, content } = file::load_file(&toml)?; let path = path.to_string_lossy(); @@ -151,41 +151,11 @@ pub fn load_toml(toml: &Path) -> Result, Error> { Ok(doc) } -#[rustversion::since(1.94)] -compile_error!("config-includes are stable since Rust 1.94; fix the TODOs below."); - pub fn apply_preset(config: &mut DocumentMut, preset: &ImDocument) -> Result<(), Error> { - for (key, value) in preset.iter() { - // We override with a depth of zero or one granularity. - - // TODO: Until we have config-includes stabilized, we skip alias sections. - if key == "alias" { - continue; - } - - match value { - Item::Table(src) => { - let dst = config.entry(key).or_insert(Item::Table(Table::new())); - - if let Item::Table(dst) = dst { - dst.clear(); + config.clear(); - for (key, value) in src.iter() { - dst.insert(key, value.clone()); - } - } else { - return Err(anyhow!( - "type mismatch when applying preset key '{}': expected table, found {}", - key, - dst.type_name() - ) - .into()); - } - } - _ => { - config.insert(key, value.clone()); - } - } + for (key, value) in preset.iter() { + config.insert(key, value.clone()); } Ok(()) diff --git a/xtasks/crates/config/src/main.rs b/xtasks/crates/config/src/main.rs index 85320c1..37ebaf6 100644 --- a/xtasks/crates/config/src/main.rs +++ b/xtasks/crates/config/src/main.rs @@ -58,7 +58,9 @@ pub fn main() { } fn ask_confirmation(prompt: &str) -> bool { - print!("{} (y/N): ", prompt); + print!("{}\n\n(y/N): ", + prompt + ); if let Err(_) = std::io::Write::flush(&mut std::io::stdout()) { return false; @@ -79,14 +81,13 @@ fn run_load_preset(preset_name: &str, no_confirm: bool, current_dir: &Path) -> R let preset_path = PathBuf::from("presets").join(format!("{preset_name}.toml")); let preset = config::load_toml(&preset_path)?; - let config_path = current_dir.join(".cargo/config.toml"); + let config_path = current_dir.join("config.toml"); let mut config = config::load_toml_mut(&config_path)?; // Ask for confirmation if !no_confirm - && !ask_confirmation(&format!( - "Are you sure you want to apply the preset '{preset_name}' to {}?\nThis will overwrite all existing configuration options.", + && !ask_confirmation(&format!("\nApply preset '{preset_name}' to '{}'?\nThis overwrites all existing configuration options.", config_path.display() )) { @@ -111,14 +112,14 @@ fn run_clean(no_confirm: bool, current_dir: &Path) -> Result<(), Error> { // Ask for confirmation if !no_confirm && !ask_confirmation( - "Are you sure you want to remove all configuration options from .cargo/config.toml?", + "Are you sure you want to remove all configuration options from config.toml?", ) { log::info!("Abort."); return Ok(()); } - let config_path = current_dir.join(".cargo/config.toml"); + let config_path = current_dir.join("config.toml"); let mut config = config::load_toml_mut(&config_path)?; @@ -142,7 +143,7 @@ fn run_clean(no_confirm: bool, current_dir: &Path) -> Result<(), Error> { } fn run_ui(current_dir: &Path) { - let config_path = current_dir.join(".cargo/config.toml"); + let config_path = current_dir.join("config.toml"); let node = config::load_config(¤t_dir, "options.toml"); diff --git a/xtasks/crates/injector/src/main.rs b/xtasks/crates/injector/src/main.rs index 973b8c7..3fafe36 100644 --- a/xtasks/crates/injector/src/main.rs +++ b/xtasks/crates/injector/src/main.rs @@ -104,7 +104,7 @@ fn inject(elf: &PathBuf) -> Result<(), String> { } fn get_target_from_cargo_config(manifest_dir: &PathBuf) -> Option { - let cargo_config = manifest_dir.join(".cargo").join("config.toml"); + let cargo_config = manifest_dir.join("config.toml"); if !cargo_config.exists() { return None; From b01b4635f7386de1d599bf1d4f8999ba4949702a Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Thu, 26 Mar 2026 21:28:03 +0000 Subject: [PATCH 22/42] sanify --- Cargo.lock | 11 - Cargo.toml | 5 +- examples/hello-world/Cargo.toml | 13 +- examples/hello-world/src/main.rs | 11 +- interface/Cargo.lock | 473 ----------------------------- interface/Cargo.toml | 12 - interface/build.rs | 45 --- interface/include/bindings.h | 76 ----- interface/src/lib.rs | 82 ----- justfile | 1 - machine/arm/common/CMakeLists.txt | 1 - machine/arm/common/crt0.S | 2 +- machine/arm/common/entry.c | 50 --- machine/arm/stm32l4xx/r5zi/lib.c | 30 -- macros/src/lib.rs | 27 ++ src/lib.rs | 31 +- src/main.rs | 20 +- src/mem.rs | 5 +- src/sched/thread.rs | 2 +- src/syscalls/file.rs | 4 +- src/uspace.rs | 23 +- xtasks/crates/pack/Cargo.toml | 1 - xtasks/crates/pack/src/bootinfo.rs | 67 ---- xtasks/crates/pack/src/main.rs | 1 - xtasks/crates/pack/src/pack.rs | 4 +- 25 files changed, 87 insertions(+), 910 deletions(-) delete mode 100644 interface/Cargo.lock delete mode 100644 interface/Cargo.toml delete mode 100644 interface/build.rs delete mode 100644 interface/include/bindings.h delete mode 100644 interface/src/lib.rs delete mode 100644 machine/arm/common/entry.c delete mode 100644 xtasks/crates/pack/src/bootinfo.rs diff --git a/Cargo.lock b/Cargo.lock index d1469e6..0b14c67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -831,15 +831,6 @@ dependencies = [ "syn", ] -[[package]] -name = "interface" -version = "0.1.0" -dependencies = [ - "bytemuck", - "cbindgen", - "cfg_aliases", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1097,7 +1088,6 @@ dependencies = [ "envparse", "hal-select", "hal-testing", - "interface", "kani", "macros", "quote", @@ -1116,7 +1106,6 @@ dependencies = [ "clap", "crc-fast", "elf", - "interface", "log", "logging", "tempfile", diff --git a/Cargo.toml b/Cargo.toml index 0cf5255..bca123c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,8 @@ members = ["examples/*", "xtasks", "xtasks/crates/*"] default-members = ["."] [workspace.dependencies] -interface = { path = "interface" } logging = { path = "xtasks/logging" } +osiris = { path = "." } [package] name = "osiris" @@ -20,8 +20,7 @@ path = "src/main.rs" [dependencies] hal = { package = "hal-select", path = "machine/select" } -macros = { path = "macros" } -interface = { path = "interface" } +proc_macros = { package = "macros", path = "macros" } envparse = "0.1.0" bitflags = "2.10.0" diff --git a/examples/hello-world/Cargo.toml b/examples/hello-world/Cargo.toml index 8f7248e..1bd3456 100644 --- a/examples/hello-world/Cargo.toml +++ b/examples/hello-world/Cargo.toml @@ -4,7 +4,18 @@ version = "0.1.0" edition = "2024" [dependencies] -osiris = { path = "../../" } +osiris = { workspace = true } [build-dependencies] cfg_aliases = "0.2.1" + +[profile.dev] +panic = "abort" +strip = false +opt-level = 2 + +[profile.release] +panic = "abort" +opt-level = "z" +codegen-units = 1 +lto = true diff --git a/examples/hello-world/src/main.rs b/examples/hello-world/src/main.rs index 351dd79..d78020d 100644 --- a/examples/hello-world/src/main.rs +++ b/examples/hello-world/src/main.rs @@ -1,13 +1,10 @@ #![no_std] #![no_main] -#[unsafe(no_mangle)] -extern "C" fn main() { - osiris::syscall_print(0, "Hello World!".as_bytes().as_ptr(), 12); -} +use osiris::app_main; -#[cfg(freestanding)] -#[panic_handler] -fn panic(_info: &core::panic::PanicInfo) -> ! { +#[app_main] +fn main() { + osiris::syscall_print(0, "Hello World!".as_bytes().as_ptr(), 12); loop {} } diff --git a/interface/Cargo.lock b/interface/Cargo.lock deleted file mode 100644 index adeff39..0000000 --- a/interface/Cargo.lock +++ /dev/null @@ -1,473 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys", -] - -[[package]] -name = "bitflags" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" - -[[package]] -name = "bytemuck" -version = "1.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "cbindgen" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" -dependencies = [ - "clap", - "heck", - "indexmap", - "log", - "proc-macro2", - "quote", - "serde", - "serde_json", - "syn", - "tempfile", - "toml", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "clap" -version = "4.5.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" -dependencies = [ - "clap_builder", -] - -[[package]] -name = "clap_builder" -version = "4.5.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_lex" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" - -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "indexmap" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "interface" -version = "0.1.0" -dependencies = [ - "bytemuck", - "cbindgen", - "cfg_aliases", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itoa" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" - -[[package]] -name = "libc" -version = "0.2.180" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "memchr" -version = "2.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "proc-macro2" -version = "1.0.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "rustix" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "syn" -version = "2.0.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" -dependencies = [ - "fastrand", - "getrandom", - "once_cell", - "rustix", - "windows-sys", -] - -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", - "winnow", -] - -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - -[[package]] -name = "unicode-ident" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "winnow" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "zmij" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" diff --git a/interface/Cargo.toml b/interface/Cargo.toml deleted file mode 100644 index a435764..0000000 --- a/interface/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "interface" -version = "0.1.0" -edition = "2024" - -[dependencies] -bytemuck = { version = "1.24.0", features = ["derive"] } - - -[build-dependencies] -cfg_aliases = "0.2.1" -cbindgen = "0.28.0" diff --git a/interface/build.rs b/interface/build.rs deleted file mode 100644 index 2dd23a2..0000000 --- a/interface/build.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::env; - -use cfg_aliases::cfg_aliases; - -fn main() { - cfg_aliases! { - freestanding: { all(not(test), not(doctest), not(doc), not(kani), any(target_os = "none", target_os = "unknown")) }, - } - - generate_c_api(); -} - -fn generate_c_api() { - let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - - let config: cbindgen::Config = cbindgen::Config { - no_includes: true, - includes: vec![ - "stdint.h".to_string(), - "stdbool.h".to_string(), - "stdarg.h".to_string(), - ], - layout: cbindgen::LayoutConfig { - packed: Some("__attribute__((packed))".to_string()), - ..Default::default() - }, - language: cbindgen::Language::C, - cpp_compat: false, - ..Default::default() - }; - - cbindgen::Builder::new() - .with_crate(crate_dir) - .with_config(config) - .generate() - .map_or_else( - |error| match error { - cbindgen::Error::ParseSyntaxError { .. } => {} - e => panic!("{e:?}"), - }, - |bindings| { - bindings.write_to_file("include/bindings.h"); - }, - ); -} diff --git a/interface/include/bindings.h b/interface/include/bindings.h deleted file mode 100644 index dd7b765..0000000 --- a/interface/include/bindings.h +++ /dev/null @@ -1,76 +0,0 @@ -#include "stdint.h" -#include "stdbool.h" -#include "stdarg.h" - -#define BOOT_INFO_MAGIC 221566477 - -/** - * The memory map entry type. - * - * This structure shall be compatible with the multiboot_memory_map_t struct at - * Link: [https://www.gnu.org/software/grub/manual/multiboot/multiboot.html]() - */ -typedef struct __attribute__((packed)) MemMapEntry { - /** - * The size of the entry. - */ - uint32_t size; - /** - * The base address of the memory region. - */ - uint64_t addr; - /** - * The length of the memory region. - */ - uint64_t length; - /** - * The type of the memory region. - */ - uint32_t ty; -} MemMapEntry; - -typedef struct InitDescriptor { - /** - * Pointer to the start of the binary of the init program. - */ - uint64_t begin; - /** - * Length of the binary of the init program. - */ - uint64_t len; - uint64_t entry_offset; -} InitDescriptor; - -typedef struct Args { - struct InitDescriptor init; -} Args; - -/** - * The boot information structure. - */ -typedef struct BootInfo { - /** - * The magic number that indicates valid boot information. - */ - uint32_t magic; - /** - * The version of the boot information structure. - */ - uint32_t version; - /** - * The implementer of the processor. - * The variant of the processor. - * The memory map. - */ - struct MemMapEntry mmap[8]; - /** - * The length of the memory map. - */ - uint64_t mmap_len; - /** - * The command line arguments. - */ - struct Args args; -} BootInfo; - -extern void kernel_init(const struct BootInfo *boot_info); diff --git a/interface/src/lib.rs b/interface/src/lib.rs deleted file mode 100644 index ad78f0a..0000000 --- a/interface/src/lib.rs +++ /dev/null @@ -1,82 +0,0 @@ -#![cfg_attr(freestanding, no_std)] - -/// The memory map entry type. -/// -/// This structure shall be compatible with the multiboot_memory_map_t struct at -/// Link: [https://www.gnu.org/software/grub/manual/multiboot/multiboot.html]() -#[repr(packed, C)] -#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] -pub struct MemMapEntry { - /// The size of the entry. - pub size: u32, - /// The base address of the memory region. - pub addr: u64, - /// The length of the memory region. - pub length: u64, - /// The type of the memory region. - pub ty: u32, -} - -#[cfg(kani)] -impl kani::Arbitrary for MemMapEntry { - fn any() -> Self { - let size: u32 = kani::any_where(|&x| x % size_of::() as u32 == 0); - let length = kani::any(); - let addr = kani::any(); - - kani::assume(addr > 0); - - MemMapEntry { - size, - addr, - length, - ty: kani::any(), - } - } - - fn any_array() -> [Self; MAX_ARRAY_LENGTH] { - [(); MAX_ARRAY_LENGTH].map(|_| Self::any()) - } -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] -pub struct InitDescriptor { - /// Pointer to the start of the binary of the init program. - pub begin: u64, - /// Length of the binary of the init program. - pub len: u64, - pub entry_offset: u64, -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] -pub struct Args { - pub init: InitDescriptor, -} - -pub const BOOT_INFO_MAGIC: u32 = 0xD34D60D; - -/// The boot information structure. -#[repr(C)] -#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] -pub struct BootInfo { - /// The magic number that indicates valid boot information. - pub magic: u32, - /// The version of the boot information structure. - pub version: u32, - /// The implementer of the processor. - //pub implementer: u64, - /// The variant of the processor. - //pub variant: u64, - /// The memory map. - pub mmap: [MemMapEntry; 8], - /// The length of the memory map. - pub mmap_len: u64, - /// The command line arguments. - pub args: Args, -} - -unsafe extern "C" { - pub fn kernel_init(boot_info: *const BootInfo) -> !; -} diff --git a/justfile b/justfile index c1d3dd4..b14c0c4 100644 --- a/justfile +++ b/justfile @@ -10,7 +10,6 @@ pack *args: example name *args: (build args) cargo build -p {{name}} {{args}} - cargo xtask pack --output {{name}}.bin --init examples/{{name}} {{args}} fmt *args: cargo fmt {{args}} diff --git a/machine/arm/common/CMakeLists.txt b/machine/arm/common/CMakeLists.txt index f426b3a..0346c0d 100644 --- a/machine/arm/common/CMakeLists.txt +++ b/machine/arm/common/CMakeLists.txt @@ -33,7 +33,6 @@ set_property(SOURCE irq.S APPEND PROPERTY COMPILE_OPTIONS "-x" "assembler-with-c add_library(common STATIC ivt.S - entry.c syscall.c irq.S crt0.S diff --git a/machine/arm/common/crt0.S b/machine/arm/common/crt0.S index e26deb6..ff93ecd 100644 --- a/machine/arm/common/crt0.S +++ b/machine/arm/common/crt0.S @@ -28,7 +28,7 @@ bootstrap: strlt r3, [r1], #4 blt 2b @ Call the pre_init function. - bl pre_init + bl kernel_init @ If main returns, loop forever. hang: b hang \ No newline at end of file diff --git a/machine/arm/common/entry.c b/machine/arm/common/entry.c deleted file mode 100644 index 52dad42..0000000 --- a/machine/arm/common/entry.c +++ /dev/null @@ -1,50 +0,0 @@ - -#include -#include "mem.h" - -#include - -typedef void (*func_t)(void); - -extern func_t __init_array_start; -extern func_t __init_array_end; -extern func_t __fini_array_start; -extern func_t __fini_array_end; - -extern void pre_init(void) __attribute__((noreturn)); -extern void init_mem_maps(BootInfo *boot_info); - -__attribute__((section(".bootinfo"), used, aligned(4))) -static BootInfo _boot_info = { - .magic = BOOT_INFO_MAGIC, - .version = 1, - .mmap = {0}, - .mmap_len = 0, - .args = {.init = {0}}, -}; - -void call_constructors(void) -{ - for (func_t *func = &__init_array_start; func < &__init_array_end; func++) - { - (*func)(); - } -} - -void call_destructors(void) -{ - for (func_t *func = &__fini_array_start; func < &__fini_array_end; func++) - { - (*func)(); - } -} - -void pre_init(void) -{ - // Init memory maps, etc. - init_mem_maps(&_boot_info); - - // Boot! - kernel_init(&_boot_info); - unreachable(); -} diff --git a/machine/arm/stm32l4xx/r5zi/lib.c b/machine/arm/stm32l4xx/r5zi/lib.c index 877422d..83c8135 100644 --- a/machine/arm/stm32l4xx/r5zi/lib.c +++ b/machine/arm/stm32l4xx/r5zi/lib.c @@ -1,6 +1,4 @@ #include - -#include #include /* @@ -197,31 +195,3 @@ const uintptr_t vector_table_ext[] __attribute__((section(".ivt.ext"))) = { (uintptr_t)&gfxmmu_hndlr, (uintptr_t)&dmamux1_ovr_hndlr, }; - -void init_mem_maps(BootInfo *boot_info) { - boot_info->mmap_len = 3; - - // SRAM1 - boot_info->mmap[0] = (MemMapEntry){ - .size = sizeof(MemMapEntry), - .addr = 0x20000000, - .length = 0x30000, - .ty = 1, - }; - - // SRAM2 - boot_info->mmap[1] = (MemMapEntry){ - .size = sizeof(MemMapEntry), - .addr = 0x20030000, - .length = 0x10000, - .ty = 1, - }; - - // SRAM3 - boot_info->mmap[2] = (MemMapEntry){ - .size = sizeof(MemMapEntry), - .addr = 0x20040000, - .length = 0x60000, - .ty = 1, - }; -} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 178322c..2387d03 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -15,6 +15,33 @@ pub fn derive_tagged_links(input: proc_macro::TokenStream) -> proc_macro::TokenS }.into() } +#[proc_macro_attribute] +pub fn app_main(input: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let item = syn::parse_macro_input!(item as syn::ItemFn); + let block = &item.block; + + let expanded = quote::quote! { + #[unsafe(no_mangle)] + #[unsafe(naked)] + extern "C" fn main() { + osiris::hal::asm::startup_trampoline!(); + } + + #[cfg(freestanding)] + #[panic_handler] + fn panic(info: &core::panic::PanicInfo) -> ! { + osiris::panic(info); + } + + #[unsafe(no_mangle)] + pub extern "C" fn app_main() -> () { + #block + } + }; + + expanded.into() +} + #[proc_macro_attribute] pub fn service( attr: proc_macro::TokenStream, diff --git a/src/lib.rs b/src/lib.rs index f033cad..39bab6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,10 +20,12 @@ pub mod time; pub mod uspace; use hal::Machinelike; -use interface::BootInfo; include!(concat!(env!("OUT_DIR"), "/syscalls_export.rs")); include!(concat!(env!("OUT_DIR"), "/device_tree.rs")); +pub use hal; +pub use proc_macros::app_main; + /// The kernel initialization function. /// /// `boot_info` - The boot information. @@ -33,22 +35,15 @@ include!(concat!(env!("OUT_DIR"), "/device_tree.rs")); /// This function must be called only once during the kernel startup. /// The `boot_info` pointer must be valid and point to a properly initialized `BootInfo` structure. #[unsafe(no_mangle)] -pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { +pub unsafe extern "C" fn kernel_init() -> ! { // Initialize basic hardware and the logging system. hal::Machine::init(); hal::Machine::bench_start(); - if boot_info.is_null() || !boot_info.is_aligned() { - panic!("boot_info pointer is null or unaligned."); - } - - // Safety: We trust the bootloader to provide a valid boot_info structure. - let boot_info = unsafe { &*boot_info }; - print::print_header(); // Initialize the memory allocator. - let kaddr_space = mem::init_memory(boot_info); + let kaddr_space = mem::init_memory(); kprintln!("Memory initialized."); @@ -68,7 +63,7 @@ pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { ); // Start the init application. - if let Err(e) = uspace::init_app(boot_info) { + if let Err(e) = uspace::init_app() { panic!("failed to start init application. Error: {e:?}"); } @@ -76,3 +71,17 @@ pub unsafe extern "C" fn kernel_init(boot_info: *const BootInfo) -> ! { loop {} } + +pub fn panic(info: &core::panic::PanicInfo) -> ! { + kprintln!("**************************** PANIC ****************************"); + kprintln!(""); + kprintln!("Message: {}", info.message()); + + if let Some(location) = info.location() { + kprintln!("Location: {}:{}", location.file(), location.line()); + } + + kprintln!("**************************** PANIC ****************************"); + + hal::Machine::panic_handler(info); +} diff --git a/src/main.rs b/src/main.rs index ae72d07..3191b0f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,23 +7,17 @@ pub extern "C" fn main() -> ! { hal::asm::startup_trampoline!(); } +#[unsafe(no_mangle)] +pub extern "C" fn app_main() -> ! { + osiris::syscall_print(0, "Hello World!".as_bytes().as_ptr(), 12); + loop {} +} + /// The panic handler. #[cfg(freestanding)] #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { - use hal::Machinelike; - - osiris::kprintln!("**************************** PANIC ****************************"); - osiris::kprintln!(""); - osiris::kprintln!("Message: {}", info.message()); - - if let Some(location) = info.location() { - osiris::kprintln!("Location: {}:{}", location.file(), location.line()); - } - - osiris::kprintln!("**************************** PANIC ****************************"); - - hal::Machine::panic_handler(info); + osiris::panic(info); } #[cfg(not(freestanding))] diff --git a/src/mem.rs b/src/mem.rs index a01d32e..f037097 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -3,9 +3,8 @@ use crate::mem::pfa::PAGE_SIZE; use crate::mem::vmm::{AddressSpacelike, Backing, Perms, Region}; use crate::sync::spinlock::SpinLocked; -use crate::BootInfo; use alloc::Allocator; -use hal::mem::{PhysAddr, VirtAddr}; +use hal::mem::{PhysAddr}; use core::ptr::NonNull; pub mod alloc; @@ -44,7 +43,7 @@ static GLOBAL_ALLOCATOR: SpinLocked = /// `regions` - The memory node module of device tree codegen file. /// /// Returns an error if the memory allocator could not be initialized. -pub fn init_memory(boot_info: &BootInfo) -> vmm::AddressSpace { +pub fn init_memory() -> vmm::AddressSpace { let stack_top = &raw const __stack_top as usize; if let Err(e) = pfa::init_pfa(PhysAddr::new(stack_top)) { // TODO: Get this from the DeviceTree. panic!("failed to initialize PFA. Error: {e:?}"); diff --git a/src/sched/thread.rs b/src/sched/thread.rs index 66f4ec5..75a1890 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -4,7 +4,7 @@ use core::{borrow::Borrow, ffi::c_void}; use hal::{Stack, stack::EntryFn}; use hal::stack::{FinFn, Stacklike}; -use macros::TaggedLinks; +use proc_macros::TaggedLinks; use crate::sched::task::{self, KERNEL_TASK}; use crate::types::list; diff --git a/src/syscalls/file.rs b/src/syscalls/file.rs index 65131b9..957a765 100644 --- a/src/syscalls/file.rs +++ b/src/syscalls/file.rs @@ -1,7 +1,5 @@ use core::{ffi::c_int, str}; - -use crate::kprintln; -use macros::syscall_handler; +use proc_macros::syscall_handler; #[syscall_handler(num = 0)] fn syscall_print(fd: usize, buf: *const u8, len: usize) -> c_int { diff --git a/src/uspace.rs b/src/uspace.rs index febcf01..d6799de 100644 --- a/src/uspace.rs +++ b/src/uspace.rs @@ -1,24 +1,19 @@ //! This module provides access to userspace structures and services. -use ::core::mem::transmute; - use crate::sched; -pub fn init_app(boot_info: &crate::BootInfo) -> Result<(), crate::utils::KernelError> { - let len = boot_info.args.init.len; - - if len == 0 { - return Err(crate::utils::KernelError::InvalidArgument); - } +unsafe extern "C" { + /// The entry point for the userspace application. + fn app_main() -> (); +} - let entry = unsafe { - transmute::( - boot_info.args.init.begin as usize + boot_info.args.init.entry_offset as usize, - ) - }; +extern "C" fn app_main_entry() { + unsafe { app_main() } +} +pub fn init_app() -> Result<(), crate::utils::KernelError> { let attrs = sched::thread::Attributes { - entry, + entry: app_main_entry, fin: None, }; let uid = sched::create_thread(sched::task::KERNEL_TASK, &attrs)?; diff --git a/xtasks/crates/pack/Cargo.toml b/xtasks/crates/pack/Cargo.toml index 7ca2656..e898333 100644 --- a/xtasks/crates/pack/Cargo.toml +++ b/xtasks/crates/pack/Cargo.toml @@ -10,7 +10,6 @@ clap = { version = "4.5.47", features = ["derive"] } crc-fast = "1.8.0" elf = "0.8.0" log = "0.4.27" -interface = { workspace = true } bytemuck = { version = "1.24.0", features = ["derive"] } tempfile = "3.23.0" cargo_metadata = "0.23.1" diff --git a/xtasks/crates/pack/src/bootinfo.rs b/xtasks/crates/pack/src/bootinfo.rs deleted file mode 100644 index 8f9fcf3..0000000 --- a/xtasks/crates/pack/src/bootinfo.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::image; - -pub struct BootInfo { - inner: Vec, -} - -impl BootInfo { - pub fn new(img_paddr: usize, section: &image::Section) -> Self { - let boot_info = interface::BootInfo { - magic: interface::BOOT_INFO_MAGIC, - version: 1, - mmap: [interface::MemMapEntry { - size: 0, - addr: 0, - length: 0, - ty: 0, - }; 8], - mmap_len: 0, - args: interface::Args { - init: interface::InitDescriptor { - begin: (img_paddr + section.offset()) as u64, - len: section.size() as u64, - entry_offset: section.entry_offset() as u64, - }, - }, - }; - - let boot_info_bytes = bytemuck::bytes_of(&boot_info); - - Self { - inner: boot_info_bytes.to_vec(), - } - } - - pub fn inner(&self) -> &Vec { - &self.inner - } -} - -// Tests for bootinfo -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_bootinfo_fields() { - let boot_info = BootInfo::new( - 0x4000, - &image::Section::from_parts(0x4000, 0x2000, 0x100, 0x1000, 0, false), - ); - - // Deserialize back to struct for comparison - assert_eq!( - boot_info.inner().len(), - std::mem::size_of::() - ); - - let reconstructed: interface::BootInfo = - unsafe { std::ptr::read(boot_info.inner().as_ptr() as *const interface::BootInfo) }; - - assert_eq!(reconstructed.magic, interface::BOOT_INFO_MAGIC); - assert_eq!(reconstructed.version, 1); - assert_eq!(reconstructed.args.init.begin, 0x4000 + 0x4000); - assert_eq!(reconstructed.args.init.len, 0x2000); - assert_eq!(reconstructed.args.init.entry_offset, 0x100); - } -} diff --git a/xtasks/crates/pack/src/main.rs b/xtasks/crates/pack/src/main.rs index 5e985a8..f84c7a7 100644 --- a/xtasks/crates/pack/src/main.rs +++ b/xtasks/crates/pack/src/main.rs @@ -2,7 +2,6 @@ use std::path::PathBuf; use clap::Parser; -mod bootinfo; mod elf; mod image; mod pack; diff --git a/xtasks/crates/pack/src/pack.rs b/xtasks/crates/pack/src/pack.rs index e4cf12f..6cab370 100644 --- a/xtasks/crates/pack/src/pack.rs +++ b/xtasks/crates/pack/src/pack.rs @@ -4,7 +4,6 @@ use anyhow::{Result, anyhow, bail}; use cargo_metadata::MetadataCommand; use crate::{ - bootinfo, elf::ElfInfo, image::{self}, }; @@ -126,8 +125,7 @@ pub fn pack(init_info: &ElfInfo, kernel_info: &mut ElfInfo, out: &Path) -> Resul let init_section = img.add_elf(init_info, image::SectionDescripter::Loadable(None))?; // Patch bootinfo into kernel. - let boot_info = bootinfo::BootInfo::new(img.paddr(), &init_section); - kernel_info.patch_section(".bootinfo", 0, boot_info.inner())?; + //kernel_info.patch_section(".bootinfo", 0, boot_info.inner())?; // Update kernel in image. img.update(kernel_info, 0)?; From 4d6de8998702034ad2d9004c52665e662ab0ba00 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Thu, 26 Mar 2026 21:37:03 +0000 Subject: [PATCH 23/42] sanify --- machine/arm/build.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/machine/arm/build.rs b/machine/arm/build.rs index 0a12b84..57df5f2 100644 --- a/machine/arm/build.rs +++ b/machine/arm/build.rs @@ -303,13 +303,12 @@ fn merge_compile_commands(files: &[String]) -> String { /// # Returns /// /// PathBuf pointing to the workspace root directory -fn workspace_dir() -> PathBuf { +fn workspace_dir() -> Option { let output = Command::new("cargo") .args(["locate-project", "--workspace", "--message-format=plain"]) - .output() - .expect("failed to run cargo locate-project"); + .output().ok()?; let path = String::from_utf8(output.stdout).expect("utf8"); - PathBuf::from(path.trim()).parent().unwrap().to_path_buf() + Some(PathBuf::from(path.trim()).parent()?.to_path_buf()) } /// Main build script entry point. @@ -388,8 +387,10 @@ fn main() { // Merge and export compile_commands.json for IDE integration let merged = merge_compile_commands(&[hal_cc, common_cc]); - let project_root = workspace_dir(); - let out_file = project_root.join("compile_commands.json"); - - fs::write(out_file, merged).expect("write merged compile_commands.json"); + if let Some(project_root) = workspace_dir() { + let out_file = project_root.join("compile_commands.json"); + fs::write(out_file, merged).expect("write merged compile_commands.json"); + } else { + println!("cargo::warning=Could not determine workspace root, skipping compile_commands.json generation."); + } } From 2f3d7212434a29380f192e73b0f8a76d008d28dc Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Thu, 26 Mar 2026 21:51:54 +0000 Subject: [PATCH 24/42] remove old options --- options.toml | 24 ++++++------------------ presets/stm32l4r5zi_def.toml | 3 --- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/options.toml b/options.toml index eb66692..d292dd3 100644 --- a/options.toml +++ b/options.toml @@ -26,26 +26,14 @@ description = "Enables the Floating Point Unit (FPU). This is required for appli type = "Boolean" default = false -[stackpages] -name = "Stack Pages" -description = "Number of pages to allocate for the kernel stack." -type = { type = "Integer", min = 1 } -default = 1 - -[tuning.appmemsize] -name = "Application Memory Size" -description = "Sets the size of the initial memory region for the init application. This memory is used for the heap and stack." -type = { type = "Integer", min = 0 } -default = 8192 - -[tuning.appstacksize] -name = "Application Stack Size" -description = "Sets the size of the stack for the init application. This must be less than the application memory size." -type = { type = "Integer", min = 0 } -default = 2048 - [tuning.dts] name = "Device Tree Source" description = "Board DTS file targeted to build OS for. Relative to the toplevel boards/ directory." type = "String" default = "nucleo_l4r5zi.dts" + +[stackpages] +name = "Stack Pages" +description = "Number of pages to allocate for the kernel stack." +type = { type = "Integer", min = 1 } +default = 1 diff --git a/presets/stm32l4r5zi_def.toml b/presets/stm32l4r5zi_def.toml index 8751d75..fc6a853 100644 --- a/presets/stm32l4r5zi_def.toml +++ b/presets/stm32l4r5zi_def.toml @@ -13,8 +13,5 @@ OSIRIS_DEBUG_RUNTIMESYMBOLS = "false" OSIRIS_TUNING_ENABLEFPU = "false" OSIRIS_STACKPAGES = "1" -OSIRIS_TUNING_APPSTACKSIZE = "2048" -OSIRIS_TUNING_APPMEMSIZE = "8192" - [build] target = "thumbv7em-none-eabi" \ No newline at end of file From 5e11a88b084c8337e9cc79fab8cc5fba3a22e863 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Sat, 28 Mar 2026 13:57:48 +0000 Subject: [PATCH 25/42] fix lots of stuff --- build.rs | 60 +------ examples/hello-world/src/main.rs | 10 +- machine/api/src/mem.rs | 16 +- machine/arm/src/asm.rs | 75 ++++++-- machine/arm/src/lib.rs | 6 +- machine/arm/src/panic.rs | 7 +- machine/testing/src/asm.rs | 6 +- src/error.rs | 178 +++++++++++++++++++ src/idle.rs | 15 +- src/lib.rs | 28 ++- src/main.rs | 1 - src/mem.rs | 25 +-- src/mem/alloc.rs | 4 +- src/mem/alloc/bestfit.rs | 38 +++-- src/mem/pfa.rs | 8 +- src/mem/pfa/bitset.rs | 13 +- src/mem/vmm.rs | 12 +- src/mem/vmm/nommu.rs | 19 +-- src/sched.rs | 282 ++++++++++++++++++++----------- src/sched/rr.rs | 35 ++-- src/sched/rt.rs | 15 +- src/sched/scheduler.rs | 206 ---------------------- src/sched/task.rs | 23 ++- src/sched/thread.rs | 116 ++++++++++--- src/sync/atomic.rs | 11 +- src/syscalls.rs | 4 +- src/syscalls/sched.rs | 29 ++++ src/syscalls/tasks.rs | 33 ---- src/types/array.rs | 34 ++-- src/types/boxed.rs | 12 +- src/types/heap.rs | 5 +- src/types/list.rs | 52 +++++- src/types/rbtree.rs | 31 ++++ src/uapi.rs | 2 + src/uapi/print.rs | 24 +++ src/uapi/sched.rs | 8 + src/uspace.rs | 16 +- src/utils.rs | 85 ---------- 38 files changed, 850 insertions(+), 694 deletions(-) create mode 100644 src/error.rs delete mode 100644 src/sched/scheduler.rs create mode 100644 src/syscalls/sched.rs delete mode 100644 src/syscalls/tasks.rs create mode 100644 src/uapi.rs create mode 100644 src/uapi/print.rs create mode 100644 src/uapi/sched.rs delete mode 100644 src/utils.rs diff --git a/build.rs b/build.rs index a1075cd..a07afab 100644 --- a/build.rs +++ b/build.rs @@ -6,9 +6,8 @@ extern crate syn; extern crate walkdir; use cfg_aliases::cfg_aliases; -use quote::ToTokens; use std::io::Write; -use syn::{Attribute, FnArg, LitInt, punctuated::Punctuated, token::Comma}; +use syn::{Attribute, LitInt}; use walkdir::WalkDir; extern crate cbindgen; @@ -18,7 +17,6 @@ fn main() { println!("cargo::rerun-if-changed=build.rs"); generate_syscall_map("src/syscalls").expect("Failed to generate syscall map."); - generate_syscalls_export("src/syscalls").expect("Failed to generate syscall exports."); cfg_aliases! { freestanding: { all(not(test), not(doctest), not(doc), not(kani), any(target_os = "none", target_os = "unknown")) }, @@ -320,59 +318,3 @@ fn collect_syscalls>(root: P) -> HashMap { syscalls } - -type SyscallDataExport = (u16, Punctuated); - -fn collect_syscalls_export>(root: P) -> HashMap { - let mut syscalls = HashMap::new(); - let mut numbers = HashMap::new(); - - for entry in WalkDir::new(&root) { - let entry = match entry { - Ok(entry) => entry, - Err(_) => continue, - }; - - if entry.file_type().is_file() { - let path = entry.path(); - - println!("Processing file: {}", path.display()); - - let contents = match std::fs::read_to_string(path) { - Ok(contents) => contents, - Err(_) => continue, - }; - - let file = match syn::parse_file(&contents) { - Ok(file) => file, - Err(_) => continue, - }; - - for item in file.items { - let item = match item { - syn::Item::Fn(item) => item, - _ => continue, - }; - - let name = item.sig.ident.to_string(); - - if let Some(num) = is_syscall(&item.attrs, &name) { - if syscalls.contains_key(&name) { - println!("cargo:warning=Duplicate syscall handler: {name}"); - continue; - } - - if numbers.contains_key(&num) { - println!("cargo:warning=Duplicate syscall number: {num} for {name}"); - continue; - } - - syscalls.insert(name.clone(), (num, item.sig.inputs)); - numbers.insert(num, name); - } - } - } - } - - syscalls -} diff --git a/examples/hello-world/src/main.rs b/examples/hello-world/src/main.rs index d78020d..2702505 100644 --- a/examples/hello-world/src/main.rs +++ b/examples/hello-world/src/main.rs @@ -5,6 +5,12 @@ use osiris::app_main; #[app_main] fn main() { - osiris::syscall_print(0, "Hello World!".as_bytes().as_ptr(), 12); - loop {} + osiris::uprintln!("Hello World!"); + let mut tick = 0; + + loop { + osiris::uprintln!("Tick: {}", tick); + tick += 1; + osiris::uapi::sched::sleep_for(1000); + } } diff --git a/machine/api/src/mem.rs b/machine/api/src/mem.rs index 4aa98d6..d3a6223 100644 --- a/machine/api/src/mem.rs +++ b/machine/api/src/mem.rs @@ -1,7 +1,7 @@ -use core::{fmt::Display, ops::{Add, Div, Rem, Sub}, ptr::NonNull}; +use core::{fmt::{Display, LowerHex, UpperHex}, ops::{Add, Div, Rem, Sub}, ptr::NonNull}; #[repr(transparent)] -#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] pub struct PhysAddr(usize); impl PhysAddr { @@ -100,6 +100,18 @@ impl From for usize { } } +impl LowerHex for PhysAddr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:x}", self.0) + } +} + +impl UpperHex for PhysAddr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:X}", self.0) + } +} + #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] pub struct VirtAddr(usize); diff --git a/machine/arm/src/asm.rs b/machine/arm/src/asm.rs index 9f12f75..b69e90e 100644 --- a/machine/arm/src/asm.rs +++ b/machine/arm/src/asm.rs @@ -22,31 +22,57 @@ macro_rules! __macro_syscall { ($num:expr) => { use core::arch::asm; unsafe { - asm!("svc {0}", const $num); + asm!("svc {num}", num = const $num, clobber_abi("C")); } }; ($num:expr, $arg0:expr) => { use core::arch::asm; unsafe { - asm!("mov r0, {0}", "svc {1}", in(reg)$arg0, const $num); + asm!( + "svc {num}", + in("r0") $arg0, + num = const $num, + clobber_abi("C") + ); } }; ($num:expr, $arg0:expr, $arg1:expr) => { use core::arch::asm; unsafe { - asm!("mov r0, {0}", "mov r1, {1}", "svc {2}", in(reg)$arg0, in(reg)$arg1, const $num); + asm!( + "svc {num}", + in("r0") $arg0, + in("r1") $arg1, + num = const $num, + clobber_abi("C") + ); } }; ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => { use core::arch::asm; unsafe { - asm!("mov r0, {0}", "mov r1, {1}", "mov r2, {2}", "svc {3}", in(reg)$arg0, in(reg)$arg1, in(reg)$arg2, const $num); + asm!( + "svc {num}", + in("r0") $arg0, + in("r1") $arg1, + in("r2") $arg2, + num = const $num, + clobber_abi("C") + ); } }; ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => { use core::arch::asm; unsafe { - asm!("mov r0, {0}", "mov r1, {1}", "mov r2, {2}", "mov r3, {3}", "svc {4}", in(reg)$arg0, in(reg)$arg1, in(reg)$arg2, in(reg)$arg3, const $num); + asm!( + "svc {num}", + in("r0") $arg0, + in("r1") $arg1, + in("r2") $arg2, + in("r3") $arg3, + num = const $num, + clobber_abi("C") + ); } }; } @@ -65,17 +91,28 @@ pub use crate::__macro_syscall as syscall; #[cfg(not(feature = "host"))] #[inline(always)] -pub fn disable_interrupts() { +pub fn disable_irq_save() -> usize { use core::arch::asm; - use core::sync::atomic::compiler_fence; - unsafe { asm!("cpsid i", options(nomem, nostack, preserves_flags)) }; - compiler_fence(core::sync::atomic::Ordering::SeqCst); + let old: usize; + + unsafe { + asm!( + "mrs {old}, primask", + "cpsid i", + "isb", + old = out(reg) old, + options(nostack, preserves_flags) + ); + } + old } #[cfg(feature = "host")] #[inline(always)] -pub fn disable_interrupts() {} +pub fn disable_irq_save() -> usize { + 0 +} #[cfg(not(feature = "host"))] #[inline(always)] @@ -97,17 +134,23 @@ pub fn are_interrupts_enabled() -> bool { #[cfg(not(feature = "host"))] #[inline(always)] -pub fn enable_interrupts() { +pub fn enable_irq_restr(state: usize) { use core::arch::asm; - use core::sync::atomic::compiler_fence; - - unsafe { asm!("cpsie i", options(nomem, nostack, preserves_flags)) }; - compiler_fence(core::sync::atomic::Ordering::SeqCst); + + unsafe { + asm!( + "dsb", + "msr primask, {state}", + "isb", + state = in(reg) state, + options(nostack, preserves_flags) + ); + } } #[cfg(feature = "host")] #[inline(always)] -pub fn enable_interrupts() {} +pub fn enable_irq_restr(state: usize) {} #[cfg(not(feature = "host"))] #[macro_export] diff --git a/machine/arm/src/lib.rs b/machine/arm/src/lib.rs index 31666f5..00fb103 100644 --- a/machine/arm/src/lib.rs +++ b/machine/arm/src/lib.rs @@ -40,14 +40,14 @@ impl hal_api::Machinelike for ArmMachine { fn print(s: &str) -> Result<()> { use crate::asm; - asm::disable_interrupts(); + let state = asm::disable_irq_save(); if (unsafe { bindings::write_debug_uart(s.as_ptr() as *const c_char, s.len() as i32) } != 0) { - asm::enable_interrupts(); + asm::enable_irq_restr(state); Ok(()) } else { - asm::enable_interrupts(); + asm::enable_irq_restr(state); Err(hal_api::Error::default()) } } diff --git a/machine/arm/src/panic.rs b/machine/arm/src/panic.rs index 3d6dfbc..9025b8a 100644 --- a/machine/arm/src/panic.rs +++ b/machine/arm/src/panic.rs @@ -6,9 +6,6 @@ use core::panic::PanicInfo; use crate::asm; pub fn panic_handler(_info: &PanicInfo) -> ! { - asm::disable_interrupts(); - - loop { - core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); - } + asm::disable_irq_save(); + loop {} } diff --git a/machine/testing/src/asm.rs b/machine/testing/src/asm.rs index 9322be2..a303684 100644 --- a/machine/testing/src/asm.rs +++ b/machine/testing/src/asm.rs @@ -21,7 +21,9 @@ macro_rules! __macro_syscall { pub use crate::__macro_syscall as syscall; #[inline(always)] -pub fn disable_interrupts() {} +pub fn disable_irq_save() -> usize { + 0 +} #[inline(always)] pub fn are_interrupts_enabled() -> bool { @@ -29,7 +31,7 @@ pub fn are_interrupts_enabled() -> bool { } #[inline(always)] -pub fn enable_interrupts() {} +pub fn enable_irq_restr(state: usize) {} #[macro_export] macro_rules! __macro_startup_trampoline { diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..f9edb31 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,178 @@ +//! Utility functions and definitions for the kernel. +#![cfg_attr(feature = "nightly", feature(likely_unlikely))] + +use core::fmt::Display; +use hal::mem::PhysAddr; +use core::fmt::Debug; + +/// These two definitions are copied from https://github.com/rust-lang/hashbrown +#[cfg(not(feature = "nightly"))] +#[allow(unused_imports)] +pub(crate) use core::convert::{identity as likely, identity as unlikely}; + +#[cfg(feature = "nightly")] +pub(crate) use core::hint::{likely, unlikely}; + +pub type Result = core::result::Result; + +/// This is a macro that is used to panic when a bug is detected. +/// It is similar to the BUG() macro in the Linux kernel. Link: [https://www.kernel.org/]() +#[macro_export] +macro_rules! bug { + () => { + panic!("BUG at {}:{}", file!(), line!()); + }; + ($fmt:literal $(, $arg:expr)* $(,)?) => {{ + panic!(concat!("BUG at {}:{}: ", $fmt), file!(), line!() $(, $arg)*); + }}; +} + +#[macro_export] +macro_rules! warn { + () => { + kprintln!("WARN at {}:{}", file!(), line!()); + }; + ($fmt:literal $(, $arg:expr)* $(,)?) => {{ + kprintln!(concat!("WARN at {}:{}: ", $fmt), file!(), line!() $(, $arg)*); + }}; +} + +/// This is a macro that is used to panic when a condition is true. +/// It is similar to the BUG_ON() macro in the Linux kernel. Link: [https://www.kernel.org/]() +macro_rules! bug_on { + ($cond:expr) => {{ + let cond = $cond; + #[allow(unused_unsafe)] + if unsafe { $crate::error::unlikely(cond) } { + panic!("BUG({}) at {}:{}", stringify!($cond), file!(), line!()); + } + }}; + ($cond:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {{ + let cond = $cond; + #[allow(unused_unsafe)] + if unsafe { $crate::error::unlikely(cond) } { + panic!(concat!("BUG({}) at {}:{}: ", $fmt), stringify!($cond), file!(), line!() $(, $arg)*); + } + }}; +} + +macro_rules! warn_on { + ($cond:expr) => {{ + let cond = $cond; + #[allow(unused_unsafe)] + if unsafe { $crate::error::unlikely(cond) } { + kprintln!("WARN({}) at {}:{}", stringify!($cond), file!(), line!()); + } + }}; + ($cond:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {{ + let cond = $cond; + #[allow(unused_unsafe)] + if unsafe { $crate::error::unlikely(cond) } { + kprintln!(concat!("WARN({}) at {}:{}: ", $fmt), stringify!($cond), file!(), line!() $(, $arg)*); + } + }}; +} + +macro_rules! kerr { + ($kind:ident) => { + $crate::error::Error::new($crate::error::Kind::$kind) + }; + ($kind:expr, $msg:expr) => { + use $crate::error::Error; + #[cfg(feature = "error-msg")] + { + Error::new($crate::error::Kind::$kind).with_msg($msg) + } + #[cfg(not(feature = "error-msg"))] + { + Error::new($crate::error::Kind::$kind) + } + }; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Kind { + InvalidAlign, + OutOfMemory, + InvalidSize, + InvalidAddress(PhysAddr), + InvalidArgument, + NotFound, + Hal(hal::Error), +} + +impl Display for Kind { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Kind::InvalidAlign => write!(f, "Invalid alignment"), + Kind::OutOfMemory => write!(f, "Out of memory"), + Kind::InvalidSize => write!(f, "Invalid size"), + Kind::InvalidAddress(addr) => write!(f, "Invalid address: {addr:#x}"), + Kind::InvalidArgument => write!(f, "Invalid argument"), + Kind::NotFound => write!(f, "Not found"), + Kind::Hal(e) => write!(f, "HAL error: {e:?}"), + } + } +} + +pub struct Error { + pub kind: Kind, + #[cfg(feature = "error-msg")] + msg: Option<&'static str>, +} + +impl Error { + pub fn new(kind: Kind) -> Self { + #[cfg(feature = "error-msg")] + { + Self { kind, msg: None } + } + #[cfg(not(feature = "error-msg"))] + { + Self { kind } + } + } + + #[cfg(feature = "error-msg")] + pub fn with_msg(mut self, msg: &'static str) -> Self { + self.msg = Some(msg); + self + } +} + +impl Debug for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + #[cfg(feature = "error-msg")] + { + match self.msg { + Some(msg) => write!(f, "{}: {}", self.kind, msg), + None => write!(f, "{}", self.kind), + } + } + #[cfg(not(feature = "error-msg"))] + { + write!(f, "{}", self.kind) + } + } +} + +impl Display for Error { + #[cfg(not(feature = "error-msg"))] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.kind) + } + + #[cfg(feature = "error-msg")] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self.msg { + Some(msg) => write!(f, "{}: {}", self.kind, msg), + None => write!(f, "{}", self.kind), + } + } +} + +impl From for Error { + fn from(e: hal::Error) -> Self { + Self::new(Kind::Hal(e)) + } +} \ No newline at end of file diff --git a/src/idle.rs b/src/idle.rs index 7c79b37..70633a7 100644 --- a/src/idle.rs +++ b/src/idle.rs @@ -7,11 +7,10 @@ extern "C" fn entry() { } pub fn init() { - let attrs = sched::thread::Attributes { - entry, - fin: None, - }; - if let Err(e) = sched::create_thread(sched::task::KERNEL_TASK, &attrs) { - panic!("failed to create idle thread. Error: {e:?}"); - } -} \ No newline at end of file + let attrs = sched::thread::Attributes { entry, fin: None }; + sched::with(|sched| { + if let Err(e) = sched.create_thread(sched::task::KERNEL_TASK, &attrs) { + panic!("failed to create idle thread. Error: {}", e); + } + }); +} diff --git a/src/lib.rs b/src/lib.rs index 39bab6f..2ce97dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,21 +6,22 @@ #[macro_use] mod macros; #[macro_use] -mod utils; +mod error; mod faults; mod mem; mod types; mod idle; +mod uspace; +mod print; -pub mod print; -pub mod sched; -pub mod sync; -pub mod syscalls; -pub mod time; -pub mod uspace; +mod sched; +mod sync; +mod syscalls; +mod time; + +pub mod uapi; use hal::Machinelike; -include!(concat!(env!("OUT_DIR"), "/syscalls_export.rs")); include!(concat!(env!("OUT_DIR"), "/device_tree.rs")); pub use hal; @@ -44,17 +45,12 @@ pub unsafe extern "C" fn kernel_init() -> ! { // Initialize the memory allocator. let kaddr_space = mem::init_memory(); - kprintln!("Memory initialized."); - if let Err(e) = sched::init(kaddr_space) { - panic!("failed to initialize scheduler. Error: {e:?}"); - } - + sched::init(kaddr_space); kprintln!("Scheduler initialized."); idle::init(); - kprintln!("Idle thread initialized."); let (cyc, ns) = hal::Machine::bench_end(); @@ -63,9 +59,7 @@ pub unsafe extern "C" fn kernel_init() -> ! { ); // Start the init application. - if let Err(e) = uspace::init_app() { - panic!("failed to start init application. Error: {e:?}"); - } + uspace::init_app(); sched::enable(); diff --git a/src/main.rs b/src/main.rs index 3191b0f..5ed2555 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,6 @@ pub extern "C" fn main() -> ! { #[unsafe(no_mangle)] pub extern "C" fn app_main() -> ! { - osiris::syscall_print(0, "Hello World!".as_bytes().as_ptr(), 12); loop {} } diff --git a/src/mem.rs b/src/mem.rs index f037097..dc1bcdf 100644 --- a/src/mem.rs +++ b/src/mem.rs @@ -13,23 +13,6 @@ pub mod pfa; pub const BITS_PER_PTR: usize = core::mem::size_of::() * 8; -/// The possible types of memory. Which is compatible with the multiboot2 memory map. -/// Link: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html -#[repr(C)] -#[allow(unused)] -enum MemoryTypes { - /// Memory that is available for use. - Available = 1, - /// Memory that is reserved for the system. - Reserved = 2, - /// Memory that is reclaimable after ACPI tables are read. - ACPIReclaimable = 3, - /// ACPI Non-volatile-sleeping memory. - Nvs = 4, - /// Memory that is bad. - BadMemory = 5, -} - unsafe extern "C" { unsafe static __stack_top: u8; } @@ -46,18 +29,18 @@ static GLOBAL_ALLOCATOR: SpinLocked = pub fn init_memory() -> vmm::AddressSpace { let stack_top = &raw const __stack_top as usize; if let Err(e) = pfa::init_pfa(PhysAddr::new(stack_top)) { // TODO: Get this from the DeviceTree. - panic!("failed to initialize PFA. Error: {e:?}"); + panic!("failed to initialize PFA. Error: {e}"); } // TODO: Configure. let pgs = 10; let mut kaddr_space = vmm::AddressSpace::new(pgs).unwrap_or_else(|e| { - panic!("failed to create kernel address space. Error: {e:?}"); + panic!("failed to create kernel address space. Error: {e}"); }); let begin = kaddr_space.map(Region::new(None, 2 * PAGE_SIZE, Backing::Zeroed, Perms::all())).unwrap_or_else(|e| { - panic!("failed to map kernel address space. Error: {e:?}"); + panic!("failed to map kernel address space. Error: {e}"); }); { @@ -65,7 +48,7 @@ pub fn init_memory() -> vmm::AddressSpace { let range = begin..(begin + pgs * PAGE_SIZE); if let Err(e) = unsafe { allocator.add_range(&range) } { - panic!("failed to add range to allocator. Error: {e:?}"); + panic!("failed to add range to allocator. Error: {e}"); } } diff --git a/src/mem/alloc.rs b/src/mem/alloc.rs index 85d8830..c7a7831 100644 --- a/src/mem/alloc.rs +++ b/src/mem/alloc.rs @@ -5,7 +5,7 @@ use core::ptr::NonNull; use hal::mem::PhysAddr; -use crate::utils; +use crate::error::Result; pub mod bestfit; @@ -25,7 +25,7 @@ pub const MAX_ADDR: usize = usize::MAX; /// Each range added to the allocator must be valid for the whole lifetime of the allocator and must not overlap with any other range. /// The lifetime of any allocation is only valid as long as the allocator is valid. (A pointer must not be used after the allocator is dropped.) pub trait Allocator { - fn malloc(&mut self, size: usize, align: usize, request: Option) -> Result, utils::KernelError>; + fn malloc(&mut self, size: usize, align: usize, request: Option) -> Result>; unsafe fn free(&mut self, ptr: NonNull, size: usize); } diff --git a/src/mem/alloc/bestfit.rs b/src/mem/alloc/bestfit.rs index a14c251..9c91fa1 100644 --- a/src/mem/alloc/bestfit.rs +++ b/src/mem/alloc/bestfit.rs @@ -2,7 +2,7 @@ use core::{ops::Range, ptr::NonNull}; use hal::mem::PhysAddr; -use crate::utils::{self, KernelError}; +use crate::error::Result; /// The metadata that is before any block in the BestFitAllocator. struct BestFitMeta { @@ -44,13 +44,13 @@ impl BestFitAllocator { /// The range must be valid, 128bit aligned and must not overlapping with any other current or future range. /// The range must also be at least as large as `MIN_RANGE_SIZE`. /// Also the range must stay valid, for the whole lifetime of the allocator. Also the lifetime of any allocation is only valid as long as the allocator is valid. - pub unsafe fn add_range(&mut self, range: &Range) -> Result<(), utils::KernelError> { + pub unsafe fn add_range(&mut self, range: &Range) -> Result<()> { let ptr = range.start; // Check if the pointer is 128bit aligned. if !ptr.is_multiple_of(align_of::()) { - return Err(utils::KernelError::InvalidAlign); - } + return Err(kerr!(InvalidArgument)); + } debug_assert!(range.end > range.start); debug_assert!(range.end.diff(range.start) > size_of::() + Self::align_up()); @@ -92,8 +92,8 @@ impl BestFitAllocator { &mut self, size: usize, requested: Option, - ) -> Result<(NonNull, Option>), utils::KernelError> { - let mut best_fit = Err(utils::KernelError::OutOfMemory); + ) -> Result<(NonNull, Option>)> { + let mut best_fit = Err(kerr!(OutOfMemory)); let mut best_fit_size = usize::MAX; let mut current = self.head; @@ -190,27 +190,27 @@ impl super::Allocator for BestFitAllocator { /// `align` - The alignment of the block. /// /// Returns the user pointer to the block if successful, otherwise an error. - fn malloc(&mut self, size: usize, align: usize, request: Option) -> Result, utils::KernelError> { + fn malloc(&mut self, size: usize, align: usize, request: Option) -> Result> { // Check if the alignment is valid. if align > align_of::() { - return Err(utils::KernelError::InvalidAlign); + return Err(kerr!(InvalidArgument)); } if let Some(request) = request { if !request.is_multiple_of(align) { - return Err(utils::KernelError::InvalidAlign); + return Err(kerr!(InvalidArgument)); } } // Check if the size is valid. if size == 0 { - return Err(utils::KernelError::InvalidSize); + return Err(kerr!(InvalidArgument)); } // For some cfg this warning is correct. But for others its not. #[allow(clippy::absurd_extreme_comparisons)] if size >= super::MAX_ADDR { - return Err(utils::KernelError::InvalidSize); + return Err(kerr!(InvalidArgument)); } // Align the size. @@ -324,7 +324,7 @@ impl super::Allocator for BestFitAllocator { meta.next = self.head; // Check if the size of the block is correct. - BUG_ON!(meta.size != super::super::align_up(size), "Invalid size in free()"); + bug_on!(meta.size != super::super::align_up(size), "Invalid size in free()"); // Set the size of the block. meta.size = size; @@ -338,6 +338,8 @@ impl super::Allocator for BestFitAllocator { #[cfg(test)] mod tests { + use crate::error::Kind; + use super::*; use super::super::*; @@ -421,7 +423,7 @@ mod tests { let request = range.start + 4096; let ptr = allocator.malloc::(128, 1, Some(request)); - assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + assert!(ptr.is_err_and(|e| e.kind == Kind::OutOfMemory)); } #[test] @@ -436,7 +438,7 @@ mod tests { let request = range.start + 127; let ptr = allocator.malloc::(128, 8, Some(request)); - assert!(ptr.is_err_and(|e| e == utils::KernelError::InvalidAlign)); + assert!(ptr.is_err_and(|e| e.kind == Kind::InvalidAlign)); } #[test] @@ -453,7 +455,7 @@ mod tests { verify_block(ptr, 128, None); let ptr = allocator.malloc::(128, 1, Some(request)); - assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + assert!(ptr.is_err_and(|e| e.kind == Kind::OutOfMemory)); } #[test] @@ -468,7 +470,7 @@ mod tests { let request = range.end + 128; let ptr = allocator.malloc::(128, 1, Some(request)); - assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + assert!(ptr.is_err_and(|e| e.kind == Kind::OutOfMemory)); } #[test] @@ -534,7 +536,7 @@ mod tests { } let ptr = allocator.malloc::(SIZE, 1, None); - assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + assert!(ptr.is_err_and(|e| e.kind == Kind::OutOfMemory)); } #[test] @@ -646,7 +648,7 @@ mod tests { } let ptr = allocator.malloc::(SIZE, 1, None); - assert!(ptr.is_err_and(|e| e == utils::KernelError::OutOfMemory)); + assert!(ptr.is_err_and(|e| e.kind == Kind::OutOfMemory)); verify_ptrs_not_overlaping(ptrs.as_slice()); } diff --git a/src/mem/pfa.rs b/src/mem/pfa.rs index c62b8c1..e20f212 100644 --- a/src/mem/pfa.rs +++ b/src/mem/pfa.rs @@ -2,9 +2,9 @@ use hal::mem::PhysAddr; +use crate::error::Result; use crate::sync::spinlock::SpinLocked; use crate::types::boxed::Box; -use crate::utils::KernelError; use core::pin::Pin; @@ -27,16 +27,16 @@ trait Allocator { /// Safety: /// /// - The returned function must only be called with a useable and valid physical address. - fn initializer() -> unsafe fn(PhysAddr, usize) -> Result>, KernelError>; + fn initializer() -> unsafe fn(PhysAddr, usize) -> Result>>; fn alloc(&mut self, page_count: usize) -> Option; fn free(&mut self, addr: PhysAddr, page_count: usize); } -pub fn init_pfa(addr: PhysAddr) -> Result<(), KernelError> { +pub fn init_pfa(addr: PhysAddr) -> Result<()> { let mut pfa = PFA.lock(); if pfa.is_some() { - return Err(KernelError::CustomError("Page frame allocator is already initialized")); + return Err(kerr!(InvalidArgument)); } let initializer = AllocatorType::initializer(); diff --git a/src/mem/pfa/bitset.rs b/src/mem/pfa/bitset.rs index 391ab9a..7dc5692 100644 --- a/src/mem/pfa/bitset.rs +++ b/src/mem/pfa/bitset.rs @@ -4,8 +4,7 @@ use core::ptr::NonNull; use hal::mem::PhysAddr; use crate::{ - types::boxed::{self, Box}, - utils::KernelError, + error::Result, types::boxed::{self, Box} }; pub struct Allocator { @@ -33,17 +32,17 @@ impl Allocator { } impl super::Allocator for Allocator { - fn initializer() -> unsafe fn(PhysAddr, usize) -> Result>, KernelError> { - |addr: PhysAddr, pcnt: usize| -> Result>, KernelError> { + fn initializer() -> unsafe fn(PhysAddr, usize) -> Result>> { + |addr: PhysAddr, pcnt: usize| -> Result>> { if pcnt > N { todo!("Runtime page frame allocator for more than {} pages", N) } if !addr.is_multiple_of(core::mem::align_of::()) { - return Err(KernelError::InvalidAlign); + return Err(kerr!(InvalidArgument)); } - let ptr = NonNull::new(addr.as_mut_ptr::()).ok_or(KernelError::InvalidAddress(addr))?; + let ptr = NonNull::new(addr.as_mut_ptr::()).ok_or(kerr!(InvalidArgument))?; // Align this up to PAGE_SIZE let begin = addr + size_of::(); let begin = if begin.is_multiple_of(super::PAGE_SIZE) { @@ -52,7 +51,7 @@ impl super::Allocator for Allocator { PhysAddr::new((begin.as_usize() + super::PAGE_SIZE - 1) & !(super::PAGE_SIZE - 1)) }; // TODO: Subtract the needed pages from the available - unsafe { core::ptr::write(ptr.as_ptr(), Self::new(begin).ok_or(KernelError::InvalidAddress(begin))?) }; + unsafe { core::ptr::write(ptr.as_ptr(), Self::new(begin).ok_or(kerr!(InvalidArgument))?) }; // Safety: Ptr is properly aligned and non-null. The validity of the memory at that address is valid by the call contract. Ok(Pin::new(unsafe { boxed::Box::from_raw(ptr) })) diff --git a/src/mem/vmm.rs b/src/mem/vmm.rs index ee86228..6ddc7e9 100644 --- a/src/mem/vmm.rs +++ b/src/mem/vmm.rs @@ -1,6 +1,6 @@ use hal::mem::{PhysAddr, VirtAddr}; -use crate::{utils::KernelError}; +use crate::error::Result; mod nommu; @@ -62,12 +62,12 @@ impl Region { pub trait AddressSpacelike { // Size is the amount of pages in the address space. On nommu systems this will be reserved. - fn new(pages: usize) -> Result where Self: Sized; - fn map(&mut self, region: Region) -> Result; - fn unmap(&mut self, region: &Region) -> Result<(), KernelError>; - fn protect(&mut self, region: &Region, perms: Perms) -> Result<(), KernelError>; + fn new(pages: usize) -> Result where Self: Sized; + fn map(&mut self, region: Region) -> Result; + fn unmap(&mut self, region: &Region) -> Result<()>; + fn protect(&mut self, region: &Region, perms: Perms) -> Result<()>; fn virt_to_phys(&self, addr: VirtAddr) -> Option; fn phys_to_virt(&self, addr: PhysAddr) -> Option; fn end(&self) -> VirtAddr; - fn activate(&self) -> Result<(), KernelError>; + fn activate(&self) -> Result<()>; } \ No newline at end of file diff --git a/src/mem/vmm/nommu.rs b/src/mem/vmm/nommu.rs index 8927471..f169c14 100644 --- a/src/mem/vmm/nommu.rs +++ b/src/mem/vmm/nommu.rs @@ -3,11 +3,10 @@ use core::ptr::copy_nonoverlapping; use hal::mem::{PhysAddr, VirtAddr}; use crate::{ - mem::{ + error::Result, mem::{ alloc::{Allocator, bestfit}, pfa, vmm, - }, - utils::KernelError, + } }; pub struct AddressSpace { @@ -17,11 +16,11 @@ pub struct AddressSpace { } impl vmm::AddressSpacelike for AddressSpace { - fn new(pgs: usize) -> Result { - let begin = pfa::alloc_page(pgs).ok_or(KernelError::OutOfMemory)?; + fn new(pgs: usize) -> Result { + let begin = pfa::alloc_page(pgs).ok_or(kerr!(OutOfMemory))?; let end = begin .checked_add(pgs * pfa::PAGE_SIZE) - .ok_or(KernelError::OutOfMemory)?; + .ok_or(kerr!(OutOfMemory))?; let mut allocator = bestfit::BestFitAllocator::new(); unsafe { allocator.add_range(&(begin..end))? }; @@ -33,7 +32,7 @@ impl vmm::AddressSpacelike for AddressSpace { }) } - fn map(&mut self, region: vmm::Region) -> Result { + fn map(&mut self, region: vmm::Region) -> Result { let req = region.start.and_then(|virt| self.virt_to_phys(virt)); // TODO: per page align let align = core::mem::align_of::(); @@ -54,11 +53,11 @@ impl vmm::AddressSpacelike for AddressSpace { Ok(start.into()) } - fn unmap(&mut self, _region: &vmm::Region) -> Result<(), KernelError> { + fn unmap(&mut self, _region: &vmm::Region) -> Result<()> { Ok(()) } - fn protect(&mut self, _region: &vmm::Region, _perms: vmm::Perms) -> Result<(), KernelError> { + fn protect(&mut self, _region: &vmm::Region, _perms: vmm::Perms) -> Result<()> { Ok(()) } @@ -76,7 +75,7 @@ impl vmm::AddressSpacelike for AddressSpace { self.phys_to_virt(self.end).unwrap() } - fn activate(&self) -> Result<(), KernelError> { + fn activate(&self) -> Result<()> { Ok(()) } } diff --git a/src/sched.rs b/src/sched.rs index 510ff80..39583d8 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -1,32 +1,44 @@ //! This module provides access to the scheduler. mod dispch; -pub mod rt; pub mod rr; +pub mod rt; pub mod task; pub mod thread; -use core::{ffi::c_void, sync::atomic::{AtomicBool, Ordering}}; +use core::{ + ffi::c_void, + sync::atomic::{AtomicBool, Ordering}, +}; use hal::Schedable; use crate::{ - mem, sync::{atomic::AtomicU64, spinlock::SpinLocked}, time::{self, tick}, types::{ + error::Result, + mem, + sched::thread::Waiter, + sync::{self, atomic::AtomicU64, spinlock::SpinLocked}, + time::{self}, + types::{ array::IndexMap, rbtree::RbTree, - traits::{Get, GetMut}, + traits::{Get, GetMut, Project}, view::ViewMut, - }, utils::KernelError + }, }; type ThreadMap = IndexMap; type TaskMap = IndexMap; -static SCHED: SpinLocked> = SpinLocked::new(Scheduler::new()); +type GlobalScheduler = Scheduler<32>; + +static SCHED: SpinLocked = SpinLocked::new(GlobalScheduler::new()); static DISABLED: AtomicBool = AtomicBool::new(true); static NEXT_TICK: AtomicU64 = AtomicU64::new(0); +type WaiterView<'a, const N: usize> = ViewMut<'a, thread::UId, thread::Waiter, ThreadMap>; + pub struct Scheduler { threads: ThreadMap, tasks: TaskMap, @@ -55,98 +67,186 @@ impl Scheduler { } } - fn land(&mut self, ctx: *mut c_void) -> Result<(), KernelError> { + fn land(&mut self, ctx: *mut c_void) { if let Some(current) = self.current { - let thread = self.threads.get_mut(current).ok_or(KernelError::InvalidArgument)?; - return thread.save_ctx(ctx); + let mut kill = None; + if let Some(thread) = self.threads.get_mut(current) { + if thread.save_ctx(ctx).is_err() { + warn!( + "failed to save context (SP: {:x}) of thread {}.", + ctx as usize, current + ); + kill = Some(thread.task_id()); + } + } else { + bug!("failed to land thread {}. Does not exist.", current); + } + + if let Some(task_id) = kill { + self.dequeue(current); + self.current = None; + self.kill_task(task_id); + } } + } - Ok(()) + fn schedule_resched(now: u64, next: u64) { + let old = NEXT_TICK.load(Ordering::Acquire); + + if old > now && old <= next { + return; + } + + NEXT_TICK.store(next, Ordering::Release); } - pub fn enqueue(&mut self, uid: thread::UId) -> Result<(), KernelError> { - let thread = self.threads.get(uid).ok_or(KernelError::InvalidArgument)?; + pub fn enqueue(&mut self, now: u64, uid: thread::UId) -> Result<()> { + let thread = self.threads.get(uid).ok_or(kerr!(InvalidArgument))?; if thread.rt_server().is_some() { - let mut view = - ViewMut::>::new(&mut self.threads); - self.rt_scheduler.enqueue(uid, &mut view); + let mut view = rt::ServerView::::new(&mut self.threads); + self.rt_scheduler.enqueue(uid, now, &mut view); } else { self.rr_scheduler.enqueue(uid, &mut self.threads)?; } - - // A new thread was added -> Trigger a reschedule. - NEXT_TICK.store(tick(), Ordering::Release); + reschedule(); Ok(()) } - pub fn do_sched( - &mut self, - now: u64, - old: Option, - ) -> Option<(*mut c_void, &mut task::Task)> { + fn do_wakeups(&mut self, now: u64) { + while let Some(uid) = self.wakeup.min() { + { + let mut view = WaiterView::::new(&mut self.threads); + let waiter = view.get(uid).expect("THIS IS A BUG!"); + + if waiter.until() > now { + Self::schedule_resched(now, waiter.until()); + break; + } + + self.wakeup.remove(uid, &mut view); + } + + self.enqueue(now, uid); + } + } + + pub fn do_sched(&mut self, now: u64) -> Option<(*mut c_void, &mut task::Task)> { let dt = now - self.last_tick; self.last_tick = now; - if let Some(old) = old { + if let Some(old) = self.current { let mut view = rt::ServerView::::new(&mut self.threads); - // If this is not a real-time thread, this will just do nothing. self.rt_scheduler.put(old, dt, &mut view); - // If this is not a round-robin thread, this will just do nothing. self.rr_scheduler.put(old, dt); - - // TODO: thread is still enqueued. Dequeue if blocked or sleeping and put to the respective tree/list. - // If it exited remove it completely. } + self.do_wakeups(now); + let mut view = rt::ServerView::::new(&mut self.threads); - let (new, budget) = if let Some((new, budget)) = self.rt_scheduler.pick(now, &mut view) { - (new, budget) - } else if let Some((new, budget)) = self.rr_scheduler.pick(&mut self.threads) { - (new, budget) - } else { - // No thread to run. Run the idle thread. - (thread::IDLE_THREAD, u64::MAX) - }; + let (new, budget) = self + .rt_scheduler + .pick(now, &mut view) + .or_else(|| self.rr_scheduler.pick(&mut self.threads)) + .unwrap_or((thread::IDLE_THREAD, 1000)); let ctx = self.threads.get(new)?.ctx(); let task = self.tasks.get_mut(self.threads.get(new)?.task_id())?; self.current = Some(new); + let next = now.saturating_add(budget); - // Only store next_tick if now + budget is smaller than the current next tick. - let next_tick = now + budget; - let mut old_tick = NEXT_TICK.load(Ordering::Acquire); + Self::schedule_resched(now, next); + Some((ctx, task)) + } - while NEXT_TICK.compare_exchange(old_tick, next_tick, Ordering::Release, Ordering::Acquire).is_err() { - old_tick = NEXT_TICK.load(Ordering::Acquire); - if next_tick >= old_tick { - break; - } + pub fn sleep_until(&mut self, until: u64, now: u64) -> Result<()> { + if until <= now { + return Ok(()); } + let uid = self.current.ok_or(kerr!(InvalidArgument))?; - Some((ctx, task)) + if let Some(thread) = self.threads.get_mut(uid) { + thread.set_waiter(Some(Waiter::new(until, uid))); + } else { + bug!( + "failed to put current thread {} to sleep. Does not exist.", + uid + ); + } + + if self + .wakeup + .insert(uid, &mut WaiterView::::new(&mut self.threads)) + .is_err() + { + bug!("failed to insert thread {} into wakeup tree.", uid); + } + + + + self.dequeue(uid); + reschedule(); + Ok(()) + } + + pub fn kick(&mut self, uid: thread::UId) -> Result<()> { + let thread = self.threads.get_mut(uid).ok_or(kerr!(InvalidArgument))?; + if let Some(waiter) = Project::::project_mut(thread) { + waiter.set_until(0); + } + Ok(()) } - pub fn dequeue(&mut self, uid: thread::UId) -> Option { + pub fn dequeue(&mut self, uid: thread::UId) { let mut view = rt::ServerView::::new(&mut self.threads); - // If this is not a real-time thread, this will just do nothing. self.rt_scheduler.dequeue(uid, &mut view); - - self.threads.remove(&uid) + self.rr_scheduler.dequeue(uid, &mut self.threads); } - pub fn create_task(&mut self, task: &task::Attributes) -> Result { - let uid = task::UId::new(self.id_gen).ok_or(KernelError::InvalidArgument)?; + pub fn create_task(&mut self, task: &task::Attributes) -> Result { + let uid = task::UId::new(self.id_gen).ok_or(kerr!(InvalidArgument))?; self.id_gen += 1; self.tasks.insert(&uid, task::Task::new(uid, task)?)?; Ok(uid) } - pub fn create_thread(&mut self, task: task::UId, attrs: &thread::Attributes) -> Result { - let task = self.tasks.get_mut(task).ok_or(KernelError::InvalidArgument)?; + pub fn kill_task(&mut self, uid: task::UId) -> Result<()> { + let task_id = self.tasks.get(uid).ok_or(kerr!(InvalidArgument))?.id; + self.tasks.remove(&uid).ok_or(kerr!(InvalidArgument))?; + + let begin = match self.threads.next(None) { + Some(i) => i, + None => return Ok(()), + }; + let mut i = begin; + + while i != begin { + i = (i + 1) % N; + + let mut id = None; + if let Some(thread) = self.threads.at_cont(i) { + if thread.task_id() == task_id { + id = Some(thread.uid()); + } + } + + if let Some(id) = id { + self.dequeue(id); + } + } + + Ok(()) + } + + pub fn create_thread( + &mut self, + task: task::UId, + attrs: &thread::Attributes, + ) -> Result { + let task = self.tasks.get_mut(task).ok_or(kerr!(InvalidArgument))?; let thread = task.create_thread(self.id_gen, attrs)?; let uid = thread.uid(); @@ -157,23 +257,24 @@ impl Scheduler { } } -pub fn init(kaddr_space: mem::vmm::AddressSpace) -> Result<(), KernelError> { - let mut sched = SCHED.lock(); - let uid = task::KERNEL_TASK; - sched.tasks.insert(&uid, task::Task::from_addr_space(uid, kaddr_space)?) +pub fn with T>(f: F) -> T { + sync::atomic::irq_free(|| { + let mut sched = SCHED.lock(); + f(&mut sched) + }) } -pub fn create_task(attrs: &task::Attributes) -> Result { - SCHED.lock().create_task(attrs) -} - -pub fn create_thread(task: task::UId, attrs: &thread::Attributes) -> Result { - let mut sched = SCHED.lock(); - sched.create_thread(task, attrs) -} - -pub fn enqueue(uid: thread::UId) -> Result<(), KernelError> { - SCHED.lock().enqueue(uid) +pub fn init(kaddr_space: mem::vmm::AddressSpace) { + with(|sched| { + let uid = task::KERNEL_TASK; + if let Ok(task) = task::Task::from_addr_space(uid, kaddr_space) { + if sched.tasks.insert(&uid, task).is_err() { + panic!("failed to create kernel task."); + } + } else { + panic!("failed to create kernel address space."); + } + }) } pub fn needs_reschedule(now: u64) -> bool { @@ -196,41 +297,30 @@ pub fn enable() { /// Reschedule the tasks. pub fn reschedule() { + if DISABLED.load(Ordering::Acquire) { + return; + } + hal::Machine::trigger_reschedule(); } /// cbindgen:ignore /// cbindgen:no-export #[unsafe(no_mangle)] -pub extern "C" fn sched_enter(ctx: *mut c_void) -> *mut c_void { - let mut sched = SCHED.lock(); - let mut broken = false; - let old = sched.current; - - if sched.land(ctx).is_err() { - sched.current.inspect(|uid| { - if *uid == thread::IDLE_THREAD { - BUG!("failed to land the idle thread. something is horribly broken."); - } +pub extern "C" fn sched_enter(mut ctx: *mut c_void) -> *mut c_void { + with(|sched| { + let old = sched.current.map(|c| c.owner()); + sched.land(ctx); - // If we cannot reasonably land. We dequeue the thread. - sched.dequeue(*uid); - // TODO: Warn - sched.current = None; - broken = true; - }); - } - - if let Some((ctx, task)) = sched.do_sched(time::tick(), old) { - if let Some(old) = old - && task.id != old.owner() { + if let Some((new, task)) = sched.do_sched(time::tick()) { + if old != Some(task.id) { dispch::prepare(task); } - - ctx - } else if broken { - BUG!("failed to reschedule after a failed landing. something is horribly broken."); - } else { + ctx = new; + } else { + bug!("failed to schedule a thread. No threads available."); + } + ctx - } + }) } diff --git a/src/sched/rr.rs b/src/sched/rr.rs index c4613f2..ee9f877 100644 --- a/src/sched/rr.rs +++ b/src/sched/rr.rs @@ -1,10 +1,5 @@ use crate::{ - sched::{ - thread::{self}, - }, - types::{ - list::List, - }, + error::Result, sched::thread::{self}, types::list::List }; pub struct Scheduler { @@ -21,8 +16,8 @@ impl Scheduler { Self { queue: List::new(), current: None, current_left: 0, quantum: 1000 } } - pub fn enqueue(&mut self, uid: thread::UId, storage: &mut super::ThreadMap) -> Result<(), crate::utils::KernelError> { - self.queue.push_back(uid, storage).map_err(|_| crate::utils::KernelError::InvalidArgument) + pub fn enqueue(&mut self, uid: thread::UId, storage: &mut super::ThreadMap) -> Result<()> { + self.queue.push_back(uid, storage).map_err(|_| kerr!(InvalidArgument)) } pub fn put(&mut self, uid: thread::UId, dt: u64) { @@ -34,15 +29,29 @@ impl Scheduler { } pub fn pick(&mut self, storage: &mut super::ThreadMap) -> Option<(thread::UId, u64)> { - if self.current_left == 0 { - if let Some(current) = self.current { + match self.current { + Some(current) if self.current_left > 0 => return Some((current, self.current_left)), + Some(current) => { + self.queue.pop_front(storage); self.queue.push_back(current, storage); - } - self.current = self.queue.pop_front(storage).ok().flatten(); - self.current_left = self.quantum; + self.current = self.queue.head(); + self.current_left = self.quantum; + } + None => { + self.current = self.queue.head(); + self.current_left = self.quantum; + } } self.current.map(|id| (id, self.current_left)) } + + pub fn dequeue(&mut self, uid: thread::UId, storage: &mut super::ThreadMap) { + self.queue.remove(uid, storage); + + if self.current == Some(uid) { + self.current = None; + } + } } diff --git a/src/sched/rt.rs b/src/sched/rt.rs index b5a66b9..7c3405b 100644 --- a/src/sched/rt.rs +++ b/src/sched/rt.rs @@ -13,13 +13,20 @@ impl Scheduler { } } - pub fn enqueue(&mut self, uid: thread::UId, storage: &mut ServerView) { - self.edf.insert(uid, storage); + pub fn enqueue(&mut self, uid: thread::UId, now: u64, storage: &mut ServerView) { + if let Some(server) = storage.get_mut(uid) { + server.replenish(now); + self.edf.insert(uid, storage); + } } pub fn put(&mut self, uid: thread::UId, dt: u64, storage: &mut ServerView) { - if let Some(server) = storage.get_mut(uid) { - server.consume(dt); + if Some(uid) == self.edf.min() { + if let Some(server) = storage.get_mut(uid) { + server.consume(dt); + } else { + bug!("thread {} not found in storage", uid); + } } } diff --git a/src/sched/scheduler.rs b/src/sched/scheduler.rs deleted file mode 100644 index aded20b..0000000 --- a/src/sched/scheduler.rs +++ /dev/null @@ -1,206 +0,0 @@ -//! The scheduler module is responsible for managing the tasks and threads in the system. -//! It provides the necessary functions to create tasks and threads, and to switch between them. - -use core::{ffi::c_void, sync::atomic::AtomicBool}; - -use super::task::{Task, TaskId}; -use crate::{ - mem::{self, array::IndexMap, heap::BinaryHeap, queue::Queue}, - sched::{ - task::TaskDescriptor, - thread::{RunState, ThreadMap, ThreadUId, Timing}, - }, - sync::spinlock::SpinLocked, - utils, -}; - -/// The global scheduler instance. -pub static SCHEDULER: SpinLocked = SpinLocked::new(Scheduler::new()); -static SCHEDULER_ENABLED: AtomicBool = AtomicBool::new(false); - -/// The scheduler struct. It keeps track of the tasks and threads in the system. -/// This scheduler is a simple Rate Monotonic Scheduler (RMS) implementation. -#[derive(Debug)] -pub struct Scheduler { - /// The current running thread. - current: Option, - /// Fast interval store. This gets updated every time a new thread is selected. - current_interval: usize, - /// Stores the tasks in the system. - user_tasks: IndexMap, - /// Stores the threads in the system. - threads: ThreadMap<8>, - /// The priority queue that yields the next thread to run. - queue: BinaryHeap<(usize, ThreadUId), 32>, - /// The callbacks queue that stores the threads that need to be fired in the future. - callbacks: Queue<(ThreadUId, usize), 32>, - /// The progression of the time interval of the scheduler. - time: usize, -} - -impl Scheduler { - /// Create a new scheduler instance. - pub const fn new() -> Self { - Self { - current: None, - current_interval: 0, - user_tasks: IndexMap::new(), - threads: ThreadMap::new(), - queue: BinaryHeap::new(), - callbacks: Queue::new(), - time: 0, - } - } - - pub fn create_task(&mut self, desc: TaskDescriptor) -> Result { - let size = mem::align_up(desc.mem_size); - let idx = self - .user_tasks - .find_empty() - .ok_or(utils::KernelError::OutOfMemory)?; - let task_id = TaskId::new_user(idx); - - let task = Task::new(size, task_id)?; - self.user_tasks.insert(&idx, task)?; - Ok(task_id) - } - - pub fn create_thread( - &mut self, - entry: extern "C" fn(), - fin: Option !>, - timing: Timing, - task_id: TaskId, - ) -> Result { - let task_idx: usize = task_id.into(); - - if let Some(task) = self.user_tasks.get_mut(&task_idx) { - let desc = task.create_thread(entry, fin, timing)?; - let id = self.threads.create(desc)?; - self.queue.push((timing.period, id))?; - Ok(id) - } else { - Err(utils::KernelError::InvalidArgument) - } - } - - /// Updates the current thread context with the given context. - /// - /// `ctx` - The new context to update the current thread with. - fn update_current_ctx(&mut self, ctx: *mut c_void) { - if let Some(id) = self.current - && let Some(thread) = self.threads.get_mut(&id) - { - thread - .update_sp(ctx) - .expect("Failed to update thread context"); - } - } - - /// Selects a new thread to run, sets the previous thread as ready, and sets the new thread as runs. - /// The old thread will be added to the queue to be fired in the next period. - /// The new thread will be selected based on the priority queue. - /// - /// Returns the context of the new thread to run, or `None` if no thread is available. - fn select_new_thread(&mut self) -> Option<*mut c_void> { - if let Some(id) = self.queue.pop().map(|(_, id)| id) { - // Set the previous thread as ready. And add a callback from now. - if let Some(id) = self.current - && let Some(thread) = self.threads.get_mut(&id) - { - thread.update_run_state(RunState::Ready); - // The delay that is already in the queue. - let delay = self.callbacks.back().map(|(_, delay)| *delay).unwrap_or(0); - // Check if the period is already passed. - if thread.timing().period > (self.time + delay) { - // Add the callback to the queue. If it fails, we can't do much. - let _ = self - .callbacks - .push_back((id, thread.timing().period - (self.time + delay))); - } else { - // If the period is already passed, add it to the queue immediately. - let _ = self.queue.push((thread.timing().exec_time, id)); - } - } - - if let Some(thread) = self.threads.get_mut(&id) { - thread.update_run_state(RunState::Runs); - - // Set the new thread as the current one. - self.current_interval = thread.timing().exec_time; - self.current = Some(id); - - // Return the new thread context. - return Some(thread.sp()); - } - } - - None - } - - /// Fires the thread if necessary. - /// - /// Returns `true` if a thread was fired, otherwise `false`. - fn fire_thread_if_necessary(&mut self) -> bool { - let mut found = false; - while let Some((id, cnt)) = self.callbacks.front().cloned() { - // If the delay is 0, we can fire the thread. - if cnt - 1 == 0 { - self.callbacks.pop_front(); - if let Some(thread) = self.threads.get_mut(&id) { - thread.update_run_state(RunState::Ready); - - let _ = self.queue.push((thread.timing().exec_time, id)); - found = true; - } - } else { - // If the delay is not 0, we need to update the delay and reinsert it. - let _ = self.callbacks.insert(0, (id, cnt - 1)); - break; - } - } - - found - } - - /// Ticks the scheduler. This function is called every time the system timer ticks. - pub fn tick(&mut self) -> bool { - self.time += 1; - - // If a thread was fired, we need to reschedule. - if self.fire_thread_if_necessary() { - return true; - } - - // If the current thread is done, we need to reschedule. - if self.time >= self.current_interval { - self.time = 0; - return true; - } - - false - } -} - -pub fn enabled() -> bool { - SCHEDULER_ENABLED.load(core::sync::atomic::Ordering::Acquire) -} - -pub fn set_enabled(enabled: bool) { - SCHEDULER_ENABLED.store(enabled, core::sync::atomic::Ordering::Release); -} - -/// cbindgen:ignore -/// cbindgen:no-export -#[unsafe(no_mangle)] -pub extern "C" fn sched_enter(ctx: *mut c_void) -> *mut c_void { - { - let mut scheduler = SCHEDULER.lock(); - - // Update the current context. - scheduler.update_current_ctx(ctx); - - // Select a new thread to run, if available. - scheduler.select_new_thread().unwrap_or(ctx) - } -} diff --git a/src/sched/task.rs b/src/sched/task.rs index 9527f81..52b0345 100644 --- a/src/sched/task.rs +++ b/src/sched/task.rs @@ -1,4 +1,5 @@ //! This module provides the basic task and thread structures for the scheduler. +use core::fmt::Display; use core::num::NonZero; use core::borrow::Borrow; @@ -7,12 +8,12 @@ use hal::{Stack}; use hal::stack::{Stacklike}; +use crate::error::Result; use crate::sched::thread; use crate::{mem, sched}; use crate::mem::vmm::{AddressSpacelike}; use crate::types::traits::ToIndex; -use crate::utils::KernelError; pub struct Defaults { pub stack_pages: usize, @@ -50,6 +51,12 @@ impl ToIndex for UId { } } +impl Display for UId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Task-{}", self.uid) + } +} + pub struct Attributes { pub resrv_pgs: Option>, } @@ -65,14 +72,14 @@ pub struct Task { } impl Task { - pub fn new(id: UId, attrs: &Attributes) -> Result { + pub fn new(id: UId, attrs: &Attributes) -> Result { // TODO: On MMU systems, the resrv_pgs attribute will be ignored, as memory will not be reserved. - let resrv_pgs = attrs.resrv_pgs.ok_or(KernelError::OutOfMemory)?; + let resrv_pgs = attrs.resrv_pgs.ok_or(kerr!(InvalidArgument))?; let address_space = mem::vmm::AddressSpace::new(resrv_pgs.get())?; Self::from_addr_space(id, address_space) } - pub fn from_addr_space(id: UId, address_space: mem::vmm::AddressSpace) -> Result { + pub fn from_addr_space(id: UId, address_space: mem::vmm::AddressSpace) -> Result { Ok(Self { id, address_space, @@ -90,7 +97,7 @@ impl Task { fn allocate_stack( &mut self, attrs: &thread::Attributes, - ) -> Result { + ) -> Result { let size = DEFAULTS.stack_pages * mem::pfa::PAGE_SIZE; let region = mem::vmm::Region::new( None, @@ -112,7 +119,7 @@ impl Task { &mut self, uid: usize, attrs: &thread::Attributes, - ) -> Result { + ) -> Result { let stack = self.allocate_stack(attrs)?; let stack = unsafe { Stack::new(stack) }?; @@ -120,4 +127,8 @@ impl Task { Ok(sched::thread::Thread::new(tid.get_uid(uid), stack)) } + + pub fn tid_cntr(&self) -> usize { + self.tid_cntr + } } diff --git a/src/sched/thread.rs b/src/sched/thread.rs index 75a1890..86af4aa 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -1,17 +1,20 @@ // ----------------------------------- Identifiers ----------------------------------- +use core::fmt::Display; use core::{borrow::Borrow, ffi::c_void}; use hal::{Stack, stack::EntryFn}; use hal::stack::{FinFn, Stacklike}; use proc_macros::TaggedLinks; +use crate::error::Result; use crate::sched::task::{self, KERNEL_TASK}; +use crate::time::tick; use crate::types::list; -use crate::{types::{rbtree::{self, Compare}, traits::{Project, ToIndex}}, utils::KernelError}; +use crate::{types::{rbtree::{self, Compare}, traits::{Project, ToIndex}}}; pub const IDLE_THREAD: UId = UId { - uid: 0, + uid: 1, tid: Id { id: 0, owner: KERNEL_TASK }, }; @@ -94,6 +97,12 @@ impl ToIndex for UId { } } +impl Display for UId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "T{}-{}", self.tid.owner(), self.tid.as_usize()) + } +} + // ------------------------------------------------------------------------- /// The state of a thread. @@ -118,9 +127,8 @@ pub struct State { #[derive(TaggedLinks)] pub struct RtServer { budget: u64, - total_budget: u64, - - reservation: u64, + budget_left: u64, + period: u64, deadline: u64, // Back-reference to the thread uid. @@ -132,32 +140,35 @@ pub struct RtServer { } impl RtServer { - pub fn new(budget: u64, reservation: u64, deadline: u64, uid: UId) -> Self { + pub fn new(budget: u64, period: u64, uid: UId) -> Self { Self { budget, - total_budget: budget, - reservation, - deadline, + budget_left: budget, + period, + deadline: tick() + period, uid, _rt_links: rbtree::Links::new(), } } + pub fn budget_left(&self) -> u64 { + self.budget_left + } + pub fn budget(&self) -> u64 { self.budget } pub fn replenish(&mut self, now: u64) { - let next = self.deadline + self.reservation; - self.deadline = next.max(now + self.reservation); - self.budget = self.total_budget; + self.deadline += self.period; + self.budget_left = self.budget; } pub fn consume(&mut self, dt: u64) { - if self.budget >= dt { - self.budget -= dt; + if self.budget_left >= dt { + self.budget_left -= dt; } else { - self.budget = 0; + self.budget_left = 0; } } @@ -182,6 +193,46 @@ impl Compare for RtServer { } } +#[derive(Debug, Clone, Copy)] +#[derive(TaggedLinks)] +pub struct Waiter { + /// The time when the Thread will be awakened. + until: u64, + + // Back-reference to the thread uid. + uid: UId, + /// Wakup tree links for the thread. + #[rbtree(tag = WakupTree, idx = UId)] + _wakeup_links: rbtree::Links, +} + +impl Waiter { + pub fn new(until: u64, uid: UId) -> Self { + Self { + until, + uid, + _wakeup_links: rbtree::Links::new(), + } + } + + pub fn until(&self) -> u64 { + self.until + } + + pub fn set_until(&mut self, until: u64) { + self.until = until; + } +} + +impl Compare for Waiter { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + match self.until.cmp(&other.until) { + core::cmp::Ordering::Equal => self.uid.cmp(&other.uid), + ord => ord, + } + } +} + #[derive(Debug, Clone, Copy)] pub struct WakupTree; #[derive(Debug, Clone, Copy)] @@ -205,9 +256,8 @@ pub struct Thread { uid: UId, /// If the thread is real-time, its contains a constant bandwidth server. rt_server: Option, - /// Wakup tree links for the thread. - #[rbtree(tag = WakupTree, idx = UId)] - _wakeup_links: rbtree::Links, + + waiter: Option, #[list(tag = RRList, idx = UId)] rr_links: list::Links, @@ -228,12 +278,20 @@ impl Thread { }, uid, rt_server: None, - _wakeup_links: rbtree::Links::new(), + waiter: None, rr_links: list::Links::new(), } } - pub fn save_ctx(&mut self, ctx: *mut c_void) -> Result<(), KernelError> { + pub fn set_waiter(&mut self, waiter: Option) { + self.waiter = waiter; + } + + pub fn waiter(&self) -> Option<&Waiter> { + self.waiter.as_ref() + } + + pub fn save_ctx(&mut self, ctx: *mut c_void) -> Result<()> { let sp = self.state.stack.create_sp(ctx)?; self.state.stack.set_sp(sp); Ok(()) @@ -260,12 +318,6 @@ impl Thread { } } -impl Compare for Thread { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.uid.cmp(&other.uid) - } -} - impl Project for Thread { fn project(&self) -> Option<&RtServer> { self.rt_server.as_ref() @@ -274,4 +326,14 @@ impl Project for Thread { fn project_mut(&mut self) -> Option<&mut RtServer> { self.rt_server.as_mut() } -} \ No newline at end of file +} + +impl Project for Thread { + fn project(&self) -> Option<&Waiter> { + self.waiter.as_ref() + } + + fn project_mut(&mut self) -> Option<&mut Waiter> { + self.waiter.as_mut() + } +} diff --git a/src/sync/atomic.rs b/src/sync/atomic.rs index e3a4bde..a1f54be 100644 --- a/src/sync/atomic.rs +++ b/src/sync/atomic.rs @@ -15,16 +15,9 @@ pub use core::sync::atomic::Ordering; #[inline(always)] pub fn irq_free(f: impl FnOnce() -> T) -> T { - let enabled = hal::asm::are_interrupts_enabled(); - if enabled { - hal::asm::disable_interrupts(); - } - + let state = hal::asm::disable_irq_save(); let result = f(); - - if enabled { - hal::asm::enable_interrupts(); - } + hal::asm::enable_irq_restr(state); result } diff --git a/src/syscalls.rs b/src/syscalls.rs index eda3f92..c573d2f 100644 --- a/src/syscalls.rs +++ b/src/syscalls.rs @@ -3,11 +3,11 @@ use core::ffi::{c_int, c_uint}; mod file; -mod tasks; +mod sched; // We need to import everything so that the macro is able to find the entry functions. use file::*; -use tasks::*; +use sched::*; #[unsafe(no_mangle)] pub extern "C" fn handle_syscall(number: usize, args: *const c_uint) -> c_int { diff --git a/src/syscalls/sched.rs b/src/syscalls/sched.rs new file mode 100644 index 0000000..38688dd --- /dev/null +++ b/src/syscalls/sched.rs @@ -0,0 +1,29 @@ +//! This module provides task management related syscalls. + +use core::ffi::c_int; + +use proc_macros::syscall_handler; + +use crate::{sched, time}; + +#[syscall_handler(num = 1)] +fn sleep(until_hi: u32, until_lo: u32) -> c_int { + let until = ((until_hi as u64) << 32) | (until_lo as u64); + sched::with(|sched| { + sched.sleep_until(until, time::tick()); + }); + 0 +} + +#[syscall_handler(num = 2)] +fn sleep_for(duration_hi: u32, duration_lo: u32) -> c_int { + let duration = ((duration_hi as u64) << 32) | (duration_lo as u64); + sched::with(|sched| { + let now = time::tick(); + if sched.sleep_until(now + duration, now).is_err() { + panic!("failed to sleep for duration: {duration}"); + } + }); + 0 +} + diff --git a/src/syscalls/tasks.rs b/src/syscalls/tasks.rs deleted file mode 100644 index 2a6d68d..0000000 --- a/src/syscalls/tasks.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! This module provides task management related syscalls. - -use core::ffi::c_int; - -/* -use crate::sched; -use macros::syscall_handler; - -/// Syscall handler: reschedule. -/// This syscall is used to request a reschedule. -/// -/// No arguments are passed to this syscall. -#[syscall_handler(num = 1)] -fn syscall_reschedule() -> c_int { - sched::reschedule(); - 0 -} - -#[syscall_handler(num = 2)] -fn syscall_exec(entry: usize) -> c_int { - let entry: extern "C" fn() -> () = unsafe { core::mem::transmute(entry) }; - - let timing = sched::thread::Timing { - period: 8, - deadline: 8, - exec_time: 2, - }; - - sched::create_task(sched::task::TaskDescriptor { mem_size: 0 }) - .and_then(|task| sched::create_thread(task, entry, None, timing)) - .map(|_| 0) - .unwrap_or(-1) -}*/ diff --git a/src/types/array.rs b/src/types/array.rs index 3e50514..67a9cba 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -1,12 +1,12 @@ //! This module implements static and dynamic arrays for in-kernel use. +use crate::error::Result; + use super::{ traits::{Get, GetMut, ToIndex}, boxed::Box, }; -use crate::utils::KernelError; - use core::{borrow::Borrow, mem::MaybeUninit}; use core::{ ops::{Index, IndexMut}, @@ -39,14 +39,14 @@ impl IndexMap /// `value` - The value to insert. /// /// Returns `Ok(())` if the index was in-bounds, otherwise `Err(KernelError::OutOfMemory)`. - pub fn insert(&mut self, idx: &K, value: V) -> Result<(), KernelError> { + pub fn insert(&mut self, idx: &K, value: V) -> Result<()> { let idx = K::to_index(Some(idx)); if idx < N { self.data[idx] = Some(value); Ok(()) } else { - Err(KernelError::OutOfMemory) + Err(kerr!(OutOfMemory)) } } @@ -55,7 +55,7 @@ impl IndexMap /// `value` - The value to insert. /// /// Returns `Ok(index)` if the value was inserted, otherwise `Err(KernelError::OutOfMemory)`. - pub fn insert_next(&mut self, value: V) -> Result { + pub fn insert_next(&mut self, value: V) -> Result { for (i, slot) in self.data.iter_mut().enumerate() { if slot.is_none() { *slot = Some(value); @@ -63,7 +63,7 @@ impl IndexMap } } - Err(KernelError::OutOfMemory) + Err(kerr!(OutOfMemory)) } /// Remove the value at the given index. @@ -113,6 +113,14 @@ impl IndexMap None } + pub fn at_cont(&self, idx: usize) -> Option<&V> { + if idx < N { + self.data[idx].as_ref() + } else { + None + } + } + pub fn find_empty(&self) -> Option { for (i, slot) in self.data.iter().enumerate() { if slot.is_none() { @@ -237,7 +245,7 @@ impl Vec { /// `additional` - The additional space to reserve. /// /// Returns `Ok(())` if the space was reserved, otherwise `Err(KernelError::OutOfMemory)`. - pub fn reserve(&mut self, additional: usize) -> Result<(), KernelError> { + pub fn reserve(&mut self, additional: usize) -> Result<()> { let len_extra = self.extra.len(); // Check if we have enough space in the inline storage. @@ -250,7 +258,7 @@ impl Vec { let mut new_extra = Box::new_slice_uninit(grow)?; // Check that the new extra storage has the requested length. - BUG_ON!(new_extra.len() != grow); + bug_on!(new_extra.len() != grow); // Copy the old extra storage into the new one. new_extra[..len_extra].copy_from_slice(&self.extra); @@ -265,7 +273,7 @@ impl Vec { /// `total_capacity` - The total space to be reserved. /// /// Returns `Ok(())` if the space was reserved, otherwise `Err(KernelError::OutOfMemory)`. - pub fn reserve_total_capacity(&mut self, total_capacity: usize) -> Result<(), KernelError> { + pub fn reserve_total_capacity(&mut self, total_capacity: usize) -> Result<()> { // Check if we already have enough space if self.capacity() >= total_capacity { return Ok(()); @@ -276,7 +284,7 @@ impl Vec { let mut new_extra = Box::new_slice_uninit(new_out_of_line_cap)?; // Check that the new extra storage has the requested length. - BUG_ON!(new_extra.len() != new_out_of_line_cap); + bug_on!(new_extra.len() != new_out_of_line_cap); let curr_out_of_line_size = self.extra.len(); // Copy the old extra storage into the new one. @@ -293,7 +301,7 @@ impl Vec { /// `value` - The value to initialize the elements in the Vec with. /// /// Returns the new Vec or `Err(KernelError::OutOfMemory)` if the allocation failed. - pub fn new_init(length: usize, value: T) -> Result { + pub fn new_init(length: usize, value: T) -> Result { let mut vec = Self::new(); // Check if we can fit all elements in the inline storage. @@ -329,7 +337,7 @@ impl Vec { /// `value` - The value to push. /// /// Returns `Ok(())` if the value was pushed, otherwise `Err(KernelError::OutOfMemory)`. - pub fn push(&mut self, value: T) -> Result<(), KernelError> { + pub fn push(&mut self, value: T) -> Result<()> { // Check if we have enough space in the inline storage. if self.len < N { // Push the value into the inline storage. @@ -350,7 +358,7 @@ impl Vec { let grow = (len_extra + 1) * 2; let mut new_extra = Box::new_slice_uninit(grow)?; - BUG_ON!(new_extra.len() != grow); + bug_on!(new_extra.len() != grow); // Copy the old extra storage into the new one. new_extra[..len_extra].copy_from_slice(&self.extra); diff --git a/src/types/boxed.rs b/src/types/boxed.rs index 3e2277a..0f23e56 100644 --- a/src/types/boxed.rs +++ b/src/types/boxed.rs @@ -1,7 +1,7 @@ //! This module provides a simple heap-allocated memory block for in-kernel use. -use crate::mem; -use crate::utils::KernelError; +use crate::{error::Result, mem}; + use core::{ mem::{MaybeUninit, forget}, ops::{Deref, DerefMut, Index, IndexMut, Range, RangeFrom, RangeTo}, @@ -23,7 +23,7 @@ impl Box<[T]> { /// `len` - The length of the slice. /// /// Returns a new heap-allocated slice with the given length or an error if the allocation failed. - pub fn new_slice_zeroed(len: usize) -> Result { + pub fn new_slice_zeroed(len: usize) -> Result { if len == 0 { return Ok(Self::new_slice_empty()); } @@ -34,7 +34,7 @@ impl Box<[T]> { ptr: unsafe { NonNull::new_unchecked(ptr) }, }) } else { - Err(KernelError::OutOfMemory) + Err(kerr!(OutOfMemory)) } } @@ -53,7 +53,7 @@ impl Box<[T]> { /// `len` - The length of the slice. /// /// Returns a new heap-allocated slice with the given length or an error if the allocation failed. - pub fn new_slice_uninit(len: usize) -> Result]>, KernelError> { + pub fn new_slice_uninit(len: usize) -> Result]>> { if let Some(ptr) = mem::malloc( size_of::>() * len, align_of::>(), @@ -63,7 +63,7 @@ impl Box<[T]> { ptr: unsafe { NonNull::new_unchecked(ptr) }, }) } else { - Err(KernelError::OutOfMemory) + Err(kerr!(OutOfMemory)) } } } diff --git a/src/types/heap.rs b/src/types/heap.rs index b3b0af3..aa0b579 100644 --- a/src/types/heap.rs +++ b/src/types/heap.rs @@ -1,7 +1,8 @@ //! This module provides a binary heap implementation. +use crate::error::Result; + use super::array::Vec; -use crate::utils::KernelError; /// An array-based binary heap, with N elements stored inline. #[derive(Debug)] @@ -20,7 +21,7 @@ impl BinaryHeap { /// `value` - The value to push onto the binary heap. /// /// Returns `Ok(())` if the value was pushed onto the binary heap, or an error if the heap cannot be extended (e.g. OOM). - pub fn push(&mut self, value: T) -> Result<(), KernelError> { + pub fn push(&mut self, value: T) -> Result<()> { self.vec.push(value)?; self.sift_up(self.len() - 1); Ok(()) diff --git a/src/types/list.rs b/src/types/list.rs index d3c9779..bb64980 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -188,9 +188,23 @@ impl List { where >::Output: Linkable, { - let node = storage.get_mut(id).ok_or(())?; - node.links_mut().prev = None; - node.links_mut().next = None; + let linked = { + let node = storage.get(id).ok_or(())?; + let links = node.links(); + self.head == Some(id) + || self.tail == Some(id) + || links.prev.is_some() + || links.next.is_some() + }; + + if linked { + self.remove(id, storage)?; + } else { + let node = storage.get_mut(id).ok_or(())?; + node.links_mut().prev = None; + node.links_mut().next = None; + } + Ok(()) } } @@ -239,7 +253,7 @@ mod tests { fn storage() -> IndexMap { let mut map = IndexMap::new(); for i in 0..4 { - map.insert(&Id(i), Node::new()).unwrap(); + assert!(map.insert(&Id(i), Node::new()).is_ok()); } map } @@ -268,6 +282,36 @@ mod tests { assert_eq!(n1.links().prev, Some(Id(3))); } + #[test] + fn push_back_and_remove() { + let mut s = storage(); + let mut list = List::::new(); + + list.push_back(Id(1), &mut s).unwrap(); + list.remove(Id(1), &mut s); + + assert_eq!(list.head(), None); + assert_eq!(list.tail(), None); + assert_eq!(list.len(), 0); + } + + #[test] + fn push_back_same_id_reinserts() { + let mut s = storage(); + let mut list = List::::new(); + + list.push_back(Id(1), &mut s).unwrap(); + list.push_back(Id(1), &mut s).unwrap(); + + assert_eq!(list.head(), Some(Id(1))); + assert_eq!(list.tail(), Some(Id(1))); + assert_eq!(list.len(), 1); + + let n1 = s.get(Id(1)).unwrap(); + assert_eq!(n1.links().prev, None); + assert_eq!(n1.links().next, None); + } + #[test] fn pop_back_ordered() { let mut s = storage(); diff --git a/src/types/rbtree.rs b/src/types/rbtree.rs index 969df19..887e386 100644 --- a/src/types/rbtree.rs +++ b/src/types/rbtree.rs @@ -61,6 +61,19 @@ impl RbTree pub fn insert + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> where >::Output: Linkable + Compare,{ + let already_linked = { + let node = storage.get(id).ok_or(())?; + let links = node.links(); + self.root == Some(id) + || links.parent.is_some() + || links.left.is_some() + || links.right.is_some() + }; + + if already_linked { + self.remove(id, storage)?; + } + let mut last = None; { @@ -871,6 +884,24 @@ mod tests { validate_tree(&tree, &store, &keys); } + #[test] + fn reinsert_same_id_is_stable() { + let keys = vec![10, 5, 15]; + let mut store = NodeStore::new(&keys); + let mut tree = RbTree::new(); + + tree.insert(0, &mut store).unwrap(); + tree.insert(1, &mut store).unwrap(); + tree.insert(2, &mut store).unwrap(); + + // Reinsert existing node id. This should not create duplicate structural links. + tree.insert(1, &mut store).unwrap(); + + let mut expected = keys.clone(); + expected.sort(); + validate_tree(&tree, &store, &expected); + } + #[test] fn min_updates_on_insert_and_remove() { let keys = vec![10, 5, 15, 3, 7, 12, 18, 1, 6]; diff --git a/src/uapi.rs b/src/uapi.rs new file mode 100644 index 0000000..bfc9b49 --- /dev/null +++ b/src/uapi.rs @@ -0,0 +1,2 @@ +pub mod print; +pub mod sched; \ No newline at end of file diff --git a/src/uapi/print.rs b/src/uapi/print.rs new file mode 100644 index 0000000..d148107 --- /dev/null +++ b/src/uapi/print.rs @@ -0,0 +1,24 @@ +use core::fmt::{self, Write}; + +use hal::Machinelike; + +#[macro_export] +macro_rules! uprintln { + ($($arg:tt)*) => ({ + use core::fmt::Write; + use osiris::uapi::print::Printer; + + let mut printer = Printer; + printer.write_fmt(format_args!($($arg)*)).unwrap(); + printer.write_str("\n").unwrap(); + }); +} + +pub struct Printer; + +impl Write for Printer { + fn write_str(&mut self, s: &str) -> fmt::Result { + hal::Machine::print(s).map_err(|_| fmt::Error)?; + Ok(()) + } +} \ No newline at end of file diff --git a/src/uapi/sched.rs b/src/uapi/sched.rs new file mode 100644 index 0000000..43df677 --- /dev/null +++ b/src/uapi/sched.rs @@ -0,0 +1,8 @@ + +pub fn sleep(until: u64) { + hal::asm::syscall!(1, (until >> 32) as u32, until as u32); +} + +pub fn sleep_for(duration: u64) { + hal::asm::syscall!(2, (duration >> 32) as u32, duration as u32); +} \ No newline at end of file diff --git a/src/uspace.rs b/src/uspace.rs index d6799de..bc40be2 100644 --- a/src/uspace.rs +++ b/src/uspace.rs @@ -1,6 +1,6 @@ //! This module provides access to userspace structures and services. -use crate::sched; +use crate::{sched, time}; unsafe extern "C" { /// The entry point for the userspace application. @@ -11,12 +11,18 @@ extern "C" fn app_main_entry() { unsafe { app_main() } } -pub fn init_app() -> Result<(), crate::utils::KernelError> { +pub fn init_app() { let attrs = sched::thread::Attributes { entry: app_main_entry, fin: None, }; - let uid = sched::create_thread(sched::task::KERNEL_TASK, &attrs)?; - - sched::enqueue(uid) + sched::with(|sched| { + if let Ok(uid) = sched.create_thread(sched::task::KERNEL_TASK, &attrs) { + if sched.enqueue(time::tick(), uid).is_err() { + panic!("failed to enqueue init thread."); + } + } else { + panic!("failed to create init task."); + } + }) } diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index 0f1bd71..0000000 --- a/src/utils.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! Utility functions and definitions for the kernel. -#![cfg_attr(feature = "nightly", feature(likely_unlikely))] - -use core::fmt::Debug; -use core::ptr::NonNull; -use core::mem::offset_of; - -/// These two definitions are copied from https://github.com/rust-lang/hashbrown -#[cfg(not(feature = "nightly"))] -#[allow(unused_imports)] -pub(crate) use core::convert::{identity as likely, identity as unlikely}; - -#[cfg(feature = "nightly")] -pub(crate) use core::hint::{likely, unlikely}; - -use hal::mem::PhysAddr; - - - -/// This is a macro that is used to panic when a bug is detected. -/// It is similar to the BUG() macro in the Linux kernel. Link: [https://www.kernel.org/]() -#[macro_export] -macro_rules! BUG { - () => { - panic!("BUG triggered at {}:{}", file!(), line!()); - }; - ($msg:expr) => { - panic!("BUG triggered: {} at {}:{}", $msg, file!(), line!()); - }; -} - -/// This is a macro that is used to panic when a condition is true. -/// It is similar to the BUG_ON() macro in the Linux kernel. Link: [https://www.kernel.org/]() -#[macro_export] -macro_rules! BUG_ON { - ($cond:expr) => {{ - let cond = $cond; - #[allow(unused_unsafe)] - if unsafe { $crate::utils::unlikely(cond) } { - BUG!(); - } - }}; - ($cond:expr, $msg:expr) => {{ - let cond = $cond; - #[allow(unused_unsafe)] - if unsafe { $crate::utils::unlikely(cond) } { - BUG!($msg); - } - }}; -} - -/// The error type that is returned when an error in the kernel occurs. -#[derive(PartialEq, Eq, Clone)] -pub enum KernelError { - /// The alignment is invalid. - InvalidAlign, - /// The kernel is out of memory. - OutOfMemory, - InvalidSize, - InvalidAddress(PhysAddr), - InvalidArgument, - HalError(hal::Error), - CustomError(&'static str), -} - -/// Debug msg implementation for KernelError. -impl Debug for KernelError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - KernelError::InvalidAlign => write!(f, "Invalid alignment"), - KernelError::OutOfMemory => write!(f, "Out of memory"), - KernelError::InvalidSize => write!(f, "Invalid size"), - KernelError::InvalidAddress(addr) => write!(f, "Invalid address ({})", addr), - KernelError::InvalidArgument => write!(f, "Invalid argument"), - KernelError::HalError(e) => write!(f, "{e} (in HAL)"), - KernelError::CustomError(msg) => write!(f, "{}", msg), - } - } -} - -impl From for KernelError { - fn from(err: hal::Error) -> Self { - KernelError::HalError(err) - } -} From 8816105e8280682b90325ed5727d4b66c2d24e26 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Sat, 28 Mar 2026 17:17:06 +0000 Subject: [PATCH 26/42] cleanup and fixes. --- machine/arm/src/asm.rs | 117 +++++++++++++++----------- machine/testing/src/asm.rs | 10 +-- macros/src/lib.rs | 50 ------------ src/mem/alloc/bestfit.rs | 14 ++-- src/sched.rs | 93 ++++++++++++++------- src/syscalls/sched.rs | 6 +- src/types/list.rs | 49 +++++++---- src/types/rbtree.rs | 163 +++++++++++++++++++++++++------------ src/types/view.rs | 5 ++ src/uapi/sched.rs | 10 +-- 10 files changed, 302 insertions(+), 215 deletions(-) diff --git a/machine/arm/src/asm.rs b/machine/arm/src/asm.rs index b69e90e..1ba1ebb 100644 --- a/machine/arm/src/asm.rs +++ b/machine/arm/src/asm.rs @@ -20,59 +20,84 @@ pub use crate::__macro_nop as nop; #[macro_export] macro_rules! __macro_syscall { ($num:expr) => { - use core::arch::asm; - unsafe { - asm!("svc {num}", num = const $num, clobber_abi("C")); + { + use core::arch::asm; + let ret: isize; + unsafe { + asm!( + "svc {num}", + lateout("r0") ret, + num = const $num, + clobber_abi("C") + ); + } + ret } }; ($num:expr, $arg0:expr) => { - use core::arch::asm; - unsafe { - asm!( - "svc {num}", - in("r0") $arg0, - num = const $num, - clobber_abi("C") - ); + { + use core::arch::asm; + let ret: isize; + unsafe { + asm!( + "svc {num}", + inlateout("r0") $arg0 => ret, + num = const $num, + clobber_abi("C") + ); + } + ret } }; ($num:expr, $arg0:expr, $arg1:expr) => { - use core::arch::asm; - unsafe { - asm!( - "svc {num}", - in("r0") $arg0, - in("r1") $arg1, - num = const $num, - clobber_abi("C") - ); + { + use core::arch::asm; + let ret: isize; + unsafe { + asm!( + "svc {num}", + inlateout("r0") $arg0 => ret, + in("r1") $arg1, + num = const $num, + clobber_abi("C") + ); + } + ret } }; ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => { - use core::arch::asm; - unsafe { - asm!( - "svc {num}", - in("r0") $arg0, - in("r1") $arg1, - in("r2") $arg2, - num = const $num, - clobber_abi("C") - ); + { + use core::arch::asm; + let ret: isize; + unsafe { + asm!( + "svc {num}", + inlateout("r0") $arg0 => ret, + in("r1") $arg1, + in("r2") $arg2, + num = const $num, + clobber_abi("C") + ); + } + ret } }; ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => { - use core::arch::asm; - unsafe { - asm!( - "svc {num}", - in("r0") $arg0, - in("r1") $arg1, - in("r2") $arg2, - in("r3") $arg3, - num = const $num, - clobber_abi("C") - ); + { + use core::arch::asm; + let ret: isize; + unsafe { + asm!( + "svc {num}", + inlateout("r0") $arg0 => ret, + in("r1") $arg1, + in("r2") $arg2, + in("r3") $arg3, + num = const $num, + clobber_abi("C") + ); + } + ret } }; } @@ -80,11 +105,11 @@ macro_rules! __macro_syscall { #[cfg(feature = "host")] #[macro_export] macro_rules! __macro_syscall { - ($num:expr) => {{}}; - ($num:expr, $arg0:expr) => {{}}; - ($num:expr, $arg0:expr, $arg1:expr) => {{}}; - ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => {{}}; - ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {{}}; + ($num:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr, $arg1:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {{ 0isize }}; } pub use crate::__macro_syscall as syscall; diff --git a/machine/testing/src/asm.rs b/machine/testing/src/asm.rs index a303684..fbd7dd5 100644 --- a/machine/testing/src/asm.rs +++ b/machine/testing/src/asm.rs @@ -11,11 +11,11 @@ pub use crate::__macro_nop as nop; /// Macro for doing a system call. #[macro_export] macro_rules! __macro_syscall { - ($num:expr) => {{}}; - ($num:expr, $arg0:expr) => {{}}; - ($num:expr, $arg0:expr, $arg1:expr) => {{}}; - ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => {{}}; - ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {{}}; + ($num:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr, $arg1:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr) => {{ 0isize }}; + ($num:expr, $arg0:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {{ 0isize }}; } pub use crate::__macro_syscall as syscall; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 2387d03..b2084a6 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -42,56 +42,6 @@ pub fn app_main(input: proc_macro::TokenStream, item: proc_macro::TokenStream) - expanded.into() } -#[proc_macro_attribute] -pub fn service( - attr: proc_macro::TokenStream, - item: proc_macro::TokenStream, -) -> proc_macro::TokenStream { - // This macro should be used to annotate a service struct. - let item = syn::parse_macro_input!(item as syn::ItemStruct); - - let service_name = item.ident.clone(); - - let mut mem_size: usize = 0; - let mut stack_size: usize = 0; - - let parser = syn::meta::parser(|meta| { - if meta.path.is_ident("mem_size") { - mem_size = meta.value()?.parse::()?.base10_parse()?; - Ok(()) - } else if meta.path.is_ident("stack_size") { - stack_size = meta.value()?.parse::()?.base10_parse()?; - Ok(()) - } else { - Err(meta.error("unknown attribute")) - } - }); - - parse_macro_input!(attr with parser); - - let mem_size_ident = format_ident!("TASK_{}_MEM_SIZE", service_name.to_string().to_uppercase()); - let stack_size_ident = format_ident!( - "TASK_{}_STACK_SIZE", - service_name.to_string().to_uppercase() - ); - - let expanded = quote::quote! { - const #mem_size_ident: usize = #mem_size; - const #stack_size_ident: usize = #stack_size; - #item - - impl #service_name { - pub fn task_desc() -> crate::sched::task::TaskDescriptor { - crate::sched::task::TaskDescriptor { - mem_size: #mem_size_ident, - } - } - } - }; - - expanded.into() -} - const SYSCALL_MAX_ARGS: usize = 4; fn is_return_type_register_sized_check( diff --git a/src/mem/alloc/bestfit.rs b/src/mem/alloc/bestfit.rs index 9c91fa1..00db5a7 100644 --- a/src/mem/alloc/bestfit.rs +++ b/src/mem/alloc/bestfit.rs @@ -24,7 +24,7 @@ pub struct BestFitAllocator { /// Implementation of the BestFitAllocator. impl BestFitAllocator { - pub const MIN_RANGE_SIZE: usize = size_of::() + Self::align_up() + 1; + pub const MIN_RANGE_SIZE: usize = size_of::() + Self::align_up() + 1; /// Creates a new BestFitAllocator. /// @@ -50,7 +50,11 @@ impl BestFitAllocator { // Check if the pointer is 128bit aligned. if !ptr.is_multiple_of(align_of::()) { return Err(kerr!(InvalidArgument)); - } + } + + if range.end.diff(range.start) < Self::MIN_RANGE_SIZE { + return Err(kerr!(InvalidArgument)); + } debug_assert!(range.end > range.start); debug_assert!(range.end.diff(range.start) > size_of::() + Self::align_up()); @@ -192,13 +196,13 @@ impl super::Allocator for BestFitAllocator { /// Returns the user pointer to the block if successful, otherwise an error. fn malloc(&mut self, size: usize, align: usize, request: Option) -> Result> { // Check if the alignment is valid. - if align > align_of::() { - return Err(kerr!(InvalidArgument)); + if align == 0 || align > align_of::() { + return Err(kerr!(InvalidAlign)); } if let Some(request) = request { if !request.is_multiple_of(align) { - return Err(kerr!(InvalidArgument)); + return Err(kerr!(InvalidAlign)); } } diff --git a/src/sched.rs b/src/sched.rs index 39583d8..e3cd1e5 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -22,7 +22,7 @@ use crate::{ types::{ array::IndexMap, rbtree::RbTree, - traits::{Get, GetMut, Project}, + traits::{Get, GetMut}, view::ViewMut, }, }; @@ -85,11 +85,15 @@ impl Scheduler { if let Some(task_id) = kill { self.dequeue(current); self.current = None; - self.kill_task(task_id); + if self.kill_task(task_id).is_err() { + // Should not be possible. The thread exists, so the task must exist. + bug!("failed to kill task {}", task_id); + } } } } + /// Triggers a reschedule at *latest* when we hit timepoint `next`. fn schedule_resched(now: u64, next: u64) { let old = NEXT_TICK.load(Ordering::Acquire); @@ -107,7 +111,12 @@ impl Scheduler { let mut view = rt::ServerView::::new(&mut self.threads); self.rt_scheduler.enqueue(uid, now, &mut view); } else { - self.rr_scheduler.enqueue(uid, &mut self.threads)?; + if self.rr_scheduler.enqueue(uid, &mut self.threads).is_err() { + // This should not be possible. + // - Thread is in the thread list. + // - Thread is not linked into a different list. + bug!("failed to enqueue thread {} into RR scheduler.", uid); + } } reschedule(); Ok(()) @@ -115,19 +124,27 @@ impl Scheduler { fn do_wakeups(&mut self, now: u64) { while let Some(uid) = self.wakeup.min() { - { - let mut view = WaiterView::::new(&mut self.threads); + let mut done = false; + WaiterView::::with(&mut self.threads, |view| { let waiter = view.get(uid).expect("THIS IS A BUG!"); - if waiter.until() > now { Self::schedule_resched(now, waiter.until()); - break; + done = true; + return; + } + + if let Err(_) = self.wakeup.remove(uid, view) { + bug!("failed to remove thread {} from wakeup tree.", uid); } + }); - self.wakeup.remove(uid, &mut view); + if done { + break; } - self.enqueue(now, uid); + if self.enqueue(now, uid).is_err() { + bug!("failed to enqueue thread {} after wakeup.", uid); + } } } @@ -136,28 +153,35 @@ impl Scheduler { self.last_tick = now; if let Some(old) = self.current { - let mut view = rt::ServerView::::new(&mut self.threads); - self.rt_scheduler.put(old, dt, &mut view); + rt::ServerView::::with(&mut self.threads, |view| { + self.rt_scheduler.put(old, dt, view); + }); self.rr_scheduler.put(old, dt); } self.do_wakeups(now); - let mut view = rt::ServerView::::new(&mut self.threads); + let pick = + rt::ServerView::::with(&mut self.threads, |view| self.rt_scheduler.pick(now, view)); + let pick = pick.or_else(|| self.rr_scheduler.pick(&mut self.threads)); + let (new, budget) = pick.unwrap_or((thread::IDLE_THREAD, 1000)); - let (new, budget) = self - .rt_scheduler - .pick(now, &mut view) - .or_else(|| self.rr_scheduler.pick(&mut self.threads)) - .unwrap_or((thread::IDLE_THREAD, 1000)); + // At this point, the task/thread must exist. Everything else is a bug. + let (ctx, task_id) = if let Some(thread) = self.threads.get(new) { + (thread.ctx(), thread.task_id()) + } else { + bug!("failed to pick thread {}. Does not exist.", new); + }; - let ctx = self.threads.get(new)?.ctx(); - let task = self.tasks.get_mut(self.threads.get(new)?.task_id())?; + let task = if let Some(task) = self.tasks.get_mut(task_id) { + task + } else { + bug!("failed to get task {}. Does not exist.", task_id); + }; + // We don't need to resched if the thread has budget. self.current = Some(new); - let next = now.saturating_add(budget); - - Self::schedule_resched(now, next); + Self::schedule_resched(now, now.saturating_add(budget)); Some((ctx, task)) } @@ -170,6 +194,7 @@ impl Scheduler { if let Some(thread) = self.threads.get_mut(uid) { thread.set_waiter(Some(Waiter::new(until, uid))); } else { + // This should not be possible. The thread must exist since it's the current thread. bug!( "failed to put current thread {} to sleep. Does not exist.", uid @@ -181,27 +206,33 @@ impl Scheduler { .insert(uid, &mut WaiterView::::new(&mut self.threads)) .is_err() { + // This should not be possible. The thread exists. bug!("failed to insert thread {} into wakeup tree.", uid); } - - self.dequeue(uid); reschedule(); Ok(()) } pub fn kick(&mut self, uid: thread::UId) -> Result<()> { - let thread = self.threads.get_mut(uid).ok_or(kerr!(InvalidArgument))?; - if let Some(waiter) = Project::::project_mut(thread) { - waiter.set_until(0); - } - Ok(()) + WaiterView::::with(&mut self.threads, |view| { + self.wakeup.remove(uid, view)?; + let thread = view.get_mut(uid).unwrap_or_else(|| { + bug!("failed to get thread {} from wakeup tree.", uid); + }); + thread.set_until(0); + self.wakeup.insert(uid, view).unwrap_or_else(|_| { + bug!("failed to re-insert thread {} into wakeup tree.", uid); + }); + Ok(()) + }) } pub fn dequeue(&mut self, uid: thread::UId) { - let mut view = rt::ServerView::::new(&mut self.threads); - self.rt_scheduler.dequeue(uid, &mut view); + rt::ServerView::::with(&mut self.threads, |view| { + self.rt_scheduler.dequeue(uid, view); + }); self.rr_scheduler.dequeue(uid, &mut self.threads); } diff --git a/src/syscalls/sched.rs b/src/syscalls/sched.rs index 38688dd..2b7d434 100644 --- a/src/syscalls/sched.rs +++ b/src/syscalls/sched.rs @@ -10,7 +10,9 @@ use crate::{sched, time}; fn sleep(until_hi: u32, until_lo: u32) -> c_int { let until = ((until_hi as u64) << 32) | (until_lo as u64); sched::with(|sched| { - sched.sleep_until(until, time::tick()); + if sched.sleep_until(until, time::tick()).is_err() { + bug!("no current thread set."); + } }); 0 } @@ -21,7 +23,7 @@ fn sleep_for(duration_hi: u32, duration_lo: u32) -> c_int { sched::with(|sched| { let now = time::tick(); if sched.sleep_until(now + duration, now).is_err() { - panic!("failed to sleep for duration: {duration}"); + bug!("no current thread set."); } }); 0 diff --git a/src/types/list.rs b/src/types/list.rs index bb64980..7156d29 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -1,5 +1,7 @@ use core::marker::PhantomData; +use crate::error::Result; + use super::traits::{Get, GetMut}; #[allow(dead_code)] @@ -62,7 +64,7 @@ impl List { self.len == 0 } - pub fn push_front + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + pub fn push_front + GetMut>(&mut self, id: T, storage: &mut S) -> Result<()> where >::Output: Linkable, { @@ -71,7 +73,9 @@ impl List { match self.head { Some(old_head) => { let (new_node, old_head_node) = storage.get2_mut(id, old_head); - let (new_node, old_head_node) = (new_node.ok_or(())?, old_head_node.ok_or(())?); + let (new_node, old_head_node) = (new_node.ok_or(kerr!(NotFound))?, old_head_node.unwrap_or_else(|| { + bug!("node linked from list does not exist in storage."); + })); new_node.links_mut().prev = None; new_node.links_mut().next = Some(old_head); @@ -79,7 +83,7 @@ impl List { old_head_node.links_mut().prev = Some(id); } None => { - let new_node = storage.get_mut(id).ok_or(())?; + let new_node = storage.get_mut(id).ok_or(kerr!(NotFound))?; new_node.links_mut().prev = None; new_node.links_mut().next = None; self.tail = Some(id); @@ -91,7 +95,10 @@ impl List { Ok(()) } - pub fn push_back + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + /// Pushes `id` to the back of the list. If `id` is already in the list, it is moved to the back. + /// + /// Errors if `id` does not exist in `storage` or if the node corresponding to `id` is linked but not in the list. + pub fn push_back + GetMut>(&mut self, id: T, storage: &mut S) -> Result<()> where >::Output: Linkable, { @@ -100,7 +107,9 @@ impl List { match self.tail { Some(old_tail) => { let (new_node, old_tail_node) = storage.get2_mut(id, old_tail); - let (new_node, old_tail_node) = (new_node.ok_or(())?, old_tail_node.ok_or(())?); + let (new_node, old_tail_node) = (new_node.ok_or(kerr!(NotFound))?, old_tail_node.unwrap_or_else(|| { + bug!("node linked from list does not exist in storage."); + })); new_node.links_mut().next = None; new_node.links_mut().prev = Some(old_tail); @@ -108,7 +117,7 @@ impl List { old_tail_node.links_mut().next = Some(id); } None => { - let new_node = storage.get_mut(id).ok_or(())?; + let new_node = storage.get_mut(id).ok_or(kerr!(NotFound))?; new_node.links_mut().next = None; new_node.links_mut().prev = None; self.head = Some(id); @@ -120,7 +129,7 @@ impl List { Ok(()) } - pub fn pop_front + GetMut>(&mut self, storage: &mut S) -> Result, ()> + pub fn pop_front + GetMut>(&mut self, storage: &mut S) -> Result> where >::Output: Linkable, { @@ -132,7 +141,7 @@ impl List { Ok(Some(id)) } - pub fn pop_back + GetMut>(&mut self, storage: &mut S) -> Result, ()> + pub fn pop_back + GetMut>(&mut self, storage: &mut S) -> Result> where >::Output: Linkable, { @@ -144,12 +153,13 @@ impl List { Ok(Some(id)) } - pub fn remove + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + /// Removes `id` from the list. Errors if `id` does not exist in `storage` or if the node corresponding to `id` is not linked. + pub fn remove + GetMut>(&mut self, id: T, storage: &mut S) -> Result<()> where >::Output: Linkable, { let (prev, next, linked) = { - let node = storage.get(id).ok_or(())?; + let node = storage.get(id).ok_or(kerr!(NotFound))?; let links = node.links(); let linked = self.head == Some(id) || self.tail == Some(id) @@ -159,24 +169,28 @@ impl List { }; if !linked { - return Err(()); + return Err(kerr!(NotFound)); } if let Some(prev_id) = prev { - let prev_node = storage.get_mut(prev_id).ok_or(())?; + let prev_node = storage.get_mut(prev_id).unwrap_or_else(|| { + bug!("node linked from list does not exist in storage."); + }); prev_node.links_mut().next = next; } else { self.head = next; } if let Some(next_id) = next { - let next_node = storage.get_mut(next_id).ok_or(())?; + let next_node = storage.get_mut(next_id).unwrap_or_else(|| { + bug!("node linked from list does not exist in storage."); + }); next_node.links_mut().prev = prev; } else { self.tail = prev; } - let node = storage.get_mut(id).ok_or(())?; + let node = storage.get_mut(id).ok_or(kerr!(NotFound))?; node.links_mut().prev = None; node.links_mut().next = None; @@ -184,12 +198,13 @@ impl List { Ok(()) } - fn detach_links + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + /// Detaches `id` from any list it is currently in. If `id` is not in any list but is linked, the links are cleared. + fn detach_links + GetMut>(&mut self, id: T, storage: &mut S) -> Result<()> where >::Output: Linkable, { let linked = { - let node = storage.get(id).ok_or(())?; + let node = storage.get(id).ok_or(kerr!(NotFound))?; let links = node.links(); self.head == Some(id) || self.tail == Some(id) @@ -200,7 +215,7 @@ impl List { if linked { self.remove(id, storage)?; } else { - let node = storage.get_mut(id).ok_or(())?; + let node = storage.get_mut(id).ok_or(kerr!(NotFound))?; node.links_mut().prev = None; node.links_mut().next = None; } diff --git a/src/types/rbtree.rs b/src/types/rbtree.rs index 887e386..842ffe2 100644 --- a/src/types/rbtree.rs +++ b/src/types/rbtree.rs @@ -1,5 +1,7 @@ use core::{marker::PhantomData}; +use crate::error::Result; + use super::traits::{Get, GetMut}; #[allow(dead_code)] @@ -59,10 +61,11 @@ impl RbTree } } - pub fn insert + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + /// Inserts `id` into the tree. If `id` already exists in the tree, it is first removed and then re-inserted. Errors if `id` does not exist in `storage`. + pub fn insert + GetMut>(&mut self, id: T, storage: &mut S) -> Result<()> where >::Output: Linkable + Compare,{ let already_linked = { - let node = storage.get(id).ok_or(())?; + let node = storage.get(id).ok_or(kerr!(NotFound))?; let links = node.links(); self.root == Some(id) || links.parent.is_some() @@ -77,12 +80,14 @@ impl RbTree let mut last = None; { - let node = storage.get(id).ok_or(())?; + let node = storage.get(id).ok_or(kerr!(NotFound))?; let mut current = self.root; while let Some(current_id) = current { last = current; - let current_node = storage.get(current_id).ok_or(())?; + let current_node = storage.get(current_id).unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); let go_left = node.cmp(current_node) == core::cmp::Ordering::Less; current = if go_left { @@ -94,7 +99,7 @@ impl RbTree } { - let node = storage.get_mut(id).ok_or(())?.links_mut(); + let node = storage.get_mut(id).ok_or(kerr!(NotFound))?.links_mut(); node.parent = last; node.left = None; node.right = None; @@ -115,8 +120,10 @@ impl RbTree } if let Some(min_id) = self.min { - let node = storage.get(id).ok_or(())?; - let min_node = storage.get(min_id).ok_or(())?; + let node = storage.get(id).ok_or(kerr!(NotFound))?; + let min_node = storage.get(min_id).unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); if node.cmp(min_node) == core::cmp::Ordering::Less { self.min = Some(id); } @@ -127,18 +134,27 @@ impl RbTree self.insert_fixup(id, storage) } - pub fn remove + GetMut>(&mut self, id: T, storage: &mut S) -> Result<(), ()> + pub fn remove + GetMut>(&mut self, id: T, storage: &mut S) -> Result<()> where >::Output: Linkable + Compare { - let (node_left, node_right, node_parent, node_is_red) = { - let node = storage.get(id).ok_or(())?; + let (node_left, node_right, node_parent, node_is_red, linked) = { + let node = storage.get(id).ok_or(kerr!(NotFound))?; + let links = node.links(); ( - node.links().left, - node.links().right, - node.links().parent, - matches!(node.links().color, Color::Red), + links.left, + links.right, + links.parent, + matches!(links.color, Color::Red), + self.root == Some(id) + || links.parent.is_some() + || links.left.is_some() + || links.right.is_some(), ) }; + if !linked { + return Err(kerr!(NotFound)); + } + let mut succ_was_red = node_is_red; let child: Option; let child_parent: Option; @@ -154,7 +170,9 @@ impl RbTree self.transplant(id, node_left, storage)?; } else { - let right_id = node_right.ok_or(())?; + let right_id = node_right.unwrap_or_else(|| { + bug!("node's right child is None, but it is not None according to previous get."); + }); let succ = self.minimum(right_id, storage)?; let succ_right = storage.get(succ).and_then(|n| n.links().right); let succ_parent = storage.get(succ).and_then(|n| n.links().parent); @@ -173,7 +191,7 @@ impl RbTree succ_node.links_mut().right = Some(right_id); right_node.links_mut().parent = Some(succ); } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } child_parent = succ_parent; @@ -181,13 +199,15 @@ impl RbTree self.transplant(id, Some(succ), storage)?; - let left_id = node_left.ok_or(())?; + let left_id = node_left.unwrap_or_else(|| { + bug!("node's left child is None, but it is not None according to previous get."); + }); if let (Some(succ_node), Some(left_node)) = storage.get2_mut(succ, left_id) { succ_node.links_mut().left = Some(left_id); left_node.links_mut().parent = Some(succ); } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } if let Some(succ_node) = storage.get_mut(succ) { @@ -197,7 +217,7 @@ impl RbTree Color::Black }; } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } } @@ -205,6 +225,17 @@ impl RbTree self.delete_fixup(child, child_parent, storage)?; } + // Fully detach the removed node so stale links cannot be observed on reinsertion. + if let Some(node) = storage.get_mut(id) { + let links = node.links_mut(); + links.parent = None; + links.left = None; + links.right = None; + links.color = Color::Red; + } else { + bug!("node linked from tree does not exist in storage."); + } + if self.min == Some(id) { self.min = match self.root { Some(root_id) => Some(self.minimum(root_id, storage)?), @@ -219,7 +250,7 @@ impl RbTree self.min } - fn insert_fixup + GetMut>(&mut self, mut id: T, storage: &mut S) -> Result<(), ()> + fn insert_fixup + GetMut>(&mut self, mut id: T, storage: &mut S) -> Result<()> where >::Output: Linkable + Compare, { while let Some(parent) = storage.get(id).and_then(|n| n.links().parent) && storage @@ -229,7 +260,9 @@ impl RbTree let grandparent = storage .get(parent) .and_then(|n| n.links().parent) - .ok_or(())?; + .unwrap_or_else(|| { + bug!("node linked from tree does not have a parent."); + }); // Is left child node if storage @@ -264,11 +297,13 @@ impl RbTree id = old_parent; } - let parent = storage.get(id).and_then(|n| n.links().parent).ok_or(())?; + let parent = storage.get(id).and_then(|n| n.links().parent).ok_or(kerr!(NotFound))?; let grandparent = storage .get(parent) .and_then(|n| n.links().parent) - .ok_or(())?; + .unwrap_or_else(|| { + bug!("node linked from tree does not have a parent."); + }); if let (Some(parent_node), Some(grandparent_node)) = storage.get2_mut(parent, grandparent) @@ -308,11 +343,13 @@ impl RbTree id = old_parent; } - let parent = storage.get(id).and_then(|n| n.links().parent).ok_or(())?; + let parent = storage.get(id).and_then(|n| n.links().parent).ok_or(kerr!(NotFound))?; let grandparent = storage .get(parent) .and_then(|n| n.links().parent) - .ok_or(())?; + .unwrap_or_else(|| { + bug!("node linked from tree does not have a parent."); + }); if let (Some(parent_node), Some(grandparent_node)) = storage.get2_mut(parent, grandparent) @@ -340,7 +377,7 @@ impl RbTree mut id: Option, mut parent: Option, storage: &mut S, - ) -> Result<(), ()> + ) -> Result<()> where >::Output: Linkable + Compare, { let is_red = |node_id: Option, storage: &S| -> bool { node_id @@ -351,7 +388,9 @@ impl RbTree let is_black = |node_id: Option, storage: &S| -> bool { !is_red(node_id, storage) }; while id != self.root && is_black(id, storage) { - let parent_id = parent.ok_or(())?; + let parent_id = parent.unwrap_or_else(|| { + bug!("node linked from tree does not have a parent."); + }); let is_left_child = storage .get(parent_id) @@ -361,20 +400,24 @@ impl RbTree let mut sibling_opt = storage.get(parent_id).and_then(|n| n.links().right); if is_red(sibling_opt, storage) { - let sibling_id = sibling_opt.ok_or(())?; + let sibling_id = sibling_opt.unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); // Color sibling node black and parent node red, rotate if let (Some(sib), Some(par)) = storage.get2_mut(sibling_id, parent_id) { sib.links_mut().color = Color::Black; par.links_mut().color = Color::Red; } else { - return Err(()); + return Err(kerr!(NotFound)); } self.rotate_left(parent_id, sibling_id, storage)?; sibling_opt = storage.get(parent_id).and_then(|n| n.links().right); } // Sibling node is black - let sibling_id = sibling_opt.ok_or(())?; + let sibling_id = sibling_opt.unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); let sib_left = storage.get(sibling_id).and_then(|n| n.links().left); let sib_right = storage.get(sibling_id).and_then(|n| n.links().right); @@ -383,26 +426,30 @@ impl RbTree if let Some(sib) = storage.get_mut(sibling_id) { sib.links_mut().color = Color::Red; } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } id = Some(parent_id); parent = storage.get(parent_id).and_then(|n| n.links().parent); } else { // Sibling's left node is red if is_black(sib_right, storage) { - let sib_left_id = sib_left.ok_or(())?; + let sib_left_id = sib_left.unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); if let (Some(sib), Some(left)) = storage.get2_mut(sibling_id, sib_left_id) { sib.links_mut().color = Color::Red; left.links_mut().color = Color::Black; } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } self.rotate_right(sibling_id, sib_left_id, storage)?; sibling_opt = storage.get(parent_id).and_then(|n| n.links().right); } // Sibling's right child node is red - let sibling_id = sibling_opt.ok_or(())?; + let sibling_id = sibling_opt.unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); let parent_is_red = storage .get(parent_id) .map_or(false, |n| matches!(n.links().color, Color::Red)); @@ -433,19 +480,23 @@ impl RbTree let mut sibling_opt = storage.get(parent_id).and_then(|n| n.links().left); if is_red(sibling_opt, storage) { - let sibling_id = sibling_opt.ok_or(())?; + let sibling_id = sibling_opt.unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); if let (Some(sib), Some(par)) = storage.get2_mut(sibling_id, parent_id) { sib.links_mut().color = Color::Black; par.links_mut().color = Color::Red; } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } self.rotate_right(parent_id, sibling_id, storage)?; sibling_opt = storage.get(parent_id).and_then(|n| n.links().left); } // Sibling node is black - let sibling_id = sibling_opt.ok_or(())?; + let sibling_id = sibling_opt.unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); let sib_left = storage.get(sibling_id).and_then(|n| n.links().left); let sib_right = storage.get(sibling_id).and_then(|n| n.links().right); @@ -453,27 +504,31 @@ impl RbTree if let Some(sib) = storage.get_mut(sibling_id) { sib.links_mut().color = Color::Red; } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } id = Some(parent_id); parent = storage.get(parent_id).and_then(|n| n.links().parent); } else { // Sibling's right node is red if is_black(sib_left, storage) { - let sib_right_id = sib_right.ok_or(())?; + let sib_right_id = sib_right.unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); if let (Some(sib), Some(right)) = storage.get2_mut(sibling_id, sib_right_id) { sib.links_mut().color = Color::Red; right.links_mut().color = Color::Black; } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } self.rotate_left(sibling_id, sib_right_id, storage)?; sibling_opt = storage.get(parent_id).and_then(|n| n.links().left); } // Sibling's left child node is red - let sibling_id = sibling_opt.ok_or(())?; + let sibling_id = sibling_opt.unwrap_or_else(|| { + bug!("node linked from tree does not exist in storage."); + }); let parent_is_red = storage .get(parent_id) .map_or(false, |n| matches!(n.links().color, Color::Red)); @@ -513,10 +568,10 @@ impl RbTree Ok(()) } - fn minimum>(&self, mut id: T, storage: &S) -> Result + fn minimum>(&self, mut id: T, storage: &S) -> Result where >::Output: Linkable + Compare, { loop { - let left = storage.get(id).ok_or(())?.links().left; + let left = storage.get(id).ok_or(kerr!(NotFound))?.links().left; match left { Some(left_id) => id = left_id, None => return Ok(id), @@ -524,7 +579,7 @@ impl RbTree } } - fn transplant + GetMut>(&mut self, u: T, v: Option, storage: &mut S) -> Result<(), ()> + fn transplant + GetMut>(&mut self, u: T, v: Option, storage: &mut S) -> Result<()> where >::Output: Linkable + Compare, { let u_parent = storage.get(u).and_then(|n| n.links().parent); @@ -538,7 +593,7 @@ impl RbTree parent_node.links_mut().right = v; } } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } } } @@ -547,17 +602,17 @@ impl RbTree if let Some(v_node) = storage.get_mut(v_id) { v_node.links_mut().parent = u_parent; } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } } Ok(()) } - fn rotate_right + GetMut>(&mut self, pivot: T, left: T, storage: &mut S) -> Result<(), ()> + fn rotate_right + GetMut>(&mut self, pivot: T, left: T, storage: &mut S) -> Result<()> where >::Output: Linkable + Compare, { if pivot == left { - return Err(()); + return Err(kerr!(NotFound)); } let (right, parent) = @@ -580,7 +635,7 @@ impl RbTree (old_right, old_parent) } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); }; if let Some(right_id) = right { @@ -599,7 +654,7 @@ impl RbTree parent_node.links_mut().right = Some(left); } } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } } } @@ -607,10 +662,10 @@ impl RbTree Ok(()) } - fn rotate_left + GetMut>(&mut self, pivot: T, right: T, storage: &mut S) -> Result<(), ()> + fn rotate_left + GetMut>(&mut self, pivot: T, right: T, storage: &mut S) -> Result<()> where >::Output: Linkable + Compare, { if pivot == right { - return Err(()); + return Err(kerr!(NotFound)); } let (left, parent) = @@ -633,7 +688,7 @@ impl RbTree (old_left, old_parent) } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); }; if let Some(left_id) = left { @@ -652,7 +707,7 @@ impl RbTree parent_node.links_mut().right = Some(right); } } else { - return Err(()); + bug!("node linked from tree does not exist in storage."); } } } diff --git a/src/types/view.rs b/src/types/view.rs index c07de3a..de56096 100644 --- a/src/types/view.rs +++ b/src/types/view.rs @@ -23,6 +23,11 @@ where _proj: PhantomData, } } + + pub fn with R, R>(data: &'a mut S, f: F) -> R { + let mut view = Self::new(data); + f(&mut view) + } } impl<'a, K: ?Sized + ToIndex, P, S: GetMut> Get for ViewMut<'a, K, P, S> diff --git a/src/uapi/sched.rs b/src/uapi/sched.rs index 43df677..db6bddd 100644 --- a/src/uapi/sched.rs +++ b/src/uapi/sched.rs @@ -1,8 +1,8 @@ -pub fn sleep(until: u64) { - hal::asm::syscall!(1, (until >> 32) as u32, until as u32); +pub fn sleep(until: u64) -> isize { + hal::asm::syscall!(1, (until >> 32) as u32, until as u32) } - -pub fn sleep_for(duration: u64) { - hal::asm::syscall!(2, (duration >> 32) as u32, duration as u32); + +pub fn sleep_for(duration: u64) -> isize { + hal::asm::syscall!(2, (duration >> 32) as u32, duration as u32) } \ No newline at end of file From f69c956cc83a4956f1ffd9083c2791c21054a00e Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Sat, 28 Mar 2026 17:43:33 +0000 Subject: [PATCH 27/42] spawn thread syscall --- examples/hello-world/src/main.rs | 12 ++++++++++++ src/idle.rs | 2 +- src/sched.rs | 7 ++++++- src/sched/thread.rs | 4 ++++ src/syscalls/sched.rs | 19 +++++++++++++++++++ src/uapi/sched.rs | 6 ++++++ src/uspace.rs | 2 +- 7 files changed, 49 insertions(+), 3 deletions(-) diff --git a/examples/hello-world/src/main.rs b/examples/hello-world/src/main.rs index 2702505..70500bb 100644 --- a/examples/hello-world/src/main.rs +++ b/examples/hello-world/src/main.rs @@ -3,10 +3,22 @@ use osiris::app_main; +extern "C" fn second_thread() { + osiris::uprintln!("Hello from the second thread!"); + + let mut tick = 0; + loop { + osiris::uprintln!("Second thread tick: {}", tick); + tick += 1; + osiris::uapi::sched::sleep_for(1500); + } +} + #[app_main] fn main() { osiris::uprintln!("Hello World!"); let mut tick = 0; + osiris::uapi::sched::spawn_thread(second_thread); loop { osiris::uprintln!("Tick: {}", tick); diff --git a/src/idle.rs b/src/idle.rs index 70633a7..479f407 100644 --- a/src/idle.rs +++ b/src/idle.rs @@ -9,7 +9,7 @@ extern "C" fn entry() { pub fn init() { let attrs = sched::thread::Attributes { entry, fin: None }; sched::with(|sched| { - if let Err(e) = sched.create_thread(sched::task::KERNEL_TASK, &attrs) { + if let Err(e) = sched.create_thread(Some(sched::task::KERNEL_TASK), &attrs) { panic!("failed to create idle thread. Error: {}", e); } }); diff --git a/src/sched.rs b/src/sched.rs index e3cd1e5..e063b48 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -274,10 +274,15 @@ impl Scheduler { pub fn create_thread( &mut self, - task: task::UId, + task: Option, attrs: &thread::Attributes, ) -> Result { + let task = match task { + Some(t) => t, + None => self.current.ok_or(kerr!(InvalidArgument))?.owner() + }; let task = self.tasks.get_mut(task).ok_or(kerr!(InvalidArgument))?; + let thread = task.create_thread(self.id_gen, attrs)?; let uid = thread.uid(); diff --git a/src/sched/thread.rs b/src/sched/thread.rs index 86af4aa..7cc628d 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -60,6 +60,10 @@ impl UId { self.tid } + pub fn as_usize(&self) -> usize { + self.uid + } + pub fn owner(&self) -> task::UId { self.tid.owner } diff --git a/src/syscalls/sched.rs b/src/syscalls/sched.rs index 2b7d434..b7b1ffd 100644 --- a/src/syscalls/sched.rs +++ b/src/syscalls/sched.rs @@ -29,3 +29,22 @@ fn sleep_for(duration_hi: u32, duration_lo: u32) -> c_int { 0 } +#[syscall_handler(num = 3)] +fn spawn_thread(func_ptr: usize) -> c_int { + sched::with(|sched| { + let attrs = sched::thread::Attributes { + entry: unsafe { core::mem::transmute(func_ptr) }, + fin: None, + }; + match sched.create_thread(None, &attrs) { + Ok(uid) => { + if sched.enqueue(time::tick(), uid).is_err() { + panic!("failed to enqueue thread."); + } + uid.as_usize() as c_int + } + Err(_) => -1, + } + }) +} + diff --git a/src/uapi/sched.rs b/src/uapi/sched.rs index db6bddd..5437a2b 100644 --- a/src/uapi/sched.rs +++ b/src/uapi/sched.rs @@ -1,3 +1,5 @@ +use hal::stack::EntryFn; + pub fn sleep(until: u64) -> isize { hal::asm::syscall!(1, (until >> 32) as u32, until as u32) @@ -5,4 +7,8 @@ pub fn sleep(until: u64) -> isize { pub fn sleep_for(duration: u64) -> isize { hal::asm::syscall!(2, (duration >> 32) as u32, duration as u32) +} + +pub fn spawn_thread(func_ptr: EntryFn) -> isize { + hal::asm::syscall!(3, func_ptr as u32) } \ No newline at end of file diff --git a/src/uspace.rs b/src/uspace.rs index bc40be2..2f11b34 100644 --- a/src/uspace.rs +++ b/src/uspace.rs @@ -17,7 +17,7 @@ pub fn init_app() { fin: None, }; sched::with(|sched| { - if let Ok(uid) = sched.create_thread(sched::task::KERNEL_TASK, &attrs) { + if let Ok(uid) = sched.create_thread(Some(sched::task::KERNEL_TASK), &attrs) { if sched.enqueue(time::tick(), uid).is_err() { panic!("failed to enqueue init thread."); } From e0af649c6e830dffbccfe69bd8036bbe76530ae9 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Mon, 30 Mar 2026 18:19:13 +0000 Subject: [PATCH 28/42] wip --- Cargo.lock | 75 ++++++++++++-- Cargo.toml | 3 + build.rs | 45 ++++----- examples/hello-world/Cargo.toml | 2 +- examples/hello-world/src/main.rs | 17 +++- machine/arm/Cargo.toml | 1 + machine/arm/src/crit.rs | 14 +++ machine/arm/src/excep.rs | 27 ++++++ machine/arm/src/lib.rs | 1 + macros/src/lib.rs | 161 +++---------------------------- macros/src/logging.rs | 24 +++++ macros/src/syscall.rs | 146 ++++++++++++++++++++++++++++ src/error.rs | 3 +- src/lib.rs | 5 +- src/macros.rs | 48 ++++++--- src/mem/alloc/bestfit.rs | 2 +- src/sched.rs | 52 +++++----- src/sched/task.rs | 50 ++++++---- src/sched/thread.rs | 50 +++++++--- src/sync/spinlock.rs | 4 +- src/syscalls.rs | 3 +- src/syscalls/sched.rs | 17 +++- src/types/array.rs | 46 +++++---- src/types/boxed.rs | 2 +- src/types/heap.rs | 2 +- src/types/list.rs | 6 +- src/types/queue.rs | 2 +- src/types/rbtree.rs | 9 +- src/types/traits.rs | 6 ++ src/uapi/sched.rs | 12 +++ 30 files changed, 542 insertions(+), 293 deletions(-) create mode 100644 machine/arm/src/crit.rs create mode 100644 macros/src/logging.rs create mode 100644 macros/src/syscall.rs diff --git a/Cargo.lock b/Cargo.lock index 0b14c67..8ebf8eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,7 +95,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags", + "bitflags 2.10.0", "cexpr", "clang-sys", "itertools 0.12.1", @@ -118,7 +118,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags", + "bitflags 2.10.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -132,6 +132,12 @@ dependencies = [ "syn", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.10.0" @@ -412,13 +418,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossterm" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags", + "bitflags 2.10.0", "crossterm_winapi", "libc", "mio 0.8.11", @@ -434,7 +446,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags", + "bitflags 2.10.0", "crossterm_winapi", "mio 1.1.1", "parking_lot", @@ -514,6 +526,48 @@ dependencies = [ "syn", ] +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.17", +] + +[[package]] +name = "defmt-rtt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" +dependencies = [ + "critical-section", + "defmt", +] + [[package]] name = "digest" version = "0.10.7" @@ -713,6 +767,7 @@ dependencies = [ "bindgen 0.72.1", "cbindgen", "cmake", + "critical-section", "hal-api", "serde_json", ] @@ -1081,9 +1136,11 @@ name = "osiris" version = "0.1.0" dependencies = [ "bindgen 0.69.5", - "bitflags", + "bitflags 2.10.0", "cbindgen", "cfg_aliases", + "defmt", + "defmt-rtt", "dtgen", "envparse", "hal-select", @@ -1257,7 +1314,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags", + "bitflags 2.10.0", "cassowary", "compact_str", "crossterm 0.28.1", @@ -1278,7 +1335,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.10.0", ] [[package]] @@ -1336,7 +1393,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -1349,7 +1406,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.11.0", diff --git a/Cargo.toml b/Cargo.toml index bca123c..e451399 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,8 @@ hal = { package = "hal-select", path = "machine/select" } proc_macros = { package = "macros", path = "macros" } envparse = "0.1.0" bitflags = "2.10.0" +defmt = { version = "1.0", optional = true } +defmt-rtt = { version = "1.0", optional = true } [dev-dependencies] # This is a host-compatible HAL which will be used for running tests and verification on the host. @@ -37,6 +39,7 @@ default = [] nightly = [] no-atomic-cas = [] multi-core = [] +defmt = ["dep:defmt", "dep:defmt-rtt"] [build-dependencies] cbindgen = "0.28.0" diff --git a/build.rs b/build.rs index a07afab..1efb348 100644 --- a/build.rs +++ b/build.rs @@ -6,6 +6,7 @@ extern crate syn; extern crate walkdir; use cfg_aliases::cfg_aliases; +use quote::format_ident; use std::io::Write; use syn::{Attribute, LitInt}; use walkdir::WalkDir; @@ -15,8 +16,11 @@ extern crate cbindgen; fn main() { println!("cargo::rerun-if-changed=src"); println!("cargo::rerun-if-changed=build.rs"); + let out_dir = std::env::var("OUT_DIR").unwrap(); - generate_syscall_map("src/syscalls").expect("Failed to generate syscall map."); + if gen_syscall_match(Path::new("src/syscalls"), Path::new(&out_dir)).is_err() { + panic!("Failed to generate syscall match statement."); + } cfg_aliases! { freestanding: { all(not(test), not(doctest), not(doc), not(kani), any(target_os = "none", target_os = "unknown")) }, @@ -203,27 +207,26 @@ fn get_arg_names(args: &str) -> String { }) } -fn generate_syscall_map>(root: P) -> Result<(), std::io::Error> { - let syscalls = collect_syscalls(root); - - let out_dir = std::env::var("OUT_DIR").unwrap(); - let out_path = Path::new(&out_dir).join("syscall_dispatcher.in"); - let mut file = File::create(out_path)?; - - writeln!(file, "// This file is @generated by build.rs. Do not edit!")?; - writeln!(file)?; - writeln!(file, "match number {{")?; +fn gen_syscall_match(root: &Path, out: &Path) -> Result<(), std::io::Error> { + let syscalls = find_syscalls(root); + let mut file = File::create(out.join("syscall_match.in"))?; - for (name, number) in &syscalls { - writeln!(file, " {number} => entry_{name}(args),")?; - } + let arms = syscalls.iter().map(|(name, number)| { + let entry = format_ident!("entry_{}", name); + quote::quote! { + #number => #entry(args), + } + }); - writeln!( - file, - " _ => panic!(\"Unknown syscall number: {{}}\", number)," - )?; - writeln!(file, "}}")?; + let syscall_match = quote::quote! { + // This match statement is @generated by build.rs. Do not edit. + match number { + #(#arms)* + _ => panic!("Unknown syscall number: {}", number), + } + }; + writeln!(file, "{syscall_match}")?; Ok(()) } @@ -263,9 +266,7 @@ fn is_syscall(attrs: &[Attribute], name: &str) -> Option { None } -type SyscallData = u16; - -fn collect_syscalls>(root: P) -> HashMap { +fn find_syscalls(root: &Path) -> HashMap { let mut syscalls = HashMap::new(); let mut numbers = HashMap::new(); diff --git a/examples/hello-world/Cargo.toml b/examples/hello-world/Cargo.toml index 1bd3456..81645ee 100644 --- a/examples/hello-world/Cargo.toml +++ b/examples/hello-world/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -osiris = { workspace = true } +osiris = { workspace = true, features = ["defmt"] } [build-dependencies] cfg_aliases = "0.2.1" diff --git a/examples/hello-world/src/main.rs b/examples/hello-world/src/main.rs index 70500bb..baa7bb3 100644 --- a/examples/hello-world/src/main.rs +++ b/examples/hello-world/src/main.rs @@ -7,11 +7,24 @@ extern "C" fn second_thread() { osiris::uprintln!("Hello from the second thread!"); let mut tick = 0; - loop { + for i in 0..5 { osiris::uprintln!("Second thread tick: {}", tick); tick += 1; osiris::uapi::sched::sleep_for(1500); } + + osiris::uapi::sched::exit(0); + osiris::uprintln!("This will never be printed."); +} + +extern "C" fn generator_thread() { + + let mut cnt = 0; + loop { + osiris::uapi::sched::yield_thread(); + osiris::uprintln!("Number: {}", cnt); + cnt += 1; + } } #[app_main] @@ -19,7 +32,7 @@ fn main() { osiris::uprintln!("Hello World!"); let mut tick = 0; osiris::uapi::sched::spawn_thread(second_thread); - + osiris::uapi::sched::spawn_thread(generator_thread); loop { osiris::uprintln!("Tick: {}", tick); tick += 1; diff --git a/machine/arm/Cargo.toml b/machine/arm/Cargo.toml index 9e5a441..9fb4753 100644 --- a/machine/arm/Cargo.toml +++ b/machine/arm/Cargo.toml @@ -11,6 +11,7 @@ crate-type = ["rlib"] [dependencies] hal-api = { path = "../api" } +critical-section = { version = "1.2", features = ["restore-state-usize"] } [build-dependencies] cbindgen = "0.28.0" diff --git a/machine/arm/src/crit.rs b/machine/arm/src/crit.rs new file mode 100644 index 0000000..134f2f5 --- /dev/null +++ b/machine/arm/src/crit.rs @@ -0,0 +1,14 @@ +use critical_section::RawRestoreState; + +struct CriticalSection; +critical_section::set_impl!(CriticalSection); + +unsafe impl critical_section::Impl for CriticalSection { + unsafe fn acquire() -> RawRestoreState { + crate::asm::disable_irq_save() + } + + unsafe fn release(token: RawRestoreState) { + crate::asm::enable_irq_restr(token); + } +} \ No newline at end of file diff --git a/machine/arm/src/excep.rs b/machine/arm/src/excep.rs index 4e50bbc..ab51268 100644 --- a/machine/arm/src/excep.rs +++ b/machine/arm/src/excep.rs @@ -1,4 +1,5 @@ use core::fmt::Display; +use core::mem::align_of; #[repr(C)] pub struct ExcepStackFrame { @@ -43,6 +44,11 @@ impl Display for ExcepStackFrame { const BACKTRACE_MAX_FRAMES: usize = 20; +#[inline] +fn is_call_aligned(ptr: *const usize) -> bool { + (ptr as usize).is_multiple_of(align_of::()) +} + #[repr(C)] pub struct ExcepBacktrace { stack_frame: ExcepStackFrame, @@ -79,6 +85,11 @@ impl Display for ExcepBacktrace { writeln!(f, "0: 0x{:08x}", self.stack_frame.pc)?; } + if fp.is_null() || !is_call_aligned(fp) { + writeln!(f, "", fp as usize)?; + return writeln!(f); + } + for i in 1..BACKTRACE_MAX_FRAMES { // Read the return address from the stack. let ret_addr = unsafe { fp.add(1).read_volatile() }; @@ -89,6 +100,9 @@ impl Display for ExcepBacktrace { break; } + // Return addresses in Thumb mode carry bit0 = 1. + let ret_addr = ret_addr & !1; + // Print the return address. if let Some(symbol) = crate::debug::find_nearest_symbol(ret_addr) { writeln!(f, "{i}: {symbol} (0x{ret_addr:08x})")?; @@ -101,6 +115,19 @@ impl Display for ExcepBacktrace { break; } + let fp_addr = fp as usize; + let next_fp_addr = next_fp; + + if next_fp_addr <= fp_addr { + writeln!(f, "")?; + break; + } + + if !is_call_aligned(next_fp_addr as *const usize) { + writeln!(f, "")?; + break; + } + // Move to the next frame. fp = next_fp as *const usize; diff --git a/machine/arm/src/lib.rs b/machine/arm/src/lib.rs index 00fb103..46ba0b9 100644 --- a/machine/arm/src/lib.rs +++ b/machine/arm/src/lib.rs @@ -10,6 +10,7 @@ pub mod excep; pub mod panic; pub mod sched; +mod crit; mod print; mod bindings { diff --git a/macros/src/lib.rs b/macros/src/lib.rs index b2084a6..49f1b4a 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,9 +1,8 @@ -use quote::{ToTokens, format_ident}; use syn::parse_macro_input; -use proc_macro2::TokenStream; - mod tree; +mod syscall; +mod logging; #[proc_macro_derive(TaggedLinks, attributes(rbtree, list))] pub fn derive_tagged_links(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -15,6 +14,16 @@ pub fn derive_tagged_links(input: proc_macro::TokenStream) -> proc_macro::TokenS }.into() } +#[proc_macro_attribute] +pub fn fmt(args: proc_macro::TokenStream, input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = syn::parse_macro_input!(input as syn::DeriveInput); + + match logging::derive_fmt(&input) { + Ok(tokens) => tokens, + Err(e) => e.to_compile_error(), + }.into() +} + #[proc_macro_attribute] pub fn app_main(input: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { let item = syn::parse_macro_input!(item as syn::ItemFn); @@ -42,74 +51,6 @@ pub fn app_main(input: proc_macro::TokenStream, item: proc_macro::TokenStream) - expanded.into() } -const SYSCALL_MAX_ARGS: usize = 4; - -fn is_return_type_register_sized_check( - item: &syn::ItemFn, -) -> Result { - let ret_ty = match &item.sig.output { - syn::ReturnType::Default => { - // no "-> Type" present - return Err(syn::Error::new_spanned( - &item.sig.output, - "syscall_handler: missing return type; expected a register‐sized type", - )); - } - syn::ReturnType::Type(_, ty) => (*ty).clone(), - }; - - Ok(quote::quote! { - const _: () = { - if core::mem::size_of::<#ret_ty>() > core::mem::size_of::() { - panic!("syscall_handler: the return type is bigger than usize. return type must fit in a register."); - } - }; - }) -} - -fn check_and_collect_argument_types(item: &syn::ItemFn) -> Result, syn::Error> { - let types: Vec> = item - .sig - .inputs - .iter() - .map(|arg| { - if let syn::FnArg::Typed(pat_type) = arg { - Ok((*pat_type.ty).clone()) - } else { - Err(syn::Error::new( - item.sig.ident.span(), - format!( - "argument {} is invalid. expected a typed argument.\n", - arg.to_token_stream() - ), - )) - } - }) - .collect(); - - let concat_errors: Vec<_> = types - .iter() - .filter_map(|arg0: &std::result::Result| Result::err(arg0.clone())) - .collect(); - - if !concat_errors.is_empty() { - return Err(syn::Error::new( - item.sig.ident.span(), - format!( - "syscall_handler: function {} has invalid arguments: {}", - item.sig.ident, - concat_errors - .iter() - .map(|e| e.to_string()) - .collect::>() - .join(", ") - ), - )); - } - - Ok(types.into_iter().map(Result::unwrap).collect()) -} - #[proc_macro_attribute] pub fn syscall_handler( attr: proc_macro::TokenStream, @@ -129,83 +70,7 @@ pub fn syscall_handler( parse_macro_input!(attr with parser); let item = syn::parse_macro_input!(item as syn::ItemFn); - syscall_handler_fn(&item).into() + syscall::syscall_handler_fn(&item).into() } -fn syscall_handler_fn(item: &syn::ItemFn) -> TokenStream { - let name = item.sig.ident.to_string().to_uppercase(); - let num_args = item.sig.inputs.len(); - - // Check if the function has a valid signature. So args <= 4 and return type is u32. - if num_args > SYSCALL_MAX_ARGS { - return syn::Error::new( - item.sig.ident.span(), - format!( - "syscall_handler: function {name} has too many arguments (max is {SYSCALL_MAX_ARGS})" - ), - ) - .to_compile_error(); - } - - let ret_check = match is_return_type_register_sized_check(item) { - Ok(check) => check, - Err(e) => return e.to_compile_error(), - }; - let types = match check_and_collect_argument_types(item) { - Ok(types) => { - if types.len() > SYSCALL_MAX_ARGS { - return syn::Error::new( - item.sig.ident.span(), - format!( - "syscall_handler: function {name} has too many arguments (max is {SYSCALL_MAX_ARGS})" - ), - ) - .to_compile_error(); - } - types - } - Err(e) => return e.to_compile_error(), - }; - - // Check if each argument type is valid and fits in a register. - let size_checks: Vec = types.iter().map(|ty| { - quote::quote! { - const _: () = { - if core::mem::size_of::<#ty>() > core::mem::size_of::() { - panic!("syscall_handler: an argument type is bigger than usize. arguments must fit in a register."); - } - }; - } - }).collect(); - - let unpack = types.iter().enumerate().map(|(i, ty)| { - quote::quote! { - unsafe { *(args.add(#i) as *const #ty) } - } - }); - - let wrapper_name = format_ident!("entry_{}", item.sig.ident.clone()); - let func_name = item.sig.ident.clone(); - - let call = quote::quote! { - #func_name( #(#unpack),* ) - }; - - let wrapper = quote::quote! { - #[unsafe(no_mangle)] - pub extern "C" fn #wrapper_name(svc_args: *const core::ffi::c_uint) -> core::ffi::c_int { - // This function needs to extract the arguments from the pointer and call the original function by passing the arguments as actual different parameters. - let args = unsafe { svc_args as *const usize }; - // Call the original function with the extracted arguments. - #call - } - }; - - quote::quote! { - #wrapper - #item - #ret_check - #(#size_checks)* - } -} diff --git a/macros/src/logging.rs b/macros/src/logging.rs new file mode 100644 index 0000000..9e857fe --- /dev/null +++ b/macros/src/logging.rs @@ -0,0 +1,24 @@ +use syn::{DeriveInput, ItemFn}; + +pub fn derive_fmt(input: &DeriveInput) -> syn::Result { + // Check if the env variable "OSIRIS_DEBUG_DEFMT" is set. If it is, generate a defmt::Format implementation. Otherwise, generate a Debug implementation. + if std::env::var("OSIRIS_DEBUG_DEFMT").is_ok() { + Ok(derive_fmt_defmt(input)) + } else { + Ok(derive_fmt_debug(input)) + } +} + +fn derive_fmt_defmt(input: &DeriveInput) -> proc_macro2::TokenStream { + quote::quote! { + #[derive(defmt::Format)] + #input + } +} + +fn derive_fmt_debug(input: &DeriveInput) -> proc_macro2::TokenStream { + quote::quote! { + #[derive(core::fmt::Debug)] + #input + } +} \ No newline at end of file diff --git a/macros/src/syscall.rs b/macros/src/syscall.rs new file mode 100644 index 0000000..45c071c --- /dev/null +++ b/macros/src/syscall.rs @@ -0,0 +1,146 @@ +use quote::{ToTokens, format_ident}; +use proc_macro2::TokenStream; + +pub const MAX_ARGS: usize = 4; + +pub fn valid_ret_type_check(item: &syn::ItemFn) -> Result { + let ret_ty = match &item.sig.output { + syn::ReturnType::Default => { + // no "-> Type" present + return Err(syn::Error::new_spanned( + &item.sig.output, + "syscall_handler: missing return type; expected a register‐sized type", + )); + } + syn::ReturnType::Type(_, ty) => (*ty).clone(), + }; + + Ok(quote::quote! { + const _: () = { + if core::mem::size_of::<#ret_ty>() > core::mem::size_of::() { + panic!("syscall_handler: the return type is bigger than usize. return type must fit in a register."); + } + }; + }) +} + +pub fn valid_arg_types_check(item: &syn::ItemFn) -> Result, syn::Error> { + let types: Vec> = item + .sig + .inputs + .iter() + .map(|arg| { + if let syn::FnArg::Typed(pat_type) = arg { + Ok((*pat_type.ty).clone()) + } else { + Err(syn::Error::new( + item.sig.ident.span(), + format!( + "argument {} is invalid. expected a typed argument.\n", + arg.to_token_stream() + ), + )) + } + }) + .collect(); + + let concat_errors: Vec<_> = types + .iter() + .filter_map(|arg0: &std::result::Result| Result::err(arg0.clone())) + .collect(); + + if !concat_errors.is_empty() { + return Err(syn::Error::new( + item.sig.ident.span(), + format!( + "syscall_handler: function {} has invalid arguments: {}", + item.sig.ident, + concat_errors + .iter() + .map(|e| e.to_string()) + .collect::>() + .join(", ") + ), + )); + } + + Ok(types.into_iter().map(Result::unwrap).collect()) +} + +pub fn syscall_handler_fn(item: &syn::ItemFn) -> TokenStream { + let name = item.sig.ident.to_string().to_uppercase(); + let num_args = item.sig.inputs.len(); + + // Check if the function has a valid signature. So args <= 4 and return type is u32. + if num_args > MAX_ARGS { + return syn::Error::new( + item.sig.ident.span(), + format!( + "syscall_handler: function {name} has too many arguments (max is {MAX_ARGS})", + ), + ) + .to_compile_error(); + } + + let ret_check = match valid_ret_type_check(item) { + Ok(check) => check, + Err(e) => return e.to_compile_error(), + }; + + let types = match valid_arg_types_check(item) { + Ok(types) => { + if types.len() > MAX_ARGS { + return syn::Error::new( + item.sig.ident.span(), + format!( + "syscall_handler: function {name} has too many arguments (max is {MAX_ARGS})", + ), + ) + .to_compile_error(); + } + types + } + Err(e) => return e.to_compile_error(), + }; + + // Check if each argument type is valid and fits in a register. + let size_checks: Vec = types.iter().map(|ty| { + quote::quote! { + const _: () = { + if core::mem::size_of::<#ty>() > core::mem::size_of::() { + panic!("syscall_handler: an argument type is bigger than usize. arguments must fit in a register."); + } + }; + } + }).collect(); + + let unpack = types.iter().enumerate().map(|(i, ty)| { + quote::quote! { + unsafe { *(args.add(#i) as *const #ty) } + } + }); + + let wrapper_name = format_ident!("entry_{}", item.sig.ident.clone()); + let func_name = item.sig.ident.clone(); + + let call = quote::quote! { + #func_name( #(#unpack),* ) + }; + + let wrapper = quote::quote! { + #[unsafe(no_mangle)] + pub extern "C" fn #wrapper_name(svc_args: *const core::ffi::c_uint) -> core::ffi::c_int { + // This function needs to extract the arguments from the pointer and call the original function by passing the arguments as actual different parameters. + let args = unsafe { svc_args as *const usize }; + // Call the original function with the extracted arguments. + #call + } + }; + + quote::quote! { + #wrapper + #item + #ret_check + #(#size_checks)* + } +} diff --git a/src/error.rs b/src/error.rs index f9edb31..cc48035 100644 --- a/src/error.rs +++ b/src/error.rs @@ -90,7 +90,8 @@ macro_rules! kerr { }; } -#[derive(Debug, Clone, PartialEq, Eq)] +#[proc_macros::fmt] +#[derive(Clone, PartialEq, Eq)] pub enum Kind { InvalidAlign, OutOfMemory, diff --git a/src/lib.rs b/src/lib.rs index 2ce97dd..1218892 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,12 +29,9 @@ pub use proc_macros::app_main; /// The kernel initialization function. /// -/// `boot_info` - The boot information. -/// /// # Safety /// /// This function must be called only once during the kernel startup. -/// The `boot_info` pointer must be valid and point to a properly initialized `BootInfo` structure. #[unsafe(no_mangle)] pub unsafe extern "C" fn kernel_init() -> ! { // Initialize basic hardware and the logging system. @@ -43,6 +40,8 @@ pub unsafe extern "C" fn kernel_init() -> ! { print::print_header(); + error!("Hello World!"); + // Initialize the memory allocator. let kaddr_space = mem::init_memory(); kprintln!("Memory initialized."); diff --git a/src/macros.rs b/src/macros.rs index 9d9d06f..0ddf819 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,27 +1,43 @@ //! Macros for kernel development. +use defmt_rtt as _; + + +#[macro_export] +macro_rules! debug { + ($fmt:literal $(, $arg:expr)* $(,)?) => { + #[cfg(feature = "defmt")] + defmt::debug!($fmt $(, $arg)*); + }; +} + +#[macro_export] +macro_rules! trace { + ($fmt:literal $(, $arg:expr)* $(,)?) => { + #[cfg(feature = "defmt")] + defmt::trace!($fmt $(, $arg)*); + }; +} -/// Creates a slice from the raw arguments. #[macro_export] -macro_rules! args_from_raw { - ($argc:expr, $argv:expr) => {{ - let argc = $argc; - let argv = $argv; - - if argc == 0 || argv.is_null() { - &[] - } else { - unsafe { core::slice::from_raw_parts(argv, argc) } - } - }}; +macro_rules! info { + ($fmt:literal $(, $arg:expr)* $(,)?) => { + #[cfg(feature = "defmt")] + defmt::info!($fmt $(, $arg)*); + }; +} + +#[macro_export] +macro_rules! error { + ($fmt:literal $(, $arg:expr)* $(,)?) => { + #[cfg(feature = "defmt")] + defmt::error!($fmt $(, $arg)*); + }; } #[macro_export] macro_rules! kprint { ($($arg:tt)*) => ({ - use core::fmt::Write; - use $crate::print::Printer; - let mut printer = Printer; - printer.write_fmt(format_args!($($arg)*)).unwrap(); + }); } diff --git a/src/mem/alloc/bestfit.rs b/src/mem/alloc/bestfit.rs index 00db5a7..4732a85 100644 --- a/src/mem/alloc/bestfit.rs +++ b/src/mem/alloc/bestfit.rs @@ -16,7 +16,7 @@ struct BestFitMeta { /// That does mean, when we allocate a block, we try to find the smallest block that fits the requested size. /// Blocks are stored in a singly linked list. The important part is that the linked list is stored in-line with the memory blocks. /// This means that every block has a header that contains the size of the block and a pointer to the next block. -#[derive(Debug)] +#[proc_macros::fmt] pub struct BestFitAllocator { /// Head of the free block list. head: Option>, diff --git a/src/sched.rs b/src/sched.rs index e063b48..4001fa1 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -112,7 +112,7 @@ impl Scheduler { self.rt_scheduler.enqueue(uid, now, &mut view); } else { if self.rr_scheduler.enqueue(uid, &mut self.threads).is_err() { - // This should not be possible. + // This should not be possible. // - Thread is in the thread list. // - Thread is not linked into a different list. bug!("failed to enqueue thread {} into RR scheduler.", uid); @@ -245,30 +245,22 @@ impl Scheduler { } pub fn kill_task(&mut self, uid: task::UId) -> Result<()> { - let task_id = self.tasks.get(uid).ok_or(kerr!(InvalidArgument))?.id; - self.tasks.remove(&uid).ok_or(kerr!(InvalidArgument))?; - - let begin = match self.threads.next(None) { - Some(i) => i, - None => return Ok(()), - }; - let mut i = begin; - - while i != begin { - i = (i + 1) % N; + let task = self.tasks.get_mut(uid).ok_or(kerr!(InvalidArgument))?; - let mut id = None; - if let Some(thread) = self.threads.at_cont(i) { - if thread.task_id() == task_id { - id = Some(thread.uid()); - } - } + while let Some(id) = task.threads().head() { + // Borrow checker... + rt::ServerView::::with(&mut self.threads, |view| { + self.rt_scheduler.dequeue(id, view); + }); + self.rr_scheduler.dequeue(id, &mut self.threads); - if let Some(id) = id { - self.dequeue(id); + if task.threads_mut().remove(id, &mut self.threads).is_err() { + // This should not be possible. The thread ID is from the thread list of the task, so it must exist. + bug!("failed to remove thread {} from task {}.", id, uid); } } + self.tasks.remove(&uid).ok_or(kerr!(InvalidArgument))?; Ok(()) } @@ -279,18 +271,26 @@ impl Scheduler { ) -> Result { let task = match task { Some(t) => t, - None => self.current.ok_or(kerr!(InvalidArgument))?.owner() + None => self.current.ok_or(kerr!(InvalidArgument))?.owner(), }; let task = self.tasks.get_mut(task).ok_or(kerr!(InvalidArgument))?; - - let thread = task.create_thread(self.id_gen, attrs)?; - let uid = thread.uid(); - - self.threads.insert(&uid, thread)?; + let uid = task.create_thread(self.id_gen, attrs, &mut self.threads)?; self.id_gen += 1; Ok(uid) } + + pub fn kill_thread(&mut self, uid: Option) -> Result<()> { + let uid = uid.unwrap_or(self.current.ok_or(kerr!(InvalidArgument))?); + self.dequeue(uid); + self.threads.remove(&uid).ok_or(kerr!(InvalidArgument))?; + + if Some(uid) == self.current { + self.current = None; + reschedule(); + } + Ok(()) + } } pub fn with T>(f: F) -> T { diff --git a/src/sched/task.rs b/src/sched/task.rs index 52b0345..c1e094a 100644 --- a/src/sched/task.rs +++ b/src/sched/task.rs @@ -1,19 +1,20 @@ //! This module provides the basic task and thread structures for the scheduler. +use core::borrow::Borrow; use core::fmt::Display; use core::num::NonZero; -use core::borrow::Borrow; use envparse::parse_env; -use hal::{Stack}; +use hal::Stack; -use hal::stack::{Stacklike}; +use hal::stack::Stacklike; use crate::error::Result; -use crate::sched::thread; +use crate::sched::{GlobalScheduler, ThreadMap, thread}; +use crate::types::list; use crate::{mem, sched}; -use crate::mem::vmm::{AddressSpacelike}; -use crate::types::traits::ToIndex; +use crate::mem::vmm::AddressSpacelike; +use crate::types::traits::{Get, GetMut, ToIndex}; pub struct Defaults { pub stack_pages: usize, @@ -26,18 +27,15 @@ const DEFAULTS: Defaults = Defaults { pub const KERNEL_TASK: UId = UId { uid: 0 }; /// Id of a task. This is unique across all tasks. -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] +#[proc_macros::fmt] +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] pub struct UId { uid: usize, } impl UId { pub fn new(uid: usize) -> Option { - if uid == 0 { - None - } else { - Some(Self { uid }) - } + if uid == 0 { None } else { Some(Self { uid }) } } pub fn is_kernel(&self) -> bool { @@ -53,7 +51,7 @@ impl ToIndex for UId { impl Display for UId { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "Task-{}", self.uid) + write!(f, "{}", self.uid) } } @@ -69,6 +67,8 @@ pub struct Task { tid_cntr: usize, /// Sets up the memory for the task. address_space: mem::vmm::AddressSpace, + /// The threads belonging to this task. + threads: list::List, } impl Task { @@ -84,6 +84,7 @@ impl Task { id, address_space, tid_cntr: 0, + threads: list::List::new(), }) } @@ -94,10 +95,7 @@ impl Task { sched::thread::Id::new(tid, self.id) } - fn allocate_stack( - &mut self, - attrs: &thread::Attributes, - ) -> Result { + fn allocate_stack(&mut self, attrs: &thread::Attributes) -> Result { let size = DEFAULTS.stack_pages * mem::pfa::PAGE_SIZE; let region = mem::vmm::Region::new( None, @@ -115,20 +113,32 @@ impl Task { }) } - pub fn create_thread( + pub fn create_thread( &mut self, uid: usize, attrs: &thread::Attributes, - ) -> Result { + storage: &mut ThreadMap, + ) -> Result { let stack = self.allocate_stack(attrs)?; let stack = unsafe { Stack::new(stack) }?; let tid = self.allocate_tid(); + let new = sched::thread::Thread::new(tid.get_uid(uid), stack); + storage.insert(&tid.get_uid(uid), new); + self.threads.push_back(tid.get_uid(uid), storage); - Ok(sched::thread::Thread::new(tid.get_uid(uid), stack)) + Ok(tid.get_uid(uid)) } pub fn tid_cntr(&self) -> usize { self.tid_cntr } + + pub fn threads_mut(&mut self) -> &mut list::List { + &mut self.threads + } + + pub fn threads(&self) -> &list::List { + &self.threads + } } diff --git a/src/sched/thread.rs b/src/sched/thread.rs index 7cc628d..a83203d 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -19,7 +19,8 @@ pub const IDLE_THREAD: UId = UId { }; /// Id of a task. This is only unique within a Task. -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] +#[proc_macros::fmt] +#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] pub struct Id { id: usize, owner: task::UId, @@ -45,7 +46,8 @@ impl Id { } /// Unique identifier for a thread. Build from TaskId and ThreadId. -#[derive(Clone, Copy, Debug)] +#[proc_macros::fmt] +#[derive(Clone, Copy)] #[allow(dead_code)] pub struct UId { /// A globally unique identifier for the thread. @@ -65,7 +67,7 @@ impl UId { } pub fn owner(&self) -> task::UId { - self.tid.owner + self.tid.owner() } } @@ -103,14 +105,15 @@ impl ToIndex for UId { impl Display for UId { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "T{}-{}", self.tid.owner(), self.tid.as_usize()) + write!(f, "{}-{}", self.tid.owner(), self.tid.as_usize()) } } // ------------------------------------------------------------------------- /// The state of a thread. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[proc_macros::fmt] +#[derive(Clone, Copy, PartialEq, Eq)] #[allow(dead_code)] pub enum RunState { /// The thread is currently using the cpu. @@ -121,13 +124,15 @@ pub enum RunState { Waits, } -#[derive(Debug, Clone, Copy)] +#[proc_macros::fmt] +#[derive(Clone, Copy)] pub struct State { run_state: RunState, stack: Stack, } -#[derive(Debug, Clone, Copy)] +#[proc_macros::fmt] +#[derive(Clone, Copy)] #[derive(TaggedLinks)] pub struct RtServer { budget: u64, @@ -197,7 +202,8 @@ impl Compare for RtServer { } } -#[derive(Debug, Clone, Copy)] +#[proc_macros::fmt] +#[derive(Clone, Copy)] #[derive(TaggedLinks)] pub struct Waiter { /// The time when the Thread will be awakened. @@ -237,21 +243,29 @@ impl Compare for Waiter { } } -#[derive(Debug, Clone, Copy)] +#[proc_macros::fmt] +#[derive(Clone, Copy)] pub struct WakupTree; -#[derive(Debug, Clone, Copy)] +#[proc_macros::fmt] +#[derive(Clone, Copy)] pub struct RtTree; -#[derive(Debug, Clone, Copy)] +#[proc_macros::fmt] +#[derive(Clone, Copy)] pub struct RRList; +#[proc_macros::fmt] +#[derive(Clone, Copy)] +pub struct ThreadList; + pub struct Attributes { pub entry: EntryFn, pub fin: Option, } /// The struct representing a thread. -#[derive(Debug, Clone, Copy)] +#[proc_macros::fmt] +#[derive(Clone, Copy)] #[derive(TaggedLinks)] pub struct Thread { /// The current state of the thread. @@ -265,6 +279,9 @@ pub struct Thread { #[list(tag = RRList, idx = UId)] rr_links: list::Links, + + #[list(tag = ThreadList, idx = UId)] + thread_links: list::Links, } #[allow(dead_code)] @@ -284,6 +301,7 @@ impl Thread { rt_server: None, waiter: None, rr_links: list::Links::new(), + thread_links: list::Links::new(), } } @@ -318,7 +336,13 @@ impl Thread { } pub fn task_id(&self) -> task::UId { - self.uid.tid().owner + self.uid.tid().owner() + } +} + +impl PartialEq for Thread { + fn eq(&self, other: &Self) -> bool { + self.uid == other.uid } } diff --git a/src/sync/spinlock.rs b/src/sync/spinlock.rs index 7e1b2de..14178cf 100644 --- a/src/sync/spinlock.rs +++ b/src/sync/spinlock.rs @@ -6,7 +6,7 @@ use core::sync::atomic::AtomicBool; use core::sync::atomic::Ordering; /// A mutual exclusion primitive, facilitating busy-waiting. -#[derive(Debug)] +#[proc_macros::fmt] pub struct SpinLock { lock: AtomicBool, } @@ -56,7 +56,7 @@ impl SpinLock { } /// A guard that releases the SpinLock when dropped. -#[derive(Debug)] +#[proc_macros::fmt] pub struct SpinLockGuard<'a, T: ?Sized> { lock: &'a SpinLock, value: NonNull, diff --git a/src/syscalls.rs b/src/syscalls.rs index c573d2f..d692f26 100644 --- a/src/syscalls.rs +++ b/src/syscalls.rs @@ -11,7 +11,8 @@ use sched::*; #[unsafe(no_mangle)] pub extern "C" fn handle_syscall(number: usize, args: *const c_uint) -> c_int { + let number = number as u16; // All functions that are annotated with the #[syscall_handler(num = X)] macro are syscalls. // build.rs will generate a match statement that matches the syscall number to the function which is then included here. - include!(concat!(env!("OUT_DIR"), "/syscall_dispatcher.in")) + include!(concat!(env!("OUT_DIR"), "/syscall_match.in")) } diff --git a/src/syscalls/sched.rs b/src/syscalls/sched.rs index b7b1ffd..1972b31 100644 --- a/src/syscalls/sched.rs +++ b/src/syscalls/sched.rs @@ -39,7 +39,7 @@ fn spawn_thread(func_ptr: usize) -> c_int { match sched.create_thread(None, &attrs) { Ok(uid) => { if sched.enqueue(time::tick(), uid).is_err() { - panic!("failed to enqueue thread."); + bug!("failed to enqueue thread."); } uid.as_usize() as c_int } @@ -48,3 +48,18 @@ fn spawn_thread(func_ptr: usize) -> c_int { }) } +#[syscall_handler(num = 4)] +fn exit(code: usize) -> c_int { + sched::with(|sched| { + if sched.kill_thread(None).is_err() { + bug!("failed to terminate thread."); + } + }); + 0 +} + +#[syscall_handler(num = 5)] +fn kick_thread(uid: usize) -> c_int { + 0 +} + diff --git a/src/types/array.rs b/src/types/array.rs index 67a9cba..15eae5b 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -13,7 +13,7 @@ use core::{ }; /// This is a fixed-size map that can store up to N consecutive elements. -#[derive(Debug)] +#[proc_macros::fmt] pub struct IndexMap { data: [Option; N], @@ -113,7 +113,7 @@ impl IndexMap None } - pub fn at_cont(&self, idx: usize) -> Option<&V> { + pub fn raw_at(&self, idx: usize) -> Option<&V> { if idx < N { self.data[idx].as_ref() } else { @@ -121,6 +121,14 @@ impl IndexMap } } + pub fn raw_at_mut(&mut self, idx: usize) -> Option<&mut V> { + if idx < N { + self.data[idx].as_mut() + } else { + None + } + } + pub fn find_empty(&self) -> Option { for (i, slot) in self.data.iter().enumerate() { if slot.is_none() { @@ -137,14 +145,14 @@ impl Index for IndexMap type Output = V; fn index(&self, index: K) -> &Self::Output { - self.get(&index).unwrap() + self.get::(index).unwrap() } } impl IndexMut for IndexMap { fn index_mut(&mut self, index: K) -> &mut Self::Output { - self.get_mut(&index).unwrap() + self.get_mut::(index).unwrap() } } @@ -173,23 +181,23 @@ impl GetMut for IndexMap { } fn get2_mut>(&mut self, index1: Q, index2: Q) -> (Option<&mut Self::Output>, Option<&mut Self::Output>) { - let index1 = K::to_index(Some(index1.borrow())); - let index2 = K::to_index(Some(index2.borrow())); + let idx1 = K::to_index(Some(index1.borrow())); + let idx2 = K::to_index(Some(index2.borrow())); - if index1 == index2 { + if idx1 == idx2 { debug_assert!(false, "get2_mut called with identical indices"); return (None, None); } - let (left, right) = self.data.split_at_mut(index1.max(index2)); + let (left, right) = self.data.split_at_mut(idx1.max(idx2)); - if index1 < index2 { - let elem1 = left[index1].as_mut(); + if idx1 < idx2 { + let elem1 = left[idx1].as_mut(); let elem2 = right[0].as_mut(); (elem1, elem2) } else { let elem1 = right[0].as_mut(); - let elem2 = left[index2].as_mut(); + let elem2 = left[idx2].as_mut(); (elem1, elem2) } } @@ -200,18 +208,18 @@ impl GetMut for IndexMap { index2: Q, index3: Q, ) -> (Option<&mut Self::Output>, Option<&mut Self::Output>, Option<&mut Self::Output>) { - let index1 = K::to_index(Some(index1.borrow())); - let index2 = K::to_index(Some(index2.borrow())); - let index3 = K::to_index(Some(index3.borrow())); + let idx1 = K::to_index(Some(index1.borrow())); + let idx2 = K::to_index(Some(index2.borrow())); + let idx3 = K::to_index(Some(index3.borrow())); - if index1 == index2 || index1 == index3 || index2 == index3 { + if idx1 == idx2 || idx1 == idx3 || idx2 == idx3 { debug_assert!(false, "get3_mut called with identical indices"); return (None, None, None); } - let ptr1 = &mut self.data[index1] as *mut Option; - let ptr2 = &mut self.data[index2] as *mut Option; - let ptr3 = &mut self.data[index3] as *mut Option; + let ptr1 = &mut self.data[idx1] as *mut Option; + let ptr2 = &mut self.data[idx2] as *mut Option; + let ptr3 = &mut self.data[idx3] as *mut Option; // Safety: the elements at index1, index2 and index3 are nowhere else borrowed mutably by function contract. // And they are disjoint because of the check above. @@ -220,7 +228,7 @@ impl GetMut for IndexMap { } /// This is a vector that can store up to N elements inline and will allocate on the heap if more are needed. -#[derive(Debug)] +#[proc_macros::fmt] pub struct Vec { len: usize, data: [MaybeUninit; N], diff --git a/src/types/boxed.rs b/src/types/boxed.rs index 0f23e56..012245b 100644 --- a/src/types/boxed.rs +++ b/src/types/boxed.rs @@ -9,7 +9,7 @@ use core::{ }; /// A heap-allocated memory block. -#[derive(Debug)] +#[proc_macros::fmt] pub struct Box { /// Pointer to the heap-allocated memory. /// This is uniquely owned, so no covariance issues. diff --git a/src/types/heap.rs b/src/types/heap.rs index aa0b579..a25b1f8 100644 --- a/src/types/heap.rs +++ b/src/types/heap.rs @@ -5,7 +5,7 @@ use crate::error::Result; use super::array::Vec; /// An array-based binary heap, with N elements stored inline. -#[derive(Debug)] +#[proc_macros::fmt] pub struct BinaryHeap { vec: Vec, } diff --git a/src/types/list.rs b/src/types/list.rs index 7156d29..be84923 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -19,7 +19,8 @@ pub trait Linkable { } #[allow(dead_code)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[proc_macros::fmt] +#[derive(Clone, Copy, PartialEq, Eq)] pub struct Links { prev: Option, next: Option, @@ -231,7 +232,8 @@ mod tests { use super::{Linkable, Links, List}; use crate::types::{array::IndexMap, traits::{Get, ToIndex}}; - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + #[proc_macros::fmt] + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] struct Id(usize); impl ToIndex for Id { diff --git a/src/types/queue.rs b/src/types/queue.rs index 4e2d159..fbd0ba0 100644 --- a/src/types/queue.rs +++ b/src/types/queue.rs @@ -5,7 +5,7 @@ use super::boxed::Box; use crate::utils::KernelError; /// A ring-buffer based queue, with N elements stored inline. TODO: Make this growable. -#[derive(Debug)] +#[proc_macros::fmt] pub struct Queue { data: Vec, len: usize, diff --git a/src/types/rbtree.rs b/src/types/rbtree.rs index 842ffe2..3a5b0f0 100644 --- a/src/types/rbtree.rs +++ b/src/types/rbtree.rs @@ -22,7 +22,8 @@ pub trait Compare { } #[allow(dead_code)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[proc_macros::fmt] +#[derive(Clone, Copy, PartialEq, Eq)] pub struct Links { parent: Option, left: Option, @@ -44,7 +45,8 @@ impl Links { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[proc_macros::fmt] +#[derive(Clone, Copy, PartialEq, Eq)] enum Color { Red, Black, @@ -62,7 +64,8 @@ impl RbTree } /// Inserts `id` into the tree. If `id` already exists in the tree, it is first removed and then re-inserted. Errors if `id` does not exist in `storage`. - pub fn insert + GetMut>(&mut self, id: T, storage: &mut S) -> Result<()> + pub fn insert< + S: Get + GetMut>(&mut self, id: T, storage: &mut S) -> Result<()> where >::Output: Linkable + Compare,{ let already_linked = { let node = storage.get(id).ok_or(kerr!(NotFound))?; diff --git a/src/types/traits.rs b/src/types/traits.rs index cc02d85..aa8c951 100644 --- a/src/types/traits.rs +++ b/src/types/traits.rs @@ -18,6 +18,12 @@ pub trait ToIndex { fn to_index>(index: Option) -> usize; } +impl ToIndex for usize { + fn to_index>(index: Option) -> usize { + index.map_or(0, |i| *i.borrow()) + } +} + pub trait Project

{ fn project(&self) -> Option<&P>; fn project_mut(&mut self) -> Option<&mut P>; diff --git a/src/uapi/sched.rs b/src/uapi/sched.rs index 5437a2b..540060a 100644 --- a/src/uapi/sched.rs +++ b/src/uapi/sched.rs @@ -9,6 +9,18 @@ pub fn sleep_for(duration: u64) -> isize { hal::asm::syscall!(2, (duration >> 32) as u32, duration as u32) } +pub fn yield_thread() -> isize { + let until = u64::MAX; + hal::asm::syscall!(1, (until >> 32) as u32, until as u32) +} + pub fn spawn_thread(func_ptr: EntryFn) -> isize { hal::asm::syscall!(3, func_ptr as u32) +} + +pub fn exit(code: usize) -> ! { + hal::asm::syscall!(4, code as u32); + loop { + hal::asm::nop!(); + } } \ No newline at end of file From 5f22c50b44df81f38a001fb5fa25c32bce99fa12 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Tue, 31 Mar 2026 18:35:45 +0000 Subject: [PATCH 29/42] fixed rt sched. --- examples/hello-world/Cargo.toml | 2 +- examples/hello-world/src/main.rs | 24 +++------ src/idle.rs | 2 +- src/macros.rs | 2 - src/sched.rs | 93 ++++++++++++++++++++++---------- src/sched/rr.rs | 8 +-- src/sched/rt.rs | 22 ++++---- src/sched/task.rs | 10 ++-- src/sched/thread.rs | 78 +++++++++++++++++---------- src/syscalls/sched.rs | 11 +++- src/uapi.rs | 3 +- src/uapi/sched.rs | 18 +++++-- src/uapi/time.rs | 9 ++++ src/uspace.rs | 2 + 14 files changed, 178 insertions(+), 106 deletions(-) create mode 100644 src/uapi/time.rs diff --git a/examples/hello-world/Cargo.toml b/examples/hello-world/Cargo.toml index 81645ee..1bd3456 100644 --- a/examples/hello-world/Cargo.toml +++ b/examples/hello-world/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -osiris = { workspace = true, features = ["defmt"] } +osiris = { workspace = true } [build-dependencies] cfg_aliases = "0.2.1" diff --git a/examples/hello-world/src/main.rs b/examples/hello-world/src/main.rs index baa7bb3..4bad2e0 100644 --- a/examples/hello-world/src/main.rs +++ b/examples/hello-world/src/main.rs @@ -4,26 +4,13 @@ use osiris::app_main; extern "C" fn second_thread() { - osiris::uprintln!("Hello from the second thread!"); - - let mut tick = 0; - for i in 0..5 { - osiris::uprintln!("Second thread tick: {}", tick); - tick += 1; - osiris::uapi::sched::sleep_for(1500); - } - - osiris::uapi::sched::exit(0); - osiris::uprintln!("This will never be printed."); -} - -extern "C" fn generator_thread() { - + let mut time = osiris::uapi::time::tick(); let mut cnt = 0; loop { - osiris::uapi::sched::yield_thread(); + time += 100; osiris::uprintln!("Number: {}", cnt); cnt += 1; + osiris::uapi::sched::sleep(time); } } @@ -31,8 +18,9 @@ extern "C" fn generator_thread() { fn main() { osiris::uprintln!("Hello World!"); let mut tick = 0; - osiris::uapi::sched::spawn_thread(second_thread); - osiris::uapi::sched::spawn_thread(generator_thread); + let attrs = osiris::uapi::sched::RtAttrs { deadline: 100, period: 100, budget: 100 }; + + osiris::uapi::sched::spawn_thread(second_thread, Some(attrs)); loop { osiris::uprintln!("Tick: {}", tick); tick += 1; diff --git a/src/idle.rs b/src/idle.rs index 479f407..940605e 100644 --- a/src/idle.rs +++ b/src/idle.rs @@ -7,7 +7,7 @@ extern "C" fn entry() { } pub fn init() { - let attrs = sched::thread::Attributes { entry, fin: None }; + let attrs = sched::thread::Attributes { entry, fin: None, attrs: None }; sched::with(|sched| { if let Err(e) = sched.create_thread(Some(sched::task::KERNEL_TASK), &attrs) { panic!("failed to create idle thread. Error: {}", e); diff --git a/src/macros.rs b/src/macros.rs index 0ddf819..78ddb33 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,6 +1,4 @@ //! Macros for kernel development. -use defmt_rtt as _; - #[macro_export] macro_rules! debug { diff --git a/src/sched.rs b/src/sched.rs index 4001fa1..4e5f130 100644 --- a/src/sched.rs +++ b/src/sched.rs @@ -94,7 +94,7 @@ impl Scheduler { } /// Triggers a reschedule at *latest* when we hit timepoint `next`. - fn schedule_resched(now: u64, next: u64) { + fn next_resched(now: u64, next: u64) { let old = NEXT_TICK.load(Ordering::Acquire); if old > now && old <= next { @@ -126,15 +126,18 @@ impl Scheduler { while let Some(uid) = self.wakeup.min() { let mut done = false; WaiterView::::with(&mut self.threads, |view| { - let waiter = view.get(uid).expect("THIS IS A BUG!"); - if waiter.until() > now { - Self::schedule_resched(now, waiter.until()); - done = true; - return; - } - - if let Err(_) = self.wakeup.remove(uid, view) { - bug!("failed to remove thread {} from wakeup tree.", uid); + if let Some(waiter) = view.get(uid) { + if waiter.until() > now { + Self::next_resched(now, waiter.until()); + done = true; + return; + } + + if let Err(_) = self.wakeup.remove(uid, view) { + bug!("failed to remove thread {} from wakeup tree.", uid); + } + } else { + bug!("failed to get thread {} from wakeup tree.", uid); } }); @@ -148,40 +151,57 @@ impl Scheduler { } } - pub fn do_sched(&mut self, now: u64) -> Option<(*mut c_void, &mut task::Task)> { + /// Syncs the new state after the last do_sched call to the scheduler, and returns whether we need to immediately reschedule. + fn sync_to_sched(&mut self, now: u64) -> bool { let dt = now - self.last_tick; self.last_tick = now; if let Some(old) = self.current { - rt::ServerView::::with(&mut self.threads, |view| { - self.rt_scheduler.put(old, dt, view); + let throttle = rt::ServerView::::with(&mut self.threads, |view| { + self.rt_scheduler.put(old, dt, view) }); - self.rr_scheduler.put(old, dt); + + if let Some(throttle) = throttle { + self.sleep_until(throttle, now); + return true; + } + + self.rr_scheduler.put(old, dt as u32); } self.do_wakeups(now); + false + } + + fn select_next(&mut self) -> (thread::UId, u32) { + rt::ServerView::::with(&mut self.threads, |view| self.rt_scheduler.pick(view)) + .or_else(|| self.rr_scheduler.pick(&mut self.threads)) + .unwrap_or((thread::IDLE_THREAD, 1000)) + } + + pub fn do_sched(&mut self, now: u64) -> Option<(*mut c_void, &mut task::Task)> { + // Sync the new state to the scheduler. + if self.sync_to_sched(now) { + // Trigger reschedule after interrupts are enabled. + return None; + } - let pick = - rt::ServerView::::with(&mut self.threads, |view| self.rt_scheduler.pick(now, view)); - let pick = pick.or_else(|| self.rr_scheduler.pick(&mut self.threads)); - let (new, budget) = pick.unwrap_or((thread::IDLE_THREAD, 1000)); + // Pick the next thread to run. + let (new, budget) = self.select_next(); // At this point, the task/thread must exist. Everything else is a bug. - let (ctx, task_id) = if let Some(thread) = self.threads.get(new) { - (thread.ctx(), thread.task_id()) - } else { + let Some(thread) = self.threads.get(new) else { bug!("failed to pick thread {}. Does not exist.", new); }; + let (ctx, task_id) = (thread.ctx(), thread.task_id()); - let task = if let Some(task) = self.tasks.get_mut(task_id) { - task - } else { + let Some(task) = self.tasks.get_mut(task_id) else { bug!("failed to get task {}. Does not exist.", task_id); }; // We don't need to resched if the thread has budget. self.current = Some(new); - Self::schedule_resched(now, now.saturating_add(budget)); + Self::next_resched(now, now.saturating_add(budget as u64)); Some((ctx, task)) } @@ -253,11 +273,23 @@ impl Scheduler { self.rt_scheduler.dequeue(id, view); }); self.rr_scheduler.dequeue(id, &mut self.threads); + self.wakeup + .remove(id, &mut WaiterView::::new(&mut self.threads)); if task.threads_mut().remove(id, &mut self.threads).is_err() { // This should not be possible. The thread ID is from the thread list of the task, so it must exist. bug!("failed to remove thread {} from task {}.", id, uid); } + + if self.threads.remove(&id).is_none() { + // This should not be possible. The thread ID is from the thread list of the task, so it must exist. + bug!("failed to remove thread {} from thread list.", id); + } + + if Some(id) == self.current { + self.current = None; + reschedule(); + } } self.tasks.remove(&uid).ok_or(kerr!(InvalidArgument))?; @@ -283,6 +315,15 @@ impl Scheduler { pub fn kill_thread(&mut self, uid: Option) -> Result<()> { let uid = uid.unwrap_or(self.current.ok_or(kerr!(InvalidArgument))?); self.dequeue(uid); + self.wakeup + .remove(uid, &mut WaiterView::::new(&mut self.threads)); + + self.tasks + .get_mut(uid.tid().owner()) + .ok_or(kerr!(InvalidArgument))? + .threads_mut() + .remove(uid, &mut self.threads)?; + self.threads.remove(&uid).ok_or(kerr!(InvalidArgument))?; if Some(uid) == self.current { @@ -353,8 +394,6 @@ pub extern "C" fn sched_enter(mut ctx: *mut c_void) -> *mut c_void { dispch::prepare(task); } ctx = new; - } else { - bug!("failed to schedule a thread. No threads available."); } ctx diff --git a/src/sched/rr.rs b/src/sched/rr.rs index ee9f877..1e966c9 100644 --- a/src/sched/rr.rs +++ b/src/sched/rr.rs @@ -6,8 +6,8 @@ pub struct Scheduler { queue: List, current: Option, - current_left: u64, - quantum: u64, + current_left: u32, + quantum: u32, } impl Scheduler { @@ -20,7 +20,7 @@ impl Scheduler { self.queue.push_back(uid, storage).map_err(|_| kerr!(InvalidArgument)) } - pub fn put(&mut self, uid: thread::UId, dt: u64) { + pub fn put(&mut self, uid: thread::UId, dt: u32) { if let Some(current) = self.current { if current == uid { self.current_left = self.current_left.saturating_sub(dt); @@ -28,7 +28,7 @@ impl Scheduler { } } - pub fn pick(&mut self, storage: &mut super::ThreadMap) -> Option<(thread::UId, u64)> { + pub fn pick(&mut self, storage: &mut super::ThreadMap) -> Option<(thread::UId, u32)> { match self.current { Some(current) if self.current_left > 0 => return Some((current, self.current_left)), Some(current) => { diff --git a/src/sched/rt.rs b/src/sched/rt.rs index 7c3405b..33799a6 100644 --- a/src/sched/rt.rs +++ b/src/sched/rt.rs @@ -15,31 +15,27 @@ impl Scheduler { pub fn enqueue(&mut self, uid: thread::UId, now: u64, storage: &mut ServerView) { if let Some(server) = storage.get_mut(uid) { - server.replenish(now); + // Threads are only enqueued when they are runnable. + server.on_wakeup(now); self.edf.insert(uid, storage); } } - pub fn put(&mut self, uid: thread::UId, dt: u64, storage: &mut ServerView) { + /// This should be called on each do_schedule call, to update the internal scheduler state. + /// If this function returns Some(u64) it means the current thread has exhausted its budget and should be throttled until the returned timestamp. + pub fn put(&mut self, uid: thread::UId, dt: u64, storage: &mut ServerView) -> Option { if Some(uid) == self.edf.min() { if let Some(server) = storage.get_mut(uid) { - server.consume(dt); + return server.consume(dt); } else { bug!("thread {} not found in storage", uid); } } - } - pub fn pick(&mut self, now: u64, storage: &mut ServerView) -> Option<(thread::UId, u64)> { - let id = self.edf.min()?; - - if storage.get(id)?.budget() == 0 { - self.edf.remove(id, storage); - storage.get_mut(id)?.replenish(now); - self.edf.insert(id, storage); - } + None + } - // Insert updated the min cache. + pub fn pick(&mut self, storage: &mut ServerView) -> Option<(thread::UId, u32)> { self.edf.min().and_then(|id| storage.get(id).map(|s| (id, s.budget()))) } diff --git a/src/sched/task.rs b/src/sched/task.rs index c1e094a..3c74835 100644 --- a/src/sched/task.rs +++ b/src/sched/task.rs @@ -9,12 +9,12 @@ use hal::Stack; use hal::stack::Stacklike; use crate::error::Result; -use crate::sched::{GlobalScheduler, ThreadMap, thread}; +use crate::sched::{ThreadMap, thread}; use crate::types::list; use crate::{mem, sched}; use crate::mem::vmm::AddressSpacelike; -use crate::types::traits::{Get, GetMut, ToIndex}; +use crate::types::traits::ToIndex; pub struct Defaults { pub stack_pages: usize, @@ -123,9 +123,9 @@ impl Task { let stack = unsafe { Stack::new(stack) }?; let tid = self.allocate_tid(); - let new = sched::thread::Thread::new(tid.get_uid(uid), stack); - storage.insert(&tid.get_uid(uid), new); - self.threads.push_back(tid.get_uid(uid), storage); + let new = sched::thread::Thread::new(tid.get_uid(uid), stack, attrs.attrs); + storage.insert(&tid.get_uid(uid), new)?; + self.threads.push_back(tid.get_uid(uid), storage)?; Ok(tid.get_uid(uid)) } diff --git a/src/sched/thread.rs b/src/sched/thread.rs index a83203d..80d01f1 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -3,19 +3,25 @@ use core::fmt::Display; use core::{borrow::Borrow, ffi::c_void}; -use hal::{Stack, stack::EntryFn}; use hal::stack::{FinFn, Stacklike}; +use hal::{Stack, stack::EntryFn}; use proc_macros::TaggedLinks; use crate::error::Result; use crate::sched::task::{self, KERNEL_TASK}; -use crate::time::tick; use crate::types::list; -use crate::{types::{rbtree::{self, Compare}, traits::{Project, ToIndex}}}; +use crate::types::{ + rbtree::{self, Compare}, + traits::{Project, ToIndex}, +}; +use crate::uapi; pub const IDLE_THREAD: UId = UId { uid: 1, - tid: Id { id: 0, owner: KERNEL_TASK }, + tid: Id { + id: 0, + owner: KERNEL_TASK, + }, }; /// Id of a task. This is only unique within a Task. @@ -132,12 +138,11 @@ pub struct State { } #[proc_macros::fmt] -#[derive(Clone, Copy)] -#[derive(TaggedLinks)] +#[derive(Clone, Copy, TaggedLinks)] pub struct RtServer { - budget: u64, - budget_left: u64, - period: u64, + budget: u32, + budget_left: u32, + period: u32, deadline: u64, // Back-reference to the thread uid. @@ -149,36 +154,50 @@ pub struct RtServer { } impl RtServer { - pub fn new(budget: u64, period: u64, uid: UId) -> Self { + pub fn new(budget: u32, period: u32, deadline: u64, uid: UId) -> Self { Self { budget, budget_left: budget, period, - deadline: tick() + period, + deadline, uid, _rt_links: rbtree::Links::new(), } } - pub fn budget_left(&self) -> u64 { + pub fn budget_left(&self) -> u32 { self.budget_left } - pub fn budget(&self) -> u64 { + pub fn budget(&self) -> u32 { self.budget } - pub fn replenish(&mut self, now: u64) { - self.deadline += self.period; - self.budget_left = self.budget; + fn violates_sched(&self, now: u64) -> bool { + self.budget_left as u64 * self.period as u64 + > self.budget as u64 * (self.deadline.saturating_sub(now)) } - pub fn consume(&mut self, dt: u64) { - if self.budget_left >= dt { - self.budget_left -= dt; - } else { - self.budget_left = 0; + pub fn on_wakeup(&mut self, now: u64) { + if self.deadline <= now || self.violates_sched(now) { + self.deadline = now + self.period as u64; + self.budget_left = self.budget; + } + } + + pub fn replenish(&mut self) { + self.deadline = self.deadline + self.period as u64; + self.budget_left += self.budget; + } + + pub fn consume(&mut self, dt: u64) -> Option { + self.budget_left = self.budget_left.saturating_sub(dt as u32); + + if self.budget_left == 0 { + return Some(self.deadline); } + + None } pub fn deadline(&self) -> u64 { @@ -203,8 +222,7 @@ impl Compare for RtServer { } #[proc_macros::fmt] -#[derive(Clone, Copy)] -#[derive(TaggedLinks)] +#[derive(Clone, Copy, TaggedLinks)] pub struct Waiter { /// The time when the Thread will be awakened. until: u64, @@ -261,12 +279,12 @@ pub struct ThreadList; pub struct Attributes { pub entry: EntryFn, pub fin: Option, + pub attrs: Option, } /// The struct representing a thread. #[proc_macros::fmt] -#[derive(Clone, Copy)] -#[derive(TaggedLinks)] +#[derive(Clone, Copy, TaggedLinks)] pub struct Thread { /// The current state of the thread. state: State, @@ -291,14 +309,16 @@ impl Thread { /// `stack` - The stack of the thread. /// /// Returns a new thread. - pub fn new(uid: UId, stack: Stack) -> Self { + pub fn new(uid: UId, stack: Stack, rtattrs: Option) -> Self { + let server = + rtattrs.map(|attrs| RtServer::new(attrs.budget, attrs.period, attrs.deadline, uid)); Self { state: State { run_state: RunState::Ready, stack, }, uid, - rt_server: None, + rt_server: server, waiter: None, rr_links: list::Links::new(), thread_links: list::Links::new(), @@ -348,7 +368,7 @@ impl PartialEq for Thread { impl Project for Thread { fn project(&self) -> Option<&RtServer> { - self.rt_server.as_ref() + self.rt_server.as_ref() } fn project_mut(&mut self) -> Option<&mut RtServer> { @@ -358,7 +378,7 @@ impl Project for Thread { impl Project for Thread { fn project(&self) -> Option<&Waiter> { - self.waiter.as_ref() + self.waiter.as_ref() } fn project_mut(&mut self) -> Option<&mut Waiter> { diff --git a/src/syscalls/sched.rs b/src/syscalls/sched.rs index 1972b31..258574e 100644 --- a/src/syscalls/sched.rs +++ b/src/syscalls/sched.rs @@ -4,7 +4,7 @@ use core::ffi::c_int; use proc_macros::syscall_handler; -use crate::{sched, time}; +use crate::{sched, time, uapi::sched::RtAttrs}; #[syscall_handler(num = 1)] fn sleep(until_hi: u32, until_lo: u32) -> c_int { @@ -30,11 +30,18 @@ fn sleep_for(duration_hi: u32, duration_lo: u32) -> c_int { } #[syscall_handler(num = 3)] -fn spawn_thread(func_ptr: usize) -> c_int { +fn spawn_thread(func_ptr: usize, attrs: *const RtAttrs) -> c_int { sched::with(|sched| { + let attrs = if attrs.is_null() { + None + } else { + Some(unsafe { *attrs }) + }; + let attrs = sched::thread::Attributes { entry: unsafe { core::mem::transmute(func_ptr) }, fin: None, + attrs, }; match sched.create_thread(None, &attrs) { Ok(uid) => { diff --git a/src/uapi.rs b/src/uapi.rs index bfc9b49..60a2b0e 100644 --- a/src/uapi.rs +++ b/src/uapi.rs @@ -1,2 +1,3 @@ pub mod print; -pub mod sched; \ No newline at end of file +pub mod sched; +pub mod time; \ No newline at end of file diff --git a/src/uapi/sched.rs b/src/uapi/sched.rs index 540060a..ef39306 100644 --- a/src/uapi/sched.rs +++ b/src/uapi/sched.rs @@ -1,6 +1,5 @@ use hal::stack::EntryFn; - pub fn sleep(until: u64) -> isize { hal::asm::syscall!(1, (until >> 32) as u32, until as u32) } @@ -14,8 +13,21 @@ pub fn yield_thread() -> isize { hal::asm::syscall!(1, (until >> 32) as u32, until as u32) } -pub fn spawn_thread(func_ptr: EntryFn) -> isize { - hal::asm::syscall!(3, func_ptr as u32) +#[repr(C)] +#[derive(Clone, Copy)] +pub struct RtAttrs { + pub deadline: u64, + pub period: u32, + pub budget: u32, +} + +pub fn spawn_thread(func_ptr: EntryFn, attrs: Option) -> isize { + let attr_ptr = if let Some(attrs) = attrs { + &attrs as *const RtAttrs as usize + } else { + 0 + }; + hal::asm::syscall!(3, func_ptr as u32, attr_ptr) } pub fn exit(code: usize) -> ! { diff --git a/src/uapi/time.rs b/src/uapi/time.rs new file mode 100644 index 0000000..1b63ebb --- /dev/null +++ b/src/uapi/time.rs @@ -0,0 +1,9 @@ +use crate::time; + +pub fn mono_now() -> u64 { + time::mono_now() +} + +pub fn tick() -> u64 { + time::tick() +} \ No newline at end of file diff --git a/src/uspace.rs b/src/uspace.rs index 2f11b34..91ae66b 100644 --- a/src/uspace.rs +++ b/src/uspace.rs @@ -15,7 +15,9 @@ pub fn init_app() { let attrs = sched::thread::Attributes { entry: app_main_entry, fin: None, + attrs: None, }; + sched::with(|sched| { if let Ok(uid) = sched.create_thread(Some(sched::task::KERNEL_TASK), &attrs) { if sched.enqueue(time::tick(), uid).is_err() { From 9a86fefccdd4e56bba3a99c8f724558804b6fa69 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Wed, 1 Apr 2026 15:42:57 +0000 Subject: [PATCH 30/42] fixed bug. --- machine/arm/stm32l4xx/interface/clock.c | 2 +- machine/arm/stm32l4xx/interface/lib.c | 2 +- machine/arm/stm32l4xx/interface/lib.h | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/machine/arm/stm32l4xx/interface/clock.c b/machine/arm/stm32l4xx/interface/clock.c index ed3acac..4110c3c 100644 --- a/machine/arm/stm32l4xx/interface/clock.c +++ b/machine/arm/stm32l4xx/interface/clock.c @@ -53,7 +53,7 @@ void tim2_hndlr(void) } } -void SystemClock_Config(void) +void init_clock_cfg(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; diff --git a/machine/arm/stm32l4xx/interface/lib.c b/machine/arm/stm32l4xx/interface/lib.c index 3c7d723..41319fa 100644 --- a/machine/arm/stm32l4xx/interface/lib.c +++ b/machine/arm/stm32l4xx/interface/lib.c @@ -31,7 +31,7 @@ void init_hal(void) { enable_faults(); - SystemClock_Config(); + init_clock_cfg(); init_systick(); } diff --git a/machine/arm/stm32l4xx/interface/lib.h b/machine/arm/stm32l4xx/interface/lib.h index 7b9637e..c66f171 100644 --- a/machine/arm/stm32l4xx/interface/lib.h +++ b/machine/arm/stm32l4xx/interface/lib.h @@ -1 +1,3 @@ -#pragma once \ No newline at end of file +#pragma once + +void init_clock_cfg(void); \ No newline at end of file From b5823f755b10dd5ae762d27b71c1022bfeaa12fb Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:29:20 +0000 Subject: [PATCH 31/42] fix stuff --- .devcontainer/Dockerfile | 2 +- .devcontainer/devcontainer.json | 4 ++- build.rs | 54 ++-------------------------- config.toml | 7 +--- examples/hello-world/Cargo.toml | 7 ++++ machine/arm/Cargo.toml | 1 - machine/arm/src/panic.rs | 1 + machine/arm/src/sched.rs | 2 +- machine/arm/stm32l4xx/CMakeLists.txt | 2 +- machine/testing/src/lib.rs | 3 ++ xtasks/Cargo.toml | 2 +- xtasks/crates/config/Cargo.toml | 2 +- xtasks/crates/config/src/lib.rs | 3 ++ xtasks/crates/config/src/main.rs | 4 +++ xtasks/crates/dtgen/Cargo.toml | 2 +- xtasks/crates/dtgen/src/lib.rs | 3 ++ xtasks/crates/dtgen/src/main.rs | 4 +++ xtasks/crates/injector/Cargo.toml | 2 +- xtasks/crates/injector/src/main.rs | 4 +++ xtasks/crates/pack/Cargo.toml | 2 +- xtasks/crates/pack/src/main.rs | 4 +++ xtasks/logging/Cargo.toml | 2 +- xtasks/logging/src/lib.rs | 4 +++ xtasks/src/main.rs | 4 +++ 24 files changed, 57 insertions(+), 68 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 0857d86..d0a9646 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -86,7 +86,7 @@ ENV CARGO_HOME=/usr/local/cargo ENV KANI_HOME=/usr/local/kani ENV PATH="/usr/local/cargo/bin:${PATH}" -ARG RUST_VERSION=1.93.1 +ARG RUST_VERSION=1.94.1 RUN --mount=type=cache,target=/usr/local/cargo/registry \ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ | sh -s -- -y --no-modify-path --default-toolchain ${RUST_VERSION} && \ diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 89b2ba7..175a961 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -36,7 +36,9 @@ "runArgs": [ // Mount USB devices under Linux "--device", - "/dev/bus/usb:/dev/bus/usb" + "/dev/bus/usb:/dev/bus/usb", + "--group-add", + "keep-groups" ], "mounts": [ // Make ssh keys available diff --git a/build.rs b/build.rs index 1efb348..42b6eb3 100644 --- a/build.rs +++ b/build.rs @@ -22,6 +22,8 @@ fn main() { panic!("Failed to generate syscall match statement."); } + generate_device_tree().expect("Failed to generate device tree."); + cfg_aliases! { freestanding: { all(not(test), not(doctest), not(doc), not(kani), any(target_os = "none", target_os = "unknown")) }, } @@ -32,9 +34,8 @@ fn main() { fn generate_device_tree() -> Result<(), Box> { let dts = std::env::var("OSIRIS_TUNING_DTS").unwrap_or_else(|_| "nucleo_l4r5zi.dts".to_string()); - println!("cargo::rerun-if-changed={dts}"); - let dts_path = std::path::Path::new("boards").join(dts); + println!("cargo::rerun-if-changed={}", dts_path.display()); // dependencies SoC/HAL/pins let zephyr = Path::new(&std::env::var("OUT_DIR").unwrap()).join("zephyr"); @@ -158,55 +159,6 @@ fn sparse_clone( // Syscalls --------------------------------------------------------------------------------------- -fn generate_syscalls_export>(root: P) -> Result<(), std::io::Error> { - let syscalls = collect_syscalls_export(root); - - let out_dir = std::env::var("OUT_DIR").unwrap(); - let out_path = Path::new(&out_dir).join("syscalls_export.rs"); - let mut file = File::create(out_path)?; - - writeln!(file, "// This file is @generated by build.rs. Do not edit!")?; - - for (name, (number, inputs)) in &syscalls { - let mut args = &inputs.iter().fold("".to_owned(), |acc, arg| { - acc + "," + &arg.into_token_stream().to_string() - })[..]; - if !args.is_empty() { - args = &args[1..]; - } - let names = get_arg_names(args); - writeln!(file)?; - writeln!(file, "pub fn {name}({args}) {{")?; - writeln!(file, " hal::asm::syscall!({number}{names});")?; - writeln!(file, "}}")?; - } - - Ok(()) -} - -fn get_arg_names(args: &str) -> String { - if args.is_empty() { - return "".to_string(); - } - let mut in_arg_name = true; - - ", ".to_owned() - + &args.chars().fold("".to_owned(), |mut acc, char| { - if char.eq(&' ') { - in_arg_name = false; - return acc; - } - if char.eq(&',') { - in_arg_name = true; - return acc + ", "; - } - if in_arg_name { - acc.push(char); - } - acc - }) -} - fn gen_syscall_match(root: &Path, out: &Path) -> Result<(), std::io::Error> { let syscalls = find_syscalls(root); let mut file = File::create(out.join("syscall_match.in"))?; diff --git a/config.toml b/config.toml index 519b669..0c03ce5 100644 --- a/config.toml +++ b/config.toml @@ -1,10 +1,5 @@ [env] -OSIRIS_ARM_HAL = "stm32l4xx" -OSIRIS_ARM_STM32L4XX_VARIANT = "r5zi" -OSIRIS_DEBUG_UART = "UART5" -OSIRIS_DEBUG_RUNTIMESYMBOLS = "false" -OSIRIS_TUNING_ENABLEFPU = "false" OSIRIS_STACKPAGES = "1" [build] -target = "thumbv7em-none-eabi" +target = "host-tuple" diff --git a/examples/hello-world/Cargo.toml b/examples/hello-world/Cargo.toml index 1bd3456..a86e875 100644 --- a/examples/hello-world/Cargo.toml +++ b/examples/hello-world/Cargo.toml @@ -3,6 +3,13 @@ name = "hello-world" version = "0.1.0" edition = "2024" +[[bin]] +name = "hello-world" +path = "src/main.rs" +test = false +bench = false +doctest = false + [dependencies] osiris = { workspace = true } diff --git a/machine/arm/Cargo.toml b/machine/arm/Cargo.toml index 9fb4753..bafea9d 100644 --- a/machine/arm/Cargo.toml +++ b/machine/arm/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "hal-arm" version = "0.1.0" -rust-version = "1.85.0" authors = ["Thomas Wachter"] edition = "2024" build = "build.rs" diff --git a/machine/arm/src/panic.rs b/machine/arm/src/panic.rs index 9025b8a..946ec21 100644 --- a/machine/arm/src/panic.rs +++ b/machine/arm/src/panic.rs @@ -7,5 +7,6 @@ use crate::asm; pub fn panic_handler(_info: &PanicInfo) -> ! { asm::disable_irq_save(); + #[allow(clippy::empty_loop)] loop {} } diff --git a/machine/arm/src/sched.rs b/machine/arm/src/sched.rs index eb3f274..9521f33 100644 --- a/machine/arm/src/sched.rs +++ b/machine/arm/src/sched.rs @@ -72,7 +72,7 @@ impl ArmStack { } fn is_call_aligned(sp: StackPtr) -> bool { - (sp.offset % 2) == 0 + sp.offset.is_multiple_of(2) } fn in_bounds(&self, sp: *mut u32) -> Option { diff --git a/machine/arm/stm32l4xx/CMakeLists.txt b/machine/arm/stm32l4xx/CMakeLists.txt index 3e11634..d692d85 100644 --- a/machine/arm/stm32l4xx/CMakeLists.txt +++ b/machine/arm/stm32l4xx/CMakeLists.txt @@ -44,7 +44,7 @@ set(LINKER_SCRIPT_OUT ${OUT_DIR}/link.ld) # We track environment variables that start with OSIRIS_ as dependencies as they can change the output of the compilation set(DEPS_FILE ${OUT_DIR}/deps.txt) -file(WRITE ${DEPS_FILE} "${CONFIG_DEFINES}") +file(GENERATE OUTPUT ${DEPS_FILE} CONTENT "${CONFIG_DEFINES}") add_custom_command( OUTPUT ${LINKER_SCRIPT_OUT} diff --git a/machine/testing/src/lib.rs b/machine/testing/src/lib.rs index 7c8dc6a..4477ff1 100644 --- a/machine/testing/src/lib.rs +++ b/machine/testing/src/lib.rs @@ -1,3 +1,6 @@ +#![cfg_attr(target_os = "none", no_std)] +#![cfg(not(target_os = "none"))] + use core::result::Result::Ok; use hal_api::{Result, Schedable}; diff --git a/xtasks/Cargo.toml b/xtasks/Cargo.toml index bb63b52..96e7895 100644 --- a/xtasks/Cargo.toml +++ b/xtasks/Cargo.toml @@ -3,7 +3,7 @@ name = "xtask" version = "0.1.0" edition = "2024" -[dependencies] +[target.'cfg(not(target_os = "none"))'.dependencies] logging = { path = "logging" } clap = "4.5.53" walkdir = "2.5.0" diff --git a/xtasks/crates/config/Cargo.toml b/xtasks/crates/config/Cargo.toml index 5428bb0..78dc4c2 100644 --- a/xtasks/crates/config/Cargo.toml +++ b/xtasks/crates/config/Cargo.toml @@ -3,7 +3,7 @@ name = "config" version = "0.1.0" edition = "2024" -[dependencies] +[target.'cfg(not(target_os = "none"))'.dependencies] logging = { workspace = true } ratatui = "0.29.0" crossterm = "0.27" diff --git a/xtasks/crates/config/src/lib.rs b/xtasks/crates/config/src/lib.rs index 65f431e..a5f0704 100644 --- a/xtasks/crates/config/src/lib.rs +++ b/xtasks/crates/config/src/lib.rs @@ -1,3 +1,6 @@ +#![cfg_attr(target_os = "none", no_std)] +#![cfg(not(target_os = "none"))] + use std::{path::Path, process::exit}; use crate::{ diff --git a/xtasks/crates/config/src/main.rs b/xtasks/crates/config/src/main.rs index 37ebaf6..00f1244 100644 --- a/xtasks/crates/config/src/main.rs +++ b/xtasks/crates/config/src/main.rs @@ -1,3 +1,7 @@ +#![cfg_attr(target_os = "none", no_std)] +#![cfg_attr(target_os = "none", no_main)] +#![cfg(not(target_os = "none"))] + use std::path::{Path, PathBuf}; use config::error::Error; diff --git a/xtasks/crates/dtgen/Cargo.toml b/xtasks/crates/dtgen/Cargo.toml index 2cedf04..d6cc676 100644 --- a/xtasks/crates/dtgen/Cargo.toml +++ b/xtasks/crates/dtgen/Cargo.toml @@ -11,7 +11,7 @@ path = "src/lib.rs" name = "dtgen" path = "src/main.rs" -[dependencies] +[target.'cfg(not(target_os = "none"))'.dependencies] fdt = "0.1.5" clap = { version = "4", features = ["derive"] } quote = "1" diff --git a/xtasks/crates/dtgen/src/lib.rs b/xtasks/crates/dtgen/src/lib.rs index 36d6917..ec069a5 100644 --- a/xtasks/crates/dtgen/src/lib.rs +++ b/xtasks/crates/dtgen/src/lib.rs @@ -1,3 +1,6 @@ +#![cfg_attr(target_os = "none", no_std)] +#![cfg(not(target_os = "none"))] + mod codegen; mod ir; mod parser; diff --git a/xtasks/crates/dtgen/src/main.rs b/xtasks/crates/dtgen/src/main.rs index 370bf88..af2bd1f 100644 --- a/xtasks/crates/dtgen/src/main.rs +++ b/xtasks/crates/dtgen/src/main.rs @@ -1,3 +1,7 @@ +#![cfg_attr(target_os = "none", no_std)] +#![cfg_attr(target_os = "none", no_main)] +#![cfg(not(target_os = "none"))] + use clap::Parser; use std::path::PathBuf; diff --git a/xtasks/crates/injector/Cargo.toml b/xtasks/crates/injector/Cargo.toml index 9364c7e..0faa7cf 100644 --- a/xtasks/crates/injector/Cargo.toml +++ b/xtasks/crates/injector/Cargo.toml @@ -3,7 +3,7 @@ name = "injector" version = "0.1.0" edition = "2024" -[dependencies] +[target.'cfg(not(target_os = "none"))'.dependencies] logging = { workspace = true } clap = { version = "4.5", features = ["derive"] } object = "0.36" diff --git a/xtasks/crates/injector/src/main.rs b/xtasks/crates/injector/src/main.rs index 3fafe36..3171e88 100644 --- a/xtasks/crates/injector/src/main.rs +++ b/xtasks/crates/injector/src/main.rs @@ -1,3 +1,7 @@ +#![cfg_attr(target_os = "none", no_std)] +#![cfg_attr(target_os = "none", no_main)] +#![cfg(not(target_os = "none"))] + use cargo_metadata::MetadataCommand; use clap::Parser; use object::{Object, ObjectSection}; diff --git a/xtasks/crates/pack/Cargo.toml b/xtasks/crates/pack/Cargo.toml index e898333..a9d72eb 100644 --- a/xtasks/crates/pack/Cargo.toml +++ b/xtasks/crates/pack/Cargo.toml @@ -3,7 +3,7 @@ name = "pack" version = "0.1.0" edition = "2024" -[dependencies] +[target.'cfg(not(target_os = "none"))'.dependencies] anyhow = "1.0.100" logging = { workspace = true } clap = { version = "4.5.47", features = ["derive"] } diff --git a/xtasks/crates/pack/src/main.rs b/xtasks/crates/pack/src/main.rs index f84c7a7..6eba097 100644 --- a/xtasks/crates/pack/src/main.rs +++ b/xtasks/crates/pack/src/main.rs @@ -1,3 +1,7 @@ +#![cfg_attr(target_os = "none", no_std)] +#![cfg_attr(target_os = "none", no_main)] +#![cfg(not(target_os = "none"))] + use std::path::PathBuf; use clap::Parser; diff --git a/xtasks/logging/Cargo.toml b/xtasks/logging/Cargo.toml index be24d26..fb270e1 100644 --- a/xtasks/logging/Cargo.toml +++ b/xtasks/logging/Cargo.toml @@ -3,7 +3,7 @@ name = "logging" version = "0.1.0" edition = "2024" -[dependencies] +[target.'cfg(not(target_os = "none"))'.dependencies] env_logger = "0.11.8" log = "0.4.29" once_cell = "1.21.3" diff --git a/xtasks/logging/src/lib.rs b/xtasks/logging/src/lib.rs index 9b64f20..0bec258 100644 --- a/xtasks/logging/src/lib.rs +++ b/xtasks/logging/src/lib.rs @@ -1,3 +1,7 @@ +#![cfg_attr(target_os = "none", no_std)] +#![cfg_attr(target_os = "none", no_main)] +#![cfg(not(target_os = "none"))] + use std::io::Write; use std::sync::OnceLock; diff --git a/xtasks/src/main.rs b/xtasks/src/main.rs index ad7cb3a..a2a4820 100644 --- a/xtasks/src/main.rs +++ b/xtasks/src/main.rs @@ -1,3 +1,7 @@ +#![cfg_attr(target_os = "none", no_std)] +#![cfg_attr(target_os = "none", no_main)] +#![cfg(not(target_os = "none"))] + use std::path::{Path, PathBuf}; use cargo_metadata::{MetadataCommand, TargetKind}; From cd7891184bb1e39e5c23c18700676f8ae688d663 Mon Sep 17 00:00:00 2001 From: xarantolus Date: Fri, 3 Apr 2026 19:32:44 +0000 Subject: [PATCH 32/42] Fix underflows in at, drop --- src/types/array.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/types/array.rs b/src/types/array.rs index 15eae5b..e3e7321 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -449,7 +449,7 @@ impl Vec { /// Returns `Some(&T)` if the index is in-bounds, otherwise `None`. pub fn at(&self, index: usize) -> Option<&T> { // Check if the index is in-bounds. - if index > self.len - 1 { + if index >= self.len { return None; } @@ -597,8 +597,9 @@ impl Drop for Vec { } // Drop all elements in the extra storage. - for elem in &mut (*self.extra)[0..self.len - N] { - // Safety: the elements until self.len - N are initialized. + let extra_init = self.len.saturating_sub(N).min(self.extra.len()); + for elem in &mut (*self.extra)[0..extra_init] { + // Safety: the elements until extra_init are initialized. unsafe { elem.assume_init_drop(); } @@ -650,3 +651,19 @@ impl GetMut for Vec { self.at3_mut(*index1.borrow(), *index2.borrow(), *index3.borrow()) } } + + +#[cfg(test)] +mod tests { + use super::Vec; + + #[test] + fn no_length_underflow() { + let vec = Vec::::new(); + assert!(vec.len() == 0); + + // If the length check is wrong, at(0) would panic due to an underflow + assert_eq!(vec.at(0), None); + } +} + From 6615309fa7703e1317bd27241828bc887e55574f Mon Sep 17 00:00:00 2001 From: xarantolus Date: Fri, 3 Apr 2026 19:52:56 +0000 Subject: [PATCH 33/42] Fix underflow, now gives OOM --- src/types/array.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/types/array.rs b/src/types/array.rs index e3e7321..5b36d5e 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -262,7 +262,7 @@ impl Vec { } // If we don't have enough space, we need to grow the extra storage. - let grow = additional - N + len_extra; + let grow = self.len + additional - N; let mut new_extra = Box::new_slice_uninit(grow)?; // Check that the new extra storage has the requested length. @@ -665,5 +665,19 @@ mod tests { // If the length check is wrong, at(0) would panic due to an underflow assert_eq!(vec.at(0), None); } + + #[test] + fn reserve_underflow() { + // N=8, fill 7 elements so len=7 and extra.len()=0. + // reserve(2): 7+2=9 > 8+0=8, needs grow. grow = 2 - 8 + 0 = underflow. + let mut vec = Vec::::new(); + for i in 0..7usize { + vec.push(i).unwrap(); + } + assert_eq!(vec.len(), 7); + + // FIXME: Gives OOM error + vec.reserve(2).unwrap(); + } } From 2e81a0d811b298e85c39834075102e665cc72c6d Mon Sep 17 00:00:00 2001 From: xarantolus Date: Fri, 3 Apr 2026 19:54:50 +0000 Subject: [PATCH 34/42] Drop underflow test --- src/types/array.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/types/array.rs b/src/types/array.rs index 5b36d5e..1f9b689 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -679,5 +679,13 @@ mod tests { // FIXME: Gives OOM error vec.reserve(2).unwrap(); } + + #[test] + fn drop_underflow() { + let mut vec = Vec::::new(); + for i in 0..7usize { vec.push(i).unwrap(); } + // Drop used to panic here + drop(vec); + } } From 53eba6f1a6cf15771c1ac928abc15a4edd1aba44 Mon Sep 17 00:00:00 2001 From: xarantolus Date: Fri, 3 Apr 2026 20:14:46 +0000 Subject: [PATCH 35/42] Kani proofs --- src/types/array.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/src/types/array.rs b/src/types/array.rs index 1f9b689..1f08e56 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -655,7 +655,7 @@ impl GetMut for Vec { #[cfg(test)] mod tests { - use super::Vec; + use super::{Vec, IndexMap}; #[test] fn no_length_underflow() { @@ -687,5 +687,71 @@ mod tests { // Drop used to panic here drop(vec); } + + #[test] + fn index_map_iter_cycle_max() { + let m: IndexMap = IndexMap::new(); + let _ = m.iter_from_cycle(Some(&usize::MAX)).next(); + } } + + +#[cfg(kani)] +mod verification { + use super::IndexMap; + use crate::types::traits::Get; + + /// Verify that insert followed by get returns the inserted value. + #[kani::proof] + fn verify_insert_get_roundtrip() { + let mut m: IndexMap = IndexMap::new(); + let idx: usize = kani::any(); + kani::assume(idx < 8); + let val: u32 = kani::any(); + + m.insert(&idx, val).unwrap(); + assert_eq!(m.get(&idx), Some(&val)); + } + + /// Verify that removing an inserted value leaves the slot empty. + #[kani::proof] + fn verify_remove_clears_slot() { + let mut m: IndexMap = IndexMap::new(); + let idx: usize = kani::any(); + kani::assume(idx < 8); + let val: u32 = kani::any(); + + m.insert(&idx, val).unwrap(); + let removed = m.remove(&idx); + assert_eq!(removed, Some(val)); + assert_eq!(m.get(&idx), None); + } + + /// Verify iter_from_cycle does not overflow when idx is a valid in-bounds index. + #[kani::proof] + #[kani::unwind(16)] + fn verify_iter_from_cycle_no_overflow_valid_idx() { + let m: IndexMap = IndexMap::new(); + let idx: usize = kani::any(); + kani::assume(idx < 8); // only valid indices + + // iter_from_cycle computes to_index(Some(idx)) + 1 = idx + 1 (≤ 8, no overflow) + let _ = m.iter_from_cycle(Some(&idx)).next(); + } + + /// Verify next() does not overflow when idx is a valid in-bounds index. + #[kani::proof] + #[kani::unwind(16)] + fn verify_next_no_overflow_valid_idx() { + let mut m: IndexMap = IndexMap::new(); + // Fill all slots so next() iterates the maximum i times. + for i in 0usize..8 { + m.insert(&i, i as u32).unwrap(); + } + let idx: usize = kani::any(); + kani::assume(idx < 8); + let result = m.next(Some(&idx)); + assert!(result.is_some()); // map is full, must find something + } +} From fce9a244d01c19322a1671de3c40d6a8d86a1084 Mon Sep 17 00:00:00 2001 From: thomasw04 <35061939+thomasw04@users.noreply.github.com> Date: Fri, 3 Apr 2026 21:15:26 +0000 Subject: [PATCH 36/42] wip --- .cargo/config.toml | 1 + Cargo.lock | 3 + boards/nucleo_l4r5zi.dts | 7 +++ build.rs | 30 +++++++--- machine/arm/stm32l4xx/CMakeLists.txt | 1 - machine/arm/stm32l4xx/link.ld | 7 +-- machine/arm/stm32l4xx/r5zi/link.ld | 7 --- xtasks/crates/dtgen/Cargo.toml | 3 + xtasks/crates/dtgen/src/ldgen.rs | 84 ++++++++++++++++++++++++++++ xtasks/crates/dtgen/src/lib.rs | 20 +++++-- xtasks/crates/dtgen/src/main.rs | 18 +++++- 11 files changed, 152 insertions(+), 29 deletions(-) delete mode 100644 machine/arm/stm32l4xx/r5zi/link.ld create mode 100644 xtasks/crates/dtgen/src/ldgen.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index dfc05b1..eb378db 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -8,5 +8,6 @@ xtask = "--config xtasks/.cargo/config.toml run -p xtask --release --" [target.'cfg(target_os = "none")'] rustflags = [ "-C", "link-arg=--entry=main", + "-C", "link-arg=-Tprelude.ld", "-C", "link-arg=-Tlink.ld", ] \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 8ebf8eb..e0af60e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -583,6 +583,9 @@ version = "0.1.0" dependencies = [ "clap", "fdt", + "indoc", + "log", + "logging", "prettyplease", "proc-macro2", "quote", diff --git a/boards/nucleo_l4r5zi.dts b/boards/nucleo_l4r5zi.dts index d99a7d1..ce19d57 100644 --- a/boards/nucleo_l4r5zi.dts +++ b/boards/nucleo_l4r5zi.dts @@ -12,6 +12,8 @@ #include #include +/delete-node/ &sram1; + / { model = "STMicroelectronics STM32L4R5ZI-NUCLEO board"; compatible = "st,stm32l4r5zi-nucleo"; @@ -27,6 +29,11 @@ osiris,entropy = &rng; }; + sram1: memory@20030000 { + device_type = "memory"; + reg = <0x20030000 0x00010000>; + }; + leds: leds { compatible = "gpio-leds"; diff --git a/build.rs b/build.rs index 42b6eb3..27c4cfa 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,4 @@ +use core::panic; use std::process::Command; use std::{collections::HashMap, fs, fs::File, path::Path, path::PathBuf}; @@ -22,7 +23,13 @@ fn main() { panic!("Failed to generate syscall match statement."); } - generate_device_tree().expect("Failed to generate device tree."); + let dt = build_device_tree(Path::new(&out_dir)).unwrap_or_else(|e| { + panic!("Failed to build device tree from DTS files: {e}"); + }); + + if let Err(e) = generate_device_tree(&dt, Path::new(&out_dir)) { + panic!("Failed to generate device tree scripts: {e}"); + } cfg_aliases! { freestanding: { all(not(test), not(doctest), not(doc), not(kani), any(target_os = "none", target_os = "unknown")) }, @@ -31,15 +38,25 @@ fn main() { // Device Tree Codegen ---------------------------------------------------------------------------- -fn generate_device_tree() -> Result<(), Box> { +fn generate_device_tree(dt: &dtgen::ir::DeviceTree, out: &Path) -> Result<(), Box> { + let rust_content = dtgen::generate_rust(dt); + std::fs::write(out.join("device_tree.rs"), rust_content)?; + + let ld_content = dtgen::generate_ld(dt).map_err(|e| format!("linker script generation failed: {e}"))?; + std::fs::write(out.join("prelude.ld"), ld_content)?; + println!("cargo::rustc-link-search=native={}", out.display()); + Ok(()) +} + +fn build_device_tree(out: &Path) -> Result> { let dts = std::env::var("OSIRIS_TUNING_DTS").unwrap_or_else(|_| "nucleo_l4r5zi.dts".to_string()); let dts_path = std::path::Path::new("boards").join(dts); println!("cargo::rerun-if-changed={}", dts_path.display()); // dependencies SoC/HAL/pins - let zephyr = Path::new(&std::env::var("OUT_DIR").unwrap()).join("zephyr"); - let hal_stm32 = Path::new(&std::env::var("OUT_DIR").unwrap()).join("hal_stm32"); + let zephyr = Path::new(out).join("zephyr"); + let hal_stm32 = Path::new(out).join("hal_stm32"); // clean state if zephyr.exists() { @@ -70,7 +87,7 @@ fn generate_device_tree() -> Result<(), Box> { Some(&hal_rev), )?; - let out = Path::new(&std::env::var("OUT_DIR").unwrap()).join("device_tree.rs"); + //let out = Path::new(&std::env::var("OUT_DIR").unwrap()).join("device_tree.rs"); let include_paths = [ zephyr.join("include"), zephyr.join("dts/arm/st"), @@ -90,8 +107,7 @@ fn generate_device_tree() -> Result<(), Box> { } } - dtgen::run(&dts_path, &include_refs, &out)?; - Ok(()) + Ok(dtgen::parse_dts(&dts_path, &include_refs)?) } fn get_hal_revision(zephyr_path: &Path) -> Result> { diff --git a/machine/arm/stm32l4xx/CMakeLists.txt b/machine/arm/stm32l4xx/CMakeLists.txt index d692d85..9410027 100644 --- a/machine/arm/stm32l4xx/CMakeLists.txt +++ b/machine/arm/stm32l4xx/CMakeLists.txt @@ -50,7 +50,6 @@ add_custom_command( OUTPUT ${LINKER_SCRIPT_OUT} COMMAND ${CMAKE_C_COMPILER} -E -P -x c ${CONFIG_DEFINES} - -DMCU_HEADER=\"${OSIRIS_ARM_STM32L4XX_VARIANT}/link.ld\" ${LINKER_SCRIPT_IN} -o ${LINKER_SCRIPT_OUT} DEPENDS ${LINKER_SCRIPT_IN} ${DEPS_FILE} diff --git a/machine/arm/stm32l4xx/link.ld b/machine/arm/stm32l4xx/link.ld index b3e0ee8..ef1eae1 100644 --- a/machine/arm/stm32l4xx/link.ld +++ b/machine/arm/stm32l4xx/link.ld @@ -1,11 +1,6 @@ -#ifndef MCU_HEADER -#error "MCU_HEADER is not defined." -#endif - -#include MCU_HEADER - __ram_start = ORIGIN(RAM); __ram_end = ORIGIN(RAM) + LENGTH(RAM); +__stack_size = 0x8000; #if OSIRIS_DEBUG_RUNTIMESYMBOLS /* at least 250kb for the symbol table */ diff --git a/machine/arm/stm32l4xx/r5zi/link.ld b/machine/arm/stm32l4xx/r5zi/link.ld deleted file mode 100644 index 06a4b1f..0000000 --- a/machine/arm/stm32l4xx/r5zi/link.ld +++ /dev/null @@ -1,7 +0,0 @@ -MEMORY -{ - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2M - RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 640K -} - -__stack_size = 0x8000; \ No newline at end of file diff --git a/xtasks/crates/dtgen/Cargo.toml b/xtasks/crates/dtgen/Cargo.toml index d6cc676..432387d 100644 --- a/xtasks/crates/dtgen/Cargo.toml +++ b/xtasks/crates/dtgen/Cargo.toml @@ -13,8 +13,11 @@ path = "src/main.rs" [target.'cfg(not(target_os = "none"))'.dependencies] fdt = "0.1.5" +logging = { workspace = true } +log = "0.4.27" clap = { version = "4", features = ["derive"] } quote = "1" proc-macro2 = "1" prettyplease = "0.2" syn = { version = "2", features = ["full"] } +indoc = "2.0.7" \ No newline at end of file diff --git a/xtasks/crates/dtgen/src/ldgen.rs b/xtasks/crates/dtgen/src/ldgen.rs new file mode 100644 index 0000000..e0b5041 --- /dev/null +++ b/xtasks/crates/dtgen/src/ldgen.rs @@ -0,0 +1,84 @@ +use crate::ir::DeviceTree; + +fn format_region(name: &str, base: u64, size: u64) -> String { + format!( + " {} : ORIGIN = 0x{:08x}, LENGTH = 0x{:08x}", + name, base, size + ) +} + +fn format_memory_section(regions: &[(&str, u64, u64)]) -> String { + let regions = regions + .iter() + .map(|&(name, base, size)| format_region(name, base, size)) + .collect::>() + .join("\n"); + + indoc::formatdoc! {" + /* This file is @generated by dtgen. Do not edit. */ + MEMORY {{ + {regions} + }} + ", regions = regions} +} + +fn format_irq_provides(num: u32) -> String { + format!("PROVIDE(__irq_{}_handler = default_handler);", num) +} + +fn coalesce_regions<'a>(name: &'a str, regions: Vec<(&'a str, u64, u64)>) -> Result, String> { + regions + .clone() + .into_iter() + .try_fold(None, |acc, (_, base, size)| { + if let Some((_, acc_base, acc_size)) = acc { + if base > acc_base + acc_size || acc_base > base + size { + return Err(format!("Regions are not contiguous. {regions:?}")); + } + + let end = (base + size).max(acc_base + acc_size); + let base = base.min(acc_base); + let size = end - base; + Ok(Some((name, base, size))) + } else { + Ok(Some((name, base, size))) + } + }) +} + +pub fn generate_ld(dt: &DeviceTree) -> Result { + // Generates a linker script prelude that defines the memory regions for the device tree. + + let mut ram: Vec<(&str, u64, u64)> = dt + .nodes + .iter() + .filter(|n| n.name.starts_with("memory@") || n.name == "memory") + .filter_map(|n| { + let (base, size) = n.reg?; + Some((n.name.as_str(), base, size)) + }) + .collect(); + ram.sort_by_key(|&(_, base, _)| base); + + let mut flash: Vec<(&str, u64, u64)> = dt + .nodes + .iter() + .filter(|n| n.name.starts_with("flash@") || n.name == "flash") + .filter_map(|n| { + let (base, size) = n.reg?; + Some((n.name.as_str(), base, size)) + }) + .collect(); + flash.sort_by_key(|&(_, base, _)| base); + + let flash = coalesce_regions("FLASH", flash)?; + let ram = coalesce_regions("RAM", ram)?; + + let regions = flash.into_iter().chain(ram).collect::>(); + + if regions.is_empty() { + return Err("No memory regions found in device tree".to_string()); + } + + Ok(format_memory_section(®ions)) +} diff --git a/xtasks/crates/dtgen/src/lib.rs b/xtasks/crates/dtgen/src/lib.rs index ec069a5..aa3d761 100644 --- a/xtasks/crates/dtgen/src/lib.rs +++ b/xtasks/crates/dtgen/src/lib.rs @@ -2,15 +2,23 @@ #![cfg(not(target_os = "none"))] mod codegen; -mod ir; +pub mod ir; mod parser; +mod ldgen; use std::path::Path; -pub fn run(dts_path: &Path, include_dirs: &[&Path], out_path: &Path) -> Result<(), String> { +use crate::ir::DeviceTree; + +pub fn parse_dts(dts_path: &Path, include_dirs: &[&Path]) -> Result { let dtb = parser::dts_to_dtb(dts_path, include_dirs)?; - let dt = parser::dtb_to_devicetree(&dtb)?; - let src = codegen::generate_rust(&dt); - std::fs::write(out_path, src) - .map_err(|e| format!("dtgen: failed to write {}: {e}", out_path.display())) + parser::dtb_to_devicetree(&dtb) +} + +pub fn generate_rust(dt: &DeviceTree) -> String { + codegen::generate_rust(dt) +} + +pub fn generate_ld(dt: &DeviceTree) -> Result { + ldgen::generate_ld(dt) } diff --git a/xtasks/crates/dtgen/src/main.rs b/xtasks/crates/dtgen/src/main.rs index af2bd1f..e2a917e 100644 --- a/xtasks/crates/dtgen/src/main.rs +++ b/xtasks/crates/dtgen/src/main.rs @@ -25,11 +25,25 @@ struct Args { } fn main() { + logging::init(); let args = Args::parse(); let refs: Vec<&std::path::Path> = args.include_dirs.iter().map(|p| p.as_path()).collect(); - dtgen::run(&args.input, &refs, &args.output).unwrap_or_else(|e| { - eprintln!("dtgen error: {e}"); + let dt = dtgen::parse_dts(&args.input, &refs).unwrap_or_else(|e| { + log::error!("dtgen error: Failed to parse device tree: {e}"); + std::process::exit(1); + }); + + let output = args.output.as_path(); + std::fs::create_dir_all(output.parent().unwrap()).unwrap_or_else(|e| { + log::error!("dtgen error: Failed to create output directory: {e}"); + std::process::exit(1); + }); + + let content = dtgen::generate_rust(&dt); + + std::fs::write(&args.output, content).unwrap_or_else(|e| { + log::error!("dtgen error: Failed to write output file: {e}"); std::process::exit(1); }); } From 8a475769f39434c44a971326e4d847ecf0ce3656 Mon Sep 17 00:00:00 2001 From: xarantolus Date: Sat, 4 Apr 2026 08:27:58 +0000 Subject: [PATCH 37/42] Bitset: Add underflow verification/test --- src/mem/pfa/bitset.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/mem/pfa/bitset.rs b/src/mem/pfa/bitset.rs index 7dc5692..2170ae7 100644 --- a/src/mem/pfa/bitset.rs +++ b/src/mem/pfa/bitset.rs @@ -180,6 +180,17 @@ impl super::Allocator for Allocator { mod tests { use super::*; + #[test] + fn last_bit_underflow() { + // Only the last page in word 0 is free + let mut allocator = Allocator::<1>::new(PhysAddr::new(0)).unwrap(); + allocator.l1[0] = 1; + + let result = super::super::Allocator::alloc(&mut allocator, 1); + + assert!(result.is_some()); + } + #[test] fn test_random_pattern() { const ITARATIONS: usize = 1000; @@ -224,4 +235,26 @@ mod tests { } } } -} \ No newline at end of file +} + +#[cfg(kani)] +mod verification { + use super::*; + use hal::mem::PhysAddr; + + #[kani::proof] + #[kani::unwind(70)] + fn verify_alloc_no_rem_underflow_single_word() { + let mut allocator = Allocator::<1>::new(PhysAddr::new(0)).unwrap(); + + let l1_0: usize = kani::any(); + + allocator.l1[0] = l1_0; + + let page_count: usize = kani::any(); + + kani::assume(page_count >= 1 && page_count <= 64); + + let _ = super::super::Allocator::alloc(&mut allocator, page_count); + } +} From 573ae247469aff2b5f4e8b605a49293b2987e6de Mon Sep 17 00:00:00 2001 From: xarantolus Date: Sat, 4 Apr 2026 08:49:57 +0000 Subject: [PATCH 38/42] Fix bestfit free corruption --- src/mem/alloc/bestfit.rs | 81 +++++++++++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 14 deletions(-) diff --git a/src/mem/alloc/bestfit.rs b/src/mem/alloc/bestfit.rs index 4732a85..9948e58 100644 --- a/src/mem/alloc/bestfit.rs +++ b/src/mem/alloc/bestfit.rs @@ -49,7 +49,7 @@ impl BestFitAllocator { // Check if the pointer is 128bit aligned. if !ptr.is_multiple_of(align_of::()) { - return Err(kerr!(InvalidArgument)); + return Err(kerr!(InvalidArgument)); } if range.end.diff(range.start) < Self::MIN_RANGE_SIZE { @@ -169,7 +169,11 @@ impl BestFitAllocator { } unsafe fn contains(meta: &BestFitMeta, target: PhysAddr, size: usize) -> bool { - let begin = unsafe { Self::user_ptr(NonNull::new_unchecked(meta as *const BestFitMeta as *mut u8)) }; + let begin = unsafe { + Self::user_ptr(NonNull::new_unchecked( + meta as *const BestFitMeta as *mut u8, + )) + }; debug_assert!(size > 0); if target >= begin.into() { @@ -194,7 +198,12 @@ impl super::Allocator for BestFitAllocator { /// `align` - The alignment of the block. /// /// Returns the user pointer to the block if successful, otherwise an error. - fn malloc(&mut self, size: usize, align: usize, request: Option) -> Result> { + fn malloc( + &mut self, + size: usize, + align: usize, + request: Option, + ) -> Result> { // Check if the alignment is valid. if align == 0 || align > align_of::() { return Err(kerr!(InvalidAlign)); @@ -204,7 +213,7 @@ impl super::Allocator for BestFitAllocator { if !request.is_multiple_of(align) { return Err(kerr!(InvalidAlign)); } - } + } // Check if the size is valid. if size == 0 { @@ -309,7 +318,9 @@ impl super::Allocator for BestFitAllocator { } if let Some(request) = request { - debug_assert!(unsafe { Self::contains(block.cast::().as_ref(), request, size) }); + debug_assert!(unsafe { + Self::contains(block.cast::().as_ref(), request, size) + }); } // Return the user pointer. @@ -328,10 +339,13 @@ impl super::Allocator for BestFitAllocator { meta.next = self.head; // Check if the size of the block is correct. - bug_on!(meta.size != super::super::align_up(size), "Invalid size in free()"); + bug_on!( + meta.size != super::super::align_up(size), + "Invalid size in free()" + ); - // Set the size of the block. - meta.size = size; + // Set the size of the block (must stay aligned, matching what malloc stored). + meta.size = super::super::align_up(size); // Set the block as the new head. self.head = Some(block); @@ -343,9 +357,10 @@ impl super::Allocator for BestFitAllocator { #[cfg(test)] mod tests { use crate::error::Kind; + use crate::mem::align_up; - use super::*; use super::super::*; + use super::*; fn verify_block(user_ptr: NonNull, size: usize, next: Option>) { let control_ptr = unsafe { BestFitAllocator::control_ptr(user_ptr) }; @@ -411,7 +426,11 @@ mod tests { let ptr = allocator.malloc::(128, 1, Some(request)).unwrap(); // Check that the returned pointer contains the requested address. - let meta = unsafe { BestFitAllocator::control_ptr(ptr).cast::().as_ref() }; + let meta = unsafe { + BestFitAllocator::control_ptr(ptr) + .cast::() + .as_ref() + }; assert!(unsafe { BestFitAllocator::contains(meta, request, 128) }); } @@ -626,6 +645,40 @@ mod tests { verify_ptrs_not_overlaping(ptrs.as_slice()); } + #[test] + fn free_corrupts_metadata() { + let mut allocator = BestFitAllocator::new(); + // Use a size NOT 16-byte aligned so align_up(size) > size. + const SIZE: usize = 17; + const ALIGNED: usize = 32; // align_up(17) on 64-bit: (17+15)&!15 = 32 + assert!(align_up(SIZE) == ALIGNED); + + // Allocate just enough space for one block. + let range = alloc_range(ALIGNED + size_of::() + BestFitAllocator::align_up()); + unsafe { + allocator.add_range(&range).unwrap(); + } + + // First alloc: meta.size = align_up(17) = 32. + let ptr1: core::ptr::NonNull = allocator.malloc(SIZE, 1, None).unwrap(); + + // First free: meta.size set to 17 (BUG: should stay 32). + unsafe { + allocator.free(ptr1, SIZE); + } + + // Second alloc: select_block(32) fails (meta.size=17 < 32), fallback select_block(17) + // succeeds and returns the block with meta.size still = 17. + let ptr2: core::ptr::NonNull = allocator + .malloc(SIZE, 1, None) + .expect("second malloc should succeed via fallback path"); + + // Second free: bug_on!(meta.size(17) != align_up(17)(32)) → panics. + unsafe { + allocator.free(ptr2, SIZE); + } + } + #[test] fn multi_range_oom() { // This function allocates multiple ranges and then frees one of them randomly. And only then there is no oom. @@ -663,8 +716,8 @@ mod tests { // VERIFICATION ------------------------------------------------------------------------------------------------------- #[cfg(kani)] mod verification { + use super::super::*; use super::*; - use core::{alloc::Layout, ptr}; fn verify_block(user_ptr: NonNull, size: usize, next: Option>) { let control_ptr = unsafe { BestFitAllocator::control_ptr(user_ptr) }; @@ -674,14 +727,14 @@ mod verification { assert_eq!(meta.next, next); } - fn alloc_range(length: usize) -> Option> { + fn alloc_range(length: usize) -> Option> { let alloc_range = std::alloc::Layout::from_size_align(length, align_of::()).unwrap(); let ptr = unsafe { std::alloc::alloc(alloc_range) }; if ptr.is_null() || ((ptr as usize) >= isize::MAX as usize - length) { None } else { - Some(ptr as usize..ptr as usize + length) + Some(PhysAddr::new(ptr as usize)..PhysAddr::new(ptr as usize + length)) } } @@ -699,7 +752,7 @@ mod verification { if let Some(range) = alloc_range(larger_size) { unsafe { - assert_eq!(allocator.add_range(range), Ok(())); + assert!(matches!(allocator.add_range(&range), Ok(()))); } let ptr = allocator.malloc(size, 1, None).unwrap(); From a0305abbbcdd145261be944d48266e1fc6e03ebe Mon Sep 17 00:00:00 2001 From: xarantolus Date: Sat, 4 Apr 2026 14:16:36 +0000 Subject: [PATCH 39/42] Add budget_left panic test --- src/sched/thread.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/sched/thread.rs b/src/sched/thread.rs index 80d01f1..b5ad71f 100644 --- a/src/sched/thread.rs +++ b/src/sched/thread.rs @@ -385,3 +385,26 @@ impl Project for Thread { self.waiter.as_mut() } } + +#[cfg(test)] +mod tests { + use super::RtServer; + + fn make_server(budget: u32, period: u32, deadline: u64) -> RtServer { + let tid = super::Id::new(1, super::task::KERNEL_TASK); + let uid = tid.get_uid(1); + RtServer::new(budget, period, deadline, uid) + } + + #[test] + fn replenish_budget_overflow() { + // 2 * budget = 4_294_967_296 > u32::MAX → overflows. + // In release: wraps to 0, which is less than budget. + let budget: u32 = u32::MAX / 2 + 1; + + let mut server = make_server(budget, 1, 0); + + server.replenish(); + server.budget_left(); + } +} From af3200f26f08d16242022ede1938f212712edd6b Mon Sep 17 00:00:00 2001 From: xarantolus Date: Sat, 4 Apr 2026 14:28:22 +0000 Subject: [PATCH 40/42] Heap: verification and tests --- src/types/heap.rs | 118 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/src/types/heap.rs b/src/types/heap.rs index a25b1f8..62de196 100644 --- a/src/types/heap.rs +++ b/src/types/heap.rs @@ -106,3 +106,121 @@ impl BinaryHeap { self.vec.len() } } + + +#[cfg(kani)] +mod verification { + use super::BinaryHeap; + + /// Verify that pushing a single element and popping it returns the same element. + #[kani::proof] + #[kani::unwind(5)] + fn verify_push_pop_roundtrip() { + let mut heap = BinaryHeap::::new(); + let v: u32 = kani::any(); + heap.push(v).unwrap(); + let popped = heap.pop(); + assert_eq!(popped, Some(v)); + assert!(heap.is_empty()); + } + + /// Verify that pushing two elements and popping gives the smaller one first (min-heap). + #[kani::proof] + #[kani::unwind(5)] + fn verify_min_heap_two_elements() { + let mut heap = BinaryHeap::::new(); + let a: u32 = kani::any(); + let b: u32 = kani::any(); + heap.push(a).unwrap(); + heap.push(b).unwrap(); + let first = heap.pop().unwrap(); + let second = heap.pop().unwrap(); + // Min-heap: first <= second, and {first, second} == {a, b} + assert!(first <= second); + assert!((first == a && second == b) || (first == b && second == a)); + } + + /// Verify that pushing three elements pops them in non-decreasing order. + #[kani::proof] + #[kani::unwind(6)] + fn verify_min_heap_three_elements_sorted() { + let mut heap = BinaryHeap::::new(); + let a: u32 = kani::any(); + let b: u32 = kani::any(); + let c: u32 = kani::any(); + heap.push(a).unwrap(); + heap.push(b).unwrap(); + heap.push(c).unwrap(); + let x = heap.pop().unwrap(); + let y = heap.pop().unwrap(); + let z = heap.pop().unwrap(); + // Must come out in non-decreasing order. + assert!(x <= y); + assert!(y <= z); + } + + /// Verify that peek() always returns the minimum element after arbitrary pushes. + #[kani::proof] + #[kani::unwind(6)] + fn verify_peek_is_minimum() { + let mut heap = BinaryHeap::::new(); + let a: u32 = kani::any(); + let b: u32 = kani::any(); + let c: u32 = kani::any(); + heap.push(a).unwrap(); + heap.push(b).unwrap(); + heap.push(c).unwrap(); + let peeked = *heap.peek().unwrap(); + // peeked must be <= all elements + assert!(peeked <= a); + assert!(peeked <= b); + assert!(peeked <= c); + } +} + + +#[cfg(test)] +mod tests { + use super::BinaryHeap; + + #[test] + fn test_heap_sorted_order() { + let mut heap = BinaryHeap::::new(); + for &v in &[5u32, 2, 8, 1, 9, 3] { + heap.push(v).unwrap(); + } + let mut prev = 0u32; + while let Some(v) = heap.pop() { + assert!(v >= prev, "heap pop out of order: {} after {}", v, prev); + prev = v; + } + } + + #[test] + fn test_heap_single_element() { + let mut heap = BinaryHeap::::new(); + heap.push(42).unwrap(); + assert_eq!(heap.peek(), Some(&42)); + assert_eq!(heap.pop(), Some(42)); + assert!(heap.is_empty()); + } + + #[test] + fn test_heap_empty_peek_pop() { + let mut heap = BinaryHeap::::new(); + assert!(heap.peek().is_none()); + assert!(heap.pop().is_none()); + } + + #[test] + fn test_heap_duplicate_values() { + let mut heap = BinaryHeap::::new(); + heap.push(3).unwrap(); + heap.push(3).unwrap(); + heap.push(1).unwrap(); + assert_eq!(heap.pop(), Some(1)); + assert_eq!(heap.pop(), Some(3)); + assert_eq!(heap.pop(), Some(3)); + } +} + From 5d699f40f0a70843bc2f528460c0ff3654cf5a7d Mon Sep 17 00:00:00 2001 From: xarantolus Date: Sat, 4 Apr 2026 14:48:52 +0000 Subject: [PATCH 41/42] RBtree tests, verification --- src/types/list.rs | 249 +++++++++++++++++++++- src/types/rbtree.rs | 509 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 743 insertions(+), 15 deletions(-) diff --git a/src/types/list.rs b/src/types/list.rs index be84923..f8f3f1b 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -97,7 +97,7 @@ impl List { } /// Pushes `id` to the back of the list. If `id` is already in the list, it is moved to the back. - /// + /// /// Errors if `id` does not exist in `storage` or if the node corresponding to `id` is linked but not in the list. pub fn push_back + GetMut>(&mut self, id: T, storage: &mut S) -> Result<()> where @@ -360,4 +360,249 @@ mod tests { assert_eq!(list.pop_front(&mut s).unwrap(), None); assert!(list.is_empty()); } -} \ No newline at end of file +} + +#[cfg(kani)] +mod verification { + use core::borrow::Borrow; + + use super::{Linkable, Links, List}; + use crate::types::{array::IndexMap, traits::{Get, ToIndex}}; + + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + struct Id(usize); + + impl ToIndex for Id { + fn to_index>(idx: Option) -> usize { + idx.as_ref().map_or(0, |k| k.borrow().0) + } + } + + #[derive(Clone, Copy)] + struct Tag; + + struct Node { + links: Links, + } + + impl Node { + fn new() -> Self { + Self { links: Links::new() } + } + } + + impl Linkable for Node { + fn links(&self) -> &Links { &self.links } + fn links_mut(&mut self) -> &mut Links { &mut self.links } + } + + fn make_storage() -> IndexMap { + let mut map = IndexMap::new(); + map.insert(&Id(0), Node::new()).unwrap(); + map.insert(&Id(1), Node::new()).unwrap(); + map.insert(&Id(2), Node::new()).unwrap(); + map.insert(&Id(3), Node::new()).unwrap(); + map + } + + /// Verifies the bug! in push_front (old_head not in storage) is unreachable + /// through correct API usage: all IDs we push exist in storage. + #[kani::proof] + fn verify_push_front_bug_unreachable() { + let mut s = make_storage(); + let mut list = List::::new(); + + list.push_front(Id(0), &mut s).unwrap(); + list.push_front(Id(1), &mut s).unwrap(); + list.push_front(Id(2), &mut s).unwrap(); + + assert_eq!(list.len(), 3); + assert_eq!(list.head(), Some(Id(2))); + assert_eq!(list.tail(), Some(Id(0))); + } + + /// Verifies the bug! in push_back (old_tail not in storage) is unreachable + /// through correct API usage. + #[kani::proof] + fn verify_push_back_bug_unreachable() { + let mut s = make_storage(); + let mut list = List::::new(); + + list.push_back(Id(0), &mut s).unwrap(); + list.push_back(Id(1), &mut s).unwrap(); + list.push_back(Id(2), &mut s).unwrap(); + + assert_eq!(list.len(), 3); + assert_eq!(list.head(), Some(Id(0))); + assert_eq!(list.tail(), Some(Id(2))); + } + + /// Verifies the bug! calls in remove (prev/next not in storage) are unreachable + /// when removing the middle element of a 3-item list. + #[kani::proof] + fn verify_remove_middle_bug_unreachable() { + let mut s = make_storage(); + let mut list = List::::new(); + + list.push_back(Id(0), &mut s).unwrap(); + list.push_back(Id(1), &mut s).unwrap(); + list.push_back(Id(2), &mut s).unwrap(); + + list.remove(Id(1), &mut s).unwrap(); + + assert_eq!(list.len(), 2); + assert_eq!(list.head(), Some(Id(0))); + assert_eq!(list.tail(), Some(Id(2))); + assert_eq!(s.get(Id(0)).unwrap().links().next, Some(Id(2))); + assert_eq!(s.get(Id(2)).unwrap().links().prev, Some(Id(0))); + } + + /// Verifies pop_front on empty list returns Ok(None) without panic. + #[kani::proof] + fn verify_pop_empty_no_panic() { + let mut s = make_storage(); + let mut list = List::::new(); + let result = list.pop_front(&mut s); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); + } + + /// Verifies length invariant: push N distinct items, len == N. + /// Uses symbolic ID ordering so kani explores all 3! = 6 permutations. + #[kani::proof] + #[kani::unwind(4)] + fn verify_len_invariant_push_three() { + let mut s = make_storage(); + let mut list = List::::new(); + + let a: usize = kani::any(); + let b: usize = kani::any(); + let c: usize = kani::any(); + kani::assume(a < 4 && b < 4 && c < 4); + kani::assume(a != b && b != c && a != c); + + list.push_back(Id(a), &mut s).unwrap(); + list.push_back(Id(b), &mut s).unwrap(); + list.push_back(Id(c), &mut s).unwrap(); + + assert_eq!(list.len(), 3); + assert_eq!(list.head(), Some(Id(a))); + assert_eq!(list.tail(), Some(Id(c))); + } + + /// Verifies that reinserting an already-present item does not change len. + #[kani::proof] + fn verify_reinsert_preserves_len() { + let mut s = make_storage(); + let mut list = List::::new(); + + list.push_back(Id(0), &mut s).unwrap(); + list.push_back(Id(1), &mut s).unwrap(); + assert_eq!(list.len(), 2); + + list.push_back(Id(1), &mut s).unwrap(); + assert_eq!(list.len(), 2); + } + + /// Verifies full push/pop cycle leaves list empty. + #[kani::proof] + fn verify_push_pop_cycle_empty() { + let mut s = make_storage(); + let mut list = List::::new(); + + list.push_back(Id(0), &mut s).unwrap(); + list.push_back(Id(1), &mut s).unwrap(); + list.push_back(Id(2), &mut s).unwrap(); + + list.pop_front(&mut s).unwrap(); + list.pop_front(&mut s).unwrap(); + list.pop_front(&mut s).unwrap(); + + assert!(list.is_empty()); + assert_eq!(list.len(), 0); + assert!(list.head().is_none()); + assert!(list.tail().is_none()); + } + + // ----------------------------------------------------------------------- + // FIFO ordering and len ≤ N proofs (Task #12) + // ----------------------------------------------------------------------- + + /// Verify FIFO ordering: push_back(a, b, c) then pop_front yields a, b, c in order. + /// Uses symbolic distinct IDs so Kani explores all 24 permutations (4P3 = 24). + #[kani::proof] + #[kani::unwind(8)] + fn verify_fifo_ordering_symbolic() { + let mut s = make_storage(); + let mut list = List::::new(); + + let a: usize = kani::any(); + let b: usize = kani::any(); + let c: usize = kani::any(); + kani::assume(a < 4 && b < 4 && c < 4); + kani::assume(a != b && b != c && a != c); + + list.push_back(Id(a), &mut s).unwrap(); + list.push_back(Id(b), &mut s).unwrap(); + list.push_back(Id(c), &mut s).unwrap(); + + // FIFO: pop order must match push order + assert_eq!(list.pop_front(&mut s).unwrap(), Some(Id(a))); + assert_eq!(list.pop_front(&mut s).unwrap(), Some(Id(b))); + assert_eq!(list.pop_front(&mut s).unwrap(), Some(Id(c))); + assert_eq!(list.pop_front(&mut s).unwrap(), None); + assert!(list.is_empty()); + } + + /// Verify len ≤ N: after any sequence of pushes with N distinct nodes in storage, + /// len never exceeds N. + /// With 4-slot storage, push all 4 distinct IDs → len == 4. + /// Re-inserting an existing ID is an in-place move, not an increase. + #[kani::proof] + #[kani::unwind(6)] + fn verify_len_never_exceeds_capacity() { + let mut s = make_storage(); // 4 slots + let mut list = List::::new(); + + // Fill the list. + list.push_back(Id(0), &mut s).unwrap(); + list.push_back(Id(1), &mut s).unwrap(); + list.push_back(Id(2), &mut s).unwrap(); + list.push_back(Id(3), &mut s).unwrap(); + assert_eq!(list.len(), 4); + assert!(list.len() <= 4); + + // Re-inserting an already-present ID must not increase len. + let x: usize = kani::any(); + kani::assume(x < 4); + list.push_back(Id(x), &mut s).unwrap(); + assert_eq!(list.len(), 4); + assert!(list.len() <= 4); + } + + /// Verify that head() is always the first-inserted element and tail() is always + /// the last, for any two symbolic distinct IDs. + #[kani::proof] + #[kani::unwind(5)] + fn verify_head_tail_invariant() { + let mut s = make_storage(); + let mut list = List::::new(); + + let a: usize = kani::any(); + let b: usize = kani::any(); + kani::assume(a < 4 && b < 4 && a != b); + + list.push_back(Id(a), &mut s).unwrap(); + assert_eq!(list.head(), Some(Id(a))); + assert_eq!(list.tail(), Some(Id(a))); + + list.push_back(Id(b), &mut s).unwrap(); + assert_eq!(list.head(), Some(Id(a))); + assert_eq!(list.tail(), Some(Id(b))); + + // After popping the front, tail stays, new head is b. + list.pop_front(&mut s).unwrap(); + assert_eq!(list.head(), Some(Id(b))); + assert_eq!(list.tail(), Some(Id(b))); + } +} diff --git a/src/types/rbtree.rs b/src/types/rbtree.rs index 3a5b0f0..9870ae8 100644 --- a/src/types/rbtree.rs +++ b/src/types/rbtree.rs @@ -116,7 +116,7 @@ impl RbTree if node.cmp(last) == core::cmp::Ordering::Less { last.links_mut().left = Some(id); } else { - last.links_mut().right = Some(id); + last.links_mut().right = Some(id); } } } @@ -389,7 +389,7 @@ impl RbTree }; let is_black = |node_id: Option, storage: &S| -> bool { !is_red(node_id, storage) }; - + while id != self.root && is_black(id, storage) { let parent_id = parent.unwrap_or_else(|| { bug!("node linked from tree does not have a parent."); @@ -720,22 +720,19 @@ impl RbTree // TESTING ------------------------------------------------------------------------------------------------------------ -#[cfg(test)] -mod tests { - use super::*; - use super::{Get, GetMut}; - use std::borrow::Borrow; - use std::collections::HashSet; +#[cfg(any(test, kani))] +mod test_common { + use super::{Compare, Linkable, Links}; - struct Tree; + pub(super) struct Tree; - struct Node { - key: i32, - links: Links, + pub(super) struct Node { + pub(super) key: i32, + pub(super) links: Links, } impl Node { - fn new(key: i32) -> Self { + pub(super) fn new(key: i32) -> Self { Self { key, links: Links::new(), @@ -758,6 +755,15 @@ mod tests { &mut self.links } } +} + +#[cfg(test)] +mod tests { + use super::*; + use super::{Get, GetMut}; + use super::test_common::{Tree, Node}; + use std::borrow::Borrow; + use std::collections::HashSet; struct NodeStore { nodes: Vec, @@ -1148,3 +1154,480 @@ mod tests { } // END TESTING + +#[cfg(kani)] +mod verification { + use core::borrow::Borrow; + + use super::{Color, Compare, Linkable, Links, RbTree}; + use super::test_common::{Tree, Node}; + use crate::types::{array::IndexMap, traits::{Get, GetMut, ToIndex}}; + + + /// Assert the three RB colour invariants hold on every reachable node: + /// 1. Root is black. + /// 2. No red node has a red child. + /// 3. Every root-to-null path passes through the same number of black nodes. + /// + /// Also asserts child→parent back-pointers are consistent. + fn assert_rb_invariants( + root: Option, + storage: &IndexMap, + ) { + // 1. Root colour. + if let Some(rid) = root { + assert!( + matches!(storage.get(rid).unwrap().links().color, Color::Black), + "root must be black" + ); + } + + const SENTINEL: usize = usize::MAX; + // Stack entry: (raw_id [SENTINEL = null], accumulated_black_count) + let mut stack: [(usize, u32); 16] = [(SENTINEL, 0); 16]; + let mut sp = 0usize; + + // Push the root. + stack[sp] = (root.unwrap_or(SENTINEL), 0); + sp += 1; + + let mut expected_bh: i64 = -1; // not yet observed + + while sp > 0 { + sp -= 1; + let (raw_id, bh) = stack[sp]; + + if raw_id == SENTINEL { + // Null leaf: counts as one extra black level. + let path_bh = bh as i64 + 1; + if expected_bh < 0 { + expected_bh = path_bh; + } else { + assert_eq!(path_bh, expected_bh, "black-height invariant violated"); + } + continue; + } + + let node = storage.get(raw_id).unwrap(); + let links = node.links(); + let is_red = matches!(links.color, Color::Red); + let new_bh = if is_red { bh } else { bh + 1 }; + + // 2. No adjacent red nodes. + if is_red { + if let Some(l) = links.left { + assert!( + !matches!(storage.get(l).unwrap().links().color, Color::Red), + "red node has red left child" + ); + } + if let Some(r) = links.right { + assert!( + !matches!(storage.get(r).unwrap().links().color, Color::Red), + "red node has red right child" + ); + } + } + + // Parent-pointer consistency. + if let Some(l) = links.left { + assert_eq!( + storage.get(l).unwrap().links().parent, + Some(raw_id), + "left child has wrong parent pointer" + ); + } + if let Some(r) = links.right { + assert_eq!( + storage.get(r).unwrap().links().parent, + Some(raw_id), + "right child has wrong parent pointer" + ); + } + + // Push children (right first so left is processed first). + assert!(sp + 2 <= 16, "DFS stack overflow — tree deeper than expected"); + stack[sp] = (links.right.unwrap_or(SENTINEL), new_bh); + sp += 1; + stack[sp] = (links.left.unwrap_or(SENTINEL), new_bh); + sp += 1; + } + } + + /// Assert the BST ordering property: an iterative in-order traversal + /// visits keys in strictly ascending order. + fn assert_bst_order( + root: Option, + storage: &IndexMap, + ) { + // Iterative in-order: push nodes going left, pop and visit, then go right. + let mut stack: [usize; 16] = [0; 16]; + let mut sp = 0usize; + let mut cur = root; + let mut prev_key: Option = None; + + loop { + // Descend left. + while let Some(id) = cur { + assert!(sp < 16, "in-order stack overflow"); + stack[sp] = id; + sp += 1; + cur = storage.get(id).unwrap().links().left; + } + if sp == 0 { + break; + } + sp -= 1; + let id = stack[sp]; + let node = storage.get(id).unwrap(); + // Strictly increasing. + if let Some(prev) = prev_key { + assert!(node.key > prev, "BST order violated"); + } + prev_key = Some(node.key); + cur = node.links().right; + } + } + + /// Verify inserting a single node doesn't panic and min() is correct. + #[kani::proof] + fn verify_insert_single_no_bug() { + let mut s: IndexMap = IndexMap::new(); + s.insert(&0, Node::new(42)).unwrap(); + + let mut tree: RbTree = RbTree::new(); + tree.insert(0, &mut s).unwrap(); + + assert_eq!(tree.min(), Some(0)); + } + + /// Verify inserting two nodes (both orderings via symbolic keys) doesn't panic, + /// and min() returns the node with the smaller key. + #[kani::proof] + #[kani::unwind(5)] + fn verify_insert_two_min_correct() { + let mut s: IndexMap = IndexMap::new(); + let key_a: i32 = kani::any(); + let key_b: i32 = kani::any(); + kani::assume(key_a != key_b); + + s.insert(&0, Node::new(key_a)).unwrap(); + s.insert(&1, Node::new(key_b)).unwrap(); + + let mut tree: RbTree = RbTree::new(); + tree.insert(0, &mut s).unwrap(); + tree.insert(1, &mut s).unwrap(); + + let min_id = tree.min().unwrap(); + let min_key = s.get(min_id).unwrap().key; + assert!(min_key <= key_a); + assert!(min_key <= key_b); + } + + /// Verify insert of three concrete nodes exercises insert_fixup without bug!. + #[kani::proof] + #[kani::unwind(6)] + fn verify_insert_three_no_bug() { + let mut s: IndexMap = IndexMap::new(); + s.insert(&0, Node::new(10)).unwrap(); + s.insert(&1, Node::new(5)).unwrap(); + s.insert(&2, Node::new(15)).unwrap(); + + let mut tree: RbTree = RbTree::new(); + tree.insert(0, &mut s).unwrap(); + tree.insert(1, &mut s).unwrap(); + tree.insert(2, &mut s).unwrap(); + + assert_eq!(tree.min(), Some(1)); + } + + /// Verify remove of root from 3-node tree exercises delete_fixup without bug!. + #[kani::proof] + #[kani::unwind(6)] + fn verify_remove_root_no_bug() { + let mut s: IndexMap = IndexMap::new(); + s.insert(&0, Node::new(10)).unwrap(); + s.insert(&1, Node::new(5)).unwrap(); + s.insert(&2, Node::new(15)).unwrap(); + + let mut tree: RbTree = RbTree::new(); + tree.insert(0, &mut s).unwrap(); + tree.insert(1, &mut s).unwrap(); + tree.insert(2, &mut s).unwrap(); + + tree.remove(0, &mut s).unwrap(); + + // After removing 10 (root), min must still be 5 (id=1) + assert_eq!(tree.min(), Some(1)); + } + + /// Verify removing the current minimum updates min() correctly. + #[kani::proof] + #[kani::unwind(6)] + fn verify_min_updates_after_remove_min() { + let mut s: IndexMap = IndexMap::new(); + s.insert(&0, Node::new(10)).unwrap(); + s.insert(&1, Node::new(5)).unwrap(); + s.insert(&2, Node::new(15)).unwrap(); + + let mut tree: RbTree = RbTree::new(); + tree.insert(0, &mut s).unwrap(); + tree.insert(1, &mut s).unwrap(); + tree.insert(2, &mut s).unwrap(); + + assert_eq!(tree.min(), Some(1)); + tree.remove(1, &mut s).unwrap(); // remove minimum (key=5) + assert_eq!(tree.min(), Some(0)); // new minimum is key=10 + } + + /// Verify that after removing all nodes the tree is empty. + #[kani::proof] + #[kani::unwind(6)] + fn verify_remove_all_empty() { + let mut s: IndexMap = IndexMap::new(); + s.insert(&0, Node::new(10)).unwrap(); + s.insert(&1, Node::new(5)).unwrap(); + s.insert(&2, Node::new(15)).unwrap(); + + let mut tree: RbTree = RbTree::new(); + tree.insert(0, &mut s).unwrap(); + tree.insert(1, &mut s).unwrap(); + tree.insert(2, &mut s).unwrap(); + + tree.remove(1, &mut s).unwrap(); + tree.remove(0, &mut s).unwrap(); + tree.remove(2, &mut s).unwrap(); + + assert!(tree.min().is_none()); + } + + /// Verify ascending-order insertion [1,2,3] — stress-tests right-leaning fixup. + #[kani::proof] + #[kani::unwind(6)] + fn verify_insert_ascending_order() { + let mut s: IndexMap = IndexMap::new(); + s.insert(&0, Node::new(1)).unwrap(); + s.insert(&1, Node::new(2)).unwrap(); + s.insert(&2, Node::new(3)).unwrap(); + + let mut tree: RbTree = RbTree::new(); + tree.insert(0, &mut s).unwrap(); + tree.insert(1, &mut s).unwrap(); + tree.insert(2, &mut s).unwrap(); + + let min_id = tree.min().unwrap(); + assert_eq!(s.get(min_id).unwrap().key, 1); + } + + // ----------------------------------------------------------------------- + // Invariant-checking proofs + // ----------------------------------------------------------------------- + + /// After inserting 3 concrete nodes the RB colour invariants and BST order hold. + /// Baseline check that the helpers themselves are correct on a small tree. + #[kani::proof] + #[kani::unwind(20)] + fn verify_rb_invariants_three_nodes() { + let mut s: IndexMap = IndexMap::new(); + s.insert(&0, Node::new(10)).unwrap(); + s.insert(&1, Node::new(5)).unwrap(); + s.insert(&2, Node::new(15)).unwrap(); + + let mut tree: RbTree = RbTree::new(); + tree.insert(0, &mut s).unwrap(); + tree.insert(1, &mut s).unwrap(); + tree.insert(2, &mut s).unwrap(); + + assert_rb_invariants(tree.root, &s); + assert_bst_order(tree.root, &s); + } + + /// RB invariants hold after inserting 7 nodes in ascending order and after + /// removing all 7 nodes. Ascending insertion maximally stresses the fixup path. + /// Unwind budget: 7 inserts×3 fixup iters = 21; 7 removes×3 = 21; 2 DFS calls×15 = 30; + /// 2 in-order calls×7 = 14; total ≈ 86. Using unwind(100) for headroom. + #[kani::proof] + #[kani::unwind(100)] + fn verify_rb_invariants_seven_ascending() { + let mut s: IndexMap = IndexMap::new(); + for i in 0usize..7 { + s.insert(&i, Node::new(i as i32 + 1)).unwrap(); + } + + let mut tree: RbTree = RbTree::new(); + for i in 0usize..7 { + tree.insert(i, &mut s).unwrap(); + } + + assert_rb_invariants(tree.root, &s); + assert_bst_order(tree.root, &s); + + // Remove all and verify the tree empties cleanly. + for i in 0usize..7 { + tree.remove(i, &mut s).unwrap(); + } + assert!(tree.root.is_none()); + assert!(tree.min().is_none()); + assert_rb_invariants(tree.root, &s); // trivially true for empty tree + } + + /// RB invariants hold after inserting 7 nodes in descending order. + #[kani::proof] + #[kani::unwind(100)] + fn verify_rb_invariants_seven_descending() { + let mut s: IndexMap = IndexMap::new(); + for i in 0usize..7 { + s.insert(&i, Node::new(7 - i as i32)).unwrap(); + } + + let mut tree: RbTree = RbTree::new(); + for i in 0usize..7 { + tree.insert(i, &mut s).unwrap(); + } + + assert_rb_invariants(tree.root, &s); + assert_bst_order(tree.root, &s); + } + + /// RB invariants hold after balanced insertion [4,2,6,1,3,5,7] and after + /// removing the root and minimum. + #[kani::proof] + #[kani::unwind(100)] + fn verify_rb_invariants_balanced_with_removes() { + let keys: [i32; 7] = [4, 2, 6, 1, 3, 5, 7]; + let mut s: IndexMap = IndexMap::new(); + for (i, &k) in keys.iter().enumerate() { + s.insert(&i, Node::new(k)).unwrap(); + } + + let mut tree: RbTree = RbTree::new(); + for i in 0usize..7 { + tree.insert(i, &mut s).unwrap(); + } + + assert_rb_invariants(tree.root, &s); + assert_bst_order(tree.root, &s); + + // Remove root (key=4, idx=0). + tree.remove(0, &mut s).unwrap(); + assert_rb_invariants(tree.root, &s); + assert_bst_order(tree.root, &s); + + // Remove current minimum (key=1, idx=3). + tree.remove(3, &mut s).unwrap(); + assert_rb_invariants(tree.root, &s); + assert_bst_order(tree.root, &s); + } + + /// Verify descending-order insertion [3,2,1] — stress-tests left-leaning fixup. + #[kani::proof] + #[kani::unwind(6)] + fn verify_insert_descending_order() { + let mut s: IndexMap = IndexMap::new(); + s.insert(&0, Node::new(3)).unwrap(); + s.insert(&1, Node::new(2)).unwrap(); + s.insert(&2, Node::new(1)).unwrap(); + + let mut tree: RbTree = RbTree::new(); + tree.insert(0, &mut s).unwrap(); + tree.insert(1, &mut s).unwrap(); + tree.insert(2, &mut s).unwrap(); + + let min_id = tree.min().unwrap(); + assert_eq!(s.get(min_id).unwrap().key, 1); + } + + /// 7-node ascending insertion [1..7] — maximally stresses right-rotation fixup. + /// min() must equal 1 throughout; after removing all nodes tree is empty. + #[kani::proof] + #[kani::unwind(30)] + fn verify_seven_ascending_insert_remove_all() { + let mut s: IndexMap = IndexMap::new(); + for i in 0usize..7 { + s.insert(&i, Node::new(i as i32 + 1)).unwrap(); // keys 1..=7 + } + + let mut tree: RbTree = RbTree::new(); + for i in 0usize..7 { + tree.insert(i, &mut s).unwrap(); + } + + // Min must be key 1 (stored at index 0). + assert_eq!(tree.min(), Some(0)); + assert_eq!(s.get(0usize).unwrap().key, 1); + + // Remove all nodes in insertion order; min must update correctly. + for i in 0usize..7 { + tree.remove(i, &mut s).unwrap(); + } + assert!(tree.min().is_none()); + } + + /// 7-node descending insertion [7..1] — maximally stresses left-rotation fixup. + #[kani::proof] + #[kani::unwind(30)] + fn verify_seven_descending_insert_remove_all() { + let mut s: IndexMap = IndexMap::new(); + for i in 0usize..7 { + s.insert(&i, Node::new(7 - i as i32)).unwrap(); // keys 7,6,5,4,3,2,1 + } + + let mut tree: RbTree = RbTree::new(); + for i in 0usize..7 { + tree.insert(i, &mut s).unwrap(); + } + + // After inserting [7,6,5,4,3,2,1], min is key 1 (stored at index 6). + assert_eq!(tree.min(), Some(6)); + assert_eq!(s.get(6usize).unwrap().key, 1); + + // Remove min repeatedly and verify it advances each time. + let expected_mins: [i32; 7] = [1, 2, 3, 4, 5, 6, 7]; + for &expected_key in &expected_mins { + let min_id = tree.min().unwrap(); + assert_eq!(s.get(min_id).unwrap().key, expected_key); + tree.remove(min_id, &mut s).unwrap(); + } + assert!(tree.min().is_none()); + } + + /// 7-node BFS-level insertion [4,2,6,1,3,5,7] — produces a perfectly balanced tree. + /// Exercises the case where very few fixup rotations are needed. + /// Then removes nodes in reverse BFS order and checks min after each. + #[kani::proof] + #[kani::unwind(30)] + fn verify_seven_balanced_insert_remove() { + // keys stored at their storage indices + // idx: 0=4, 1=2, 2=6, 3=1, 4=3, 5=5, 6=7 + let keys: [i32; 7] = [4, 2, 6, 1, 3, 5, 7]; + let mut s: IndexMap = IndexMap::new(); + for (i, &k) in keys.iter().enumerate() { + s.insert(&i, Node::new(k)).unwrap(); + } + + let mut tree: RbTree = RbTree::new(); + for i in 0usize..7 { + tree.insert(i, &mut s).unwrap(); + } + + // Min is key 1 at index 3. + assert_eq!(tree.min(), Some(3)); + assert_eq!(s.get(3usize).unwrap().key, 1); + + // Remove the root (key=4, idx=0). + tree.remove(0, &mut s).unwrap(); + // New min is still key 1 (index 3). + assert_eq!(tree.min(), Some(3)); + + // Remove min (key=1, idx=3). New min = key 2 (idx=1). + tree.remove(3, &mut s).unwrap(); + assert_eq!(tree.min(), Some(1)); + assert_eq!(s.get(1usize).unwrap().key, 2); + + // Remove remaining 5 nodes. + for i in [1usize, 2, 4, 5, 6] { + tree.remove(i, &mut s).unwrap(); + } + assert!(tree.min().is_none()); + } +} From 493a2d5fe29446e4495fef6fc14f5e1f2b3e1064 Mon Sep 17 00:00:00 2001 From: xarantolus Date: Sat, 4 Apr 2026 14:52:36 +0000 Subject: [PATCH 42/42] Fix typo --- src/mem/pfa/bitset.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mem/pfa/bitset.rs b/src/mem/pfa/bitset.rs index 2170ae7..16137e6 100644 --- a/src/mem/pfa/bitset.rs +++ b/src/mem/pfa/bitset.rs @@ -137,14 +137,14 @@ impl super::Allocator for Allocator { // Mark all bits in the middle words as used. { let mid_cnt = len / Self::BITS_PER_WORD; - + for i in 0..mid_cnt { self.l1[idx + i] = 0; } idx += mid_cnt; } - + // Mark the remaining bits in the last word as used. self.l1[idx] &= !((!0usize).unbounded_shl((Self::BITS_PER_WORD - (len % Self::BITS_PER_WORD)) as u32)); return Some(self.begin + (start * super::PAGE_SIZE)); @@ -193,9 +193,9 @@ mod tests { #[test] fn test_random_pattern() { - const ITARATIONS: usize = 1000; + const ITERATIONS: usize = 1000; - for i in 0..ITARATIONS { + for _ in 0..ITERATIONS { const N: usize = 1024; const BITS: usize = Allocator::::BITS_PER_WORD; const ALLOC_SIZE: usize = 100;