feat(kernel): add Kernel struct, MockPlatform, and simple syscalls (GetTime, GetProcessId, Yield)

This commit is contained in:
2026-10-18 00:15:40 +02:00
parent 8dd36e7d6d
commit d0a0ba788f
9 changed files with 355 additions and 10 deletions

View File

@@ -17,7 +17,7 @@ pub struct Capability {
} }
impl Capability { impl Capability {
pub(crate) fn new( pub fn new(
resource: Resource, resource: Resource,
permissions: PermissionSet, permissions: PermissionSet,
generation: Generation, generation: Generation,

View File

@@ -30,6 +30,10 @@ impl MultiCoreScheduler {
} }
} }
pub fn current(&self, core: CoreId) -> Option<ThreadId> {
self.cores[core.raw() as usize].current()
}
fn thread_count_on(&self, core: CoreId) -> usize { fn thread_count_on(&self, core: CoreId) -> usize {
self.placements self.placements
.iter() .iter()

View File

@@ -4,3 +4,6 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
types = { workspace = true }
domain = { workspace = true }
platform = { workspace = true }

View File

@@ -0,0 +1,15 @@
use domain::scheduler::SchedulerDecision;
use platform::Platform;
use types::id::{CoreId, IrqId, ThreadId};
use crate::Kernel;
impl<P: Platform> Kernel<P> {
pub fn handle_tick(&mut self, _core: CoreId) -> SchedulerDecision {
SchedulerDecision::Continue
}
pub fn handle_interrupt(&mut self, _irq: IrqId) -> Option<ThreadId> {
None
}
}

View File

@@ -1,14 +1,151 @@
pub fn add(left: u64, right: u64) -> u64 { #![no_std]
left + right
} pub mod interrupt;
pub mod syscall;
#[cfg(test)] #[cfg(test)]
mod tests { #[path = "tests/mod.rs"]
use super::*; mod tests;
#[test] use domain::{
fn it_works() { capability::{Capability, CapabilityTable},
let result = add(2, 2); endpoint::EndpointTable,
assert_eq!(result, 4); 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<P: Platform> {
platform: P,
processes: ProcessTable,
threads: ThreadTable,
endpoints: EndpointTable,
scheduler: MultiCoreScheduler,
page_pool: PagePool,
process_memory: [Option<ProcessMemory>; MAX_PROCESSES],
regions: RegionTable,
irqs: IrqRoutingTable,
pending_messages: [Option<Message>; MAX_THREADS],
pending_results: [Option<SyscallResult>; MAX_THREADS],
}
impl<P: Platform> Kernel<P> {
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<CapSlot, KernelError> {
let cap = Capability::new(resource, permissions, Generation::initial());
self.processes.get_mut(to)?.capabilities_mut().insert(cap)
} }
} }

View File

@@ -0,0 +1,63 @@
use domain::syscall::Syscall;
use platform::Platform;
use crate::{Kernel, SyscallOutcome, SyscallResult};
impl<P: Platform> Kernel<P> {
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)
}
}
}
}

View File

@@ -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<MockPlatform> {
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<MockPlatform>, ProcessId) {
let mut kernel = test_kernel();
let (pid, _tid) = kernel
.boot(Priority::new(16).unwrap(), TimeSlice::new(100))
.unwrap();
(kernel, pid)
}

View File

@@ -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(_))
));
}

View File

@@ -2,6 +2,14 @@
pub struct Ticks(u64); pub struct Ticks(u64);
impl Ticks { 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 { pub fn elapsed_since(&self, earlier: &Ticks) -> Ticks {
Ticks(self.0.saturating_sub(earlier.0)) Ticks(self.0.saturating_sub(earlier.0))
} }