summaryrefslogtreecommitdiffstats
path: root/rsnix/src/interrupts.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-01 14:21:11 +0200
committersalmonik <salmonik@tuta.io>2026-08-01 14:21:11 +0200
commit6447eb12948d37b74adae2fdbf1db5a9d7b363d5 (patch)
tree0aa03cc79db7ff49f5a64a484ff197ba05f9b6bc /rsnix/src/interrupts.rs
parentklog: kernel log ring + /dev/klog + klog shell command (diff)
downloadrs-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 'rsnix/src/interrupts.rs')
-rw-r--r--rsnix/src/interrupts.rs64
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// ---------------------------------------------------------------------------
4use core::arch::asm; 4use core::arch::asm;
5use core::sync::atomic::{AtomicU16, Ordering}; 5use 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
92const PIT_CMD: u16 = 0x43; 92const PIT_CMD: u16 = 0x43;
93const PIT_CH0: u16 = 0x40; 93const PIT_CH0: u16 = 0x40;
94const PIT_FREQ: u64 = 1_193_182; 94const PIT_FREQ: u64 = 1_193_182; // PIT clock: 1.193182 MHz
95const 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`).
97fn pit_set_rate(hz: u64) { 98pub 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.
103fn 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.
119fn 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).
137static 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.
143pub 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
290pub static TICKS: AtomicU16 = AtomicU16::new(0); 335/// uptime never wraps; `uptime_ms` interpolates the PIT counter for the
336/// sub-tick fraction.
337pub 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.
293pub fn init() { 340pub 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;