From 8f05ea44f69cb796e7b1f581ba1b8045c98d615b Mon Sep 17 00:00:00 2001 From: salmonik Date: Sat, 1 Aug 2026 17:38:57 +0200 Subject: rsnix: preemptive kernel tasks (ADR 0003) — sched, IST fault stacks, 176-byte frames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sched: 16-slot static table, shell=task 0, idle=task 1, spawn with guard page + 8 KiB stack, RR on the 100 Hz tick, int 0x60 yield, sleep_ms, lazy reap, jobs/bg/sleep/kill shell commands - gdt: own GDT mirroring UEFI selectors + TSS with IST1/IST2 (guard-page overflows die gracefully instead of triple-faulting) - interrupts: isr_dispatch returns next frame in rax; IntFrame grows rsp/ss fields — QEMU's 64-bit iretq always pops all five slots from a 176-byte frame, so synthetic task frames must carry rsp=top+ss (a 160-byte frame made the first iretq pop rsp/ss from the unmapped page beyond the frame) - panic handler: task panics abort the task, shell keeps running - main: sched::init moved after page allocator + adopted tables (the idle's stack+guard need both; it was silently not spawning) --- rsnix/src/interrupts.rs | 139 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 107 insertions(+), 32 deletions(-) (limited to 'rsnix/src/interrupts.rs') diff --git a/rsnix/src/interrupts.rs b/rsnix/src/interrupts.rs index c176276..6abd54d 100644 --- a/rsnix/src/interrupts.rs +++ b/rsnix/src/interrupts.rs @@ -22,14 +22,14 @@ struct IdtEntry { } /// Fill one IDT entry at the given offset (in bytes). -unsafe fn idt_set_entry(idt: *mut u8, vec: u8, handler: u64, selector: u16, flags: u8) { +unsafe fn idt_set_entry(idt: *mut u8, vec: u8, handler: u64, selector: u16, flags: u8, ist: u8) { unsafe { let off = (vec as usize) * 16; let p = idt.add(off) as *mut IdtEntry; p.write(IdtEntry { offset_lo: handler as u16, selector, - ist: 0, + ist, flags: flags | 0x80, // bit 7 = present offset_mid: (handler >> 16) as u16, offset_hi: (handler >> 32) as u32, @@ -213,6 +213,19 @@ static KEYBUF: KeyBuf = KeyBuf::new(); core::arch::global_asm!( // ISR stubs — push vector number + error code, jump to common. + // Exceptions that push their own error code (8, 14) skip the push-0. + ".globl isr_stub_08", + ".p2align 4", + "isr_stub_08:", + " push 8", // vector (CPU already pushed the error code) + " jmp isr_common", + "", + ".globl isr_stub_14", + ".p2align 4", + "isr_stub_14:", + " push 14", + " jmp isr_common", + "", ".globl isr_stub_32", ".p2align 4", "isr_stub_32:", @@ -227,6 +240,15 @@ core::arch::global_asm!( " push 33", " jmp isr_common", "", + // Software yield (vector 0x60 — outside the PIC's 32-47 range, which + // is why 0x40 was rejected: 0x40 = 64 decimal = slave IRQ 8 slot). + ".globl isr_stub_60", + ".p2align 4", + "isr_stub_60:", + " push 0", + " push 0x60", + " jmp isr_common", + "", // Catch-all for unexpected interrupts. ".globl isr_stub_spurious", ".p2align 4", @@ -235,7 +257,8 @@ core::arch::global_asm!( " push 0xFF", " jmp isr_common", "", - // Common handler — save all registers, call into Rust, restore, iretq. + // Common handler — save all registers, call into Rust (which returns + // the next task's frame pointer in rax), switch rsp, restore, iretq. "isr_common:", " push rax", " push rcx", @@ -255,6 +278,7 @@ core::arch::global_asm!( "", " mov rcx, rsp", // first arg (rcx = MS x64 ABI) " call isr_dispatch", + " mov rsp, rax", // context switch: rax = next task's frame ptr "", " pop r15", " pop r14", @@ -277,37 +301,49 @@ core::arch::global_asm!( /// Saved register frame (as pushed by `isr_common`). #[repr(C)] -struct IntFrame { - r15: u64, - r14: u64, - r13: u64, - r12: u64, - r11: u64, - r10: u64, - r9: u64, - r8: u64, - rdi: u64, - rsi: u64, - rbp: u64, - rbx: u64, - rdx: u64, - rcx: u64, - rax: u64, - vector: u64, - error: u64, - rip: u64, - cs: u64, - rflags: u64, +pub(crate) struct IntFrame { + pub(crate) r15: u64, + pub(crate) r14: u64, + pub(crate) r13: u64, + pub(crate) r12: u64, + pub(crate) r11: u64, + pub(crate) r10: u64, + pub(crate) r9: u64, + pub(crate) r8: u64, + pub(crate) rdi: u64, + pub(crate) rsi: u64, + pub(crate) rbp: u64, + pub(crate) rbx: u64, + pub(crate) rdx: u64, + pub(crate) rcx: u64, + pub(crate) rax: u64, + pub(crate) vector: u64, + pub(crate) error: u64, + pub(crate) rip: u64, + pub(crate) cs: u64, + pub(crate) rflags: u64, + // QEMU's 64-bit interrupt entry ALWAYS pushes SS/RSP/RFLAGS/CS/RIP + // (40 bytes) and iretq always pops all five, so the tail must be + // 40 bytes — these two fields are the CPU-pushed RSP/SS. Synthetic + // task frames set them to the task's initial stack top and the + // kernel SS so the first iretq lands with a valid stack. + pub(crate) rsp: u64, + pub(crate) ss: u64, } -/// Called from the asm common handler — dispatches to the right Rust handler. +/// Called from the asm common handler — dispatches to the right Rust +/// handler and returns the frame pointer the asm should switch to (the +/// next task's, or the same one when no switch happens). #[unsafe(no_mangle)] -unsafe extern "C" fn isr_dispatch(frame: &IntFrame) { - unsafe { +unsafe extern "C" fn isr_dispatch(frame: &IntFrame) -> usize { + let fp = frame as *const IntFrame as usize; + crate::sched::set_in_isr(true); + let next = unsafe { match frame.vector { 32 => { TICKS.fetch_add(1, Ordering::Relaxed); eoi(0); + crate::sched::on_tick(fp) } 33 => { let byte: u8; @@ -316,10 +352,24 @@ unsafe extern "C" fn isr_dispatch(frame: &IntFrame) { // Push scancode to ring buffer (called with interrupts disabled). KEYBUF.push(byte); eoi(1); + fp + } + 8 => { + crate::serial_println!("double fault — halting"); + halt() } - _ => {} + 14 => { + let cr2: u64; + core::arch::asm!("mov {}, cr2", out(reg) cr2, + options(nostack, preserves_flags)); + crate::sched::page_fault(cr2, fp) + } + 0x60 => crate::sched::on_yield(fp), + _ => fp, } - } + }; + crate::sched::set_in_isr(false); + next } // --------------------------------------------------------------------------- @@ -350,12 +400,25 @@ pub fn init() { // Fill all 256 entries with the spurious handler. for vec in 0..=255u8 { - idt_set_entry(idt_ptr, vec, spurious, cs, 0x0Eu8); + idt_set_entry(idt_ptr, vec, spurious, cs, 0x0Eu8, 0); } // Override vectors for interrupt we actually handle. - idt_set_entry(idt_ptr, 32, isr_stub_32 as *const () as u64, cs, 0x0Eu8); - idt_set_entry(idt_ptr, 33, isr_stub_33 as *const () as u64, cs, 0x0Eu8); + idt_set_entry(idt_ptr, 32, isr_stub_32 as *const () as u64, cs, 0x0Eu8, 0); + idt_set_entry(idt_ptr, 33, isr_stub_33 as *const () as u64, cs, 0x0Eu8, 0); + // Yield (software interrupt). + idt_set_entry( + idt_ptr, + 0x60, + isr_stub_60 as *const () as u64, + cs, + 0x0Eu8, + 0, + ); + // Double fault and page fault run on dedicated IST stacks (see + // gdt.rs) so a task fault can't corrupt the faulting task's stack. + idt_set_entry(idt_ptr, 8, isr_stub_08 as *const () as u64, cs, 0x0Eu8, 2); + idt_set_entry(idt_ptr, 14, isr_stub_14 as *const () as u64, cs, 0x0Eu8, 1); // Build the IDTR pseudo-descriptor. IDTR = [ @@ -389,11 +452,23 @@ pub fn init() { } } +/// Halt the CPU forever with interrupts disabled. +pub fn halt() -> ! { + loop { + unsafe { + core::arch::asm!("cli; hlt", options(nostack, preserves_flags)); + } + } +} + // --------------------------------------------------------------------------- // Extern symbols from global_asm! // --------------------------------------------------------------------------- unsafe extern "C" { + fn isr_stub_08(); + fn isr_stub_14(); fn isr_stub_32(); fn isr_stub_33(); + fn isr_stub_60(); fn isr_stub_spurious(); } -- cgit v1.2.3