From 4db677f82ef4d3dac1462d42fbe90bf3a43a816b Mon Sep 17 00:00:00 2001 From: Andreas Wessing Date: Tue, 23 Jun 2026 13:32:36 +0200 Subject: [PATCH 1/2] feat: add PS/2 keyboard interrupt driver --- Cargo.toml | 5 ++ src/arch/x86_64/kernel/interrupts.rs | 9 +++- src/arch/x86_64/kernel/keyboard.rs | 75 ++++++++++++++++++++++++++++ src/arch/x86_64/kernel/mod.rs | 2 + src/syscalls/system.rs | 14 ++++++ 5 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/arch/x86_64/kernel/keyboard.rs diff --git a/Cargo.toml b/Cargo.toml index 467138ff40..ba5eb8e1e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -216,6 +216,11 @@ virtio-vsock = ["virtio"] ## This is only useful on PCs (x86-64). vga = [] +## Enables the PS/2 keyboard driver. +## +## This is only useful on PCs (x86-64). +keyboard = [] + #! ### Performance Features ## Disables putting the CPU to sleep. diff --git a/src/arch/x86_64/kernel/interrupts.rs b/src/arch/x86_64/kernel/interrupts.rs index 3ed4572acb..3aefd34511 100644 --- a/src/arch/x86_64/kernel/interrupts.rs +++ b/src/arch/x86_64/kernel/interrupts.rs @@ -157,7 +157,14 @@ pub(crate) fn install() { IRQ_NAMES.lock().insert(7, "FPU"); } -pub(crate) fn install_handlers(handlers: InterruptHandlerMap) { +pub(crate) fn install_handlers(#[allow(unused_mut)] mut handlers: InterruptHandlerMap) { + #[cfg(feature = "keyboard")] + { + use crate::arch::kernel::keyboard::get_keyboard_handler; + let (irq, handler) = get_keyboard_handler(); + handlers.entry(irq).or_default().push_back(handler); + } + IRQ_HANDLERS.set(handlers).unwrap(); } diff --git a/src/arch/x86_64/kernel/keyboard.rs b/src/arch/x86_64/kernel/keyboard.rs new file mode 100644 index 0000000000..1fd0009ab3 --- /dev/null +++ b/src/arch/x86_64/kernel/keyboard.rs @@ -0,0 +1,75 @@ +use core::sync::atomic::{AtomicU8, AtomicUsize, Ordering}; + +use x86_64::instructions::port::Port; + +use crate::kernel::interrupts; + +const BUFFER_SIZE: usize = 256; +#[allow(clippy::declare_interior_mutable_const)] +const ATOMIC_ZERO: AtomicU8 = AtomicU8::new(0); +const PS2_DATA_PORT: u16 = 0x60; +const PS2_CMD_PORT: u16 = 0x64; +const PS2_CMD_READ_CNFG: u8 = 0x20; +const PS2_CMD_WRITE_CNFG: u8 = 0x60; +const PS2_CMD_DISABLE_KEYBOARD: u8 = 0xad; +const PS2_CMD_DISABLE_MOUSE: u8 = 0xa7; +const PS2_CMD_ENABLE_KEYBOARD: u8 = 0xae; +const PS2_CNFG_ENABLE_KEYBOARD_INTERRUPT: u8 = 0x01; +const PS2_BUFFER_FULL: u8 = 0x01; + +static KEYBOARD_BUFFER: [AtomicU8; BUFFER_SIZE] = [ATOMIC_ZERO; BUFFER_SIZE]; +static WRITE_INDEX: AtomicUsize = AtomicUsize::new(0); +static READ_INDEX: AtomicUsize = AtomicUsize::new(0); + +pub(crate) fn get_keyboard_handler() -> (u8, fn()) { + unsafe { + let mut cmd_port = Port::::new(PS2_CMD_PORT); + let mut data_port = Port::::new(PS2_DATA_PORT); + cmd_port.write(PS2_CMD_DISABLE_KEYBOARD); + cmd_port.write(PS2_CMD_DISABLE_MOUSE); + + while (cmd_port.read() & PS2_BUFFER_FULL) != 0 { + let _ = data_port.read(); + } + + cmd_port.write(PS2_CMD_READ_CNFG); + let mut config = data_port.read(); + + config |= PS2_CNFG_ENABLE_KEYBOARD_INTERRUPT; + + cmd_port.write(PS2_CMD_WRITE_CNFG); + data_port.write(config); + cmd_port.write(PS2_CMD_ENABLE_KEYBOARD); + } + fn keyboard_handler() { + let mut data_port = Port::::new(PS2_DATA_PORT); + let scancode = unsafe { data_port.read() }; + + let write_idx = WRITE_INDEX.load(Ordering::Relaxed); + let next_write_idx = write_idx.wrapping_add(1) % BUFFER_SIZE; + + let read_idx = READ_INDEX.load(Ordering::Acquire); + if next_write_idx != read_idx { + KEYBOARD_BUFFER[write_idx].store(scancode, Ordering::Release); + WRITE_INDEX.store(next_write_idx, Ordering::Release); + } + } + + interrupts::add_irq_name(1, "PS/2 Keyboard"); + + (1, keyboard_handler) +} + +/// Pops a scancode from the keyboard buffer, returning None if the buffer is empty. +pub fn pop_scancode() -> Option { + let read_idx = READ_INDEX.load(Ordering::Relaxed); + let write_idx = WRITE_INDEX.load(Ordering::Acquire); + + if read_idx == write_idx { + None + } else { + let scancode = KEYBOARD_BUFFER[read_idx].load(Ordering::Acquire); + READ_INDEX.store(read_idx.wrapping_add(1) % BUFFER_SIZE, Ordering::Release); + Some(scancode) + } +} diff --git a/src/arch/x86_64/kernel/mod.rs b/src/arch/x86_64/kernel/mod.rs index e0696d93a6..9a94d4270e 100644 --- a/src/arch/x86_64/kernel/mod.rs +++ b/src/arch/x86_64/kernel/mod.rs @@ -20,6 +20,8 @@ pub mod gdt; pub mod interrupts; #[cfg(feature = "kernel-stack")] pub mod kernel_stack; +#[cfg(feature = "keyboard")] +pub mod keyboard; #[cfg(all(not(feature = "pci"), feature = "virtio"))] pub mod mmio; #[cfg(feature = "pci")] diff --git a/src/syscalls/system.rs b/src/syscalls/system.rs index de963e3fed..4b6a3010b7 100644 --- a/src/syscalls/system.rs +++ b/src/syscalls/system.rs @@ -6,3 +6,17 @@ use crate::arch::mm::paging::{BasePageSize, PageSize}; pub extern "C" fn sys_getpagesize() -> i32 { BasePageSize::SIZE.try_into().unwrap() } + +#[cfg(all(target_arch = "x86_64", feature = "keyboard"))] +#[hermit_macro::system] +#[unsafe(no_mangle)] +pub extern "C" fn sys_read_keyboard() -> u8 { + crate::kernel::keyboard::pop_scancode().unwrap_or(0) +} + +#[cfg(not(all(target_arch = "x86_64", feature = "keyboard")))] +#[hermit_macro::system] +#[unsafe(no_mangle)] +pub extern "C" fn sys_read_keyboard() -> u8 { + 0 +} From 153302f0c0c3d5ef976f2f03b29e9654fac9b622 Mon Sep 17 00:00:00 2001 From: Andreas Wessing Date: Wed, 15 Jul 2026 16:20:41 +0200 Subject: [PATCH 2/2] refactor: rename ps2 keyboard driver to pc-keyboard --- Cargo.toml | 10 +++++++--- src/arch/x86_64/kernel/interrupts.rs | 4 ++-- src/arch/x86_64/kernel/mod.rs | 4 ++-- .../x86_64/kernel/{keyboard.rs => pc_keyboard.rs} | 0 src/syscalls/system.rs | 11 ++--------- 5 files changed, 13 insertions(+), 16 deletions(-) rename src/arch/x86_64/kernel/{keyboard.rs => pc_keyboard.rs} (100%) diff --git a/Cargo.toml b/Cargo.toml index ba5eb8e1e0..c6c4a0d216 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -217,9 +217,13 @@ virtio-vsock = ["virtio"] vga = [] ## Enables the PS/2 keyboard driver. -## -## This is only useful on PCs (x86-64). -keyboard = [] +## +## This feature initializes the PS/2 keyboard controller and installs a keyboard interrupt handler. +## It also provides a system call to receive the last scancode from the internal keyboard buffer. +## Note that this is not a complete keyboard driver and not needed for general keyboard support. +## It allows receiving scancodes from the PS/2 keyboard that can be used to port applications. +## This is only useful on PCs (x86-64). +pc-keyboard = [] #! ### Performance Features diff --git a/src/arch/x86_64/kernel/interrupts.rs b/src/arch/x86_64/kernel/interrupts.rs index 3aefd34511..cec803bb66 100644 --- a/src/arch/x86_64/kernel/interrupts.rs +++ b/src/arch/x86_64/kernel/interrupts.rs @@ -158,9 +158,9 @@ pub(crate) fn install() { } pub(crate) fn install_handlers(#[allow(unused_mut)] mut handlers: InterruptHandlerMap) { - #[cfg(feature = "keyboard")] + #[cfg(feature = "pc-keyboard")] { - use crate::arch::kernel::keyboard::get_keyboard_handler; + use crate::arch::kernel::pc_keyboard::get_keyboard_handler; let (irq, handler) = get_keyboard_handler(); handlers.entry(irq).or_default().push_back(handler); } diff --git a/src/arch/x86_64/kernel/mod.rs b/src/arch/x86_64/kernel/mod.rs index 9a94d4270e..249d93116f 100644 --- a/src/arch/x86_64/kernel/mod.rs +++ b/src/arch/x86_64/kernel/mod.rs @@ -20,10 +20,10 @@ pub mod gdt; pub mod interrupts; #[cfg(feature = "kernel-stack")] pub mod kernel_stack; -#[cfg(feature = "keyboard")] -pub mod keyboard; #[cfg(all(not(feature = "pci"), feature = "virtio"))] pub mod mmio; +#[cfg(feature = "pc-keyboard")] +pub mod pc_keyboard; #[cfg(feature = "pci")] pub mod pci; pub mod pic; diff --git a/src/arch/x86_64/kernel/keyboard.rs b/src/arch/x86_64/kernel/pc_keyboard.rs similarity index 100% rename from src/arch/x86_64/kernel/keyboard.rs rename to src/arch/x86_64/kernel/pc_keyboard.rs diff --git a/src/syscalls/system.rs b/src/syscalls/system.rs index 4b6a3010b7..4403bbb430 100644 --- a/src/syscalls/system.rs +++ b/src/syscalls/system.rs @@ -7,16 +7,9 @@ pub extern "C" fn sys_getpagesize() -> i32 { BasePageSize::SIZE.try_into().unwrap() } -#[cfg(all(target_arch = "x86_64", feature = "keyboard"))] +#[cfg(all(target_arch = "x86_64", feature = "pc-keyboard"))] #[hermit_macro::system] #[unsafe(no_mangle)] pub extern "C" fn sys_read_keyboard() -> u8 { - crate::kernel::keyboard::pop_scancode().unwrap_or(0) -} - -#[cfg(not(all(target_arch = "x86_64", feature = "keyboard")))] -#[hermit_macro::system] -#[unsafe(no_mangle)] -pub extern "C" fn sys_read_keyboard() -> u8 { - 0 + crate::kernel::pc_keyboard::pop_scancode().unwrap_or(0) }