pluto/src/kernel/arch/x86/keyboard.zig
2023-01-18 21:38:25 +00:00

369 lines
13 KiB
Zig

const builtin = @import("builtin");
const build_options = @import("build_options");
const std = @import("std");
const Allocator = std.mem.Allocator;
const testing = std.testing;
const log = std.log.scoped(.x86_keyboard);
const irq = @import("irq.zig");
const pic = @import("pic.zig");
const arch = if (builtin.is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig");
const panic = @import("../../panic.zig").panic;
const kb = @import("../../keyboard.zig");
const Keyboard = kb.Keyboard;
const KeyPosition = kb.KeyPosition;
const KeyAction = kb.KeyAction;
/// The initialised keyboard
var keyboard: *Keyboard = undefined;
/// The number of keys pressed without a corresponding release
var pressed_keys: usize = 0;
/// If we're in the middle of a special key sequence (e.g. print_screen and arrow keys)
var special_sequence = false;
/// The number of release scan codes expected before registering a release event
/// This is used in special sequences since they end in a certain number of release scan codes
var expected_releases: usize = 0;
/// If a print_screen press is being processed
var on_print_screen = false;
///
/// Read a byte from the keyboard buffer
///
/// Return: u8
/// The byte waiting in the keyboard buffer
///
fn readKeyboardBuffer() u8 {
return arch.in(u8, 0x60);
}
///
/// Parse a keyboard scan code and return the associated keyboard action.
/// Some keys require a specific sequence of scan codes so this function acts as a state machine
///
/// Arguments:
/// IN scan_code: u8 - The scan code from the keyboard
///
/// Return: ?KeyAction
/// The keyboard action resulting from processing the scan code, or null if the scan code doesn't result in a finished keyboard action
///
fn parseScanCode(scan_code: u8) ?KeyAction {
var released = false;
// The print screen key requires special processing since it uses a unique byte sequence
if (on_print_screen or scan_code >= 128) {
released = true;
if (special_sequence or on_print_screen) {
// Special sequences are followed by a certain number of release scan codes that should be ignored. Update the expected number
if (expected_releases >= 1) {
expected_releases -= 1;
return null;
}
} else {
if (pressed_keys == 0) {
// A special sequence is started by a lone key release scan code
special_sequence = true;
return null;
}
}
}
// Cut off the top bit, which denotes that the key was released
const key_code = @truncate(u7, scan_code);
var key_pos: ?KeyPosition = null;
if (special_sequence or on_print_screen) {
if (!released) {
// Most special sequences are followed by an extra key release byte
expected_releases = 1;
}
switch (key_code) {
72 => key_pos = KeyPosition.UP_ARROW,
75 => key_pos = KeyPosition.LEFT_ARROW,
77 => key_pos = KeyPosition.RIGHT_ARROW,
80 => key_pos = KeyPosition.DOWN_ARROW,
// First byte sent for the pause key
29 => return null,
42 => {
// The print screen key is followed by five extra key release bytes
key_pos = KeyPosition.PRINT_SCREEN;
if (!released) {
on_print_screen = true;
expected_releases = 5;
}
},
// Second and final byte sent for the pause key
69 => {
// The pause key is followed by two extra key release bytes
key_pos = KeyPosition.PAUSE;
if (!released) {
expected_releases = 2;
}
},
82 => key_pos = KeyPosition.INSERT,
71 => key_pos = KeyPosition.HOME,
73 => key_pos = KeyPosition.PAGE_UP,
83 => key_pos = KeyPosition.DELETE,
79 => key_pos = KeyPosition.END,
81 => key_pos = KeyPosition.PAGE_DOWN,
53 => key_pos = KeyPosition.KEYPAD_SLASH,
28 => key_pos = KeyPosition.KEYPAD_ENTER,
56 => key_pos = KeyPosition.RIGHT_ALT,
91 => key_pos = KeyPosition.SPECIAL,
else => return null,
}
}
key_pos = key_pos orelse switch (key_code) {
1 => KeyPosition.ESC,
// Number keys and second row
2...28 => @intToEnum(KeyPosition, @enumToInt(KeyPosition.ONE) + (key_code - 2)),
29 => KeyPosition.LEFT_CTRL,
30...40 => @intToEnum(KeyPosition, @enumToInt(KeyPosition.A) + (key_code - 30)),
41 => KeyPosition.BACKTICK,
42 => KeyPosition.LEFT_SHIFT,
43 => KeyPosition.HASH,
44...54 => @intToEnum(KeyPosition, @enumToInt(KeyPosition.Z) + (key_code - 44)),
55 => KeyPosition.KEYPAD_ASTERISK,
56 => KeyPosition.LEFT_ALT,
57 => KeyPosition.SPACE,
58 => KeyPosition.CAPS_LOCK,
59...68 => @intToEnum(KeyPosition, @enumToInt(KeyPosition.F1) + (key_code - 59)),
69 => KeyPosition.NUM_LOCK,
70 => KeyPosition.SCROLL_LOCK,
71...73 => @intToEnum(KeyPosition, @enumToInt(KeyPosition.KEYPAD_7) + (key_code - 71)),
74 => KeyPosition.KEYPAD_MINUS,
75...77 => @intToEnum(KeyPosition, @enumToInt(KeyPosition.KEYPAD_4) + (key_code - 75)),
78 => KeyPosition.KEYPAD_PLUS,
79...81 => @intToEnum(KeyPosition, @enumToInt(KeyPosition.KEYPAD_1) + (key_code - 79)),
82 => KeyPosition.KEYPAD_0,
83 => KeyPosition.KEYPAD_DOT,
86 => KeyPosition.BACKSLASH,
87 => KeyPosition.F11,
88 => KeyPosition.F12,
else => null,
};
if (key_pos) |k| {
// If we're releasing a key decrement the number of keys pressed, else increment it
if (!released) {
pressed_keys += 1;
} else {
pressed_keys -= 1;
// Releasing a special key means we are no longer on that special key
special_sequence = false;
}
return KeyAction{ .position = k, .released = released };
}
return null;
}
///
/// Register a keyboard action. Should only be called in response to a keyboard IRQ
///
/// Arguments:
/// IN ctx: *arch.CpuState - The state of the CPU when the keyboard action occurred
///
/// Return: usize
/// The stack pointer value to use when returning from the interrupt
///
fn onKeyEvent(ctx: *arch.CpuState) usize {
const scan_code = readKeyboardBuffer();
if (parseScanCode(scan_code)) |action| {
if (!keyboard.writeKey(action)) {
log.warn("No room for keyboard action {}\n", .{action});
}
}
return @ptrToInt(ctx);
}
///
/// Initialise the PS/2 keyboard
///
/// Arguments:
/// IN allocator: Allocator - The allocator to use to create the keyboard instance
///
/// Return: *Keyboard
/// The keyboard created
///
/// Error: std.mem.Allocator.Error
/// OutOfMemory - There isn't enough memory to allocate the keyboard instance
///
pub fn init(allocator: Allocator) Allocator.Error!*Keyboard {
irq.registerIrq(pic.IRQ_KEYBOARD, onKeyEvent) catch |e| {
panic(@errorReturnTrace(), "Failed to register keyboard IRQ: {}\n", .{e});
};
keyboard = try allocator.create(Keyboard);
keyboard.* = Keyboard.init();
return keyboard;
}
fn testResetGlobals() void {
pressed_keys = 0;
special_sequence = false;
expected_releases = 0;
on_print_screen = false;
}
test "parseScanCode" {
testResetGlobals();
// Test basic keys
const basic_keys = &[_]?KeyPosition{
KeyPosition.ESC,
KeyPosition.ONE,
KeyPosition.TWO,
KeyPosition.THREE,
KeyPosition.FOUR,
KeyPosition.FIVE,
KeyPosition.SIX,
KeyPosition.SEVEN,
KeyPosition.EIGHT,
KeyPosition.NINE,
KeyPosition.ZERO,
KeyPosition.HYPHEN,
KeyPosition.EQUALS,
KeyPosition.BACKSPACE,
KeyPosition.TAB,
KeyPosition.Q,
KeyPosition.W,
KeyPosition.E,
KeyPosition.R,
KeyPosition.T,
KeyPosition.Y,
KeyPosition.U,
KeyPosition.I,
KeyPosition.O,
KeyPosition.P,
KeyPosition.LEFT_BRACKET,
KeyPosition.RIGHT_BRACKET,
KeyPosition.ENTER,
KeyPosition.LEFT_CTRL,
KeyPosition.A,
KeyPosition.S,
KeyPosition.D,
KeyPosition.F,
KeyPosition.G,
KeyPosition.H,
KeyPosition.J,
KeyPosition.K,
KeyPosition.L,
KeyPosition.SEMICOLON,
KeyPosition.APOSTROPHE,
KeyPosition.BACKTICK,
KeyPosition.LEFT_SHIFT,
KeyPosition.HASH,
KeyPosition.Z,
KeyPosition.X,
KeyPosition.C,
KeyPosition.V,
KeyPosition.B,
KeyPosition.N,
KeyPosition.M,
KeyPosition.COMMA,
KeyPosition.DOT,
KeyPosition.FORWARD_SLASH,
KeyPosition.RIGHT_SHIFT,
KeyPosition.KEYPAD_ASTERISK,
KeyPosition.LEFT_ALT,
KeyPosition.SPACE,
KeyPosition.CAPS_LOCK,
KeyPosition.F1,
KeyPosition.F2,
KeyPosition.F3,
KeyPosition.F4,
KeyPosition.F5,
KeyPosition.F6,
KeyPosition.F7,
KeyPosition.F8,
KeyPosition.F9,
KeyPosition.F10,
KeyPosition.NUM_LOCK,
KeyPosition.SCROLL_LOCK,
KeyPosition.KEYPAD_7,
KeyPosition.KEYPAD_8,
KeyPosition.KEYPAD_9,
KeyPosition.KEYPAD_MINUS,
KeyPosition.KEYPAD_4,
KeyPosition.KEYPAD_5,
KeyPosition.KEYPAD_6,
KeyPosition.KEYPAD_PLUS,
KeyPosition.KEYPAD_1,
KeyPosition.KEYPAD_2,
KeyPosition.KEYPAD_3,
KeyPosition.KEYPAD_0,
KeyPosition.KEYPAD_DOT,
null,
null,
KeyPosition.BACKSLASH,
KeyPosition.F11,
KeyPosition.F12,
};
comptime var scan_code = 1;
inline for (basic_keys) |key| {
var res = parseScanCode(scan_code);
if (key) |k| {
const r = res orelse unreachable;
try testing.expectEqual(k, r.position);
try testing.expectEqual(false, r.released);
try testing.expectEqual(pressed_keys, 1);
}
try testing.expectEqual(on_print_screen, false);
try testing.expectEqual(special_sequence, false);
try testing.expectEqual(expected_releases, 0);
// Test release scan code for key
if (key) |k| {
res = parseScanCode(scan_code | 128);
const r = res orelse unreachable;
try testing.expectEqual(k, r.position);
try testing.expectEqual(true, r.released);
try testing.expectEqual(pressed_keys, 0);
try testing.expectEqual(on_print_screen, false);
try testing.expectEqual(special_sequence, false);
try testing.expectEqual(expected_releases, 0);
}
scan_code += 1;
}
// Test the special keys
// 'simple' sepcial keys consist of one release byte, a key then an extra release byte
const simple_special_keys = &[_]?KeyPosition{
KeyPosition.UP_ARROW,
KeyPosition.LEFT_ARROW,
KeyPosition.RIGHT_ARROW,
KeyPosition.DOWN_ARROW,
KeyPosition.INSERT,
KeyPosition.HOME,
KeyPosition.PAGE_UP,
KeyPosition.DELETE,
KeyPosition.END,
KeyPosition.PAGE_DOWN,
KeyPosition.KEYPAD_SLASH,
KeyPosition.KEYPAD_ENTER,
KeyPosition.RIGHT_ALT,
KeyPosition.SPECIAL,
};
const simple_special_codes = &[_]u8{ 72, 75, 77, 80, 82, 71, 73, 83, 79, 81, 53, 28, 56, 91 };
for (simple_special_keys) |key, i| {
try testing.expectEqual(parseScanCode(128), null);
try testing.expectEqual(pressed_keys, 0);
try testing.expectEqual(on_print_screen, false);
try testing.expectEqual(special_sequence, true);
try testing.expectEqual(expected_releases, 0);
var res = parseScanCode(simple_special_codes[i]) orelse unreachable;
try testing.expectEqual(false, res.released);
try testing.expectEqual(key, res.position);
try testing.expectEqual(pressed_keys, 1);
try testing.expectEqual(on_print_screen, false);
try testing.expectEqual(special_sequence, true);
try testing.expectEqual(expected_releases, 1);
try testing.expectEqual(parseScanCode(128), null);
try testing.expectEqual(pressed_keys, 1);
try testing.expectEqual(on_print_screen, false);
try testing.expectEqual(special_sequence, true);
try testing.expectEqual(expected_releases, 0);
res = parseScanCode(simple_special_codes[i] | 128) orelse unreachable;
try testing.expectEqual(true, res.released);
try testing.expectEqual(key, res.position);
try testing.expectEqual(pressed_keys, 0);
try testing.expectEqual(on_print_screen, false);
try testing.expectEqual(special_sequence, false);
try testing.expectEqual(expected_releases, 0);
}
}