Add basic i8042 keyboard support

This commit is contained in:
Qingsong Chen 2025-06-25 02:04:24 +00:00 committed by Tate, Hongliang Tian
parent 3f1bf99b2a
commit 5e3e23bf7c
13 changed files with 1084 additions and 4 deletions

13
Cargo.lock generated
View File

@ -117,6 +117,7 @@ name = "aster-framebuffer"
version = "0.1.0"
dependencies = [
"aster-console",
"aster-keyboard",
"component",
"font8x8",
"log",
@ -134,6 +135,17 @@ dependencies = [
"spin",
]
[[package]]
name = "aster-keyboard"
version = "0.1.0"
dependencies = [
"bitflags 2.9.1",
"component",
"log",
"ostd",
"spin",
]
[[package]]
name = "aster-logger"
version = "0.1.0"
@ -187,6 +199,7 @@ dependencies = [
"aster-console",
"aster-framebuffer",
"aster-input",
"aster-keyboard",
"aster-logger",
"aster-mlsdisk",
"aster-network",

View File

@ -17,6 +17,7 @@ members = [
"kernel/comps/console",
"kernel/comps/framebuffer",
"kernel/comps/input",
"kernel/comps/keyboard",
"kernel/comps/network",
"kernel/comps/softirq",
"kernel/comps/systree",

View File

@ -12,6 +12,7 @@ framebuffer = { name = "aster-framebuffer" }
network = { name = "aster-network" }
mlsdisk = { name = "aster-mlsdisk" }
systree = { name = "aster-systree" }
keyboard = { name = "aster-keyboard" }
[whitelist]
[whitelist.nix.main]

View File

@ -180,6 +180,7 @@ OSDK_CRATES := \
kernel/comps/console \
kernel/comps/framebuffer \
kernel/comps/input \
kernel/comps/keyboard \
kernel/comps/network \
kernel/comps/softirq \
kernel/comps/systree \

View File

@ -19,6 +19,7 @@ aster-time = { path = "comps/time" }
aster-virtio = { path = "comps/virtio" }
aster-rights = { path = "libs/aster-rights" }
aster-systree = { path = "comps/systree" }
aster-keyboard = { path = "comps/keyboard" }
component = { path = "libs/comp-sys/component" }
controlled = { path = "libs/comp-sys/controlled" }
osdk-frame-allocator = { path = "../osdk/deps/frame-allocator" }

View File

@ -9,6 +9,7 @@ edition = "2021"
ostd = { path = "../../../ostd" }
component = { path = "../../libs/comp-sys/component" }
aster-console = { path = "../console" }
aster-keyboard ={ path = "../keyboard" }
log = "0.4"
spin = "0.9.4"
font8x8 = { version = "0.2.5", default-features = false, features = [ "unicode" ] }

View File

@ -3,8 +3,10 @@
use alloc::{sync::Arc, vec::Vec};
use aster_console::{AnyConsoleDevice, ConsoleCallback};
use aster_keyboard::InputKey;
use font8x8::UnicodeFonts;
use ostd::{
mm::VmReader,
sync::{LocalIrqDisabled, SpinLock},
Error, Result,
};
@ -19,9 +21,9 @@ const FONT_WIDTH: usize = 8;
const FONT_HEIGHT: usize = 8;
/// A text console rendered onto the framebuffer.
#[derive(Debug)]
pub struct FramebufferConsole {
state: SpinLock<ConsoleState, LocalIrqDisabled>,
callbacks: SpinLock<Vec<&'static ConsoleCallback>, LocalIrqDisabled>,
}
pub const CONSOLE_NAME: &str = "Framebuffer-Console";
@ -35,6 +37,7 @@ pub(crate) fn init() {
};
FRAMEBUFFER_CONSOLE.call_once(|| Arc::new(FramebufferConsole::new(fb.clone())));
aster_keyboard::register_callback(&handle_keyboard_input);
}
impl AnyConsoleDevice for FramebufferConsole {
@ -42,8 +45,8 @@ impl AnyConsoleDevice for FramebufferConsole {
self.state.lock().send_buf(buf);
}
fn register_callback(&self, _: &'static ConsoleCallback) {
// Unsupported, do nothing.
fn register_callback(&self, callback: &'static ConsoleCallback) {
self.callbacks.lock().push(callback);
}
}
@ -61,6 +64,7 @@ impl FramebufferConsole {
bytes,
backend: framebuffer,
}),
callbacks: SpinLock::new(Vec::new()),
}
}
@ -118,6 +122,12 @@ impl FramebufferConsole {
}
}
impl core::fmt::Debug for FramebufferConsole {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("FramebufferConsole").finish_non_exhaustive()
}
}
#[derive(Debug)]
struct ConsoleState {
// FIXME: maybe we should drop the whole `ConsoleState` when it's disabled.
@ -210,3 +220,15 @@ impl ConsoleState {
}
}
}
fn handle_keyboard_input(key: InputKey) {
let Some(console) = FRAMEBUFFER_CONSOLE.get() else {
return;
};
let buffer = key.as_xterm_control_sequence();
for callback in console.callbacks.lock().iter() {
let reader = VmReader::from(buffer);
callback(reader);
}
}

