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    // ========================================================================
137    // DEBUG: Verbose logging for timer calibration
138    // ========================================================================
139    log::info!("========================================");
140    log::info!("APIC TIMER CALIBRATION (verbose debug)");
141    log::info!("========================================");
142
143    // PIT channel 2 count for ~10ms: 1193182 / 100 = 11932
144    const PIT_10MS_COUNT: u16 = 11932;
145    const PIT_WINDOW_NS: u64 = (PIT_10MS_COUNT as u64) * 1_000_000_000 / (PIT_FREQUENCY as u64);
146    // Maximum poll iterations to prevent infinite loop
147    const MAX_POLL_ITERATIONS: u32 = 10_000_000;
148
149    log::info!("PIT frequency: {} Hz", PIT_FREQUENCY);
150    log::info!("PIT 10ms count: {}", PIT_10MS_COUNT);
151    log::info!("Target wait time: ~10ms");
152
153    // Set APIC timer to a known state before calibration:
154    // - masked one-shot (no interrupts during measurement)
155    // - divide by 16
156    // SAFETY: APIC is initialized
157    unsafe {
158        apic::write_reg(apic::REG_LVT_TIMER, apic::LVT_TIMER_MASKED);
159        apic::write_reg(apic::REG_TIMER_DIVIDE, 0x03);
160        apic::write_reg(apic::REG_TIMER_INIT, 0);
161    }
162    log::info!("APIC timer divide set to 16 (0x03)");
163
164    // ========================================================================
165    // CRITICAL TIMING SECTION : no log messages between gate-up and poll end
166    // ========================================================================
167    // The PIT channel 2 gate must be LOW while programming the counter.
168    // In mode 0, counting starts as soon as the count is loaded AND gate is
169    // HIGH. If gate is already HIGH when we load the count, the 10 ms window
170    // begins before we can start the APIC timer → measurement is wrong.
171    //
172    // Correct sequence:
173    //   1. Gate LOW  : prevent counting while we program the PIT
174    //   2. Program PIT channel 2 (mode 0, one-shot, count = PIT_10MS_COUNT)
175    //   3. Set APIC timer initial count to 0xFFFF_FFFF
176    //   4. Gate HIGH : PIT starts counting NOW, APIC is already counting
177    //   5. Poll bit 5 of port 0x61 until PIT output goes HIGH (10 ms elapsed)
178    //   6. Read APIC timer current count
179    // ========================================================================
180
181    // Step 1: Disable PIT channel 2 gate (prevent counting during setup)
182    // Port 0x61: bit 0 = gate, bit 1 = speaker enable
183    unsafe {
184        let val = inb(0x61);
185        log::info!("Port 0x61 initial value: 0x{:02X}", val);
186        outb(0x61, val & 0xFC); // Clear bit 0 (gate) and bit 1 (speaker)
187    }
188    log::info!("PIT channel 2 gate DISABLED for setup");
189
190    // Step 2: Program PIT channel 2 in mode 0 (one-shot)
191    // Command: 0xB0 = channel 2, lobyte/hibyte, mode 0, binary
192    // Writing the command sets output LOW. Count is loaded but gate is LOW
193    // so counting does NOT start yet.
194    unsafe {
195        outb(0x43, 0xB0);
196        outb(0x42, (PIT_10MS_COUNT & 0xFF) as u8); // Low byte
197        outb(0x42, ((PIT_10MS_COUNT >> 8) & 0xFF) as u8); // High byte
198    }
199    log::info!(
200        "PIT channel 2 programmed: mode 0 (one-shot), count={}",
201        PIT_10MS_COUNT
202    );
203    log::info!("  Low byte:  0x{:02X}", (PIT_10MS_COUNT & 0xFF) as u8);
204    log::info!(
205        "  High byte: 0x{:02X}",
206        ((PIT_10MS_COUNT >> 8) & 0xFF) as u8
207    );
208
209    // Step 3: Set APIC timer initial count to maximum
210    // SAFETY: APIC is initialized
211    // NOTE: No log::info! between APIC write and PIT gate : the APIC timer
212    // starts counting immediately, so any delay here inflates the calibrated
213    // value and skews the resulting periodic frequency.
214    unsafe {
215        apic::write_reg(apic::REG_TIMER_INIT, 0xFFFF_FFFF);
216    }
217
218    // Step 4: Enable PIT channel 2 gate : starts PIT counting
219    // APIC timer is already counting from step 3, so the measurement
220    // window begins precisely here.  NO LOG MESSAGES until poll completes.
221    let tsc_start = super::rdtsc();
222    unsafe {
223        let val = inb(0x61);
224        outb(0x61, (val | 0x01) & 0xFD); // Set bit 0 (gate), clear bit 1 (speaker)
225    }
226
227    // Step 5: Poll PIT channel 2 output (bit 5 of port 0x61)
228    // When the count reaches 0, bit 5 goes high
229    let mut iterations: u32 = 0;
230    loop {
231        // SAFETY: reading port 0x61 is safe
232        let status = unsafe { inb(0x61) };
233        if status & 0x20 != 0 {
234            break; // PIT output went high : 10ms elapsed
235        }
236        iterations += 1;
237        if iterations >= MAX_POLL_ITERATIONS {
238            log::warn!(
239                "APIC timer calibration: PIT poll timeout after {} iterations",
240                iterations
241            );
242            log::warn!("  This may indicate a hardware issue or incorrect PIT configuration");
243            // SAFETY: APIC is initialized
244            unsafe {
245                apic::write_reg(apic::REG_TIMER_INIT, 0);
246            }
247            super::restore_flags(saved_flags);
248            return 0;
249        }
250    }
251    let tsc_delta = super::rdtsc().wrapping_sub(tsc_start);
252
253    // Step 6: Read APIC timer current count : end of critical section
254    // SAFETY: APIC is initialized
255    let current = unsafe { apic::read_reg(apic::REG_TIMER_CURRENT) };
256    let elapsed = 0xFFFF_FFFFu32.wrapping_sub(current);
257
258    // Stop and mask the APIC timer
259    // SAFETY: APIC is initialized
260    unsafe {
261        apic::write_reg(apic::REG_LVT_TIMER, apic::LVT_TIMER_MASKED);
262        apic::write_reg(apic::REG_TIMER_INIT, 0);
263    }
264
265    // ========================================================================
266    // END CRITICAL TIMING SECTION : safe to log again
267    // ========================================================================
268    log::info!("PIT poll completed after {} iterations", iterations);
269    log::info!("APIC timer current count: 0x{:08X}", current);
270    log::info!("APIC timer elapsed ticks: {} (0x{:08X})", elapsed, elapsed);
271
272    // Validate calibration result
273    if elapsed == 0 {
274        log::error!("APIC timer calibration: ZERO ticks measured!");
275        log::error!("  This indicates a serious problem with the APIC timer");
276        super::restore_flags(saved_flags);
277        return 0;
278    }
279
280    // Check for suspicious values
281    // With div=16, ticks_10ms = APIC_bus_freq / 16 / 100.
282    // QEMU default APIC frequency is ~1 GHz → ~625,000 ticks/10ms.
283    // Real hardware with a 200 MHz bus → ~125,000 ticks/10ms.
284    // Use wide bounds to support both real hardware and emulators.
285    const MIN_EXPECTED_TICKS: u32 = 1_000; // extremely slow / throttled
286    const MAX_EXPECTED_TICKS: u32 = 5_000_000; // very fast host or low divider
287
288    if elapsed < MIN_EXPECTED_TICKS {
289        log::warn!(
290            "APIC calibration SUSPICIOUS: {} ticks/10ms is TOO LOW",
291            elapsed
292        );
293        log::warn!(
294            "  Expected range: {} - {} ticks/10ms",
295            MIN_EXPECTED_TICKS,
296            MAX_EXPECTED_TICKS
297        );
298        log::warn!("  Possible causes:");
299        log::warn!("    - APIC divide configured incorrectly");
300        log::warn!("    - PIT frequency mismatch");
301        log::warn!("    - hardware issue");
302        log::warn!("  Forcing fallback to PIT timer");
303        super::restore_flags(saved_flags);
304        return 0;
305    }
306
307    if elapsed > MAX_EXPECTED_TICKS {
308        log::warn!(
309            "APIC calibration SUSPICIOUS: {} ticks/10ms is TOO HIGH",
310            elapsed
311        );
312        log::warn!(
313            "  Expected range: {} - {} ticks/10ms",
314            MIN_EXPECTED_TICKS,
315            MAX_EXPECTED_TICKS
316        );
317        log::warn!("  Possible causes:");
318        log::warn!("    - PIT poll completed too early");
319        log::warn!("    - APIC timer running at wrong frequency");
320        log::warn!("    - hardware issue");
321        log::warn!("  Forcing fallback to PIT timer");
322        super::restore_flags(saved_flags);
323        return 0;
324    }
325
326    // Calculate estimated CPU frequency
327    // CPU_freq = elapsed_ticks * div * 100
328    let estimated_cpu_freq_mhz = (elapsed as u64) * 16 * 100 / 1_000_000;
329    log::info!(
330        "Estimated CPU frequency: {} MHz (based on APIC ticks)",
331        estimated_cpu_freq_mhz
332    );
333    super::boot_timestamp::calibrate(PIT_WINDOW_NS, tsc_delta);
334    log::info!(
335        "Measured TSC frequency: {} KHz (window={} ns, delta={})",
336        super::boot_timestamp::tsc_khz(),
337        PIT_WINDOW_NS,
338        tsc_delta
339    );
340
341    // Store calibration result
342    APIC_TICKS_PER_10MS.store(elapsed, Ordering::Release);
343
344    log::info!("========================================");
345    log::info!("APIC TIMER CALIBRATION COMPLETE");
346    log::info!("  Ticks per 10ms: {}", elapsed);
347    log::info!("  Expected frequency: ~100Hz");
348    log::info!("  Estimated CPU: {} MHz", estimated_cpu_freq_mhz);
349    log::info!("========================================");
350
351    super::restore_flags(saved_flags);
352    elapsed
353}
354
355/// Start the APIC timer in periodic mode.
356///
357/// `ticks_per_10ms` is the calibrated tick count from `calibrate_apic_timer()`.
358/// The timer fires at vector 0x20 (same as PIT timer), 100Hz.
359pub fn start_apic_timer(ticks_per_10ms: u32) {
360    use super::apic;
361
362    log::info!("========================================");
363    log::info!("APIC TIMER START");
364    log::info!("========================================");
365
366    if ticks_per_10ms == 0 {
367        log::warn!("APIC timer: cannot start with 0 ticks");
368        return;
369    }
370
371    log::info!("Ticks per 10ms: {}", ticks_per_10ms);
372    log::info!("Target frequency: 100Hz (10ms interval)");
373
374    // Ensure the LAPIC timer vector is routed in the IDT before unmasking it.
375    super::idt::register_lapic_timer_vector(apic::LVT_TIMER_VECTOR);
376
377    // SAFETY: APIC is initialized
378    unsafe {
379        // Set divide to 16 (same as calibration)
380        let divide_val = apic::read_reg(apic::REG_TIMER_DIVIDE);
381        log::info!("APIC timer divide register before: 0x{:08X}", divide_val);
382        apic::write_reg(apic::REG_TIMER_DIVIDE, 0x03);
383        let divide_val_after = apic::read_reg(apic::REG_TIMER_DIVIDE);
384        log::info!(
385            "APIC timer divide register after: 0x{:08X}",
386            divide_val_after
387        );
388
389        // Configure LVT Timer: periodic mode on dedicated LAPIC timer vector
390        let lvt_before = apic::read_reg(apic::REG_LVT_TIMER);
391        log::info!("LVT Timer register before: 0x{:08X}", lvt_before);
392
393        let lvt_config = apic::LVT_TIMER_PERIODIC | (apic::LVT_TIMER_VECTOR as u32);
394        log::info!(
395            "LVT Timer config: 0x{:08X} (periodic + vector {:#x})",
396            lvt_config,
397            apic::LVT_TIMER_VECTOR
398        );
399        apic::write_reg(apic::REG_LVT_TIMER, lvt_config);
400
401        let lvt_after = apic::read_reg(apic::REG_LVT_TIMER);
402        log::info!("LVT Timer register after: 0x{:08X}", lvt_after);
403
404        stop_pit();
405
406        // Set initial count (fires every ~10ms = 100Hz)
407        log::info!(
408            "Setting timer initial count to: {} (0x{:08X})",
409            ticks_per_10ms,
410            ticks_per_10ms
411        );
412        apic::write_reg(apic::REG_TIMER_INIT, ticks_per_10ms);
413
414        let init_verify = apic::read_reg(apic::REG_TIMER_INIT);
415        log::info!(
416            "Timer initial count verified: {} (0x{:08X})",
417            init_verify,
418            init_verify
419        );
420    }
421
422    APIC_TIMER_ACTIVE.store(true, Ordering::Relaxed);
423
424    log::info!(
425        "APIC timer: started periodic mode, vector={:#x}, count={} ({}Hz)",
426        apic::LVT_TIMER_VECTOR,
427        ticks_per_10ms,
428        TIMER_HZ,
429    );
430    log::info!("========================================");
431}
432
433/// Return the cached calibration value (ticks per 10ms).
434pub fn apic_ticks_per_10ms() -> u32 {
435    APIC_TICKS_PER_10MS.load(Ordering::Acquire)
436}
437
438/// Start the APIC timer using the cached calibration value.
439///
440/// If calibration failed (ticks=0), falls back to PIT so the system always
441/// has a working timer. Prevents freeze when APIC calibration times out.
442pub fn start_apic_timer_cached() {
443    let ticks = apic_ticks_per_10ms();
444    if ticks == 0 {
445        log::warn!("APIC timer: cached ticks=0, falling back to PIT");
446        init_pit(TIMER_HZ as u32);
447        return;
448    }
449    start_apic_timer(ticks);
450}