Set timeout for i8042 transactions
This commit is contained in:
parent
0b664a012e
commit
3ddbef06e4
|
|
@ -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<SpinLock<I8042Controller, LocalIrqDisabled>> = Once::new();
|
||||
|
||||
|
|
@ -124,9 +128,6 @@ pub(super) struct I8042Controller {
|
|||
status_or_command_port: IoPort<u8, ReadWriteAccess>,
|
||||
}
|
||||
|
||||
/// 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<Self, I8042ControllerError> {
|
||||
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<u8, I8042ControllerError> {
|
||||
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<u8, I8042ControllerError> {
|
||||
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<u8, I8042ControllerError> {
|
||||
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<u8> {
|
||||
|
|
@ -243,6 +249,47 @@ impl I8042Controller {
|
|||
}
|
||||
}
|
||||
|
||||
/// Timeout in milliseconds for sending commands or receiving data.
|
||||
///
|
||||
/// Reference: <https://elixir.bootlin.com/linux/v6.17.9/source/drivers/input/serio/libps2.c#L344>
|
||||
#[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<F, R>(timeout: Timeout, mut f: F) -> Option<R>
|
||||
where
|
||||
F: FnMut() -> Option<R>,
|
||||
{
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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<MappedIrqLine> = Once::new();
|
||||
|
|
@ -33,16 +33,16 @@ static REGISTERED_DEVICE: Once<RegisteredInputDevice> = 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 <https://wiki.osdev.org/I8042_PS/2_Controller#Detecting_PS/2_Device_Types> for a list of IDs.
|
||||
|
|
|
|||
|
|
@ -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<MappedIrqLine> = Once::new();
|
||||
|
|
@ -36,18 +36,19 @@ const ISA_INTR_NUM: u8 = 12;
|
|||
static PACKET_STATE: SpinLock<PacketState, LocalIrqDisabled> = 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 <https://wiki.osdev.org/I8042_PS/2_Controller#Detecting_PS/2_Device_Types> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue