From 9b9758e5ee2564b04685eadf2b9ce23dd1060b86 Mon Sep 17 00:00:00 2001 From: salmonik Date: Sat, 1 Aug 2026 19:34:40 +0200 Subject: rsnix v0.4.0: GUI input foundation — PS/2 mouse + scheduler message queues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mouse (IRQ12, stream mode): ByteRing generalized for both PS/2 devices, ISR routes data-port bytes by the controller's aux bit; mouse_init with bounded-ACK handshake (boots without a mouse); packet decoder with bit-3 resync, screen-relative deltas, buttons. Exposed as /dev/mouse, the `mouse` stream command, and `make test-mouse`. Message queues (ADR 0003's GUI-era primitive): every task gets a static 16-slot TaskMsg ring; send/recv/recv_blocking with direct wake + tick scan safety net for the lost-wakeup race; `msgrecv`/`send` commands, `mouse ` routes real IRQ12 events into a task's queue, jobs shows q=. `make test-msg` + `make test-disk` regression pass. --- rsnix/src/interrupts.rs | 68 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 13 deletions(-) (limited to 'rsnix/src/interrupts.rs') diff --git a/rsnix/src/interrupts.rs b/rsnix/src/interrupts.rs index 6abd54d..7ea0b07 100644 --- a/rsnix/src/interrupts.rs +++ b/rsnix/src/interrupts.rs @@ -153,19 +153,22 @@ pub fn uptime_ms() -> u64 { } // --------------------------------------------------------------------------- -// Lock-free SPSC ring buffer for keyboard scancodes +// Lock-free SPSC ring buffers for PS/2 input (keyboard scancodes + mouse +// bytes). One per device: the ISR routes each data-port byte to the right +// ring via the controller status register's aux bit (bit 5), so the two +// devices never steal each other's bytes even when both are pending at once. // --------------------------------------------------------------------------- /// Single-producer (ISR) / single-consumer (main) lock-free ring buffer. -struct KeyBuf { +struct ByteRing { 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 {} +unsafe impl Sync for ByteRing {} -impl KeyBuf { +impl ByteRing { 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. @@ -205,7 +208,8 @@ impl KeyBuf { } } -static KEYBUF: KeyBuf = KeyBuf::new(); +static KEYBUF: ByteRing = ByteRing::new(); +static MOUSEBUF: ByteRing = ByteRing::new(); // --------------------------------------------------------------------------- // Interrupt stubs (assembly) and dispatch @@ -240,6 +244,14 @@ core::arch::global_asm!( " push 33", " jmp isr_common", "", + // PS/2 mouse IRQ (IRQ12 → vector 44). + ".globl isr_stub_44", + ".p2align 4", + "isr_stub_44:", + " push 0", + " push 44", + " 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", @@ -345,13 +357,27 @@ unsafe extern "C" fn isr_dispatch(frame: &IntFrame) -> usize { eoi(0); crate::sched::on_tick(fp) } - 33 => { - let byte: u8; - core::arch::asm!("in al, dx", out("al") byte, in("dx") 0x60u16, + 33 | 44 => { + // PS/2 data byte. The controller's status register tells us + // which device it came from (bit 5 = auxiliary/mouse), so the + // byte is routed to the matching ring — both handlers must + // check, because when both devices have data pending the + // first IRQ serviced can find the other device's byte in the + // output buffer. + let status: u8; + core::arch::asm!("in al, dx", out("al") status, in("dx") 0x64u16, options(nostack, preserves_flags)); - // Push scancode to ring buffer (called with interrupts disabled). - KEYBUF.push(byte); - eoi(1); + if status & 0x01 != 0 { + let byte: u8; + core::arch::asm!("in al, dx", out("al") byte, in("dx") 0x60u16, + options(nostack, preserves_flags)); + if status & 0x20 != 0 { + MOUSEBUF.push(byte); + } else { + KEYBUF.push(byte); + } + } + eoi((frame.vector - 32) as u8); fp } 8 => { @@ -381,6 +407,12 @@ pub fn read_scancode() -> Option { KEYBUF.pop() } +/// Read one mouse data byte from the interrupt-driven ring buffer +/// (non-blocking). Packets are assembled in `ps2::try_poll_mouse`. +pub fn read_mouse_byte() -> Option { + MOUSEBUF.pop() +} + /// Global tick counter (incremented by timer IRQ, ~100 Hz). u64 so /// uptime never wraps; `uptime_ms` interpolates the PIT counter for the /// sub-tick fraction. @@ -406,6 +438,8 @@ pub fn init() { // Override vectors for interrupt we actually handle. 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); + // PS/2 mouse (IRQ12). + idt_set_entry(idt_ptr, 44, isr_stub_44 as *const () as u64, cs, 0x0Eu8, 0); // Yield (software interrupt). idt_set_entry( idt_ptr, @@ -439,13 +473,20 @@ pub fn init() { pit_set_rate(); PIT_READY.store(true, Ordering::Relaxed); - // Unmask timer (IRQ0 → bit 0) and keyboard (IRQ1 → bit 1) on master. + // Unmask timer (IRQ0), keyboard (IRQ1) and the cascade line (IRQ2) + // on the master; unmask IRQ12 (PS/2 mouse) on the slave. let mut mask: u8; asm!("in al, dx", out("al") mask, in("dx") PIC1_DATA, options(nostack, preserves_flags)); - mask &= !0x03u8; + mask &= !0x07u8; asm!("out dx, al", in("dx") PIC1_DATA, in("al") mask, options(nostack, preserves_flags)); + let mut mask2: u8; + asm!("in al, dx", out("al") mask2, in("dx") PIC2_DATA, + options(nostack, preserves_flags)); + mask2 &= !0x10u8; // IRQ12 → bit 4 of the slave PIC + asm!("out dx, al", in("dx") PIC2_DATA, in("al") mask2, + options(nostack, preserves_flags)); // Enable interrupts. asm!("sti", options(nostack)); @@ -469,6 +510,7 @@ unsafe extern "C" { fn isr_stub_14(); fn isr_stub_32(); fn isr_stub_33(); + fn isr_stub_44(); fn isr_stub_60(); fn isr_stub_spurious(); } -- cgit v1.2.3