From 4db677f82ef4d3dac1462d42fbe90bf3a43a816b Mon Sep 17 00:00:00 2001 From: Andreas Wessing Date: Tue, 23 Jun 2026 13:32:36 +0200 Subject: [PATCH 1/8] 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/8] 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) } From df6d5cf928328b07125197d672147d47271b8e3d Mon Sep 17 00:00:00 2001 From: Andreas Wessing Date: Tue, 21 Jul 2026 17:32:01 +0200 Subject: [PATCH 3/8] refactor: use mutex with vecdeque instead of atomic ringbuffer --- src/arch/x86_64/kernel/interrupts.rs | 4 ++- src/arch/x86_64/kernel/pc_keyboard.rs | 45 +++++++++++---------------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/arch/x86_64/kernel/interrupts.rs b/src/arch/x86_64/kernel/interrupts.rs index cec803bb66..e69df65a05 100644 --- a/src/arch/x86_64/kernel/interrupts.rs +++ b/src/arch/x86_64/kernel/interrupts.rs @@ -157,10 +157,12 @@ pub(crate) fn install() { IRQ_NAMES.lock().insert(7, "FPU"); } -pub(crate) fn install_handlers(#[allow(unused_mut)] mut handlers: InterruptHandlerMap) { +#[allow(unused_mut)] +pub(crate) fn install_handlers(mut handlers: InterruptHandlerMap) { #[cfg(feature = "pc-keyboard")] { 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/pc_keyboard.rs b/src/arch/x86_64/kernel/pc_keyboard.rs index 1fd0009ab3..c9731ce917 100644 --- a/src/arch/x86_64/kernel/pc_keyboard.rs +++ b/src/arch/x86_64/kernel/pc_keyboard.rs @@ -1,12 +1,10 @@ -use core::sync::atomic::{AtomicU8, AtomicUsize, Ordering}; +use alloc::collections::VecDeque; +use hermit_sync::{InterruptTicketMutex, Lazy}; 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; @@ -17,17 +15,20 @@ 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); +const BUFFER_SIZE: usize = 256; + +static KEYBOARD_BUFFER: Lazy>> = + Lazy::new(|| InterruptTicketMutex::new(VecDeque::with_capacity(BUFFER_SIZE))); pub(crate) fn get_keyboard_handler() -> (u8, fn()) { + let mut cmd_port = Port::::new(PS2_CMD_PORT); + let mut data_port = Port::::new(PS2_DATA_PORT); + 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); + // Clear garbage data from the PS/2 buffer while (cmd_port.read() & PS2_BUFFER_FULL) != 0 { let _ = data_port.read(); } @@ -41,20 +42,21 @@ pub(crate) fn get_keyboard_handler() -> (u8, fn()) { 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 mut buffer = KEYBOARD_BUFFER.lock(); - 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); + if buffer.len() >= BUFFER_SIZE { + buffer.pop_front(); } + buffer.push_back(scancode); } + // Force the initialization of the keyboard buffer to ensure it is ready before any interrupts occur. + Lazy::force(&KEYBOARD_BUFFER); + interrupts::add_irq_name(1, "PS/2 Keyboard"); (1, keyboard_handler) @@ -62,14 +64,5 @@ pub(crate) fn get_keyboard_handler() -> (u8, fn()) { /// 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) - } + KEYBOARD_BUFFER.lock().pop_front() } From 68d80826bd022aad10732281512db78d77e732db Mon Sep 17 00:00:00 2001 From: Andreas Wessing Date: Wed, 22 Jul 2026 17:56:40 +0200 Subject: [PATCH 4/8] refactor: ps2 controller port access abstraction --- src/arch/x86_64/kernel/pc_keyboard.rs | 71 +++++++++++++++++---------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/src/arch/x86_64/kernel/pc_keyboard.rs b/src/arch/x86_64/kernel/pc_keyboard.rs index c9731ce917..cd7224fde6 100644 --- a/src/arch/x86_64/kernel/pc_keyboard.rs +++ b/src/arch/x86_64/kernel/pc_keyboard.rs @@ -16,43 +16,60 @@ const PS2_CNFG_ENABLE_KEYBOARD_INTERRUPT: u8 = 0x01; const PS2_BUFFER_FULL: u8 = 0x01; const BUFFER_SIZE: usize = 256; +struct Ps2; -static KEYBOARD_BUFFER: Lazy>> = - Lazy::new(|| InterruptTicketMutex::new(VecDeque::with_capacity(BUFFER_SIZE))); +impl Ps2 { + pub fn read_status() -> u8 { + let mut status_port = Port::::new(PS2_CMD_PORT); + unsafe { status_port.read() } + } -pub(crate) fn get_keyboard_handler() -> (u8, fn()) { - let mut cmd_port = Port::::new(PS2_CMD_PORT); - let mut data_port = Port::::new(PS2_DATA_PORT); + pub fn write_cmd(cmd: u8) { + let mut cmd_port = Port::::new(PS2_CMD_PORT); + unsafe { cmd_port.write(cmd) } + } - unsafe { - cmd_port.write(PS2_CMD_DISABLE_KEYBOARD); - cmd_port.write(PS2_CMD_DISABLE_MOUSE); + pub fn read_data() -> u8 { + let mut data_port = Port::::new(PS2_DATA_PORT); + unsafe { data_port.read() } + } + + pub fn write_data(data: u8) { + let mut data_port = Port::::new(PS2_DATA_PORT); + unsafe { data_port.write(data) } + } +} - // Clear garbage data from the PS/2 buffer - while (cmd_port.read() & PS2_BUFFER_FULL) != 0 { - let _ = data_port.read(); - } +static KEYBOARD_BUFFER: Lazy>> = + Lazy::new(|| InterruptTicketMutex::new(VecDeque::with_capacity(BUFFER_SIZE))); - cmd_port.write(PS2_CMD_READ_CNFG); - let mut config = data_port.read(); +fn keyboard_handler() { + let scancode = Ps2::read_data(); + let mut buffer = KEYBOARD_BUFFER.lock(); - config |= PS2_CNFG_ENABLE_KEYBOARD_INTERRUPT; + if buffer.len() >= BUFFER_SIZE { + buffer.pop_front(); + } + buffer.push_back(scancode); +} - cmd_port.write(PS2_CMD_WRITE_CNFG); - data_port.write(config); - cmd_port.write(PS2_CMD_ENABLE_KEYBOARD); +pub(crate) fn get_keyboard_handler() -> (u8, fn()) { + Ps2::write_cmd(PS2_CMD_DISABLE_KEYBOARD); + Ps2::write_cmd(PS2_CMD_DISABLE_MOUSE); + // Ensure an empty buffer to guard against stuck data + while (Ps2::read_status() & PS2_BUFFER_FULL) != 0 { + let _ = Ps2::read_data(); } - fn keyboard_handler() { - let mut data_port = Port::::new(PS2_DATA_PORT); - let scancode = unsafe { data_port.read() }; - let mut buffer = KEYBOARD_BUFFER.lock(); + Ps2::write_cmd(PS2_CMD_READ_CNFG); + let mut config = Ps2::read_data(); - if buffer.len() >= BUFFER_SIZE { - buffer.pop_front(); - } - buffer.push_back(scancode); - } + config |= PS2_CNFG_ENABLE_KEYBOARD_INTERRUPT; + + Ps2::write_cmd(PS2_CMD_WRITE_CNFG); + + Ps2::write_data(config); + Ps2::write_cmd(PS2_CMD_ENABLE_KEYBOARD); // Force the initialization of the keyboard buffer to ensure it is ready before any interrupts occur. Lazy::force(&KEYBOARD_BUFFER); From 2913b39ae635e24ebdf64d11457fff802c79241d Mon Sep 17 00:00:00 2001 From: Andreas Wessing Date: Fri, 24 Jul 2026 14:21:04 +0200 Subject: [PATCH 5/8] style: change abstraction functions to oneliners --- src/arch/x86_64/kernel/pc_keyboard.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/arch/x86_64/kernel/pc_keyboard.rs b/src/arch/x86_64/kernel/pc_keyboard.rs index cd7224fde6..4bfdee95e7 100644 --- a/src/arch/x86_64/kernel/pc_keyboard.rs +++ b/src/arch/x86_64/kernel/pc_keyboard.rs @@ -1,4 +1,5 @@ use alloc::collections::VecDeque; +use core::num::NonZero; use hermit_sync::{InterruptTicketMutex, Lazy}; use x86_64::instructions::port::Port; @@ -16,27 +17,23 @@ const PS2_CNFG_ENABLE_KEYBOARD_INTERRUPT: u8 = 0x01; const PS2_BUFFER_FULL: u8 = 0x01; const BUFFER_SIZE: usize = 256; -struct Ps2; +struct Ps2; impl Ps2 { pub fn read_status() -> u8 { - let mut status_port = Port::::new(PS2_CMD_PORT); - unsafe { status_port.read() } + unsafe { Port::::new(PS2_CMD_PORT).read() } } pub fn write_cmd(cmd: u8) { - let mut cmd_port = Port::::new(PS2_CMD_PORT); - unsafe { cmd_port.write(cmd) } + unsafe { Port::::new(PS2_CMD_PORT).write(cmd) } } pub fn read_data() -> u8 { - let mut data_port = Port::::new(PS2_DATA_PORT); - unsafe { data_port.read() } + unsafe { Port::::new(PS2_DATA_PORT).read() } } pub fn write_data(data: u8) { - let mut data_port = Port::::new(PS2_DATA_PORT); - unsafe { data_port.write(data) } + unsafe { Port::::new(PS2_DATA_PORT).write(data) } } } @@ -47,15 +44,18 @@ fn keyboard_handler() { let scancode = Ps2::read_data(); let mut buffer = KEYBOARD_BUFFER.lock(); + // Don't allow the buffer to grow infinitely, pop the oldest scancode if the buffer is full. if buffer.len() >= BUFFER_SIZE { buffer.pop_front(); } + buffer.push_back(scancode); } pub(crate) fn get_keyboard_handler() -> (u8, fn()) { Ps2::write_cmd(PS2_CMD_DISABLE_KEYBOARD); Ps2::write_cmd(PS2_CMD_DISABLE_MOUSE); + // Ensure an empty buffer to guard against stuck data while (Ps2::read_status() & PS2_BUFFER_FULL) != 0 { let _ = Ps2::read_data(); @@ -80,6 +80,6 @@ pub(crate) fn get_keyboard_handler() -> (u8, fn()) { } /// Pops a scancode from the keyboard buffer, returning None if the buffer is empty. -pub fn pop_scancode() -> Option { +pub fn pop_scancode() -> Option> { KEYBOARD_BUFFER.lock().pop_front() } From 166f39699aa195ecefe5769e2fe92f32bbe42803 Mon Sep 17 00:00:00 2001 From: Andreas Wessing Date: Sat, 1 Aug 2026 19:23:26 +0200 Subject: [PATCH 6/8] refactor: systemcall inspired by linux design --- src/arch/x86_64/kernel/pc_keyboard.rs | 58 +++++++++++++++++++++------ src/syscalls/system.rs | 16 +++++++- 2 files changed, 59 insertions(+), 15 deletions(-) diff --git a/src/arch/x86_64/kernel/pc_keyboard.rs b/src/arch/x86_64/kernel/pc_keyboard.rs index 4bfdee95e7..56f497e8ac 100644 --- a/src/arch/x86_64/kernel/pc_keyboard.rs +++ b/src/arch/x86_64/kernel/pc_keyboard.rs @@ -1,10 +1,11 @@ use alloc::collections::VecDeque; -use core::num::NonZero; +use core::num::NonZeroU8; use hermit_sync::{InterruptTicketMutex, Lazy}; use x86_64::instructions::port::Port; use crate::kernel::interrupts; +use crate::synch::semaphore::Semaphore; const PS2_DATA_PORT: u16 = 0x60; const PS2_CMD_PORT: u16 = 0x64; @@ -13,10 +14,13 @@ 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; +#[allow(dead_code)] +const PS2_CMD_ENABLE_MOUSE: u8 = 0xa8; const PS2_CNFG_ENABLE_KEYBOARD_INTERRUPT: u8 = 0x01; const PS2_BUFFER_FULL: u8 = 0x01; const BUFFER_SIZE: usize = 256; +static KEYBOARD_SEMAPHORE: Semaphore = Semaphore::new(0); struct Ps2; impl Ps2 { @@ -37,26 +41,34 @@ impl Ps2 { } } -static KEYBOARD_BUFFER: Lazy>> = +static KEYBOARD_BUFFER: Lazy>> = Lazy::new(|| InterruptTicketMutex::new(VecDeque::with_capacity(BUFFER_SIZE))); fn keyboard_handler() { let scancode = Ps2::read_data(); - let mut buffer = KEYBOARD_BUFFER.lock(); - - // Don't allow the buffer to grow infinitely, pop the oldest scancode if the buffer is full. - if buffer.len() >= BUFFER_SIZE { - buffer.pop_front(); + if let Some(valid_scancode) = NonZeroU8::new(scancode) { + let mut sem = true; + { + let mut buffer = KEYBOARD_BUFFER.lock(); + + // Pop the oldest scancode if the buffer is full. + if buffer.len() >= BUFFER_SIZE { + buffer.pop_front(); + sem = false; + } + buffer.push_back(valid_scancode); + } + if sem { + KEYBOARD_SEMAPHORE.release(); + } } - - buffer.push_back(scancode); } pub(crate) fn get_keyboard_handler() -> (u8, fn()) { Ps2::write_cmd(PS2_CMD_DISABLE_KEYBOARD); Ps2::write_cmd(PS2_CMD_DISABLE_MOUSE); - // Ensure an empty buffer to guard against stuck data + // Ensure an empty buffer to guard against stuck/garbage data while (Ps2::read_status() & PS2_BUFFER_FULL) != 0 { let _ = Ps2::read_data(); } @@ -79,7 +91,27 @@ pub(crate) fn get_keyboard_handler() -> (u8, fn()) { (1, keyboard_handler) } -/// Pops a scancode from the keyboard buffer, returning None if the buffer is empty. -pub fn pop_scancode() -> Option> { - KEYBOARD_BUFFER.lock().pop_front() +/// Pops scancodes from the keyboard buffer into the provided slice. If `nonblocking` is false, the +/// function will sleep the current thread until a scancode has been received. Returns the number of scancodes +/// popped into the slice. +pub fn pop_scancodes(slice: &mut [u8], nonblocking: bool) -> usize { + if slice.is_empty() { + return 0; + } + if nonblocking { + if !KEYBOARD_SEMAPHORE.try_acquire() { + return 0; + } + } else { + KEYBOARD_SEMAPHORE.acquire(None); + } + let mut amount: usize = 1; + while amount < slice.len() && KEYBOARD_SEMAPHORE.try_acquire() { + amount += 1; + } + let mut buffer = KEYBOARD_BUFFER.lock(); + for scancode in slice[..amount].iter_mut() { + *scancode = buffer.pop_front().unwrap().get(); + } + amount } diff --git a/src/syscalls/system.rs b/src/syscalls/system.rs index 4403bbb430..5df5991e78 100644 --- a/src/syscalls/system.rs +++ b/src/syscalls/system.rs @@ -10,6 +10,18 @@ pub extern "C" fn sys_getpagesize() -> i32 { #[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::pc_keyboard::pop_scancode().unwrap_or(0) +pub unsafe extern "C" fn sys_read_keyboard(buffer: *mut u8, size: usize, nonblock: bool) -> isize { + if buffer.is_null() { + return -(crate::errno::Errno::Fault as isize); + } + if size == 0 { + return 0; + } + let buffer_slice: &mut [u8] = unsafe { core::slice::from_raw_parts_mut(buffer, size) }; + let result = crate::kernel::pc_keyboard::pop_scancodes(buffer_slice, nonblock); + if result == 0 && nonblock { + -(crate::errno::Errno::Again as isize) + } else { + result as isize + } } From 216783732328ee6000aeaafa464db5d72c9db947 Mon Sep 17 00:00:00 2001 From: Andreas Wessing Date: Sat, 1 Aug 2026 19:43:01 +0200 Subject: [PATCH 7/8] style: change Ps2 Commands to enums, add Ps2 Port test --- src/arch/x86_64/kernel/pc_keyboard.rs | 42 +++++++++++++++++---------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/arch/x86_64/kernel/pc_keyboard.rs b/src/arch/x86_64/kernel/pc_keyboard.rs index 56f497e8ac..0f65aa4604 100644 --- a/src/arch/x86_64/kernel/pc_keyboard.rs +++ b/src/arch/x86_64/kernel/pc_keyboard.rs @@ -9,13 +9,19 @@ use crate::synch::semaphore::Semaphore; 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; -#[allow(dead_code)] -const PS2_CMD_ENABLE_MOUSE: u8 = 0xa8; + +#[repr(u8)] +enum Ps2Command { + ReadConfig = 0x20, + WriteConfig = 0x60, + DisableKeyboard = 0xad, + DisableMouse = 0xa7, + EnableKeyboard = 0xae, + #[allow(dead_code)] + EnableMouse = 0xa8, + TestFirstPort = 0xab, +} + const PS2_CNFG_ENABLE_KEYBOARD_INTERRUPT: u8 = 0x01; const PS2_BUFFER_FULL: u8 = 0x01; @@ -28,8 +34,8 @@ impl Ps2 { unsafe { Port::::new(PS2_CMD_PORT).read() } } - pub fn write_cmd(cmd: u8) { - unsafe { Port::::new(PS2_CMD_PORT).write(cmd) } + pub fn write_cmd(cmd: Ps2Command) { + unsafe { Port::::new(PS2_CMD_PORT).write(cmd as u8) } } pub fn read_data() -> u8 { @@ -65,23 +71,29 @@ fn keyboard_handler() { } pub(crate) fn get_keyboard_handler() -> (u8, fn()) { - Ps2::write_cmd(PS2_CMD_DISABLE_KEYBOARD); - Ps2::write_cmd(PS2_CMD_DISABLE_MOUSE); + Ps2::write_cmd(Ps2Command::DisableKeyboard); + Ps2::write_cmd(Ps2Command::DisableMouse); // Ensure an empty buffer to guard against stuck/garbage data while (Ps2::read_status() & PS2_BUFFER_FULL) != 0 { let _ = Ps2::read_data(); } - Ps2::write_cmd(PS2_CMD_READ_CNFG); + Ps2::write_cmd(Ps2Command::ReadConfig); let mut config = Ps2::read_data(); config |= PS2_CNFG_ENABLE_KEYBOARD_INTERRUPT; - Ps2::write_cmd(PS2_CMD_WRITE_CNFG); - + Ps2::write_cmd(Ps2Command::WriteConfig); Ps2::write_data(config); - Ps2::write_cmd(PS2_CMD_ENABLE_KEYBOARD); + + Ps2::write_cmd(Ps2Command::TestFirstPort); + + if Ps2::read_data() != 0 { + error!("PS/2 keyboard test failed"); + } + + Ps2::write_cmd(Ps2Command::EnableKeyboard); // Force the initialization of the keyboard buffer to ensure it is ready before any interrupts occur. Lazy::force(&KEYBOARD_BUFFER); From a0b942a8a119b9b906574efcf89ba582dfa0fff2 Mon Sep 17 00:00:00 2001 From: Andreas Wessing <31967704+GloriousAlpaca@users.noreply.github.com> Date: Sat, 1 Aug 2026 19:47:06 +0200 Subject: [PATCH 8/8] Update Cargo.toml Co-authored-by: Jonathan --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c6c4a0d216..5530f756ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -220,7 +220,7 @@ vga = [] ## ## 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. +## Note that this is not a complete keyboard driver and not needed for serial input/output. ## 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 = []