diff --git a/kernel/comps/i8042/src/controller.rs b/kernel/comps/i8042/src/controller.rs index 302fcb46f..246fae528 100644 --- a/kernel/comps/i8042/src/controller.rs +++ b/kernel/comps/i8042/src/controller.rs @@ -14,9 +14,13 @@ use ostd::{ use spin::Once; pub(super) const PS2_CMD_RESET: u8 = 0xFF; -pub(super) const PS2_ACK: u8 = 0xFA; pub(super) const PS2_BAT_OK: u8 = 0xAA; +pub(super) const PS2_ACK: u8 = 0xFA; +pub(super) const PS2_NAK: u8 = 0xFE; +pub(super) const PS2_ERR: u8 = 0xFC; +pub(super) const PS2_RESULTS: &[u8] = &[PS2_ACK, PS2_NAK, PS2_ERR]; + /// The `I8042Controller` singleton. pub(super) static I8042_CONTROLLER: Once> = Once::new(); @@ -124,9 +128,6 @@ pub(super) struct I8042Controller { status_or_command_port: IoPort, } -/// The maximum number of times to wait for the i8042 controller to be ready. -const MAX_WAITING_COUNT: usize = 64; - impl I8042Controller { fn new() -> Result { const DATA_PORT_ADDR: u16 = 0x60; @@ -162,13 +163,8 @@ impl I8042Controller { } fn wait_and_send_command(&mut self, command: Command) -> Result<(), I8042ControllerError> { - for _ in 0..MAX_WAITING_COUNT { - if self.send_command(command).is_ok() { - return Ok(()); - } - core::hint::spin_loop(); - } - Err(I8042ControllerError::OutputBusy) + spin_wait_until(Timeout::Short, || self.send_command(command).ok()) + .ok_or(I8042ControllerError::OutputBusy) } fn send_command(&mut self, command: Command) -> Result<(), I8042ControllerError> { @@ -189,13 +185,8 @@ impl I8042Controller { } pub(super) fn wait_and_send_data(&mut self, data: u8) -> Result<(), I8042ControllerError> { - for _ in 0..MAX_WAITING_COUNT { - if self.send_data(data).is_ok() { - return Ok(()); - } - core::hint::spin_loop(); - } - Err(I8042ControllerError::OutputBusy) + spin_wait_until(Timeout::Short, || self.send_data(data).ok()) + .ok_or(I8042ControllerError::OutputBusy) } pub(super) fn write_to_second_port(&mut self, data: u8) -> Result<(), I8042ControllerError> { @@ -217,13 +208,28 @@ impl I8042Controller { } pub(super) fn wait_and_recv_data(&mut self) -> Result { - for _ in 0..MAX_WAITING_COUNT { - if let Some(data) = self.receive_data() { - return Ok(data); - } - core::hint::spin_loop(); - } - Err(I8042ControllerError::NoInput) + spin_wait_until(Timeout::Short, || self.receive_data()).ok_or(I8042ControllerError::NoInput) + } + + /// Waits a long time for the data to be received. + /// + /// This is usually used when performing a reset that takes a long time to complete. + pub(super) fn wait_long_and_recv_data(&mut self) -> Result { + spin_wait_until(Timeout::Long, || self.receive_data()).ok_or(I8042ControllerError::NoInput) + } + + /// Waits for the specified data to be received. + /// + /// This is typically used when waiting for an acknowledgment of a command. Any garbage data + /// before the acknowledgment is ignored. + pub(super) fn wait_for_specific_data( + &mut self, + data: &[u8], + ) -> Result { + spin_wait_until(Timeout::Short, || { + self.receive_data().filter(|val| data.contains(val)) + }) + .ok_or(I8042ControllerError::NoInput) } pub(super) fn receive_data(&mut self) -> Option { @@ -243,6 +249,47 @@ impl I8042Controller { } } +/// Timeout in milliseconds for sending commands or receiving data. +/// +/// Reference: +#[repr(u16)] +#[derive(Debug, Clone, Copy)] +enum Timeout { + /// Short timeout for normal commands (500 ms). + Short = 500, + /// Long timeout for the reset command (4000 ms). + Long = 4000, +} + +/// Spins and waits until the timeout occurs or `f` returns `Some(_)`. +// +// TODO: The timeout is relatively large, up to several seconds. Therefore, spinning here is not +// appropriate. The code needs to be refactored to use asynchronous interrupts. +fn spin_wait_until(timeout: Timeout, mut f: F) -> Option +where + F: FnMut() -> Option, +{ + use ostd::arch::{read_tsc, tsc_freq}; + + const MSEC_PER_SEC: u64 = 1000; + + if let Some(res) = f() { + return Some(res); + } + + let current = read_tsc(); + let distance = tsc_freq() / MSEC_PER_SEC * (timeout as u16 as u64); + loop { + if let Some(res) = f() { + return Some(res); + } + if read_tsc().wrapping_sub(current) >= distance { + return None; + } + core::hint::spin_loop(); + } +} + /// Errors that can occur when initializing the i8042 controller. #[derive(Debug, Clone, Copy)] pub(super) enum I8042ControllerError { diff --git a/kernel/comps/i8042/src/keyboard.rs b/kernel/comps/i8042/src/keyboard.rs index 130b78144..6aaa8a8b8 100644 --- a/kernel/comps/i8042/src/keyboard.rs +++ b/kernel/comps/i8042/src/keyboard.rs @@ -21,7 +21,7 @@ use spin::Once; use super::controller::{ I8042Controller, I8042ControllerError, I8042_CONTROLLER, PS2_ACK, PS2_BAT_OK, PS2_CMD_RESET, }; -use crate::alloc::string::ToString; +use crate::{alloc::string::ToString, controller::PS2_RESULTS}; /// IRQ line for i8042 keyboard. static IRQ_LINE: Once = Once::new(); @@ -33,16 +33,16 @@ static REGISTERED_DEVICE: Once = Once::new(); const ISA_INTR_NUM: u8 = 1; pub(super) fn init(controller: &mut I8042Controller) -> Result<(), I8042ControllerError> { - // Reset keyboard device by sending `PS2_CMD_RESET` (reset command, supported by all PS/2 devices) to port 1 - // and waiting for a response. + // Reset the keyboard device by sending `PS2_CMD_RESET` (reset command, supported by all PS/2 + // devices) to port 1 and waiting for a response. controller.wait_and_send_data(PS2_CMD_RESET)?; // The response should be `PS2_ACK` and `PS2_BAT_OK`, followed by the device PS/2 ID. - if controller.wait_and_recv_data()? != PS2_ACK { + if controller.wait_for_specific_data(PS2_RESULTS)? != PS2_ACK { return Err(I8042ControllerError::DeviceResetFailed); } - // The reset command may take some time to finish. Try again a few times. - if (0..5).find_map(|_| controller.wait_and_recv_data().ok()) != Some(PS2_BAT_OK) { + // The reset command may take some time to finish. + if controller.wait_long_and_recv_data()? != PS2_BAT_OK { return Err(I8042ControllerError::DeviceResetFailed); } // See for a list of IDs. diff --git a/kernel/comps/i8042/src/mouse.rs b/kernel/comps/i8042/src/mouse.rs index 010d4b250..e03f4354f 100644 --- a/kernel/comps/i8042/src/mouse.rs +++ b/kernel/comps/i8042/src/mouse.rs @@ -21,7 +21,7 @@ use spin::Once; use super::controller::{ I8042Controller, I8042ControllerError, I8042_CONTROLLER, PS2_ACK, PS2_BAT_OK, PS2_CMD_RESET, }; -use crate::alloc::string::ToString; +use crate::{alloc::string::ToString, controller::PS2_RESULTS}; /// IRQ line for i8042 mouse. static IRQ_LINE: Once = Once::new(); @@ -36,18 +36,19 @@ const ISA_INTR_NUM: u8 = 12; static PACKET_STATE: SpinLock = SpinLock::new(PacketState::new()); pub(super) fn init(controller: &mut I8042Controller) -> Result<(), I8042ControllerError> { - // Reset mouse device by sending `PS2_CMD_RESET` to the second PS/2 port. + // Reset the mouse device by sending `PS2_CMD_RESET` (reset command, supported by all PS/2 + // devices) to port 2 and waiting for a response. controller.write_to_second_port(PS2_CMD_RESET)?; // The response should be `PS2_ACK` and `PS2_BAT_OK`, followed by the device PS/2 ID. - if controller.wait_and_recv_data()? != PS2_ACK { + if controller.wait_for_specific_data(PS2_RESULTS)? != PS2_ACK { return Err(I8042ControllerError::DeviceResetFailed); } - // The reset command may take some time to finish. Try again a few times. - if (0..5).find_map(|_| controller.wait_and_recv_data().ok()) != Some(PS2_BAT_OK) { + // The reset command may take some time to finish. + if controller.wait_long_and_recv_data()? != PS2_BAT_OK { return Err(I8042ControllerError::DeviceResetFailed); } - + // See for a list of IDs. let device_id = controller.wait_and_recv_data()?; log::info!("PS/2 mouse device ID: 0x{:02X}", device_id); @@ -134,13 +135,13 @@ impl InitCtx<'_> { assert_eq!(out.len(), C::RES_LEN); self.0.write_to_second_port(C::CMD_BYTE)?; - if self.0.wait_and_recv_data()? != PS2_ACK { + if self.0.wait_for_specific_data(PS2_RESULTS)? != PS2_ACK { return Err(I8042ControllerError::DeviceResetFailed); } for &arg in args { self.0.write_to_second_port(arg)?; - if self.0.wait_and_recv_data()? != PS2_ACK { + if self.0.wait_for_specific_data(PS2_RESULTS)? != PS2_ACK { return Err(I8042ControllerError::DeviceResetFailed); } }