feat(kernel): add Kernel struct, MockPlatform, and simple syscalls (GetTime, GetProcessId, Yield)
This commit is contained in:
15
crates/kernel/src/interrupt.rs
Normal file
15
crates/kernel/src/interrupt.rs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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<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)
|
||||
}
|
||||
}
|
||||
|
||||
63
crates/kernel/src/syscall.rs
Normal file
63
crates/kernel/src/syscall.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
70
crates/kernel/src/tests/mod.rs
Normal file
70
crates/kernel/src/tests/mod.rs
Normal 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)
|
||||
}
|
||||
45
crates/kernel/src/tests/simple.rs
Normal file
45
crates/kernel/src/tests/simple.rs
Normal 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(_))
|
||||
));
|
||||
}
|
||||
Reference in New Issue
Block a user