Skip to content

feat: add PS/2 keyboard interrupt driver#2532

Open
GloriousAlpaca wants to merge 1 commit into
hermit-os:mainfrom
GloriousAlpaca:pr-keyboard-clean
Open

feat: add PS/2 keyboard interrupt driver#2532
GloriousAlpaca wants to merge 1 commit into
hermit-os:mainfrom
GloriousAlpaca:pr-keyboard-clean

Conversation

@GloriousAlpaca

Copy link
Copy Markdown

This feature adds support for the PS2 legacy keyboard in Qemu x86_64.
Currently it does:

  1. A minimized hardware initialization (e.g. flush the output buffer) to use the keyboard, according to the standards for the Intel 8042 chip: Link to OSDev
  2. Install an interrupt handler, which is done in the install_handlers function in interrupt.rs, as it is a hardware interrupt but does not belong in the pci interrupt handlers list (legacy device).
  3. Add a systemcall to pop the last scancode out of the keyboard input ringbuffer (which currently has a length of 256). The Ringbuffer also uses Atomics, to prevent races and deadlocks.
    The systemcall returns 0 if the keyboard feature is disabled.

I have only tested this feature with C programs on a Mac using Qemu.

@mkroening mkroening self-assigned this Jul 6, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark Results

Details
Benchmark Current: f6c859b Previous: b3883fa Performance Ratio
startup_benchmark Build Time 80.43 s 80.39 s 1.00
startup_benchmark File Size 0.79 MB 0.79 MB 1.00
Startup Time - 1 core 0.74 s (±0.02 s) 0.74 s (±0.01 s) 0.99
Startup Time - 2 cores 0.75 s (±0.02 s) 0.75 s (±0.02 s) 1.00
Startup Time - 4 cores 0.74 s (±0.02 s) 0.76 s (±0.02 s) 0.98
multithreaded_benchmark Build Time 78.14 s 83.71 s 0.93
multithreaded_benchmark File Size 0.90 MB 0.90 MB 1.00
Multithreaded Pi Efficiency - 2 Threads 91.24 % (±6.29 %) 87.67 % (±5.37 %) 1.04
Multithreaded Pi Efficiency - 4 Threads 44.09 % (±3.01 %) 43.34 % (±2.79 %) 1.02
Multithreaded Pi Efficiency - 8 Threads 25.97 % (±1.36 %) 25.32 % (±1.83 %) 1.03
micro_benchmarks Build Time 77.31 s 80.38 s 0.96
micro_benchmarks File Size 0.90 MB 0.90 MB 1.00
Scheduling time - 1 thread 64.87 ticks (±1.95 ticks) 68.25 ticks (±5.81 ticks) 0.95
Scheduling time - 2 threads 35.83 ticks (±4.52 ticks) 38.15 ticks (±5.34 ticks) 0.94
Micro - Time for syscall (getpid) 4.31 ticks (±0.68 ticks) 4.13 ticks (±0.69 ticks) 1.05
Memcpy speed - (built_in) block size 4096 84909.85 MByte/s (±58667.15 MByte/s) 82192.20 MByte/s (±56877.97 MByte/s) 1.03
Memcpy speed - (built_in) block size 1048576 30612.01 MByte/s (±24660.72 MByte/s) 30409.95 MByte/s (±24564.57 MByte/s) 1.01
Memcpy speed - (built_in) block size 16777216 29334.73 MByte/s (±24120.88 MByte/s) 26462.68 MByte/s (±21974.68 MByte/s) 1.11
Memset speed - (built_in) block size 4096 85374.66 MByte/s (±58982.93 MByte/s) 82271.23 MByte/s (±56937.12 MByte/s) 1.04
Memset speed - (built_in) block size 1048576 31379.42 MByte/s (±25114.87 MByte/s) 31147.37 MByte/s (±25005.00 MByte/s) 1.01
Memset speed - (built_in) block size 16777216 30110.51 MByte/s (±24574.49 MByte/s) 27241.00 MByte/s (±22475.91 MByte/s) 1.11
Memcpy speed - (rust) block size 4096 75734.61 MByte/s (±52842.48 MByte/s) 74974.37 MByte/s (±52256.45 MByte/s) 1.01
Memcpy speed - (rust) block size 1048576 30314.32 MByte/s (±24509.72 MByte/s) 30241.00 MByte/s (±24501.06 MByte/s) 1.00
Memcpy speed - (rust) block size 16777216 29430.51 MByte/s (±24217.05 MByte/s) 27430.85 MByte/s (±22631.84 MByte/s) 1.07
Memset speed - (rust) block size 4096 76210.80 MByte/s (±53189.43 MByte/s) 75374.74 MByte/s (±52547.87 MByte/s) 1.01
Memset speed - (rust) block size 1048576 31078.99 MByte/s (±24965.20 MByte/s) 30996.28 MByte/s (±24944.22 MByte/s) 1.00
Memset speed - (rust) block size 16777216 30218.81 MByte/s (±24682.21 MByte/s) 28210.43 MByte/s (±23118.03 MByte/s) 1.07
alloc_benchmarks Build Time 76.26 s 76.93 s 0.99
alloc_benchmarks File Size 0.87 MB 0.87 MB 1.00
Allocations - Allocation success 91.31 % 91.31 % 1
Allocations - Deallocation success 100.00 % 100.00 % 1
Allocations - Pre-fail Allocations 61.44 % 61.44 % 1
Allocations - Average Allocation time 5077.51 Ticks (±103.05 Ticks) 5120.57 Ticks (±121.11 Ticks) 0.99
Allocations - Average Allocation time (no fail) 5832.75 Ticks (±108.42 Ticks) 5812.28 Ticks (±133.21 Ticks) 1.00
Allocations - Average Deallocation time 1294.70 Ticks (±142.42 Ticks) 1001.83 Ticks (±184.72 Ticks) 1.29
mutex_benchmark Build Time 75.95 s 77.85 s 0.98
mutex_benchmark File Size 0.90 MB 0.90 MB 1.00
Mutex Stress Test Average Time per Iteration - 1 Threads 12.24 ns (±0.68 ns) 12.44 ns (±0.61 ns) 0.98
Mutex Stress Test Average Time per Iteration - 2 Threads 41.90 ns (±2.26 ns) 44.68 ns (±3.31 ns) 0.94

