summaryrefslogtreecommitdiffstats
path: root/rsnix
diff options
context:
space:
mode:
Diffstat (limited to 'rsnix')
-rw-r--r--rsnix/src/interrupts.rs64
-rw-r--r--rsnix/src/klog.rs30
-rw-r--r--rsnix/src/vt.rs12
3 files changed, 89 insertions, 17 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;
diff --git a/rsnix/src/klog.rs b/rsnix/src/klog.rs
index 1e23460..0c7dc06 100644
--- a/rsnix/src/klog.rs
+++ b/rsnix/src/klog.rs
@@ -32,6 +32,10 @@ struct Ring {
32 count: usize, 32 count: usize,
33 /// Slot the next line will be written to (oldest once full). 33 /// Slot the next line will be written to (oldest once full).
34 next: usize, 34 next: usize,
35 /// Highest timestamp seen, so the log stays monotonic even before
36 /// the timer is running (early-boot PIT reads are uninitialised).
37 last_ms: u64,
38 ms: [u64; MAX_LINES],
35 lens: [u8; MAX_LINES], 39 lens: [u8; MAX_LINES],
36 bytes: [[u8; MAX_LINE]; MAX_LINES], 40 bytes: [[u8; MAX_LINE]; MAX_LINES],
37} 41}
@@ -41,6 +45,8 @@ static mut RING: Ring = Ring {
41 dropped: 0, 45 dropped: 0,
42 count: 0, 46 count: 0,
43 next: 0, 47 next: 0,
48 last_ms: 0,
49 ms: [0; MAX_LINES],
44 lens: [0; MAX_LINES], 50 lens: [0; MAX_LINES],
45 bytes: [[0; MAX_LINE]; MAX_LINES], 51 bytes: [[0; MAX_LINE]; MAX_LINES],
46}; 52};
@@ -49,10 +55,16 @@ fn ring() -> &'static mut Ring {
49 unsafe { &mut *core::ptr::addr_of_mut!(RING) } 55 unsafe { &mut *core::ptr::addr_of_mut!(RING) }
50} 56}
51 57
52/// Append one line to the ring (line-oriented; newlines are stripped). 58/// Append one line to the ring with its timestamp in ms (line-oriented;
53/// Truncated at MAX_LINE bytes. Never allocates, never logs. 59/// newlines are stripped). Truncated at MAX_LINE bytes. Never
54pub fn append(line: &str) { 60/// allocates, never logs. Timestamps are clamped to be monotonic.
61pub fn append(line: &str, mut ms: u64) {
55 let r = ring(); 62 let r = ring();
63 if ms > r.last_ms {
64 r.last_ms = ms;
65 } else {
66 ms = r.last_ms;
67 }
56 let b = line.as_bytes(); 68 let b = line.as_bytes();
57 let n = b.len().min(MAX_LINE); 69 let n = b.len().min(MAX_LINE);
58 if r.count == MAX_LINES { 70 if r.count == MAX_LINES {
@@ -60,6 +72,7 @@ pub fn append(line: &str) {
60 } else { 72 } else {
61 r.count += 1; 73 r.count += 1;
62 } 74 }
75 r.ms[r.next] = ms;
63 r.bytes[r.next][..n].copy_from_slice(&b[..n]); 76 r.bytes[r.next][..n].copy_from_slice(&b[..n]);
64 r.lens[r.next] = n as u8; 77 r.lens[r.next] = n as u8;
65 r.next = (r.next + 1) % MAX_LINES; 78 r.next = (r.next + 1) % MAX_LINES;
@@ -68,11 +81,14 @@ pub fn append(line: &str) {
68 81
69/// Append the formatted output of a `serial_println!` — the macro hook. 82/// Append the formatted output of a `serial_println!` — the macro hook.
70/// `fmt::Write` may deliver the line in several chunks, so accumulate 83/// `fmt::Write` may deliver the line in several chunks, so accumulate
71/// into one line buffer first, then append the whole line. 84/// into one line buffer first, then append the whole line. The
85/// timestamp is taken when the line is emitted (monotonic ms since
86/// boot, from the PIT tick + live-counter interpolation).
72pub fn append_fmt(args: core::fmt::Arguments<'_>) { 87pub fn append_fmt(args: core::fmt::Arguments<'_>) {
88 let ms = crate::interrupts::uptime_ms();
73 let mut line = LineBuf::new(); 89 let mut line = LineBuf::new();
74 let _ = core::fmt::write(&mut line, args); 90 let _ = core::fmt::write(&mut line, args);
75 append(line.as_str()); 91 append(line.as_str(), ms);
76} 92}
77 93
78struct LineBuf { 94struct LineBuf {
@@ -124,7 +140,9 @@ pub fn dump() -> String {
124 let idx = (start + i) % MAX_LINES; 140 let idx = (start + i) % MAX_LINES;
125 let n = r.lens[idx] as usize; 141 let n = r.lens[idx] as usize;
126 let text = core::str::from_utf8(&r.bytes[idx][..n]).unwrap_or(""); 142 let text = core::str::from_utf8(&r.bytes[idx][..n]).unwrap_or("");
127 out += &alloc::format!("{:06} {}\n", first + i as u64, text); 143 let s = r.ms[idx] / 1000;
144 let m = r.ms[idx] % 1000;
145 out += &alloc::format!("[{:04}.{:03}] {:06} {}\n", s, m, first + i as u64, text);
128 } 146 }
129 out 147 out
130} 148}
diff --git a/rsnix/src/vt.rs b/rsnix/src/vt.rs
index 709ea71..53821b7 100644
--- a/rsnix/src/vt.rs
+++ b/rsnix/src/vt.rs
@@ -224,7 +224,13 @@ fn put_byte(b: u8) {
224} 224}
225 225
226fn param(i: usize, default: usize) -> usize { 226fn param(i: usize, default: usize) -> usize {
227 unsafe { if i < NPARAMS { PARAMS[i] } else { default } } 227 unsafe {
228 if i < NPARAMS {
229 PARAMS[i]
230 } else {
231 default
232 }
233 }
228} 234}
229 235
230/// Place a printable glyph at the cursor and advance (wrapping/scrolling). 236/// Place a printable glyph at the cursor and advance (wrapping/scrolling).
@@ -409,7 +415,7 @@ fn newline() {
409/// only the newly-exposed rows. 415/// only the newly-exposed rows.
410const SCROLL_BATCH: usize = 16; 416const SCROLL_BATCH: usize = 16;
411static mut PENDING_SCROLLS: usize = 0; 417static mut PENDING_SCROLLS: usize = 0;
412static mut LAST_SYNC_TICK: u16 = 0; 418static mut LAST_SYNC_TICK: u64 = 0;
413 419
414/// Shift the logical grid up one row; defer the framebuffer shift. 420/// Shift the logical grid up one row; defer the framebuffer shift.
415fn scroll_up() { 421fn scroll_up() {
@@ -424,7 +430,7 @@ fn scroll_up() {
424 // Batch: sync when enough scrolls piled up, or when the stream 430 // Batch: sync when enough scrolls piled up, or when the stream
425 // pauses (~40 ms at the 100 Hz timer) so interactive output still 431 // pauses (~40 ms at the 100 Hz timer) so interactive output still
426 // appears promptly. 432 // appears promptly.
427 const TICK_LIMIT: u16 = 4; 433 const TICK_LIMIT: u64 = 4;
428 let now = crate::interrupts::TICKS.load(core::sync::atomic::Ordering::Relaxed); 434 let now = crate::interrupts::TICKS.load(core::sync::atomic::Ordering::Relaxed);
429 if PENDING_SCROLLS >= SCROLL_BATCH || now.wrapping_sub(LAST_SYNC_TICK) > TICK_LIMIT { 435 if PENDING_SCROLLS >= SCROLL_BATCH || now.wrapping_sub(LAST_SYNC_TICK) > TICK_LIMIT {
430 sync_scroll(); 436 sync_scroll();