Skip to main content

rpi_hal/
generic_timer.rs

1//! Per-core ARM generic timer -- the architected timer built into the
2//! Cortex-A7 (BCM2836) and Cortex-A53 (BCM2837) -- and the
3//! interrupt-driven deadline scheduling it provides.
4//!
5//! Unlike the BCM System Timer ([`crate::timer`]), which is a single
6//! memory-mapped counter shared by all cores and the GPU firmware, the
7//! generic timer is *per-core* CPU state, read and armed through system
8//! registers (CP15 coprocessor registers on AArch32, the `CNT*_EL0`
9//! registers on AArch64) rather than MMIO. Each core has its own
10//! comparator and its own timer interrupt, so each core can schedule and
11//! take its own tick independently -- which is what an interrupt-driven
12//! executor (e.g. Embassy) needs and what the polling-only, single-
13//! comparator System Timer path can't give per core.
14//!
15//! This drives the *physical* timer (`CNTP`), the one accessible from the
16//! non-secure PL1/EL1 state `rt`'s boot code leaves the core in (`boot.s`
17//! drops HYP->SVC, `boot64.s` drops EL2->EL1, both non-secure). Its
18//! interrupt is the non-secure physical timer IRQ (`CNTPNSIRQ`), routed to
19//! the calling core through the BCM2836/2837 ARM-local interrupt
20//! controller -- the *per-core* controller that the legacy `crate::lic`
21//! is explicitly **not** (see that module's doc comment). Routing lives on
22//! [`GenericTimer::route_irq`](crate::generic_timer::GenericTimer::route_irq)
23//! here rather than in a separate controller
24//! type: the generic timer is (today) the only user of that controller
25//! this crate wraps, and its routing is inherently per-core, so folding it
26//! in avoids a half-built shared abstraction the crate doesn't yet need.
27//!
28//! Three independent gates all have to be open for a tick to fire, the
29//! same shape as the System Timer path:
30//!
31//! 1. the peripheral raising it --
32//!    [`arm_after_us`](crate::generic_timer::GenericTimer::arm_after_us) /
33//!    [`set_deadline`](crate::generic_timer::GenericTimer::set_deadline)
34//!    program the comparator and enable the timer;
35//! 2. the source routed to this core --
36//!    [`route_irq`](crate::generic_timer::GenericTimer::route_irq);
37//! 3. the CPU-level IRQ mask open -- [`crate::irq::enable_irq`].
38//!
39//! The counter is already free-running from boot at the firmware-
40//! programmed rate reported by
41//! [`GenericTimer::frequency`](crate::generic_timer::GenericTimer::frequency)
42//! (19.2 MHz on
43//! this hardware), so -- like the System Timer -- it needs no clock setup;
44//! this deliberately does not touch the ARM-local control/prescaler
45//! registers and relies on the firmware's default counter configuration.
46
47use core::arch::asm;
48
49/// Base of the ARM-local "Core `N` timers interrupt control" registers:
50/// core `N`'s register is at `0x4000_0040 + 4 * N`. Part of the same
51/// ARM-local peripheral block `mmu.rs` device-maps for the inter-core
52/// mailboxes (see [`crate::multicore`]).
53const LOCAL_TIMER_IRQCTL_BASE: usize = 0x4000_0040;
54
55/// `CNTPNSIRQ` (non-secure physical timer) IRQ-enable bit in a core's
56/// "timers interrupt control" register -- the physical timer this module
57/// drives, in the non-secure state boot leaves the core in.
58const CNTPNSIRQ: u32 = 1 << 1;
59
60/// `CNTP_CTL.ENABLE` (bit 0): the timer comparator is running.
61const CTL_ENABLE: u32 = 1 << 0;
62/// `CNTP_CTL.ISTATUS` (bit 2, read-only): the timer condition is met
63/// (`CNTPCT >= CNTP_CVAL`) and the interrupt is asserted. Level-sensitive:
64/// it clears only when the comparator is moved back into the future, which
65/// is why acking a fired tick means re-arming, not a write-1-to-clear (see
66/// [`GenericTimer::arm_after_us`]). `CNTP_CTL.IMASK` (bit 1) is left 0
67/// throughout -- the interrupt condition is never masked at the timer
68/// itself; gating is done at the controller and CPU level instead.
69const CTL_ISTATUS: u32 = 1 << 2;
70
71/// The per-core ARM generic (architected) timer, driving the non-secure
72/// physical timer (`CNTP`).
73///
74/// A zero-sized handle: it owns no peripheral token because the generic
75/// timer is CPU state, not an MMIO peripheral, and every method acts on
76/// *the core it is called from*. A `GenericTimer` obtained on core 0
77/// configures core 0's timer; one obtained inside a secondary core's entry
78/// point configures that core's. Re-arming from an interrupt handler (as
79/// the examples do) is exactly this pattern -- construct one and act on the
80/// core currently taking the interrupt.
81pub struct GenericTimer {
82    _private: (),
83}
84
85impl GenericTimer {
86    /// Creates a handle to the calling core's generic timer. Needs no
87    /// configuration -- the counter is already free-running from boot.
88    pub fn new() -> Self {
89        Self { _private: () }
90    }
91
92    /// The counter frequency in Hz, as programmed by the firmware
93    /// (`CNTFRQ`) -- 19.2 MHz on this hardware. Used to convert between
94    /// microseconds and counter ticks.
95    pub fn frequency(&self) -> u32 {
96        read_cntfrq()
97    }
98
99    /// The current 64-bit physical count (`CNTPCT`) -- a monotonic tick
100    /// count since the counter started, at [`frequency`](Self::frequency)
101    /// ticks per second. The architected read is atomic, so unlike the
102    /// System Timer's split `CLO`/`CHI` this needs no re-read guard.
103    pub fn now(&self) -> u64 {
104        read_cntpct()
105    }
106
107    /// Busy-waits for approximately `us` microseconds against the counter.
108    /// Independent of the System Timer peripheral, so a core that doesn't
109    /// own `SYSTMR` can still delay.
110    pub fn delay_us(&self, us: u32) {
111        self.delay_ticks((us as u64 * self.frequency() as u64) / 1_000_000);
112    }
113
114    /// Busy-waits for approximately `ms` milliseconds against the counter.
115    pub fn delay_ms(&self, ms: u32) {
116        self.delay_ticks((ms as u64 * self.frequency() as u64) / 1_000);
117    }
118
119    /// Arms the physical timer to fire `us` microseconds from now and
120    /// enables it (`CNTP_TVAL` <- `us` in ticks, `CNTP_CTL.ENABLE` <- 1).
121    ///
122    /// Also the way to *acknowledge* a fired tick: the interrupt is
123    /// level-sensitive on `CNTP_CTL.ISTATUS`, so re-arming past the current
124    /// count is what deasserts it -- there is no write-1-to-clear. Calling
125    /// this from the handler both acks the tick just taken and schedules
126    /// the next one.
127    ///
128    /// `CNTP_TVAL` is a 32-bit signed down-counter, so the interval is
129    /// bounded (~111 s at 19.2 MHz); for longer or drift-free periods use
130    /// [`set_deadline`](Self::set_deadline) against an absolute
131    /// [`now`](Self::now)-based deadline instead.
132    pub fn arm_after_us(&self, us: u32) {
133        let ticks = (us as u64 * self.frequency() as u64) / 1_000_000;
134        write_cntp_tval(ticks as u32);
135        write_cntp_ctl(CTL_ENABLE);
136    }
137
138    /// Arms the physical timer to fire when the count reaches the absolute
139    /// `deadline` (`CNTP_CVAL` <- `deadline`, in the same units as
140    /// [`now`](Self::now)) and enables it.
141    ///
142    /// This is the drift-free primitive an executor wants: advancing the
143    /// deadline by a fixed period each tick (`set_deadline(deadline +=
144    /// period)`) keeps the cadence pinned to the counter rather than to
145    /// when the handler happened to run, and the full 64-bit comparator
146    /// removes the interval bound [`arm_after_us`](Self::arm_after_us) has.
147    pub fn set_deadline(&self, deadline: u64) {
148        write_cntp_cval(deadline);
149        write_cntp_ctl(CTL_ENABLE);
150    }
151
152    /// True if the timer condition is currently met (`CNTP_CTL.ISTATUS`) --
153    /// i.e. this timer is the source asserting an interrupt. Lets a handler
154    /// tell the generic timer apart from other sources without reading the
155    /// ARM-local controller's IRQ-source register.
156    pub fn is_pending(&self) -> bool {
157        read_cntp_ctl() & CTL_ISTATUS != 0
158    }
159
160    /// Stops the timer (`CNTP_CTL.ENABLE` <- 0). The counter keeps running
161    /// -- [`now`](Self::now) is unaffected -- but no further interrupt is
162    /// raised until it is armed again.
163    pub fn disable(&self) {
164        write_cntp_ctl(0);
165    }
166
167    /// Routes this core's non-secure physical timer IRQ (`CNTPNSIRQ`) to
168    /// the core through the ARM-local interrupt controller -- the second of
169    /// the three gates (see the module doc comment). Per-core: it programs
170    /// the control register belonging to whichever core calls it.
171    pub fn route_irq(&self) {
172        modify_local_timer_ctl(|v| v | CNTPNSIRQ);
173    }
174
175    /// Masks this core's physical timer IRQ at the ARM-local interrupt
176    /// controller -- the inverse of [`route_irq`](Self::route_irq).
177    pub fn mask_irq(&self) {
178        modify_local_timer_ctl(|v| v & !CNTPNSIRQ);
179    }
180
181    /// Busy-waits until the count advances by `ticks`.
182    fn delay_ticks(&self, ticks: u64) {
183        let target = self.now() + ticks;
184        while self.now() < target {}
185    }
186}
187
188impl Default for GenericTimer {
189    /// Same as [`GenericTimer::new`].
190    fn default() -> Self {
191        Self::new()
192    }
193}
194
195impl embedded_hal::delay::DelayNs for GenericTimer {
196    /// Busy-waits `ns` nanoseconds against the counter, rounding the
197    /// tick count up so a nonzero request always waits at least one tick.
198    fn delay_ns(&mut self, ns: u32) {
199        self.delay_ticks((ns as u64 * self.frequency() as u64).div_ceil(1_000_000_000));
200    }
201
202    /// Delegates to the inherent [`GenericTimer::delay_us`].
203    fn delay_us(&mut self, us: u32) {
204        GenericTimer::delay_us(self, us);
205    }
206
207    /// Delegates to the inherent [`GenericTimer::delay_ms`].
208    fn delay_ms(&mut self, ms: u32) {
209        GenericTimer::delay_ms(self, ms);
210    }
211}
212
213/// Read-modify-writes the calling core's ARM-local "timers interrupt
214/// control" register.
215fn modify_local_timer_ctl(f: impl FnOnce(u32) -> u32) {
216    let reg = (LOCAL_TIMER_IRQCTL_BASE + 4 * crate::cpu::core_id()) as *mut u32;
217    // SAFETY: `reg` is the device-mapped ARM-local timer-IRQ control
218    // register for the calling core (mapped by `mmu.rs`; a core only ever
219    // addresses its own register, so the read-modify-write races with no
220    // other core). Callers set routing up during single-threaded init with
221    // IRQs still masked, so it also can't race this core's own handler.
222    unsafe {
223        let cur = core::ptr::read_volatile(reg);
224        core::ptr::write_volatile(reg, f(cur));
225    }
226}
227
228// ---- Architecture-specific register access -------------------------------
229//
230// The generic timer registers are CP15 coprocessor registers on AArch32
231// and `CNT*_EL0` system registers on AArch64 -- the only part of this
232// module that differs by execution state. 64-bit reads/writes are one
233// MRRC/MCRR (two 32-bit halves) on AArch32 and a single MRS/MSR on
234// AArch64. An `isb` precedes each counter read so it isn't satisfied
235// speculatively ahead of program order, which would skew a timestamp.
236
237/// Reads `CNTFRQ` (counter frequency, Hz).
238#[cfg(target_arch = "arm")]
239#[inline(always)]
240fn read_cntfrq() -> u32 {
241    let freq;
242    unsafe { asm!("mrc p15, 0, {}, c14, c0, 0", out(reg) freq) };
243    freq
244}
245
246/// Reads the 64-bit `CNTPCT` physical count.
247#[cfg(target_arch = "arm")]
248#[inline(always)]
249fn read_cntpct() -> u64 {
250    let (lo, hi): (u32, u32);
251    unsafe { asm!("isb", "mrrc p15, 0, {0}, {1}, c14", out(reg) lo, out(reg) hi) };
252    ((hi as u64) << 32) | lo as u64
253}
254
255/// Writes `CNTP_TVAL` (physical timer value, a signed down-counter).
256#[cfg(target_arch = "arm")]
257#[inline(always)]
258fn write_cntp_tval(v: u32) {
259    unsafe { asm!("mcr p15, 0, {}, c14, c2, 0", in(reg) v) };
260}
261
262/// Writes the 64-bit `CNTP_CVAL` physical compare value.
263#[cfg(target_arch = "arm")]
264#[inline(always)]
265fn write_cntp_cval(v: u64) {
266    let (lo, hi) = (v as u32, (v >> 32) as u32);
267    unsafe { asm!("mcrr p15, 2, {0}, {1}, c14", in(reg) lo, in(reg) hi) };
268}
269
270/// Writes `CNTP_CTL` (physical timer control).
271#[cfg(target_arch = "arm")]
272#[inline(always)]
273fn write_cntp_ctl(v: u32) {
274    unsafe { asm!("mcr p15, 0, {}, c14, c2, 1", in(reg) v) };
275}
276
277/// Reads `CNTP_CTL` (physical timer control).
278#[cfg(target_arch = "arm")]
279#[inline(always)]
280fn read_cntp_ctl() -> u32 {
281    let ctl;
282    unsafe { asm!("mrc p15, 0, {}, c14, c2, 1", out(reg) ctl) };
283    ctl
284}
285
286/// See the AArch32 sibling above.
287#[cfg(target_arch = "aarch64")]
288#[inline(always)]
289fn read_cntfrq() -> u32 {
290    let freq: u64;
291    unsafe { asm!("mrs {}, cntfrq_el0", out(reg) freq) };
292    freq as u32
293}
294
295/// See the AArch32 sibling above.
296#[cfg(target_arch = "aarch64")]
297#[inline(always)]
298fn read_cntpct() -> u64 {
299    let count: u64;
300    unsafe { asm!("isb", "mrs {}, cntpct_el0", out(reg) count) };
301    count
302}
303
304/// See the AArch32 sibling above.
305#[cfg(target_arch = "aarch64")]
306#[inline(always)]
307fn write_cntp_tval(v: u32) {
308    let v = v as u64;
309    unsafe { asm!("msr cntp_tval_el0, {}", in(reg) v) };
310}
311
312/// See the AArch32 sibling above.
313#[cfg(target_arch = "aarch64")]
314#[inline(always)]
315fn write_cntp_cval(v: u64) {
316    unsafe { asm!("msr cntp_cval_el0, {}", in(reg) v) };
317}
318
319/// See the AArch32 sibling above.
320#[cfg(target_arch = "aarch64")]
321#[inline(always)]
322fn write_cntp_ctl(v: u32) {
323    let v = v as u64;
324    unsafe { asm!("msr cntp_ctl_el0, {}", in(reg) v) };
325}
326
327/// See the AArch32 sibling above.
328#[cfg(target_arch = "aarch64")]
329#[inline(always)]
330fn read_cntp_ctl() -> u32 {
331    let ctl: u64;
332    unsafe { asm!("mrs {}, cntp_ctl_el0", out(reg) ctl) };
333    ctl as u32
334}