Caps, Num and Scroll Lock now light their LEDs on PS/2 and USB keyboards, share one state between keyboards, and stop toggling on key repeat. X sets them through evdev writes while it holds the keyboard. Unmapping memory in a process with threads on other CPUs now flushes their TLBs with an IPI. Without it a freed page stayed reachable through stale translations, which corrupted Epiphany's heap on SMP. ACPI batteries and AC adapters appear under /sys/class/power_supply. /proc/<pid>/maps lists file mappings, and a process killed by a fault names the file and offset it died in. r8169 gets a 256-entry RX ring. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
311 lines
9.9 KiB
C
311 lines
9.9 KiB
C
#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");
|