diff --git a/Cargo.toml b/Cargo.toml index 467138ff40..5530f756ae 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 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 = [] + #! ### 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..0f65aa4604 --- /dev/null +++ b/src/arch/x86_64/kernel/pc_keyboard.rs @@ -0,0 +1,129 @@ +use alloc::collections::VecDeque; +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; + +#[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; + +const BUFFER_SIZE: usize = 256; +static KEYBOARD_SEMAPHORE: Semaphore = Semaphore::new(0); + +struct Ps2; +impl Ps2 { + pub fn read_status() -> u8 { + unsafe { Port::::new(PS2_CMD_PORT).read() } + } + + pub fn write_cmd(cmd: Ps2Command) { + unsafe { Port::::new(PS2_CMD_PORT).write(cmd as u8) } + } + + pub fn read_data() -> u8 { + unsafe { Port::::new(PS2_DATA_PORT).read() } + } + + pub fn write_data(data: u8) { + unsafe { Port::::new(PS2_DATA_PORT).write(data) } + } +} + +static KEYBOARD_BUFFER: Lazy>> = + Lazy::new(|| InterruptTicketMutex::new(VecDeque::with_capacity(BUFFER_SIZE))); + +fn keyboard_handler() { + let scancode = Ps2::read_data(); + 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(); + } + } +} + +pub(crate) fn get_keyboard_handler() -> (u8, fn()) { + 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(Ps2Command::ReadConfig); + let mut config = Ps2::read_data(); + + config |= PS2_CNFG_ENABLE_KEYBOARD_INTERRUPT; + + Ps2::write_cmd(Ps2Command::WriteConfig); + Ps2::write_data(config); + + 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); + + interrupts::add_irq_name(1, "PS/2 Keyboard"); + + (1, keyboard_handler) +} + +/// 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 de963e3fed..5df5991e78 100644 --- a/src/syscalls/system.rs +++ b/src/syscalls/system.rs @@ -6,3 +6,22 @@ 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 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 + } +}