Abstract common PS/2 logics
This commit is contained in:
parent
3ddbef06e4
commit
3258a264de
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use component::{init_component, ComponentInitError};
|
|||
mod controller;
|
||||
mod keyboard;
|
||||
mod mouse;
|
||||
mod ps2;
|
||||
|
||||
#[init_component]
|
||||
fn init() -> Result<(), ComponentInitError> {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue