SchOS/kernel/dev/kbd.c
Nexoniarz 438e7af141 Keyboard lights, TLB shootdowns, battery in sysfs, /proc/pid/maps
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
2026-10-25 13:43:27 +01:00

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");