diff --git a/crates/kernel/src/interrupt.rs b/crates/kernel/src/interrupt.rs index 680c773..49ea814 100644 --- a/crates/kernel/src/interrupt.rs +++ b/crates/kernel/src/interrupt.rs @@ -1,15 +1,63 @@ -use domain::scheduler::SchedulerDecision; +use domain::scheduler::{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_tick(&mut self, core: CoreId) -> SchedulerDecision { + let current_tid = match self.scheduler.current(core) { + Some(tid) => tid, + None => return SchedulerDecision::Continue, + }; + + let thread = match self.threads.get_mut(current_tid) { + Ok(t) => t, + Err(_) => return SchedulerDecision::Continue, + }; + + let expired = thread.time_slice_mut().tick(); + if expired { + thread.time_slice_mut().reset(); + let priority = thread.priority(); + if thread.preempt().is_err() { + return SchedulerDecision::Continue; + } + self.scheduler.add(current_tid, priority, core); + match self.scheduler.next(core) { + Some(next) => { + if self.threads.get_mut(next).and_then(|t| t.set_running(core)).is_ok() { + SchedulerDecision::Preempt(next) + } else { + let _ = self.threads.get_mut(current_tid).map(|t| t.set_running(core)); + SchedulerDecision::Continue + } + } + None => { + let _ = self.threads.get_mut(current_tid).map(|t| t.set_running(core)); + SchedulerDecision::Continue + } + } + } else { + self.scheduler.tick(core, current_tid) + } } - pub fn handle_interrupt(&mut self, _irq: IrqId) -> Option { - None + pub fn handle_interrupt(&mut self, irq: IrqId) -> Option { + self.platform.mask_irq(irq); + + let tid = self.irqs.owner(irq)?; + let thread = self.threads.get_mut(tid).ok()?; + + if !matches!(thread.state(), domain::thread::ThreadState::Blocked(_)) { + return None; + } + + let priority = thread.priority(); + let core = self.platform.core_id(); + let _ = thread.unblock(); + self.scheduler.add(tid, priority, core); + + Some(tid) } } diff --git a/crates/kernel/src/lib.rs b/crates/kernel/src/lib.rs index ac7539a..12e886f 100644 --- a/crates/kernel/src/lib.rs +++ b/crates/kernel/src/lib.rs @@ -152,4 +152,41 @@ impl Kernel

{ let cap = Capability::new(resource, permissions, Generation::initial()); self.processes.get_mut(to)?.capabilities_mut().insert(cap) } + + #[cfg(test)] + pub fn spawn_thread_for_test( + &mut self, + pid: ProcessId, + priority: Priority, + time_slice: TimeSlice, + ) -> ThreadId { + use domain::scheduler::Scheduler; + let tid = self.threads.spawn(pid, priority, time_slice, None).expect("spawn"); + let _ = self.processes.get_mut(pid).map(|p| p.add_thread(tid)); + let core = self.platform.core_id(); + self.scheduler.add(tid, priority, core); + tid + } + + #[cfg(test)] + pub fn register_irq_for_test(&mut self, irq: types::id::IrqId, thread: ThreadId) { + let _ = self.irqs.register(irq, thread); + } + + #[cfg(test)] + pub fn block_thread_for_test( + &mut self, + tid: ThreadId, + reason: domain::thread::BlockReason, + ) { + use domain::scheduler::Scheduler; + let core = self.platform.core_id(); + if let Ok(t) = self.threads.get_mut(tid) { + if matches!(t.state(), ThreadState::Ready) { + let _ = t.set_running(core); + } + let _ = t.block(reason); + } + self.scheduler.remove(tid); + } } diff --git a/crates/kernel/src/tests/mod.rs b/crates/kernel/src/tests/mod.rs index 2a94df6..df62df3 100644 --- a/crates/kernel/src/tests/mod.rs +++ b/crates/kernel/src/tests/mod.rs @@ -1,5 +1,6 @@ mod exit; mod simple; +mod tick; use domain::memory::page_pool::PagePool; use platform::Platform; diff --git a/crates/kernel/src/tests/tick.rs b/crates/kernel/src/tests/tick.rs new file mode 100644 index 0000000..3d9cb80 --- /dev/null +++ b/crates/kernel/src/tests/tick.rs @@ -0,0 +1,55 @@ +use super::*; +use domain::scheduler::SchedulerDecision; +use types::id::{CoreId, IrqId, ThreadId}; +use types::time::TimeSlice; + +#[test] +fn tick_continues_when_time_remaining() { + let (mut k, _) = kernel_with_process(); + let decision = k.handle_tick(CoreId::from_raw(0)); + assert!(matches!(decision, SchedulerDecision::Continue)); +} + +#[test] +fn tick_preempts_when_time_expired_and_other_thread_ready() { + let mut k = test_kernel(); + let mock = MockPlatform::new(1); + let pool = PagePool::new(PhysicalAddress::from_raw(0x8000_0000), 1024); + let mut k = Kernel::new(mock, pool); + + // Boot with quantum=2 so it expires after 2 ticks + let (pid, tid1) = k.boot(Priority::new(16).unwrap(), TimeSlice::new(2)).unwrap(); + + // Spawn a second thread so scheduler has someone to switch to + let tid2 = k.spawn_thread_for_test(pid, Priority::new(16).unwrap(), TimeSlice::new(100)); + + // First tick: time remaining + let d1 = k.handle_tick(CoreId::from_raw(0)); + assert!(matches!(d1, SchedulerDecision::Continue)); + + // Second tick: time expired, should preempt + let d2 = k.handle_tick(CoreId::from_raw(0)); + assert!(matches!(d2, SchedulerDecision::Preempt(_))); +} + +#[test] +fn interrupt_unknown_irq_returns_none() { + let (mut k, _) = kernel_with_process(); + assert!(k.handle_interrupt(IrqId::from_raw(99)).is_none()); +} + +#[test] +fn interrupt_unblocks_registered_thread() { + let (mut k, _) = kernel_with_process(); + let tid = ThreadId::from_raw(0); + let irq = IrqId::from_raw(5); + + // Register and block the thread on this IRQ + k.register_irq_for_test(irq, tid); + k.block_thread_for_test(tid, domain::thread::BlockReason::WaitingForInterrupt( + types::bounded::CapSlot::new(0).unwrap() + )); + + let result = k.handle_interrupt(irq); + assert_eq!(result, Some(tid)); +}