// --------------------------------------------------------------------------- // Interrupt handling: IDT, PIC remap, ISR dispatch. // --------------------------------------------------------------------------- use core::arch::asm; use core::sync::atomic::{AtomicU16, Ordering}; // --------------------------------------------------------------------------- // IDT entry and table // --------------------------------------------------------------------------- /// A single 64-bit IDT entry (16 bytes). #[derive(Copy, Clone)] #[repr(C, packed)] struct IdtEntry { offset_lo: u16, selector: u16, ist: u8, flags: u8, offset_mid: u16, offset_hi: u32, _reserved: u32, } /// 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 { 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, flags: flags | 0x80, // bit 7 = present offset_mid: (handler >> 16) as u16, offset_hi: (handler >> 32) as u32, _reserved: 0, }); } } /// The one true IDT (live in .bss, zero-initialised). static mut IDT: [u8; 256 * 16] = [0u8; 256 * 16]; static mut IDTR: [u16; 5] = [0; 5]; // 2-byte limit + 4-byte base (48-bit pseudo-desc) // --------------------------------------------------------------------------- // PIC (8259A) // --------------------------------------------------------------------------- const PIC1_CMD: u16 = 0x20; const PIC1_DATA: u16 = 0x21; const PIC2_CMD: u16 = 0xA0; const PIC2_DATA: u16 = 0xA1; const PIC_EOI: u8 = 0x20; const ICW1: u8 = 0x11; // edge, cascade, icw4 const ICW4: u8 = 0x01; // x86 mode /// Remap PIC IRQs 0-15 to IDT vectors 32-47, then mask all. unsafe fn remap_pic() { unsafe { asm!("out dx, al", in("dx") PIC1_CMD, in("al") ICW1, options(nostack, preserves_flags)); asm!("out dx, al", in("dx") PIC2_CMD, in("al") ICW1, options(nostack, preserves_flags)); asm!("out dx, al", in("dx") PIC1_DATA, in("al") 0x20u8, options(nostack, preserves_flags)); asm!("out dx, al", in("dx") PIC2_DATA, in("al") 0x28u8, options(nostack, preserves_flags)); asm!("out dx, al", in("dx") PIC1_DATA, in("al") 0x04u8, options(nostack, preserves_flags)); asm!("out dx, al", in("dx") PIC2_DATA, in("al") 0x02u8, options(nostack, preserves_flags)); asm!("out dx, al", in("dx") PIC1_DATA, in("al") ICW4, options(nostack, preserves_flags)); asm!("out dx, al", in("dx") PIC2_DATA, in("al") ICW4, options(nostack, preserves_flags)); asm!("out dx, al", in("dx") PIC1_DATA, in("al") 0xFFu8, options(nostack, preserves_flags)); asm!("out dx, al", in("dx") PIC2_DATA, in("al") 0xFFu8, options(nostack, preserves_flags)); } } /// Send End-Of-Interrupt to the master PIC (and slave if needed). fn eoi(irq: u8) { unsafe { // Slave IRQs are 8-15, mapped to vectors 40-47. if irq >= 8 { asm!("out dx, al", in("dx") PIC2_CMD, in("al") PIC_EOI, options(nostack, preserves_flags)); } asm!("out dx, al", in("dx") PIC1_CMD, in("al") PIC_EOI, options(nostack, preserves_flags)); } } // --------------------------------------------------------------------------- // PIT timer (channel 0, ~100 Hz) // --------------------------------------------------------------------------- const PIT_CMD: u16 = 0x43; const PIT_CH0: u16 = 0x40; const PIT_FREQ: u64 = 1_193_182; /// Set up PIT channel 0 in mode 2 (rate generator) at `hz` (1–1000ish). fn pit_set_rate(hz: u64) { let divisor = (PIT_FREQ / hz) as u16; unsafe { // Mode 2, channel 0, lobyte/hibyte, binary asm!("out dx, al", in("dx") PIT_CMD, in("al") 0b0011_0100u8, options(nostack, preserves_flags)); asm!("out dx, al", in("dx") PIT_CH0, in("al") (divisor & 0xFF) as u8, options(nostack, preserves_flags)); asm!("out dx, al", in("dx") PIT_CH0, in("al") (divisor >> 8) as u8, options(nostack, preserves_flags)); } } // --------------------------------------------------------------------------- // Lock-free SPSC ring buffer for keyboard scancodes // --------------------------------------------------------------------------- /// Single-producer (ISR) / single-consumer (main) lock-free ring buffer. struct KeyBuf { buf: [core::cell::UnsafeCell; 256], head: AtomicU16, // next slot to read (written by consumer / main) tail: AtomicU16, // next slot to write (written by producer / ISR) } unsafe impl Sync for KeyBuf {} impl KeyBuf { const fn new() -> Self { // Inline-const array repeat (stable since 1.79): each element is a // fresh `UnsafeCell::new(0)` value, so no interior-mutable const. Self { buf: [const { core::cell::UnsafeCell::new(0) }; 256], head: AtomicU16::new(0), tail: AtomicU16::new(0), } } /// Push one byte — called from ISR (interrupts disabled, single-producer). fn push(&self, byte: u8) { let t = self.tail.load(Ordering::Relaxed); let h = self.head.load(Ordering::Acquire); let next = t.wrapping_add(1); // Full if the distance between head and tail == capacity (256). if next.wrapping_sub(h) > 255 { return; // drop — ring is full } unsafe { *self.buf[t as usize & 255].get() = byte; } // Ensure byte is globally visible before incrementing tail. self.tail.store(next, Ordering::Release); } /// Pop one byte — called from main context. Returns None when empty. fn pop(&self) -> Option { let h = self.head.load(Ordering::Relaxed); let t = self.tail.load(Ordering::Acquire); if h == t { return None; } let byte = unsafe { *self.buf[h as usize & 255].get() }; self.head.store(h.wrapping_add(1), Ordering::Release); Some(byte) } } static KEYBUF: KeyBuf = KeyBuf::new(); // --------------------------------------------------------------------------- // Interrupt stubs (assembly) and dispatch // --------------------------------------------------------------------------- core::arch::global_asm!( // ISR stubs — push vector number + error code, jump to common. ".globl isr_stub_32", ".p2align 4", "isr_stub_32:", " push 0", // error code (non-exception) " push 32", // vector " jmp isr_common", "", ".globl isr_stub_33", ".p2align 4", "isr_stub_33:", " push 0", " push 33", " jmp isr_common", "", // Catch-all for unexpected interrupts. ".globl isr_stub_spurious", ".p2align 4", "isr_stub_spurious:", " push 0", " push 0xFF", " jmp isr_common", "", // Common handler — save all registers, call into Rust, restore, iretq. "isr_common:", " push rax", " push rcx", " push rdx", " push rbx", " push rbp", " push rsi", " push rdi", " push r8", " push r9", " push r10", " push r11", " push r12", " push r13", " push r14", " push r15", "", " mov rcx, rsp", // first arg (rcx = MS x64 ABI) " call isr_dispatch", "", " pop r15", " pop r14", " pop r13", " pop r12", " pop r11", " pop r10", " pop r9", " pop r8", " pop rdi", " pop rsi", " pop rbp", " pop rbx", " pop rdx", " pop rcx", " pop rax", " add rsp, 16", // pop vector + error code " iretq", ); /// 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, } /// Called from the asm common handler — dispatches to the right Rust handler. #[no_mangle] unsafe extern "C" fn isr_dispatch(frame: &IntFrame) { unsafe { match frame.vector { 32 => { TICKS.fetch_add(1, Ordering::Relaxed); eoi(0); } 33 => { let byte: u8; core::arch::asm!("in al, dx", out("al") byte, in("dx") 0x60u16, options(nostack, preserves_flags)); // Push scancode to ring buffer (called with interrupts disabled). KEYBUF.push(byte); eoi(1); } _ => {} } } } // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- /// Read one scancode from the interrupt-driven ring buffer (non-blocking). pub fn read_scancode() -> Option { KEYBUF.pop() } /// Global tick counter (incremented by timer IRQ, ~100 Hz). pub static TICKS: AtomicU16 = AtomicU16::new(0); /// Initialise the IDT, remap the PIC, set up the PIT, and enable interrupts. pub fn init() { unsafe { // Read the current code segment selector from CS. let cs: u64; asm!("mov {0}, cs", out(reg) cs, options(nostack, preserves_flags)); let cs = cs as u16; // Build the IDT. let idt_ptr = core::ptr::addr_of_mut!(IDT[0]); let spurious = isr_stub_spurious as *const () as u64; // Fill all 256 entries with the spurious handler. for vec in 0..=255u8 { idt_set_entry(idt_ptr, vec, spurious, cs, 0x0Eu8); } // 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); // Build the IDTR pseudo-descriptor. IDTR = [ (256u16 * 16 - 1), // limit = 256*16 - 1 idt_ptr as u16, // base lo (idt_ptr as u64 >> 16) as u16, // base mid (idt_ptr as u64 >> 32) as u16, // base hi (idt_ptr as u64 >> 48) as u16, // base top ]; // Load IDT. asm!("lidt [{}]", in(reg) core::ptr::addr_of!(IDTR), options(nostack, preserves_flags)); // Remap PIC and mask everything. remap_pic(); // Set up PIT timer at ~100 Hz. pit_set_rate(100); // Unmask timer (IRQ0 → bit 0) and keyboard (IRQ1 → bit 1) on master. let mut mask: u8; asm!("in al, dx", out("al") mask, in("dx") PIC1_DATA, options(nostack, preserves_flags)); mask &= !0x03u8; asm!("out dx, al", in("dx") PIC1_DATA, in("al") mask, options(nostack, preserves_flags)); // Enable interrupts. asm!("sti", options(nostack)); } } // --------------------------------------------------------------------------- // Extern symbols from global_asm! // --------------------------------------------------------------------------- extern "C" { fn isr_stub_32(); fn isr_stub_33(); fn isr_stub_spurious(); }