//! The Arm **generic timer**: every arm64 CPU has a counter that ticks at a //! fixed rate (`CNTFRQ_EL0` says how fast: 62.5 MHz on QEMU) and a few //! timers that compare against it. We use the EL1 **virtual** timer //! (`CNTV_*`): write "fire in N counts" to `CNTV_TVAL_EL0`, switch it on in //! `CNTV_CTL_EL0`, and it raises its per-CPU interrupt (PPI 11 = INTID 27 //! on the GIC) when the time comes. Each tick re-arms it. use core::arch::asm; use core::sync::atomic::{AtomicU64, Ordering}; static TICKS: AtomicU64 = AtomicU64::new(0); static INTERVAL: AtomicU64 = AtomicU64::new(0); /// Counts per second. pub fn frequency() -> u64 { let f: u64; unsafe { asm!("mrs {}, cntfrq_el0", out(reg) f) }; f } /// The counter right now. pub fn counter() -> u64 { let c: u64; unsafe { asm!("isb", "mrs {}, cntvct_el0", out(reg) c) }; c } fn arm(counts: u64) { unsafe { asm!("msr cntv_tval_el0, {}", "msr cntv_ctl_el0, {}", in(reg) counts, in(reg) 1u64); } } /// Tick `hz` times a second (the interrupt must be enabled at the GIC). pub fn start(hz: u64) { let interval = frequency() / hz; INTERVAL.store(interval, Ordering::Relaxed); arm(interval); } /// The timer's interrupt: count, and set the next one. pub fn on_interrupt() { TICKS.fetch_add(1, Ordering::Relaxed); arm(INTERVAL.load(Ordering::Relaxed)); } /// Ticks so far (the `arch` interface's `timer::ticks`). pub fn ticks() -> u64 { TICKS.load(Ordering::Relaxed) }