Skip to main content

strat9_kernel/arch/x86_64/
mouse.rs

1//! PS/2 Mouse driver (IRQ12)
2//!
3//! Supports standard 3-byte PS/2 mouse and IntelliMouse (4-byte, scroll wheel).
4//! Initialization follows the standard PS/2 controller protocol via I/O ports
5//! 0x60 (data) and 0x64 (command/status).
6
7use super::io::{inb, outb};
8use core::sync::atomic::{AtomicBool, AtomicI32, AtomicU8, Ordering};
9use spin::Mutex;
10
11// ── PS/2 controller ports ─────────────────────────────────────────────────────
12const PS2_DATA: u16 = 0x60;
13const PS2_CMD: u16 = 0x64;
14
15// PS/2 controller commands
16const CMD_READ_CFG: u8 = 0x20;
17const CMD_WRITE_CFG: u8 = 0x60;
18const CMD_ENABLE_AUX: u8 = 0xA8;
19const CMD_SEND_TO_MOUSE: u8 = 0xD4;
20
21// Mouse commands
22const MOUSE_RESET: u8 = 0xFF;
23const MOUSE_SET_DEFAULTS: u8 = 0xF6;
24const MOUSE_ENABLE_STREAM: u8 = 0xF4;
25const MOUSE_GET_ID: u8 = 0xF2;
26const MOUSE_SET_SAMPLE_RATE: u8 = 0xF3;
27const MOUSE_ACK: u8 = 0xFA;
28
29// PS/2 status bits
30const STATUS_OUTPUT_FULL: u8 = 0x01; // data available on port 0x60
31const STATUS_INPUT_FULL: u8 = 0x02; // controller busy, don't write
32
33// ── Event ring buffer ─────────────────────────────────────────────────────────
34const EVENT_BUF_SIZE: usize = 64;
35
36struct EventBuffer {
37    buf: [MouseEvent; EVENT_BUF_SIZE],
38    head: usize,
39    tail: usize,
40}
41
42static EVENT_BUF: Mutex<EventBuffer> = Mutex::new(EventBuffer {
43    buf: [MouseEvent {
44        dx: 0,
45        dy: 0,
46        dz: 0,
47        left: false,
48        right: false,
49        middle: false,
50    }; EVENT_BUF_SIZE],
51    head: 0,
52    tail: 0,
53});
54
55// ── Absolute cursor position (accumulated) ───────────────────────────────────
56static MOUSE_ABS_X: AtomicI32 = AtomicI32::new(0);
57static MOUSE_ABS_Y: AtomicI32 = AtomicI32::new(0);
58// Cached screen bounds to avoid locking VGA from IRQ context.
59static SCREEN_W: AtomicI32 = AtomicI32::new(1280);
60static SCREEN_H: AtomicI32 = AtomicI32::new(800);
61
62// ── Packet state machine ──────────────────────────────────────────────────────
63/// Current byte index within the current packet (0, 1, 2, [3])
64static MOUSE_CYCLE: AtomicU8 = AtomicU8::new(0);
65/// Whether IntelliMouse (4-byte) mode is active
66static INTELLIMOUSE: AtomicBool = AtomicBool::new(false);
67/// Mouse is initialized and streaming
68pub static MOUSE_READY: AtomicBool = AtomicBool::new(false);
69
70static PACKET_BUF: Mutex<[u8; 4]> = Mutex::new([0u8; 4]);
71
72/// A decoded mouse event.
73#[derive(Clone, Copy)]
74pub struct MouseEvent {
75    /// Horizontal delta (positive = right)
76    pub dx: i16,
77    /// Vertical delta (positive = down, matching screen coordinates)
78    pub dy: i16,
79    /// Scroll wheel delta (negative = scroll down / forward)
80    pub dz: i8,
81    pub left: bool,
82    pub right: bool,
83    pub middle: bool,
84}
85
86// ── PS/2 helpers ──────────────────────────────────────────────────────────────
87
88/// Spin until the PS/2 input buffer is empty (safe to write).
89#[inline]
90fn wait_write() {
91    for _ in 0..100_000u32 {
92        if unsafe { inb(PS2_CMD) } & STATUS_INPUT_FULL == 0 {
93            return;
94        }
95        core::hint::spin_loop();
96    }
97}
98
99/// Spin until the PS/2 output buffer has data (safe to read).
100#[inline]
101fn wait_read() {
102    for _ in 0..100_000u32 {
103        if unsafe { inb(PS2_CMD) } & STATUS_OUTPUT_FULL != 0 {
104            return;
105        }
106        core::hint::spin_loop();
107    }
108}
109
110/// Read a byte from the PS/2 data port (waits for data).
111fn ps2_read() -> u8 {
112    wait_read();
113    unsafe { inb(PS2_DATA) }
114}
115
116/// Write a byte to the PS/2 command port.
117fn ps2_write_cmd(cmd: u8) {
118    wait_write();
119    unsafe { outb(PS2_CMD, cmd) };
120}
121
122/// Write a byte to the PS/2 data port.
123fn ps2_write_data(data: u8) {
124    wait_write();
125    unsafe { outb(PS2_DATA, data) };
126}
127
128/// Send a byte directly to the mouse (via the 0xD4 mux).
129fn mouse_write(data: u8) {
130    ps2_write_cmd(CMD_SEND_TO_MOUSE);
131    ps2_write_data(data);
132}
133
134/// Send a command to the mouse and wait for ACK. Returns true on success.
135fn mouse_cmd(cmd: u8) -> bool {
136    mouse_write(cmd);
137    let ack = ps2_read();
138    ack == MOUSE_ACK
139}
140
141/// Send a command + argument to the mouse and wait for ACK.
142fn mouse_cmd_arg(cmd: u8, arg: u8) -> bool {
143    mouse_write(cmd);
144    let ack = ps2_read();
145    if ack != MOUSE_ACK {
146        return false;
147    }
148    mouse_write(arg);
149    let ack2 = ps2_read();
150    ack2 == MOUSE_ACK
151}
152
153/// Drain any pending bytes in the PS/2 output buffer.
154fn flush_output() {
155    for _ in 0..16 {
156        if unsafe { inb(PS2_CMD) } & STATUS_OUTPUT_FULL == 0 {
157            break;
158        }
159        unsafe { inb(PS2_DATA) };
160    }
161}
162
163// ── Initialization ────────────────────────────────────────────────────────────
164
165/// Initialize the PS/2 mouse.
166///
167/// Must be called after the IDT and I/O APIC are set up (IRQ12 routed).
168/// Returns `true` on success.
169pub fn init() -> bool {
170    flush_output();
171
172    // Enable the PS/2 auxiliary (mouse) channel
173    ps2_write_cmd(CMD_ENABLE_AUX);
174
175    // Enable IRQ12 in the PS/2 controller configuration byte
176    ps2_write_cmd(CMD_READ_CFG);
177    let mut cfg = ps2_read();
178    cfg |= 0x02; // enable IRQ12 (mouse interrupt)
179    cfg &= !0x20; // clear "mouse clock disable" bit
180    ps2_write_cmd(CMD_WRITE_CFG);
181    ps2_write_data(cfg);
182
183    // Reset the mouse
184    mouse_cmd(MOUSE_RESET);
185    // Drain the reset response (0xAA 0x00)
186    flush_output();
187
188    // Set defaults
189    if !mouse_cmd(MOUSE_SET_DEFAULTS) {
190        crate::serial_println!("[mouse] set_defaults failed");
191        return false;
192    }
193
194    // Attempt IntelliMouse activation (magic sequence: rates 200, 100, 80)
195    let intellimouse = try_enable_intellimouse();
196    INTELLIMOUSE.store(intellimouse, Ordering::Relaxed);
197    if intellimouse {
198        crate::serial_println!("[mouse] IntelliMouse (scroll wheel) detected");
199    } else {
200        crate::serial_println!("[mouse] Standard PS/2 mouse (3-byte packets)");
201    }
202
203    // Enable mouse data streaming
204    if !mouse_cmd(MOUSE_ENABLE_STREAM) {
205        crate::serial_println!("[mouse] enable_stream failed");
206        return false;
207    }
208
209    // Cache screen size once (safe in init context) to avoid locking VGA in IRQs.
210    let w = crate::arch::x86_64::vga::width() as i32;
211    let h = crate::arch::x86_64::vga::height() as i32;
212    if w > 0 {
213        SCREEN_W.store(w, Ordering::Relaxed);
214    }
215    if h > 0 {
216        SCREEN_H.store(h, Ordering::Relaxed);
217    }
218
219    MOUSE_READY.store(true, Ordering::Relaxed);
220    crate::serial_println!("[mouse] PS/2 mouse initialized OK");
221    true
222}
223
224/// Try activating IntelliMouse scroll wheel mode.
225/// Returns true if the mouse identifies as IntelliMouse (ID = 0x03).
226fn try_enable_intellimouse() -> bool {
227    // Magic rate sequence to unlock IntelliMouse mode
228    mouse_cmd_arg(MOUSE_SET_SAMPLE_RATE, 200);
229    mouse_cmd_arg(MOUSE_SET_SAMPLE_RATE, 100);
230    mouse_cmd_arg(MOUSE_SET_SAMPLE_RATE, 80);
231
232    // Query device ID
233    mouse_write(MOUSE_GET_ID);
234    let ack = ps2_read();
235    if ack != MOUSE_ACK {
236        return false;
237    }
238    let id = ps2_read();
239    id == 0x03
240}
241
242// ── IRQ12 handler ─────────────────────────────────────────────────────────────
243
244/// Called from the IDT IRQ12 handler (interrupt context, interrupts disabled).
245///
246/// Reads one byte from the PS/2 data port and advances the packet state machine.
247pub fn handle_irq() {
248    let byte = unsafe { inb(PS2_DATA) };
249    let cycle = MOUSE_CYCLE.load(Ordering::Relaxed);
250    let packet_len: u8 = if INTELLIMOUSE.load(Ordering::Relaxed) {
251        4
252    } else {
253        3
254    };
255
256    // Byte 0 sanity check: bit 3 must always be set; resync if not
257    if cycle == 0 && (byte & 0x08) == 0 {
258        // Out of sync – just drop and wait for a valid first byte
259        return;
260    }
261
262    // Store byte in packet buffer
263    {
264        if let Some(mut buf) = PACKET_BUF.try_lock() {
265            buf[cycle as usize] = byte;
266        } else {
267            return; // IRQ re-entrancy guard: drop
268        }
269    }
270
271    let next_cycle = cycle + 1;
272    if next_cycle >= packet_len {
273        // Full packet received – decode it
274        MOUSE_CYCLE.store(0, Ordering::Relaxed);
275        decode_packet();
276    } else {
277        MOUSE_CYCLE.store(next_cycle, Ordering::Relaxed);
278    }
279}
280
281/// Decode the current packet buffer and push a `MouseEvent`.
282fn decode_packet() {
283    let buf = {
284        match PACKET_BUF.try_lock() {
285            Some(b) => *b,
286            None => return,
287        }
288    };
289
290    let flags = buf[0];
291    let raw_dx = buf[1] as i16;
292    let raw_dy = buf[2] as i16;
293
294    // Sign-extend dx / dy using bits 4 and 5 of flags (overflow bits 6/7 ignored)
295    let dx: i16 = if flags & 0x10 != 0 {
296        raw_dx - 256
297    } else {
298        raw_dx
299    };
300    // Y axis: PS/2 uses positive = up; we flip to positive = down (screen coords)
301    let dy_ps2: i16 = if flags & 0x20 != 0 {
302        raw_dy - 256
303    } else {
304        raw_dy
305    };
306    let dy = -dy_ps2;
307
308    let dz: i8 = if INTELLIMOUSE.load(Ordering::Relaxed) {
309        // Lower 4 bits of byte 3, sign-extend
310        let raw = (buf[3] & 0x0F) as i8;
311        if raw & 0x08 != 0 {
312            raw | -16i8
313        } else {
314            raw
315        }
316    } else {
317        0
318    };
319
320    let left = flags & 0x01 != 0;
321    let right = flags & 0x02 != 0;
322    let middle = flags & 0x04 != 0;
323
324    // Clamp and accumulate absolute position using cached bounds (IRQ-safe)
325    let scr_w = SCREEN_W.load(Ordering::Relaxed);
326    let scr_h = SCREEN_H.load(Ordering::Relaxed);
327    let max_x = if scr_w > 0 { scr_w - 1 } else { 1279 };
328    let max_y = if scr_h > 0 { scr_h - 1 } else { 799 };
329
330    let prev_x = MOUSE_ABS_X.load(Ordering::Relaxed);
331    let prev_y = MOUSE_ABS_Y.load(Ordering::Relaxed);
332    let new_x = (prev_x + dx as i32).clamp(0, max_x);
333    let new_y = (prev_y + dy as i32).clamp(0, max_y);
334    MOUSE_ABS_X.store(new_x, Ordering::Relaxed);
335    MOUSE_ABS_Y.store(new_y, Ordering::Relaxed);
336
337    let event = MouseEvent {
338        dx,
339        dy,
340        dz,
341        left,
342        right,
343        middle,
344    };
345
346    // Push to event ring buffer (non-blocking, drop on overflow)
347    if let Some(mut q) = EVENT_BUF.try_lock() {
348        let tail = q.tail;
349        let next_tail = (tail + 1) % EVENT_BUF_SIZE;
350        if next_tail != q.head {
351            q.buf[tail] = event;
352            q.tail = next_tail;
353        }
354    }
355}
356
357// ── Public API ────────────────────────────────────────────────────────────────
358
359/// Dequeue the oldest mouse event (non-blocking).
360///
361/// Must be called from task context only (not interrupt context).
362pub fn read_event() -> Option<MouseEvent> {
363    // Disable interrupts before taking the lock to prevent IRQ12 deadlock.
364    let saved = super::save_flags_and_cli();
365    let result = {
366        let mut q = EVENT_BUF.lock();
367        if q.head == q.tail {
368            None
369        } else {
370            let ev = q.buf[q.head];
371            q.head = (q.head + 1) % EVENT_BUF_SIZE;
372            Some(ev)
373        }
374    };
375    super::restore_flags(saved);
376    result
377}
378
379/// Returns `true` if at least one mouse event is pending.
380pub fn has_event() -> bool {
381    let saved = super::save_flags_and_cli();
382    let result = {
383        let q = EVENT_BUF.lock();
384        q.head != q.tail
385    };
386    super::restore_flags(saved);
387    result
388}
389
390/// Returns the current accumulated mouse position (pixel coordinates).
391pub fn mouse_pos() -> (i32, i32) {
392    (
393        MOUSE_ABS_X.load(Ordering::Relaxed),
394        MOUSE_ABS_Y.load(Ordering::Relaxed),
395    )
396}