rivet_arch_cortex_m/dwt.rs
1//! DWT `CYCCNT`-based cycle counter (plan.md Phase 10).
2//!
3//! `CYCCNT` is architecturally optional (the `NOCYCCNT` bit in
4//! `DWT_CTRL` is implementation-defined) even though every real
5//! Cortex-M3/4/7 core ships it in practice. Rather than assume that,
6//! [`init`] runs a genuine probe — enable, write a known value, confirm it
7//! actually counts — and [`enabled`] records the result so
8//! [`cycle_count`] can fall back to [`systick::now_micros`] (coarser, but
9//! still monotonic, which is all `__rivet_arch_cycle_count`'s contract
10//! requires) on a core where the probe fails.
11
12use core::sync::atomic::{AtomicBool, Ordering};
13use cortex_m::peripheral::{DCB, DWT};
14
15static DWT_USABLE: AtomicBool = AtomicBool::new(false);
16
17/// Enable the DWT cycle counter and confirm it actually advances.
18/// Idempotent; safe to call once from `__rivet_arch_init`.
19pub fn init() {
20 // Raw PTR access (consistent with the rest of this crate, which never
21 // holds a `Peripherals` singleton) rather than `Peripherals::take()`.
22 // SAFETY: DCB/DWT are the statically-known ARMv7-M debug peripheral
23 // addresses, present as MMIO on every Cortex-M3/4/7 whether or not a
24 // debugger is attached; this module exclusively owns the
25 // cycle-counter subset of their registers (other bits are untouched).
26 unsafe {
27 (*DCB::PTR).demcr.modify(|w| w | (1 << 24)); // TRCENA
28 (*DWT::PTR).lar.write(0xC5AC_CE55); // unlock (no-op on cores without a lock register)
29 (*DWT::PTR).cyccnt.write(0);
30 (*DWT::PTR).ctrl.modify(|w| w | 1); // CYCCNTENA
31 }
32 // Probe: the counter must have advanced past zero after a handful of
33 // instructions. If `NOCYCCNT` is set, `ctrl`'s CYCCNTENA bit itself
34 // reads back as unwritable-to-1 on some cores; checking the counter's
35 // actual movement (rather than trusting the control bit) catches both
36 // cases with one test.
37 for _ in 0..8 {
38 cortex_m::asm::nop();
39 }
40 let advanced = DWT::cycle_count() != 0;
41 DWT_USABLE.store(advanced, Ordering::Release);
42}
43
44pub fn cycle_count() -> u64 {
45 if DWT_USABLE.load(Ordering::Acquire) {
46 return DWT::cycle_count() as u64;
47 }
48 // Fallback: microsecond-resolution but still monotonic — callers only
49 // ever take deltas (see `__rivet_arch_cycle_count`'s contract), so
50 // this degrades precision, not correctness. Only available with the
51 // `systick` feature; without either source, 0 is returned (still
52 // "monotonic", trivially — a board with neither DWT nor SysTick has
53 // no cycle-adjacent source at all to report).
54 #[cfg(feature = "systick")]
55 {
56 super::systick::now_micros()
57 }
58 #[cfg(not(feature = "systick"))]
59 {
60 0
61 }
62}