refactor(kernel): the keyboard bridge, shared

The stick, increment 1, task 1 (plan 2026-10-26-stick-1-screen-and-keyboard).
repl.c's keyboard bridge -- the key tables, Shift/Alt/Caps Lock/Num Lock and
their lights, the keypad, Alt+TAB, and the per-tick USB keyboard servicing --
moves unchanged to kernel/src/hal/kbd_input.c as sk_input_getc() (and
sk_input_drop()), so that the v4 boot's console reads the same keyboards
(docs/v4.0.0/STICK.md 5.4). v3's behaviour is unchanged.

scripts/qemu_session.py: the typed-QEMU-session harness, kept in the repo
from here on (the identity gate's sessions, and v3_keys: system=v3 typed on
QEMU's keyboard with send-key).

Yardstick, system=v3 typed by keyboard: before, amd64 PS/2 134805 PASS,
aarch64 virtio keyboard 135549 PASS; after, 135746 and 135835 PASS.
(riscv64 134850: v3 never reached its prompt -- Artemis virtio_blk writes
time out; v3's block path, before this change; being investigated.)
Accepted: suite EXIT 0; three clean qemu boots to [zuse@Hera] ok>, POST
538/538, dict_hash 0xfec8c47cfc6f437f (145722, 145859, 150231); test-32,
39 modules, 0 failures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-26 15:09:38 -04:00
co-authored by Claude Opus 5.5
parent 6f96502fdf
commit 83d3692976
20 changed files with 46086 additions and 179 deletions
+193
View File
@@ -0,0 +1,193 @@
/* kbd_input.c -- one byte of console input, from the serial line or a
* keyboard: the keyboard bridge, moved here from kernel/src/repl.c on
* 2026-10-26 unchanged in behaviour, so that v3's console and the v4 boot's
* read the same keyboards (docs/v4.0.0/STICK.md 5.4;
* docs/v4.0.0/plans/2026-10-26-stick-1-screen-and-keyboard.md, task 1).
*/
#include "starkernel/kbd_input.h"
#include "console.h"
#include "starkernel/timer.h"
#include "starkernel/xhci_driver.h"
#include "starkernel/usb_kbd.h" /* usb_kbd_set_leds() -- the lock lights, A3 */
#include "word_source/include/keyboard_words.h" /* sk_key_event_poll() */
#include <stdint.h>
/*===========================================================================
* FABRIC-0.md item 4.4v: keyboard-to-REPL bridge.
*
* Translates sk_key_event_poll()'s converged Linux-keycode-namespace
* stream (keyboard_words.c -- one implementation shared with KEY-EVENT,
* live-verified on all three architectures per item 4.3.5f) into the same
* byte stream sk_console_readline() already reads from console_getc(): -1 for
* "nothing ready", else a raw ASCII byte with '\n'/0x7F meaning the same
* thing they mean for the serial path below.
*
* Table covers exactly the keys a line editor needs -- letters, digits,
* the standard US-QWERTY punctuation row, space, enter, backspace, tab
* (for the Ctrl+TAB toggle interception, 4.4y/4.4u step 8) -- not full
* keyboard coverage. Keycodes are Linux input-event-codes.h values,
* confirmed against this build host's own header, not guessed (§25.0
* rule 4). Index 0 means "no mapping"; arrows/F-keys/etc. fall through
* unmapped and are silently dropped, consistent with this REPL's
* append/backspace-only editing model (4.4u: no mid-line cursor
* movement).
*===========================================================================*/
#define SK_KBD_TABLE_SIZE 98u /* highest keycode used below is KEY_RIGHTCTRL=97 */
static const char sk_kbd_unshifted[SK_KBD_TABLE_SIZE] = {
[2]='1',[3]='2',[4]='3',[5]='4',[6]='5',[7]='6',[8]='7',[9]='8',[10]='9',[11]='0',
[12]='-',[13]='=',
[16]='q',[17]='w',[18]='e',[19]='r',[20]='t',[21]='y',[22]='u',[23]='i',[24]='o',[25]='p',
[26]='[',[27]=']',
[30]='a',[31]='s',[32]='d',[33]='f',[34]='g',[35]='h',[36]='j',[37]='k',[38]='l',
[39]=';',[40]='\'',[41]='`',[43]='\\',
[44]='z',[45]='x',[46]='c',[47]='v',[48]='b',[49]='n',[50]='m',
[51]=',',[52]='.',[53]='/',
[57]=' ',
};
static const char sk_kbd_shifted[SK_KBD_TABLE_SIZE] = {
[2]='!',[3]='@',[4]='#',[5]='$',[6]='%',[7]='^',[8]='&',[9]='*',[10]='(',[11]=')',
[12]='_',[13]='+',
[16]='Q',[17]='W',[18]='E',[19]='R',[20]='T',[21]='Y',[22]='U',[23]='I',[24]='O',[25]='P',
[26]='{',[27]='}',
[30]='A',[31]='S',[32]='D',[33]='F',[34]='G',[35]='H',[36]='J',[37]='K',[38]='L',
[39]=':',[40]='"',[41]='~',[43]='|',
[44]='Z',[45]='X',[46]='C',[47]='V',[48]='B',[49]='N',[50]='M',
[51]='<',[52]='>',[53]='?',
[57]=' ',
};
#define SK_KEY_BACKSPACE 14u
#define SK_KEY_TAB 15u
#define SK_KEY_ENTER 28u
#define SK_KEY_LEFTSHIFT 42u
#define SK_KEY_RIGHTSHIFT 54u
#define SK_KEY_LEFTALT 56u
#define SK_KEY_RIGHTALT 100u
#define SK_KEY_CAPSLOCK 58u
#define SK_KEY_NUMLOCK 69u
#define SK_KEY_KPENTER 96u
#define SK_KEY_KPSLASH 98u
#define SK_KEY_102ND 86u
static int g_kbd_shift_down; /* zero-initialized (BSS) */
static int g_kbd_alt_down;
/* A full-size keyboard (Captain Bob, 2026-10-14: "include caps lock,
* numlock, keypad and arrow"). Caps Lock and Num Lock are toggles, held
* here for every keyboard alike and shown on a USB keyboard's lights
* (usb_kbd_set_leds()). Num Lock starts on, as a PC's firmware usually
* leaves it. Caps Lock acts on letters only; Shift reverses it. */
static int g_kbd_caps_lock;
static int g_kbd_num_lock = 1;
static void sk_kbd_show_locks(void)
{
usb_kbd_set_leds((uint8_t)((g_kbd_num_lock ? USB_KBD_LED_NUM : 0) |
(g_kbd_caps_lock ? USB_KBD_LED_CAPS : 0)));
}
/* The keypad: what each key types. With Num Lock on, 0-9 and '.'; with it
* off those are Home/arrows/PgUp/... -- navigation, which this REPL's
* append/backspace editing has no use for, so they type nothing (as the
* arrow keys do). / * - + always type; keypad Enter is Enter. */
static int sk_kbd_keypad(uint16_t keycode)
{
switch (keycode) {
case 55u: return '*';
case 74u: return '-';
case 78u: return '+';
case SK_KEY_KPSLASH: return '/';
case SK_KEY_KPENTER: return '\n';
default: break;
}
if (!g_kbd_num_lock) return 0;
switch (keycode) {
case 71u: return '7'; case 72u: return '8'; case 73u: return '9';
case 75u: return '4'; case 76u: return '5'; case 77u: return '6';
case 79u: return '1'; case 80u: return '2'; case 81u: return '3';
case 82u: return '0'; case 83u: return '.';
default: return 0;
}
}
/* Drains and translates one physically-typed key. Modifier state persists
* across calls (a real keyboard's shift/alt state is global, not
* per-line). Alt+TAB is intercepted here and drives the graphics/text
* toggle directly (console_fb_toggle_graphics(), the same state-machine
* transition the ALT+TAB FORTH word calls) -- never reaches the line
* buffer as a character either way. */
static int sk_kbd_getc(void)
{
uint16_t keycode;
int pressed;
while (sk_key_event_poll(&keycode, &pressed)) {
if (keycode == SK_KEY_LEFTSHIFT || keycode == SK_KEY_RIGHTSHIFT) {
g_kbd_shift_down = pressed;
continue;
}
if (keycode == SK_KEY_LEFTALT || keycode == SK_KEY_RIGHTALT) {
g_kbd_alt_down = pressed;
continue;
}
if (!pressed) continue; /* only act on press/repeat */
if (keycode == SK_KEY_CAPSLOCK) { g_kbd_caps_lock = !g_kbd_caps_lock; sk_kbd_show_locks(); continue; }
if (keycode == SK_KEY_NUMLOCK) { g_kbd_num_lock = !g_kbd_num_lock; sk_kbd_show_locks(); continue; }
{
int k = sk_kbd_keypad(keycode);
if (k) return k;
}
if (keycode == SK_KEY_102ND) return g_kbd_shift_down ? '|' : '\\';
if (keycode == SK_KEY_TAB) {
if (g_kbd_alt_down) console_fb_toggle_graphics();
continue; /* bare TAB: not mapped, same as arrows/F-keys */
}
if (keycode == SK_KEY_ENTER) return '\n';
if (keycode == SK_KEY_BACKSPACE) return 0x7F;
if (keycode < SK_KBD_TABLE_SIZE) {
char lower = sk_kbd_unshifted[keycode];
int shift = g_kbd_shift_down;
if (lower >= 'a' && lower <= 'z' && g_kbd_caps_lock) shift = !shift;
char c = shift ? sk_kbd_shifted[keycode] : lower;
if (c) return (unsigned char)c;
}
/* unmapped keycode -- drop and keep draining */
}
return -1;
}
/* One raw byte from either input source (serial console or the keyboard-
* event bridge), non-blocking, -1 if neither has one ready right now. Not
* itself a FORTH word -- the shared byte-fetch underneath sk_console_getkey()/
* sk_console_key_available() (the standard dictionary's KEY/?TERMINAL, wired
* through shim.c's getchar()) and sk_console_readline() (QUERY/EXPECT, wired
* through shim.c's fgets()) alike. */
static uint64_t g_kbd_poll_tick;
int sk_input_getc(void)
{
/* A USB keyboard's reports come in on the xHCI event ring: while one is
* running, service the ring once per timer tick here, not only on the
* once-a-second idle beat -- keys arrive at typing speed (A3). */
if (xhci_keyboard_running()) {
uint64_t t = heartbeat_ticks();
if (t != g_kbd_poll_tick) {
g_kbd_poll_tick = t;
xhci_poll_events();
}
}
int c = console_getc();
if (c < 0) c = sk_kbd_getc(); /* FABRIC-0.md 4.4v: second source, same buffer */
return c;
}
void sk_input_drop(void)
{
while (sk_input_getc() >= 0) { }
}
+9 -179
View File
@@ -50,7 +50,7 @@
#include "starkernel/vm/stadium.h" /* task 3.8 -- stadium_conserved() evidence print */
#include "block_subsystem.h"
#include "word_source/include/keyboard_words.h"
#include "starkernel/usb_kbd.h" /* usb_kbd_set_leds() -- the lock lights, A3 */
#include "starkernel/kbd_input.h" /* sk_input_getc() */
#include "word_source/include/block_words.h"
#include "word_registry.h"
#include "freestanding/stdio.h"
@@ -869,180 +869,10 @@ static void sk_repl_idle(VM *active_vm)
}
}
/*===========================================================================
* FABRIC-0.md item 4.4v: keyboard-to-REPL bridge.
*
* Translates sk_key_event_poll()'s converged Linux-keycode-namespace
* stream (keyboard_words.c -- one implementation shared with KEY-EVENT,
* live-verified on all three architectures per item 4.3.5f) into the same
* byte stream sk_console_readline() already reads from console_getc(): -1 for
* "nothing ready", else a raw ASCII byte with '\n'/0x7F meaning the same
* thing they mean for the serial path below.
*
* Table covers exactly the keys a line editor needs -- letters, digits,
* the standard US-QWERTY punctuation row, space, enter, backspace, tab
* (for the Ctrl+TAB toggle interception, 4.4y/4.4u step 8) -- not full
* keyboard coverage. Keycodes are Linux input-event-codes.h values,
* confirmed against this build host's own header, not guessed (§25.0
* rule 4). Index 0 means "no mapping"; arrows/F-keys/etc. fall through
* unmapped and are silently dropped, consistent with this REPL's
* append/backspace-only editing model (4.4u: no mid-line cursor
* movement).
*===========================================================================*/
#define SK_KBD_TABLE_SIZE 98u /* highest keycode used below is KEY_RIGHTCTRL=97 */
static const char sk_kbd_unshifted[SK_KBD_TABLE_SIZE] = {
[2]='1',[3]='2',[4]='3',[5]='4',[6]='5',[7]='6',[8]='7',[9]='8',[10]='9',[11]='0',
[12]='-',[13]='=',
[16]='q',[17]='w',[18]='e',[19]='r',[20]='t',[21]='y',[22]='u',[23]='i',[24]='o',[25]='p',
[26]='[',[27]=']',
[30]='a',[31]='s',[32]='d',[33]='f',[34]='g',[35]='h',[36]='j',[37]='k',[38]='l',
[39]=';',[40]='\'',[41]='`',[43]='\\',
[44]='z',[45]='x',[46]='c',[47]='v',[48]='b',[49]='n',[50]='m',
[51]=',',[52]='.',[53]='/',
[57]=' ',
};
static const char sk_kbd_shifted[SK_KBD_TABLE_SIZE] = {
[2]='!',[3]='@',[4]='#',[5]='$',[6]='%',[7]='^',[8]='&',[9]='*',[10]='(',[11]=')',
[12]='_',[13]='+',
[16]='Q',[17]='W',[18]='E',[19]='R',[20]='T',[21]='Y',[22]='U',[23]='I',[24]='O',[25]='P',
[26]='{',[27]='}',
[30]='A',[31]='S',[32]='D',[33]='F',[34]='G',[35]='H',[36]='J',[37]='K',[38]='L',
[39]=':',[40]='"',[41]='~',[43]='|',
[44]='Z',[45]='X',[46]='C',[47]='V',[48]='B',[49]='N',[50]='M',
[51]='<',[52]='>',[53]='?',
[57]=' ',
};
#define SK_KEY_BACKSPACE 14u
#define SK_KEY_TAB 15u
#define SK_KEY_ENTER 28u
#define SK_KEY_LEFTSHIFT 42u
#define SK_KEY_RIGHTSHIFT 54u
#define SK_KEY_LEFTALT 56u
#define SK_KEY_RIGHTALT 100u
#define SK_KEY_CAPSLOCK 58u
#define SK_KEY_NUMLOCK 69u
#define SK_KEY_KPENTER 96u
#define SK_KEY_KPSLASH 98u
#define SK_KEY_102ND 86u
static int g_kbd_shift_down; /* zero-initialized (BSS) */
static int g_kbd_alt_down;
/* A full-size keyboard (Captain Bob, 2026-10-14: "include caps lock,
* numlock, keypad and arrow"). Caps Lock and Num Lock are toggles, held
* here for every keyboard alike and shown on a USB keyboard's lights
* (usb_kbd_set_leds()). Num Lock starts on, as a PC's firmware usually
* leaves it. Caps Lock acts on letters only; Shift reverses it. */
static int g_kbd_caps_lock;
static int g_kbd_num_lock = 1;
static void sk_kbd_show_locks(void)
{
usb_kbd_set_leds((uint8_t)((g_kbd_num_lock ? USB_KBD_LED_NUM : 0) |
(g_kbd_caps_lock ? USB_KBD_LED_CAPS : 0)));
}
/* The keypad: what each key types. With Num Lock on, 0-9 and '.'; with it
* off those are Home/arrows/PgUp/... -- navigation, which this REPL's
* append/backspace editing has no use for, so they type nothing (as the
* arrow keys do). / * - + always type; keypad Enter is Enter. */
static int sk_kbd_keypad(uint16_t keycode)
{
switch (keycode) {
case 55u: return '*';
case 74u: return '-';
case 78u: return '+';
case SK_KEY_KPSLASH: return '/';
case SK_KEY_KPENTER: return '\n';
default: break;
}
if (!g_kbd_num_lock) return 0;
switch (keycode) {
case 71u: return '7'; case 72u: return '8'; case 73u: return '9';
case 75u: return '4'; case 76u: return '5'; case 77u: return '6';
case 79u: return '1'; case 80u: return '2'; case 81u: return '3';
case 82u: return '0'; case 83u: return '.';
default: return 0;
}
}
/* Drains and translates one physically-typed key. Modifier state persists
* across calls (a real keyboard's shift/alt state is global, not
* per-line). Alt+TAB is intercepted here and drives the graphics/text
* toggle directly (console_fb_toggle_graphics(), the same state-machine
* transition the ALT+TAB FORTH word calls) -- never reaches the line
* buffer as a character either way. */
static int sk_kbd_getc(void)
{
uint16_t keycode;
int pressed;
while (sk_key_event_poll(&keycode, &pressed)) {
if (keycode == SK_KEY_LEFTSHIFT || keycode == SK_KEY_RIGHTSHIFT) {
g_kbd_shift_down = pressed;
continue;
}
if (keycode == SK_KEY_LEFTALT || keycode == SK_KEY_RIGHTALT) {
g_kbd_alt_down = pressed;
continue;
}
if (!pressed) continue; /* only act on press/repeat */
if (keycode == SK_KEY_CAPSLOCK) { g_kbd_caps_lock = !g_kbd_caps_lock; sk_kbd_show_locks(); continue; }
if (keycode == SK_KEY_NUMLOCK) { g_kbd_num_lock = !g_kbd_num_lock; sk_kbd_show_locks(); continue; }
{
int k = sk_kbd_keypad(keycode);
if (k) return k;
}
if (keycode == SK_KEY_102ND) return g_kbd_shift_down ? '|' : '\\';
if (keycode == SK_KEY_TAB) {
if (g_kbd_alt_down) console_fb_toggle_graphics();
continue; /* bare TAB: not mapped, same as arrows/F-keys */
}
if (keycode == SK_KEY_ENTER) return '\n';
if (keycode == SK_KEY_BACKSPACE) return 0x7F;
if (keycode < SK_KBD_TABLE_SIZE) {
char lower = sk_kbd_unshifted[keycode];
int shift = g_kbd_shift_down;
if (lower >= 'a' && lower <= 'z' && g_kbd_caps_lock) shift = !shift;
char c = shift ? sk_kbd_shifted[keycode] : lower;
if (c) return (unsigned char)c;
}
/* unmapped keycode -- drop and keep draining */
}
return -1;
}
/* One raw byte from either input source (serial console or the keyboard-
* event bridge), non-blocking, -1 if neither has one ready right now. Not
* itself a FORTH word -- the shared byte-fetch underneath sk_console_getkey()/
* sk_console_key_available() (the standard dictionary's KEY/?TERMINAL, wired
* through shim.c's getchar()) and sk_console_readline() (QUERY/EXPECT, wired
* through shim.c's fgets()) alike. */
static uint64_t g_kbd_poll_tick;
static int sk_console_getc_raw(void)
{
/* A USB keyboard's reports come in on the xHCI event ring: while one is
* running, service the ring once per timer tick here, not only on the
* once-a-second idle beat -- keys arrive at typing speed (A3). */
if (xhci_keyboard_running()) {
uint64_t t = heartbeat_ticks();
if (t != g_kbd_poll_tick) {
g_kbd_poll_tick = t;
xhci_poll_events();
}
}
int c = console_getc();
if (c < 0) c = sk_kbd_getc(); /* FABRIC-0.md 4.4v: second source, same buffer */
return c;
}
/* The keyboard bridge -- the key tables, Shift/Alt/Caps Lock/Num Lock, the
* keypad, Alt+TAB and the per-tick USB keyboard servicing -- is the HAL's
* since 2026-10-26 (kernel/src/hal/kbd_input.c, sk_input_getc()), so that
* the v4 boot's console reads the same keyboards (docs/v4.0.0/STICK.md 5.4). */
/* One-byte pushback so sk_console_key_available() can peek without losing
* the byte -- ?TERMINAL must be non-destructive (a caller checks readiness,
@@ -1067,7 +897,7 @@ int sk_console_getkey(VM *active_vm)
c = g_console_pending_key;
g_console_pending_key = -1;
} else {
c = sk_console_getc_raw();
c = sk_input_getc();
}
if (c >= 0) return c;
@@ -1118,7 +948,7 @@ void sk_repl_headless_wait(VM *mama)
int sk_console_key_available(void)
{
if (g_console_pending_key >= 0) return 1;
int c = sk_console_getc_raw();
int c = sk_input_getc();
if (c >= 0) { g_console_pending_key = c; return 1; }
return 0;
}
@@ -1177,7 +1007,7 @@ int sk_console_readline(char* buf, int size, VM* active_vm, int reanchor_prompt)
c = g_console_pending_key;
g_console_pending_key = -1;
} else {
c = sk_console_getc_raw();
c = sk_input_getc();
}
if (c < 0) {
@@ -1336,7 +1166,7 @@ int sk_console_readline(char* buf, int size, VM* active_vm, int reanchor_prompt)
* matches every other non-blocking read in this function. */
{
int pair = (c == '\r') ? '\n' : '\r';
int next = sk_console_getc_raw();
int next = sk_input_getc();
if (next >= 0 && next != pair) g_console_pending_key = next;
}
break;