Skip to main content

strat9_kernel/process/
timer.rs

1//! Interval timer support (ITIMER_REAL, ITIMER_VIRTUAL, ITIMER_PROF)
2//!
3//! POSIX interval timers allow processes to receive periodic signals.
4//! Three types are supported:
5//! - ITIMER_REAL: Real (wall clock) time, sends SIGALRM
6//! - ITIMER_VIRTUAL: Process CPU time, sends SIGVTALRM (not yet impl)
7//! - ITIMER_PROF: Process + system CPU time, sends SIGPROF (not yet impl)
8
9use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
10
11/// Interval timer types
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[repr(u32)]
14pub enum ITimerWhich {
15    Real = 0,
16    Virtual = 1,
17    Prof = 2,
18}
19
20impl ITimerWhich {
21    /// Builds this from u32.
22    pub fn from_u32(value: u32) -> Option<Self> {
23        match value {
24            0 => Some(ITimerWhich::Real),
25            1 => Some(ITimerWhich::Virtual),
26            2 => Some(ITimerWhich::Prof),
27            _ => None,
28        }
29    }
30
31    /// Returns the signal number to send when this timer expires
32    pub fn signal(self) -> u32 {
33        match self {
34            ITimerWhich::Real => 14,    // SIGALRM
35            ITimerWhich::Virtual => 26, // SIGVTALRM
36            ITimerWhich::Prof => 27,    // SIGPROF
37        }
38    }
39}
40
41/// Interval timer specification (matches POSIX itimerval)
42#[repr(C)]
43#[derive(Debug, Clone, Copy)]
44pub struct ITimerVal {
45    /// Interval for periodic timer (0 = one-shot)
46    pub it_interval: TimeVal,
47    /// Current value (time until next expiration)
48    pub it_value: TimeVal,
49}
50
51impl ITimerVal {
52    /// Performs the zero operation.
53    pub const fn zero() -> Self {
54        Self {
55            it_interval: TimeVal::zero(),
56            it_value: TimeVal::zero(),
57        }
58    }
59
60    /// Convert to nanoseconds
61    pub fn to_nanos(&self) -> (u64, u64) {
62        (self.it_interval.to_nanos(), self.it_value.to_nanos())
63    }
64}
65
66/// Time value (seconds + microseconds)
67#[repr(C)]
68#[derive(Debug, Clone, Copy)]
69pub struct TimeVal {
70    pub tv_sec: i64,
71    pub tv_usec: i64,
72}
73
74impl TimeVal {
75    /// Performs the zero operation.
76    pub const fn zero() -> Self {
77        Self {
78            tv_sec: 0,
79            tv_usec: 0,
80        }
81    }
82
83    /// Convert to nanoseconds
84    pub fn to_nanos(&self) -> u64 {
85        (self.tv_sec as u64)
86            .saturating_mul(1_000_000_000)
87            .saturating_add((self.tv_usec as u64).saturating_mul(1_000))
88    }
89
90    /// Create from nanoseconds
91    pub fn from_nanos(nanos: u64) -> Self {
92        let tv_sec = (nanos / 1_000_000_000) as i64;
93        let tv_usec = ((nanos % 1_000_000_000) / 1_000) as i64;
94        Self { tv_sec, tv_usec }
95    }
96}
97
98/// Per-task interval timer state
99pub struct ITimerState {
100    /// Next expiration time in nanoseconds (0 = disarmed)
101    next_expiration: AtomicU64,
102    /// Interval in nanoseconds (0 = one-shot)
103    interval_ns: AtomicU64,
104    /// Whether this timer is armed
105    armed: AtomicBool,
106}
107
108impl ITimerState {
109    /// Creates a new instance.
110    pub const fn new() -> Self {
111        Self {
112            next_expiration: AtomicU64::new(0),
113            interval_ns: AtomicU64::new(0),
114            armed: AtomicBool::new(false),
115        }
116    }
117
118    /// Get current timer value (time until next expiration)
119    pub fn get(&self, current_time_ns: u64) -> ITimerVal {
120        let next = self.next_expiration.load(Ordering::Relaxed);
121        let interval = self.interval_ns.load(Ordering::Relaxed);
122
123        let value_ns = if next > current_time_ns {
124            next - current_time_ns
125        } else {
126            0
127        };
128
129        ITimerVal {
130            it_interval: TimeVal::from_nanos(interval),
131            it_value: TimeVal::from_nanos(value_ns),
132        }
133    }
134
135    /// Set timer value
136    pub fn set(&self, value: &ITimerVal, current_time_ns: u64) {
137        let (interval_ns, value_ns) = value.to_nanos();
138
139        if value_ns == 0 {
140            self.armed.store(false, Ordering::Release);
141            self.next_expiration.store(0, Ordering::Relaxed);
142            self.interval_ns.store(interval_ns, Ordering::Relaxed);
143        } else {
144            self.interval_ns.store(interval_ns, Ordering::Relaxed);
145            let next = current_time_ns.saturating_add(value_ns);
146            self.next_expiration.store(next, Ordering::Relaxed);
147            self.armed.store(true, Ordering::Release);
148        }
149    }
150
151    /// Check if timer has expired and needs to fire
152    pub fn check_expired(&self, current_time_ns: u64) -> bool {
153        if !self.armed.load(Ordering::Acquire) {
154            return false;
155        }
156
157        let next = self.next_expiration.load(Ordering::Relaxed);
158        if current_time_ns >= next && next != 0 {
159            let interval = self.interval_ns.load(Ordering::Relaxed);
160            if interval == 0 {
161                self.armed.store(false, Ordering::Release);
162                self.next_expiration.store(0, Ordering::Relaxed);
163            } else {
164                // Anchor to previous expiration to avoid drift accumulation.
165                let new_next = next.saturating_add(interval);
166                let new_next = if new_next <= current_time_ns {
167                    current_time_ns.saturating_add(interval)
168                } else {
169                    new_next
170                };
171                self.next_expiration.store(new_next, Ordering::Relaxed);
172            }
173            true
174        } else {
175            false
176        }
177    }
178
179    /// Disarm the timer
180    pub fn disarm(&self) {
181        self.armed.store(false, Ordering::Release);
182        self.next_expiration.store(0, Ordering::Relaxed);
183        self.interval_ns.store(0, Ordering::Relaxed);
184    }
185}
186
187/// Container for all three interval timers
188pub struct ITimers {
189    pub real: ITimerState,
190    pub virtual_timer: ITimerState,
191    pub prof: ITimerState,
192}
193
194impl ITimers {
195    /// Creates a new instance.
196    pub const fn new() -> Self {
197        Self {
198            real: ITimerState::new(),
199            virtual_timer: ITimerState::new(),
200            prof: ITimerState::new(),
201        }
202    }
203
204    /// Performs the get operation.
205    pub fn get(&self, which: ITimerWhich) -> &ITimerState {
206        match which {
207            ITimerWhich::Real => &self.real,
208            ITimerWhich::Virtual => &self.virtual_timer,
209            ITimerWhich::Prof => &self.prof,
210        }
211    }
212
213    /// Check all timers for expiration and send signals if necessary
214    /// Returns a list of (ITimerWhich, signal_number) pairs for expired timers
215    pub fn check_all(&self, current_time_ns: u64) -> alloc::vec::Vec<(ITimerWhich, u32)> {
216        use alloc::vec::Vec;
217        let mut expired = Vec::new();
218
219        if self.real.check_expired(current_time_ns) {
220            expired.push((ITimerWhich::Real, ITimerWhich::Real.signal()));
221        }
222        if self.virtual_timer.check_expired(current_time_ns) {
223            expired.push((ITimerWhich::Virtual, ITimerWhich::Virtual.signal()));
224        }
225        if self.prof.check_expired(current_time_ns) {
226            expired.push((ITimerWhich::Prof, ITimerWhich::Prof.signal()));
227        }
228
229        expired
230    }
231}
232
233/// Global timer tick function to be called from timer interrupt handler.
234/// Checks all tasks for expired interval timers and delivers signals.
235///
236/// Zero-allocation: iterates tasks directly under the scheduler try_lock
237/// and sets pending signals via atomic ops. This avoids Vec/heap allocation
238/// in interrupt context which would deadlock against the buddy allocator.
239pub fn tick_all_timers(current_time_ns: u64) {
240    use crate::process::{scheduler::SCHEDULER, signal::Signal};
241
242    let scheduler = match SCHEDULER.try_lock() {
243        Some(guard) => guard,
244        None => return,
245    };
246    let Some(ref sched) = *scheduler else { return };
247
248    for (_, task) in sched.all_tasks.iter() {
249        for which in [ITimerWhich::Real, ITimerWhich::Virtual, ITimerWhich::Prof] {
250            if task.itimers.get(which).check_expired(current_time_ns) {
251                if let Some(sig) = Signal::from_u32(which.signal()) {
252                    task.pending_signals.add(sig);
253                }
254            }
255        }
256    }
257}