feat: add PS/2 keyboard interrupt driver - #2532
Conversation
7c21b3f to
5d35a06
Compare
There was a problem hiding this comment.
Benchmark Results
Details
| Benchmark | Current: 9afbade | Previous: 2e23902 | Performance Ratio |
|---|---|---|---|
| startup_benchmark Build Time | 126.10 s |
80.34 s |
1.57 ❗ |
| startup_benchmark File Size | 0.77 MB |
0.80 MB |
0.97 ❗ |
| Startup Time - 1 core | 0.94 s (±0.02 s) |
0.75 s (±0.02 s) |
1.26 ❗ |
| Startup Time - 2 cores | 0.96 s (±0.04 s) |
0.74 s (±0.02 s) |
1.31 ❗ |
| Startup Time - 4 cores | 0.98 s (±0.04 s) |
0.74 s (±0.02 s) |
1.33 ❗ |
| multithreaded_benchmark Build Time | 125.62 s |
82.11 s |
1.53 ❗ |
| multithreaded_benchmark File Size | 0.83 MB |
0.86 MB |
0.97 ❗ |
| Multithreaded Pi Efficiency - 2 Threads | 64.48 % (±8.42 %) |
85.89 % (±6.61 %) |
0.75 ❗ |
| Multithreaded Pi Efficiency - 4 Threads | 36.52 % (±4.07 %) |
43.43 % (±2.56 %) |
0.84 ❗ |
| Multithreaded Pi Efficiency - 8 Threads | 21.49 % (±1.94 %) |
25.76 % (±1.53 %) |
0.83 ❗ |
| micro_benchmarks Build Time | 124.34 s |
80.40 s |
1.55 ❗ |
| micro_benchmarks File Size | 0.83 MB |
0.86 MB |
0.97 ❗ |
| Scheduling time - 1 thread | 78.04 ticks (±3.56 ticks) |
62.65 ticks (±4.06 ticks) |
1.25 ❗ |
| Scheduling time - 2 threads | 54.74 ticks (±17.17 ticks) |
34.08 ticks (±4.10 ticks) |
1.61 ❗ |
| Micro - Time for syscall (getpid) | 4.27 ticks (±0.32 ticks) |
3.45 ticks (±0.58 ticks) |
1.24 ❗ |
| Memcpy speed - (built_in) block size 4096 | 56720.74 MByte/s (±39303.87 MByte/s) |
82448.38 MByte/s (±56997.13 MByte/s) |
0.69 |
| Memcpy speed - (built_in) block size 1048576 | 29337.44 MByte/s (±23911.65 MByte/s) |
30585.98 MByte/s (±24707.84 MByte/s) |
0.96 |
| Memcpy speed - (built_in) block size 16777216 | 28588.14 MByte/s (±23656.77 MByte/s) |
26340.06 MByte/s (±21720.96 MByte/s) |
1.09 |
| Memset speed - (built_in) block size 4096 | 56921.26 MByte/s (±39459.25 MByte/s) |
82292.76 MByte/s (±56891.50 MByte/s) |
0.69 |
| Memset speed - (built_in) block size 1048576 | 30051.94 MByte/s (±24326.42 MByte/s) |
31323.85 MByte/s (±25145.86 MByte/s) |
0.96 |
| Memset speed - (built_in) block size 16777216 | 29337.79 MByte/s (±24081.68 MByte/s) |
27104.68 MByte/s (±22209.94 MByte/s) |
1.08 |
| Memcpy speed - (rust) block size 4096 | 52014.76 MByte/s (±36688.83 MByte/s) |
74097.96 MByte/s (±51811.44 MByte/s) |
0.70 |
| Memcpy speed - (rust) block size 1048576 | 29216.27 MByte/s (±23812.96 MByte/s) |
30361.60 MByte/s (±24602.37 MByte/s) |
0.96 |
| Memcpy speed - (rust) block size 16777216 | 28537.90 MByte/s (±23603.90 MByte/s) |
27625.34 MByte/s (±22806.88 MByte/s) |
1.03 |
| Memset speed - (rust) block size 4096 | 52505.83 MByte/s (±37054.89 MByte/s) |
74373.47 MByte/s (±51976.48 MByte/s) |
0.71 |
| Memset speed - (rust) block size 1048576 | 29950.54 MByte/s (±24234.44 MByte/s) |
31110.89 MByte/s (±25033.24 MByte/s) |
0.96 |
| Memset speed - (rust) block size 16777216 | 29267.09 MByte/s (±24014.38 MByte/s) |
28386.93 MByte/s (±23265.03 MByte/s) |
1.03 |
| alloc_benchmarks Build Time | 120.37 s |
74.76 s |
1.61 ❗ |
| alloc_benchmarks File Size | 0.85 MB |
0.87 MB |
0.97 ❗ |
| Allocations - Allocation success | 91.35 % |
91.31 % |
1.00 ❗ |
| Allocations - Deallocation success | 100.00 % |
100.00 % |
1 |
| Allocations - Pre-fail Allocations | 61.55 % |
61.44 % |
1.00 ❗ |
| Allocations - Average Allocation time | 5654.02 Ticks (±224.98 Ticks) |
5860.58 Ticks (±98.43 Ticks) |
0.96 |
| Allocations - Average Allocation time (no fail) | 6316.33 Ticks (±232.98 Ticks) |
6554.81 Ticks (±92.86 Ticks) |
0.96 |
| Allocations - Average Deallocation time | 1320.59 Ticks (±478.29 Ticks) |
1805.01 Ticks (±250.35 Ticks) |
0.73 |
| mutex_benchmark Build Time | 121.25 s |
79.82 s |
1.52 ❗ |
| mutex_benchmark File Size | 0.83 MB |
0.86 MB |
0.97 ❗ |
| Mutex Stress Test Average Time per Iteration - 1 Threads | 14.80 ns (±0.75 ns) |
12.10 ns (±0.41 ns) |
1.22 ❗ |
| Mutex Stress Test Average Time per Iteration - 2 Threads | 24.52 ns (±8.33 ns) |
40.26 ns (±1.68 ns) |
0.61 ❗ |
This comment was automatically generated by workflow using github-action-benchmark.
2c424d5 to
f6c859b
Compare
mkroening
left a comment
There was a problem hiding this comment.
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.
| /// Pops a scancode from the keyboard buffer, returning None if the buffer is empty. | ||
| pub fn pop_scancode() -> Option<u8> { |
There was a problem hiding this comment.
A scancode can never be zero, right? Returning Option<NonZero<u8>> would be preferable in that case.
| #[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) | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Since we don't use a filesystem like Linux I would propose something like this:
pub extern "C" fn sys_read_keyboard(buffer: *mut u8 ,size: usize, nonblock: bool) -> isize
(Should I also rename the systemcall to sys_read_pc_keyboard?)
This would be pretty flexible, we can throw standard errorcodes or return the buffersize in bytes like this.
We can then use a semaphore to block the thread until there are scancodes in the vecdeque.
a7180a7 to
153302f
Compare
| let scancode = Ps2::read_data(); | ||
| let mut buffer = KEYBOARD_BUFFER.lock(); | ||
|
|
||
| if buffer.len() >= BUFFER_SIZE { | ||
| buffer.pop_front(); | ||
| } | ||
| buffer.push_back(scancode); |
There was a problem hiding this comment.
I wonder if retaining the keys in the queue is how this is handled best. I'm thinking that maybe adding a timestamp to each key event and discarding it after x seconds is a correct approach. But maybe I'm prematurely optimizing this. It would be interesting to know how other systems are handling this.
| ## | ||
| ## 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. |
There was a problem hiding this comment.
| ## 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. |
3473d4f to
2913b39
Compare
4e33054 to
166f396
Compare
2167837 to
452458e
Compare
452458e to
9afbade
Compare
| struct Ps2; | ||
| impl Ps2 { | ||
| pub fn read_status() -> u8 { | ||
| unsafe { Port::<u8>::new(PS2_CMD_PORT).read() } |
There was a problem hiding this comment.
Please add a brief safety comment:
| unsafe { Port::<u8>::new(PS2_CMD_PORT).read() } | |
| // SAFETY: Correct port access without safety related side-effects. | |
| unsafe { Port::<u8>::new(PS2_CMD_PORT).read() } |
| #[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 size == 0 { | ||
| return 0; | ||
| } | ||
| let buffer_slice: &mut [u8] = unsafe { core::slice::from_raw_parts_mut(buffer, size) }; |
There was a problem hiding this comment.
| let buffer_slice: &mut [u8] = unsafe { core::slice::from_raw_parts_mut(buffer, size) }; | |
| // SAFETY: We have to trust the user input, because we are a unikernel and if the user wants to crash the program they are free to do so. | |
| let buffer_slice: &mut [u8] = unsafe { core::slice::from_raw_parts_mut(buffer, size) }; |
| #[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 { |
| const PS2_CNFG_ENABLE_KEYBOARD_INTERRUPT: u8 = 0x01; | ||
| const PS2_BUFFER_FULL: u8 = 0x01; | ||
|
|
||
| const BUFFER_SIZE: usize = 256; |
There was a problem hiding this comment.
| const BUFFER_SIZE: usize = 256; | |
| const MAX_INP_BUFFER_SIZE: usize = 256; |
| 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(); | ||
| } |
There was a problem hiding this comment.
Logic is fine, but you could rewrite it to be a bit simpler by returning in the line that is currently sem = false (don't forget the push_back).
| #[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 size == 0 { | ||
| return 0; | ||
| } | ||
| let buffer_slice: &mut [u8] = unsafe { core::slice::from_raw_parts_mut(buffer, size) }; |
There was a problem hiding this comment.
| let buffer_slice: &mut [u8] = unsafe { core::slice::from_raw_parts_mut(buffer, size) }; | |
| // SAFETY: We have to trust the user input, because we are a unikernel and if the user wants to crash the program they are free to do so. | |
| let buffer_slice: &mut [u8] = unsafe { core::slice::from_raw_parts_mut(buffer, size) }; |
| } | ||
|
|
||
| static KEYBOARD_BUFFER: Lazy<InterruptTicketMutex<VecDeque<NonZeroU8>>> = | ||
| Lazy::new(|| InterruptTicketMutex::new(VecDeque::with_capacity(BUFFER_SIZE))); |
There was a problem hiding this comment.
| Lazy::new(|| InterruptTicketMutex::new(VecDeque::with_capacity(BUFFER_SIZE))); | |
| Lazy::new(|| InterruptTicketMutex::new(VecDeque::with_capacity(32))); |
This feature adds support for the PS2 legacy keyboard in Qemu x86_64.
Currently it does:
The systemcall returns 0 if the keyboard feature is disabled.
I have only tested this feature with C programs on a Mac using Qemu.