#include "core/types.h" #include "arch/x86.h" #include "core/defs.h" #include "core/spinlock.h" #include "dev/kbd.h" #include "uapi/posix.h" #include "dev/vt.h" /* NVT -- how many terminals the chords reach */ #include "dev/input.h" #include "dev/keymap.h" // The chord, settable at run time through VT_SETCHORD. // // Ctrl+Alt+F1..F12 by default, the same keys X switches with. It used to be // AltGr+F-key, chosen because nothing else claimed it -- but under X the // server owns the keyboard and only Ctrl+Alt+F-key works, so the console and // the desktop answered different keys for the same thing. // // Ctrl+Alt+minus and Ctrl+Alt+equals step to the previous and next terminal, // for when you have two and do not care which number they are. // // Zero modifiers disables the chord; `chvt N` still works. uint vt_chord_mods = CTL | ALT; uint vt_chord_base = SCAN_F1; // Which terminal an F-key scancode names: F1..F10 are 0x3B..0x44 and F11 and // F12 sit apart, at 0x57 and 0x58. -1 for anything else. static int fkey_index(uint data) { if (data >= SCAN_F1 && data < SCAN_F1 + 10) return (int)(data - SCAN_F1); if (data == 0x57) return 10; if (data == 0x58) return 11; return -1; } // The terminal a chord key names, from `base`: the F-keys by fkey_index, any // other base as a run of ten consecutive scancodes -- a digit row is 1..9, 0, // and the minus and equals after it are already the previous and next keys. static int chord_index(uint data, uint base) { int i = base == SCAN_F1 ? fkey_index(data) : (data >= base && data < base + 10 ? (int)(data - base) : -1); return i < NVT ? i : -1; } // Which scancodes are modifiers, and which are locks. Hardware facts rather // than layout ones -- every layout agrees that 0x2A is a Shift key -- so they // stay here while the character tables live in keymap.c. static const uchar shiftcode[256] = { [0x1D] = CTL, [0x2A] = SHIFT, [0x36] = SHIFT, [0x38] = ALT, [0x9D] = CTL, [0xB8] = RALT // right Alt / AltGr -- see RALT in kbd.h }; static const uchar togglecode[256] = { [0x3A] = CAPSLOCK, [0x45] = NUMLOCK, [0x46] = SCROLLLOCK }; // A key can produce more than one byte. // // consoleintr's contract with getc() is bytes: the serial line hands it raw // UTF-8 already, and it would have no way to tell an encoded codepoint apart // from a byte that happened to have the same value. So the keyboard encodes // here and hands the bytes over one call at a time -- kbdintr loops until // getc() returns negative, so a queue of at most four drains on its own // before the next scancode is read. static uchar pend[4]; static int pend_n, pend_i; static int queue_utf8(uint32 cp) { pend_i = 0; if (cp < 0x80) { pend_n = 0; return (int)cp; // the common case: no queue at all } if (cp < 0x800) { pend[0] = 0xC0 | (cp >> 6); pend[1] = 0x80 | (cp & 0x3F); pend_n = 2; } else if (cp < 0x10000) { pend[0] = 0xE0 | (cp >> 12); pend[1] = 0x80 | ((cp >> 6) & 0x3F); pend[2] = 0x80 | (cp & 0x3F); pend_n = 3; } else { pend[0] = 0xF0 | (cp >> 18); pend[1] = 0x80 | ((cp >> 12) & 0x3F); pend[2] = 0x80 | ((cp >> 6) & 0x3F); pend[3] = 0x80 | (cp & 0x3F); pend_n = 4; } return pend[pend_i++]; } // --- the lock keys and their lights ------------------------------------------- // // Caps, Num and Scroll Lock are one state for the machine, whichever keyboard // pressed them -- the laptop's own and a USB one agree -- and the lights on // every keyboard show it. They never did: caps lock worked and its light // stayed dark. Under X the server keeps its own lock state and sets the // lights through /dev/input (see inputwrite), so these are left to it while // it holds the keyboard. static uint kbd_locks; // CAPSLOCK | NUMLOCK | SCROLLLOCK static uint lock_held; // lock keys down now: a held key repeats static struct spinlock ledlock; static int ledlock_ready; // The PS/2 side is a two-byte command, 0xED and the lights, and each byte // must be acknowledged (0xFA) before the next is sent. That cannot be waited // for here -- this runs in the keyboard's interrupt, and the acknowledgement // *is* a keyboard interrupt -- so the second byte goes out when kbdgetc sees // the first one acknowledged. static int led_want = -1; // PS/2 light byte still to send static int led_stage; // 0 idle, 1 0xED sent, 2 the byte sent static uint led_since; static void ps2_led_kick(void) { if (led_stage != 0 && ticks - led_since > HZ) led_stage = 0; // no keyboard answered: start over if (led_stage == 0 && led_want >= 0 && !(inb(KBSTATP) & 0x02)) { outb(KBDATAP, 0xED); led_stage = 1; led_since = ticks; } } // Show `locks` on every keyboard. void kbd_set_leds(uint locks) { if (!ledlock_ready) { initlock(&ledlock, "kbdled"); ledlock_ready = 1; } acquire(&ledlock); led_want = ((locks & SCROLLLOCK) ? 1 : 0) | ((locks & NUMLOCK) ? 2 : 0) | ((locks & CAPSLOCK) ? 4 : 0); ps2_led_kick(); release(&ledlock); // USB numbers them the other way round: Num, Caps, Scroll. hid_leds_want(((locks & NUMLOCK) ? 1 : 0) | ((locks & CAPSLOCK) ? 2 : 0) | ((locks & SCROLLLOCK) ? 4 : 0)); } // A lock key pressed, on any keyboard. `down` 0 is its release. void kbd_lock_key(uint lock, int down) { if (!down) { lock_held &= ~lock; return; } if (lock_held & lock) return; // the keyboard's repeat, not a new press lock_held |= lock; kbd_locks ^= lock; if (!input_is_grabbed(INPUT_KBD)) kbd_set_leds(kbd_locks); } uint kbd_lock_state(void) { return kbd_locks; } // 0xFA and 0xFE from the keyboard: answers to a light command, not keys. static int ps2_led_answer(uint data) { if (led_stage == 0 || (data != 0xFA && data != 0xFE)) return 0; if (!ledlock_ready) return 0; acquire(&ledlock); if (data == 0xFE) { led_stage = 0; // "resend": try again with the next change } else if (led_stage == 1 && led_want >= 0) { outb(KBDATAP, (uchar)led_want); led_want = -1; led_stage = 2; } else { led_stage = 0; ps2_led_kick(); // changed again meanwhile } release(&ledlock); return 1; } int kbdgetc(void) { static uint shift; uint st, data; uint32 cp; if (pend_i < pend_n) return pend[pend_i++]; // finish the last key first pend_i = pend_n = 0; st = inb(KBSTATP); if((st & KBS_DIB) == 0) return -1; if(st & 0x20) return -1; data = inb(KBDATAP); if(ps2_led_answer(data)) return 0; if(data == 0xE0){ shift |= E0ESC; return 0; } // Feed /dev/input/event0 before the byte is turned into a character. This // has to happen here rather than in a second handler because the byte can // only be read from the port once -- whoever reads it owes it to everyone // else. The console path below is untouched: both consumers see every key. input_ps2_scancode(data, (shift & E0ESC) != 0); // While something holds an exclusive grab on the keyboard, the console gets // nothing. The shift/caps state below is still tracked, so releasing the // grab does not leave the line discipline believing a modifier is held. if(input_is_grabbed(INPUT_KBD)){ if(data & 0x80){ data = (shift & E0ESC ? data : data & 0x7F); shift &= ~(shiftcode[data] | E0ESC); if(togglecode[data]) kbd_lock_key(togglecode[data], 0); } else { shift |= shiftcode[data]; if(togglecode[data]) kbd_lock_key(togglecode[data], 1); } return 0; } if(data & 0x80){ data = (shift & E0ESC ? data : data & 0x7F); shift &= ~(shiftcode[data] | E0ESC); if(togglecode[data]) kbd_lock_key(togglecode[data], 0); return 0; } else if(shift & E0ESC){ data |= 0x80; shift &= ~E0ESC; } shift |= shiftcode[data]; if(togglecode[data]) kbd_lock_key(togglecode[data], 1); // The locks are the machine's, not this keyboard's: see kbd_lock_key(). shift = (shift & ~(CAPSLOCK | NUMLOCK | SCROLLLOCK)) | kbd_locks; // The switch chord, before the character tables, because there is no // character to look up: it is a combination that means "show me a different // terminal". What it means is decided in console.c, which is where the // terminals are; this only has to name it. See console_vt_hotkey(). // // The modifier test is an equality, not a mask. `shift & ALT` would fire on // Ctrl+Alt+1 and on AltGr+1 as well, so a chord aimed at the host -- or a // Polish keystroke -- would switch terminals behind your back. Exactly the // modifiers named, and nothing else held. if (vt_chord_mods != 0 && (shift & (CTL | SHIFT | ALT | RALT)) == vt_chord_mods) { int i = chord_index(data, vt_chord_base); if (i >= 0) return KBD_VT_SWITCH + i; if (data == SCAN_MINUS) return KBD_VT_PREV; if (data == SCAN_EQUALS) return KBD_VT_NEXT; } // And Linux's own: Alt+F-key and Ctrl+Alt+F-key, whatever the chord is set // to. Under X the terminal is in VT_PROCESS, console_vt_hotkey() lets the // keys through, and X switches with them itself -- as on Linux. if (chord_index(data, SCAN_F1) >= 0 && ((shift & (CTL | SHIFT | ALT | RALT)) == ALT || (shift & (CTL | SHIFT | ALT | RALT)) == (CTL | ALT))) return KBD_VT_SWITCH + chord_index(data, SCAN_F1); cp = keymap_lookup(data, shift); if (cp == 0) return 0; if (cp >= KBD_SPECIAL) return (int)cp; // arrows and the rest, not characters return queue_utf8(cp); } void kbdintr(void) { consoleintr(kbdgetc); } // The two headers describe the same bits to two different audiences, and a // mismatch would be silent: a chord set from userspace would simply never // match, and the only symptom would be a key that does nothing. _Static_assert(VTC_SHIFT == SHIFT && VTC_CTRL == CTL && VTC_ALT == ALT && VTC_RALT == RALT, "include/uapi/posix.h VTC_* must mirror include/dev/kbd.h modifiers");