Demilade Sonuga's blog

All posts

The Keyboard Interrupt II

2023-03-06 · 47 min read

In this post, we're going to start getting our keyboard driver together, the code that will interpret the scancodes read from port 0x60 during keyboard interrupts and translate them into key events.

Aims

Before we get on to the main stuff, we need some clear aims of what our keyboard driver should be able to do. For example, should it be able to recognize key modifiers, like Control + key?

For this project, we'll skip the key modifiers stuff. Our driver will only be able to detect a key press and a key release. It will provide a function interpret_byte that will accept a scancode and return a structure representing a key event.

Scancode Set 1

As was mentioned in the previous post, scancode set 1 is a specification that associates scancodes with keys pressed and released. It's this spec that we'll use to program our keyboard driver to detect key events.

When you look through the spec itself, you'll notice that some key events have two scancodes associated with them, the first one being 0xe0. The result of these two-scancode events is that they will generate two interrupts, one for the first scancode 0xe0 (the extended byte) and the other for the second. For example, if you run the game and press the right control key, you'll have the following:

Right Control Pressed

According to scancode set 1, the code for pressing down the right control is 0xe0 followed by 0x1d. 0xe0 is hexadecimal for 224 and 0x1d is hexadecimal for 29, hence the first two lines on the game screen. The code for releasing the right control is 0xe0 followed by 0x9d. 0x9d is hexadecimal for 157, hence the last two lines on the screen.

So, the keypress of the right control key resulted in four interrupts, one for each of the scancodes.

The rest of the key events that do not begin with the extended byte (scancode 0xe0) require only two interrupts.

Modeling The Driver

To begin modeling the driver (make sure you try doing this yourself before proceeding), we're going to start with the structure that represents a key event. A key event consists of a keycode (this is the actual key being pressed, like the letter X, the key escape) and a direction that can be either up (indicating a release) or down (press).

Create a new keyboard.rs file.

To indicate a direction, a key direction can be either up or down. This naturally can be modeled as an enum with two variants: up and down.

In keyboard.rs

pub enum KeyDirection {
// The key is being released
Up,
// The key is being pressed down
Down
}

A key code is an actual key associated with an event. It can be the key A or B or C or D or any key specified at all in scancode set 1.

Again, this is just going to be another enum, albeit a large one.

// A code associated with a particular key on the keyboard in scancode set 1
pub enum KeyCode {
Escape,
One,
Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
Zero,
Dash,
Equals,
Backspace,
Tab,
Q,
W,
E,
R,
T,
Y,
U,
I,
O,
P,
OpenBracket,
CloseBracket,
Enter,
LeftCtrl,
A,
S,
D,
F,
G,
H,
J,
K,
L,
SemiColon,
SingleQuote,
Backtick,
LeftShift,
BackSlash,
Z,
X,
C,
V,
B,
N,
M,
Comma,
Dot,
ForwardSlash,
RightShift,
KeypadStar,
LeftAlt,
Space,
CapsLock,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
NumLock,
ScrollLock,
KeypadSeven,
KeypadEight,
KeypadNine,
KeypadDash,
KeypadFour,
KeypadFive,
KeypadSix,
KeypadOne,
KeypadTwo,
KeypadThree,
KeypadPlus,
KeypadZero,
KeypadDot,
PrevTrack,
NextTrack,
KeypadEnter,
RightCtrl,
Mute,
Calculator,
Play,
Stop,
VolumeDown,
VolumeUp,
WWWHome,
KeypadForwardSlash,
AltGr,
Home,
ArrowUp,
ArrowDown,
ArrowLeft,
ArrowRight,
PageUp,
End,
PageDown,
Insert,
Delete,
LeftGUI,
RightGUI,
Apps,
AcpiPower,
AcpiSleep,
AcpiWake,
WWWSearch,
WWWFavorites,
WWWRefresh,
WWWStop,
WWWForward,
WWWBack,
MyComputer,
Email,
MediaSelect
}

An event is a combination of one variant of KeyCode and one variant of KeyDirection. This naturally translates to a struct.

pub struct KeyEvent {
pub keycode: KeyCode,
pub direction: KeyDirection
}

