diff --git a/Cargo.lock b/Cargo.lock index 5166958..509db75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,6 +45,9 @@ version = "0.1.0" [[package]] name = "domain" version = "0.1.0" +dependencies = [ + "types", +] [[package]] name = "gatekeeper" @@ -89,6 +92,10 @@ version = "0.1.0" [[package]] name = "platform" version = "0.1.0" +dependencies = [ + "domain", + "types", +] [[package]] name = "power-server" diff --git a/crates/domain/Cargo.toml b/crates/domain/Cargo.toml index 2f841ed..15a24d6 100644 --- a/crates/domain/Cargo.toml +++ b/crates/domain/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" edition = "2024" [dependencies] +types = { workspace = true } diff --git a/crates/domain/src/capability/authority.rs b/crates/domain/src/capability/authority.rs new file mode 100644 index 0000000..f161604 --- /dev/null +++ b/crates/domain/src/capability/authority.rs @@ -0,0 +1,33 @@ +use types::{ + bounded::CapSlot, + id::ProcessId, + permission::{Permission, PermissionSet}, +}; + +use crate::{capability::Capability, error::KernelError, resource::Resource}; + +pub trait CapabilityAuthority { + fn grant( + &mut self, + to: ProcessId, + resource: Resource, + permissions: PermissionSet, + ) -> Result; + + fn revoke(&mut self, owner: ProcessId, slot: CapSlot) -> Result<(), KernelError>; + + fn delegate( + &mut self, + from: ProcessId, + slot: CapSlot, + to: ProcessId, + restrict: PermissionSet, + ) -> Result; + + fn check( + &self, + caller: ProcessId, + slot: CapSlot, + needed: Permission, + ) -> Result<&Capability, KernelError>; +} diff --git a/crates/domain/src/capability/mod.rs b/crates/domain/src/capability/mod.rs new file mode 100644 index 0000000..0528ae3 --- /dev/null +++ b/crates/domain/src/capability/mod.rs @@ -0,0 +1,82 @@ +use types::{bounded::CapSlot, generation::Generation, permission::PermissionSet}; + +pub mod authority; + +use crate::{error::KernelError, resource::Resource}; + +#[cfg(test)] +#[path = "tests.rs"] +mod tests; + +const MAX_CAPS_PER_PROCESS: usize = 16; + +pub struct Capability { + resource: Resource, + permissions: PermissionSet, + generation: Generation, +} + +impl Capability { + pub(crate) fn new( + resource: Resource, + permissions: PermissionSet, + generation: Generation, + ) -> Self { + Self { + resource, + permissions, + generation, + } + } + + pub fn resource(&self) -> &Resource { + &self.resource + } + + pub fn permissions(&self) -> &PermissionSet { + &self.permissions + } + + pub fn generation(&self) -> &Generation { + &self.generation + } +} + +pub struct CapabilityTable { + slots: [Option; MAX_CAPS_PER_PROCESS], +} + +impl CapabilityTable { + pub fn new() -> Self { + Self { + slots: [const { None }; MAX_CAPS_PER_PROCESS], + } + } + + pub fn get(&self, slot: CapSlot) -> Result<&Capability, KernelError> { + let idx = slot.value() as usize; + self.slots[idx] + .as_ref() + .ok_or(KernelError::InvalidSlot(slot)) + } + + pub fn insert(&mut self, cap: Capability) -> Result { + let idx = self + .slots + .iter() + .position(|s| s.is_none()) + .ok_or(KernelError::NoFreeSlot)?; + + self.slots[idx] = Some(cap); + CapSlot::new(idx as u8).ok_or(KernelError::NoFreeSlot) + } + + pub fn remove(&mut self, slot: CapSlot) -> Result<(), KernelError> { + let idx = slot.value() as usize; + if self.slots[idx].is_none() { + return Err(KernelError::InvalidSlot(slot)); + } + self.slots[idx] = None; + Ok(()) + } +} diff --git a/crates/domain/src/capability/tests.rs b/crates/domain/src/capability/tests.rs new file mode 100644 index 0000000..3351def --- /dev/null +++ b/crates/domain/src/capability/tests.rs @@ -0,0 +1,152 @@ +use super::*; +use types::generation::Generation; +use types::id::{EndpointId, RegionId}; +use types::permission::{Permission, PermissionSet}; + +fn perms(permissions: &[Permission]) -> PermissionSet { + permissions + .iter() + .fold(PermissionSet::empty(), |set, &p| set.with(p)) +} + +fn endpoint_cap(id: u16, permissions: &[Permission]) -> Capability { + Capability::new( + Resource::Endpoint(EndpointId::from_raw(id)), + perms(permissions), + Generation::initial(), + ) +} + +fn memory_cap(id: u16, permissions: &[Permission]) -> Capability { + Capability::new( + Resource::Memory(RegionId::from_raw(id)), + perms(permissions), + Generation::initial(), + ) +} + +#[test] +fn new_table_is_empty() { + let table = CapabilityTable::new(); + let slot = CapSlot::new(0).unwrap(); + assert!(table.get(slot).is_err()); +} + +#[test] +fn insert_and_get() { + let mut table = CapabilityTable::new(); + let slot = table.insert(endpoint_cap(1, &[Permission::Read])).unwrap(); + let retrieved = table.get(slot).unwrap(); + + assert!(retrieved.permissions().allows(Permission::Read)); + assert!(!retrieved.permissions().allows(Permission::Write)); +} + +#[test] +fn remove_then_get_fails() { + let mut table = CapabilityTable::new(); + let slot = table.insert(endpoint_cap(1, &[Permission::Read])).unwrap(); + table.remove(slot).unwrap(); + assert!(table.get(slot).is_err()); +} + +#[test] +fn remove_invalid_slot_fails() { + let mut table = CapabilityTable::new(); + let slot = CapSlot::new(5).unwrap(); + + assert!(table.remove(slot).is_err()); +} + +#[test] +fn insert_reuses_removed_slot() { + let mut table = CapabilityTable::new(); + let slot = table.insert(endpoint_cap(1, &[Permission::Read])).unwrap(); + table.remove(slot).unwrap(); + let new_slot = table.insert(endpoint_cap(2, &[Permission::Write])).unwrap(); + assert_eq!(slot, new_slot); +} + +#[test] +fn full_table_returns_no_free_slot() { + let mut table = CapabilityTable::new(); + for i in 0..MAX_CAPS_PER_PROCESS { + assert!( + table + .insert(endpoint_cap(i as u16, &[Permission::Read])) + .is_ok() + ); + } + assert!(table.insert(endpoint_cap(999, &[])).is_err()); +} + +#[test] +fn generation_mismatch_detected() { + let gen_v1 = Generation::initial(); + let gen_v2 = gen_v1.next(); + + assert!(gen_v1.is_valid_against(&gen_v1)); + assert!(!gen_v1.is_valid_against(&gen_v2)); +} + +#[test] +fn restrict_to_never_escalates() { + let full = perms(&[Permission::Read, Permission::Write, Permission::Execute]); + let read_only = perms(&[Permission::Read]); + + let restricted = full.restrict_to(&read_only); + + assert!(restricted.allows(Permission::Read)); + assert!(!restricted.allows(Permission::Write)); + assert!(!restricted.allows(Permission::Execute)); +} + +#[test] +fn restrict_to_cannot_add_permissions() { + let read_only = perms(&[Permission::Read]); + let wants_everything = perms(&[ + Permission::Read, + Permission::Write, + Permission::Execute, + Permission::Delegate, + Permission::Revoke, + ]); + + let result = read_only.restrict_to(&wants_everything); + + assert!(result.allows(Permission::Read)); + assert!(!result.allows(Permission::Write)); + assert!(!result.allows(Permission::Execute)); + assert!(!result.allows(Permission::Delegate)); + assert!(!result.allows(Permission::Revoke)); +} + +#[test] +fn is_subset_of() { + let rw = perms(&[Permission::Read, Permission::Write]); + let read_only = perms(&[Permission::Read]); + + assert!(read_only.is_subset_of(&rw)); + assert!(!rw.is_subset_of(&read_only)); +} + +#[test] +fn empty_permission_set_allows_nothing() { + let empty = PermissionSet::empty(); + + assert!(!empty.allows(Permission::Read)); + assert!(!empty.allows(Permission::Write)); + assert!(!empty.allows(Permission::Execute)); + assert!(!empty.allows(Permission::Delegate)); + assert!(!empty.allows(Permission::Revoke)); +} + +#[test] +fn capability_preserves_resource_and_permissions() { + let permissions = [Permission::Read, Permission::Write]; + let expected_permissions = perms(&permissions); + let cap = memory_cap(42, &permissions); + + assert_eq!(cap.permissions(), &expected_permissions); + assert_eq!(cap.generation(), &Generation::initial()); +} diff --git a/crates/domain/src/endpoint/mod.rs b/crates/domain/src/endpoint/mod.rs new file mode 100644 index 0000000..438d51d --- /dev/null +++ b/crates/domain/src/endpoint/mod.rs @@ -0,0 +1,78 @@ +use types::{ + bounded::CapSlot, + generation::Generation, + id::{EndpointId, ProcessId, ThreadId}, +}; + +use crate::{endpoint::wait_queue::WaitQueue, error::KernelError, message::Message}; + +pub mod wait_queue; + +#[cfg(test)] +#[path = "tests.rs"] +mod tests; + +pub struct Endpoint { + id: EndpointId, + waiting_senders: WaitQueue, + waiting_receivers: WaitQueue, + generation: Generation, +} + +impl Endpoint { + pub fn new(id: EndpointId) -> Self { + Self { + id, + waiting_senders: WaitQueue::new(), + waiting_receivers: WaitQueue::new(), + generation: Generation::initial(), + } + } + + pub fn id(&self) -> &EndpointId { + &self.id + } + + pub fn generation(&self) -> &Generation { + &self.generation + } + + pub fn enqueue_sender(&mut self, thread: ThreadId) -> Option<()> { + self.waiting_senders.push(thread) + } + + pub fn dequeue_sender(&mut self) -> Option { + self.waiting_senders.pop() + } + + pub fn enqueue_receiver(&mut self, thread: ThreadId) -> Option<()> { + self.waiting_receivers.push(thread) + } + + pub fn dequeue_receiver(&mut self) -> Option { + self.waiting_receivers.pop() + } + + pub fn has_waiting_sender(&self) -> bool { + !self.waiting_senders.is_empty() + } + + pub fn has_waiting_receiver(&self) -> bool { + !self.waiting_receivers.is_empty() + } + + pub fn revoke(&mut self) { + self.generation = self.generation.next(); + } +} + +pub trait MessagePassing { + fn send( + &mut self, + sender: ProcessId, + endpoint: CapSlot, + message: Message, + ) -> Result<(), KernelError>; + + fn receive(&mut self, receiver: ProcessId, endpoint: CapSlot) -> Result; +} diff --git a/crates/domain/src/endpoint/tests.rs b/crates/domain/src/endpoint/tests.rs new file mode 100644 index 0000000..76e8261 --- /dev/null +++ b/crates/domain/src/endpoint/tests.rs @@ -0,0 +1,137 @@ +use crate::endpoint::wait_queue::MAX_WAITERS; + +use super::*; +use types::id::{EndpointId, ThreadId}; + +fn thread(id: u16) -> ThreadId { + ThreadId::from_raw(id) +} + +fn test_endpoint() -> Endpoint { + Endpoint::new(EndpointId::from_raw(1)) +} + +// --- WaitQueue tests --- + +#[test] +fn empty_queue_pop_returns_none() { + let mut queue = WaitQueue::new(); + assert!(queue.pop().is_none()); + assert!(queue.is_empty()); +} + +#[test] +fn push_then_pop_returns_same_thread() { + let mut queue = WaitQueue::new(); + queue.push(thread(1)).unwrap(); + assert_eq!(queue.pop(), Some(thread(1))); +} + +#[test] +fn pop_order_is_fifo() { + let mut queue = WaitQueue::new(); + queue.push(thread(1)).unwrap(); + queue.push(thread(2)).unwrap(); + queue.push(thread(3)).unwrap(); + + assert_eq!(queue.pop(), Some(thread(1))); + assert_eq!(queue.pop(), Some(thread(2))); + assert_eq!(queue.pop(), Some(thread(3))); + assert!(queue.pop().is_none()); +} + +#[test] +fn full_queue_rejects_push() { + let mut queue = WaitQueue::new(); + for i in 0..MAX_WAITERS { + assert!(queue.push(thread(i as u16)).is_some()); + } + assert!(queue.push(thread(999)).is_none()); +} + +#[test] +fn is_empty_after_drain() { + let mut queue = WaitQueue::new(); + queue.push(thread(1)).unwrap(); + queue.push(thread(2)).unwrap(); + queue.pop(); + queue.pop(); + assert!(queue.is_empty()); +} + +// --- Endpoint tests --- + +#[test] +fn new_endpoint_has_no_waiters() { + let ep = test_endpoint(); + assert!(!ep.has_waiting_sender()); + assert!(!ep.has_waiting_receiver()); +} + +#[test] +fn sender_waits_then_dequeues() { + let mut ep = test_endpoint(); + ep.enqueue_sender(thread(1)).unwrap(); + + assert!(ep.has_waiting_sender()); + assert_eq!(ep.dequeue_sender(), Some(thread(1))); + assert!(!ep.has_waiting_sender()); +} + +#[test] +fn receiver_waits_then_dequeues() { + let mut ep = test_endpoint(); + ep.enqueue_receiver(thread(1)).unwrap(); + + assert!(ep.has_waiting_receiver()); + assert_eq!(ep.dequeue_receiver(), Some(thread(1))); + assert!(!ep.has_waiting_receiver()); +} + +#[test] +fn senders_and_receivers_are_independent() { + let mut ep = test_endpoint(); + ep.enqueue_sender(thread(1)).unwrap(); + ep.enqueue_receiver(thread(2)).unwrap(); + + assert_eq!(ep.dequeue_sender(), Some(thread(1))); + assert_eq!(ep.dequeue_receiver(), Some(thread(2))); +} + +#[test] +fn revoke_bumps_generation() { + let mut ep = test_endpoint(); + let gen_before = *ep.generation(); + ep.revoke(); + let gen_after = *ep.generation(); + + assert!(!gen_before.is_valid_against(&gen_after)); +} + +#[test] +fn rendezvous_sender_first() { + let mut ep = test_endpoint(); + + // sender arrives first, no receiver waiting + ep.enqueue_sender(thread(1)).unwrap(); + assert!(!ep.has_waiting_receiver()); + + // receiver arrives, finds a sender + assert!(ep.has_waiting_sender()); + let sender = ep.dequeue_sender().unwrap(); + assert_eq!(sender, thread(1)); +} + +#[test] +fn rendezvous_receiver_first() { + let mut ep = test_endpoint(); + + // receiver arrives first, no sender waiting + ep.enqueue_receiver(thread(1)).unwrap(); + assert!(!ep.has_waiting_sender()); + + // sender arrives, finds a receiver + assert!(ep.has_waiting_receiver()); + let receiver = ep.dequeue_receiver().unwrap(); + assert_eq!(receiver, thread(1)); +} diff --git a/crates/domain/src/endpoint/wait_queue.rs b/crates/domain/src/endpoint/wait_queue.rs new file mode 100644 index 0000000..9caf380 --- /dev/null +++ b/crates/domain/src/endpoint/wait_queue.rs @@ -0,0 +1,35 @@ +use types::id::ThreadId; + +pub const MAX_WAITERS: usize = 16; + +pub struct WaitQueue { + entries: [Option; MAX_WAITERS], + count: u8, +} + +impl WaitQueue { + pub fn new() -> Self { + Self { + entries: [None; MAX_WAITERS], + count: 0, + } + } + + pub fn push(&mut self, thread: ThreadId) -> Option<()> { + let slot = self.entries.iter_mut().find(|s| s.is_none())?; + *slot = Some(thread); + self.count += 1; + Some(()) + } + + pub fn pop(&mut self) -> Option { + let slot = self.entries.iter_mut().find(|s| s.is_some())?; + let thread = slot.take(); + self.count -= 1; + thread + } + + pub fn is_empty(&self) -> bool { + self.count == 0 + } +} diff --git a/crates/domain/src/error.rs b/crates/domain/src/error.rs new file mode 100644 index 0000000..ed97982 --- /dev/null +++ b/crates/domain/src/error.rs @@ -0,0 +1,11 @@ +use types::{bounded::CapSlot, id::ProcessId, permission::Permission}; + +#[derive(Debug)] +pub enum KernelError { + InvalidSlot(CapSlot), + RevokedCapability(CapSlot), + PermissionDenied { slot: CapSlot, needed: Permission }, + NoFreeSlot, + InvalidProcess(ProcessId), + WouldBlock, +} diff --git a/crates/domain/src/fault.rs b/crates/domain/src/fault.rs new file mode 100644 index 0000000..30e35dd --- /dev/null +++ b/crates/domain/src/fault.rs @@ -0,0 +1,10 @@ +use types::{address::VirtualAddress, bounded::CapSlot, permission::Permission}; + +pub enum Fault { + CapabilityViolation { slot: CapSlot, needed: Permission }, + InvalidCapability { slot: CapSlot }, + RevokedCapability { slot: CapSlot }, + InvalidSyscall, + MemoryFault { address: VirtualAddress }, + IllegalInstruction, +} diff --git a/crates/domain/src/lib.rs b/crates/domain/src/lib.rs index b93cf3f..0479ac1 100644 --- a/crates/domain/src/lib.rs +++ b/crates/domain/src/lib.rs @@ -1,14 +1,12 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} +#![no_std] -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +pub mod capability; +pub mod endpoint; +pub mod error; +pub mod fault; +pub mod message; +pub mod process; +pub mod resource; +pub mod scheduler; +pub mod syscall; +pub mod thread; diff --git a/crates/domain/src/message.rs b/crates/domain/src/message.rs new file mode 100644 index 0000000..4901382 --- /dev/null +++ b/crates/domain/src/message.rs @@ -0,0 +1,9 @@ +use types::bounded::CapSlot; + +pub const MAX_MESSAGE_WORDS: usize = 4; +pub const MAX_CAP_TRANSFERS: usize = 4; + +pub struct Message { + words: [usize; MAX_MESSAGE_WORDS], + caps: [Option; MAX_CAP_TRANSFERS], +} diff --git a/crates/domain/src/process.rs b/crates/domain/src/process.rs new file mode 100644 index 0000000..54cdc4e --- /dev/null +++ b/crates/domain/src/process.rs @@ -0,0 +1,15 @@ +use types::{ + bounded::CapSlot, + id::{ProcessId, ThreadId}, +}; + +use crate::capability::CapabilityTable; + +pub const MAX_THREADS_PER_PROCESS: usize = 4; + +pub struct Process { + id: ProcessId, + capabilities: CapabilityTable, + threads: [Option; MAX_THREADS_PER_PROCESS], + fault_handler: Option, +} diff --git a/crates/domain/src/resource.rs b/crates/domain/src/resource.rs new file mode 100644 index 0000000..e825544 --- /dev/null +++ b/crates/domain/src/resource.rs @@ -0,0 +1,22 @@ +use types::{ + address::VirtualAddress, + bounded::PageCount, + generation::Generation, + id::{EndpointId, IrqId, ProcessId, RegionId}, +}; + +#[derive(Clone)] +pub enum Resource { + Memory(RegionId), + Endpoint(EndpointId), + Process(ProcessId), + Interrupt(IrqId), + ProcessCreate, +} + +pub struct MemoryRegion { + id: RegionId, + base: VirtualAddress, + size: PageCount, + generation: Generation, +} diff --git a/crates/domain/src/scheduler/mod.rs b/crates/domain/src/scheduler/mod.rs new file mode 100644 index 0000000..b89dbb6 --- /dev/null +++ b/crates/domain/src/scheduler/mod.rs @@ -0,0 +1,24 @@ +use types::{ + bounded::Priority, + id::{CoreId, ThreadId}, +}; + +pub mod per_core; +pub mod priority_queue; + +#[cfg(test)] +#[path = "tests.rs"] +mod tests; + +pub enum SchedulerDecision { + Continue, + Preempt(ThreadId), +} + +pub trait Scheduler { + fn add(&mut self, thread: ThreadId, priority: Priority, core: CoreId); + fn remove(&mut self, thread: ThreadId); + fn next(&mut self, core: CoreId) -> Option; + fn tick(&mut self, core: CoreId, current: ThreadId) -> SchedulerDecision; + fn balance(&mut self); +} diff --git a/crates/domain/src/scheduler/per_core.rs b/crates/domain/src/scheduler/per_core.rs new file mode 100644 index 0000000..2ad3d06 --- /dev/null +++ b/crates/domain/src/scheduler/per_core.rs @@ -0,0 +1,52 @@ +use super::priority_queue::PriorityQueue; +use types::bounded::Priority; +use types::id::{CoreId, ThreadId}; + +pub struct PerCoreScheduler { + core: CoreId, + queue: PriorityQueue, + current: Option, +} + +impl PerCoreScheduler { + pub fn new(core: CoreId) -> Self { + Self { + core, + queue: PriorityQueue::new(), + current: None, + } + } + + pub fn core(&self) -> CoreId { + self.core + } + + pub fn current(&self) -> Option { + self.current + } + + pub fn add(&mut self, thread: ThreadId, priority: Priority) { + self.queue.add(thread, priority); + } + + pub fn remove(&mut self, thread: ThreadId, priority: Priority) { + self.queue.remove(thread, priority); + } + + pub fn pick_next(&mut self) -> Option { + let (thread, _priority) = self.queue.next()?; + self.current = Some(thread); + Some(thread) + } + + pub fn clear_current(&mut self) { + self.current = None; + } + + pub fn len(&self) -> usize { + // for load balancing — count of ready threads + // could track this incrementally instead of computing + // but for now, bitmap popcount works + self.queue.bitmap().count_ones() as usize + } +} diff --git a/crates/domain/src/scheduler/priority_queue.rs b/crates/domain/src/scheduler/priority_queue.rs new file mode 100644 index 0000000..d80b18e --- /dev/null +++ b/crates/domain/src/scheduler/priority_queue.rs @@ -0,0 +1,108 @@ +use types::bounded::Priority; +use types::id::ThreadId; + +const NUM_PRIORITIES: usize = 32; +const MAX_THREADS_PER_PRIORITY: usize = 16; + +struct RunList { + threads: [Option; MAX_THREADS_PER_PRIORITY], + head: usize, + tail: usize, + count: u8, +} + +impl RunList { + fn new() -> Self { + Self { + threads: [None; MAX_THREADS_PER_PRIORITY], + head: 0, + tail: 0, + count: 0, + } + } + + fn push(&mut self, thread: ThreadId) -> bool { + match self.threads.iter_mut().find(|s| s.is_none()) { + Some(slot) => { + *slot = Some(thread); + self.count += 1; + true + } + None => false, + } + } + + fn pop(&mut self) -> Option { + if self.count == 0 { + return None; + } + let slot = self.threads.iter_mut().find(|s| s.is_some())?; + let thread = slot.take(); + self.count -= 1; + thread + } + + fn remove(&mut self, thread: ThreadId) -> bool { + for slot in &mut self.threads { + if *slot == Some(thread) { + *slot = None; + self.count -= 1; + return true; + } + } + false + } + + fn is_empty(&self) -> bool { + self.count == 0 + } +} + +pub struct PriorityQueue { + queues: [RunList; NUM_PRIORITIES], + bitmap: u32, +} + +impl PriorityQueue { + pub fn new() -> Self { + Self { + queues: core::array::from_fn(|_| RunList::new()), + bitmap: 0, + } + } + + pub fn bitmap(&self) -> u32 { + self.bitmap + } + + pub fn add(&mut self, thread: ThreadId, priority: Priority) { + let idx = priority.value() as usize; + self.queues[idx].push(thread); + self.bitmap |= 1 << idx; + } + + pub fn remove(&mut self, thread: ThreadId, priority: Priority) { + let idx = priority.value() as usize; + self.queues[idx].remove(thread); + if self.queues[idx].is_empty() { + self.bitmap &= !(1 << idx); + } + } + + pub fn next(&mut self) -> Option<(ThreadId, Priority)> { + if self.bitmap == 0 { + return None; + } + // highest set bit = highest priority + let idx = 31 - self.bitmap.leading_zeros() as usize; + let thread = self.queues[idx].pop()?; + if self.queues[idx].is_empty() { + self.bitmap &= !(1 << idx); + } + Some((thread, Priority::new(idx as u8).unwrap())) + } + + pub fn is_empty(&self) -> bool { + self.bitmap == 0 + } +} diff --git a/crates/domain/src/scheduler/tests.rs b/crates/domain/src/scheduler/tests.rs new file mode 100644 index 0000000..7606590 --- /dev/null +++ b/crates/domain/src/scheduler/tests.rs @@ -0,0 +1,124 @@ +use super::per_core::PerCoreScheduler; +use super::priority_queue::PriorityQueue; +use types::bounded::Priority; +use types::id::{CoreId, ThreadId}; + +fn thread(id: u16) -> ThreadId { + ThreadId::from_raw(id) +} + +fn prio(value: u8) -> Priority { + Priority::new(value).unwrap() +} + +// --- PriorityQueue tests --- + +#[test] +fn empty_queue_returns_none() { + let mut q = PriorityQueue::new(); + assert!(q.next().is_none()); + assert!(q.is_empty()); +} + +#[test] +fn single_thread() { + let mut q = PriorityQueue::new(); + q.add(thread(1), prio(10)); + + let (t, p) = q.next().unwrap(); + assert_eq!(t, thread(1)); + assert_eq!(p, prio(10)); +} + +#[test] +fn highest_priority_runs_first() { + let mut q = PriorityQueue::new(); + q.add(thread(1), prio(5)); + q.add(thread(2), prio(20)); + q.add(thread(3), prio(10)); + + let (t, _) = q.next().unwrap(); + assert_eq!(t, thread(2)); +} + +#[test] +fn same_priority_is_fifo() { + let mut q = PriorityQueue::new(); + q.add(thread(1), prio(10)); + q.add(thread(2), prio(10)); + q.add(thread(3), prio(10)); + + assert_eq!(q.next().unwrap().0, thread(1)); + assert_eq!(q.next().unwrap().0, thread(2)); + assert_eq!(q.next().unwrap().0, thread(3)); + assert!(q.next().is_none()); +} + +#[test] +fn remove_thread() { + let mut q = PriorityQueue::new(); + q.add(thread(1), prio(10)); + q.add(thread(2), prio(10)); + + q.remove(thread(1), prio(10)); + + let (t, _) = q.next().unwrap(); + assert_eq!(t, thread(2)); + assert!(q.next().is_none()); +} + +#[test] +fn remove_clears_bitmap_when_empty() { + let mut q = PriorityQueue::new(); + q.add(thread(1), prio(10)); + q.remove(thread(1), prio(10)); + + assert!(q.is_empty()); +} + +#[test] +fn drain_all_priorities() { + let mut q = PriorityQueue::new(); + q.add(thread(1), prio(0)); + q.add(thread(2), prio(15)); + q.add(thread(3), prio(31)); + + assert_eq!(q.next().unwrap().0, thread(3)); // highest first + assert_eq!(q.next().unwrap().0, thread(2)); + assert_eq!(q.next().unwrap().0, thread(1)); // lowest last + assert!(q.is_empty()); +} + +// --- PerCoreScheduler tests --- + +#[test] +fn new_core_has_no_current() { + let core = PerCoreScheduler::new(CoreId::from_raw(0)); + assert!(core.current().is_none()); +} + +#[test] +fn pick_next_sets_current() { + let mut core = PerCoreScheduler::new(CoreId::from_raw(0)); + core.add(thread(1), prio(10)); + + let picked = core.pick_next().unwrap(); + assert_eq!(picked, thread(1)); + assert_eq!(core.current(), Some(thread(1))); +} + +#[test] +fn pick_next_on_empty_returns_none() { + let mut core = PerCoreScheduler::new(CoreId::from_raw(0)); + assert!(core.pick_next().is_none()); +} + +#[test] +fn clear_current() { + let mut core = PerCoreScheduler::new(CoreId::from_raw(0)); + core.add(thread(1), prio(10)); + core.pick_next(); + + core.clear_current(); + assert!(core.current().is_none()); +} diff --git a/crates/domain/src/syscall.rs b/crates/domain/src/syscall.rs new file mode 100644 index 0000000..09b442b --- /dev/null +++ b/crates/domain/src/syscall.rs @@ -0,0 +1,64 @@ +use types::{ + address::{DataRef, EntryPoint, StackPointer, VirtualAddress}, + bounded::{CapSlot, PageCount}, + permission::PermissionSet, +}; + +use crate::message::Message; + +pub struct WaitSet { + endpoints: [Option; 4], + interrupts: [Option; 4], +} + +pub enum WakeReason { + MessageReady(CapSlot), + InterruptFired(CapSlot), +} + +pub enum Syscall { + Send { + endpoint: CapSlot, + message: Message, + }, + Receive { + endpoint: CapSlot, + }, + Delegate { + slot: CapSlot, + to_endpoint: CapSlot, + permissions: PermissionSet, + }, + WaitForInterrupt { + irq: CapSlot, + }, + AcknowledgeInterrupt { + irq: CapSlot, + }, + WaitAny { + sources: WaitSet, + }, + CreateProcess { + create_cap: CapSlot, + image: DataRef, + }, + Kill { + process: CapSlot, + }, + SpawnThread { + entry: EntryPoint, + stack: StackPointer, + }, + ExitThread, + RequestPages { + count: PageCount, + }, + ReleasePages { + addr: VirtualAddress, + count: PageCount, + }, + GetTime, + GetProcessId, + Yield, + Exit, +} diff --git a/crates/domain/src/thread.rs b/crates/domain/src/thread.rs new file mode 100644 index 0000000..22862a3 --- /dev/null +++ b/crates/domain/src/thread.rs @@ -0,0 +1,33 @@ +use types::{ + bounded::{CapSlot, Priority}, + id::{CoreId, ProcessId, ThreadId}, + time::TimeSlice, +}; + +pub struct Thread { + id: ThreadId, + process: ProcessId, + state: ThreadState, + saved_context: SavedContext, + priority: Priority, + time_slice: TimeSlice, + core_affinity: Option, +} + +pub enum ThreadState { + Ready, + Running(CoreId), + Blocked(BlockReason), + Dead, +} + +pub struct SavedContext { + registers: [usize; 32], +} + +pub enum BlockReason { + Sending(CapSlot), + Receiving(CapSlot), + WaitingForInterrupt(CapSlot), + WaitingAny, +} diff --git a/crates/platform/Cargo.toml b/crates/platform/Cargo.toml index 147ec6c..8176397 100644 --- a/crates/platform/Cargo.toml +++ b/crates/platform/Cargo.toml @@ -4,3 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] +types = { workspace = true } +domain = { workspace = true } diff --git a/crates/platform/src/lib.rs b/crates/platform/src/lib.rs index b93cf3f..78e7dd5 100644 --- a/crates/platform/src/lib.rs +++ b/crates/platform/src/lib.rs @@ -1,14 +1,23 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} +#![no_std] -#[cfg(test)] -mod tests { - use super::*; +use domain::{resource::MemoryRegion, thread::SavedContext}; +use types::{ + address::{EntryPoint, StackPointer}, + id::{CoreId, IrqId, ProcessId}, + time::{Ticks, TimerDuration}, +}; - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } +pub trait PlatformContext: Copy + Default {} + +pub trait Platform { + 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) -> !; + fn set_timer(&mut self, duration: TimerDuration); + fn current_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; + fn num_cores(&self) -> u8; } diff --git a/crates/types/src/address.rs b/crates/types/src/address.rs index 691c7f1..4cf9197 100644 --- a/crates/types/src/address.rs +++ b/crates/types/src/address.rs @@ -3,3 +3,12 @@ pub struct EntryPoint(usize); #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct StackPointer(usize); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct VirtualAddress(usize); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct DataRef { + addr: VirtualAddress, + len: usize, +} diff --git a/crates/types/src/bounded.rs b/crates/types/src/bounded.rs index 28a601a..a6fbbef 100644 --- a/crates/types/src/bounded.rs +++ b/crates/types/src/bounded.rs @@ -2,3 +2,4 @@ use crate::macros::bounded; bounded!(CapSlot, u8, 0, 63); bounded!(Priority, u8, 0, 31); +bounded!(PageCount, u16, 1, 1024); diff --git a/crates/types/src/time.rs b/crates/types/src/time.rs index b7d89d7..f1d596a 100644 --- a/crates/types/src/time.rs +++ b/crates/types/src/time.rs @@ -9,3 +9,27 @@ impl Ticks { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct TimerDuration(u32); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct TimeSlice { + remaining: u32, + quantum: u32, +} + +impl TimeSlice { + pub fn new(quantum: u32) -> Self { + Self { + remaining: quantum, + quantum, + } + } + + pub fn tick(&mut self) -> bool { + self.remaining = self.remaining.saturating_sub(1); + self.remaining == 0 + } + + pub fn reset(&mut self) { + self.remaining = self.quantum; + } +}