Skip to main content

strat9_kernel/arch/x86_64/
timer.rs

1//! Timer Implementation
2//!
3//! Provides timer functionality for the kernel:
4//! - PIT (Programmable Interval Timer) for legacy fallback
5//! - APIC Timer for modern systems (calibrated via PIT channel 2)
6
7use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
8use x86_64::instructions::port::Port;
9
10/// Kernel timer frequency in Hz.
11///
12/// All tick-to-time conversions must use this constant.
13/// 100 Hz → 10 ms per tick, good balance between latency and overhead.
14pub const TIMER_HZ: u64 = 100;
15
16/// Nanoseconds per timer tick (derived from TIMER_HZ).
17pub const NS_PER_TICK: u64 = 1_000_000_000 / TIMER_HZ;
18
19/// Programmable Interval Timer (PIT) constants
20const PIT_CHANNEL0_PORT: u16 = 0x40;
21const PIT_COMMAND_PORT: u16 = 0x43;
22const PIT_FREQUENCY: u32 = 1_193_182; // Hz (NTSC crystal / 12)
23
24/// Whether the APIC timer is currently active
25static APIC_TIMER_ACTIVE: AtomicBool = AtomicBool::new(false);
26/// Cached APIC ticks per 10ms from calibration (used by APs)
27static APIC_TICKS_PER_10MS: AtomicU32 = AtomicU32::new(0);
28
29/// Initialize the Programmable Interval Timer (PIT)
30///
31/// Configures PIT channel 0 to generate interrupts at the specified frequency.
32/// Used as a fallback when APIC timer calibration fails.
33pub fn init_pit(frequency_hz: u32) {
34    log::info!("========================================");
35    log::info!("PIT INITIALIZATION (fallback mode)");
36    log::info!("========================================");
37    log::info!("Target frequency: {} Hz", frequency_hz);
38    log::info!("PIT base frequency: {} Hz", PIT_FREQUENCY);
39
40    let divisor = PIT_FREQUENCY / frequency_hz;
41    log::info!("Calculated divisor: {} (0x{:04X})", divisor, divisor);
42
43    // Expected actual frequency
44    let actual_freq = PIT_FREQUENCY / divisor;
45    log::info!(
46        "Expected actual frequency: {} Hz (error: {} Hz)",
47        actual_freq,
48        if actual_freq > frequency_hz {
49            actual_freq - frequency_hz
50        } else {
51            frequency_hz - actual_freq
52        }
53    );
54
55    // Send command byte to configure PIT channel 0.
56    // 0x34 = 0b00_11_010_0:
57    //   Bits [7:6] = 00  → Channel 0
58    //   Bits [5:4] = 11  → Access mode: lobyte then hibyte
59    //   Bits [3:1] = 010 → Mode 2 (Rate Generator): one IRQ per divisor clocks,
60    //                      automatic reload, correct for periodic IRQ generation.
61    //   Bit  [0]   = 0   → Binary counting
62    //
63    // Mode 2 (Rate Generator) is preferred over Mode 3 (Square Wave, 0x36):
64    // Mode 3 halves the effective frequency when routed through IOAPIC because it
65    // triggers on the falling edge of the square wave, not every count cycle.
66    let mut cmd_port = Port::new(PIT_COMMAND_PORT);
67    unsafe {
68        cmd_port.write(0x34u8); // Channel 0, lobyte/hibyte, Mode 2 (Rate Generator)
69    }
70    log::info!(
71        "PIT command port (0x{:X}) wrote: 0x{:02X}",
72        PIT_COMMAND_PORT,
73        0x34u8
74    );
75    log::info!("  Channel: 0");
76    log::info!("  Access: low byte then high byte");
77    log::info!("  Mode: 2 (Rate Generator)");
78
79    // Send divisor (low byte, then high byte)
80    let mut ch0_port = Port::new(PIT_CHANNEL0_PORT);
81    let low_byte = (divisor & 0xFF) as u8;
82    let high_byte = ((divisor >> 8) & 0xFF) as u8;
83
84    unsafe {
85        ch0_port.write(low_byte); // Low byte
86        ch0_port.write(high_byte); // High byte
87    }
88
89    log::info!("PIT channel 0 port (0x{:X}) wrote:", PIT_CHANNEL0_PORT);
90    log::info!("  Low byte:  0x{:02X} ({})", low_byte, low_byte);
91    log::info!("  High byte: 0x{:02X} ({})", high_byte, high_byte);
92
93    log::info!("========================================");
94    log::info!("PIT INITIALIZED SUCCESSFULLY");
95    log::info!("  Frequency: {} Hz", frequency_hz);
96    log::info!("  Interval: {} ms", 1_000 / frequency_hz);
97    log::info!("========================================");
98}
99
100/// Check if the APIC timer is active
101pub fn is_apic_timer_active() -> bool {
102    APIC_TIMER_ACTIVE.load(Ordering::Relaxed)
103}
104
105/// Stop PIT channel 0 to prevent phantom interrupts.
106///
107/// Programs channel 0 in mode 0 (one-shot) with count = 0, which effectively
108/// disables further periodic interrupts from the PIT. Should be called after
109/// the APIC timer has been started.
110pub fn stop_pit() {
111    let mut cmd_port = Port::new(PIT_COMMAND_PORT);
112    let mut ch0_port = Port::new(PIT_CHANNEL0_PORT);
113    unsafe {
114        // Channel 0, lobyte/hibyte, mode 0 (one-shot), binary
115        cmd_port.write(0x30u8);
116        // Count = 0 => immediate terminal count, no further interrupts
117        ch0_port.write(0u8);
118        ch0_port.write(0u8);
119    }
120    log::debug!("PIT channel 0 stopped (one-shot mode, count=0)");
121}
122
123/// Calibrate the APIC timer using PIT channel 2 as a reference.
124///
125/// Uses PIT channel 2 in one-shot mode (~10ms) to measure how many
126/// APIC timer ticks elapse. Interrupts should be disabled during calibration.
127///
128/// Returns the number of APIC timer ticks per 10ms, or 0 on failure.
129pub fn calibrate_apic_timer() -> u32 {
130    use super::{
131        apic,
132        io::{inb, outb},
133    };
134    let saved_flags = super::save_flags_and_cli();
135
136    // Try CPUID leaf 0x15 for TSC frequency first (Linux primary method).
137    // This avoids relying on PIT channel 2 gate logic which is fragile
138    // on some emulators and hardware.
139    if let Some(tsc_khz) = super::cpuid::tsc_frequency_khz() {
140        log::info!("TSC frequency from CPUID 0x15: {} KHz", tsc_khz);
141        super::boot_timestamp::calibrate_khz(tsc_khz);
142    }
143
144    // ========================================================================
145    // DEBUG: verbose logging for timer calibration
146    // ========================================================================
147    log::info!("========================================");
148    log::info!("APIC TIMER CALIBRATION (verbose debug)");
149    log::info!("========================================");
150
151    // PIT channel 2 count for ~10ms: 1193182 / 100 = 11932
152    const PIT_10MS_COUNT: u16 = 11932;
153    const PIT_WINDOW_NS: u64 = (PIT_10MS_COUNT as u64) * 1_000_000_000 / (PIT_FREQUENCY as u64);
154    // Maximum poll iterations to prevent infinite loop
155    const MAX_POLL_ITERATIONS: u32 = 10_000_000;
156
157    log::info!("PIT frequency: {} Hz", PIT_FREQUENCY);
158    log::info!("PIT 10ms count: {}", PIT_10MS_COUNT);
159    log::info!("Target wait time: ~10ms");
160
161    // Set APIC timer to a known state before calibration:
162    // - masked one-shot (no interrupts during measurement)
163    // - divide by 16
164    // SAFETY: APIC is initialized
165    unsafe {
166        apic::write_reg(apic::REG_LVT_TIMER, apic::LVT_TIMER_MASKED);
167        apic::write_reg(apic::REG_TIMER_DIVIDE, 0x03);
168        apic::write_reg(apic::REG_TIMER_INIT, 0);
169    }
170    log::info!("APIC timer divide set to 16 (0x03)");
171
172    // ========================================================================
173    // CRITICAL TIMING SECTION : no log messages between gate-up and poll end
174    // ========================================================================
175    // The PIT channel 2 gate must be LOW while programming the counter.
176    // In mode 0, counting starts as soon as the count is loaded AND gate is
177    // HIGH. If gate is already HIGH when we load the count, the 10 ms window
178    // begins before we can start the APIC timer → measurement is wrong.
179    //
180    // Correct sequence:
181    //   1. Gate LOW  : prevent counting while we program the PIT
182    //   2. Program PIT channel 2 (mode 0, one-shot, count = PIT_10MS_COUNT)
183    //   3. Set APIC timer initial count to 0xFFFF_FFFF
184    //   4. Gate HIGH : PIT starts counting NOW, APIC is already counting
185    //   5. Poll bit 5 of port 0x61 until PIT output goes HIGH (10 ms elapsed)
186    //   6. Read APIC timer current count
187    // ========================================================================
188
189    // Step 1: Disable PIT channel 2 gate (prevent counting during setup)
190    // Port 0x61: bit 0 = gate, bit 1 = speaker enable
191    unsafe {
192        let val = inb(0x61);
193        log::info!("Port 0x61 initial value: 0x{:02X}", val);
194        outb(0x61, val & 0xFC); // Clear bit 0 (gate) and bit 1 (speaker)
195    }
196    log::info!("PIT channel 2 gate DISABLED for setup");
197
198    // Step 2: Program PIT channel 2 in mode 0 (one-shot)
199    // Command: 0xB0 = channel 2, lobyte/hibyte, mode 0, binary
200    // Writing the command sets output LOW. Count is loaded but gate is LOW
201    // so counting does NOT start yet.
202    unsafe {
203        outb(0x43, 0xB0);
204        outb(0x42, (PIT_10MS_COUNT & 0xFF) as u8); // Low byte
205        outb(0x42, ((PIT_10MS_COUNT >> 8) & 0xFF) as u8); // High byte
206    }
207    log::info!(
208        "PIT channel 2 programmed: mode 0 (one-shot), count={}",
209        PIT_10MS_COUNT
210    );
211    log::info!("  Low byte:  0x{:02X}", (PIT_10MS_COUNT & 0xFF) as u8);
212    log::info!(
213        "  High byte: 0x{:02X}",
214        ((PIT_10MS_COUNT >> 8) & 0xFF) as u8
215    );
216
217    // Step 3: Set APIC timer initial count to maximum
218    // SAFETY: APIC is initialized
219    // NOTE: No log::info! between APIC write and PIT gate : the APIC timer
220    // starts counting immediately, so any delay here inflates the calibrated
221    // value and skews the resulting periodic frequency.
222    unsafe {
223        apic::write_reg(apic::REG_TIMER_INIT, 0xFFFF_FFFF);
224    }
225
226    // Step 4: Enable PIT channel 2 gate : starts PIT counting
227    // APIC timer is already counting from step 3, so the measurement
228    // window begins precisely here.  NO LOG MESSAGES until poll completes.
229    let tsc_start = super::rdtsc();
230    unsafe {
231        let val = inb(0x61);
232        outb(0x61, (val | 0x01) & 0xFD); // Set bit 0 (gate), clear bit 1 (speaker)
233    }
234
235    // Step 5: Poll PIT channel 2 output (bit 5 of port 0x61)
236    // When the count reaches 0, bit 5 goes high
237    let mut iterations: u32 = 0;
238    loop {
239        // SAFETY: reading port 0x61 is safe
240        let status = unsafe { inb(0x61) };
241        if status & 0x20 != 0 {
242            break; // PIT output went high : 10ms elapsed
243        }
244        iterations += 1;
245        if iterations >= MAX_POLL_ITERATIONS {
246            log::warn!(
247                "APIC timer calibration: PIT poll timeout after {} iterations",
248                iterations
249            );
250            log::warn!("  This may indicate a hardware issue or incorrect PIT configuration");
251            // SAFETY: APIC is initialized
252            unsafe {
253                apic::write_reg(apic::REG_TIMER_INIT, 0);
254            }
255            super::restore_flags(saved_flags);
256            return 0;
257        }
258    }
259    let tsc_delta = super::rdtsc().wrapping_sub(tsc_start);
260
261    // Step 6: Read APIC timer current count : end of critical section
262    // SAFETY: APIC is initialized
263    let current = unsafe { apic::read_reg(apic::REG_TIMER_CURRENT) };
264    let elapsed = 0xFFFF_FFFFu32.wrapping_sub(current);
265
266    // Stop and mask the APIC timer
267    // SAFETY: APIC is initialized
268    unsafe {
269        apic::write_reg(apic::REG_LVT_TIMER, apic::LVT_TIMER_MASKED);
270        apic::write_reg(apic::REG_TIMER_INIT, 0);
271    }
272
273    // ========================================================================
274    // END CRITICAL TIMING SECTION : safe to log again
275    // ========================================================================
276    log::info!("PIT poll completed after {} iterations", iterations);
277    log::info!("APIC timer current count: 0x{:08X}", current);
278    log::info!("APIC timer elapsed ticks: {} (0x{:08X})", elapsed, elapsed);
279
280    // Validate calibration result
281    if elapsed == 0 {
282        log::error!("APIC timer calibration: ZERO ticks measured!");
283        log::error!("  This indicates a serious problem with the APIC timer");
284        super::restore_flags(saved_flags);
285        return 0;
286    }
287
288    // Check for suspicious values
289    // With div=16, ticks_10ms = APIC_bus_freq / 16 / 100.
290    // QEMU default APIC frequency is ~1 GHz → ~625,000 ticks/10ms.
291    // Real hardware with a 200 MHz bus → ~125,000 ticks/10ms.
292    // Use wide bounds to support both real hardware and emulators.
293    const MIN_EXPECTED_TICKS: u32 = 1_000; // extremely slow / throttled
294    const MAX_EXPECTED_TICKS: u32 = 5_000_000; // very fast host or low divider
295
296    if elapsed < MIN_EXPECTED_TICKS {
297        log::warn!(
298            "APIC calibration SUSPICIOUS: {} ticks/10ms is TOO LOW",
299            elapsed
300        );
301        log::warn!(
302            "  Expected range: {} - {} ticks/10ms",
303            MIN_EXPECTED_TICKS,
304            MAX_EXPECTED_TICKS
305        );
306        log::warn!("  Possible causes:");
307        log::warn!("    - APIC divide configured incorrectly");
308        log::warn!("    - PIT frequency mismatch");
309        log::warn!("    - hardware issue");
310        log::warn!("  Forcing fallback to PIT timer");
311        super::restore_flags(saved_flags);
312        return 0;
313    }
314
315    if elapsed > MAX_EXPECTED_TICKS {
316        log::warn!(
317            "APIC calibration SUSPICIOUS: {} ticks/10ms is TOO HIGH",
318            elapsed
319        );
320        log::warn!(
321            "  Expected range: {} - {} ticks/10ms",
322            MIN_EXPECTED_TICKS,
323            MAX_EXPECTED_TICKS
324        );
325        log::warn!("  Possible causes:");
326        log::warn!("    - PIT poll completed too early");
327        log::warn!("    - APIC timer running at wrong frequency");
328        log::warn!("    - hardware issue");
329        log::warn!("  Forcing fallback to PIT timer");
330        super::restore_flags(saved_flags);
331        return 0;
332    }
333
334    // Calculate estimated CPU frequency
335    // CPU_freq = elapsed_ticks * div * 100
336    let estimated_cpu_freq_mhz = (elapsed as u64) * 16 * 100 / 1_000_000;
337    log::info!(
338        "Estimated CPU frequency: {} MHz (based on APIC ticks)",
339        estimated_cpu_freq_mhz
340    );
341    super::boot_timestamp::calibrate(PIT_WINDOW_NS, tsc_delta);
342    log::info!(
343        "Measured TSC frequency: {} KHz (window={} ns, delta={})",
344        super::boot_timestamp::tsc_khz(),
345        PIT_WINDOW_NS,
346        tsc_delta
347    );
348
349    // Store calibration result
350    APIC_TICKS_PER_10MS.store(elapsed, Ordering::Release);
351
352    log::info!("========================================");
353    log::info!("APIC TIMER CALIBRATION COMPLETE");
354    log::info!("  Ticks per 10ms: {}", elapsed);
355    log::info!("  Expected frequency: ~100Hz");
356    log::info!("  Estimated CPU: {} MHz", estimated_cpu_freq_mhz);
357    log::info!("========================================");
358
359    super::restore_flags(saved_flags);
360    elapsed
361}
362
363/// Start the APIC timer in periodic mode.
364///
365/// `ticks_per_10ms` is the calibrated tick count from `calibrate_apic_timer()`.
366/// The timer fires at vector 0x20 (same as PIT timer), 100Hz.
367pub fn start_apic_timer(ticks_per_10ms: u32) {
368    use super::apic;
369
370    log::info!("========================================");
371    log::info!("APIC TIMER START");
372    log::info!("========================================");
373
374    if ticks_per_10ms == 0 {
375        log::warn!("APIC timer: cannot start with 0 ticks");
376        return;
377    }
378
379    log::info!("Ticks per 10ms: {}", ticks_per_10ms);
380    log::info!("Target frequency: 100Hz (10ms interval)");
381
382    // Ensure the LAPIC timer vector is routed in the IDT before unmasking it.
383    super::idt::register_lapic_timer_vector(apic::LVT_TIMER_VECTOR);
384
385    // SAFETY: APIC is initialized
386    unsafe {
387        // Set divide to 16 (same as calibration)
388        let divide_val = apic::read_reg(apic::REG_TIMER_DIVIDE);
389        log::info!("APIC timer divide register before: 0x{:08X}", divide_val);
390        apic::write_reg(apic::REG_TIMER_DIVIDE, 0x03);
391        let divide_val_after = apic::read_reg(apic::REG_TIMER_DIVIDE);
392        log::info!(
393            "APIC timer divide register after: 0x{:08X}",
394            divide_val_after
395        );
396
397        // Configure LVT Timer: periodic mode on dedicated LAPIC timer vector
398        let lvt_before = apic::read_reg(apic::REG_LVT_TIMER);
399        log::info!("LVT Timer register before: 0x{:08X}", lvt_before);
400
401        let lvt_config = apic::LVT_TIMER_PERIODIC | (apic::LVT_TIMER_VECTOR as u32);
402        log::info!(
403            "LVT Timer config: 0x{:08X} (periodic + vector {:#x})",
404            lvt_config,
405            apic::LVT_TIMER_VECTOR
406        );
407        apic::write_reg(apic::REG_LVT_TIMER, lvt_config);
408
409        let lvt_after = apic::read_reg(apic::REG_LVT_TIMER);
410        log::info!("LVT Timer register after: 0x{:08X}", lvt_after);
411
412        stop_pit();
413
414        // Set initial count (fires every ~10ms = 100Hz)
415        log::info!(
416            "Setting timer initial count to: {} (0x{:08X})",
417            ticks_per_10ms,
418            ticks_per_10ms
419        );
420        apic::write_reg(apic::REG_TIMER_INIT, ticks_per_10ms);
421
422        let init_verify = apic::read_reg(apic::REG_TIMER_INIT);
423        log::info!(
424            "Timer initial count verified: {} (0x{:08X})",
425            init_verify,
426            init_verify
427        );
428    }
429
430    APIC_TIMER_ACTIVE.store(true, Ordering::Relaxed);
431
432    log::info!(
433        "APIC timer: started periodic mode, vector={:#x}, count={} ({}Hz)",
434        apic::LVT_TIMER_VECTOR,
435        ticks_per_10ms,
436        TIMER_HZ,
437    );
438    log::info!("========================================");
439}
440
441/// Return the cached calibration value (ticks per 10ms).
442pub fn apic_ticks_per_10ms() -> u32 {
443    APIC_TICKS_PER_10MS.load(Ordering::Acquire)
444}
445
446/// Start the APIC timer using the cached calibration value.
447///
448/// If calibration failed (ticks=0), falls back to PIT so the system always
449/// has a working timer. Prevents freeze when APIC calibration times out.
450pub fn start_apic_timer_cached() {
451    let ticks = apic_ticks_per_10ms();
452    if ticks == 0 {
453        log::warn!("APIC timer: cached ticks=0, falling back to PIT");
454        init_pit(TIMER_HZ as u32);
455        return;
456    }
457    start_apic_timer(ticks);
458}