/// PS/2 keyboard driver — polling, scancode set 1, US QWERTY layout. // Port addresses const DATA_PORT: u16 = 0x60; const STATUS_PORT: u16 = 0x64; // Status register bits const STAT_OUTPUT_FULL: u8 = 0x01; // Modifier bit constants const MOD_LSHIFT: u8 = 0x01; const MOD_RSHIFT: u8 = 0x02; const MOD_LCTRL: u8 = 0x04; const MOD_RCTRL: u8 = 0x08; const MOD_LALT: u8 = 0x10; const MOD_RALT: u8 = 0x20; const MOD_CAPS: u8 = 0x40; /// A decoded key event. #[derive(Clone, Copy, Debug)] pub enum KeyEvent { Char(char), Enter, Backspace, Tab, Escape, /// Ctrl + key (e.g. Ctrl('c') = ^C, Ctrl('s') = ^S). Ctrl(char), /// Ctrl + arrow (0=up, 1=down, 2=left, 3=right) — word/page nav. CtrlArrow(u8), ArrowUp, ArrowDown, ArrowLeft, ArrowRight, Home, End, PageUp, PageDown, Delete, F(u8), Unknown(u8), } // ----- scancode-set-1 translation tables (US QWERTY) ----- /// Normal (unshifted) scancode → ASCII mapping. 0 = unmapped. static SCAN_NORMAL: [u8; 128] = { let mut t = [0u8; 128]; t[0x02] = b'1'; t[0x03] = b'2'; t[0x04] = b'3'; t[0x05] = b'4'; t[0x06] = b'5'; t[0x07] = b'6'; t[0x08] = b'7'; t[0x09] = b'8'; t[0x0A] = b'9'; t[0x0B] = b'0'; t[0x0C] = b'-'; t[0x0D] = b'='; t[0x10] = b'q'; t[0x11] = b'w'; t[0x12] = b'e'; t[0x13] = b'r'; t[0x14] = b't'; t[0x15] = b'y'; t[0x16] = b'u'; t[0x17] = b'i'; t[0x18] = b'o'; t[0x19] = b'p'; t[0x1A] = b'['; t[0x1B] = b']'; t[0x1E] = b'a'; t[0x1F] = b's'; t[0x20] = b'd'; t[0x21] = b'f'; t[0x22] = b'g'; t[0x23] = b'h'; t[0x24] = b'j'; t[0x25] = b'k'; t[0x26] = b'l'; t[0x27] = b';'; t[0x28] = b'\''; t[0x29] = b'`'; t[0x2B] = b'\\'; t[0x2C] = b'z'; t[0x2D] = b'x'; t[0x2E] = b'c'; t[0x2F] = b'v'; t[0x30] = b'b'; t[0x31] = b'n'; t[0x32] = b'm'; t[0x33] = b','; t[0x34] = b'.'; t[0x35] = b'/'; t[0x37] = b'*'; t[0x39] = b' '; t }; /// Shifted scancode → ASCII. static SCAN_SHIFT: [u8; 128] = { let mut t = [0u8; 128]; t[0x02] = b'!'; t[0x03] = b'@'; t[0x04] = b'#'; t[0x05] = b'$'; t[0x06] = b'%'; t[0x07] = b'^'; t[0x08] = b'&'; t[0x09] = b'*'; t[0x0A] = b'('; t[0x0B] = b')'; t[0x0C] = b'_'; t[0x0D] = b'+'; t[0x10] = b'Q'; t[0x11] = b'W'; t[0x12] = b'E'; t[0x13] = b'R'; t[0x14] = b'T'; t[0x15] = b'Y'; t[0x16] = b'U'; t[0x17] = b'I'; t[0x18] = b'O'; t[0x19] = b'P'; t[0x1A] = b'{'; t[0x1B] = b'}'; t[0x1E] = b'A'; t[0x1F] = b'S'; t[0x20] = b'D'; t[0x21] = b'F'; t[0x22] = b'G'; t[0x23] = b'H'; t[0x24] = b'J'; t[0x25] = b'K'; t[0x26] = b'L'; t[0x27] = b':'; t[0x28] = b'"'; t[0x29] = b'~'; t[0x2B] = b'|'; t[0x2C] = b'Z'; t[0x2D] = b'X'; t[0x2E] = b'C'; t[0x2F] = b'V'; t[0x30] = b'B'; t[0x31] = b'N'; t[0x32] = b'M'; t[0x33] = b'<'; t[0x34] = b'>'; t[0x35] = b'?'; t[0x37] = b'*'; t[0x39] = b' '; t }; /// Internal keyboard state struct KbdState { modifiers: u8, got_e0: bool, } static mut KBD: KbdState = KbdState { modifiers: 0, got_e0: false, }; /// Initialise the PS/2 port: drain stale bytes + enable keyboard interrupt. pub fn init() { drain_input(); // Enable the keyboard interrupt on the PS/2 controller. // Read Configuration Byte (command 0x20), set bit 0 (IRQ1 enable), // write back (command 0x60). unsafe { // Tell the keyboard controller we want to read the config byte. // Need to wait for the controller to be ready first. wait_write(); core::arch::asm!("out dx, al", in("dx") 0x64u16, in("al") 0x20u8, options(nostack, preserves_flags)); wait_read(); let mut cfg: u8; core::arch::asm!("in al, dx", out("al") cfg, in("dx") 0x60u16, options(nostack, preserves_flags)); cfg |= 0x01; // enable keyboard interrupt // Write config byte back wait_write(); core::arch::asm!("out dx, al", in("dx") 0x64u16, in("al") 0x60u8, options(nostack, preserves_flags)); wait_write(); core::arch::asm!("out dx, al", in("dx") 0x60u16, in("al") cfg, options(nostack, preserves_flags)); } } /// Wait until the PS/2 controller is ready to receive a command. fn wait_write() { unsafe { loop { let status: u8; core::arch::asm!("in al, dx", out("al") status, in("dx") 0x64u16, options(nostack, preserves_flags)); if status & 0x02 == 0 { // Input buffer empty — controller ready to receive break; } } } } /// Wait until the PS/2 controller has data available to read. fn wait_read() { unsafe { loop { let status: u8; core::arch::asm!("in al, dx", out("al") status, in("dx") 0x64u16, options(nostack, preserves_flags)); if status & 0x01 != 0 { // Output buffer full — data ready to read break; } } } } fn drain_input() { unsafe { loop { let status: u8; core::arch::asm!("in al, dx", out("al") status, in("dx") STATUS_PORT, options(nostack, preserves_flags)); if status & STAT_OUTPUT_FULL == 0 { break; } let _data: u8; core::arch::asm!("in al, dx", out("al") _data, in("dx") DATA_PORT, options(nostack, preserves_flags)); } } } /// Poll for a key event. Blocks via `hlt` until a key is pressed. /// The keyboard interrupt handler pushes raw scancodes to a ring buffer; /// this function pops and decodes them. pub fn poll_key() -> KeyEvent { loop { if let Some(byte) = crate::interrupts::read_scancode() { if let Some(ev) = process_byte(byte) { return ev; } } else { unsafe { core::arch::asm!("sti; hlt", options(nostack, preserves_flags)); } } } } /// Non-blocking poll — read from the interrupt ring buffer if available. pub fn try_poll_key() -> Option { let byte = crate::interrupts::read_scancode()?; process_byte(byte) } /// Process a single scancode byte. Returns Some(KeyEvent) when a visible /// event happens; returns None for modifier changes, releases, and prefixes. fn kbd() -> &'static mut KbdState { unsafe { &mut *core::ptr::addr_of_mut!(KBD) } } fn process_byte(byte: u8) -> Option { let state = kbd(); if byte == 0xE0 { state.got_e0 = true; return None; } if state.got_e0 { state.got_e0 = false; return process_e0(byte); } // Break code (key release) — bit 7 set if byte & 0x80 != 0 { update_modifiers(byte & 0x7F, false, false); return None; } // Make code (key press) update_modifiers(byte, true, false); match byte { 0x01 => Some(KeyEvent::Escape), 0x0E => Some(KeyEvent::Backspace), 0x0F => Some(KeyEvent::Tab), 0x1C => Some(KeyEvent::Enter), 0x37 => Some(KeyEvent::Char('*')), 0x3B..=0x44 => Some(KeyEvent::F(byte - 0x3B + 1)), _ => { let shifted = (state.modifiers & (MOD_LSHIFT | MOD_RSHIFT)) != 0; // CapsLock inverts case for letters let caps = state.modifiers & MOD_CAPS != 0; let table = if shifted { SCAN_SHIFT } else { SCAN_NORMAL }; let mut ch = table[byte as usize]; // Handle CapsLock for letters if caps && ch.is_ascii_alphabetic() { // Swap case: if table gives lowercase, make uppercase; etc. // Since normal table has lowercase, shift table has uppercase: ch = if shifted { ch.to_ascii_lowercase() } else { ch.to_ascii_uppercase() }; } // Ctrl+key — the terminal control-byte protocol (^C, ^D, ^S, ^X, …). let ctrl = state.modifiers & (MOD_LCTRL | MOD_RCTRL) != 0; if ctrl { let lc = ch.to_ascii_lowercase(); return if lc.is_ascii_graphic() { Some(KeyEvent::Ctrl(lc as char)) } else { None // swallow non-graphic Ctrl combos }; } if ch != 0 { Some(KeyEvent::Char(ch as char)) } else { Some(KeyEvent::Unknown(byte)) } } } } /// Process an E0-prefixed scancode. fn process_e0(byte: u8) -> Option { if byte & 0x80 != 0 { update_modifiers(byte & 0x7F, false, true); return None; } update_modifiers(byte, true, true); match byte { 0x48 => Some(if ctrl_held() { KeyEvent::CtrlArrow(0) } else { KeyEvent::ArrowUp }), 0x50 => Some(if ctrl_held() { KeyEvent::CtrlArrow(1) } else { KeyEvent::ArrowDown }), 0x4B => Some(if ctrl_held() { KeyEvent::CtrlArrow(2) } else { KeyEvent::ArrowLeft }), 0x4D => Some(if ctrl_held() { KeyEvent::CtrlArrow(3) } else { KeyEvent::ArrowRight }), 0x47 => Some(KeyEvent::Home), 0x4F => Some(KeyEvent::End), 0x49 => Some(KeyEvent::PageUp), 0x51 => Some(KeyEvent::PageDown), 0x53 => Some(KeyEvent::Delete), 0x35 => Some(KeyEvent::Char('/')), _ => Some(KeyEvent::Unknown(byte)), } } fn update_modifiers(scancode: u8, pressed: bool, e0: bool) { let state = kbd(); match (scancode, e0) { (0x2A, false) => set_mod(state, MOD_LSHIFT, pressed), (0x36, false) => set_mod(state, MOD_RSHIFT, pressed), (0x1D, false) => set_mod(state, MOD_LCTRL, pressed), (0x1D, true) => set_mod(state, MOD_RCTRL, pressed), (0x38, false) => set_mod(state, MOD_LALT, pressed), (0x38, true) => set_mod(state, MOD_RALT, pressed), (0x3A, false) if pressed => { state.modifiers ^= MOD_CAPS; } _ => {} } } fn set_mod(state: &mut KbdState, bit: u8, pressed: bool) { if pressed { state.modifiers |= bit; } else { state.modifiers &= !bit; } } /// Is either Ctrl key held? fn ctrl_held() -> bool { let state = kbd(); state.modifiers & (MOD_LCTRL | MOD_RCTRL) != 0 } // --------------------------------------------------------------------------- // PS/2 mouse — auxiliary channel, IRQ12, stream mode (3-byte packets) // --------------------------------------------------------------------------- // PS/2 controller commands (written to port 0x64). const CMD_AUX_ENABLE: u8 = 0xA8; // enable the auxiliary (second) port const CMD_READ_CFG: u8 = 0x20; // read Configuration Byte → port 0x60 const CMD_WRITE_CFG: u8 = 0x60; // write Configuration Byte ← port 0x60 const CMD_SEND_TO_AUX: u8 = 0xD4; // next byte on 0x60 goes to the mouse // Configuration Byte bits (as sent via CMD_READ_CFG / CMD_WRITE_CFG). // Bit 0 (IRQ1/keyboard) is set by `init`; this driver preserves it by // reading the byte and only ORing in the two mouse bits below. const CFG_AUX_IRQ: u8 = 0x02; // bit 1 — IRQ12 (mouse) const CFG_AUX_CLOCK: u8 = 0x20; // bit 5 — auxiliary port clock enable // Mouse device commands (sent via CMD_SEND_TO_AUX). const AUX_SET_DEFAULTS: u8 = 0xF6; // 100 Hz sample, 4 counts/mm, 1:1 const AUX_ENABLE: u8 = 0xF4; // start data reporting (stream mode) const AUX_ACK: u8 = 0xFA; /// A decoded PS/2 mouse event (one 3-byte packet). /// /// Movement is **screen-relative**: `dx` positive = right, `dy` positive = /// down. (The PS/2 wire format is Y-up-positive; the driver negates it so /// consumers can apply the deltas to framebuffer coordinates directly.) #[derive(Clone, Copy, Debug)] pub struct MouseEvent { pub dx: i16, pub dy: i16, pub left: bool, pub right: bool, pub middle: bool, } /// Packet assembly state — lives in main context (consumer side), fed by /// the raw byte ring that the IRQ12 handler fills. struct MouseState { phase: u8, // 0 = awaiting start byte, 1 = await X, 2 = await Y pkt: [u8; 3], } static mut MOUSE: MouseState = MouseState { phase: 0, pkt: [0; 3], }; static MOUSE_PRESENT: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false); /// True once `mouse_init` found a device that ACKed its setup commands. pub fn mouse_present() -> bool { MOUSE_PRESENT.load(core::sync::atomic::Ordering::Relaxed) } /// Enable the auxiliary (mouse) channel and put the device into stream /// mode. Returns false when no device ACKs (controller without a second /// port, or a missing mouse) — the kernel then just runs keyboard-only. /// /// Must run *before* `interrupts::init` unmasks IRQ12: the init handshake /// reads the data port directly, so any stray mouse bytes would otherwise /// be split across the ISR path mid-handshake. pub fn mouse_init() -> bool { drain_input(); unsafe { // 1. Enable the auxiliary port itself. wait_write(); core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_AUX_ENABLE, options(nostack, preserves_flags)); // 2. Configuration Byte: enable IRQ12 + the aux port clock. Read, // OR the two bits, write back (preserves the keyboard IRQ bit // that ps2::init set). wait_write(); core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_READ_CFG, options(nostack, preserves_flags)); wait_read(); let mut cfg: u8; core::arch::asm!("in al, dx", out("al") cfg, in("dx") DATA_PORT, options(nostack, preserves_flags)); cfg |= CFG_AUX_IRQ | CFG_AUX_CLOCK; wait_write(); core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_WRITE_CFG, options(nostack, preserves_flags)); wait_write(); core::arch::asm!("out dx, al", in("dx") DATA_PORT, in("al") cfg, options(nostack, preserves_flags)); // 3. Device setup. `set defaults` normalises the sample rate / // resolution before enabling reporting, so the packet stream // starts at a known cadence. Both commands need a device ACK; // a timeout here means no mouse — bail out cleanly. if !aux_command(AUX_SET_DEFAULTS) || !aux_command(AUX_ENABLE) { return false; } } MOUSE_PRESENT.store(true, core::sync::atomic::Ordering::Relaxed); true } /// Send one byte to the mouse via the 0xD4 channel and wait for its ACK. /// The wait is bounded so a missing device cannot hang the boot. fn aux_command(cmd: u8) -> bool { unsafe { wait_write(); core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_SEND_TO_AUX, options(nostack, preserves_flags)); wait_write(); core::arch::asm!("out dx, al", in("dx") DATA_PORT, in("al") cmd, options(nostack, preserves_flags)); for _ in 0..100_000 { let status: u8; core::arch::asm!("in al, dx", out("al") status, in("dx") STATUS_PORT, options(nostack, preserves_flags)); if status & STAT_OUTPUT_FULL != 0 { let b: u8; core::arch::asm!("in al, dx", out("al") b, in("dx") DATA_PORT, options(nostack, preserves_flags)); return b == AUX_ACK; } } } false } /// Non-blocking: decode the next pending mouse event, if a full packet has /// arrived. Raw bytes are drained from the IRQ ring; incomplete packets /// stay buffered in `MouseState` until the remaining bytes land. pub fn try_poll_mouse() -> Option { while let Some(b) = crate::interrupts::read_mouse_byte() { if let Some(ev) = mouse_byte(b) { return Some(ev); } } None } /// Feed one raw byte into the packet assembler. Byte 0 of every standard /// PS/2 mouse packet has bit 3 set (the sync marker), so a lost byte (ring /// overrun, init response) is detected on the next packet and the stream /// re-syncs — the marker is only accepted as a packet start. fn mouse_byte(b: u8) -> Option { let m = unsafe { &mut *core::ptr::addr_of_mut!(MOUSE) }; match m.phase { 0 => { if b & 0x08 != 0 { m.pkt[0] = b; m.phase = 1; } None } 1 => { m.pkt[1] = b; m.phase = 2; None } _ => { m.pkt[2] = b; m.phase = 0; Some(MouseEvent { // 9-bit signed: low 8 bits in the data byte, sign bit in // byte 0 (bit 4 = X, bit 5 = Y). dx: signed9(m.pkt[0], m.pkt[1], 0x10), // PS/2 reports Y positive = up; flip to screen convention. dy: -signed9(m.pkt[0], m.pkt[2], 0x20), left: m.pkt[0] & 0x01 != 0, right: m.pkt[0] & 0x02 != 0, middle: m.pkt[0] & 0x04 != 0, }) } } } /// Sign-extend an 8-bit movement byte to 9 bits using the packet's sign /// flag (bit `sign` of byte 0), widening to i16 for consumers. fn signed9(b0: u8, b: u8, sign: u8) -> i16 { let mut v = (b as i8) as i16; if b0 & sign != 0 { // Set the 9th bit (0xFF00 = sign-extension mask for i16). v |= 0xFF00u16 as i16; } v }