veridian_kernel/timer/mod.rs
1//! High-resolution timer management for VeridianOS.
2//!
3//! This module provides a software timer wheel that sits above the
4//! architecture-specific hardware timer layer ([`crate::arch::timer`]).
5//! It supports both one-shot and periodic timers with millisecond
6//! granularity, using a hierarchical timer wheel with 256 slots for
7//! efficient O(1) insertion and expiration.
8//!
9//! # Usage
10//!
11//! ```ignore
12//! // Initialize the timer subsystem (called once during boot)
13//! timer::init()?;
14//!
15//! // Create a one-shot timer that fires after 100ms
16//! let id = timer::create_timer(TimerMode::OneShot, 100, my_callback)?;
17//!
18//! // Cancel a timer
19//! timer::cancel_timer(id)?;
20//!
21//! // Called from the timer interrupt handler
22//! timer::timer_tick(elapsed_ms);
23//!
24//! // Query monotonic uptime
25//! let uptime = timer::get_uptime_ms();
26//! ```
27
28// Timer subsystem
29
30#![allow(dead_code)]
31
32use core::sync::atomic::{AtomicU64, Ordering};
33
34use spin::Mutex;
35
36use crate::{
37 error::{KernelError, KernelResult},
38 sync::once_lock::GlobalState,
39};
40
41/// Number of slots in the timer wheel.
42///
43/// 256 provides a good balance between memory usage and timer resolution.
44/// Timers are hashed into slots based on their expiration tick modulo this
45/// value.
46const TIMER_WHEEL_SLOTS: usize = 256;
47
48/// Maximum number of timers that can be active simultaneously.
49///
50/// This is a fixed upper bound to avoid unbounded heap allocation in the
51/// kernel. Each timer entry is small (~48 bytes), so 1024 entries use
52/// roughly 48 KiB.
53const MAX_TIMERS: usize = 1024;
54
55/// Monotonically increasing counter for assigning unique timer IDs.
56static NEXT_TIMER_ID: AtomicU64 = AtomicU64::new(1);
57
58/// Global timer wheel instance, protected by a spin mutex.
59static TIMER_WHEEL: GlobalState<Mutex<TimerWheel>> = GlobalState::new();
60
61/// Monotonic uptime counter in milliseconds, updated on each tick.
62static UPTIME_MS: AtomicU64 = AtomicU64::new(0);
63
64// ---------------------------------------------------------------------------
65// Public types
66// ---------------------------------------------------------------------------
67
68/// Unique identifier for a registered timer.
69///
70/// Wraps a `u64` value that is guaranteed unique for the lifetime of the
71/// kernel (barring counter wrap at 2^64, which is practically impossible).
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
73pub(crate) struct TimerId(pub(crate) u64);
74
75impl TimerId {
76 /// Allocate the next unique timer ID.
77 fn next() -> Self {
78 Self(NEXT_TIMER_ID.fetch_add(1, Ordering::Relaxed))
79 }
80}
81
82/// Timer firing mode.
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub(crate) enum TimerMode {
85 /// Fire once after the interval elapses, then auto-deactivate.
86 OneShot,
87 /// Fire repeatedly at the given interval until explicitly cancelled.
88 Periodic,
89}
90
91/// Type alias for timer callback functions.
92///
93/// Callbacks are plain function pointers (not closures) so they can be
94/// stored in static data without requiring `alloc`. The [`TimerId`] of the
95/// firing timer is passed so the callback can identify which timer expired.
96pub(crate) type TimerCallback = fn(TimerId);
97
98/// A single software timer entry.
99#[derive(Debug, Clone, Copy)]
100struct Timer {
101 /// Unique identifier for this timer.
102 id: TimerId,
103 /// One-shot or periodic.
104 mode: TimerMode,
105 /// Interval in milliseconds (used for periodic reload).
106 interval_ms: u64,
107 /// Milliseconds remaining until this timer fires.
108 remaining_ms: u64,
109 /// Function to call when the timer expires.
110 callback: TimerCallback,
111 /// Whether this timer is currently active.
112 active: bool,
113}
114
115// ---------------------------------------------------------------------------
116// TimerWheel
117// ---------------------------------------------------------------------------
118
119/// Hierarchical timer wheel with 256 slots.
120///
121/// Each slot holds a fixed-size array of timer entries. On each tick the
122/// wheel advances and fires any expired timers in the current slot, then
123/// decrements remaining timers in other slots.
124///
125/// This design avoids heap allocation by using a flat array of timer
126/// entries and a free-list encoded via the `active` flag.
127struct TimerWheel {
128 /// All timer entries (flat pool).
129 timers: [Option<Timer>; MAX_TIMERS],
130 /// Current wheel position (0..TIMER_WHEEL_SLOTS).
131 current_slot: usize,
132 /// Number of currently active timers.
133 active_count: usize,
134}
135
136impl TimerWheel {
137 /// Create a new, empty timer wheel.
138 fn new() -> Self {
139 // Initialize all slots to None using array init pattern
140 const NONE_TIMER: Option<Timer> = None;
141 Self {
142 timers: [NONE_TIMER; MAX_TIMERS],
143 current_slot: 0,
144 active_count: 0,
145 }
146 }
147
148 /// Register a new timer in the wheel.
149 ///
150 /// Returns the [`TimerId`] assigned to the new timer, or an error if
151 /// the maximum number of timers has been reached.
152 fn add_timer(
153 &mut self,
154 mode: TimerMode,
155 interval_ms: u64,
156 callback: TimerCallback,
157 ) -> KernelResult<TimerId> {
158 if interval_ms == 0 {
159 return Err(KernelError::InvalidArgument {
160 name: "interval_ms",
161 value: "must be > 0",
162 });
163 }
164
165 // Find a free slot in the timer pool.
166 let slot =
167 self.timers
168 .iter()
169 .position(|t| t.is_none())
170 .ok_or(KernelError::ResourceExhausted {
171 resource: "timer slots",
172 })?;
173
174 let id = TimerId::next();
175
176 self.timers[slot] = Some(Timer {
177 id,
178 mode,
179 interval_ms,
180 remaining_ms: interval_ms,
181 callback,
182 active: true,
183 });
184
185 self.active_count += 1;
186 Ok(id)
187 }
188
189 /// Cancel an active timer by its ID.
190 ///
191 /// Returns `Ok(())` if the timer was found and removed, or an error
192 /// if no timer with the given ID exists.
193 fn cancel_timer(&mut self, id: TimerId) -> KernelResult<()> {
194 for entry in self.timers.iter_mut() {
195 if let Some(timer) = entry {
196 if timer.id == id {
197 *entry = None;
198 self.active_count = self.active_count.saturating_sub(1);
199 return Ok(());
200 }
201 }
202 }
203
204 Err(KernelError::NotFound {
205 resource: "timer",
206 id: id.0,
207 })
208 }
209
210 /// Advance all timers by `elapsed_ms` milliseconds.
211 ///
212 /// Any timer whose remaining time reaches zero is fired (its callback
213 /// is invoked). One-shot timers are automatically removed after
214 /// firing; periodic timers are reloaded with their original interval.
215 fn tick(&mut self, elapsed_ms: u64) {
216 // Advance the wheel position for bookkeeping.
217 self.current_slot = (self.current_slot + elapsed_ms as usize) % TIMER_WHEEL_SLOTS;
218
219 // Collect IDs and callbacks of timers that need to fire so we can
220 // invoke callbacks outside the mutable borrow of self.timers.
221 // Use a fixed-size buffer to avoid heap allocation.
222 let mut fired: [(TimerId, TimerCallback); 64] = [(TimerId(0), noop_callback); 64];
223 let mut fired_count = 0usize;
224
225 for entry in self.timers.iter_mut() {
226 if let Some(timer) = entry {
227 if !timer.active {
228 continue;
229 }
230
231 if timer.remaining_ms <= elapsed_ms {
232 // Timer expired -- record it for firing.
233 if fired_count < fired.len() {
234 fired[fired_count] = (timer.id, timer.callback);
235 fired_count += 1;
236 }
237
238 match timer.mode {
239 TimerMode::OneShot => {
240 // Remove one-shot timers.
241 *entry = None;
242 self.active_count = self.active_count.saturating_sub(1);
243 }
244 TimerMode::Periodic => {
245 // Reload periodic timers, accounting for overshoot.
246 let overshoot = elapsed_ms.saturating_sub(timer.remaining_ms);
247 timer.remaining_ms = timer
248 .interval_ms
249 .saturating_sub(overshoot % timer.interval_ms);
250 }
251 }
252 } else {
253 timer.remaining_ms -= elapsed_ms;
254 }
255 }
256 }
257
258 // Fire callbacks after releasing the mutable borrow on timer entries.
259 for &(id, cb) in fired.iter().take(fired_count) {
260 (cb)(id);
261 }
262 }
263
264 /// Return the number of currently active (pending) timers.
265 fn pending_count(&self) -> usize {
266 self.active_count
267 }
268}
269
270/// No-op callback used as a placeholder in the fired-timers buffer.
271fn noop_callback(_id: TimerId) {}
272
273// ---------------------------------------------------------------------------
274// Public API
275// ---------------------------------------------------------------------------
276
277/// Initialize the timer subsystem.
278///
279/// Must be called once during kernel boot, after the global allocator is
280/// available (for the `GlobalState` mutex). Repeated calls return an
281/// error.
282pub(crate) fn init() -> KernelResult<()> {
283 TIMER_WHEEL
284 .init(Mutex::new(TimerWheel::new()))
285 .map_err(|_| KernelError::AlreadyExists {
286 resource: "timer wheel",
287 id: 0,
288 })
289}
290
291/// Create and register a new timer.
292///
293/// # Arguments
294/// * `mode` -- [`TimerMode::OneShot`] or [`TimerMode::Periodic`].
295/// * `interval_ms` -- Time in milliseconds until (each) expiration. Must be
296/// greater than zero.
297/// * `callback` -- Function to invoke when the timer fires.
298///
299/// # Returns
300/// The [`TimerId`] of the newly created timer.
301pub(crate) fn create_timer(
302 mode: TimerMode,
303 interval_ms: u64,
304 callback: TimerCallback,
305) -> KernelResult<TimerId> {
306 TIMER_WHEEL
307 .with_mut(|wheel| {
308 let mut wheel = wheel.lock();
309 wheel.add_timer(mode, interval_ms, callback)
310 })
311 .unwrap_or(Err(KernelError::NotInitialized { subsystem: "timer" }))
312}
313
314/// Cancel an active timer.
315///
316/// Returns `Ok(())` if the timer was found and removed, or a
317/// [`KernelError::NotFound`] if no such timer exists.
318pub(crate) fn cancel_timer(id: TimerId) -> KernelResult<()> {
319 TIMER_WHEEL
320 .with_mut(|wheel| {
321 let mut wheel = wheel.lock();
322 wheel.cancel_timer(id)
323 })
324 .unwrap_or(Err(KernelError::NotInitialized { subsystem: "timer" }))
325}
326
327/// Advance all timers by `elapsed_ms` milliseconds and fire expired ones.
328///
329/// This function should be called from the timer interrupt handler (or a
330/// periodic scheduler tick) with the number of milliseconds that have
331/// elapsed since the last call.
332pub(crate) fn timer_tick(elapsed_ms: u64) {
333 // Update monotonic uptime counter.
334 UPTIME_MS.fetch_add(elapsed_ms, Ordering::Relaxed);
335
336 TIMER_WHEEL.with_mut(|wheel| {
337 let mut wheel = wheel.lock();
338 wheel.tick(elapsed_ms);
339 });
340}
341
342/// Return the monotonic uptime in milliseconds since [`init`] was called.
343///
344/// This counter is incremented by [`timer_tick`] and is independent of
345/// wall-clock time. It will not wrap for over 584 million years at
346/// millisecond granularity.
347pub(crate) fn get_uptime_ms() -> u64 {
348 UPTIME_MS.load(Ordering::Relaxed)
349}
350
351/// Return the number of currently pending (active) timers.
352pub(crate) fn pending_timer_count() -> usize {
353 TIMER_WHEEL
354 .with(|wheel| {
355 let wheel = wheel.lock();
356 wheel.pending_count()
357 })
358 .unwrap_or(0)
359}
360
361// ---------------------------------------------------------------------------
362// Tests
363// ---------------------------------------------------------------------------
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368
369 /// Dummy callback that does nothing (used in tests).
370 fn test_callback(_id: TimerId) {}
371
372 #[test]
373 fn test_timer_wheel_add_and_cancel() {
374 let mut wheel = TimerWheel::new();
375
376 let id = wheel
377 .add_timer(TimerMode::OneShot, 100, test_callback)
378 .unwrap();
379 assert_eq!(wheel.pending_count(), 1);
380
381 wheel.cancel_timer(id).unwrap();
382 assert_eq!(wheel.pending_count(), 0);
383 }
384
385 #[test]
386 fn test_timer_wheel_cancel_nonexistent() {
387 let mut wheel = TimerWheel::new();
388 let result = wheel.cancel_timer(TimerId(999));
389 assert!(result.is_err());
390 }
391
392 #[test]
393 fn test_timer_wheel_one_shot_fires_and_removes() {
394 let mut wheel = TimerWheel::new();
395 let _id = wheel
396 .add_timer(TimerMode::OneShot, 50, test_callback)
397 .unwrap();
398 assert_eq!(wheel.pending_count(), 1);
399
400 // Tick past the expiry.
401 wheel.tick(60);
402 assert_eq!(wheel.pending_count(), 0);
403 }
404
405 #[test]
406 fn test_timer_wheel_periodic_reloads() {
407 let mut wheel = TimerWheel::new();
408 let _id = wheel
409 .add_timer(TimerMode::Periodic, 100, test_callback)
410 .unwrap();
411 assert_eq!(wheel.pending_count(), 1);
412
413 // Tick past the first expiry.
414 wheel.tick(110);
415 // Periodic timer should still be active.
416 assert_eq!(wheel.pending_count(), 1);
417 }
418
419 #[test]
420 fn test_timer_wheel_zero_interval_rejected() {
421 let mut wheel = TimerWheel::new();
422 let result = wheel.add_timer(TimerMode::OneShot, 0, test_callback);
423 assert!(result.is_err());
424 }
425
426 #[test]
427 fn test_timer_id_uniqueness() {
428 let id1 = TimerId::next();
429 let id2 = TimerId::next();
430 assert_ne!(id1, id2);
431 }
432
433 #[test]
434 fn test_uptime_counter() {
435 // Reset the counter for this test.
436 UPTIME_MS.store(0, Ordering::Relaxed);
437 assert_eq!(get_uptime_ms(), 0);
438 UPTIME_MS.fetch_add(42, Ordering::Relaxed);
439 assert_eq!(get_uptime_ms(), 42);
440 }
441}