diff --git a/crates/domain/src/syscall.rs b/crates/domain/src/syscall.rs index 09b442b..2e6469d 100644 --- a/crates/domain/src/syscall.rs +++ b/crates/domain/src/syscall.rs @@ -11,6 +11,28 @@ pub struct WaitSet { interrupts: [Option; 4], } +impl WaitSet { + pub fn new( + endpoints: [Option; 4], + interrupts: [Option; 4], + ) -> Self { + Self { endpoints, interrupts } + } + + pub fn endpoints(&self) -> &[Option; 4] { + &self.endpoints + } + + pub fn interrupts(&self) -> &[Option; 4] { + &self.interrupts + } + + pub fn is_empty(&self) -> bool { + self.endpoints.iter().all(|s| s.is_none()) + && self.interrupts.iter().all(|s| s.is_none()) + } +} + pub enum WakeReason { MessageReady(CapSlot), InterruptFired(CapSlot), diff --git a/crates/kernel/src/lib.rs b/crates/kernel/src/lib.rs index ac9e255..b28196a 100644 --- a/crates/kernel/src/lib.rs +++ b/crates/kernel/src/lib.rs @@ -21,6 +21,7 @@ use domain::{ process::{ProcessTable, MAX_PROCESSES}, resource::Resource, scheduler::{multi_core::MultiCoreScheduler, Scheduler}, + syscall::WaitSet, thread::{ThreadTable, ThreadState, MAX_THREADS}, }; use platform::Platform; @@ -60,6 +61,7 @@ pub struct Kernel { irqs: IrqRoutingTable, pending_messages: [Option; MAX_THREADS], pending_results: [Option; MAX_THREADS], + wait_sets: [Option; MAX_THREADS], } impl Kernel

{ @@ -77,6 +79,7 @@ impl Kernel

{ irqs: IrqRoutingTable::new(), pending_messages: [const { None }; MAX_THREADS], pending_results: [const { None }; MAX_THREADS], + wait_sets: [const { None }; MAX_THREADS], } } diff --git a/crates/kernel/src/syscall.rs b/crates/kernel/src/syscall.rs index 17324d5..f78019b 100644 --- a/crates/kernel/src/syscall.rs +++ b/crates/kernel/src/syscall.rs @@ -26,9 +26,9 @@ impl Kernel

{ } Syscall::Send { endpoint, message } => self.handle_send(tid, pid, endpoint, message), Syscall::Receive { endpoint } => self.handle_receive(tid, pid, endpoint), - _ => SyscallOutcome::Complete(SyscallResult::Error( - domain::error::KernelError::WouldBlock, - )), + Syscall::WaitForInterrupt { irq } => self.handle_wait_for_interrupt(tid, pid, irq), + Syscall::AcknowledgeInterrupt { irq } => self.handle_ack_interrupt(pid, irq), + Syscall::WaitAny { sources } => self.handle_wait_any(tid, pid, sources), } } @@ -524,4 +524,141 @@ impl Kernel

