The Linux keyboard driver

by Andries E. Brouwer

When you press a key on the console keyboard, the corresponding character is not simply added to the tty (generic terminal handling) input buffers as if it had come in over a serial port. A lot of processing is required before the kernel knows what the correct characters are. Only after processing can the generic tty code, which handles all interactive terminal devices, take over.

Roughly speaking, the picture is this: the keyboard produces scancodes, the scancodes are assembled into keycodes (one unique code for each key), and keycodes are converted to tty input characters using the kernel keymaps. After that, the normal `stty' processing takes place, just as for any other terminal.

Scancodes First

The usual PC keyboards are capable of producing three sets of scancodes. Writing 0xf0 followed by 1, 2 or 3 to port 0x60 will put the keyboard in scancode mode 1, 2 or 3. Writing 0xf0 followed by 0 queries the mode, resulting in a scancode byte 0x43, 0x41 or 0x3f from the keyboard. (Don't try this at home, kids. If you are not very careful, you will end up in a situation where rebooting is the only way out—and control-alt-delete will not be available to shut the computer down correctly. See the accompanying listing of kbd_cmd.c for details.)

Scancode mode 2 is the default. In this mode, a key press usually produces a value s in the range 0x01-0x5f and the corresponding key release produces s+0x80. In scancode mode 3, the only key releases that produce a scan code are of both Shift keys, and the left Ctrl and Alt keys; for all other keys only the key presses are noted. The produced scancodes are mostly equal to those for scancode mode 2.

In scancode mode 1 most key releases produce the same values as in scancode mode 2, but for key presses there are entirely different, unrelated values. The details are somewhat messy.

A program can request the raw scancodes by

ioctl(0, KDSKBMODE, K_RAW);

For example, X, dosemu, svgadoom, and showkey -s do this. The default keycode translation mode is restored by


See the keyboard FAQ (in kbd-0.90.tar.gz) for some advice about how to get out of raw scancode mode from the shell prompt. (At a shell prompt the commands kbd_mode [-s|-k|-a|-u] will set the keyboard mode to scancode mode, keycode mode, translation (`ASCII') mode and Unicode mode, respectively. But it is difficult to type this command when the keyboard is in raw scancode mode.)

Scancodes to Keycodes

Life would have been easy had there been a 1-1 correspondence between keys and scancodes. (And in fact there is, in scancode mode 3, but that does not suffice for Linux, since X requires both the key press and the key release events.)

But as it is, a single key press can produce a sequence of up to six scancodes, and the kernel has to parse the stream of scancodes and convert it into a series of key press and key release events. To this end, each key is provided with a unique keycode k in the range 1-127, and pressing key k produces keycode k, while releasing it produces keycode k+128. The assignment of key codes is in principle arbitrary (and has no relation to the key codes used by X), but at present the key code equals the scan code for those keys that produce a single scancode in the range 0x01-0x58.

The parsing works by

  • recognizing the special sequence 0xe1 0x1d 0x45 0xe1 0x9d 0xc5 produced by the Pause key

  • throwing out any fake Shift-down and Shift-up codes, inserted by the keyboard to make the kernel believe that you pressed Shift to undo the effect of NumLock

  • recognizing scancode pairs 0xe0 s

  • recognizing single scancodes s.

Since s can take 127 values (0 is a keyboard error condition, and the high order bit indicates press/release) this means that parsing could result in 1+127+126=254 distinct keycodes. However, at present keycodes are restricted to the range 1-127 and we have to work a little to make things fit. (No doubt sooner or later keycodes will be integers instead of 7-bit quantities, and the keymaps will be sparse, but for the time being we can avoid that—since to my knowledge no actual PC keyboard has more than 127 keys.) So, there are small tables that assign a keycode to a scancode pair 0xe0 s or to a single scancode in the range 0x59-0x7f. In the default setting everything works for most current keyboards, but in case you have some strange keyboard, you can get the kernel to recognize an otherwise unrecognized key by filling an entry in these tables using the KDSETKEYCODE ioctl; see setkeycodes(8).

