feat(usb): a USB keyboard -- a full one -- and no hang on a PC without PS/2

boards/BRINGUP.md A3 and S3, FABRIC-3 SXLI.  A HID boot keyboard is
configured (interrupt IN endpoint, SET_PROTOCOL boot) and its reports
become the same Linux-keycode events as PS/2 and virtio (usb_kbd.c): the
full layout -- F-keys, navigation block, arrows, Num Lock, keypad.  The
console treats Caps and Num Lock as toggles shown on the keyboard's
lights, and the keypad as digits or navigation.  While a USB keyboard
runs, its event ring is serviced every tick, not once a second.

Found on the way: with no i8042 (port 0x64 reads 0xFF) the kernel hung at
boot in an endless drain -- a legacy-free PC would stop there.  Detected
and skipped now; drains bounded.

QEMU, amd64 with the USB keyboard the only one: 1 2 + . on main keys and
keypad, Caps Lock, Num Lock all right; aarch64/riscv64 configure it too.
Acceptance on three ISAs unchanged; identities hot-plug.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-14 14:04:33 -04:00
co-authored by Claude Opus 5.5
parent 080b7012ba
commit 739cb9c163
30 changed files with 82072 additions and 14 deletions
+68 -1
View File
@@ -50,6 +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 "word_source/include/block_words.h"
#include "word_registry.h"
#include "freestanding/stdio.h"
@@ -906,10 +907,53 @@ static const char sk_kbd_shifted[SK_KBD_TABLE_SIZE] = {
#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
@@ -932,6 +976,14 @@ static int sk_kbd_getc(void)
}
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 */
@@ -940,7 +992,10 @@ static int sk_kbd_getc(void)
if (keycode == SK_KEY_BACKSPACE) return 0x7F;
if (keycode < SK_KBD_TABLE_SIZE) {
char c = g_kbd_shift_down ? sk_kbd_shifted[keycode] : sk_kbd_unshifted[keycode];
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 */
@@ -954,8 +1009,20 @@ static int sk_kbd_getc(void)
* 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;