Skip to main content

someboot/
timer.rs

1use core::time::Duration;
2
3use crate::ArchTrait;
4
5const NANOS_PER_SEC: u64 = 1_000_000_000;
6
7/// Hardware counter contract exposed to the platform scheduler clock.
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub enum CounterStability {
10    /// Every runtime CPU observes one synchronized system counter.
11    Stable,
12    /// The counter is CPU-local and requires per-CPU correction.
13    Unstable,
14}
15
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17#[repr(u8)]
18pub enum ArchTimerMode {
19    El1Phys    = 0,
20    El1Virt    = 1,
21    El2HypPhys = 2,
22}
23
24impl ArchTimerMode {
25    pub const fn from_raw(raw: u8) -> Self {
26        match raw {
27            1 => Self::El1Virt,
28            2 => Self::El2HypPhys,
29            _ => Self::El1Phys,
30        }
31    }
32}
33
34static mut ARCH_TIMER_MODE: u8 = ArchTimerMode::El1Phys as u8;
35
36pub const fn select_aarch64_timer_mode(kernel_in_el2: bool, el2_available: bool) -> ArchTimerMode {
37    if kernel_in_el2 {
38        ArchTimerMode::El2HypPhys
39    } else if el2_available {
40        ArchTimerMode::El1Phys
41    } else {
42        ArchTimerMode::El1Virt
43    }
44}
45
46pub const fn aarch64_timer_irq_index(mode: ArchTimerMode) -> usize {
47    match mode {
48        ArchTimerMode::El1Phys => 1,
49        ArchTimerMode::El1Virt => 2,
50        ArchTimerMode::El2HypPhys => 3,
51    }
52}
53
54pub fn set_aarch64_timer_mode(mode: ArchTimerMode) {
55    // Written once by the primary CPU during early boot before secondary CPUs run.
56    unsafe { ARCH_TIMER_MODE = mode as u8 };
57}
58
59pub fn aarch64_timer_mode() -> ArchTimerMode {
60    // After early boot this mode is read-only platform state.
61    unsafe { ArchTimerMode::from_raw(ARCH_TIMER_MODE) }
62}
63
64#[cfg(any(target_arch = "aarch64", test))]
65pub(crate) mod aarch64_deadline {
66    /// Converts a relative timer interval into an absolute counter compare value.
67    ///
68    /// Architectural counters and compare registers wrap together, so this must
69    /// use wrapping rather than saturating arithmetic.
70    pub(crate) const fn from_interval(current_ticks: u64, interval_ticks: u64) -> u64 {
71        current_ticks.wrapping_add(interval_ticks)
72    }
73
74    #[cfg(any(not(feature = "hv"), test))]
75    pub(crate) mod el1 {
76        use super::{super::ArchTimerMode, from_interval};
77
78        pub(crate) trait TimerRegisters {
79            fn read_virtual_counter(&self) -> u64;
80            fn read_physical_counter(&self) -> u64;
81            fn write_virtual_compare(&self, deadline: u64);
82            fn write_physical_compare(&self, deadline: u64);
83        }
84
85        pub(crate) fn program(
86            registers: &impl TimerRegisters,
87            mode: ArchTimerMode,
88            interval_ticks: u64,
89        ) {
90            match mode {
91                ArchTimerMode::El1Virt => registers.write_virtual_compare(from_interval(
92                    registers.read_virtual_counter(),
93                    interval_ticks,
94                )),
95                ArchTimerMode::El1Phys | ArchTimerMode::El2HypPhys => registers
96                    .write_physical_compare(from_interval(
97                        registers.read_physical_counter(),
98                        interval_ticks,
99                    )),
100            }
101        }
102    }
103
104    #[cfg(any(feature = "hv", test))]
105    pub(crate) mod el2 {
106        use super::from_interval;
107
108        pub(crate) trait TimerRegisters {
109            fn read_physical_counter(&self) -> u64;
110            fn write_hyp_physical_compare(&self, deadline: u64);
111        }
112
113        pub(crate) fn program(registers: &impl TimerRegisters, interval_ticks: u64) {
114            registers.write_hyp_physical_compare(from_interval(
115                registers.read_physical_counter(),
116                interval_ticks,
117            ));
118        }
119    }
120}
121
122#[cfg(any(target_arch = "riscv64", test))]
123pub(crate) mod riscv64_interval {
124    /// Converts an SBI relative interval into an absolute timer deadline.
125    pub(crate) fn absolute_deadline(current_ticks: u64, interval_ticks: u64) -> u64 {
126        let interval_ticks = if interval_ticks == 0 {
127            1
128        } else {
129            interval_ticks
130        };
131        current_ticks.saturating_add(interval_ticks)
132    }
133}
134
135#[cfg(any(target_arch = "loongarch64", test))]
136pub(crate) mod loongarch64_interval {
137    const ALIGNMENT: usize = 4;
138    const MIN_TICKS: usize = 4;
139
140    /// Converts a relative interval to the bounded 4-tick value encoded by TCFG.
141    pub(crate) fn aligned_ticks(interval_ticks: usize) -> usize {
142        let max_aligned = usize::MAX - usize::MAX % ALIGNMENT;
143        let clamped = interval_ticks.max(MIN_TICKS).min(max_aligned);
144        (clamped + (ALIGNMENT - 1)) & !(ALIGNMENT - 1)
145    }
146}
147
148pub fn since_boot() -> Duration {
149    elapsed()
150}
151
152/// Get the timer frequency in Hz.
153#[inline]
154pub fn freq() -> usize {
155    crate::arch::Arch::systimer_freq()
156}
157
158/// Get the current timer tick count.
159#[inline]
160pub fn ticks() -> usize {
161    crate::arch::Arch::systimer_tick()
162}
163
164/// Reports whether scheduler users may sample the raw counter on any CPU.
165#[inline]
166pub fn scheduler_clock_stability() -> CounterStability {
167    crate::arch::Arch::systimer_stability()
168}
169
170/// Convert ticks to Duration.
171#[inline]
172pub fn ticks_to_duration(ticks: usize) -> Duration {
173    let freq = freq();
174    if freq == 0 {
175        return Duration::ZERO;
176    }
177    // ticks * 1_000_000_000 / freq
178    // Use u128 to avoid overflow
179    let nanos = (ticks as u128 * NANOS_PER_SEC as u128) / freq as u128;
180    Duration::from_nanos(nanos as u64)
181}
182
183/// Convert Duration to ticks.
184#[inline]
185pub fn duration_to_ticks(duration: Duration) -> usize {
186    let freq = freq();
187    if freq == 0 {
188        return 0;
189    }
190    // duration.as_nanos() * freq / 1_000_000_000
191    // Use u128 to avoid overflow
192    let ticks = (duration.as_nanos() * freq as u128) / NANOS_PER_SEC as u128;
193    ticks as _
194}
195
196/// Get the elapsed time since boot.
197#[inline]
198pub fn elapsed() -> Duration {
199    ticks_to_duration(ticks())
200}
201
202#[cfg(test)]
203mod tests {
204    use core::cell::Cell;
205
206    use super::{
207        aarch64_deadline::{
208            self,
209            el1::{self, TimerRegisters as El1TimerRegisters},
210            el2::{self, TimerRegisters as El2TimerRegisters},
211        },
212        *,
213    };
214
215    #[test]
216    fn el2_kernel_uses_hyp_physical_timer() {
217        assert_eq!(
218            select_aarch64_timer_mode(true, true),
219            ArchTimerMode::El2HypPhys
220        );
221        assert_eq!(
222            select_aarch64_timer_mode(true, false),
223            ArchTimerMode::El2HypPhys
224        );
225    }
226
227    #[test]
228    fn el1_kernel_uses_physical_timer_when_el2_is_available() {
229        assert_eq!(
230            select_aarch64_timer_mode(false, true),
231            ArchTimerMode::El1Phys
232        );
233    }
234
235    #[test]
236    fn el1_kernel_uses_virtual_timer_when_el2_is_unavailable() {
237        assert_eq!(
238            select_aarch64_timer_mode(false, false),
239            ArchTimerMode::El1Virt
240        );
241    }
242
243    #[test]
244    fn timer_mode_maps_to_fdt_interrupt_index() {
245        assert_eq!(aarch64_timer_irq_index(ArchTimerMode::El1Phys), 1);
246        assert_eq!(aarch64_timer_irq_index(ArchTimerMode::El1Virt), 2);
247        assert_eq!(aarch64_timer_irq_index(ArchTimerMode::El2HypPhys), 3);
248    }
249
250    #[test]
251    fn compare_value_preserves_intervals_beyond_tval_width() {
252        let current = 0x1234_5678_0000_0000;
253        let interval = u32::MAX as u64 + 17;
254
255        assert_eq!(
256            aarch64_deadline::from_interval(current, interval),
257            current + interval
258        );
259        assert_eq!(aarch64_deadline::from_interval(u64::MAX - 3, 8), 4);
260    }
261
262    #[test]
263    fn riscv64_deadline_saturates_at_counter_limit() {
264        assert_eq!(
265            riscv64_interval::absolute_deadline(u64::MAX - 3, 8),
266            u64::MAX
267        );
268        assert_eq!(riscv64_interval::absolute_deadline(10, 0), 11);
269        assert_eq!(riscv64_interval::absolute_deadline(u64::MAX, 0), u64::MAX);
270    }
271
272    #[test]
273    fn loongarch64_interval_clamps_before_rounding() {
274        assert_eq!(loongarch64_interval::aligned_ticks(1), 4);
275        assert_eq!(loongarch64_interval::aligned_ticks(5), 8);
276        assert_eq!(
277            loongarch64_interval::aligned_ticks(usize::MAX),
278            usize::MAX & !3
279        );
280    }
281
282    #[test]
283    fn el1_virtual_timer_uses_virtual_counter_and_compare_register() {
284        let registers = RecordingEl1TimerRegisters::new(0x1234_5678_0000_0000, 17);
285        let interval = u32::MAX as u64 + 17;
286
287        el1::program(&registers, ArchTimerMode::El1Virt, interval);
288
289        assert_eq!(registers.virtual_compare.get(), Some(0x1234_5679_0000_0010));
290        assert_eq!(registers.physical_compare.get(), None);
291        assert_eq!(registers.virtual_counter_reads.get(), 1);
292        assert_eq!(registers.physical_counter_reads.get(), 0);
293    }
294
295    #[test]
296    fn el1_physical_timer_uses_physical_counter_and_compare_register() {
297        let registers = RecordingEl1TimerRegisters::new(17, u64::MAX - 3);
298
299        el1::program(&registers, ArchTimerMode::El1Phys, 8);
300
301        assert_eq!(registers.virtual_compare.get(), None);
302        assert_eq!(registers.physical_compare.get(), Some(4));
303        assert_eq!(registers.virtual_counter_reads.get(), 0);
304        assert_eq!(registers.physical_counter_reads.get(), 1);
305    }
306
307    #[test]
308    fn el2_hyp_timer_uses_physical_counter_and_hyp_compare_register() {
309        let registers = RecordingEl2TimerRegisters::new(u64::MAX - 3);
310
311        el2::program(&registers, 8);
312
313        assert_eq!(registers.hyp_physical_compare.get(), Some(4));
314        assert_eq!(registers.physical_counter_reads.get(), 1);
315    }
316
317    struct RecordingEl1TimerRegisters {
318        virtual_counter: u64,
319        physical_counter: u64,
320        virtual_counter_reads: Cell<usize>,
321        physical_counter_reads: Cell<usize>,
322        virtual_compare: Cell<Option<u64>>,
323        physical_compare: Cell<Option<u64>>,
324    }
325
326    impl RecordingEl1TimerRegisters {
327        fn new(virtual_counter: u64, physical_counter: u64) -> Self {
328            Self {
329                virtual_counter,
330                physical_counter,
331                virtual_counter_reads: Cell::new(0),
332                physical_counter_reads: Cell::new(0),
333                virtual_compare: Cell::new(None),
334                physical_compare: Cell::new(None),
335            }
336        }
337    }
338
339    impl El1TimerRegisters for RecordingEl1TimerRegisters {
340        fn read_virtual_counter(&self) -> u64 {
341            self.virtual_counter_reads
342                .set(self.virtual_counter_reads.get() + 1);
343            self.virtual_counter
344        }
345
346        fn read_physical_counter(&self) -> u64 {
347            self.physical_counter_reads
348                .set(self.physical_counter_reads.get() + 1);
349            self.physical_counter
350        }
351
352        fn write_virtual_compare(&self, deadline: u64) {
353            self.virtual_compare.set(Some(deadline));
354        }
355
356        fn write_physical_compare(&self, deadline: u64) {
357            self.physical_compare.set(Some(deadline));
358        }
359    }
360
361    struct RecordingEl2TimerRegisters {
362        physical_counter: u64,
363        physical_counter_reads: Cell<usize>,
364        hyp_physical_compare: Cell<Option<u64>>,
365    }
366
367    impl RecordingEl2TimerRegisters {
368        fn new(physical_counter: u64) -> Self {
369            Self {
370                physical_counter,
371                physical_counter_reads: Cell::new(0),
372                hyp_physical_compare: Cell::new(None),
373            }
374        }
375    }
376
377    impl El2TimerRegisters for RecordingEl2TimerRegisters {
378        fn read_physical_counter(&self) -> u64 {
379            self.physical_counter_reads
380                .set(self.physical_counter_reads.get() + 1);
381            self.physical_counter
382        }
383
384        fn write_hyp_physical_compare(&self, deadline: u64) {
385            self.hyp_physical_compare.set(Some(deadline));
386        }
387    }
388}