The fields are public because we'll need to access them to figure out what to do after interpreting a scancode into a KeyEvent.

Next, we'll move to the actual processing of the scancodes. Each key event has either one scancode associated with it or the extended scancode 0xe0 followed by another scancode. This leads to a need to maintain the state. For example, in scancode set 1 the scancode 0x1d alone indicates that the left control key was pressed but 0xe0 followed by 0x1d indicates that the right control key was pressed.

Suppose that an interrupt occurs and the scancode is 0xe0. We'll have to save some kind of state so that in the next interrupt, we'll be able to determine that the scancode read follows 0xe0 and is not alone.

Since this state indicates that the next scancode either starts or doesn't start with the extended scancode, we can model this like so:

pub struct Keyboard {
// Tells if the next scancode to be interpreted
// follows an extended scancode
code_is_extended: bool
}

code_is_extended is a boolean value that will be true if 0xe0 has been read and false if it hasn't. When it's been set to true, it will be set back to false after the second scancode has been read, indicating that the keyboard state has been reset.

To create a new instance of this structure:

impl Keyboard {
// Creates a new instance of the Keyboard
pub fn new() -> Self {
// Initially, the extended keycode has not been read
Self { code_is_extended: false }
}
}

Now, to write the interpret_byte function:

impl Keyboard {
// Accepts a byte and changes the keyboard state in the case of beginning
// or end of an extended code.
// Else, just returns the event associated with the scancode byte.
pub fn interpret_byte(&mut self, scancode: u8) -> KeyEvent {

}
}

But this function signature does not tell the full story about what this function could return. In the event where the scancode does not follow the extended scancode and the scancode is valid, a KeyEvent will be returned.

But in the event where the scancode is the extended scancode itself, we don't have enough information yet on what key event is taking place. We'll need the second scancode that follows the extended one to determine that.

To indicate this possibility of a scancode not being translated into an event, we return an Option:

impl Keyboard {
// DELETED: pub fn interpret_byte(&mut self, scancode: u8) -> KeyEvent {
pub fn interpret_byte(&mut self, scancode: u8) -> Option<KeyEvent> { // NEW

}
}

But there's still one more thing: what if the argument passed as scancode is not a valid scancode? If the scancode is not valid, then that means the function should fail to return a KeyEvent. To indicate this possibility of failure, we return a Result.

impl Keyboard {
// DELETED: pub fn interpret_byte(&mut self, scancode: u8) -> Option<KeyEvent> {
pub fn interpret_byte(&mut self, scancode: u8) -> Result<Option<KeyEvent>, ()> { // NEW

}
}

I think it's okay to return a unit-tuple here in the case of a failure because if the function fails, it means only one thing: that the scancode is not valid.

To interpret the byte, we're just going to have to write something like a bunch of big fat match and if-else statements. It's just a matter of mapping the code to the right key event or changing state (in the case of events with the extended scancodes).

Firstly, what to do next depends on whether or not the current scancode follows the extended scancode.

impl Keyboard {
pub fn interpret_byte(&mut self, scancode: u8) -> Result<Option<KeyEvent>, ()> {
// NEW:
match self.code_is_extended {
// scancode does not follow the extended scancode
false => {

}
// scancode follows the extended scancode
true => {

}
}
}
}

We'll start with scancodes that don't follow the extended code. In this case, the scancode is either the extended scancode itself or it's a valid scancode of a key down event or it's a valid scancode of a key up event or it's an invalid scancode.

When the scancode is the extended scancode the Keyboard's code_is_extended should be set to true to indicate in the next call to the function that the code begins with 0xe0 and a None should be returned to indicate that the scancode does not translate to an event.

When the scancode is invalid, an Err(()) should be returned.

For a scancode that indicates a key press or release, we inevitably have to use a big match of if-else statement to map it to the appropriate event. To reduce the number of match statements, we need to look for similarities between the codes.

If you check the scancode set carefully, you'll notice that every key up (release) event is just the scancode of the key down event added to 0x80. For example, pressing down key 3 results in a scancode of 0x04 but releasing it results in a scancode of 0x84 which is 0x80 + 0x04.

We can exploit this regularity to reuse the code mapping function we'll use for key downs in key ups.

