--- a/drivers/input/ps2d/src/controller.rs 21:31:04.000000000 +0100 +++ b/drivers/input/ps2d/src/controller.rs 2026-05-02 02:50:19.907179957 +0100 @@ -97,6 +97,14 @@ const DEFAULT_TIMEOUT: u64 = 50_000; // Reset timeout in microseconds const RESET_TIMEOUT: u64 = 1_000_000; +// Maximum bytes to drain during flush (Linux: I8042_BUFFER_SIZE) +const FLUSH_LIMIT: usize = 4096; +// Controller self-test pass value (Linux: I8042_RET_CTL_TEST) +const SELFTEST_PASS: u8 = 0x55; +// Controller self-test retries (Linux: 5 attempts) +const SELFTEST_RETRIES: usize = 5; +// AUX port test pass value (Linux returns 0x00 on success) +const AUX_TEST_PASS: u8 = 0x00; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub struct Ps2 { @@ -261,6 +269,30 @@ self.write(command as u8) } + pub fn set_leds(&mut self, caps: bool, num: bool, scroll: bool) { + let mut led_byte = 0u8; + if scroll { led_byte |= 1; } + if num { led_byte |= 2; } + if caps { led_byte |= 4; } + if let Err(err) = self.keyboard_command_inner(0xED) { + warn!("ps2d: failed to send LED command 0xED: {:?}", err); + return; + } + match self.read_timeout(DEFAULT_TIMEOUT) { + Ok(0xFA) => { + if let Err(err) = self.write(led_byte) { + warn!("ps2d: failed to send LED byte {:02X}: {:?}", led_byte, err); + } + } + Ok(val) => { + warn!("ps2d: LED command ACK expected 0xFA, got {:02X}", val); + } + Err(err) => { + warn!("ps2d: LED command ACK timeout: {:?}", err); + } + } + } + pub fn next(&mut self) -> Option<(bool, u8)> { let status = self.status(); if status.contains(StatusFlags::OUTPUT_FULL) { @@ -271,6 +303,50 @@ } } + /// Drain all pending bytes from the controller output buffer. + /// Borrowed from Linux i8042_flush(): stale firmware/BIOS bytes can be + /// misinterpreted as device responses during initialization. + fn flush(&mut self) -> usize { + let mut count = 0; + while self.status().contains(StatusFlags::OUTPUT_FULL) { + if count >= FLUSH_LIMIT { + warn!("flush: exceeded limit, controller may be stuck"); + break; + } + let data = self.data.read(); + trace!("flush: discarded {:02X}", data); + count += 1; + } + if count > 0 { + debug!("flushed {} stale bytes from controller", count); + } + count + } + + /// Test the AUX (mouse) port via controller command 0xA9. + /// Borrowed from Linux: verifies electrical connectivity before + /// attempting to talk to the mouse. Returns true if the port passed. + fn test_aux_port(&mut self) -> bool { + if let Err(err) = self.command(Command::TestSecond) { + warn!("aux port test command failed: {:?}", err); + return false; + } + match self.read() { + Ok(AUX_TEST_PASS) => { + debug!("aux port test passed"); + true + } + Ok(val) => { + warn!("aux port test failed: {:02X}", val); + false + } + Err(err) => { + warn!("aux port test read timeout: {:?}", err); + false + } + } + } + pub fn init_keyboard(&mut self) -> Result<(), Error> { let mut b; @@ -308,66 +384,125 @@ } pub fn init(&mut self) -> Result<(), Error> { + // Linux i8042_controller_check(): verify controller is present by + // flushing any stale data. A stuck output buffer means no controller. + self.flush(); + + // Bare-metal controllers may be slow after firmware handoff. + // Give the controller a moment to finish POST before sending commands. + std::thread::sleep(std::time::Duration::from_millis(50)); + { - // Disable devices - self.command(Command::DisableFirst)?; - self.command(Command::DisableSecond)?; + // Disable both ports first — use retry because the controller + // may still be settling or temporarily unresponsive. + // Failure here is non-fatal: we continue and attempt the rest + // of initialization. A truly absent controller will fail later + // at self-test or keyboard reset. + if let Err(err) = self.retry( + format_args!("disable first port"), + 3, + |x| x.command(Command::DisableFirst), + ) { + warn!("disable first port failed: {:?}", err); + } + if let Err(err) = self.retry( + format_args!("disable second port"), + 3, + |x| x.command(Command::DisableSecond), + ) { + warn!("disable second port failed: {:?}", err); + } } - // Disable clocks, disable interrupts, and disable translate + // Flush again after disabling — firmware may have queued more bytes + self.flush(); + + // Linux i8042_controller_init() step 1: write a known-safe config + // (interrupts off, both ports disabled) so stale config can't cause + // spurious interrupts during the rest of init. { - // Since the default config may have interrupts enabled, and the kernel may eat up - // our data in that case, we will write a config without reading the current one let config = ConfigFlags::POST_PASSED | ConfigFlags::FIRST_DISABLED | ConfigFlags::SECOND_DISABLED; self.set_config(config)?; } - // The keyboard seems to still collect bytes even when we disable - // the port, so we must disable the keyboard too + // Linux i8042_controller_selftest(): retry up to 5 times with delay. + // "On some really fragile systems this does not take the first time." + { + let mut passed = false; + for attempt in 0..SELFTEST_RETRIES { + if let Err(err) = self.command(Command::TestController) { + warn!("self-test command failed (attempt {}): {:?}", attempt + 1, err); + continue; + } + match self.read() { + Ok(SELFTEST_PASS) => { + passed = true; + break; + } + Ok(val) => { + warn!( + "self-test unexpected value {:02X} (attempt {}/{})", + val, + attempt + 1, + SELFTEST_RETRIES + ); + } + Err(err) => { + warn!("self-test read timeout (attempt {}): {:?}", attempt + 1, err); + } + } + // Linux: msleep(50) between retries + std::thread::sleep(std::time::Duration::from_millis(50)); + } + if !passed { + // Linux on x86: "giving up on controller selftest, continuing anyway" + warn!("controller self-test did not pass after {} attempts, continuing", SELFTEST_RETRIES); + } + } + + // Flush any bytes the self-test may have left behind + self.flush(); + + // Linux i8042_controller_init() step 2: set keyboard defaults + // (disable scanning so keyboard doesn't send scancodes during init) self.retry(format_args!("keyboard defaults"), 4, |x| { - // Set defaults and disable scanning let b = x.keyboard_command(KeyboardCommand::SetDefaultsDisable)?; if b != 0xFA { error!("keyboard failed to set defaults: {:02X}", b); return Err(Error::CommandRetry); } - Ok(b) })?; - { - // Perform the self test - self.command(Command::TestController)?; - let r = self.read()?; - if r != 0x55 { - warn!("self test unexpected value: {:02X}", r); - } - } - // Initialize keyboard if let Err(err) = self.init_keyboard() { error!("failed to initialize keyboard: {:?}", err); return Err(err); } - // Enable second device - let enable_mouse = match self.command(Command::EnableSecond) { - Ok(()) => true, - Err(err) => { - error!("failed to initialize mouse: {:?}", err); - false + // Linux: test AUX port (command 0xA9) before enabling. + // Skips mouse init entirely if the port is not electrically present. + let aux_ok = self.test_aux_port(); + + // Enable second device (mouse) only if AUX port tested OK + let enable_mouse = if aux_ok { + match self.command(Command::EnableSecond) { + Ok(()) => true, + Err(err) => { + warn!("failed to enable aux port after test passed: {:?}", err); + false + } } + } else { + info!("skipping mouse init: aux port test did not pass"); + false }; { - // Enable keyboard data reporting - // Use inner function to prevent retries - // Response is ignored since scanning is now on if let Err(err) = self.keyboard_command_inner(KeyboardCommand::EnableReporting as u8) { error!("failed to initialize keyboard reporting: {:?}", err); - //TODO: fix by using interrupts? } } --- a/drivers/input/ps2d/src/state.rs 21:31:04.000000000 +0100 +++ b/drivers/input/ps2d/src/state.rs 2026-05-02 04:22:27.342569199 +0100 @@ -1,4 +1,4 @@ -use inputd::ProducerHandle; +use inputd::InputProducer; use log::{error, warn}; use orbclient::{ButtonEvent, KeyEvent, MouseEvent, MouseRelativeEvent, ScrollEvent}; use std::{ @@ -44,7 +44,8 @@ ps2: Ps2, vmmouse: bool, vmmouse_relative: bool, - input: ProducerHandle, + keyboard_input: InputProducer, + mouse_input: InputProducer, time_file: File, extended: bool, mouse_x: i32, @@ -56,12 +57,18 @@ mouse_timeout: Option, packets: [u8; 4], packet_i: usize, + caps_lock: bool, + num_lock: bool, + scroll_lock: bool, + leds_dirty: bool, } impl Ps2d { - pub fn new(input: ProducerHandle, time_file: File) -> Self { + pub fn new(keyboard_input: InputProducer, mouse_input: InputProducer, time_file: File) -> Self { let mut ps2 = Ps2::new(); - ps2.init().expect("failed to initialize"); + if let Err(err) = ps2.init() { + log::error!("ps2d: controller init failed: {:?}", err); + } // FIXME add an option for orbital to disable this when an app captures the mouse. let vmmouse_relative = false; @@ -77,7 +84,8 @@ ps2, vmmouse, vmmouse_relative, - input, + keyboard_input, + mouse_input, time_file, extended: false, mouse_x: 0, @@ -89,6 +97,10 @@ mouse_timeout: None, packets: [0; 4], packet_i: 0, + caps_lock: false, + num_lock: true, + scroll_lock: false, + leds_dirty: true, }; if !this.vmmouse { @@ -96,6 +108,12 @@ this.handle_mouse(None); } + // Flush initial LED state (Num Lock on by default) + if this.leds_dirty { + this.leds_dirty = false; + this.ps2.set_leds(this.caps_lock, this.num_lock, this.scroll_lock); + } + this } @@ -272,8 +290,21 @@ } }; + if scancode != 0 && pressed { + match scancode { + orbclient::K_CAPS => { self.caps_lock = !self.caps_lock; self.leds_dirty = true; }, + orbclient::K_NUM => { self.num_lock = !self.num_lock; self.leds_dirty = true; }, + orbclient::K_SCROLL => { self.scroll_lock = !self.scroll_lock; self.leds_dirty = true; }, + _ => (), + } + } + if self.leds_dirty { + self.leds_dirty = false; + self.ps2.set_leds(self.caps_lock, self.num_lock, self.scroll_lock); + } + if scancode != 0 { - self.input + self.keyboard_input .write_event( KeyEvent { character: '\0', @@ -304,7 +335,7 @@ if self.vmmouse_relative { if dx != 0 || dy != 0 { - self.input + self.mouse_input .write_event( MouseRelativeEvent { dx: dx as i32, @@ -320,14 +351,14 @@ if x != self.mouse_x || y != self.mouse_y { self.mouse_x = x; self.mouse_y = y; - self.input + self.mouse_input .write_event(MouseEvent { x, y }.to_event()) .expect("ps2d: failed to write mouse event"); } }; if dz != 0 { - self.input + self.mouse_input .write_event( ScrollEvent { x: 0, @@ -348,7 +379,7 @@ self.mouse_left = left; self.mouse_middle = middle; self.mouse_right = right; - self.input + self.mouse_input .write_event( ButtonEvent { left, @@ -441,13 +472,13 @@ } if dx != 0 || dy != 0 { - self.input + self.mouse_input .write_event(MouseRelativeEvent { dx, dy }.to_event()) .expect("ps2d: failed to write mouse event"); } if dz != 0 { - self.input + self.mouse_input .write_event(ScrollEvent { x: 0, y: dz }.to_event()) .expect("ps2d: failed to write scroll event"); } @@ -462,7 +493,7 @@ self.mouse_left = left; self.mouse_middle = middle; self.mouse_right = right; - self.input + self.mouse_input .write_event( ButtonEvent { left, diff --git a/drivers/input/ps2d/src/main.rs b/drivers/input/ps2d/src/main.rs index db17de2a..86f903bf 100644 --- a/drivers/input/ps2d/src/main.rs 21:31:04.000000000 +0100 +++ b/drivers/input/ps2d/src/main.rs @@ -14,4 +14,4 @@ -use inputd::ProducerHandle; +use inputd::InputProducer; use crate::state::Ps2d; @@ -31,7 +31,8 @@ fn daemon(daemon: daemon::Daemon) -> ! { acquire_port_io_rights().expect("ps2d: failed to get I/O permission"); - let input = ProducerHandle::new().expect("ps2d: failed to open input producer"); + let keyboard_input = InputProducer::new_named_or_fallback("ps2-keyboard").expect("ps2d: failed to open keyboard input"); + let mouse_input = InputProducer::new_named_or_fallback("ps2-mouse").expect("ps2d: failed to open mouse input"); user_data! { enum Source { @@ -93,7 +94,7 @@ fn daemon(daemon: daemon::Daemon) -> ! { daemon.ready(); - let mut ps2d = Ps2d::new(input, time_file); + let mut ps2d = Ps2d::new(keyboard_input, mouse_input, time_file); let mut data = [0; 256]; for event in event_queue.map(|e| e.expect("ps2d: failed to get next event").user_data) { +31,8 @@ fn daemon(daemon: daemon::Daemon) -> ! { acquire_port_io_rights().expect("ps2d: failed to get I/O permission"); - let input = ProducerHandle::new().expect("ps2d: failed to open input producer"); + let keyboard_input = InputProducer::new_named_or_fallback("ps2-keyboard").expect("ps2d: failed to open keyboard input"); + let mouse_input = InputProducer::new_named_or_fallback("ps2-mouse").expect("ps2d: failed to open mouse input"); user_data! { enum Source { @@ -93,7 +94,7 @@ fn daemon(daemon: daemon::Daemon) -> ! { daemon.ready(); - let mut ps2d = Ps2d::new(input, time_file); + let mut ps2d = Ps2d::new(keyboard_input, mouse_input, time_file); let mut data = [0; 256]; for event in event_queue.map(|e| e.expect("ps2d: failed to get next event").user_data) {