diff --git a/Cargo.toml b/Cargo.toml index 467138ff40..c6c4a0d216 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -216,6 +216,15 @@ virtio-vsock = ["virtio"] ## This is only useful on PCs (x86-64). vga = [] +## Enables the PS/2 keyboard driver. +## +## 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 ## 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..e69df65a05 100644 --- a/src/arch/x86_64/kernel/interrupts.rs +++ b/src/arch/x86_64/kernel/interrupts.rs @@ -157,7 +157,16 @@ pub(crate) fn install() { IRQ_NAMES.lock().insert(7, "FPU"); } -pub(crate) fn install_handlers(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); + } + IRQ_HANDLERS.set(handlers).unwrap(); } diff --git a/src/arch/x86_64/kernel/mod.rs b/src/arch/x86_64/kernel/mod.rs index e0696d93a6..249d93116f 100644 --- a/src/arch/x86_64/kernel/mod.rs +++ b/src/arch/x86_64/kernel/mod.rs @@ -22,6 +22,8 @@ pub mod interrupts; pub mod kernel_stack; #[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/pc_keyboard.rs b/src/arch/x86_64/kernel/pc_keyboard.rs new file mode 100644 index 0000000000..c9731ce917 --- /dev/null +++ b/src/arch/x86_64/kernel/pc_keyboard.rs @@ -0,0 +1,68 @@ +use alloc::collections::VecDeque; + +use hermit_sync::{InterruptTicketMutex, Lazy}; +use x86_64::instructions::port::Port; + +use crate::kernel::interrupts; + +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; + +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 { + 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(); + } + + 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 mut buffer = KEYBOARD_BUFFER.lock(); + + 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) +} + +/// Pops a scancode from the keyboard buffer, returning None if the buffer is empty. +pub fn pop_scancode() -> Option { + KEYBOARD_BUFFER.lock().pop_front() +} diff --git a/src/syscalls/system.rs b/src/syscalls/system.rs index de963e3fed..4403bbb430 100644 --- a/src/syscalls/system.rs +++ b/src/syscalls/system.rs @@ -6,3 +6,10 @@ 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 = "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) +}