Skip to main content

strat9_kernel/hardware/timer/
hpet.rs

1// HPET (High Precision Event Timer) Driver
2// Reference: HPET spec 1.0a
3//
4// Uses the kernel's ACPI subsystem (already initialized) to locate the HPET
5// table, then maps and configures the HPET MMIO registers.
6
7#![allow(dead_code)]
8
9use crate::memory::{self, phys_to_virt};
10use core::{
11    ptr,
12    sync::atomic::{AtomicBool, Ordering},
13};
14use spin::Mutex;
15
16const HPET_GENERAL_CAP_ID: usize = 0x000;
17const HPET_GENERAL_CONFIG: usize = 0x010;
18const HPET_MAIN_COUNTER: usize = 0x0F0;
19
20const HPET_CAP_NUM_TIMERS_MASK: u64 = 0x0000_0000_0000_1F00;
21const HPET_CAP_COUNTER_SIZE: u64 = 1 << 13;
22const HPET_CAP_COUNTER_CLK_PERIOD_SHIFT: u32 = 32;
23
24const HPET_CONFIG_ENABLE: u64 = 0x0000_0000_0000_0001;
25
26const GAS_ADDR_SPACE_MEMORY: u8 = 0;
27
28/// HPET MMIO register page size (minimum 1 KiB, map one full page).
29const HPET_MMIO_MAP_SIZE: u64 = 0x1000;
30
31pub struct HpetInfo {
32    base_addr: u64,
33    mmio_base: usize,
34    num_timers: u8,
35    tick_period_fs: u32,
36    tick_period_ns: u32,
37    is_64bit: bool,
38}
39
40unsafe impl Send for HpetInfo {}
41unsafe impl Sync for HpetInfo {}
42
43static HPET_INFO: Mutex<Option<HpetInfo>> = Mutex::new(None);
44static HPET_INITIALIZED: AtomicBool = AtomicBool::new(false);
45
46/// Performs the hpet read operation.
47unsafe fn hpet_read(mmio_base: usize, offset: usize) -> u64 {
48    core::ptr::read_volatile((mmio_base + offset) as *const u64)
49}
50
51/// Performs the hpet write operation.
52unsafe fn hpet_write(mmio_base: usize, offset: usize, value: u64) {
53    core::ptr::write_volatile((mmio_base + offset) as *mut u64, value);
54}
55
56/// Initialize HPET using the already-discovered ACPI table.
57pub fn init() -> Result<(), &'static str> {
58    log::info!("[HPET] Searching for HPET...");
59
60    let hpet_acpi = crate::acpi::hpet::HpetAcpiTable::get().ok_or("HPET ACPI table not found")?;
61
62    // Read packed fields safely using unaligned loads.
63    let gas = unsafe { ptr::read_unaligned(core::ptr::addr_of!(hpet_acpi.gen_addr_struct)) };
64    let base_addr = gas.phys_addr;
65    let address_space = gas.address_space;
66    let comparator_desc =
67        unsafe { ptr::read_unaligned(core::ptr::addr_of!(hpet_acpi.comparator_descriptor)) };
68    let min_tick =
69        unsafe { ptr::read_unaligned(core::ptr::addr_of!(hpet_acpi.min_periodic_clock_tick)) };
70
71    log::info!(
72        "[HPET] Found HPET: base=0x{:x}, comparators={}, min_tick={}",
73        base_addr,
74        (comparator_desc >> 3) & 0x1F,
75        min_tick,
76    );
77
78    if address_space != GAS_ADDR_SPACE_MEMORY {
79        return Err("HPET: address space is not memory-mapped");
80    }
81    if base_addr == 0 || (base_addr & 0x7) != 0 {
82        return Err("HPET: base address invalid or misaligned");
83    }
84
85    // Map the HPET MMIO page(s) into the HHDM before any register access.
86    memory::paging::ensure_identity_map_range(base_addr, HPET_MMIO_MAP_SIZE);
87
88    let mmio_base = phys_to_virt(base_addr) as usize;
89
90    unsafe {
91        let cap_id = hpet_read(mmio_base, HPET_GENERAL_CAP_ID);
92        let num_timers = ((cap_id & HPET_CAP_NUM_TIMERS_MASK) >> 8) as u8 + 1;
93        let is_64bit = (cap_id & HPET_CAP_COUNTER_SIZE) != 0;
94        let tick_period_fs = (cap_id >> HPET_CAP_COUNTER_CLK_PERIOD_SHIFT) as u32;
95        if tick_period_fs == 0 {
96            return Err("HPET counter period is zero");
97        }
98        if tick_period_fs > 1_000_000_000 {
99            return Err("HPET counter period out of range");
100        }
101        let tick_period_ns = core::cmp::max(1, tick_period_fs / 1_000_000);
102
103        let info = HpetInfo {
104            base_addr,
105            mmio_base,
106            num_timers,
107            tick_period_fs,
108            tick_period_ns,
109            is_64bit,
110        };
111
112        *HPET_INFO.lock() = Some(info);
113
114        let mut config = hpet_read(mmio_base, HPET_GENERAL_CONFIG);
115        config &= !0x02;
116        config |= HPET_CONFIG_ENABLE;
117        hpet_write(mmio_base, HPET_GENERAL_CONFIG, config);
118
119        HPET_INITIALIZED.store(true, Ordering::SeqCst);
120
121        log::info!(
122            "[HPET] Initialized: {} timers, {}-bit, {} ns/tick",
123            num_timers,
124            if is_64bit { 64 } else { 32 },
125            tick_period_ns,
126        );
127    }
128
129    Ok(())
130}
131
132/// Read main counter value
133pub fn read_counter() -> u64 {
134    let info = HPET_INFO.lock();
135    match *info {
136        Some(ref hpet) => unsafe { hpet_read(hpet.mmio_base, HPET_MAIN_COUNTER) },
137        None => 0,
138    }
139}
140
141/// Get tick period in nanoseconds
142pub fn tick_period_ns() -> u32 {
143    let info = HPET_INFO.lock();
144    match *info {
145        Some(ref hpet) => hpet.tick_period_ns,
146        None => 0,
147    }
148}
149
150/// Get HPET frequency in Hz
151pub fn frequency_hz() -> u64 {
152    let info = HPET_INFO.lock();
153    match *info {
154        Some(ref hpet) if hpet.tick_period_fs > 0 => {
155            1_000_000_000_000_000u64 / hpet.tick_period_fs as u64
156        }
157        _ => 0,
158    }
159}
160
161/// Check if HPET is initialized
162pub fn is_available() -> bool {
163    HPET_INITIALIZED.load(Ordering::Relaxed)
164}
165
166/// Get number of timers
167pub fn num_timers() -> u8 {
168    let info = HPET_INFO.lock();
169    match *info {
170        Some(ref hpet) => hpet.num_timers,
171        None => 0,
172    }
173}
174
175/// High-precision delay in microseconds
176pub fn delay_us(us: u64) {
177    if !is_available() {
178        // Fallback to busy wait
179        for _ in 0..(us * 100) {
180            core::hint::spin_loop();
181        }
182        return;
183    }
184
185    let period_ns = tick_period_ns() as u64;
186    if period_ns == 0 {
187        return;
188    }
189    let start = read_counter();
190    let ticks_needed = if us == 0 {
191        0
192    } else {
193        core::cmp::max(1, (us.saturating_mul(1000)) / period_ns)
194    };
195    while read_counter().wrapping_sub(start) < ticks_needed {
196        core::hint::spin_loop();
197    }
198}
199
200/// High-precision delay in milliseconds
201pub fn delay_ms(ms: u64) {
202    delay_us(ms * 1000);
203}
204
205/// Get elapsed time since boot in milliseconds
206pub fn uptime_ms() -> u64 {
207    if !is_available() {
208        return 0;
209    }
210
211    let counter = read_counter() as u128;
212    let period_ns = tick_period_ns() as u128;
213    ((counter.saturating_mul(period_ns)) / 1_000_000) as u64
214}
215
216/// Get elapsed time since boot in seconds
217pub fn uptime_secs() -> u64 {
218    uptime_ms() / 1000
219}