const EXTENDED_CODE: u8 = 0xe0; // NEW


impl Keyboard {
pub fn interpret_byte(&mut self, scancode: u8) -> Result<Option<KeyEvent>, ()> {
// NEW:
match self.code_is_extended {
false => {
match scancode {
EXTENDED_CODE => {
// The next scancode follows the extended code
self.code_is_extended = true;
// The extended code can't be translated to an event
Ok(None)
}
// The scancode range for regular key presses
0x01..=0x58 => {
let keycode = self.map_scancode(scancode)?;
Ok(Some(KeyEvent {
keycode,
direction: KeyDirection::Down
}))
}
// The scancode range for regular key releases
0x81..=0xd8 => {
// key up scancodes are just 0x80 added to their counter
// key down codes
let keycode = self.map_scancode(scancode - 0x80)?;
Ok(Some(KeyEvent {
keycode,
direction: KeyDirection::Up
}))
}
// Invalid scancode
_ => Err(())
}
}
true => {

}
}
}
}

As for the map_scancode function, this just takes a scancode that doesn't follow 0xe0 and returns its KeyCode.

impl Keyboard {
// Takes a scancode that doesn't follow 0xe0 and returns
// its KeyCode, in the case of a success.
// If the scancode is invalid, a Err(()) is returned
fn map_scancode(&self, scancode: u8) -> Result<KeyCode, ()> {
match scancode {
0x01 => Ok(KeyCode::Escape),
0x02 => Ok(KeyCode::One),
0x03 => Ok(KeyCode::Two),
0x04 => Ok(KeyCode::Three),
0x05 => Ok(KeyCode::Four),
0x06 => Ok(KeyCode::Five),
0x07 => Ok(KeyCode::Six),
0x08 => Ok(KeyCode::Seven),
0x09 => Ok(KeyCode::Eight),
0x0a => Ok(KeyCode::Nine),
0x0b => Ok(KeyCode::Zero),
0x0c => Ok(KeyCode::Dash),
0x0d => Ok(KeyCode::Equals),
0xe0 => Ok(KeyCode::Backspace),
0x0f => Ok(KeyCode::Tab),
0x10 => Ok(KeyCode::Q),
0x11 => Ok(KeyCode::W),
0x12 => Ok(KeyCode::E),
0x13 => Ok(KeyCode::R),
0x14 => Ok(KeyCode::T),
0x15 => Ok(KeyCode::Y),
0x16 => Ok(KeyCode::U),
0x17 => Ok(KeyCode::I),
0x18 => Ok(KeyCode::O),
0x19 => Ok(KeyCode::P),
0x1a => Ok(KeyCode::OpenBracket),
0x1b => Ok(KeyCode::CloseBracket),
0x1c => Ok(KeyCode::Enter),
0x1d => Ok(KeyCode::LeftCtrl),
0x1e => Ok(KeyCode::A),
0x1f => Ok(KeyCode::S),
0x20 => Ok(KeyCode::D),
0x21 => Ok(KeyCode::F),
0x22 => Ok(KeyCode::G),
0x23 => Ok(KeyCode::H),
0x24 => Ok(KeyCode::J),
0x25 => Ok(KeyCode::K),
0x26 => Ok(KeyCode::L),
0x27 => Ok(KeyCode::SemiColon),
0x28 => Ok(KeyCode::SingleQuote),
0x29 => Ok(KeyCode::Backtick),
0x2a => Ok(KeyCode::LeftShift),
0x2b => Ok(KeyCode::BackSlash),
0x2c => Ok(KeyCode::Z),
0x2d => Ok(KeyCode::X),
0x2e => Ok(KeyCode::C),
0x2f => Ok(KeyCode::V),
0x30 => Ok(KeyCode::B),
0x31 => Ok(KeyCode::N),
0x32 => Ok(KeyCode::M),
0x33 => Ok(KeyCode::Comma),
0x34 => Ok(KeyCode::Dot),
0x35 => Ok(KeyCode::ForwardSlash),
0x36 => Ok(KeyCode::RightShift),
0x37 => Ok(KeyCode::KeypadStar),
0x38 => Ok(KeyCode::LeftAlt),
0x39 => Ok(KeyCode::Space),
0x3a => Ok(KeyCode::CapsLock),
0x3b => Ok(KeyCode::F1),
0x3c => Ok(KeyCode::F2),
0x3d => Ok(KeyCode::F3),
0x3e => Ok(KeyCode::F4),
0x3f => Ok(KeyCode::F5),
0x40 => Ok(KeyCode::F6),
0x41 => Ok(KeyCode::F7),
0x42 => Ok(KeyCode::F8),
0x43 => Ok(KeyCode::F9),
0x44 => Ok(KeyCode::F10),
0x57 => Ok(KeyCode::F11),
0x58 => Ok(KeyCode::F12),
0x45 => Ok(KeyCode::NumLock),
0x46 => Ok(KeyCode::ScrollLock),
0x47 => Ok(KeyCode::KeypadSeven),
0x48 => Ok(KeyCode::KeypadEight),
0x49 => Ok(KeyCode::KeypadNine),
0x4a => Ok(KeyCode::KeypadDash),
0x4b => Ok(KeyCode::KeypadFour),
0x4c => Ok(KeyCode::KeypadFive),
0x4d => Ok(KeyCode::KeypadSix),
0x4e => Ok(KeyCode::KeypadPlus),
0x4f => Ok(KeyCode::KeypadOne),
0x50 => Ok(KeyCode::KeypadTwo),
0x51 => Ok(KeyCode::KeypadThree),
0x52 => Ok(KeyCode::KeypadZero),
0x53 => Ok(KeyCode::KeypadDot),
// Invalid scancode
_ => Err(())
}
}
}