View File

@ -0,0 +1,16 @@
[package]
name = "aster-keyboard"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
component = { path = "../../libs/comp-sys/component" }
ostd = { path = "../../../ostd" }
bitflags = "2.5"
log = "0.4"
spin = "0.9.4"
[lints]
workspace = true

View File

@ -0,0 +1,285 @@
// SPDX-License-Identifier: MPL-2.0
//! Provides i8042 PS/2 Controller I/O port access.
//!
//! Reference: <https://wiki.osdev.org/I8042_PS/2_Controller>
//!
use bitflags::bitflags;
use ostd::{
arch::device::io_port::ReadWriteAccess,
io::IoPort,
sync::{LocalIrqDisabled, SpinLock},
};
use spin::Once;
/// The `I8042Controller` singleton.
pub(super) static I8042_CONTROLLER: Once<SpinLock<I8042Controller, LocalIrqDisabled>> = Once::new();
pub(super) fn init() -> Result<(), I8042ControllerError> {
let mut controller = I8042Controller::new()?;
// The steps to initialize the i8042 controller are from:
// <https://wiki.osdev.org/I8042_PS/2_Controller#Initialising_the_PS/2_Controller>.
// Disable devices so that they won't send data at the wrong time and mess up initialization.
controller.wait_and_send_command(Command::DisableFirstPort)?;
controller.wait_and_send_command(Command::DisableSecondPort)?;
// Flush the output buffer by reading from the data port and discarding the data.
controller.flush_output_buffer();
// Set the controller configuration byte.
let mut config = controller.read_configuration()?;
config.remove(
Configuration::FIRST_PORT_INTERRUPT_ENABLED
| Configuration::FIRST_PORT_TRANSLATION_ENABLED
| Configuration::SECOND_PORT_INTERRUPT_ENABLED,
);
controller.write_configuration(&config)?;
// Perform controller self-test.
controller.wait_and_send_command(Command::TestController)?;
let result = controller.wait_and_recv_data()?;
if result != 0x55 {
// Any value other than 0x55 indicates a self-test fail.
return Err(I8042ControllerError::ControllerTestFailed);
}
// The self-test may reset the controller. Restore the original configuration.
controller.write_configuration(&config)?;
// The ports may have been enabled if the controller was reset. Flush the output buffer.
controller.flush_output_buffer();
// Determine if there are two channels.
controller.wait_and_send_command(Command::EnableSecondPort)?;
let has_second_port = config.contains(Configuration::SECOND_PORT_CLOCK_DISABLED)
&& !controller
.read_configuration()?
.contains(Configuration::SECOND_PORT_CLOCK_DISABLED);
controller.wait_and_send_command(Command::DisableSecondPort)?;
// Flush the output buffer again since we may have enabled the second port.
controller.flush_output_buffer();
// Perform interface tests to the first PS/2 port.
controller.wait_and_send_command(Command::TestFirstPort)?;
let result = controller.wait_and_recv_data()?;
if result != 0x00 {
return Err(I8042ControllerError::FirstPortTestFailed);
}
// Perform interface tests to the second PS/2 port (if it exists).
if has_second_port {
controller.wait_and_send_command(Command::TestSecondPort)?;
let result = controller.wait_and_recv_data()?;
if result != 0x00 {
return Err(I8042ControllerError::SecondPortTestFailed);
}
}
// Enable the first PS/2 port.
controller.wait_and_send_command(Command::EnableFirstPort)?;
if let Err(err) = super::keyboard::init(&mut controller) {
log::warn!("i8042 keyboard initialization failed: {:?}", err);
controller.wait_and_send_command(Command::DisableFirstPort)?;
} else {
config.remove(Configuration::FIRST_PORT_CLOCK_DISABLED);
config.insert(
Configuration::FIRST_PORT_INTERRUPT_ENABLED
| Configuration::FIRST_PORT_TRANSLATION_ENABLED,
);
}
// TODO: Add a mouse driver and enable the second PS/2 port (if it exists).
I8042_CONTROLLER.call_once(|| SpinLock::new(controller));
let mut controller = I8042_CONTROLLER.get().unwrap().lock();
// Write the new configuration to enable the interrupts after setting up `I8042_CONTROLLER`.
controller.write_configuration(&config)?;
// Flush the output buffer to ensure that new data can trigger interrupts.
controller.flush_output_buffer();
Ok(())
}
/// An I8042 PS/2 Controller.
pub(super) struct I8042Controller {
data_port: IoPort<u8, ReadWriteAccess>,
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> {
// TODO: Check the flags in the ACPI table to determine if the PS/2 controller exists. See:
// <https://uefi.org/specs/ACPI/6.5/05_ACPI_Software_Programming_Model.html#ia-pc-boot-architecture-flags>.
let controller = Self {
data_port: IoPort::acquire(0x60).unwrap(),
status_or_command_port: IoPort::acquire(0x64).unwrap(),
};
Ok(controller)
}
fn read_configuration(&mut self) -> Result<Configuration, I8042ControllerError> {
self.wait_and_send_command(Command::ReadConfiguration)?;
self.wait_and_recv_data()
.map(Configuration::from_bits_retain)
}
fn write_configuration(&mut self, config: &Configuration) -> Result<(), I8042ControllerError> {
self.wait_and_send_command(Command::WriteConfiguration)?;
self.wait_and_send_data(config.bits())
}
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)
}
fn send_command(&mut self, command: Command) -> Result<(), I8042ControllerError> {
if !self.read_status().contains(Status::INPUT_BUFFER_IS_FULL) {
self.write_command(command as u8);
Ok(())
} else {
Err(I8042ControllerError::OutputBusy)
}
}
fn read_status(&self) -> Status {
Status::from_bits_retain(self.status_or_command_port.read())
}
fn write_command(&mut self, command: u8) {
self.status_or_command_port.write(command);
}
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)
}
pub(super) fn send_data(&mut self, data: u8) -> Result<(), I8042ControllerError> {
if !self.read_status().contains(Status::INPUT_BUFFER_IS_FULL) {
self.write_data(data);
Ok(())
} else {
Err(I8042ControllerError::OutputBusy)
}
}
fn write_data(&mut self, data: u8) {
self.data_port.write(data);
}
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)
}
pub(super) fn receive_data(&mut self) -> Option<u8> {
if self.read_status().contains(Status::OUTPUT_BUFFER_IS_FULL) {
Some(self.read_data())
} else {
None
}
}
fn read_data(&self) -> u8 {
self.data_port.read()
}
fn flush_output_buffer(&mut self) {
while self.receive_data().is_some() {}
}
}
/// Errors that can occur when initializing the i8042 controller.
#[derive(Debug, Clone, Copy)]
pub(super) enum I8042ControllerError {
ControllerTestFailed,
FirstPortTestFailed,
SecondPortTestFailed,
OutputBusy,
NoInput,
DeviceResetFailed,
DeviceUnknown,
DeviceAllocIrqFailed,
}
/// The commands that can be sent to the PS/2 controller.
///
/// Reference: <https://wiki.osdev.org/I8042_PS/2_Controller#PS/2_Controller_Commands>.
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
enum Command {
ReadConfiguration = 0x20,
WriteConfiguration = 0x60,
DisableSecondPort = 0xA7,
EnableSecondPort = 0xA8,
TestSecondPort = 0xA9,
TestController = 0xAA,
TestFirstPort = 0xAB,
DisableFirstPort = 0xAD,
EnableFirstPort = 0xAE,
}
bitflags! {
/// The configuration of the PS/2 controller.
///
/// Reference: <https://wiki.osdev.org/I8042_PS/2_Controller#PS/2_Controller_Configuration_Byte>.
struct Configuration: u8 {
/// First PS/2 port interrupt (1 = enabled, 0 = disabled)
const FIRST_PORT_INTERRUPT_ENABLED = 1 << 0;
/// Second PS/2 port interrupt (1 = enabled, 0 = disabled, only if 2 PS/2 ports supported)
const SECOND_PORT_INTERRUPT_ENABLED = 1 << 1;
/// System Flag (1 = system passed POST, 0 = your OS shouldn't be running)
const SYSTEM_POST_PASSED = 1 << 2;
/// First PS/2 port clock (1 = disabled, 0 = enabled)
const FIRST_PORT_CLOCK_DISABLED = 1 << 4;
/// Second PS/2 port clock (1 = disabled, 0 = enabled, only if 2 PS/2 ports supported)
const SECOND_PORT_CLOCK_DISABLED = 1 << 5;
/// First PS/2 port translation (1 = enabled, 0 = disabled)
const FIRST_PORT_TRANSLATION_ENABLED = 1 << 6;
}
}
bitflags! {
/// The status of the i8042 PS/2 controller.
///
/// Reference: <https://wiki.osdev.org/I8042_PS/2_Controller#Status_Register>.
struct Status: u8 {
/// Output buffer status (0 = empty, 1 = full)
/// Must be set before attempting to read data from port 0x60.
const OUTPUT_BUFFER_IS_FULL = 1 << 0;
/// Input buffer status (0 = empty, 1 = full)
/// Must be clear before attempting to write data to IO port 0x60 or IO port 0x64.
const INPUT_BUFFER_IS_FULL = 1 << 1;
/// System Flag
/// Meant to be cleared on reset and set by firmware (via. PS/2 Controller Configuration Byte)
/// if the system passes self tests (POST).
const SYSTEM_FLAG = 1 << 2;
/// Command or data (0 = data, 1 = command)
/// Data written to input buffer is data for PS/2 device or command for controller.
const IS_COMMAND = 1 << 3;
/// Time-out error (0 = no error, 1 = time-out error)
const TIME_OUT_ERROR = 1 << 6;
/// Parity error (0 = no error, 1 = parity error)
const PARITY_ERROR = 1 << 7;
}
}

