summaryrefslogtreecommitdiffstats
path: root/rsnix/src/interrupts.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-01 22:28:34 +0200
committersalmonik <salmonik@tuta.io>2026-08-01 22:28:34 +0200
commitab7d87bcde31c1681ea09c30534dde840a58d6fa (patch)
tree42eed2c86663cf6ecfa3507ca46399e31b441c99 /rsnix/src/interrupts.rs
parentrsboot: track latest upstream HEAD (92367a8) instead of pinned ee1c85b (diff)
downloadrs-dos-ab7d87bcde31c1681ea09c30534dde840a58d6fa.tar.gz
rs-dos-ab7d87bcde31c1681ea09c30534dde840a58d6fa.zip
rsnix v0.5.2: fault diagnostics + real-hardware scroll-crash hardening
The real-hardware scroll crash (white bands + instant freeze, QEMU-clean) was invisible because the only trace was a single serial line on machines with no serial console. This makes every crash self-identifying and hardens the real-hardware failure surfaces: - Every CPU exception vector 0-31 now has an IDT entry (macro-generated stubs; 8/14 keep their IST stacks). A fault in the shell prints a white-on-red band to the framebuffer via gop::dump_lines (raw fb writes bypassing the vt, fully clamped) plus a parseable serial line: '!!! <kind> vec=N err=… rip=… cr2=… rsp=… rflags=… task=N "name"', then halts — instead of converging to a cryptic double fault. Background-task faults abort the task as before. - Boot log now reports the authoritative post-SetMode geometry: 'gop: fb base=… size=… pitch=… WxH bpp=… bgr=…', so the next real machine's report identifies whether pitch*height exceeds the mapping. - Framebuffer writes (clear, push_rect, shift_up_n bulk push, cursor overlay) are clamped to the firmware's framebuffer_size: a pitch- padded mode degrades to a truncated view instead of faulting past the mapping. - The GOP framebuffer region is excluded from the page allocator (and subtracted from the heap's conventional-region pick) even when the firmware lists it as ConventionalMemory — kernel allocations can never overwrite the display again. The GOP protocol is located and peeked before the memory map is captured so the exclusion is known at heap-init time. - Shell gains a hidden 'fault' command (deliberate unmapped write) and 'make test-fault' verifies the dump: vec=14, cr2=0x400000000000, task=0, halt — no triple fault. All other suites re-pass. - CONTEXT.md: fault-dump format documented for real-hardware reports.
Diffstat (limited to '')
-rw-r--r--rsnix/src/interrupts.rs146
1 files changed, 144 insertions, 2 deletions
diff --git a/rsnix/src/interrupts.rs b/rsnix/src/interrupts.rs
index 7ea0b07..be252df 100644
--- a/rsnix/src/interrupts.rs
+++ b/rsnix/src/interrupts.rs
@@ -269,6 +269,56 @@ core::arch::global_asm!(
269 " push 0xFF", 269 " push 0xFF",
270 " jmp isr_common", 270 " jmp isr_common",
271 "", 271 "",
272 // Exception stubs for the remaining vectors 0-31 so every fault gets
273 // a diagnosable dump instead of converging to a cryptic double fault.
274 // Vectors that push their own error code (10, 11, 12, 13, 17, 18 — 8
275 // and 14 have dedicated stubs above) skip the fake push-0.
276 ".macro ISR_NOERR vec",
277 " .globl isr_stub_x_\\vec",
278 " .p2align 4",
279 "isr_stub_x_\\vec:",
280 " push 0",
281 " push \\vec",
282 " jmp isr_common",
283 ".endm",
284 ".macro ISR_ERR vec",
285 " .globl isr_stub_x_\\vec",
286 " .p2align 4",
287 "isr_stub_x_\\vec:",
288 " push \\vec",
289 " jmp isr_common",
290 ".endm",
291 "ISR_NOERR 0",
292 "ISR_NOERR 1",
293 "ISR_NOERR 2",
294 "ISR_NOERR 3",
295 "ISR_NOERR 4",
296 "ISR_NOERR 5",
297 "ISR_NOERR 6",
298 "ISR_NOERR 7",
299 "ISR_NOERR 9",
300 "ISR_ERR 10",
301 "ISR_ERR 11",
302 "ISR_ERR 12",
303 "ISR_ERR 13",
304 "ISR_NOERR 15",
305 "ISR_NOERR 16",
306 "ISR_ERR 17",
307 "ISR_ERR 18",
308 "ISR_NOERR 19",
309 "ISR_NOERR 20",
310 "ISR_NOERR 21",
311 "ISR_NOERR 22",
312 "ISR_NOERR 23",
313 "ISR_NOERR 24",
314 "ISR_NOERR 25",
315 "ISR_NOERR 26",
316 "ISR_NOERR 27",
317 "ISR_NOERR 28",
318 "ISR_NOERR 29",
319 "ISR_NOERR 30",
320 "ISR_NOERR 31",
321 "",
272 // Common handler — save all registers, call into Rust (which returns 322 // Common handler — save all registers, call into Rust (which returns
273 // the next task's frame pointer in rax), switch rsp, restore, iretq. 323 // the next task's frame pointer in rax), switch rsp, restore, iretq.
274 "isr_common:", 324 "isr_common:",
@@ -381,8 +431,9 @@ unsafe extern "C" fn isr_dispatch(frame: &IntFrame) -> usize {
381 fp 431 fp
382 } 432 }
383 8 => { 433 8 => {
384 crate::serial_println!("double fault — halting"); 434 // Double fault — dump the frame (we're on the IST stack
385 halt() 435 // so this is safe) and halt.
436 crate::sched::fault_dump("double fault", 0, frame)
386 } 437 }
387 14 => { 438 14 => {
388 let cr2: u64; 439 let cr2: u64;
@@ -391,6 +442,11 @@ unsafe extern "C" fn isr_dispatch(frame: &IntFrame) -> usize {
391 crate::sched::page_fault(cr2, fp) 442 crate::sched::page_fault(cr2, fp)
392 } 443 }
393 0x60 => crate::sched::on_yield(fp), 444 0x60 => crate::sched::on_yield(fp),
445 v if v < 32 => {
446 // Any other CPU exception: full dump (screen + serial),
447 // then halt the shell or abort the faulting background task.
448 crate::sched::fault_dump("exception", 0, frame)
449 }
394 _ => fp, 450 _ => fp,
395 } 451 }
396 }; 452 };
@@ -418,6 +474,47 @@ pub fn read_mouse_byte() -> Option<u8> {
418/// sub-tick fraction. 474/// sub-tick fraction.
419pub static TICKS: AtomicU64 = AtomicU64::new(0); 475pub static TICKS: AtomicU64 = AtomicU64::new(0);
420 476
477/// Address of the stub for CPU exception vector `vec` (0-31), or 0 for
478/// vectors without a stub. 8 (double fault) and 14 (page fault) use
479/// dedicated stubs that run on IST stacks.
480fn exc_stub_addr(vec: u8) -> u64 {
481 match vec {
482 8 => isr_stub_08 as *const () as u64,
483 14 => isr_stub_14 as *const () as u64,
484 0 => isr_stub_x_0 as *const () as u64,
485 1 => isr_stub_x_1 as *const () as u64,
486 2 => isr_stub_x_2 as *const () as u64,
487 3 => isr_stub_x_3 as *const () as u64,
488 4 => isr_stub_x_4 as *const () as u64,
489 5 => isr_stub_x_5 as *const () as u64,
490 6 => isr_stub_x_6 as *const () as u64,
491 7 => isr_stub_x_7 as *const () as u64,
492 9 => isr_stub_x_9 as *const () as u64,
493 10 => isr_stub_x_10 as *const () as u64,
494 11 => isr_stub_x_11 as *const () as u64,
495 12 => isr_stub_x_12 as *const () as u64,
496 13 => isr_stub_x_13 as *const () as u64,
497 15 => isr_stub_x_15 as *const () as u64,
498 16 => isr_stub_x_16 as *const () as u64,
499 17 => isr_stub_x_17 as *const () as u64,
500 18 => isr_stub_x_18 as *const () as u64,
501 19 => isr_stub_x_19 as *const () as u64,
502 20 => isr_stub_x_20 as *const () as u64,
503 21 => isr_stub_x_21 as *const () as u64,
504 22 => isr_stub_x_22 as *const () as u64,
505 23 => isr_stub_x_23 as *const () as u64,
506 24 => isr_stub_x_24 as *const () as u64,
507 25 => isr_stub_x_25 as *const () as u64,
508 26 => isr_stub_x_26 as *const () as u64,
509 27 => isr_stub_x_27 as *const () as u64,
510 28 => isr_stub_x_28 as *const () as u64,
511 29 => isr_stub_x_29 as *const () as u64,
512 30 => isr_stub_x_30 as *const () as u64,
513 31 => isr_stub_x_31 as *const () as u64,
514 _ => 0,
515 }
516}
517
421/// Initialise the IDT, remap the PIC, set up the PIT, and enable interrupts. 518/// Initialise the IDT, remap the PIC, set up the PIT, and enable interrupts.
422pub fn init() { 519pub fn init() {
423 unsafe { 520 unsafe {
@@ -435,6 +532,20 @@ pub fn init() {
435 idt_set_entry(idt_ptr, vec, spurious, cs, 0x0Eu8, 0); 532 idt_set_entry(idt_ptr, vec, spurious, cs, 0x0Eu8, 0);
436 } 533 }
437 534
535 // Wire up every CPU exception vector (0-31) so any fault produces
536 // a diagnosable dump instead of converging to a double fault.
537 for vec in 0..=31u8 {
538 let a = exc_stub_addr(vec);
539 if a != 0 {
540 let ist = match vec {
541 8 => 2u8,
542 14 => 1u8,
543 _ => 0u8,
544 };
545 idt_set_entry(idt_ptr, vec, a, cs, 0x0Eu8, ist);
546 }
547 }
548
438 // Override vectors for interrupt we actually handle. 549 // Override vectors for interrupt we actually handle.
439 idt_set_entry(idt_ptr, 32, isr_stub_32 as *const () as u64, cs, 0x0Eu8, 0); 550 idt_set_entry(idt_ptr, 32, isr_stub_32 as *const () as u64, cs, 0x0Eu8, 0);
440 idt_set_entry(idt_ptr, 33, isr_stub_33 as *const () as u64, cs, 0x0Eu8, 0); 551 idt_set_entry(idt_ptr, 33, isr_stub_33 as *const () as u64, cs, 0x0Eu8, 0);
@@ -513,4 +624,35 @@ unsafe extern "C" {
513 fn isr_stub_44(); 624 fn isr_stub_44();
514 fn isr_stub_60(); 625 fn isr_stub_60();
515 fn isr_stub_spurious(); 626 fn isr_stub_spurious();
627 // Macro-generated exception stubs (isr_stub_x_<vec>).
628 fn isr_stub_x_0();
629 fn isr_stub_x_1();
630 fn isr_stub_x_2();
631 fn isr_stub_x_3();
632 fn isr_stub_x_4();
633 fn isr_stub_x_5();
634 fn isr_stub_x_6();
635 fn isr_stub_x_7();
636 fn isr_stub_x_9();
637 fn isr_stub_x_10();
638 fn isr_stub_x_11();
639 fn isr_stub_x_12();
640 fn isr_stub_x_13();
641 fn isr_stub_x_15();
642 fn isr_stub_x_16();
643 fn isr_stub_x_17();
644 fn isr_stub_x_18();
645 fn isr_stub_x_19();
646 fn isr_stub_x_20();
647 fn isr_stub_x_21();
648 fn isr_stub_x_22();
649 fn isr_stub_x_23();
650 fn isr_stub_x_24();
651 fn isr_stub_x_25();
652 fn isr_stub_x_26();
653 fn isr_stub_x_27();
654 fn isr_stub_x_28();
655 fn isr_stub_x_29();
656 fn isr_stub_x_30();
657 fn isr_stub_x_31();
516} 658}