Skip to content

Commit 573acc1

Browse files
authored
Merge pull request #2532 from GloriousAlpaca/pr-keyboard-clean
feat: add PS/2 keyboard interrupt driver
2 parents 9b4a2f0 + 009e0fa commit 573acc1

5 files changed

Lines changed: 178 additions & 1 deletion

File tree

Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,15 @@ virtio-vsock = ["virtio"]
231231
## This is only useful on PCs (x86-64).
232232
vga = []
233233

234+
## Enables the PS/2 keyboard driver.
235+
##
236+
## This feature initializes the PS/2 keyboard controller and installs a keyboard interrupt handler.
237+
## It also provides a system call to receive the last scancode from the internal keyboard buffer.
238+
## Note that this is not a complete keyboard driver and not needed for serial input/output.
239+
## It allows receiving scancodes from the PS/2 keyboard that can be used to port applications.
240+
## This is only useful on PCs (x86-64).
241+
pc-keyboard = []
242+
234243
#! ### Performance Features
235244

236245
## Disables putting the CPU to sleep.

src/arch/x86_64/kernel/interrupts.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,16 @@ pub(crate) fn install() {
162162
IRQ_NAMES.lock().insert(7, "FPU");
163163
}
164164

165-
pub(crate) fn install_handlers(handlers: InterruptHandlerMap) {
165+
#[allow(unused_mut)]
166+
pub(crate) fn install_handlers(mut handlers: InterruptHandlerMap) {
167+
#[cfg(feature = "pc-keyboard")]
168+
{
169+
use crate::arch::kernel::pc_keyboard::get_keyboard_handler;
170+
171+
let (irq, handler) = get_keyboard_handler();
172+
handlers.entry(irq).or_default().push_back(handler);
173+
}
174+
166175
IRQ_HANDLERS.set(handlers).unwrap();
167176
}
168177

src/arch/x86_64/kernel/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ pub mod interrupts;
2222
pub mod kernel_stack;
2323
#[cfg(all(not(feature = "pci"), feature = "virtio"))]
2424
pub mod mmio;
25+
#[cfg(feature = "pc-keyboard")]
26+
pub mod pc_keyboard;
2527
#[cfg(feature = "pci")]
2628
pub mod pci;
2729
pub mod pic;
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
use alloc::collections::VecDeque;
2+
use core::num::NonZeroU8;
3+
4+
use hermit_sync::{InterruptTicketMutex, Lazy};
5+
use x86_64::instructions::port::Port;
6+
7+
use crate::arch::kernel::interrupts;
8+
use crate::synch::semaphore::Semaphore;
9+
10+
const PS2_DATA_PORT: u16 = 0x60;
11+
const PS2_CMD_PORT: u16 = 0x64;
12+
13+
#[repr(u8)]
14+
enum Ps2Command {
15+
ReadConfig = 0x20,
16+
WriteConfig = 0x60,
17+
DisableKeyboard = 0xad,
18+
DisableMouse = 0xa7,
19+
EnableKeyboard = 0xae,
20+
#[allow(dead_code)]
21+
EnableMouse = 0xa8,
22+
TestFirstPort = 0xab,
23+
}
24+
25+
const PS2_CNFG_ENABLE_KEYBOARD_INTERRUPT: u8 = 0x01;
26+
const PS2_BUFFER_FULL: u8 = 0x01;
27+
28+
const MAX_INP_BUFFER_SIZE: usize = 256;
29+
static KEYBOARD_SEMAPHORE: Semaphore = Semaphore::new(0);
30+
31+
struct Ps2;
32+
impl Ps2 {
33+
pub fn read_status() -> u8 {
34+
// SAFETY: Correct port access without safety related side-effects.
35+
unsafe { Port::<u8>::new(PS2_CMD_PORT).read() }
36+
}
37+
38+
pub fn write_cmd(cmd: Ps2Command) {
39+
// SAFETY: Correct port access without memory safety related side-effects.
40+
unsafe { Port::<u8>::new(PS2_CMD_PORT).write(cmd as u8) }
41+
}
42+
43+
pub fn read_data() -> u8 {
44+
// SAFETY: Correct port access without safety related side-effects.
45+
unsafe { Port::<u8>::new(PS2_DATA_PORT).read() }
46+
}
47+
48+
pub fn write_data(data: u8) {
49+
// SAFETY: Correct port access without memory safety related side-effects.
50+
unsafe { Port::<u8>::new(PS2_DATA_PORT).write(data) }
51+
}
52+
}
53+
54+
static KEYBOARD_BUFFER: Lazy<InterruptTicketMutex<VecDeque<NonZeroU8>>> =
55+
Lazy::new(|| InterruptTicketMutex::new(VecDeque::with_capacity(32)));
56+
57+
fn keyboard_handler() {
58+
let scancode = Ps2::read_data();
59+
if let Some(valid_scancode) = NonZeroU8::new(scancode) {
60+
{
61+
let mut buffer = KEYBOARD_BUFFER.lock();
62+
63+
// Pop the oldest scancode if the buffer is full.
64+
if buffer.len() >= MAX_INP_BUFFER_SIZE {
65+
buffer.pop_front();
66+
buffer.push_back(valid_scancode);
67+
return;
68+
}
69+
buffer.push_back(valid_scancode);
70+
}
71+
KEYBOARD_SEMAPHORE.release();
72+
}
73+
}
74+
75+
pub(crate) fn get_keyboard_handler() -> (u8, fn()) {
76+
Ps2::write_cmd(Ps2Command::DisableKeyboard);
77+
Ps2::write_cmd(Ps2Command::DisableMouse);
78+
79+
// Ensure an empty buffer to guard against stuck/garbage data
80+
while (Ps2::read_status() & PS2_BUFFER_FULL) != 0 {
81+
let _ = Ps2::read_data();
82+
}
83+
84+
Ps2::write_cmd(Ps2Command::ReadConfig);
85+
let mut config = Ps2::read_data();
86+
87+
config |= PS2_CNFG_ENABLE_KEYBOARD_INTERRUPT;
88+
89+
Ps2::write_cmd(Ps2Command::WriteConfig);
90+
Ps2::write_data(config);
91+
92+
Ps2::write_cmd(Ps2Command::TestFirstPort);
93+
94+
if Ps2::read_data() != 0 {
95+
error!("PS/2 keyboard test failed");
96+
}
97+
98+
Ps2::write_cmd(Ps2Command::EnableKeyboard);
99+
100+
// Force the initialization of the keyboard buffer to ensure it is ready before any interrupts occur.
101+
Lazy::force(&KEYBOARD_BUFFER);
102+
103+
interrupts::add_irq_name(1, "PS/2 Keyboard");
104+
105+
(1, keyboard_handler)
106+
}
107+
108+
/// Pops scancodes from the keyboard buffer into the provided slice. If `nonblocking` is false, the
109+
/// function will sleep the current thread until a scancode has been received. Returns the number of scancodes
110+
/// popped into the slice.
111+
pub fn pop_scancodes(slice: &mut [u8], nonblocking: bool) -> usize {
112+
if slice.is_empty() {
113+
return 0;
114+
}
115+
if nonblocking {
116+
if !KEYBOARD_SEMAPHORE.try_acquire() {
117+
return 0;
118+
}
119+
} else {
120+
KEYBOARD_SEMAPHORE.acquire(None);
121+
}
122+
let mut amount: usize = 1;
123+
while amount < slice.len() && KEYBOARD_SEMAPHORE.try_acquire() {
124+
amount += 1;
125+
}
126+
let mut buffer = KEYBOARD_BUFFER.lock();
127+
for scancode in slice[..amount].iter_mut() {
128+
*scancode = buffer.pop_front().unwrap().get();
129+
}
130+
amount
131+
}

src/syscalls/system.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,29 @@ use crate::arch::mm::paging::{BasePageSize, PageSize};
66
pub extern "C" fn sys_getpagesize() -> i32 {
77
BasePageSize::SIZE.try_into().unwrap()
88
}
9+
10+
// Writes the scancodes from the keyboard buffer into the provided buffer.
11+
// If 'nonblock' is true, it will return immediately if there are no scancodes available,
12+
// otherwise it will block until at least one scancode is available.
13+
// Returns the number of bytes written to the buffer,
14+
// or a negative error code on failure.
15+
#[cfg(all(target_arch = "x86_64", feature = "pc-keyboard"))]
16+
#[hermit_macro::system]
17+
#[unsafe(no_mangle)]
18+
pub unsafe extern "C" fn sys_read_keyboard(buffer: *mut u8, size: usize, nonblock: bool) -> isize {
19+
if buffer.is_null() {
20+
return -(crate::errno::Errno::Fault as isize);
21+
}
22+
if size == 0 {
23+
return 0;
24+
}
25+
// SAFETY: We have to trust the user input, because we are a unikernel and if the user wants to crash the program
26+
// they are free to do so.
27+
let buffer_slice: &mut [u8] = unsafe { core::slice::from_raw_parts_mut(buffer, size) };
28+
let result = crate::arch::kernel::pc_keyboard::pop_scancodes(buffer_slice, nonblock);
29+
if result == 0 && nonblock {
30+
-(crate::errno::Errno::Again as isize)
31+
} else {
32+
result as isize
33+
}
34+
}

0 commit comments

Comments
 (0)