⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

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}