From d0a0ba788f257f72bddd0595f18ec5f4154fe348 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 18 Oct 2026 00:15:40 +0200 Subject: [PATCH] feat(kernel): add Kernel struct, MockPlatform, and simple syscalls (GetTime, GetProcessId, Yield) --- crates/domain/src/capability/mod.rs | 2 +- crates/domain/src/scheduler/multi_core.rs | 4 + crates/kernel/Cargo.toml | 3 + crates/kernel/src/interrupt.rs | 15 +++ crates/kernel/src/lib.rs | 155 ++++++++++++++++++++-- crates/kernel/src/syscall.rs | 63 +++++++++ crates/kernel/src/tests/mod.rs | 70 ++++++++++ crates/kernel/src/tests/simple.rs | 45 +++++++ crates/types/src/time.rs | 8 ++ 9 files changed, 355 insertions(+), 10 deletions(-) create mode 100644 crates/kernel/src/interrupt.rs create mode 100644 crates/kernel/src/syscall.rs create mode 100644 crates/kernel/src/tests/mod.rs create mode 100644 crates/kernel/src/tests/simple.rs diff --git a/crates/domain/src/capability/mod.rs b/crates/domain/src/capability/mod.rs index 0528ae3..ec02e00 100644 --- a/crates/domain/src/capability/mod.rs +++ b/crates/domain/src/capability/mod.rs @@ -17,7 +17,7 @@ pub struct Capability { } impl Capability { - pub(crate) fn new( + pub fn new( resource: Resource, permissions: PermissionSet, generation: Generation, diff --git a/crates/domain/src/scheduler/multi_core.rs b/crates/domain/src/scheduler/multi_core.rs index 3ddfcff..3c6cd3f 100644 --- a/crates/domain/src/scheduler/multi_core.rs +++ b/crates/domain/src/scheduler/multi_core.rs @@ -30,6 +30,10 @@ impl MultiCoreScheduler { } } + pub fn current(&self, core: CoreId) -> Option { + self.cores[core.raw() as usize].current() + } + fn thread_count_on(&self, core: CoreId) -> usize { self.placements .iter() diff --git a/crates/kernel/Cargo.toml b/crates/kernel/Cargo.toml index c9211be..06ecabe 100644 --- a/crates/kernel/Cargo.toml +++ b/crates/kernel/Cargo.toml @@ -4,3 +4,6 @@ version = "0.1.0" edition = "2024" [dependencies] +types = { workspace = true } +domain = { workspace = true } +platform = { workspace = true } diff --git a/crates/kernel/src/interrupt.rs b/crates/kernel/src/interrupt.rs new file mode 100644 index 0000000..680c773 --- /dev/null +++ b/crates/kernel/src/interrupt.rs @@ -0,0 +1,15 @@ +use domain::scheduler::SchedulerDecision; +use platform::Platform; +use types::id::{CoreId, IrqId, ThreadId}; + +use crate::Kernel; + +impl Kernel

{ + pub fn handle_tick(&mut self, _core: CoreId) -> SchedulerDecision { + SchedulerDecision::Continue + } + + pub fn handle_interrupt(&mut self, _irq: IrqId) -> Option { + None + } +} diff --git a/crates/kernel/src/lib.rs b/crates/kernel/src/lib.rs index b93cf3f..dd29b91 100644 --- a/crates/kernel/src/lib.rs +++ b/crates/kernel/src/lib.rs @@ -1,14 +1,151 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} +#![no_std] + +pub mod interrupt; +pub mod syscall; #[cfg(test)] -mod tests { - use super::*; +#[path = "tests/mod.rs"] +mod tests; - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); +use domain::{ + capability::{Capability, CapabilityTable}, + endpoint::EndpointTable, + error::KernelError, + irq::IrqRoutingTable, + memory::{ + page_pool::PagePool, + process_memory::ProcessMemory, + RegionTable, + }, + message::Message, + process::{ProcessTable, MAX_PROCESSES}, + resource::Resource, + scheduler::{multi_core::MultiCoreScheduler, Scheduler}, + thread::{ThreadTable, ThreadState, MAX_THREADS}, +}; +use platform::Platform; +use types::{ + bounded::{CapSlot, Priority}, + generation::Generation, + id::{ProcessId, ThreadId}, + permission::{Permission, PermissionSet}, + time::TimeSlice, +}; + +pub enum SyscallOutcome { + Complete(SyscallResult), + BlockCaller(domain::thread::BlockReason), + SwitchTo(ThreadId), + TerminateCaller, +} + +pub enum SyscallResult { + Ok, + Value(usize), + Slot(CapSlot), + Message(Message), + WakeReason(domain::syscall::WakeReason), + Error(KernelError), +} + +pub struct Kernel { + platform: P, + processes: ProcessTable, + threads: ThreadTable, + endpoints: EndpointTable, + scheduler: MultiCoreScheduler, + page_pool: PagePool, + process_memory: [Option; MAX_PROCESSES], + regions: RegionTable, + irqs: IrqRoutingTable, + pending_messages: [Option; MAX_THREADS], + pending_results: [Option; MAX_THREADS], +} + +impl Kernel

{ + pub fn new(platform: P, page_pool: PagePool) -> Self { + let num_cores = platform.num_cores(); + Self { + platform, + processes: ProcessTable::new(), + threads: ThreadTable::new(), + endpoints: EndpointTable::new(), + scheduler: MultiCoreScheduler::new(num_cores), + page_pool, + process_memory: [const { None }; MAX_PROCESSES], + regions: RegionTable::new(), + irqs: IrqRoutingTable::new(), + pending_messages: [const { None }; MAX_THREADS], + pending_results: [const { None }; MAX_THREADS], + } + } + + pub fn boot( + &mut self, + priority: Priority, + time_slice: TimeSlice, + ) -> Result<(ProcessId, ThreadId), KernelError> { + let pid = self.processes.create(CapabilityTable::new(), None)?; + let tid = self.threads.spawn(pid, priority, time_slice, None)?; + self.processes.get_mut(pid)?.add_thread(tid)?; + + self.process_memory[pid.raw() as usize] = + Some(ProcessMemory::new(pid, 1024)); + + let cap = Capability::new( + Resource::ProcessCreate, + PermissionSet::empty().with(Permission::Execute), + Generation::initial(), + ); + self.processes.get_mut(pid)?.capabilities_mut().insert(cap)?; + + let core = self.platform.core_id(); + self.scheduler.add(tid, priority, core); + self.scheduler.next(core); + self.threads.get_mut(tid)?.set_running(core)?; + Ok((pid, tid)) + } + + pub fn platform_mut(&mut self) -> &mut P { + &mut self.platform + } + + pub fn pending_result(&self, thread: ThreadId) -> Option<&SyscallResult> { + self.pending_results[thread.raw() as usize].as_ref() + } + + fn caller(&self) -> Result<(ThreadId, ProcessId), KernelError> { + let core = self.platform.core_id(); + let tid = self.scheduler.current(core) + .ok_or(KernelError::WouldBlock)?; + let thread = self.threads.get(tid)?; + match thread.state() { + ThreadState::Running(_) => Ok((tid, thread.process())), + _ => Err(KernelError::WouldBlock), + } + } + + fn check_cap( + &self, + owner: ProcessId, + slot: CapSlot, + needed: Permission, + ) -> Result<&Capability, KernelError> { + let process = self.processes.get(owner)?; + let cap = process.capabilities().get(slot)?; + if !cap.permissions().allows(needed) { + return Err(KernelError::PermissionDenied { slot, needed }); + } + Ok(cap) + } + + fn grant_cap( + &mut self, + to: ProcessId, + resource: Resource, + permissions: PermissionSet, + ) -> Result { + let cap = Capability::new(resource, permissions, Generation::initial()); + self.processes.get_mut(to)?.capabilities_mut().insert(cap) } } diff --git a/crates/kernel/src/syscall.rs b/crates/kernel/src/syscall.rs new file mode 100644 index 0000000..a1e2af2 --- /dev/null +++ b/crates/kernel/src/syscall.rs @@ -0,0 +1,63 @@ +use domain::syscall::Syscall; +use platform::Platform; + +use crate::{Kernel, SyscallOutcome, SyscallResult}; + +impl Kernel

{ + pub fn handle_syscall(&mut self, syscall: Syscall) -> SyscallOutcome { + let (tid, pid) = match self.caller() { + Ok(caller) => caller, + Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)), + }; + + match syscall { + Syscall::GetTime => self.handle_get_time(), + Syscall::GetProcessId => self.handle_get_process_id(pid), + Syscall::Yield => self.handle_yield(tid), + _ => SyscallOutcome::Complete(SyscallResult::Error( + domain::error::KernelError::WouldBlock, + )), + } + } + + fn handle_get_time(&self) -> SyscallOutcome { + let ticks = self.platform.current_ticks(); + SyscallOutcome::Complete(SyscallResult::Value(ticks.raw() as usize)) + } + + fn handle_get_process_id(&self, pid: types::id::ProcessId) -> SyscallOutcome { + SyscallOutcome::Complete(SyscallResult::Value(pid.raw() as usize)) + } + + fn handle_yield(&mut self, caller_tid: types::id::ThreadId) -> SyscallOutcome { + use domain::scheduler::Scheduler; + + let core = self.platform.core_id(); + let thread = match self.threads.get_mut(caller_tid) { + Ok(t) => t, + Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)), + }; + + let priority = thread.priority(); + if thread.preempt().is_err() { + return SyscallOutcome::Complete(SyscallResult::Ok); + } + + self.scheduler.add(caller_tid, priority, core); + + match self.scheduler.next(core) { + Some(next) if next != caller_tid => { + if self.threads.get_mut(next).and_then(|t| t.set_running(core)).is_ok() { + SyscallOutcome::SwitchTo(next) + } else { + let _ = self.threads.get_mut(caller_tid).map(|t| t.set_running(core)); + SyscallOutcome::Complete(SyscallResult::Ok) + } + } + _ => { + let _ = self.threads.get_mut(caller_tid).map(|t| t.set_running(core)); + SyscallOutcome::Complete(SyscallResult::Ok) + } + } + } +} diff --git a/crates/kernel/src/tests/mod.rs b/crates/kernel/src/tests/mod.rs new file mode 100644 index 0000000..6aacb27 --- /dev/null +++ b/crates/kernel/src/tests/mod.rs @@ -0,0 +1,70 @@ +mod simple; + +use domain::memory::page_pool::PagePool; +use platform::Platform; +use types::{ + address::{EntryPoint, PhysicalAddress, StackPointer}, + bounded::Priority, + id::{CoreId, IrqId, ProcessId}, + time::{Ticks, TimerDuration, TimeSlice}, +}; + +use domain::memory::MemoryRegion; +use domain::thread::SavedContext; + +use crate::Kernel; + +pub struct MockPlatform { + ticks: Ticks, + core: CoreId, + cores: u8, +} + +impl MockPlatform { + pub fn new(num_cores: u8) -> Self { + Self { + ticks: Ticks::from_raw(0), + core: CoreId::from_raw(0), + cores: num_cores, + } + } + + pub fn set_ticks(&mut self, ticks: Ticks) { + self.ticks = ticks; + } +} + +impl Platform for MockPlatform { + fn configure_protection(&mut self, _process: ProcessId, _regions: &[MemoryRegion]) {} + fn context_switch(&mut self, _from: &mut SavedContext, _to: &SavedContext) {} + fn enable_user_mode(&mut self, _entry: EntryPoint, _stack: StackPointer) -> ! { + loop {} + } + fn set_timer(&mut self, _duration: TimerDuration) {} + fn current_ticks(&self) -> Ticks { + self.ticks + } + fn mask_irq(&mut self, _irq: IrqId) {} + fn unmask_irq(&mut self, _irq: IrqId) {} + fn send_ipi(&mut self, _target: CoreId) {} + fn core_id(&self) -> CoreId { + self.core + } + fn num_cores(&self) -> u8 { + self.cores + } +} + +pub fn test_kernel() -> Kernel { + let mock = MockPlatform::new(1); + let pool = PagePool::new(PhysicalAddress::from_raw(0x8000_0000), 1024); + Kernel::new(mock, pool) +} + +pub fn kernel_with_process() -> (Kernel, ProcessId) { + let mut kernel = test_kernel(); + let (pid, _tid) = kernel + .boot(Priority::new(16).unwrap(), TimeSlice::new(100)) + .unwrap(); + (kernel, pid) +} diff --git a/crates/kernel/src/tests/simple.rs b/crates/kernel/src/tests/simple.rs new file mode 100644 index 0000000..e7f3378 --- /dev/null +++ b/crates/kernel/src/tests/simple.rs @@ -0,0 +1,45 @@ +use super::*; +use crate::{SyscallOutcome, SyscallResult}; +use domain::syscall::Syscall; +use types::time::Ticks; + +#[test] +fn get_time_returns_current_ticks() { + let (mut k, _) = kernel_with_process(); + k.platform_mut().set_ticks(Ticks::from_raw(12345)); + let outcome = k.handle_syscall(Syscall::GetTime); + assert!(matches!( + outcome, + SyscallOutcome::Complete(SyscallResult::Value(12345)) + )); +} + +#[test] +fn get_process_id_returns_caller_process() { + let (mut k, pid) = kernel_with_process(); + let outcome = k.handle_syscall(Syscall::GetProcessId); + assert!(matches!( + outcome, + SyscallOutcome::Complete(SyscallResult::Value(v)) if v == pid.raw() as usize + )); +} + +#[test] +fn yield_with_no_other_threads_returns_complete() { + let (mut k, _) = kernel_with_process(); + let outcome = k.handle_syscall(Syscall::Yield); + assert!(matches!( + outcome, + SyscallOutcome::Complete(SyscallResult::Ok) + )); +} + +#[test] +fn syscall_with_no_running_thread_returns_error() { + let mut k = test_kernel(); + let outcome = k.handle_syscall(Syscall::GetTime); + assert!(matches!( + outcome, + SyscallOutcome::Complete(SyscallResult::Error(_)) + )); +} diff --git a/crates/types/src/time.rs b/crates/types/src/time.rs index f1d596a..32dbdad 100644 --- a/crates/types/src/time.rs +++ b/crates/types/src/time.rs @@ -2,6 +2,14 @@ pub struct Ticks(u64); impl Ticks { + pub fn from_raw(value: u64) -> Self { + Self(value) + } + + pub fn raw(&self) -> u64 { + self.0 + } + pub fn elapsed_since(&self, earlier: &Ticks) -> Ticks { Ticks(self.0.saturating_sub(earlier.0)) }