Add basic PS/2 keyboard.#211
Conversation
bin/propolis-server/Cargo.toml
Outdated
| propolis-client = { path = "../../lib/propolis-client" } | ||
| propolis-server-config = { path = "../../crates/propolis-server-config" } | ||
| rfb = { git = "https://github.com/oxidecomputer/rfb", branch = "main" } | ||
| #rfb = { git = "https://github.com/oxidecomputer/rfb", branch = "main" } |
There was a problem hiding this comment.
note: will remove these (and other) local rfb deps when the associated rfb PR (oxidecomputer/rfb#8) is merged
11e1b1e to
cac70c9
Compare
cac70c9 to
dbf6988
Compare
pfmooney
left a comment
There was a problem hiding this comment.
Thanks for adding so much in the way of docs
|
|
||
| use super::keyboard::KeyEventRep; | ||
|
|
||
| /// PS/2 Controller (Intel 8042) Emulation |
There was a problem hiding this comment.
Thanks for adding a nice big theory statement
| && !state.ctrl_cfg.contains(CtrlCfg::PRI_CLOCK_DIS) | ||
| && state.pri_port.has_output(), | ||
| ); | ||
| if state.ctrl_cfg.contains(CtrlCfg::PRI_INTR_EN) |
There was a problem hiding this comment.
Now that this is no longer updating the level of the interrupt pin, but rather pulsing it events, maybe change the function name to better reflect its usage?
There was a problem hiding this comment.
we chatted about this offline -- update might be misleading, but I can't think of a better name either. punting for now
lib/propolis/src/hw/ps2ctrl.rs
Outdated
|
|
||
| const PS2K_TYPEMATIC_MASK: u8 = 0x7f; | ||
|
|
||
| // XXX(JPH): Is there a reason this needs to be this size? |
There was a problem hiding this comment.
I think I just picked an arbitrary for this when I scraped together the initial PS/2 emulation
| pub struct CtrlOutPort: u8 { | ||
| // bit 0: system reset | ||
| // should always be set to 1 | ||
| // XXX(JPH): why does this work |
There was a problem hiding this comment.
We are doing none of the work to emulate A20. Its function is a big lie if a guest actually tries to use it.
luqmana
left a comment
There was a problem hiding this comment.
Woo! Love all the comments. There were a couple scan codes I think got mixed up?
28d6430 to
587c21e
Compare
|
587c21e addresses most of the review feedback so far. |
|
Update: I had some merge issues with 587c21e and not all the changes I made made it into the commit. Will post another commit. |
Co-authored-by: Luqman Aden <me@luqman.ca>
587c21e to
c92f324
Compare
|
okay, I posted a new commit -- c92f324 addresses most of the review feedback |
|
@luqmana and I discussed how this PR does not implement some of the quirks for certain keys that change in behavior based on whether other keys are pressed (such as Insert or Delete). As this will take some rework to handle, and I intended this PR to be a baseline level of support for the keyboard, with the idea that we can fix it up incrementally, I filed #213 to track the gaps, and would like to move forward with merging this PR as is. |
This PR implements basic support for a PS/2 keyboard.
Keyboard Input
Input keyboard data comes from the VNC server in the form of a keysym. The VNC server has a reference to the controller, which has a method to pass input key data (
key_event); this part of the emulation emulates the keyboard device itself. The keyboard/controller emulation translates the keysym into an appropriate scan code based on what the guest has configured (either scan code set 1 or scan code set 2), then it writes the scan code to an internal keyboard buffer, and notifies the guest via interrupt that keyboard data is available.Translating from keysyms to scan codes
Other keyboard device emulation implementations I looked at use giant arrays as tables to map keysyms and scan code sets. Personally, I found reading these tables to be a bit of a challenge for humans, though it might be easier to write programs in. For example, indexing into a table based on an ASCII value (ASCII values are the same as their keysym value) is easy for computers, but not easy to do as a human trying to make sense of the table. Making giant global arrays that provide more type safety is also more challenging to do in Rust than in C.
I also found that other emulations sometimes divorced the translation of keysym to each scan code set. For example, the C bhvye implementation will translate keysyms to scan code set 2 first, then translate to set code 1, which I found confusing to read and reason about. I think the idea here is that scan code set 2 is what real hardware generally sends, while translating to set 1 is for backwards compatability. This more closely matches the architecture of what "real" hardware does, but as far as I can tell it doesn't offer much in terms of making the code easier to reason about. It's possible there's a performance benefit there, but I wanted to simplify my approach unless proven otherwise.
As such, I had the following goals in mind when thinking about how to structure the translation bits:
To address these, I did the following:
KeyEventRep, unifies all possible values for the keysym. Translations are performed in a gianttry_fromimplementation that unifies all the mappings in a single, more human-readable place. The unified translation struct still allows us flexibility to change our approach if we decide there's a good reason to split up the translation further, like C bhyve does.Unknown keys
The set of keysyms is much larger than the set of scan codes, meaning that not all keysyms may map cleanly to a scan code set. If we can't map a key to a scan code, we drop the key at the keyboard controller level and log a trace-level error. It's important that unknown keys do not cause any fatal errors.
Debugging keyboard behavior with dtrace probes
Testing this PR for every possible keysym / scan code mapping for this PR is a daunting prospect, so I thought a bit about how we might be able to potential issues with this code not caught in testing, to build confidence we can fix issues down the road as needed.
One possibility is that a user notices a key not behaving as expected. In that case, we would want access to the underlying keysym, the scan code we think it should be, and which scan code set it's from. For several reasons, the biggest of which is to not accidentally create a keylogger, we don't want to be logging keyevents. But I added a dtrace probe in the input path of the controller to observe these details. Anyone with access to the dtrace probe should already have sufficient privileges to snoop on keystrokes if they wish, so I think this is safe.
Keyevent probe
Here's an example use of the probe after pressing the
Akey in a vnc client connected to a running probe instance, thenshift + A. The probe fires twice for each key press, with the make code and break code:Dropped key probe
Another possibility is that a given key that isn't working is not recognized by the controller and is being dropped internally. I also added a probe for these cases.
For example, the
Menukey is mapped for USB keyboards, but is not present in the scan code sets. When I press theMenukey, the probe fires, with the underlying keysym value available for debugging:Testing
I tested this PR by trying every keyboard combination I could think of on my own keyboard, including combinations with the num lock, caps lock, and shift keys on. This only exercised scan code set 1, as both VMs I tested with (alpine linux and windows) requested scan code set 1 codes.
I confirmed that the windows key on a windows VM produces a windows menu.
Some gaps in testing include:
Notes for reviewers