Two keys are unusual in the sense that their keycode is not constant, but depends on modifiers. The PrintScrn key will yield keycode 84 when combined with either Alt key, but keycode 99 otherwise. The Pause key will yield keycode 101 when combined with either Ctrl key, but keycode 119 otherwise. (This has historic reasons, but might change, to free keycodes 99 and 119 for other purposes.)

At present there is no way to tell X about strange key(board)s. The easiest solution would be to make X use keycodes instead of scancodes, so that the information about strange keys and the scancodes they produce is located a single place.

A program can request to get keycodes by doing

ioctl(0, KDSKBMODE,

For example, showkey does this. Warning: the details of the function of both the KDSETKEYCODE ioctl and the K_MEDIUMRAW keyboard mode are likely to change in the future.


Keycodes are converted to keysymbols by looking them up on the appropriate keymap. There are eight possible modifiers (shift keys), and the combination of currently active modifiers and locks determines the keymap used.

Thus, what happens is approximately:

int shift_final = shift_state ^ kbd->lockstate;
 ushort *key_map = key_maps[shift_final];
 keysym = key_map[keycode];

The eight modifiers are known as Shift, AltGr, Control, Alt, ShiftL, ShiftR, CtrlL and CtrlR. These labels have no intrinsic meaning, and the modifiers can be used for arbitrary purposes, except that the keymap for the Shift modifier determines the action of CapsLock (and that the Shift key partially suppresses keyboard application mode). By default Shift is bound to both Shift keys and Control keys and Alt and AltGr are bound to the left and right Alt keys. The remaining four modifiers are unbound in the default kernel. X is able to distinguish ShiftL and ShiftR, etc.

Thus, there are 256 possible keymaps—for plain symbols, for Shift+symbol, for Ctrl+AltL+Shift+symbol, etc. Usually, not all of the keymaps will be allocated (combinations with more than three modifiers are rather unusual), and in fact the default kernel allocates only 7 keymaps, namely the plain, Shift, AltR, Ctrl, Ctrl+Shift, AltL and Ctrl+AltL maps. You can allocate more keymaps simply by filling some of their entries using loadkeys(1).

Key # symbols

Key symbols are shorts, i.e., they consist of two bytes. In Unicode mode, this short is just the 16-bit value returned—or, to be precise, the returned byte string is the UTF-8 representation of this Unicode character. The keyboard is put into Unicode mode by


When not in Unicode mode, the high order byte is viewed as a type, and the low order byte as a value, and we do:

type = KTYP(keysym);
 (*key_handler[type])(keysym & 0xff, up_flag);

The type selects a function from the array key_handler:

static k_hand key_handler[16] = {
     do_self, do_fn, do_spec, do_pad, do_dead,
     do_cons, do_cur, do_shift, do_meta, do_ascii,
     do_lock, do_lowercase, do_ignore, do_ignore,
     do_ignore, do_ignore
  1. do_self, commonly used for ordinary keys, just returns the given value, after possibly handling pending dead diacriticals.

  2. do_fn, commonly used for function keys, returns the string func_table[value]. Strings can be assigned using loadkeys(1).

  3. do_spec is used for special actions, not necessarily related to character input. It does spec_fn_table[value]();, where

    static void_fnp spec_fn_table[] = {
    do_null, enter, show_ptregs, show_mem,
    show_state, send_intr, lastcons, caps_toggle,
    num, hold, scroll_forw, scroll_back, boot_it,
    caps_on, compose, SAK, decr_console,
    incr_console, spawn_console, bare_num

    The associated actions (and their default key binding) are:

    • Return (Enter): return a CR and if VC_CRLF mode set also a LF. One sets/clears CRLF mode by sending ESC [ 20 h or ESC [ 20 l to the console.

    • Show_Registers (AltR-ScrollLock): print the contents of the CPU registers.

    • Show_Memory (Shift-ScrollLock): print current memory usage.

    • Show_State (Ctrl-ScrollLock): print the process tree.

    • Break (Ctrl-Break): send a Break to the current tty.

    • Last_Console (Alt-PrintScrn): switch to the last used console.

    • Caps_Lock (CapsLock): toggle the CapsLock setting.

    • Num_Lock (NumLock): in keyboard application mode: return ESC O P; otherwise, toggle the NumLock setting. One sets/clears keyboard application mode by sending ESC = or ESC > to the console. (See also Bare_Num_Lock below.)

    • Scroll_Lock (ScrollLock): stop/start tty—roughly equivalent to ^S/^Q.

    • Scroll_Forward (Shift-PageDown): scroll console down.

    • Scroll_Backward (Shift-PageUp): scroll console up. These two functions are implemented by using the memory on the video card, and provide only a very limited scrollback facility. Moreover, all scrollback information is lost when you switch virtual consoles. So, for real scrollback use a program-like screen.

    • Boot (Ctrl-AltL-Del): reboot. If you press Ctrl-AltL-Del (or whatever key was assigned the keysym Boot by loadkeys) then either the machine reboots immediately (without sync), or init is sent a SIGINT. The former behavior is the default. The default can be changed by root, using the system call reboot(): see ctrlaltdel(8) and init(8).

      Some versions of init change the default. What happens when init gets SIGINT depends on the version of init used—often it will be determined by the pf (stands for powerfail) entry in /etc/inittab, which means that you can run an arbitrary program. In the current kernel, Ctrl-AltR-Del is no longer by default assigned to Boot, only Ctrl-AltL-Del is.

      Sometimes when init hangs in a disk wait (and syncing is impossible) it can be useful to say ctrlaltdel hard, which may allow you to force a reboot without power cycling or pressing the reset button.

    • aps_On (none): set CapsLock.

    • ompose (Ctrl-.): start a compose sequence. The two following characters will be combined. This is a good way to get accented characters that you only rarely need. For example, Ctrl-.><,><c will produce a c-cedilla, and Ctrl-.&gt:<a><e the Danish letter æ. Precisely which combinations combine to what character; will show dumpkeys(1), loadkeys(1) will set combinations.

    • SAK (none): Secure Attention Key. This is supposed to kill all processes related to the current tty, and reset the tty to a known default state. It is not completely implemented—it is not quite clear what resetting the keyboard/console should do to the fonts and keymaps. The easiest solution is to send a signal to some trusted daemon, and let it reset keyboard and console as desired. In this way we obtain something closely related to the Spawn_Console function below.

    • Decr_Console (AltL-LeftArrow): switch to the virtual console that precedes the current console in the cyclic order.

    • Incr_Console (AltL-RightArrow): switch to the virtual console that follows the current console in the cyclic order.

    • Spawn_Console or KeyboardSignal (AltL-UpArrow): send a specified process a specified signal. I use this to signal init that it should create a fresh virtual console for me.

    • Bare_Num_Lock (Shift-NumLock): toggle the NumLock setting (regardless of keyboard application mode).

    As long as no new releases of init and loadkeys have come out, you can play with this by using loadkeys and starting the program spawn_console:

    % loadkeys >> EOF
     alt keycode 103 = 0x0212
     % spawn_console &

    Of course, if you put this in /etc/rc.local, you would probably want to start getty instead of bash.

  4. do_pad, commonly used for the keypad keys. In keyboard application mode this produces some three-character string ESC O X (with variable X depending on the key), provided that Shift is not pressed simultaneously. Otherwise, when NumLock is on, we get the symbol printed on the key (0123456789.+ -/* and CR).

    Finally, if NumLock is not on, the four arrow keys yield ESC [ X (with X=A, B, C, or Dp when not in cursor key mode, and ESC O X otherwise, while the remaining keys are treated as function keys, and yield the associated string. For the middle key (keypad-5) we find four possibilities:

    • in keyboard application mode (unshifted), ESC O u

    • in keyboard application mode, shifted, without NumLock, ESC O G

    • otherwise, without NumLock, ESC [ G

    • but with NumLock, 5.

    If you think this is unnecessarily complicated, I agree. It is a messy combination of VT100 and DOS keyboard behavior. However, so far suggestions for change have met with too much resistance.

  5. do_dead is used for “dead keys” that provide the following key with a diacritical. By default there are no dead keys. One may define keys producing a dead grave, acute, circumflex, tilde, or diaeresis. How a dead key combines with a following key is specified using the compose mechanism discussed above.

  6. do_cons is used for switching consoles. By default the combinations (Ctrl-)AltL-Fn switch to virtual console n for n in the range 1-12, and AltR-Fn switches to console n+12 for these same n.

  7. do_cur handles cursor keys. One gets either ESC [ X or ESC O X (with X one of A, B, C, or D) depending on the cursor key mode. (One sets or clears cursor key mode by sending ESC [ ? 1 h or ESC [ ? 1 l to the console.)

  8. do_shift maintains the shift state (the up/down state of the modifier keys).

  9. do_meta is commonly used for ordinary keys combined with AltL. If the keyboard is in metamode, this will yield a pair ESC x; otherwise x | 0x80 is produced, where x is the key pressed in both cases. (You can set or clear metamode using the tiny utility setmetamode(1).)

  10. do_ascii is used to construct given codes: press AltL, type a decimal code on the keypad, and release AltL. This yields the character with the given code. In Unicode mode the same works in hexadecimal: press AltR, type a hexadecimal code on the keypad, possibly using the ordinary a, b, c, d, e, and f keys, and release AltR. This yields the Unicode symbol with the code given.

  11. do_lock toggles the state of the corresponding modifier key lock. (Recall the line we saw above: shift_final = shift_state ^ kbd-<lockstate.) Thus, if you have your Cyrillic keys under combinations with AltR, you can use AltR together with other keys to get only a few Cyrillic symbols, but should type AltGr_Lock if you plan to type a longish Cyrillic text. (Note that the right Alt key, that I called AltR here, is usually known as AltGr.)

  12. do_lowercase is used for the handling of CapsLock. Note that CapsLock is different from ShiftLock. With ShiftLock, a digit 4 will be turned into a dollar sign (for default keyboard layout), but CapsLock will only affect lower case letters, and turn them into the corresponding upper case letters. Type 11 is equivalent to type 0, with the added information that the symbol may be affected by CapsLock (and the resulting character is the one that would have resulted from pressing Shift).

As already mentioned, almost all of this can be changed dynamically by use of loadkeys(1). The current state is dumped by dumpkeys(1). A list of known symbols is provided by dumpkeys -l. The keycodes associated with the various keys can be found using showkey(1). These and many other utilities for keyboard and console can be found in kbd-0.90.tar.gz on and its mirror sites.

Using loadkeys

Use loadkeys to change the code produced by the BackSpace key from Delete to BackSpace:

% loadkeys
keycode 14 = BackSpace

Assign the string “emacs\n” to the function key F12, and “rm *~\n” to Shift-F12 (the keycode 88 was found using showkey; the F66 is a random unused function key symbol):

% loadkeys
 keycode 88 = F12
 shift keycode 88 = F66
 string F12 = "emacs\n"
 string F66 = "rm *~\n"

Create the compose combination that will compose | and S into $:

% loadkeys
 compose '|' 'S' to '$'
 compose 'S' '|' to '$'

Reset to some default state:

% loadkeys -d

After the above handling, the obtained character(s) are put in the raw tty queue. Depending on the mode of the tty, they will be processed and transferred to the cooked tty queue. (Don't confuse raw mode as stty knows it, with the raw scancode mode discussed above.) Finally, the application will get them when it does getchar();.

Andries Brouwer,, has used Unix for various mathematical, linguistic, and playful purposes the past 20 years or so. He might be known to some for the first net release of hack.

Load Disqus comments