summaryrefslogtreecommitdiffstats
path: root/rsnix/src/ps2.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rsnix/src/ps2.rs')
-rw-r--r--rsnix/src/ps2.rs183
1 files changed, 183 insertions, 0 deletions
diff --git a/rsnix/src/ps2.rs b/rsnix/src/ps2.rs
index 35848b6..5f4a804 100644
--- a/rsnix/src/ps2.rs
+++ b/rsnix/src/ps2.rs
@@ -16,6 +16,7 @@ const MOD_RALT: u8 = 0x20;
16const MOD_CAPS: u8 = 0x40; 16const MOD_CAPS: u8 = 0x40;
17 17
18/// A decoded key event. 18/// A decoded key event.
19#[derive(Clone, Copy, Debug)]
19pub enum KeyEvent { 20pub enum KeyEvent {
20 Char(char), 21 Char(char),
21 Enter, 22 Enter,
@@ -400,3 +401,185 @@ fn ctrl_held() -> bool {
400 let state = kbd(); 401 let state = kbd();
401 state.modifiers & (MOD_LCTRL | MOD_RCTRL) != 0 402 state.modifiers & (MOD_LCTRL | MOD_RCTRL) != 0
402} 403}
404
405// ---------------------------------------------------------------------------
406// PS/2 mouse — auxiliary channel, IRQ12, stream mode (3-byte packets)
407// ---------------------------------------------------------------------------
408
409// PS/2 controller commands (written to port 0x64).
410const CMD_AUX_ENABLE: u8 = 0xA8; // enable the auxiliary (second) port
411const CMD_READ_CFG: u8 = 0x20; // read Configuration Byte → port 0x60
412const CMD_WRITE_CFG: u8 = 0x60; // write Configuration Byte ← port 0x60
413const CMD_SEND_TO_AUX: u8 = 0xD4; // next byte on 0x60 goes to the mouse
414
415// Configuration Byte bits (as sent via CMD_READ_CFG / CMD_WRITE_CFG).
416// Bit 0 (IRQ1/keyboard) is set by `init`; this driver preserves it by
417// reading the byte and only ORing in the two mouse bits below.
418const CFG_AUX_IRQ: u8 = 0x02; // bit 1 — IRQ12 (mouse)
419const CFG_AUX_CLOCK: u8 = 0x20; // bit 5 — auxiliary port clock enable
420
421// Mouse device commands (sent via CMD_SEND_TO_AUX).
422const AUX_SET_DEFAULTS: u8 = 0xF6; // 100 Hz sample, 4 counts/mm, 1:1
423const AUX_ENABLE: u8 = 0xF4; // start data reporting (stream mode)
424const AUX_ACK: u8 = 0xFA;
425
426/// A decoded PS/2 mouse event (one 3-byte packet).
427///
428/// Movement is **screen-relative**: `dx` positive = right, `dy` positive =
429/// down. (The PS/2 wire format is Y-up-positive; the driver negates it so
430/// consumers can apply the deltas to framebuffer coordinates directly.)
431#[derive(Clone, Copy, Debug)]
432pub struct MouseEvent {
433 pub dx: i16,
434 pub dy: i16,
435 pub left: bool,
436 pub right: bool,
437 pub middle: bool,
438}
439
440/// Packet assembly state — lives in main context (consumer side), fed by
441/// the raw byte ring that the IRQ12 handler fills.
442struct MouseState {
443 phase: u8, // 0 = awaiting start byte, 1 = await X, 2 = await Y
444 pkt: [u8; 3],
445}
446
447static mut MOUSE: MouseState = MouseState {
448 phase: 0,
449 pkt: [0; 3],
450};
451static MOUSE_PRESENT: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
452
453/// True once `mouse_init` found a device that ACKed its setup commands.
454pub fn mouse_present() -> bool {
455 MOUSE_PRESENT.load(core::sync::atomic::Ordering::Relaxed)
456}
457
458/// Enable the auxiliary (mouse) channel and put the device into stream
459/// mode. Returns false when no device ACKs (controller without a second
460/// port, or a missing mouse) — the kernel then just runs keyboard-only.
461///
462/// Must run *before* `interrupts::init` unmasks IRQ12: the init handshake
463/// reads the data port directly, so any stray mouse bytes would otherwise
464/// be split across the ISR path mid-handshake.
465pub fn mouse_init() -> bool {
466 drain_input();
467 unsafe {
468 // 1. Enable the auxiliary port itself.
469 wait_write();
470 core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_AUX_ENABLE,
471 options(nostack, preserves_flags));
472
473 // 2. Configuration Byte: enable IRQ12 + the aux port clock. Read,
474 // OR the two bits, write back (preserves the keyboard IRQ bit
475 // that ps2::init set).
476 wait_write();
477 core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_READ_CFG,
478 options(nostack, preserves_flags));
479 wait_read();
480 let mut cfg: u8;
481 core::arch::asm!("in al, dx", out("al") cfg, in("dx") DATA_PORT,
482 options(nostack, preserves_flags));
483 cfg |= CFG_AUX_IRQ | CFG_AUX_CLOCK;
484 wait_write();
485 core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_WRITE_CFG,
486 options(nostack, preserves_flags));
487 wait_write();
488 core::arch::asm!("out dx, al", in("dx") DATA_PORT, in("al") cfg,
489 options(nostack, preserves_flags));
490
491 // 3. Device setup. `set defaults` normalises the sample rate /
492 // resolution before enabling reporting, so the packet stream
493 // starts at a known cadence. Both commands need a device ACK;
494 // a timeout here means no mouse — bail out cleanly.
495 if !aux_command(AUX_SET_DEFAULTS) || !aux_command(AUX_ENABLE) {
496 return false;
497 }
498 }
499 MOUSE_PRESENT.store(true, core::sync::atomic::Ordering::Relaxed);
500 true
501}
502
503/// Send one byte to the mouse via the 0xD4 channel and wait for its ACK.
504/// The wait is bounded so a missing device cannot hang the boot.
505fn aux_command(cmd: u8) -> bool {
506 unsafe {
507 wait_write();
508 core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_SEND_TO_AUX,
509 options(nostack, preserves_flags));
510 wait_write();
511 core::arch::asm!("out dx, al", in("dx") DATA_PORT, in("al") cmd,
512 options(nostack, preserves_flags));
513 for _ in 0..100_000 {
514 let status: u8;
515 core::arch::asm!("in al, dx", out("al") status, in("dx") STATUS_PORT,
516 options(nostack, preserves_flags));
517 if status & STAT_OUTPUT_FULL != 0 {
518 let b: u8;
519 core::arch::asm!("in al, dx", out("al") b, in("dx") DATA_PORT,
520 options(nostack, preserves_flags));
521 return b == AUX_ACK;
522 }
523 }
524 }
525 false
526}
527
528/// Non-blocking: decode the next pending mouse event, if a full packet has
529/// arrived. Raw bytes are drained from the IRQ ring; incomplete packets
530/// stay buffered in `MouseState` until the remaining bytes land.
531pub fn try_poll_mouse() -> Option<MouseEvent> {
532 while let Some(b) = crate::interrupts::read_mouse_byte() {
533 if let Some(ev) = mouse_byte(b) {
534 return Some(ev);
535 }
536 }
537 None
538}
539
540/// Feed one raw byte into the packet assembler. Byte 0 of every standard
541/// PS/2 mouse packet has bit 3 set (the sync marker), so a lost byte (ring
542/// overrun, init response) is detected on the next packet and the stream
543/// re-syncs — the marker is only accepted as a packet start.
544fn mouse_byte(b: u8) -> Option<MouseEvent> {
545 let m = unsafe { &mut *core::ptr::addr_of_mut!(MOUSE) };
546 match m.phase {
547 0 => {
548 if b & 0x08 != 0 {
549 m.pkt[0] = b;
550 m.phase = 1;
551 }
552 None
553 }
554 1 => {
555 m.pkt[1] = b;
556 m.phase = 2;
557 None
558 }
559 _ => {
560 m.pkt[2] = b;
561 m.phase = 0;
562 Some(MouseEvent {
563 // 9-bit signed: low 8 bits in the data byte, sign bit in
564 // byte 0 (bit 4 = X, bit 5 = Y).
565 dx: signed9(m.pkt[0], m.pkt[1], 0x10),
566 // PS/2 reports Y positive = up; flip to screen convention.
567 dy: -signed9(m.pkt[0], m.pkt[2], 0x20),
568 left: m.pkt[0] & 0x01 != 0,
569 right: m.pkt[0] & 0x02 != 0,
570 middle: m.pkt[0] & 0x04 != 0,
571 })
572 }
573 }
574}
575
576/// Sign-extend an 8-bit movement byte to 9 bits using the packet's sign
577/// flag (bit `sign` of byte 0), widening to i16 for consumers.
578fn signed9(b0: u8, b: u8, sign: u8) -> i16 {
579 let mut v = (b as i8) as i16;
580 if b0 & sign != 0 {
581 // Set the 9th bit (0xFF00 = sign-extension mask for i16).
582 v |= 0xFF00u16 as i16;
583 }
584 v
585}