RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / arch / aarch64 / timer.rs
1493 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//! 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)
}