In the case where the scancode follows the extended scancode, the code_is_extended has to be set back to false to indicate that the extended key event has finished processing. Similarly to the regular scancodes, scancodes following the extended one also have a regularity. Their key releases are 0x80 added to the codes for key presses. For example, 0xe0, 0x1d signifies that the right control key has been pressed down. 0xe0, 0x9d signifies that the right control key has been released. 0x1d + 0x80 == 0x9d. This regularity exists for all the other ones, too.

impl Keyboard {
pub fn interpret_byte(&mut self, scancode: u8) -> Result<Option<KeyEvent>, ()> {
// NEW:
match self.code_is_extended {
false => { /* ...Others */ }
true => {
self.code_is_extended = false;
match scancode {
// Range of scancodes for extended key presses
0x10..=0x90 => {
let keycode = self.map_extended_scancode(scancode)?;
Ok(Some(KeyEvent {
keycode,
direction: KeyDirection::Down
}))
}
// Range for extended key releases
0x99..=0xed => {
// key up scancodes are just 0x80 added to their counter
// key down codes
let keycode = self.map_extended_scancode(scancode - 0x80)?;
Ok(Some(KeyEvent {
keycode,
direction: KeyDirection::Up
}))
}
// Invalid scancode
_ => Err(())
}
}
}
}
}

The extended scancodes mapping:

impl Keyboard {
// Takes a scancode that follows 0xe0 and returns
// its KeyCode, in the case of a success.
// If the scancode is invalid, a Err(()) is returned
fn map_extended_scancode(&self, scancode: u8) -> Result<KeyCode, ()> {
match scancode {
0x10 => Ok(KeyCode::PrevTrack),
0x19 => Ok(KeyCode::NextTrack),
0x1c => Ok(KeyCode::KeypadEnter),
0x1d => Ok(KeyCode::RightCtrl),
0x20 => Ok(KeyCode::Mute),
0x21 => Ok(KeyCode::Calculator),
0x22 => Ok(KeyCode::Play),
0x24 => Ok(KeyCode::Stop),
0x2e => Ok(KeyCode::VolumeDown),
0x30 => Ok(KeyCode::VolumeUp),
0x32 => Ok(KeyCode::WWWHome),
0x35 => Ok(KeyCode::KeypadForwardSlash),
0x38 => Ok(KeyCode::AltGr),
0x47 => Ok(KeyCode::Home),
0x48 => Ok(KeyCode::ArrowUp),
0x49 => Ok(KeyCode::PageUp),
0x4b => Ok(KeyCode::ArrowLeft),
0x4d => Ok(KeyCode::ArrowRight),
0x4f => Ok(KeyCode::End),
0x50 => Ok(KeyCode::ArrowDown),
0x51 => Ok(KeyCode::PageDown),
0x52 => Ok(KeyCode::Insert),
0x53 => Ok(KeyCode::Delete),
0x5b => Ok(KeyCode::LeftGUI),
0x5c => Ok(KeyCode::RightGUI),
0x5d => Ok(KeyCode::Apps),
0x5e => Ok(KeyCode::AcpiPower),
0x5f => Ok(KeyCode::AcpiSleep),
0x63 => Ok(KeyCode::AcpiWake),
0x65 => Ok(KeyCode::WWWSearch),
0x66 => Ok(KeyCode::WWWFavorites),
0x67 => Ok(KeyCode::WWWRefresh),
0x68 => Ok(KeyCode::WWWStop),
0x69 => Ok(KeyCode::WWWForward),
0x6a => Ok(KeyCode::WWWBack),
0x6b => Ok(KeyCode::MyComputer),
0x6c => Ok(KeyCode::Email),
0x6d => Ok(KeyCode::MediaSelect),
_ => Err(())
}
}
}

Checking It Out

To check out the new driver in action:

In main.rs

use pics::PICs;

// NEW:
mod keyboard;
use keyboard::Keyboard;

static mut SCREEN: Option<&mut Screen> = None;
// ...Others
static mut PICS: Option<PICs> = None;
static mut KEYBOARD: Option<Keyboard> = None;
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ...Others

setup_idt(cs);
setup_keyboard(); // NEW
setup_pics();

// ...Others
}
fn setup_keyboard() {
unsafe {
KEYBOARD = Some(Keyboard::new());
}
}
fn get_keyboard() -> Option<&'static mut Keyboard> {
unsafe { KEYBOARD.as_mut() }
}
extern "x86-interrupt" fn keyboard_handler(frame: interrupts::InterruptStackFrame) {
let port = port::Port::new(0x60);
let scancode = port.read();
let screen = get_screen().unwrap();
// DELETED: write!(screen, "keyboard was pressed {} ", scancode);
if let Ok(Some(event)) = get_keyboard().unwrap().interpret_byte(scancode) {
write!(screen, "{:?}", event);
}
// Signalling that the keyboard interrupt has been handled
get_pics().unwrap().end_of_interrupt(1);
}

To make it possible to print a KeyEvent instance using the debug formatting ({:?}), we need to implement Debug for KeyEvent and all its fields:

In keyboard.rs

#[derive(Debug)] // NEW
pub enum KeyDirection { /* ...Others */ }

#[derive(Debug)] // NEW
pub enum KeyCode { /* ...Others */ }

#[derive(Debug)] // NEW
pub struct KeyEvent { /* ...Others */ }

If you run the code now, you'll see this:

Panic While Printing KeyEvent

The error message says that Unicode character 125 is not representable by the font. This error can be traced back to our uefi.rs char_to_font_index function which returns an index into the FONT array for a given character and panics if no representation for the character can be found.

To avoid having to start designing another character in the font, we can just change the catch-all branch in char_to_font_index's match statement behavior from panicking to printing a dummy character. We can use a space to do that since spaces are the most unobtrusive of the characters we have.

In uefi.rs

fn char_to_font_index(c: u8) -> usize {
match c {
32 => 26, // Space
33 => 27, // Exclamation mark
39 => 39, // Apostrophe
44 => 40, // Comma
46 => 42, // Full stop
47 => 41, // Forward slash
48..=57 => (c - 20) as usize, // Digits
58 => 38, // Colon
65..=91 => (c - 65) as usize, // Big letters
97..=123 => char_to_font_index(c - 32), // Small letters to big letters
// DELETED: _ => panic!("The Unicode character {} is not representable by the font", c)
// Unrecognized characters should be printed as spaces
_ => char_to_font_index(' ' as u8) // NEW
}
}

Running again will give you and pressing the enter key:

Printing The Enter KeyEvent

Take Away

For the full code, go to the repo

In The Next Post

We'll start looking at how to create event handlers.

References