From 5e3e23bf7c5130a9251b265c5377d2e21769097c Mon Sep 17 00:00:00 2001 From: Qingsong Chen Date: Wed, 25 Jun 2025 02:04:24 +0000 Subject: [PATCH] Add basic i8042 keyboard support --- Cargo.lock | 13 + Cargo.toml | 1 + Components.toml | 1 + Makefile | 1 + kernel/Cargo.toml | 1 + kernel/comps/framebuffer/Cargo.toml | 1 + kernel/comps/framebuffer/src/console.rs | 28 +- kernel/comps/keyboard/Cargo.toml | 16 + .../keyboard/src/i8042_chip/controller.rs | 285 +++++++++++++ .../comps/keyboard/src/i8042_chip/keyboard.rs | 377 ++++++++++++++++++ kernel/comps/keyboard/src/i8042_chip/mod.rs | 10 + kernel/comps/keyboard/src/lib.rs | 353 ++++++++++++++++ tools/qemu_args.sh | 1 - 13 files changed, 1084 insertions(+), 4 deletions(-) create mode 100644 kernel/comps/keyboard/Cargo.toml create mode 100644 kernel/comps/keyboard/src/i8042_chip/controller.rs create mode 100644 kernel/comps/keyboard/src/i8042_chip/keyboard.rs create mode 100644 kernel/comps/keyboard/src/i8042_chip/mod.rs create mode 100644 kernel/comps/keyboard/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index c88219d5d..9376af9f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 9d0bf89b7..be13d3320 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/Components.toml b/Components.toml index 48215189a..670e33812 100644 --- a/Components.toml +++ b/Components.toml @@ -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] diff --git a/Makefile b/Makefile index f75b2d80f..2e6035a11 100644 --- a/Makefile +++ b/Makefile @@ -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 \ diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index bd80da56b..ac72ebaa7 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -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" } diff --git a/kernel/comps/framebuffer/Cargo.toml b/kernel/comps/framebuffer/Cargo.toml index 078cc4310..b49117e55 100644 --- a/kernel/comps/framebuffer/Cargo.toml +++ b/kernel/comps/framebuffer/Cargo.toml @@ -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" ] } diff --git a/kernel/comps/framebuffer/src/console.rs b/kernel/comps/framebuffer/src/console.rs index f39fdfd44..f092e02cb 100644 --- a/kernel/comps/framebuffer/src/console.rs +++ b/kernel/comps/framebuffer/src/console.rs @@ -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, + callbacks: SpinLock, 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); + } +} diff --git a/kernel/comps/keyboard/Cargo.toml b/kernel/comps/keyboard/Cargo.toml new file mode 100644 index 000000000..80f392668 --- /dev/null +++ b/kernel/comps/keyboard/Cargo.toml @@ -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 diff --git a/kernel/comps/keyboard/src/i8042_chip/controller.rs b/kernel/comps/keyboard/src/i8042_chip/controller.rs new file mode 100644 index 000000000..2eeee0dc3 --- /dev/null +++ b/kernel/comps/keyboard/src/i8042_chip/controller.rs @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! Provides i8042 PS/2 Controller I/O port access. +//! +//! Reference: +//! + +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> = Once::new(); + +pub(super) fn init() -> Result<(), I8042ControllerError> { + let mut controller = I8042Controller::new()?; + + // The steps to initialize the i8042 controller are from: + // . + + // 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, + 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 { + // TODO: Check the flags in the ACPI table to determine if the PS/2 controller exists. See: + // . + 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 { + 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 { + 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 { + 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: . +#[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: . + 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: . + 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; + } +} diff --git a/kernel/comps/keyboard/src/i8042_chip/keyboard.rs b/kernel/comps/keyboard/src/i8042_chip/keyboard.rs new file mode 100644 index 000000000..fdae17785 --- /dev/null +++ b/kernel/comps/keyboard/src/i8042_chip/keyboard.rs @@ -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 = 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 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: . +#[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() + } +} diff --git a/kernel/comps/keyboard/src/i8042_chip/mod.rs b/kernel/comps/keyboard/src/i8042_chip/mod.rs new file mode 100644 index 000000000..87ded8541 --- /dev/null +++ b/kernel/comps/keyboard/src/i8042_chip/mod.rs @@ -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); + } +} diff --git a/kernel/comps/keyboard/src/lib.rs b/kernel/comps/keyboard/src/lib.rs new file mode 100644 index 000000000..04bb8cbe7 --- /dev/null +++ b/kernel/comps/keyboard/src/lib.rs @@ -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>, 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: + 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~", + } + } +} diff --git a/tools/qemu_args.sh b/tools/qemu_args.sh index b5f12f9a6..f7abf8d2f 100755 --- a/tools/qemu_args.sh +++ b/tools/qemu_args.sh @@ -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 \