{ SyscallOutcome::BlockCaller(domain::thread::BlockReason::Receiving(endpoint_slot)) } } + + fn handle_wait_for_interrupt( + &mut self, + caller_tid: types::id::ThreadId, + caller_pid: types::id::ProcessId, + irq_slot: types::bounded::CapSlot, + ) -> SyscallOutcome { + use domain::scheduler::Scheduler; + + let irq_id = match self.check_cap( + caller_pid, + irq_slot, + types::permission::Permission::Read, + ) { + Ok(cap) => match cap.resource() { + domain::resource::Resource::Interrupt(id) => *id, + _ => return SyscallOutcome::Complete(SyscallResult::Error( + domain::error::KernelError::InvalidIrq(types::id::IrqId::from_raw(0)), + )), + }, + Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)), + }; + + let _ = self.irqs.register(irq_id, caller_tid); + + let caller = match self.threads.get_mut(caller_tid) { + Ok(t) => t, + Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)), + }; + let _ = caller.block(domain::thread::BlockReason::WaitingForInterrupt(irq_slot)); + self.scheduler.remove(caller_tid); + + SyscallOutcome::BlockCaller(domain::thread::BlockReason::WaitingForInterrupt(irq_slot)) + } + + fn handle_ack_interrupt( + &mut self, + caller_pid: types::id::ProcessId, + irq_slot: types::bounded::CapSlot, + ) -> SyscallOutcome { + let irq_id = match self.check_cap( + caller_pid, + irq_slot, + types::permission::Permission::Write, + ) { + Ok(cap) => match cap.resource() { + domain::resource::Resource::Interrupt(id) => *id, + _ => return SyscallOutcome::Complete(SyscallResult::Error( + domain::error::KernelError::InvalidIrq(types::id::IrqId::from_raw(0)), + )), + }, + Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)), + }; + + self.platform.unmask_irq(irq_id); + SyscallOutcome::Complete(SyscallResult::Ok) + } + + fn handle_wait_any( + &mut self, + caller_tid: types::id::ThreadId, + caller_pid: types::id::ProcessId, + sources: domain::syscall::WaitSet, + ) -> SyscallOutcome { + use domain::scheduler::Scheduler; + + if sources.is_empty() { + return SyscallOutcome::Complete(SyscallResult::Error( + domain::error::KernelError::WouldBlock, + )); + } + + for ep_slot in sources.endpoints() { + if let Some(slot) = ep_slot { + let ep_id = match self.check_cap( + caller_pid, + *slot, + types::permission::Permission::Read, + ) { + Ok(cap) => match cap.resource() { + domain::resource::Resource::Endpoint(id) => *id, + _ => continue, + }, + Err(_) => continue, + }; + + if let Some(ep) = self.endpoints.get_mut(ep_id) { + if ep.has_waiting_sender() { + return SyscallOutcome::Complete(SyscallResult::WakeReason( + domain::syscall::WakeReason::MessageReady(*slot), + )); + } + } + } + } + + for irq_slot in sources.interrupts() { + if let Some(slot) = irq_slot { + if let Ok(cap) = self.check_cap( + caller_pid, + *slot, + types::permission::Permission::Read, + ) { + if let domain::resource::Resource::Interrupt(irq_id) = cap.resource() { + if self.irqs.owner(*irq_id).is_some() { + return SyscallOutcome::Complete(SyscallResult::WakeReason( + domain::syscall::WakeReason::InterruptFired(*slot), + )); + } + } + } + } + } + + for ep_slot in sources.endpoints() { + if let Some(slot) = ep_slot { + if let Ok(cap) = self.check_cap(caller_pid, *slot, types::permission::Permission::Read) { + if let domain::resource::Resource::Endpoint(id) = cap.resource() { + if let Some(ep) = self.endpoints.get_mut(*id) { + let _ = ep.enqueue_receiver(caller_tid); + } + } + } + } + } + + self.wait_sets[caller_tid.raw() as usize] = Some(sources); + + let caller = match self.threads.get_mut(caller_tid) { + Ok(t) => t, + Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)), + }; + let _ = caller.block(domain::thread::BlockReason::WaitingAny); + self.scheduler.remove(caller_tid); + + SyscallOutcome::BlockCaller(domain::thread::BlockReason::WaitingAny) + } } diff --git a/crates/kernel/src/tests/ipc.rs b/crates/kernel/src/tests/ipc.rs index 70d6dea..b107ac4 100644 --- a/crates/kernel/src/tests/ipc.rs +++ b/crates/kernel/src/tests/ipc.rs @@ -1,10 +1,10 @@ use super::*; use crate::{SyscallOutcome, SyscallResult}; use domain::message::Message; -use domain::syscall::Syscall; +use domain::syscall::{Syscall, WaitSet, WakeReason}; use domain::thread::BlockReason; use types::bounded::CapSlot; -use types::id::ThreadId; +use types::id::{IrqId, ThreadId}; use types::permission::{Permission, PermissionSet}; fn setup_ipc_kernel() -> ( @@ -138,3 +138,96 @@ fn send_checks_endpoint_capability() { SyscallOutcome::Complete(SyscallResult::Error(_)) )); } + +// --- WaitForInterrupt / AcknowledgeInterrupt --- + +#[test] +fn wait_for_interrupt_blocks_caller() { + let (mut k, pid_a, _, _, _, _) = setup_ipc_kernel(); + let irq = IrqId::from_raw(7); + let irq_slot = k.grant_cap_for_test( + pid_a, + domain::resource::Resource::Interrupt(irq), + PermissionSet::empty() + .with(Permission::Read) + .with(Permission::Write), + ); + + let outcome = k.handle_syscall(Syscall::WaitForInterrupt { irq: irq_slot }); + assert!(matches!( + outcome, + SyscallOutcome::BlockCaller(BlockReason::WaitingForInterrupt(_)) + )); +} + +#[test] +fn ack_interrupt_returns_ok() { + let (mut k, pid_a, _, _, _, _) = setup_ipc_kernel(); + let irq = IrqId::from_raw(7); + let irq_slot = k.grant_cap_for_test( + pid_a, + domain::resource::Resource::Interrupt(irq), + PermissionSet::empty().with(Permission::Write), + ); + + let outcome = k.handle_syscall(Syscall::AcknowledgeInterrupt { irq: irq_slot }); + assert!(matches!( + outcome, + SyscallOutcome::Complete(SyscallResult::Ok) + )); +} + +// --- WaitAny --- + +#[test] +fn wait_any_with_ready_source_returns_immediately() { + let (mut k, pid_a, _, cap_a, cap_b, tid_b) = setup_ipc_kernel(); + let tid_a = ThreadId::from_raw(0); + + // Thread B sends a message (blocks since no receiver) + k.switch_to_thread_for_test(tid_b); + k.handle_syscall(Syscall::Send { + endpoint: cap_b, + message: Message::from_words([77, 0, 0, 0]), + }); + + // Thread A does WaitAny on the endpoint + k.switch_to_thread_for_test(tid_a); + let wait_set = WaitSet::new( + [Some(cap_a), None, None, None], + [None, None, None, None], + ); + let outcome = k.handle_syscall(Syscall::WaitAny { sources: wait_set }); + assert!(matches!( + outcome, + SyscallOutcome::Complete(SyscallResult::WakeReason(WakeReason::MessageReady(_))) + )); +} + +#[test] +fn wait_any_with_no_ready_source_blocks() { + let (mut k, pid_a, _, cap_a, _, _) = setup_ipc_kernel(); + let wait_set = WaitSet::new( + [Some(cap_a), None, None, None], + [None, None, None, None], + ); + let outcome = k.handle_syscall(Syscall::WaitAny { sources: wait_set }); + assert!(matches!( + outcome, + SyscallOutcome::BlockCaller(BlockReason::WaitingAny) + )); +} + +#[test] +fn wait_any_empty_sources_returns_error() { + let (mut k, _, _, _, _, _) = setup_ipc_kernel(); + let wait_set = WaitSet::new( + [None, None, None, None], + [None, None, None, None], + ); + let outcome = k.handle_syscall(Syscall::WaitAny { sources: wait_set }); + assert!(matches!( + outcome, + SyscallOutcome::Complete(SyscallResult::Error(_)) + )); +}