summaryrefslogtreecommitdiffstats
path: root/rsnix/src/sched.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-17 14:53:52 +0200
committersalmonik <salmonik@tuta.io>2026-08-17 14:53:52 +0200
commitbe868a43185826b02bf6d091019bb779678525bb (patch)
tree586988c15f1d700b1621c7f0208584b30878a56c /rsnix/src/sched.rs
parentv0.5.10: change em-dashes to dashes (diff)
downloadrs-dos-be868a43185826b02bf6d091019bb779678525bb.tar.gz
rs-dos-be868a43185826b02bf6d091019bb779678525bb.zip
USB2/1.1 (EHCI/UHCI) drivers + real-hardware bring-up hardening
- Add polled EHCI (USB2) and UHCI (USB1.1) host-controller drivers with HID boot mouse support, wired into cursor polling and the 'usb' shell cmd - Add USB Legacy ownership handoff (claim_usb_legacy) plus config_write_byte - Harden boot: verify the PIT/timer ticks before shell entry, EOI stray interrupts in the LAPIC to stop interrupt storms, bound PS/2 controller waits, and INTx-disable AHCI (polled driver) - Add boot stage markers, klog screen echo, and QR lock; scheduler mark_shell_running; NO_USB stopgap to keep BIOS USB-Legacy input alive - alloc_pages_above for DMA buffers that must avoid low conventional memory - Expand DEBUG_NOTES with EHCI/QEMU bring-up findings
Diffstat (limited to '')
-rw-r--r--rsnix/src/sched.rs103
1 files changed, 102 insertions, 1 deletions
diff --git a/rsnix/src/sched.rs b/rsnix/src/sched.rs
index dd1409e..259ddf6 100644
--- a/rsnix/src/sched.rs
+++ b/rsnix/src/sched.rs
@@ -158,6 +158,47 @@ static IN_ISR: AtomicBool = AtomicBool::new(false);
158static PREEMPT_DEPTH: AtomicU64 = AtomicU64::new(0); 158static PREEMPT_DEPTH: AtomicU64 = AtomicU64::new(0);
159 159
160// --------------------------------------------------------------------------- 160// ---------------------------------------------------------------------------
161// Watchdog — turn wedged states into visible panics instead of silent
162// freezes. Real hardware has no serial console, so the panic handler
163// renders to the framebuffer and writes the crash log; a freeze that would
164// otherwise look like a dead machine becomes a reported panic.
165//
166// The watchdog only runs when the PIT tick is firing (interrupts on). A
167// hang with interrupts disabled is invisible to it, so driver waits must
168// stay bounded (timeout loops) — enforced at the driver level.
169// ---------------------------------------------------------------------------
170
171/// Consecutive ticks where the preemption lock was held (depth > 0). The
172/// tick cannot switch tasks while the lock is held, so a hold this long is
173/// a deadlock (a driver wedged inside the serial/klog lock, ...).
174static STALL_TICKS: AtomicU64 = AtomicU64::new(0);
175const WATCHDOG_LOCK_TICKS: u64 = 500; // 5 s at 100 Hz
176
177/// Per-task "same interrupted frame" counter for spin detection.
178struct SpinWatch {
179 last_rsp: usize,
180 same: u32,
181}
182const SPIN_WATCH_INIT: SpinWatch = SpinWatch { last_rsp: 0, same: 0 };
183static mut SPIN_WATCH: [SpinWatch; MAX_TASKS] = [SPIN_WATCH_INIT; MAX_TASKS];
184/// A spinning background task is interrupted at roughly half the tick rate
185/// (it alternates with idle), so 1500 hits is ~30 s of wall time.
186const WATCHDOG_SPIN_TICKS: u32 = 1500;
187/// Boot-phase threshold for task 0 (the shell): before the REPL is live,
188/// task 0 runs init code where a fixed frame means a driver/controller
189/// hang — panic after ~15 s wall. The boot must finish well inside this.
190const WATCHDOG_BOOT_SPIN_TICKS: u32 = 750;
191
192/// True once the shell REPL is live (set by main.rs before shell::run()).
193/// The shell legitimately parks in poll_key with a fixed frame when it is;
194/// before that, task 0 running boot code must not sit in a fixed frame.
195static SHELL_RUNNING: AtomicBool = AtomicBool::new(false);
196
197pub fn mark_shell_running() {
198 SHELL_RUNNING.store(true, Ordering::Relaxed);
199}
200
201// ---------------------------------------------------------------------------
161// Preemption lock 202// Preemption lock
162// --------------------------------------------------------------------------- 203// ---------------------------------------------------------------------------
163 204
@@ -193,12 +234,49 @@ pub fn current() -> usize {
193/// Receives the interrupted task's frame pointer; returns the frame 234/// Receives the interrupted task's frame pointer; returns the frame
194/// pointer the asm should switch to (the same one when no switch). 235/// pointer the asm should switch to (the same one when no switch).
195pub fn on_tick(cur: usize) -> usize { 236pub fn on_tick(cur: usize) -> usize {
196 if !is_ready() || PREEMPT_DEPTH.load(Ordering::Relaxed) > 0 { 237 if !is_ready() {
197 return cur; 238 return cur;
198 } 239 }
199 unsafe { 240 unsafe {
241 // Watchdog: the tick fired, so the CPU is alive. A state we cannot
242 // leave is a hang, not a sleep — surface it as a visible panic.
243 if PREEMPT_DEPTH.load(Ordering::Relaxed) > 0 {
244 let n = STALL_TICKS.fetch_add(1, Ordering::Relaxed) + 1;
245 if n >= WATCHDOG_LOCK_TICKS {
246 watchdog_panic(cur, "preemption lock held");
247 }
248 return cur;
249 }
250 STALL_TICKS.store(0, Ordering::Relaxed);
251
200 TASKS[CURRENT].saved_rsp = cur; 252 TASKS[CURRENT].saved_rsp = cur;
201 TASKS[CURRENT].ticks += 1; 253 TASKS[CURRENT].ticks += 1;
254 // Spin detection: a task whose interrupted frame never moves
255 // (identical RSP on every tick) is stuck in a hot loop — no yield,
256 // no sleep, no block. The shell (id 0) legitimately parks in
257 // poll_key with a fixed frame, and idle (id 1) hlt's; both exempt.
258 let id = CURRENT;
259 let w = &mut SPIN_WATCH[id];
260 if w.last_rsp == cur {
261 w.same += 1;
262 // Background tasks (id >= 2): spinning is never legitimate.
263 // Task 0 (shell): exempt once the REPL is live (poll_key parks
264 // with a fixed frame); during the boot phase a fixed frame
265 // means usb/ehci init is wedged on real hardware.
266 let threshold = if id == SHELL_TID && !SHELL_RUNNING.load(Ordering::Relaxed) {
267 WATCHDOG_BOOT_SPIN_TICKS
268 } else if id >= 2 {
269 WATCHDOG_SPIN_TICKS
270 } else {
271 u32::MAX // idle hlt's with a fixed frame forever — fine
272 };
273 if w.same >= threshold {
274 watchdog_panic(cur, "task spinning without progress");
275 }
276 } else {
277 w.last_rsp = cur;
278 w.same = 1;
279 }
202 let now = crate::interrupts::uptime_ms(); 280 let now = crate::interrupts::uptime_ms();
203 for t in 0..MAX_TASKS { 281 for t in 0..MAX_TASKS {
204 match TASKS[t].state { 282 match TASKS[t].state {
@@ -219,6 +297,29 @@ pub fn on_tick(cur: usize) -> usize {
219 } 297 }
220} 298}
221 299
300/// Render a watchdog verdict through the normal panic path (framebuffer +
301/// serial + crash log) and halt. Runs in tick/ISR context: `cur` is the
302/// interrupted task's IntFrame, so the reported RIP pinpoints the hang.
303fn watchdog_panic(cur: usize, why: &str) -> ! {
304 let id = unsafe { CURRENT };
305 let name = name_str(id);
306 let rip = unsafe { (cur as *const IntFrame).read().rip };
307 let up = crate::interrupts::uptime_ms();
308 if id == SHELL_TID {
309 // Boot-phase hang: report the last completed boot stage so the
310 // wedged init step is identifiable without serial.
311 let stage = crate::gop::last_boot_stage();
312 panic!(
313 "watchdog: {} — task {} \"{}\" rip={:#x} (uptime {} ms) boot-stage: {}",
314 why, id, name, rip, up, stage
315 );
316 }
317 panic!(
318 "watchdog: {} — task {} \"{}\" rip={:#x} (uptime {} ms)",
319 why, id, name, rip, up
320 );
321}
322
222/// Software yield (vector 0x60). 323/// Software yield (vector 0x60).
223pub fn on_yield(cur: usize) -> usize { 324pub fn on_yield(cur: usize) -> usize {
224 if !is_ready() || PREEMPT_DEPTH.load(Ordering::Relaxed) > 0 { 325 if !is_ready() || PREEMPT_DEPTH.load(Ordering::Relaxed) > 0 {