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