This comment was automatically generated by workflow using github-action-benchmark.

@mkroening mkroening left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! :)

I was wondering why implement drivers for legacy devices instead of USB keyboards (xhci, usb-oxide, embassy-usb). I guess it is because of simplicity.

It would be great to discuss the high-level application-facing API, since that is the hardest to change once merged.

Comment thread Cargo.toml
"dhcpv4",
"fsgsbase",
"kernel-stack",
"keyboard",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be a default feature.

Comment thread Cargo.toml
Comment on lines +220 to +223
## Enables the PS/2 keyboard driver.
##
## This is only useful on PCs (x86-64).
keyboard = []

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you rename this to pc-keyboard? Also, please make sure that the docs don't imply that this is needed for keyboard support in general. Serial-based keyboard support is separate from this. Similar to the framebuffer, please also put a disclaimer that this does not make the kernel use the driver and instead exposes an API for applications that need to be ported.

Comment thread src/syscalls/system.rs
Comment on lines +17 to +22
#[cfg(not(all(target_arch = "x86_64", feature = "keyboard")))]
#[hermit_macro::system]
#[unsafe(no_mangle)]
pub extern "C" fn sys_read_keyboard() -> u8 {
0
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the framebuffer PR, please either merge the function definitions or (preferrably) just remove the symbol when the feature is disabled.

}

pub(crate) fn install_handlers(handlers: InterruptHandlerMap) {
pub(crate) fn install_handlers(#[allow(unused_mut)] mut handlers: InterruptHandlerMap) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move the allow to the function level instead of having it inline like this.

pub(crate) fn install_handlers(#[allow(unused_mut)] mut handlers: InterruptHandlerMap) {
#[cfg(feature = "keyboard")]
{
use crate::arch::kernel::keyboard::get_keyboard_handler;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
use crate::arch::kernel::keyboard::get_keyboard_handler;
use crate::arch::kernel::keyboard::get_keyboard_handler;

data_port.write(config);
cmd_port.write(PS2_CMD_ENABLE_KEYBOARD);
}
fn keyboard_handler() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move the handler to the top level outside of this function.

Comment on lines +31 to +33
while (cmd_port.read() & PS2_BUFFER_FULL) != 0 {
let _ = data_port.read();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we discard the device buffer instead of filling our read buffer?

Comment on lines +26 to +27
let mut cmd_port = Port::<u8>::new(PS2_CMD_PORT);
let mut data_port = Port::<u8>::new(PS2_DATA_PORT);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think an abstraction similar to the proposal for BGA would make sense?

struct Ps2;

impl Ps2 {
    fn read_data() -> u8;
    fn write_data(data: u8);
    fn status() -> u8;
    fn command(command: u8);
}

Comment on lines +63 to +64
/// Pops a scancode from the keyboard buffer, returning None if the buffer is empty.
pub fn pop_scancode() -> Option<u8> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A scancode can never be zero, right? Returning Option<NonZero<u8>> would be preferable in that case.

Comment thread src/syscalls/system.rs
Comment on lines +10 to +15
#[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)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not too sure about this API. Is the application supposed to busy loop on this and retrieve one event at a time?

What about doing something similar to Linux's event device (evdev) interface (Linux docs)? Reading from /dev/input/event0 would then fill a user buffer with input events and blocks if no events are there unless opened with O_NONBLOCK.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants