diff --git a/src/obkrnl/src/config/mod.rs b/src/obkrnl/src/config/mod.rs index ff075b142..2d2b01c75 100644 --- a/src/obkrnl/src/config/mod.rs +++ b/src/obkrnl/src/config/mod.rs @@ -8,11 +8,15 @@ pub use self::arch::*; #[cfg_attr(target_arch = "x86_64", path = "x86_64.rs")] mod arch; +/// # Interupt safety +/// This function is interupt safe. pub fn boot_env() -> &'static BootEnv { // SAFETY: This is safe because the setup() requirements. unsafe { &*BOOT_ENV } } +/// # Interupt safety +/// This function is interupt safe. pub fn config() -> &'static Config { // SAFETY: This is safe because the setup() requirements. unsafe { &*CONFIG } diff --git a/src/obkrnl/src/console/mod.rs b/src/obkrnl/src/console/mod.rs index d632c1c64..2732af9ea 100644 --- a/src/obkrnl/src/console/mod.rs +++ b/src/obkrnl/src/console/mod.rs @@ -12,6 +12,10 @@ mod vm; /// performance critical path. /// /// The LF character will be automatically appended. +/// +/// # Interupt safety +/// This macro is interupt safe as long as [`Display`] implementation on all arguments are interupt +/// safe (e.g. no heap allocation). #[macro_export] macro_rules! info { ($($args:tt)*) => { @@ -19,6 +23,9 @@ macro_rules! info { }; } +/// # Interupt safety +/// This function is interupt safe as long as [`Display`] implementation on `msg` are interupt safe +/// (e.g. no heap allocation). #[inline(never)] pub fn info(file: &str, line: u32, msg: impl Display) { print( @@ -33,6 +40,9 @@ pub fn info(file: &str, line: u32, msg: impl Display) { ); } +/// # Interupt safety +/// This function is interupt safe as long as [`Display`] implementation on `msg` are interupt safe +/// (e.g. no heap allocation). #[inline(never)] pub fn error(file: &str, line: u32, msg: impl Display) { print( @@ -47,6 +57,9 @@ pub fn error(file: &str, line: u32, msg: impl Display) { ) } +/// # Interupt safety +/// This function is interupt safe as long as [`Display`] implementation on `msg` are interupt safe +/// (e.g. no heap allocation). fn print(vty: MsgType, msg: impl Display) { match boot_env() { BootEnv::Vm(env) => self::vm::print(env, vty, msg), @@ -64,6 +77,7 @@ struct Log<'a, M: Display> { impl<'a, M: Display> Display for Log<'a, M> { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + // This implementation must be interupt safe. writeln!( f, "{}++++++++++++++++++ {} {}:{}{0:#}", diff --git a/src/obkrnl/src/console/vm.rs b/src/obkrnl/src/console/vm.rs index b17de3310..680c77990 100644 --- a/src/obkrnl/src/console/vm.rs +++ b/src/obkrnl/src/console/vm.rs @@ -3,6 +3,9 @@ use core::ptr::{addr_of_mut, write_volatile}; use obconf::Vm; use obvirt::console::{Memory, MsgType}; +/// # Interupt safety +/// This function is interupt safe as long as [`Display`] implementation on `msg` are interupt safe +/// (e.g. no heap allocation). pub fn print(env: &Vm, ty: MsgType, msg: impl Display) { let c = env.console as *mut Memory; let mut w = Writer(c); @@ -16,6 +19,7 @@ struct Writer(*mut Memory); impl Write for Writer { fn write_str(&mut self, s: &str) -> core::fmt::Result { + // This implementation must be interupt safe. unsafe { write_volatile(addr_of_mut!((*self.0).msg_len), s.len()) }; unsafe { write_volatile(addr_of_mut!((*self.0).msg_addr), s.as_ptr() as usize) }; Ok(()) diff --git a/src/obkrnl/src/context/mod.rs b/src/obkrnl/src/context/mod.rs index d4a967ec2..fd25e0086 100644 --- a/src/obkrnl/src/context/mod.rs +++ b/src/obkrnl/src/context/mod.rs @@ -26,6 +26,9 @@ impl Context { } } + /// # Interupt safety + /// This function is interupt safe. + #[inline(never)] pub fn thread() -> Arc { // It does not matter if we are on a different CPU after we load the Context::thread because // it is going to be the same one since it represent the current thread. @@ -38,15 +41,19 @@ impl Context { unsafe { Arc::from_raw(td) } } + /// Pin the calling thread to one CPU. + /// + /// This thread will never switch to a different CPU until the returned [`PinnedContext`] is + /// dropped (but it is allowed to sleep). + /// /// See `critical_enter` and `critical_exit` on the PS4 for a reference. + #[inline(never)] pub fn pin() -> PinnedContext { - // TODO: Verify if memory ordering here is correct. We need a call to self::arch::current() - // to execute after the thread is in a critical section. The CPU must not reorder this. Our - // current implementation follow how Drop on Arc is implemented. + // Relax ordering should be enough here since this increment will be checked by the same CPU + // when an interupt happens. let td = unsafe { self::arch::thread() }; - unsafe { (*td).critical_sections().fetch_add(1, Ordering::Release) }; - core::sync::atomic::fence(Ordering::Acquire); + unsafe { (*td).critical_sections().fetch_add(1, Ordering::Relaxed) }; // Once the thread is in a critical section it will never be switch a CPU so it is safe to // keep a pointer to a context here. @@ -83,10 +90,11 @@ impl PinnedContext { impl Drop for PinnedContext { fn drop(&mut self) { - // TODO: Verify if memory ordering here is correct. + // Relax ordering should be enough here since this decrement will be checked by the same CPU + // when an interupt happens. let td = unsafe { (*self.0).thread.load(Ordering::Relaxed) }; - unsafe { (*td).critical_sections().fetch_sub(1, Ordering::Release) }; + unsafe { (*td).critical_sections().fetch_sub(1, Ordering::Relaxed) }; // TODO: Implement td_owepreempt. } diff --git a/src/obkrnl/src/context/x86_64.rs b/src/obkrnl/src/context/x86_64.rs index d3d59532b..a0c449025 100644 --- a/src/obkrnl/src/context/x86_64.rs +++ b/src/obkrnl/src/context/x86_64.rs @@ -52,7 +52,8 @@ pub unsafe fn thread() -> *const Thread { } pub unsafe fn current() -> *const Context { - // Load current GS. + // Load current GS. Although the "rdmsr" does not read or write to any memory but it need to + // synchronize with a critical section. let mut edx: u32; let mut eax: u32; @@ -61,7 +62,7 @@ pub unsafe fn current() -> *const Context { in("ecx") 0xc0000101u32, out("edx") edx, out("eax") eax, - options(pure, nomem, preserves_flags, nostack) + options(preserves_flags, nostack) ); // Combine EDX and EAX. diff --git a/src/obkrnl/src/main.rs b/src/obkrnl/src/main.rs index af3bdded3..aec1d352f 100644 --- a/src/obkrnl/src/main.rs +++ b/src/obkrnl/src/main.rs @@ -74,6 +74,8 @@ fn main() -> ! { } } +/// # Interupt safety +/// This function is interupt safe. #[allow(dead_code)] #[cfg_attr(target_os = "none", panic_handler)] fn panic(i: &PanicInfo) -> ! { diff --git a/src/obkrnl/src/malloc/stage2.rs b/src/obkrnl/src/malloc/stage2.rs index 33125f21e..24eb312f6 100644 --- a/src/obkrnl/src/malloc/stage2.rs +++ b/src/obkrnl/src/malloc/stage2.rs @@ -66,6 +66,8 @@ impl Stage2 { Self { zones, stats } } + /// Returns null on failure. + /// /// See `malloc` on the PS4 for a reference. /// /// # Safety diff --git a/src/obkrnl/src/panic/mod.rs b/src/obkrnl/src/panic/mod.rs index 11bfbebc1..537030542 100644 --- a/src/obkrnl/src/panic/mod.rs +++ b/src/obkrnl/src/panic/mod.rs @@ -1,6 +1,9 @@ use core::arch::asm; /// Perform panic after printing the panic message. +/// +/// # Interupt safety +/// This function is interupt safe. pub fn panic() -> ! { loop { #[cfg(target_arch = "aarch64")] diff --git a/src/obkrnl/src/proc/thread.rs b/src/obkrnl/src/proc/thread.rs index e1d4cdbc7..1a0aa04b2 100644 --- a/src/obkrnl/src/proc/thread.rs +++ b/src/obkrnl/src/proc/thread.rs @@ -4,6 +4,9 @@ use core::sync::atomic::AtomicU32; /// /// All thread **must** run to completion once execution has been started otherwise resource will be /// leak if the thread is dropped while its execution currently in the kernel space. +/// +/// We subtitute `TDP_NOSLEEPING` with `td_intr_nesting_level` since the only cases the thread +/// should not allow to sleep is when it being handle an interupt. pub struct Thread { critical_sections: AtomicU32, // td_critnest active_interrupts: usize, // td_intr_nesting_level @@ -23,7 +26,12 @@ impl Thread { } } - pub fn critical_sections(&self) -> &AtomicU32 { + /// See [`crate::context::Context::pin()`] for a safe wrapper. + /// + /// # Safety + /// This is a counter. Each increment must paired with a decrement. Failure to do so will cause + /// the whole system to be in an undefined behavior. + pub unsafe fn critical_sections(&self) -> &AtomicU32 { &self.critical_sections } diff --git a/src/obkrnl/src/uma/bucket.rs b/src/obkrnl/src/uma/bucket.rs new file mode 100644 index 000000000..a0150f810 --- /dev/null +++ b/src/obkrnl/src/uma/bucket.rs @@ -0,0 +1,10 @@ +/// Implementation of `uma_bucket` structure. +pub struct UmaBucket { + len: usize, // ub_cnt +} + +impl UmaBucket { + pub fn len(&self) -> usize { + self.len + } +} diff --git a/src/obkrnl/src/uma/cache.rs b/src/obkrnl/src/uma/cache.rs new file mode 100644 index 000000000..1894eaf88 --- /dev/null +++ b/src/obkrnl/src/uma/cache.rs @@ -0,0 +1,13 @@ +use super::bucket::UmaBucket; + +/// Implementation of `uma_cache` structure. +#[derive(Default)] +pub struct UmaCache { + alloc: Option, // uc_allocbucket +} + +impl UmaCache { + pub fn alloc(&self) -> Option<&UmaBucket> { + self.alloc.as_ref() + } +} diff --git a/src/obkrnl/src/uma/mod.rs b/src/obkrnl/src/uma/mod.rs index 90eae17c0..f4c2dd2e7 100644 --- a/src/obkrnl/src/uma/mod.rs +++ b/src/obkrnl/src/uma/mod.rs @@ -1,15 +1,34 @@ +use self::cache::UmaCache; +use crate::config::config; +use crate::context::Context; use alloc::borrow::Cow; +use alloc::vec::Vec; + +mod bucket; +mod cache; /// Implementation of `uma_zone` structure. pub struct UmaZone { - size: usize, // uz_size + size: usize, // uz_size + caches: Vec, // uz_cpu } impl UmaZone { /// See `uma_zcreate` on the PS4 for a reference. pub fn new(_: Cow<'static, str>, size: usize, _: usize) -> Self { - // TODO: Check if size is allowed to be zero. If not, change it to NonZero. - Self { size } + // Ths PS4 allocate a new uma_zone from masterzone_z but we don't have that. This method + // basically an implementation of zone_ctor. + let len = config().max_cpu.get(); + let mut caches = Vec::with_capacity(len); + + for _ in 0..len { + caches.push(UmaCache::default()); + } + + Self { + size, // TODO: Check if size is allowed to be zero. If not, change it to NonZero. + caches, + } } pub fn size(&self) -> usize { @@ -18,6 +37,26 @@ impl UmaZone { /// See `uma_zalloc_arg` on the PS4 for a reference. pub fn alloc(&self) -> *mut u8 { + // Our implementation imply M_WAITOK and M_ZERO. + let td = Context::thread(); + + if td.active_interrupts() != 0 { + panic!("heap allocation in an interrupt handler is not supported"); + } + + // Try to allocate from per-CPU cache. + let cx = Context::pin(); + let cache = &self.caches[cx.cpu()]; + let bucket = cache.alloc(); + + while let Some(bucket) = bucket { + if bucket.len() != 0 { + todo!() + } + + todo!() + } + todo!() } }