diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-01 14:21:11 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-01 14:21:11 +0200 |
| commit | 6447eb12948d37b74adae2fdbf1db5a9d7b363d5 (patch) | |
| tree | 0aa03cc79db7ff49f5a64a484ff197ba05f9b6bc /rsnix/src/interrupts.rs | |
| parent | klog: kernel log ring + /dev/klog + klog shell command (diff) | |
| download | rs-dos-6447eb12948d37b74adae2fdbf1db5a9d7b363d5.tar.gz rs-dos-6447eb12948d37b74adae2fdbf1db5a9d7b363d5.zip | |
klog: [ssss.mmm] millisecond timestamps from the PIT
- interrupts::uptime_ms(): monotonic ms since boot — the 100 Hz PIT
tick (10 ms) plus interpolation from the PIT's live 1.19 MHz
down-counter (read twice around the tick load; a count jump means a
tick fired between reads) for true sub-ms resolution. TICKS widened
AtomicU16 -> AtomicU64 (u16 wrapped after ~11 min).
- PIT_READY gates interpolation until interrupts::init configures the
timer, so early-boot lines are deterministically [0000.000] instead
of uninitialised-PIT noise; klog clamps timestamps monotonic.
- The CMOS RTC cannot provide milliseconds (1 Hz BCD device, seconds
only) — millis come from the PIT.
- klog stores ms per line at append time; dump format is now
'[0000.123] 000000 === rsnix boot ===' (user-specified format).
Verified in QEMU: first line [0000.000], Phase-2 lines show real
monotonic ms (fs: vfs ready at [0000.519], self-test [0000.524]),
per-dump monotonicity, boot.log persists timestamps, test-disk round
trip fsck-clean.
Diffstat (limited to '')
| -rw-r--r-- | rsnix/src/interrupts.rs | 64 |
1 files changed, 56 insertions, 8 deletions
diff --git a/rsnix/src/interrupts.rs b/rsnix/src/interrupts.rs index 2b82a3d..c176276 100644 --- a/rsnix/src/interrupts.rs +++ b/rsnix/src/interrupts.rs | |||
| @@ -2,7 +2,7 @@ | |||
| 2 | // Interrupt handling: IDT, PIC remap, ISR dispatch. | 2 | // Interrupt handling: IDT, PIC remap, ISR dispatch. |
| 3 | // --------------------------------------------------------------------------- | 3 | // --------------------------------------------------------------------------- |
| 4 | use core::arch::asm; | 4 | use core::arch::asm; |
| 5 | use core::sync::atomic::{AtomicU16, Ordering}; | 5 | use core::sync::atomic::{AtomicBool, AtomicU16, AtomicU64, Ordering}; |
| 6 | 6 | ||
| 7 | // --------------------------------------------------------------------------- | 7 | // --------------------------------------------------------------------------- |
| 8 | // IDT entry and table | 8 | // IDT entry and table |
| @@ -91,11 +91,17 @@ fn eoi(irq: u8) { | |||
| 91 | 91 | ||
| 92 | const PIT_CMD: u16 = 0x43; | 92 | const PIT_CMD: u16 = 0x43; |
| 93 | const PIT_CH0: u16 = 0x40; | 93 | const PIT_CH0: u16 = 0x40; |
| 94 | const PIT_FREQ: u64 = 1_193_182; | 94 | const PIT_FREQ: u64 = 1_193_182; // PIT clock: 1.193182 MHz |
| 95 | const PIT_HZ: u64 = 100; | ||
| 95 | 96 | ||
| 96 | /// Set up PIT channel 0 in mode 2 (rate generator) at `hz` (1–1000ish). | 97 | /// Counter divisor loaded for the ~100 Hz tick (matches `pit_set_rate`). |
| 97 | fn pit_set_rate(hz: u64) { | 98 | pub const PIT_DIVISOR: u16 = (PIT_FREQ / PIT_HZ) as u16; |
| 98 | let divisor = (PIT_FREQ / hz) as u16; | 99 | |
| 100 | /// Set up PIT channel 0 in mode 2 (rate generator) at the fixed 100 Hz | ||
| 101 | /// tick rate — the divisor must stay `PIT_DIVISOR`, which `uptime_ms` | ||
| 102 | /// relies on for interpolation. | ||
| 103 | fn pit_set_rate() { | ||
| 104 | let divisor = PIT_DIVISOR; | ||
| 99 | unsafe { | 105 | unsafe { |
| 100 | // Mode 2, channel 0, lobyte/hibyte, binary | 106 | // Mode 2, channel 0, lobyte/hibyte, binary |
| 101 | asm!("out dx, al", in("dx") PIT_CMD, in("al") 0b0011_0100u8, | 107 | asm!("out dx, al", in("dx") PIT_CMD, in("al") 0b0011_0100u8, |
| @@ -107,6 +113,45 @@ fn pit_set_rate(hz: u64) { | |||
| 107 | } | 113 | } |
| 108 | } | 114 | } |
| 109 | 115 | ||
| 116 | /// Latch and read the PIT channel 0 down-counter (0..=PIT_DIVISOR). | ||
| 117 | /// In mode 2 the counter counts down and reloads on reaching 0, so it | ||
| 118 | /// only ever jumps *up* when a tick fires. | ||
| 119 | fn pit_count() -> u16 { | ||
| 120 | unsafe { | ||
| 121 | asm!("out dx, al", in("dx") PIT_CMD, in("al") 0x00u8, | ||
| 122 | options(nomem, nostack, preserves_flags)); | ||
| 123 | let lo: u8; | ||
| 124 | let hi: u8; | ||
| 125 | asm!("in al, dx", out("al") lo, in("dx") PIT_CH0, | ||
| 126 | options(nomem, nostack, preserves_flags)); | ||
| 127 | asm!("in al, dx", out("al") hi, in("dx") PIT_CH0, | ||
| 128 | options(nomem, nostack, preserves_flags)); | ||
| 129 | u16::from_le_bytes([lo, hi]) | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | /// True once the PIT tick is configured (`interrupts::init`). Before | ||
| 134 | /// that the PIT counter is uninitialised, so `uptime_ms` reports 0 | ||
| 135 | /// (early-boot messages are all at [0000.000], which is truthful — the | ||
| 136 | /// clock hasn't started). | ||
| 137 | static PIT_READY: AtomicBool = AtomicBool::new(false); | ||
| 138 | |||
| 139 | /// Monotonic milliseconds since boot: the 100 Hz tick (10 ms each) plus | ||
| 140 | /// interpolation from the PIT's live 1.19 MHz down-counter for the | ||
| 141 | /// sub-10 ms fraction. The countdown is read twice around the tick | ||
| 142 | /// load: if it jumped up, a tick fired between the reads. | ||
| 143 | pub fn uptime_ms() -> u64 { | ||
| 144 | if !PIT_READY.load(Ordering::Relaxed) { | ||
| 145 | return 0; | ||
| 146 | } | ||
| 147 | let c1 = pit_count(); | ||
| 148 | let ticks = TICKS.load(Ordering::Relaxed); | ||
| 149 | let c2 = pit_count(); | ||
| 150 | let ticks = if c2 > c1 { ticks + 1 } else { ticks }; | ||
| 151 | let c = u64::from(c2.min(PIT_DIVISOR)); | ||
| 152 | ticks * (1000 / PIT_HZ) + (u64::from(PIT_DIVISOR) - c) * 1000 / PIT_FREQ | ||
| 153 | } | ||
| 154 | |||
| 110 | // --------------------------------------------------------------------------- | 155 | // --------------------------------------------------------------------------- |
| 111 | // Lock-free SPSC ring buffer for keyboard scancodes | 156 | // Lock-free SPSC ring buffer for keyboard scancodes |
| 112 | // --------------------------------------------------------------------------- | 157 | // --------------------------------------------------------------------------- |
| @@ -286,8 +331,10 @@ pub fn read_scancode() -> Option<u8> { | |||
| 286 | KEYBUF.pop() | 331 | KEYBUF.pop() |
| 287 | } | 332 | } |
| 288 | 333 | ||
| 289 | /// Global tick counter (incremented by timer IRQ, ~100 Hz). | 334 | /// Global tick counter (incremented by timer IRQ, ~100 Hz). u64 so |
| 290 | pub static TICKS: AtomicU16 = AtomicU16::new(0); | 335 | /// uptime never wraps; `uptime_ms` interpolates the PIT counter for the |
| 336 | /// sub-tick fraction. | ||
| 337 | pub static TICKS: AtomicU64 = AtomicU64::new(0); | ||
| 291 | 338 | ||
| 292 | /// Initialise the IDT, remap the PIC, set up the PIT, and enable interrupts. | 339 | /// Initialise the IDT, remap the PIC, set up the PIT, and enable interrupts. |
| 293 | pub fn init() { | 340 | pub fn init() { |
| @@ -326,7 +373,8 @@ pub fn init() { | |||
| 326 | remap_pic(); | 373 | remap_pic(); |
| 327 | 374 | ||
| 328 | // Set up PIT timer at ~100 Hz. | 375 | // Set up PIT timer at ~100 Hz. |
| 329 | pit_set_rate(100); | 376 | pit_set_rate(); |
| 377 | PIT_READY.store(true, Ordering::Relaxed); | ||
| 330 | 378 | ||
| 331 | // Unmask timer (IRQ0 → bit 0) and keyboard (IRQ1 → bit 1) on master. | 379 | // Unmask timer (IRQ0 → bit 0) and keyboard (IRQ1 → bit 1) on master. |
| 332 | let mut mask: u8; | 380 | let mut mask: u8; |
