Abstract common PS/2 logics

This commit is contained in:
Ruihan Li 2025-11-29 12:21:16 +08:00 committed by Qingsong Chen
parent 3ddbef06e4
commit 3258a264de
5 changed files with 168 additions and 107 deletions

View File

@ -13,14 +13,6 @@ use ostd::{
};
use spin::Once;
pub(super) const PS2_CMD_RESET: u8 = 0xFF;
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();

View File

@ -2,7 +2,10 @@
//! The i8042 keyboard driver.
use alloc::{string::String, sync::Arc};
use alloc::{
string::{String, ToString},
sync::Arc,
};
use core::sync::atomic::{AtomicBool, Ordering};
use aster_input::{
@ -18,10 +21,10 @@ use ostd::{
};
use spin::Once;
use super::controller::{
I8042Controller, I8042ControllerError, I8042_CONTROLLER, PS2_ACK, PS2_BAT_OK, PS2_CMD_RESET,
use crate::{
controller::{I8042Controller, I8042ControllerError, I8042_CONTROLLER},
ps2::{Command, CommandCtx},
};
use crate::{alloc::string::ToString, controller::PS2_RESULTS};
/// IRQ line for i8042 keyboard.
static IRQ_LINE: Once<MappedIrqLine> = Once::new();
@ -33,25 +36,19 @@ static REGISTERED_DEVICE: Once<RegisteredInputDevice> = Once::new();
const ISA_INTR_NUM: u8 = 1;
pub(super) fn init(controller: &mut I8042Controller) -> Result<(), I8042ControllerError> {
// 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)?;
// Reference: <https://elixir.bootlin.com/linux/v6.17.9/source/drivers/input/serio/libps2.c#L184>
const DEVICE_ID_REGULAR_KEYBOARD: u8 = 0xAB;
// The response should be `PS2_ACK` and `PS2_BAT_OK`, followed by the device PS/2 ID.
if controller.wait_for_specific_data(PS2_RESULTS)? != PS2_ACK {
return Err(I8042ControllerError::DeviceResetFailed);
}
// 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 mut iter = core::iter::from_fn(|| controller.wait_and_recv_data().ok());
match (iter.next(), iter.next()) {
// Ancient AT keyboard
(None, None) => (),
// Other devices, including other kinds of keyboards (TODO: Support other kinds of keyboards)
_ => return Err(I8042ControllerError::DeviceUnknown),
let mut init_ctx = InitCtx(controller);
init_ctx.reset()?;
// Determine the keyboard's type.
let (device_id, _) = init_ctx.get_device_id()?;
log::info!("PS/2 keyboard device ID: 0x{:02X}", device_id);
if device_id != DEVICE_ID_REGULAR_KEYBOARD {
// TODO: Support other kinds of keyboards.
return Err(I8042ControllerError::DeviceUnknown);
}
let mut irq_line = IrqLine::alloc()
@ -73,6 +70,34 @@ pub(super) fn init(controller: &mut I8042Controller) -> Result<(), I8042Controll
Ok(())
}
struct InitCtx<'a>(&'a mut I8042Controller);
impl InitCtx<'_> {
fn get_device_id(&mut self) -> Result<(u8, u8), I8042ControllerError> {
let mut buf = [0u8; cmd::GetDeviceId::RES_LEN];
self.command::<cmd::GetDeviceId>(&[], &mut buf)?;
Ok((buf[0], buf[1]))
}
}
impl CommandCtx for InitCtx<'_> {
fn controller(&mut self) -> &mut I8042Controller {
self.0
}
fn write_to_port(&mut self, data: u8) -> Result<(), I8042ControllerError> {
self.0.wait_and_send_data(data)
}
}
mod cmd {
use crate::ps2::{define_commands, Command};
define_commands! {
GetDeviceId, 0xF2, fn([u8; 0]) -> [u8; 2];
}
}
#[derive(Debug)]
struct I8042Keyboard {
name: String,
@ -141,10 +166,6 @@ impl InputDevice for I8042Keyboard {
}
fn handle_keyboard_input(_trap_frame: &TrapFrame) {
if !I8042_CONTROLLER.is_completed() {
return;
}
let Some(scancode_event) = ScancodeInfo::read() else {
return;
};
@ -212,14 +233,14 @@ impl ScancodeInfo {
fn read() -> Option<Self> {
static EXTENDED_KEY: AtomicBool = AtomicBool::new(false);
let Some(data) = I8042_CONTROLLER.get().unwrap().lock().receive_data() else {
log::warn!("i8042 keyboard has no input data");
let Some(data) = I8042_CONTROLLER.get()?.lock().receive_data() else {
log::warn!("PS/2 keyboard has no input data");
return None;
};
let code = ScanCode(data);
if code.has_error() {
log::warn!("i8042 keyboard key detection error or internal buffer overrun");
log::warn!("PS/2 keyboard key detection error or internal buffer overrun");
return None;
}

View File

@ -12,6 +12,7 @@ use component::{init_component, ComponentInitError};
mod controller;
mod keyboard;
mod mouse;
mod ps2;
#[init_component]
fn init() -> Result<(), ComponentInitError> {

View File

@ -2,7 +2,11 @@
//! The i8042 mouse driver.
use alloc::{string::String, sync::Arc, vec::Vec};
use alloc::{
string::{String, ToString},
sync::Arc,
vec::Vec,
};
use aster_input::{
event_type_codes::{KeyCode, KeyStatus, RelCode, SynEvent},
@ -18,10 +22,10 @@ use ostd::{
};
use spin::Once;
use super::controller::{
I8042Controller, I8042ControllerError, I8042_CONTROLLER, PS2_ACK, PS2_BAT_OK, PS2_CMD_RESET,
use crate::{
controller::{I8042Controller, I8042ControllerError, I8042_CONTROLLER},
ps2::{Command, CommandCtx},
};
use crate::{alloc::string::ToString, controller::PS2_RESULTS};
/// IRQ line for i8042 mouse.
static IRQ_LINE: Once<MappedIrqLine> = Once::new();
@ -36,26 +40,14 @@ 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 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_for_specific_data(PS2_RESULTS)? != PS2_ACK {
return Err(I8042ControllerError::DeviceResetFailed);
}
// 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);
let mut init_ctx = InitCtx(controller);
let device_id = init_ctx
.reset()?
.ok_or(I8042ControllerError::DeviceUnknown)?;
log::info!("PS/2 mouse device ID: 0x{:02X}", device_id);
// Determine the mouse's type.
// Reference: <https://wiki.osdev.org/Mouse_Input>
let mut mouse_type =
MouseType::from_device_id(device_id).ok_or(I8042ControllerError::DeviceUnknown)?;
if mouse_type == MouseType::Standard && init_ctx.enable_intellimouse().is_ok() {
@ -125,60 +117,20 @@ impl InitCtx<'_> {
self.command::<cmd::EnableDataReporting>(&[], &mut buf)?;
Ok(())
}
fn command<C: MouseCommand>(
&mut self,
args: &[u8],
out: &mut [u8],
) -> Result<(), I8042ControllerError> {
assert_eq!(args.len(), C::DATA_LEN);
assert_eq!(out.len(), C::RES_LEN);
self.0.write_to_second_port(C::CMD_BYTE)?;
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_for_specific_data(PS2_RESULTS)? != PS2_ACK {
return Err(I8042ControllerError::DeviceResetFailed);
}
}
for slot in out.iter_mut() {
*slot = self.0.wait_and_recv_data()?;
}
Ok(())
}
}
trait MouseCommand {
const CMD_BYTE: u8;
const DATA_LEN: usize;
const RES_LEN: usize;
impl CommandCtx for InitCtx<'_> {
fn controller(&mut self) -> &mut I8042Controller {
self.0
}
fn write_to_port(&mut self, data: u8) -> Result<(), I8042ControllerError> {
self.0.write_to_second_port(data)
}
}
mod cmd {
use super::MouseCommand;
macro_rules! define_commands {
(
$(
$name:ident, $cmd:literal, fn([u8; $dlen:literal]) -> [u8; $rlen:literal];
)*
) => {
$(
pub(super) struct $name;
impl MouseCommand for $name {
const CMD_BYTE: u8 = $cmd;
const DATA_LEN: usize = $dlen;
const RES_LEN: usize = $rlen;
}
)*
};
}
use crate::ps2::{define_commands, Command};
define_commands! {
GetDeviceId, 0xF2, fn([u8; 0]) -> [u8; 1];
@ -292,6 +244,7 @@ impl MouseType {
const PACKET_LEN_MAX: usize = 4;
fn from_device_id(device_id: u8) -> Option<Self> {
// Reference: <https://wiki.osdev.org/Mouse_Input#MouseID_Byte>
const DEVICE_ID_STANDARD_MOUSE: u8 = 0x00;
const DEVICE_ID_INTELLIMOUSE: u8 = 0x03;
const DEVICE_ID_INTELLIMOUSE_EXPLORER: u8 = 0x04;

View File

@ -0,0 +1,94 @@
// SPDX-License-Identifier: MPL-2.0
//! Common utilities for PS/2 devices.
use crate::controller::{I8042Controller, I8042ControllerError};
const PS2_CMD_RESET: u8 = 0xFF;
const PS2_BAT_OK: u8 = 0xAA;
const PS2_ACK: u8 = 0xFA;
const PS2_NAK: u8 = 0xFE;
const PS2_ERR: u8 = 0xFC;
const PS2_RESULTS: &[u8] = &[PS2_ACK, PS2_NAK, PS2_ERR];
/// PS/2 device commands.
pub(super) trait Command {
const CMD_BYTE: u8;
const DATA_LEN: usize;
const RES_LEN: usize;
}
macro_rules! define_commands {
(
$(
$name:ident, $cmd:literal, fn([u8; $dlen:literal]) -> [u8; $rlen:literal];
)*
) => {
$(
pub(super) struct $name;
impl Command for $name {
const CMD_BYTE: u8 = $cmd;
const DATA_LEN: usize = $dlen;
const RES_LEN: usize = $rlen;
}
)*
};
}
pub(super) use define_commands;
/// Context to perform PS/2 commands.
pub(super) trait CommandCtx {
fn controller(&mut self) -> &mut I8042Controller;
fn write_to_port(&mut self, data: u8) -> Result<(), I8042ControllerError>;
fn reset(&mut self) -> Result<Option<u8>, I8042ControllerError> {
// Reset the device by sending `PS2_CMD_RESET` (reset command, supported by all PS/2
// devices).
self.write_to_port(PS2_CMD_RESET)?;
let controller = self.controller();
// The response should be `PS2_ACK` and `PS2_BAT_OK`, followed by the device PS/2 ID.
if controller.wait_for_specific_data(PS2_RESULTS)? != PS2_ACK {
return Err(I8042ControllerError::DeviceResetFailed);
}
// The reset command may take some time to finish. So we use `wait_long_and_recv_data`.
if controller.wait_long_and_recv_data()? != PS2_BAT_OK {
return Err(I8042ControllerError::DeviceResetFailed);
}
// Some keyboards won't reply its device ID. So we don't report any error here.
Ok(controller.wait_and_recv_data().ok())
}
fn command<C: Command>(
&mut self,
args: &[u8],
out: &mut [u8],
) -> Result<(), I8042ControllerError> {
assert_eq!(args.len(), C::DATA_LEN);
assert_eq!(out.len(), C::RES_LEN);
// Send the command.
self.write_to_port(C::CMD_BYTE)?;
if self.controller().wait_for_specific_data(PS2_RESULTS)? != PS2_ACK {
return Err(I8042ControllerError::DeviceResetFailed);
}
// Send the arguments.
for &arg in args {
self.write_to_port(arg)?;
if self.controller().wait_for_specific_data(PS2_RESULTS)? != PS2_ACK {
return Err(I8042ControllerError::DeviceResetFailed);
}
}
// Receive the response.
for slot in out.iter_mut() {
*slot = self.controller().wait_and_recv_data()?;
}
Ok(())
}
}