View File

@ -0,0 +1,377 @@
// SPDX-License-Identifier: MPL-2.0
//! The i8042 keyboard driver.
use core::sync::atomic::{AtomicBool, Ordering};
use ostd::{
arch::kernel::{MappedIrqLine, IRQ_CHIP},
trap::{irq::IrqLine, TrapFrame},
};
use spin::Once;
use super::controller::{I8042Controller, I8042ControllerError, I8042_CONTROLLER};
use crate::{InputKey, KEYBOARD_CALLBACKS};
/// IRQ line for i8042 keyboard.
static IRQ_LINE: Once<MappedIrqLine> = Once::new();
/// ISA interrupt number for i8042 keyboard.
const ISA_INTR_NUM: u8 = 1;
pub(super) fn init(controller: &mut I8042Controller) -> Result<(), I8042ControllerError> {
// Reset keyboard device by sending 0xFF (reset command, supported by all PS/2 devices) to port 1
// and waiting for a response.
controller.wait_and_send_data(0xFF)?;
// The response should be 0xFA (ACK) and 0xAA (BAT successful), followed by the device PS/2 ID.
if controller.wait_and_recv_data()? != 0xFA {
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(0xAA) {
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 irq_line = IrqLine::alloc()
.and_then(|irq_line| {
IRQ_CHIP
.get()
.unwrap()
.map_isa_pin_to(irq_line, ISA_INTR_NUM)
})
.map_err(|_| I8042ControllerError::DeviceAllocIrqFailed)?;
irq_line.on_active(handle_keyboard_input);
IRQ_LINE.call_once(|| irq_line);
Ok(())
}
fn handle_keyboard_input(_trap_frame: &TrapFrame) {
if !I8042_CONTROLLER.is_completed() {
return;
}
let key = parse_inputkey();
for callback in KEYBOARD_CALLBACKS.lock().iter() {
callback(key);
}
}
/// A scan code in the Scan Code Set 1.
///
/// Reference: <https://wiki.osdev.org/PS/2_Keyboard#Scan_Code_Set_1>.
#[derive(Debug, Clone, Copy)]
struct ScanCode(u8);
impl ScanCode {
fn has_error(&self) -> bool {
// Key detection error or internal buffer overrun.
self.0 == 0xFF
}
fn is_pressed(&self) -> bool {
self.0 & 0x80 == 0
}
fn is_released(&self) -> bool {
self.0 & 0x80 != 0
}
fn is_shift(&self) -> bool {
let code = self.0 & 0x7F;
// Left/right shift codes
code == 0x2A || code == 0x36
}
fn is_ctrl(&self) -> bool {
let code = self.0 & 0x7F;
// Left/right ctrl codes
code == 0x1D
}
fn is_caps_lock(&self) -> bool {
self.0 == 0x3A
}
fn is_extension(&self) -> bool {
self.0 == 0xE0
}
fn plain_map(&self) -> InputKey {
match self.0 & 0x7F {
0x01 => InputKey::Esc,
0x02 => InputKey::One,
0x03 => InputKey::Two,
0x04 => InputKey::Three,
0x05 => InputKey::Four,
0x06 => InputKey::Five,
0x07 => InputKey::Six,
0x08 => InputKey::Seven,
0x09 => InputKey::Eight,
0x0A => InputKey::Nine,
0x0B => InputKey::Zero,
0x0C => InputKey::Minus,
0x0D => InputKey::Equal,
0x0E => InputKey::Del,
0x0F => InputKey::Tab,
0x10 => InputKey::LowercaseQ,
0x11 => InputKey::LowercaseW,
0x12 => InputKey::LowercaseE,
0x13 => InputKey::LowercaseR,
0x14 => InputKey::LowercaseT,
0x15 => InputKey::LowercaseY,
0x16 => InputKey::LowercaseU,
0x17 => InputKey::LowercaseI,
0x18 => InputKey::LowercaseO,
0x19 => InputKey::LowercaseP,
0x1A => InputKey::LeftBracket,
0x1B => InputKey::RightBracket,
0x1C => InputKey::Cr, // Enter
0x1D => InputKey::Ign, // Left Ctrl
0x1E => InputKey::LowercaseA,
0x1F => InputKey::LowercaseS,
0x20 => InputKey::LowercaseD,
0x21 => InputKey::LowercaseF,
0x22 => InputKey::LowercaseG,
0x23 => InputKey::LowercaseH,
0x24 => InputKey::LowercaseJ,
0x25 => InputKey::LowercaseK,
0x26 => InputKey::LowercaseL,
0x27 => InputKey::SemiColon,
0x28 => InputKey::SingleQuote,
0x29 => InputKey::Backtick,
0x2A => InputKey::Ign, // Left Shift
0x2B => InputKey::BackSlash,
0x2C => InputKey::LowercaseZ,
0x2D => InputKey::LowercaseX,
0x2E => InputKey::LowercaseC,
0x2F => InputKey::LowercaseV,
0x30 => InputKey::LowercaseB,
0x31 => InputKey::LowercaseN,
0x32 => InputKey::LowercaseM,
0x33 => InputKey::Comma,
0x34 => InputKey::Period,
0x35 => InputKey::ForwardSlash,
0x36 => InputKey::Ign, // Right Shift
0x37 => InputKey::Asterisk, // Keypad-* or (*/PrtScn) on a 83/84-key keyboard
0x38 => InputKey::Ign, // Left Alt
0x39 => InputKey::Space,
0x3A => InputKey::Ign, // CapsLock
0x3B => InputKey::F1,
0x3C => InputKey::F2,
0x3D => InputKey::F3,
0x3E => InputKey::F4,
0x3F => InputKey::F5,
0x40 => InputKey::F6,
0x41 => InputKey::F7,
0x42 => InputKey::F8,
0x43 => InputKey::F9,
0x44 => InputKey::F10,
0x45 => InputKey::Ign, // NumLock
0x46 => InputKey::Ign, // ScrollLock
0x47 => InputKey::Home, // Keypad-7 or Home
0x48 => InputKey::UpArrow, // Keypad-8 or Up
0x49 => InputKey::PageUp, // Keypad-9 or PageUp
0x4A => InputKey::Minus, // Keypad--
0x4B => InputKey::LeftArrow, // Keypad-4 or Left
0x4C => InputKey::Five, // Keypad-5
0x4D => InputKey::RightArrow, // Keypad-6 or Right
0x4E => InputKey::Plus, // Keypad-+
0x4F => InputKey::End, // Keypad-1 or End
0x50 => InputKey::DownArrow, // Keypad-2 or Down
0x51 => InputKey::PageDown, // Keypad-3 or PageDown
0x52 => InputKey::Insert, // Keypad-0 or Insert
0x53 => InputKey::Delete, // Keypad-. or Del
0x57 => InputKey::F11,
0x58 => InputKey::F12,
_ => InputKey::Ign,
}
}
fn shift_map(&self) -> InputKey {
match self.0 & 0x7F {
0x01 => InputKey::Esc,
0x02 => InputKey::Exclamation,
0x03 => InputKey::At,
0x04 => InputKey::Hash,
0x05 => InputKey::Dollar,
0x06 => InputKey::Percent,
0x07 => InputKey::Caret,
0x08 => InputKey::Ampersand,
0x09 => InputKey::Asterisk,
0x0A => InputKey::LeftParen,
0x0B => InputKey::RightParen,
0x0C => InputKey::Underscore,
0x0D => InputKey::Plus,
0x0E => InputKey::Del,
0x0F => InputKey::Tab,
0x10 => InputKey::UppercaseQ,
0x11 => InputKey::UppercaseW,
0x12 => InputKey::UppercaseE,
0x13 => InputKey::UppercaseR,
0x14 => InputKey::UppercaseT,
0x15 => InputKey::UppercaseY,
0x16 => InputKey::UppercaseU,
0x17 => InputKey::UppercaseI,
0x18 => InputKey::UppercaseO,
0x19 => InputKey::UppercaseP,
0x1A => InputKey::LeftBrace,
0x1B => InputKey::RightBrace,
0x1C => InputKey::Cr,
0x1E => InputKey::UppercaseA,
0x1F => InputKey::UppercaseS,
0x20 => InputKey::UppercaseD,
0x21 => InputKey::UppercaseF,
0x22 => InputKey::UppercaseG,
0x23 => InputKey::UppercaseH,
0x24 => InputKey::UppercaseJ,
0x25 => InputKey::UppercaseK,
0x26 => InputKey::UppercaseL,
0x27 => InputKey::Colon,
0x28 => InputKey::DoubleQuote,
0x29 => InputKey::Tilde,
0x2B => InputKey::Pipe,
0x2C => InputKey::UppercaseZ,
0x2D => InputKey::UppercaseX,
0x2E => InputKey::UppercaseC,
0x2F => InputKey::UppercaseV,
0x30 => InputKey::UppercaseB,
0x31 => InputKey::UppercaseN,
0x32 => InputKey::UppercaseM,
0x33 => InputKey::LessThan,
0x34 => InputKey::GreaterThan,
0x35 => InputKey::Question,
0x39 => InputKey::Space,
_ => InputKey::Ign,
}
}
fn ctrl_map(&self) -> InputKey {
match self.0 & 0x7F {
0x02 => InputKey::One,
0x03 => InputKey::Nul,
0x04 => InputKey::Esc,
0x05 => InputKey::Fs,
0x06 => InputKey::Gs,
0x07 => InputKey::Rs,
0x08 => InputKey::Us,
0x09 => InputKey::Del,
0x0A => InputKey::Nine,
0x0B => InputKey::Zero,
0x0C => InputKey::Us,
0x0D => InputKey::Equal,
0x0E => InputKey::Bs,
0x10 => InputKey::Dc1,
0x11 => InputKey::Etb,
0x12 => InputKey::Enq,
0x13 => InputKey::Dc2,
0x14 => InputKey::Dc4,
0x15 => InputKey::Em,
0x16 => InputKey::Nak,
0x17 => InputKey::Tab,
0x18 => InputKey::Si,
0x19 => InputKey::Dle,
0x1A => InputKey::Esc,
0x1B => InputKey::Gs,
0x1C => InputKey::Cr,
0x1E => InputKey::Soh,
0x1F => InputKey::Dc3,
0x20 => InputKey::Eot,
0x21 => InputKey::Ack,
0x22 => InputKey::Bel,
0x23 => InputKey::Bs,
0x24 => InputKey::Lf,
0x25 => InputKey::Vt,
0x26 => InputKey::Ff,
0x27 => InputKey::SemiColon,
0x28 => InputKey::SingleQuote,
0x29 => InputKey::Backtick,
0x2B => InputKey::Fs,
0x2C => InputKey::Sub,
0x2D => InputKey::Can,
0x2E => InputKey::Etx,
0x2F => InputKey::Syn,
0x30 => InputKey::Stx,
0x31 => InputKey::So,
0x32 => InputKey::Cr,
0x33 => InputKey::Comma,
0x34 => InputKey::Period,
0x35 => InputKey::Us,
_ => InputKey::Ign,
}
}
}
fn parse_inputkey() -> InputKey {
static CAPS_LOCK: AtomicBool = AtomicBool::new(false); // CapsLock key state
static SHIFT_KEY: AtomicBool = AtomicBool::new(false); // Shift key pressed
static CTRL_KEY: AtomicBool = AtomicBool::new(false); // Ctrl key pressed
let Some(data) = I8042_CONTROLLER.get().unwrap().lock().receive_data() else {
log::warn!("i8042 keyboard has no input data");
return InputKey::Ign;
};
let code = ScanCode(data);
if code.has_error() {
log::warn!("i8042 keyboard key detection error or internal buffer overrun");
return InputKey::Ign;
}
// TODO: Handle the scancodes with extended byte (0xE0). It generates two
// different interrupts: the first containing 0xE0, the second containing
// the scancode.
if code.is_extension() {
return InputKey::Ign;
}
// Handle the Ctrl key, holds the state.
if code.is_ctrl() {
if code.is_pressed() {
CTRL_KEY.store(true, Ordering::Relaxed);
} else {
CTRL_KEY.store(false, Ordering::Relaxed);
}
return InputKey::Ign;
}
// Handle the Shift key, holds the state.
if code.is_shift() {
if code.is_pressed() {
SHIFT_KEY.store(true, Ordering::Relaxed);
} else {
SHIFT_KEY.store(false, Ordering::Relaxed);
}
return InputKey::Ign;
}
// Ignore other release events.
if code.is_released() {
return InputKey::Ign;
}
if code.is_caps_lock() {
CAPS_LOCK.fetch_xor(true, Ordering::Relaxed);
return InputKey::Ign;
}
let ctrl_key = CTRL_KEY.load(Ordering::Relaxed);
let shift_key = SHIFT_KEY.load(Ordering::Relaxed);
let caps_lock = CAPS_LOCK.load(Ordering::Relaxed);
if ctrl_key {
code.ctrl_map()
} else if shift_key ^ caps_lock {
code.shift_map()
} else {
code.plain_map()
}
}

View File

@ -0,0 +1,10 @@
// SPDX-License-Identifier: MPL-2.0
mod controller;
mod keyboard;
pub(crate) fn init() {
if let Err(err) = controller::init() {
log::warn!("i8042 controller initialization failed: {:?}", err);
}
}

View File

@ -0,0 +1,353 @@
// SPDX-License-Identifier: MPL-2.0
//! Handle keyboard input.
#![no_std]
#![deny(unsafe_code)]
extern crate alloc;
use alloc::{boxed::Box, vec::Vec};
use component::{init_component, ComponentInitError};
use ostd::sync::{LocalIrqDisabled, SpinLock};
#[cfg(target_arch = "x86_64")]
mod i8042_chip;
static KEYBOARD_CALLBACKS: SpinLock<Vec<Box<KeyboardCallback>>, LocalIrqDisabled> =
SpinLock::new(Vec::new());
#[init_component]
fn init() -> Result<(), ComponentInitError> {
#[cfg(target_arch = "x86_64")]
i8042_chip::init();
Ok(())
}
/// The callback function for keyboard.
pub type KeyboardCallback = dyn Fn(InputKey) + Send + Sync;
pub fn register_callback(callback: &'static KeyboardCallback) {
KEYBOARD_CALLBACKS.lock().push(Box::new(callback));
}
/// Define unified keycodes for different types of keyboards.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum InputKey {
Ign, // Ignore this key
// Control characters
Nul, // Ctrl + @, null
Soh, // Ctrl + A, start of heading
Stx, // Ctrl + B, start of text
Etx, // Ctrl + C, end of text
Eot, // Ctrl + D, end of transmission
Enq, // Ctrl + E, enquiry
Ack, // Ctrl + F, acknowledge
Bel, // Ctrl + G, bell
Bs, // Ctrl + H, backspace,
Tab, // Ctrl + I, horizontal tab
Lf, // Ctrl + J, NL line feed, new line
Vt, // Ctrl + K, vertical tab
Ff, // Ctrl + L, NP form feed, new page
Cr, // Ctrl + M, carriage return
So, // Ctrl + N, shift out
Si, // Ctrl + O, shift in
Dle, // Ctrl + P, data link escape
Dc1, // Ctrl + Q, device control 1
Dc2, // Ctrl + R, device control 2
Dc3, // Ctrl + S, device control 3
Dc4, // Ctrl + T, device control 4
Nak, // Ctrl + U, negative acknowledge
Syn, // Ctrl + V, synchronous idle
Etb, // Ctrl + W, end of trans. block
Can, // Ctrl + X, cancel
Em, // Ctrl + Y, end of medium
Sub, // Ctrl + Z, substitute
Esc, // Ctrl + [, escape
Fs, // Ctrl + \, file separator
Gs, // Ctrl + ], group separator
Rs, // Ctrl + ^, record separator
Us, // Ctrl + _, unit separator
Space, // ' '
Exclamation, // '!'
DoubleQuote, // '"'
Hash, // '#'
Dollar, // '$'
Percent, // '%'
Ampersand, // '&'
SingleQuote, // '''
LeftParen, // '('
RightParen, // ')'
Asterisk, // '*'
Plus, // '+'
Comma, // ','
Minus, // '-'
Period, // '.'
ForwardSlash, // '/'
Zero,
One,
Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
Colon, // ':'
SemiColon, // ';'
LessThan, // '<'
Equal, // '='
GreaterThan, // '>'
Question, // '?'
At, // '@'
UppercaseA,
UppercaseB,
UppercaseC,
UppercaseD,
UppercaseE,
UppercaseF,
UppercaseG,
UppercaseH,
UppercaseI,
UppercaseJ,
UppercaseK,
UppercaseL,
UppercaseM,
UppercaseN,
UppercaseO,
UppercaseP,
UppercaseQ,
UppercaseR,
UppercaseS,
UppercaseT,
UppercaseU,
UppercaseV,
UppercaseW,
UppercaseX,
UppercaseY,
UppercaseZ,
LeftBracket, // '['
BackSlash, // '\'
RightBracket, // ']'
Caret, // '^'
Underscore, // '_'
Backtick, // '`'
LowercaseA,
LowercaseB,
LowercaseC,
LowercaseD,
LowercaseE,
LowercaseF,
LowercaseG,
LowercaseH,
LowercaseI,
LowercaseJ,
LowercaseK,
LowercaseL,
LowercaseM,
LowercaseN,
LowercaseO,
LowercaseP,
LowercaseQ,
LowercaseR,
LowercaseS,
LowercaseT,
LowercaseU,
LowercaseV,
LowercaseW,
LowercaseX,
LowercaseY,
LowercaseZ,
LeftBrace, // '{'
Pipe, // '|'
RightBrace, // '}'
Tilde, // '~'
Del,
UpArrow,
DownArrow,
RightArrow,
LeftArrow,
End,
Home,
Insert,
Delete,
PageUp,
PageDown,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
}
impl InputKey {
/// Gets the xterm control sequence for this key.
///
/// Reference: <https://invisible-island.net/xterm/ctlseqs/ctlseqs.pdf>
pub fn as_xterm_control_sequence(&self) -> &[u8] {
match self {
InputKey::Ign => &[],
// ASCII control characters (character code 0-31)
InputKey::Nul => &[0x00],
InputKey::Soh => &[0x01],
InputKey::Stx => &[0x02],
InputKey::Etx => &[0x03],
InputKey::Eot => &[0x04],
InputKey::Enq => &[0x05],
InputKey::Ack => &[0x06],
InputKey::Bel => &[0x07],
InputKey::Bs => &[0x08],
InputKey::Tab => &[0x09],
InputKey::Lf => &[0x0A],
InputKey::Vt => &[0x0B],
InputKey::Ff => &[0x0C],
InputKey::Cr => &[0x0D],
InputKey::So => &[0x0E],
InputKey::Si => &[0x0F],
InputKey::Dle => &[0x10],
InputKey::Dc1 => &[0x11],
InputKey::Dc2 => &[0x12],
InputKey::Dc3 => &[0x13],
InputKey::Dc4 => &[0x14],
InputKey::Nak => &[0x15],
InputKey::Syn => &[0x16],
InputKey::Etb => &[0x17],
InputKey::Can => &[0x18],
InputKey::Em => &[0x19],
InputKey::Sub => &[0x1A],
InputKey::Esc => &[0x1B],
InputKey::Fs => &[0x1C],
InputKey::Gs => &[0x1D],
InputKey::Rs => &[0x1E],
InputKey::Us => &[0x1F],
// ASCII printable characters (character code 32-127)
InputKey::Space => b" ",
InputKey::Exclamation => b"!",
InputKey::DoubleQuote => b"\"",
InputKey::Hash => b"#",
InputKey::Dollar => b"$",
InputKey::Percent => b"%",
InputKey::Ampersand => b"&",
InputKey::SingleQuote => b"'",
InputKey::LeftParen => b"(",
InputKey::RightParen => b")",
InputKey::Asterisk => b"*",
InputKey::Plus => b"+",
InputKey::Comma => b",",
InputKey::Minus => b"-",
InputKey::Period => b".",
InputKey::ForwardSlash => b"/",
InputKey::Zero => b"0",
InputKey::One => b"1",
InputKey::Two => b"2",
InputKey::Three => b"3",
InputKey::Four => b"4",
InputKey::Five => b"5",
InputKey::Six => b"6",
InputKey::Seven => b"7",
InputKey::Eight => b"8",
InputKey::Nine => b"9",
InputKey::Colon => b":",
InputKey::SemiColon => b";",
InputKey::LessThan => b"<",
InputKey::Equal => b"=",
InputKey::GreaterThan => b">",
InputKey::Question => b"?",
InputKey::At => b"@",
InputKey::UppercaseA => b"A",
InputKey::UppercaseB => b"B",
InputKey::UppercaseC => b"C",
InputKey::UppercaseD => b"D",
InputKey::UppercaseE => b"E",
InputKey::UppercaseF => b"F",
InputKey::UppercaseG => b"G",
InputKey::UppercaseH => b"H",
InputKey::UppercaseI => b"I",
InputKey::UppercaseJ => b"J",
InputKey::UppercaseK => b"K",
InputKey::UppercaseL => b"L",
InputKey::UppercaseM => b"M",
InputKey::UppercaseN => b"N",
InputKey::UppercaseO => b"O",
InputKey::UppercaseP => b"P",
InputKey::UppercaseQ => b"Q",
InputKey::UppercaseR => b"R",
InputKey::UppercaseS => b"S",
InputKey::UppercaseT => b"T",
InputKey::UppercaseU => b"U",
InputKey::UppercaseV => b"V",
InputKey::UppercaseW => b"W",
InputKey::UppercaseX => b"X",
InputKey::UppercaseY => b"Y",
InputKey::UppercaseZ => b"Z",
InputKey::LeftBracket => b"[",
InputKey::BackSlash => b"\\",
InputKey::RightBracket => b"]",
InputKey::Caret => b"^",
InputKey::Underscore => b"_",
InputKey::Backtick => b"`",
InputKey::LowercaseA => b"a",
InputKey::LowercaseB => b"b",
InputKey::LowercaseC => b"c",
InputKey::LowercaseD => b"d",
InputKey::LowercaseE => b"e",
InputKey::LowercaseF => b"f",
InputKey::LowercaseG => b"g",
InputKey::LowercaseH => b"h",
InputKey::LowercaseI => b"i",
InputKey::LowercaseJ => b"j",
InputKey::LowercaseK => b"k",
InputKey::LowercaseL => b"l",
InputKey::LowercaseM => b"m",
InputKey::LowercaseN => b"n",
InputKey::LowercaseO => b"o",
InputKey::LowercaseP => b"p",
InputKey::LowercaseQ => b"q",
InputKey::LowercaseR => b"r",
InputKey::LowercaseS => b"s",
InputKey::LowercaseT => b"t",
InputKey::LowercaseU => b"u",
InputKey::LowercaseV => b"v",
InputKey::LowercaseW => b"w",
InputKey::LowercaseX => b"x",
InputKey::LowercaseY => b"y",
InputKey::LowercaseZ => b"z",
InputKey::LeftBrace => b"{",
InputKey::Pipe => b"|",
InputKey::RightBrace => b"}",
InputKey::Tilde => b"~",
InputKey::Del => &[0x7F],
// PC-Style Function Keys
InputKey::UpArrow => b"\x1B[A",
InputKey::DownArrow => b"\x1B[B",
InputKey::RightArrow => b"\x1B[C",
InputKey::LeftArrow => b"\x1B[D",
InputKey::End => b"\x1B[F",
InputKey::Home => b"\x1B[H",
InputKey::Insert => b"\x1B[2~",
InputKey::Delete => b"\x1B[3~",
InputKey::PageUp => b"\x1B[5~",
InputKey::PageDown => b"\x1B[6~",
InputKey::F1 => b"\x1BOP",
InputKey::F2 => b"\x1BOQ",
InputKey::F3 => b"\x1BOR",
InputKey::F4 => b"\x1BOS",
InputKey::F5 => b"\x1B[15~",
InputKey::F6 => b"\x1B[17~",
InputKey::F7 => b"\x1B[18~",
InputKey::F8 => b"\x1B[19~",
InputKey::F9 => b"\x1B[20~",
InputKey::F10 => b"\x1B[21~",
InputKey::F11 => b"\x1B[23~",
InputKey::F12 => b"\x1B[24~",
}
}
}

View File

@ -109,7 +109,6 @@ QEMU_ARGS="\
-machine q35,kernel-irqchip=split \
-device virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0,serial=vext2,disable-legacy=on,disable-modern=off,queue-size=64,num-queues=1,request-merging=off,backend_defaults=off,discard=off,write-zeroes=off,event_idx=off,indirect_desc=off,queue_reset=off$IOMMU_DEV_EXTRA \
-device virtio-blk-pci,bus=pcie.0,addr=0x7,drive=x1,serial=vexfat,disable-legacy=on,disable-modern=off,queue-size=64,num-queues=1,request-merging=off,backend_defaults=off,discard=off,write-zeroes=off,event_idx=off,indirect_desc=off,queue_reset=off$IOMMU_DEV_EXTRA \
-device virtio-keyboard-pci,disable-legacy=on,disable-modern=off$IOMMU_DEV_EXTRA \
-device virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off$VIRTIO_NET_FEATURES$IOMMU_DEV_EXTRA \
-device virtio-serial-pci,disable-legacy=on,disable-modern=off$IOMMU_DEV_EXTRA \
-device virtconsole,chardev=mux \