Keyboard
The PS/2 keyboard interface, though physically obsolete on most current hardware, remains available on essentially every x86 machine through firmware-level USB legacy emulation, making it the simplest keyboard input path a hobby kernel can implement. A keyboard connected this way communicates through the 8042 keyboard controller, a chip (or, on modern hardware, a firmware-emulated equivalent) sitting between the physical keyboard and two fixed I/O ports.
The 8042 controller
Section titled “The 8042 controller”Two ports expose the controller: 0x60, the data port, used to read a byte the keyboard has sent or write a byte to the keyboard or the controller itself; and 0x64, which behaves differently depending on direction: reading it returns a status register, while writing to it sends a command to the controller rather than to the keyboard. The status register’s low bit, Output Buffer Full, indicates whether a byte is actually waiting to be read from the data port; software polling the controller checks this bit before reading 0x60, and an interrupt-driven driver relies on IRQ1 firing only when a byte has actually arrived rather than needing to poll at all.
The controller itself, distinct from the keyboard attached to it, accepts its own commands over port 0x64: reading or writing its configuration byte, enabling or disabling either of its two ports (a legacy 8042 also multiplexes a second PS/2 port, conventionally used for a mouse), and running a controller self-test. A kernel initializing PS/2 input generally disables both ports first, flushes any stale byte sitting in the output buffer, configures the controller (in particular, ensuring translation and interrupts are set as intended), and only then re-enables the port it intends to use.
Scancodes
Section titled “Scancodes”A keyboard does not send character data: it sends scancodes, numeric identifiers for physical key positions, with the translation from scancode to character happening entirely in software. Every key produces a make code when pressed and a corresponding break code when released, letting software distinguish a key being held down from a single tap, and correctly track the state of modifier keys such as Shift or Ctrl across a sequence of other keypresses.
Several scancode sets exist for historical reasons, differing in exactly how make and break codes are encoded. Set 1, the original IBM PC XT encoding, represents a break code as the make code with the high bit set: key 0x1E pressed and released produces 0x1E then 0x9E. Set 2, the default the keyboard itself actually generates today, instead prefixes a break code with byte 0xF0 rather than setting a high bit, and prefixes several keys’ make codes with 0xE0 to mark them as “extended” (a distinction needed because Set 2’s compact single-byte codes were originally assigned to the smaller key set of an early keyboard, leaving no room for keys added later except by borrowing a byte as an escape prefix). The 8042 controller, when configured for translation (the common default), silently converts whatever the keyboard actually sends in Set 2 back into Set 1 before the byte ever reaches the data port, which is why most kernels, and this article, describe Set 1 despite the keyboard hardware itself speaking Set 2 underneath.
Reading input
Section titled “Reading input”An interrupt-driven driver registers a handler on IRQ1 (remapped to whatever vector the kernel’s PIC or APIC configuration assigns it) and reads the pending byte from port 0x60 each time the interrupt fires:
void keyboard_irq_handler(void) { uint8_t scancode = inb(0x60); // dispatch to the scancode-to-character path below pic_send_eoi(1);}Extended scancodes require holding a small amount of state across interrupts rather than processing every byte in isolation: an 0xE0 byte on its own carries no key information: it signals that the next byte belongs to an extended key, and the driver has to remember this and combine the two bytes together before looking up the actual key.
From scancodes to characters
Section titled “From scancodes to characters”Converting a scancode into a usable character is a table lookup, not a computation: a fixed array indexed by scancode maps each key to the character it produces in the simplest case, with a separate table, or a runtime transformation of the same table, applied when Shift is held. Modifier keys themselves (Shift, Ctrl, Alt) are not looked up in this table at all; instead, a driver tracks their pressed/released state directly from their own make and break codes, since a modifier’s role is to change how other keys are interpreted rather than to produce a character of its own. A minimal driver only needs to track this state correctly, apply the right table based on it, and handle the small number of keys (Caps Lock foremost among them) whose effect persists across keypresses rather than lasting only while physically held.
Keyboard layout is a separate concern from scancode handling entirely: a scancode identifies a physical key position, not a printed character, so the same scancode for the key between Tab and Caps Lock produces Q under a QWERTY layout and A under an AZERTY one purely because of which lookup table is used, with the scancode-reading logic itself unaffected by layout choice.
USB legacy emulation
Section titled “USB legacy emulation”Nearly every keyboard sold today is USB rather than PS/2, yet PS/2-style access continues to work because platform firmware implements USB legacy emulation: the system firmware itself intercepts the real USB keyboard’s HID reports and re-presents them to software as if they were arriving from an 8042 controller at the conventional ports, entirely transparently to an operating system that has not yet initialized its own USB stack. This is specifically what makes a PS/2-style driver a reasonable starting point for a hobby kernel: it works on essentially any real x86 machine and under any common emulator, without requiring USB support to exist first, even though the physical PS/2 port itself has largely disappeared from current hardware.
Implementation notes
Section titled “Implementation notes”An interrupt-driven driver that fails to read the data port before returning from its handler leaves the byte sitting in the controller’s output buffer, which on most implementations blocks IRQ1 from firing again until it is drained: a keyboard that appears to stop responding entirely, after working correctly for the very first keypress, is a common symptom of exactly this. PS/2 as a protocol also has no concept of reliably reporting more than a small, fixed number of simultaneously-held keys, a limitation generally called N-key rollover, or its absence, which rarely matters for typing but is worth knowing before relying on detecting many keys held down at once, a case where USB HID keyboards typically fare better than the PS/2 emulation layered on top of them.
References
Section titled “References”- ^ OSDev Wiki, “PS/2 Keyboard”: a detailed hobbyist reference covering scancode set differences and controller command sequences in more depth than fits here.
- ^ IBM, Personal System/2 Hardware Interface Technical Reference: the original specification of the 8042 controller’s command interface.
See also
Section titled “See also”- PIC & APIC: how IRQ1 is routed to the CPU before this driver’s handler runs.
- Serial Port: a simpler, often more reliable I/O path for early debugging.
- USB: what legacy emulation is standing in for on a keyboard with no PS/2 hardware behind it at all.
- PS/2 Mouse: the same 8042 controller’s second channel, with its own packet protocol.