Skip to main content

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}