veridian_kernel/arch/timer.rs
1//! Architecture-independent timer interface.
2//!
3//! Provides the [`PlatformTimer`] trait for architecture-agnostic timer access
4//! and free functions that delegate to the current platform's implementation.
5//!
6//! # Architecture implementations
7//!
8//! * **x86_64**: [`X86_64Timer`] -- PIT-based tick counter and RDTSC timestamp.
9//! * **AArch64**: [`AArch64Timer`] -- Generic timer counter (CNTVCT_EL0 /
10//! CNTFRQ_EL0).
11//! * **RISC-V**: [`RiscVTimer`] -- `rdtime` instruction via SBI timer.
12
13/// Platform timer abstraction trait.
14///
15/// Each architecture provides a zero-sized struct implementing this trait,
16/// exposing a common interface for tick counting, frequency queries, and
17/// hardware timer programming.
18pub trait PlatformTimer {
19 /// Read the current hardware tick/cycle counter.
20 ///
21 /// This is a monotonically increasing counter whose frequency is
22 /// given by [`ticks_per_second`](PlatformTimer::ticks_per_second).
23 fn current_ticks() -> u64;
24
25 /// Return the frequency of the tick counter in Hz.
26 ///
27 /// For x86_64 this is an approximate TSC frequency; for AArch64 the
28 /// architectural `CNTFRQ_EL0`; for RISC-V the timebase frequency.
29 fn ticks_per_second() -> u64;
30
31 /// Program the next timer interrupt to fire after `ticks` ticks from now.
32 fn set_timer(ticks: u64);
33}
34
35// ---------------------------------------------------------------------------
36// x86_64 implementation
37// ---------------------------------------------------------------------------
38
39#[cfg(target_arch = "x86_64")]
40pub struct X86_64Timer;
41
42#[cfg(target_arch = "x86_64")]
43impl PlatformTimer for X86_64Timer {
44 #[inline]
45 fn current_ticks() -> u64 {
46 // SAFETY: RDTSC is a non-privileged x86_64 instruction that reads the
47 // Time Stamp Counter. No side effects; always safe to call.
48 unsafe { core::arch::x86_64::_rdtsc() }
49 }
50
51 #[inline]
52 fn ticks_per_second() -> u64 {
53 // Approximate TSC frequency -- 2 GHz is a reasonable default for QEMU.
54 // A proper implementation would calibrate against the PIT or HPET.
55 2_000_000_000
56 }
57
58 fn set_timer(ticks: u64) {
59 // Convert ticks to PIT divisor.
60 // PIT frequency is 1.193182 MHz.
61 const PIT_FREQUENCY: u64 = 1_193_182;
62 let divisor = if ticks == 0 {
63 1u16
64 } else {
65 let d = PIT_FREQUENCY * ticks / Self::ticks_per_second();
66 if d == 0 {
67 1
68 } else if d > u16::MAX as u64 {
69 u16::MAX
70 } else {
71 d as u16
72 }
73 };
74
75 // SAFETY: I/O port writes to the 8254 PIT command (0x43) and channel 0
76 // data (0x40) registers. Standard PIT programming sequence.
77 unsafe {
78 use x86_64::instructions::port::Port;
79 let mut cmd: Port<u8> = Port::new(0x43);
80 let mut data: Port<u8> = Port::new(0x40);
81 cmd.write(0x36);
82 data.write((divisor & 0xFF) as u8);
83 data.write((divisor >> 8) as u8);
84 }
85 }
86}
87
88// ---------------------------------------------------------------------------
89// AArch64 implementation
90// ---------------------------------------------------------------------------
91
92#[cfg(target_arch = "aarch64")]
93pub struct AArch64Timer;
94
95#[cfg(target_arch = "aarch64")]
96impl PlatformTimer for AArch64Timer {
97 #[inline]
98 fn current_ticks() -> u64 {
99 let cnt: u64;
100 // SAFETY: Reading CNTVCT_EL0 (virtual timer count) is a non-privileged
101 // AArch64 operation with no side effects.
102 unsafe {
103 core::arch::asm!("mrs {}, CNTVCT_EL0", out(reg) cnt);
104 }
105 cnt
106 }
107
108 #[inline]
109 fn ticks_per_second() -> u64 {
110 let freq: u64;
111 // SAFETY: Reading CNTFRQ_EL0 returns the system counter frequency.
112 // Read-only, no side effects.
113 unsafe {
114 core::arch::asm!("mrs {}, CNTFRQ_EL0", out(reg) freq);
115 }
116 freq
117 }
118
119 fn set_timer(ticks: u64) {
120 // SAFETY: Writing CNTP_TVAL_EL0 sets the physical timer value;
121 // writing CNTP_CTL_EL0 enables the timer. Standard EL1 operations.
122 unsafe {
123 core::arch::asm!("msr CNTP_TVAL_EL0, {}", in(reg) ticks);
124 core::arch::asm!("msr CNTP_CTL_EL0, {}", in(reg) 1u64);
125 }
126 }
127}
128
129// ---------------------------------------------------------------------------
130// RISC-V implementation
131// ---------------------------------------------------------------------------
132
133#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
134pub struct RiscVTimer;
135
136#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
137impl PlatformTimer for RiscVTimer {
138 #[inline]
139 fn current_ticks() -> u64 {
140 let time: u64;
141 // SAFETY: `rdtime` reads the real-time counter CSR. Always accessible
142 // in supervisor mode, no side effects.
143 unsafe {
144 core::arch::asm!("rdtime {}", out(reg) time);
145 }
146 time
147 }
148
149 #[inline]
150 fn ticks_per_second() -> u64 {
151 // QEMU virt machine timebase frequency is 10 MHz.
152 10_000_000
153 }
154
155 fn set_timer(ticks: u64) {
156 let current = Self::current_ticks();
157 let _ = crate::arch::riscv::sbi::set_timer(current + ticks);
158 }
159}
160
161// ---------------------------------------------------------------------------
162// Free functions (delegate to the active platform)
163// ---------------------------------------------------------------------------
164
165/// Get current timer tick count (from the software tick counter maintained by
166/// each architecture's interrupt handler).
167pub fn get_ticks() -> u64 {
168 #[cfg(target_arch = "x86_64")]
169 {
170 crate::arch::x86_64::timer::get_ticks()
171 }
172
173 #[cfg(target_arch = "aarch64")]
174 {
175 crate::arch::aarch64::timer::get_ticks()
176 }
177
178 #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
179 {
180 crate::arch::riscv::timer::get_ticks()
181 }
182}
183
184/// Read the hardware timestamp counter directly.
185///
186/// Unlike [`get_ticks`], which returns a software-maintained counter
187/// incremented on each timer interrupt, this reads the raw hardware
188/// cycle/time counter for high-resolution timing.
189pub fn read_hw_timestamp() -> u64 {
190 #[cfg(target_arch = "x86_64")]
191 {
192 X86_64Timer::current_ticks()
193 }
194
195 #[cfg(target_arch = "aarch64")]
196 {
197 AArch64Timer::current_ticks()
198 }
199
200 #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
201 {
202 RiscVTimer::current_ticks()
203 }
204}
205
206/// Return the hardware timer frequency in Hz.
207pub fn hw_ticks_per_second() -> u64 {
208 #[cfg(target_arch = "x86_64")]
209 {
210 X86_64Timer::ticks_per_second()
211 }
212
213 #[cfg(target_arch = "aarch64")]
214 {
215 AArch64Timer::ticks_per_second()
216 }
217
218 #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
219 {
220 RiscVTimer::ticks_per_second()
221 }
222}
223
224/// Get the current timestamp in seconds since boot.
225///
226/// Computed by dividing the raw hardware tick counter by the
227/// platform-specific tick frequency.
228pub fn get_timestamp_secs() -> u64 {
229 let tps = hw_ticks_per_second();
230 if tps == 0 {
231 return 0;
232 }
233 read_hw_timestamp() / tps
234}
235
236/// Get the current timestamp in milliseconds since boot.
237pub fn get_timestamp_ms() -> u64 {
238 let tps = hw_ticks_per_second();
239 if tps == 0 {
240 return 0;
241 }
242 // Avoid overflow: (ticks * 1000) / tps => ticks / (tps / 1000) when tps >=
243 // 1000
244 let ticks = read_hw_timestamp();
245 if tps >= 1000 {
246 ticks / (tps / 1000)
247 } else {
248 ticks * (1000 / tps)
249 }
250}
251
252/// Program the next timer interrupt.
253pub fn set_hw_timer(ticks: u64) {
254 #[cfg(target_arch = "x86_64")]
255 {
256 X86_64Timer::set_timer(ticks);
257 }
258
259 #[cfg(target_arch = "aarch64")]
260 {
261 AArch64Timer::set_timer(ticks);
262 }
263
264 #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
265 {
266 RiscVTimer::set_timer(ticks);
267 }
268}