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
+2 -2
View File
@@ -25,7 +25,7 @@ None has been fixed: each needs your word.
|---|---|---|---|
| A1 | **Fixed in part 2026-10-14.** The USB controller was matched by QEMU's own IDs (`1B36:000D`) on PCI bus 0 alone. Now: found by its PCI class (`0C/03/30`) on any bus, slot and function; its register window mapped at the size its BAR reports; taken from the firmware through the USB Legacy Support capability (SMIs off); and 64-byte contexts (`CSZ=1`, common on real controllers) laid out instead of refused. **Still open:** only the first controller found is used -- a PC with several (the SER5 has more than one) drives only that one's ports, so a stick or keyboard has to be in one of them. | `kernel/src/usb/xhci.c`, `kernel/src/pci/pci.c`; `FABRIC-3.md` XL | Rehearsed in QEMU with another vendor's controller behind a bridge, all three ISAs. Not rehearsable: the firmware handoff and 64-byte contexts (QEMU's controller has neither). |
| A2 | **No prompt without a login.** The production default, `EMERGENCY_CONSOLE_ENABLED=n`, keeps the system headless until an identity logs in by thumbdrive. | `Kconfig.heartbeat:68`; `FABRIC-3.md` VIII.1 | With A1, the v3 system never shows a prompt on hardware. A bring-up build must set `EMERGENCY_CONSOLE_ENABLED=1`. |
| A3 | **There is no USB keyboard driver.** Keys come from the i8042 PS/2 controller (amd64), a virtio keyboard (riscv64, QEMU only), or the serial port. | `kernel/src/arch/amd64/i8042.c`, `kernel/src/kernel_main.c:773`, `kernel/src/hal/console.c` | A USB keyboard does nothing unless the board's firmware goes on emulating PS/2 after the kernel takes over, which is not known for any of them. Plan to type over serial. |
| A3 | **Fixed 2026-10-14.** A USB boot-protocol keyboard is driven (xHCI interrupt endpoint, `SET_PROTOCOL(boot)`), its reports become the same key events as the PS/2 and virtio keyboards, and the console treats a full keyboard as one: Caps Lock and Num Lock as toggles shown on the keyboard's lights, the keypad as digits or navigation, the arrows and navigation block mapped (the line editor itself still has no cursor movement). While a USB keyboard runs, the console services its reports every timer tick. | `kernel/src/usb/usb_kbd.c`, `kernel/src/usb/xhci.c`, `kernel/src/repl.c`; `FABRIC-3.md` XLI | Rehearsed in QEMU on all three ISAs; on amd64 with the USB keyboard the only keyboard. Limits: the first controller found (A1); a keyboard whose first interface is not the keyboard is not used; no key repeat. |
| A4 | **The only disk driver is virtio.** | `kernel/src/virtio/virtio_blk.c`; `kernel_main.c:493` | On hardware Artemis finds no disk ("Artemis: no virtio-blk disk"). What the fleet then does has not been tried even in QEMU: see step 3.3. |
| A5 | **Entropy:** RDRAND on amd64 only. aarch64 and riscv64 have virtio-rng alone. | `kernel/src/rng/rng.c` | Pi 5 and Mars print the "no entropy backend available" warning; anything that needs random bytes is refused. Minting needs them. |
| A6 | **The board images cannot build the v4 node.** `make boot_image` has no `STARFORTH_V4` path. | root `Makefile`, `kernel/Makefile` | Ruled 2026-10-13: both systems, switchable. Not built yet. |
@@ -36,7 +36,7 @@ None has been fixed: each needs your word.
|---|---|---|---|
| S1 | HPET address fixed at `0xFED00000`, not read from ACPI. | `FABRIC-3.md` III item 6, finding 1 | Wrong only on a machine that puts it elsewhere. Most do not. |
| S2 | Local APIC address fixed at `0xFEE00000`; the MADT's override entry is not parsed. | finding 2 | The same. |
| S3 | The i8042 controller and the legacy PIC are written to with no check that they exist. | finding 3 | Unknown on a machine with neither. |
| S3 | **Half fixed 2026-10-14.** The i8042: a PC without one (port 0x64 reads 0xFF) hung the kernel at boot, in a drain loop that never ended -- found rehearsing A3 with QEMU's `i8042=off`. Now detected and skipped, every drain bounded. Still open: the legacy PIC is written to with no check that it exists. | `kernel/src/arch/amd64/i8042.c`; finding 3; `FABRIC-3.md` XLI | A legacy-free SER5 no longer stops at the I/O APIC. The PIC: unknown on a machine without one. |
| S4 | `BYE` resets through the i8042, not the ACPI reset register. | finding 4 | `BYE` may do nothing. It does not hang. |
| S5 | Whether the SER5 has a serial header is not known. | `FABRIC-3.md` III | Without one, and with A3, the SER5 can be watched over HDMI and not typed to. |
+53
View File
@@ -5770,3 +5770,56 @@ QEMU's controller has neither. **Still open:** one controller only -- the first
several (the SER5 has more than one) drives that one's ports alone; the stick and keyboard must be
in them until the driver runs several (`xhci_get_dev()` is a single controller throughout).
## XLI. A USB keyboard, a full one (`boards/BRINGUP.md` A3) -- and a hang on any PC without PS/2 (S3) (2026-10-14)
Captain Bob, 2026-10-14: "start on A3", then, mid-way: "use this keyboard as a target. for example
include caps lock, numlock, keypad and arrow ... Note for `later`, let's build keyboard VM's"
(recorded as a concept, nothing built).
**Built.**
- **The device** (`xhci.c`): a configuration whose interface is HID / boot / keyboard (3/1/1) is
taken as a keyboard, not refused as "not a drive": its first interrupt IN endpoint is configured
(`xhci_cmd_configure_hid_endpoint()` -- interval converted from bInterval for full/low and high
speed), `SET_CONFIGURATION`, then `SET_PROTOCOL(boot)` (a keyboard starts in report protocol),
then one Normal TRB at a time on its ring; each report is read and the next queued, deferred like
every doorbell in this driver. Unplugging forgets it. "USB keyboard ready" on the console.
- **The keys** (`usb_kbd.c`): a boot report says which keys are held; the difference from the one
before is the presses and releases, modifiers included, ErrorRollOver ignored. HID usage IDs
become Linux keycodes -- the namespace the PS/2 and virtio keyboards already use -- for a full
keyboard: the main block, F1-F12, Print Screen/Scroll Lock/Pause, the navigation block, arrows,
Num Lock and the whole keypad. `sk_key_event_poll()` (`keyboard_words.c`) reads them first, on
every ISA.
- **What a key means** (`repl.c`, every keyboard alike): Caps Lock and Num Lock are toggles (Num
Lock on at start); Caps Lock acts on letters, Shift reverses it; the keypad types `/ * - +` and
Enter always, digits and `.` with Num Lock on, nothing with it off (navigation -- this REPL's
line editor appends and backspaces only, so arrows and Home/End type nothing either); the 102nd
key types `\` `|`. The lock state goes to a USB keyboard's lights by `SET_REPORT` on its next
report.
- **Speed**: one report at a time, serviced only on the once-a-second idle beat, lost keys -- the
keyboard's own queue overflowed at typing speed (found by the test: of a typed line, only `1`
arrived). While a USB keyboard runs, the console now services the event ring on every timer
tick as it waits for a key.
**S3, found on the way -- a hang.** To have the USB keyboard be QEMU's only keyboard on amd64 the
test turned the PS/2 controller off (`-machine i8042=off`), and the kernel hung at boot just after
the I/O APIC: with no controller port 0x64 reads 0xFF, "output full" for ever, and `i8042_init()`'s
drain never ended (nor would `i8042_drain_stale()`'s). A legacy-free PC -- quite possibly the SER5 --
would have stopped there. Now: 0xFF means no controller, "i8042: no PS/2 controller -- keyboard by
USB only", nothing more done to it; every drain bounded. With a controller, unchanged.
**Verified.** amd64 with the USB keyboard the only keyboard, typed over QMP (`logs/20261014-135243`):
`1 2 + .` on the main keys and again on the keypad with keypad Enter -- `3` both times; Caps Lock,
then `s" ab" type` typed in lower case -- the line reads `S" AB" TYPE` and prints `AB`; Num Lock
off, keypad 7, Enter -- nothing typed. aarch64 and riscv64 (`-135420`, `-135629`): "USB keyboard
ready" and the same three results -- there QEMU also has a virtio keyboard, so those runs show the
USB keyboard configured, not which keyboard each key came through. The hang and its fix:
`-134410` (stops after the I/O APIC), `-135022` (boots). Standard acceptance, all three ISAs: POST
1052 passed, hash unchanged, PS/2 initialised as before on amd64 (`-135808`, `-135918`, `-140123`);
five identities hot-plugged, all attached (`-140240`). Test first? No: this time the test was
written after the code, its "before" being no keyboard support at all (`-134213`, where QMP could
not reach the device, records the enumeration working).
**Limits, not hidden:** the first xHCI controller only (§XL); a keyboard whose first interface is
not its keyboard is not used; no key repeat (a boot keyboard does not repeat, and nothing here
does it for it); the lights are sent on the next report after a lock key, not at once.
+44
View File
@@ -0,0 +1,44 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2026 Robert A. James. All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* usb_kbd.h - a USB boot-protocol keyboard's keys, as key events
* (2026-10-14, boards/BRINGUP.md A3; usb_kbd.c).
*
* The xHCI driver hands each 8-byte boot report here; the keys pressed
* and released since the report before come out as Linux input keycodes,
* the namespace every keyboard in this kernel already speaks
* (sk_key_event_poll(), keyboard_words.c).
*/
#ifndef STARKERNEL_USB_KBD_H
#define STARKERNEL_USB_KBD_H
#include <stdint.h>
/* One boot report: byte 0 the modifier bits, byte 1 reserved, bytes 2-7
* up to six keys held (HID usage IDs). `len` below 3 is ignored. */
void usb_kbd_report(const uint8_t *report, uint32_t len);
/* Forgets the keys held: the keyboard has gone. */
void usb_kbd_reset(void);
/* The next key event: 1 with *keycode (Linux) and *pressed (1/0), or 0. */
int usb_kbd_pop_event(uint16_t *keycode, int *pressed);
/* The lock lights (boot output report bits, HID 1.11 appendix B.1). The
* console says which locks are on; the xHCI driver sends them to the
* keyboard with SET_REPORT whenever they differ from what it last sent. */
#define USB_KBD_LED_NUM 0x01u
#define USB_KBD_LED_CAPS 0x02u
#define USB_KBD_LED_SCROLL 0x04u
void usb_kbd_set_leds(uint8_t leds);
/* 1 and the lights to send if they changed since last sent, else 0. */
int usb_kbd_leds_pending(uint8_t *leds);
/* The driver has sent these. */
void usb_kbd_leds_sent(uint8_t leds);
#endif /* STARKERNEL_USB_KBD_H */
+16
View File
@@ -279,6 +279,14 @@ typedef struct {
#define USB_CLASS_MASS_STORAGE 0x08u
#define USB_SUBCLASS_SCSI 0x06u /* SCSI transparent command set */
#define USB_PROTOCOL_BOT 0x50u /* Bulk-Only Transport */
#define USB_IFACE_OFF_NUMBER 2u /* bInterfaceNumber */
/* A boot-protocol keyboard (HID 1.11 section 4): the class a BIOS can use,
* and what this driver reads keys from (2026-10-14, boards/BRINGUP.md A3). */
#define USB_CLASS_HID 0x03u
#define USB_SUBCLASS_HID_BOOT 0x01u
#define USB_PROTOCOL_HID_KEYBOARD 0x01u
#define USB_HID_REQ_SET_PROTOCOL 0x0Bu /* class, interface recipient */
#define USB_HID_PROTOCOL_BOOT 0u
/* Standard USB Endpoint descriptor field offsets (7 bytes, USB 2.0 spec
* table 9-13) -- found the same way as the Interface descriptor: walked
@@ -292,6 +300,8 @@ typedef struct {
#define USB_EP_ADDR_NUM_MASK 0x0Fu
#define USB_EP_ATTR_TYPE_MASK 0x03u
#define USB_EP_TYPE_BULK 0x02u
#define USB_EP_TYPE_INTERRUPT 0x03u
#define USB_EP_OFF_INTERVAL 6u /* bInterval */
/* Bulk-Only Transport Command Block Wrapper (USB Mass Storage Class Bulk-
* Only Transport spec, section 5.1) -- sent host-to-device on the bulk OUT
@@ -400,6 +410,10 @@ typedef struct {
#define XHCI_EVT_SLOT_ID(control) (((uint32_t)(control) >> 24) & 0xFFu)
#define XHCI_COMPLETION_CODE_SUCCESS 1u
#define XHCI_COMPLETION_CODE_STALL_ERROR 6u
#define XHCI_COMPLETION_CODE_SHORT_PACKET 13u
/* Transfer Event: the Endpoint ID (DCI) the transfer was on, bits 20:16 of
* the control word (xHCI 1.2 table 6-38). */
#define XHCI_EVT_EP_ID(control) (((uint32_t)(control) >> 16) & 0x1Fu)
/* Port Status Change Event TRB layout (xHCI 1.2 spec table 6-34):
* parameter[31:24] = Port ID (1-based, matches PORTSC array indexing
@@ -440,6 +454,8 @@ typedef struct {
#define XHCI_EP_CTX_TYPE_CONTROL_BIDI 4u /* EP0 */
#define XHCI_EP_CTX_TYPE_BULK_OUT 2u /* xHCI 1.2 spec table 6-9 */
#define XHCI_EP_CTX_TYPE_BULK_IN 6u
#define XHCI_EP_CTX_TYPE_INTERRUPT_IN 7u
#define XHCI_EP_CTX_INTERVAL_SHIFT 16 /* dword0: period 2^Interval x 125 us */
#define XHCI_EP_CTX_CERR_SHIFT 1
#define XHCI_EP_CTX_MAX_PACKET_SHIFT 16
+30 -2
View File
@@ -61,7 +61,9 @@ typedef enum {
XHCI_XFER_BOT_DATA_OUT,
XHCI_XFER_CSW_RECEIVED,
XHCI_XFER_CLEAR_HALT,
XHCI_XFER_BOT_RESET
XHCI_XFER_BOT_RESET,
XHCI_XFER_HID_SET_PROTOCOL,
XHCI_XFER_HID_SET_LEDS
} xhci_transfer_purpose_t;
typedef enum {
@@ -78,7 +80,9 @@ typedef enum {
XHCI_NEXT_ACTION_BOT_SEND_READ_CAPACITY10,
XHCI_NEXT_ACTION_BOT_SEND_WRITE10,
XHCI_NEXT_ACTION_CLEAR_HALT,
XHCI_NEXT_ACTION_BOT_RESET
XHCI_NEXT_ACTION_BOT_RESET,
XHCI_NEXT_ACTION_HID_SET_PROTOCOL,
XHCI_NEXT_ACTION_HID_POLL
} xhci_next_action_t;
/* FABRIC-3.md §VII (2026-09-05): per-slot device state, formerly scalar
@@ -129,6 +133,21 @@ typedef struct {
* per attached device now (was a single flag per kind, one device
* assumed) -- sk_repl_idle() scans every slot for these each idle
* tick, cheap and bounded by max_slots. */
/* A boot-protocol keyboard (2026-10-14, boards/BRINGUP.md A3): its
* interrupt IN endpoint, a ring for it, the 8-byte report buffer one
* Normal TRB at a time reads into, and the report before it (keys are
* found pressed or released by comparing the two -- usb_kbd.c). */
uint8_t hid_kbd;
uint8_t hid_running;
uint8_t hid_iface;
uint8_t hid_ep_addr;
uint16_t hid_max_packet;
uint8_t hid_interval;
xhci_trb_t *hid_ring;
uint32_t hid_ring_cycle;
uint32_t hid_ring_enq;
uint8_t *hid_buf;
uint8_t bot_msc_attach_pending;
uint8_t bot_msc_attached;
uint8_t bot_msc_detach_pending;
@@ -583,6 +602,15 @@ int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
*/
int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id);
/* A boot keyboard (2026-10-14, boards/BRINGUP.md A3): Configure Endpoint
* for its one interrupt IN endpoint, and SET_PROTOCOL(boot) on EP0 once
* it is configured. Deferred dispatch only, like the rest. */
int xhci_cmd_configure_hid_endpoint(xhci_dev_t *dev, uint32_t slot_id);
int xhci_ep0_hid_set_protocol(xhci_dev_t *dev, uint32_t slot_id);
/* 1 while a boot keyboard is running: the console then services the event
* ring every timer tick while it waits for a key. */
int xhci_keyboard_running(void);
/*
* xhci_cmd_reset_endpoint — submit a Reset Endpoint command TRB for
* slot_id's endpoint `ep_id` (a bEndpointAddress,
+24 -6
View File
@@ -62,13 +62,33 @@ static inline uint8_t inb(uint16_t port) {
return val;
}
/* Is there a controller at all? A PC without one -- "legacy-free", common
* now, the keyboard on USB alone -- answers port 0x64 with 0xFF, every bit
* set, "output full" included, for ever: until 2026-10-14 the drains below
* then never ended, and the kernel hung right here (boards/BRINGUP.md S3,
* found rehearsing A3 with QEMU's i8042=off). */
static int g_i8042_present;
/* Read and drop what the controller holds -- a bounded number of bytes:
* a real controller holds at most a few. */
static void i8042_drain(void) {
int i;
for (i = 0; i < 64 && (inb(I8042_STATUS_PORT) & I8042_STATUS_OUTPUT_FULL); i++) {
(void)inb(I8042_DATA_PORT);
}
}
void i8042_init(void) {
if (inb(I8042_STATUS_PORT) == 0xFF) {
console_println("i8042: no PS/2 controller -- keyboard by USB only");
return;
}
g_i8042_present = 1;
/* Drain any stale output byte left by firmware before enabling IRQ1 —
* otherwise the first "interrupt" would really be this leftover byte
* read via polling, not a real IRQ1 delivery. */
while (inb(I8042_STATUS_PORT) & I8042_STATUS_OUTPUT_FULL) {
(void)inb(I8042_DATA_PORT);
}
i8042_drain();
outb(I8042_CMD_PORT, I8042_CMD_READ_CONFIG);
uint8_t config = inb(I8042_DATA_PORT);
@@ -110,9 +130,7 @@ void i8042_drain_stale(void) {
* edge ever again (the textbook "fires once, then never" pattern).
* i8042_init()'s drain happens at M4; VM bootstrap runs for real wall
* time between then and unmask, so re-drain immediately before. */
while (inb(I8042_STATUS_PORT) & I8042_STATUS_OUTPUT_FULL) {
(void)inb(I8042_DATA_PORT);
}
if (g_i8042_present) i8042_drain();
}
/* Ring buffer: single producer (the ISR), single reader (i8042_pop_scancode(),
+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;
+141
View File
@@ -0,0 +1,141 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2026 Robert A. James. All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* usb_kbd.c - a USB boot-protocol keyboard's reports, as key events
* (2026-10-14, boards/BRINGUP.md A3). See usb_kbd.h.
*
* A boot report says which keys are held now, not what changed: what was
* held in the report before and is not now has been released, and the
* other way round has been pressed. The eight modifier bits are keys too
* (left/right Ctrl, Shift, Alt, GUI). A report whose keys are all 0x01 is
* the keyboard's "too many keys" (ErrorRollOver) and changes nothing.
*
* Usage IDs (HID Usage Tables 1.12, page 7) become Linux keycodes
* (input-event-codes.h) -- the same numbers the PS/2 and virtio keyboards
* already give sk_key_event_poll(). Only the keys a console uses are
* mapped; others are dropped. The keyboard does not repeat a held key:
* held is held, and nothing here repeats it either.
*/
#include "starkernel/usb_kbd.h"
/* HID usage ID -> Linux keycode; 0 = not mapped. A full-size keyboard:
* the main block, F1-F12, the navigation block, arrows and the keypad
* (Captain Bob, 2026-10-14: "include caps lock, numlock, keypad and
* arrow"). What a key means -- Caps and Num Lock as toggles, the keypad
* as digits or navigation -- is the console's (repl.c), for every
* keyboard alike. */
static const uint8_t usage_to_key[0x66] = {
[0x04] = 30, [0x05] = 48, [0x06] = 46, [0x07] = 32, [0x08] = 18, /* a b c d e */
[0x09] = 33, [0x0A] = 34, [0x0B] = 35, [0x0C] = 23, [0x0D] = 36, /* f g h i j */
[0x0E] = 37, [0x0F] = 38, [0x10] = 50, [0x11] = 49, [0x12] = 24, /* k l m n o */
[0x13] = 25, [0x14] = 16, [0x15] = 19, [0x16] = 31, [0x17] = 20, /* p q r s t */
[0x18] = 22, [0x19] = 47, [0x1A] = 17, [0x1B] = 45, [0x1C] = 21, /* u v w x y */
[0x1D] = 44, /* z */
[0x1E] = 2, [0x1F] = 3, [0x20] = 4, [0x21] = 5, [0x22] = 6, /* 1 2 3 4 5 */
[0x23] = 7, [0x24] = 8, [0x25] = 9, [0x26] = 10, [0x27] = 11, /* 6 7 8 9 0 */
[0x28] = 28, [0x29] = 1, [0x2A] = 14, [0x2B] = 15, [0x2C] = 57, /* enter esc bksp tab space */
[0x2D] = 12, [0x2E] = 13, [0x2F] = 26, [0x30] = 27, [0x31] = 43, /* - = [ ] \ */
[0x32] = 43, [0x33] = 39, [0x34] = 40, [0x35] = 41, [0x36] = 51, /* non-US # ; ' ` , */
[0x37] = 52, [0x38] = 53, [0x39] = 58, /* . / capslock */
[0x3A] = 59, [0x3B] = 60, [0x3C] = 61, [0x3D] = 62, [0x3E] = 63, /* F1-F5 */
[0x3F] = 64, [0x40] = 65, [0x41] = 66, [0x42] = 67, [0x43] = 68, /* F6-F10 */
[0x44] = 87, [0x45] = 88, /* F11 F12 */
[0x46] = 99, [0x47] = 70, [0x48] = 119, /* printscreen scrolllock pause */
[0x49] = 110, [0x4A] = 102, [0x4B] = 104, /* insert home pageup */
[0x4C] = 111, [0x4D] = 107, [0x4E] = 109, /* delete end pagedown */
[0x4F] = 106, [0x50] = 105, [0x51] = 108, [0x52] = 103, /* right left down up */
[0x53] = 69, /* numlock */
[0x54] = 98, [0x55] = 55, [0x56] = 74, [0x57] = 78, [0x58] = 96, /* kp/ kp* kp- kp+ kpenter */
[0x59] = 79, [0x5A] = 80, [0x5B] = 81, [0x5C] = 75, [0x5D] = 76, /* kp1 kp2 kp3 kp4 kp5 */
[0x5E] = 77, [0x5F] = 71, [0x60] = 72, [0x61] = 73, [0x62] = 82, /* kp6 kp7 kp8 kp9 kp0 */
[0x63] = 83, [0x64] = 86, [0x65] = 127, /* kp. non-US\ compose(menu) */
};
/* Modifier bit n -> Linux keycode: LCtrl LShift LAlt LGUI RCtrl RShift RAlt RGUI. */
static const uint8_t modifier_key[8] = { 29, 42, 56, 125, 97, 54, 100, 126 };
#define KBD_EVENTS 64u
static struct { uint16_t keycode; uint8_t pressed; } g_events[KBD_EVENTS];
static uint32_t g_head, g_count;
static uint8_t g_prev[8];
/* Num Lock on to start, matching the console (repl.c); 0xFF = nothing
* sent yet, so a new keyboard is always told. */
static uint8_t g_leds_want = USB_KBD_LED_NUM;
static uint8_t g_leds_sent = 0xFFu;
static void push(uint16_t keycode, int pressed)
{
if (keycode == 0 || g_count == KBD_EVENTS) return; /* a full queue drops: nothing reads it */
g_events[(g_head + g_count) % KBD_EVENTS].keycode = keycode;
g_events[(g_head + g_count) % KBD_EVENTS].pressed = (uint8_t)(pressed != 0);
g_count++;
}
static uint16_t key_for(uint8_t usage)
{
return usage < sizeof usage_to_key ? usage_to_key[usage] : 0;
}
static int held(const uint8_t *keys, uint32_t n, uint8_t usage)
{
uint32_t i;
for (i = 0; i < n; i++)
if (keys[i] == usage) return 1;
return 0;
}
void usb_kbd_report(const uint8_t *report, uint32_t len)
{
uint8_t now[8] = {0};
uint32_t i, n;
if (!report || len < 3) return;
if (len > 8) len = 8;
for (i = 0; i < len; i++) now[i] = report[i];
if (now[2] == 0x01) return; /* ErrorRollOver: nothing known */
for (i = 0; i < 8; i++) {
uint8_t bit = (uint8_t)(1u << i);
if ((now[0] & bit) != (g_prev[0] & bit)) push(modifier_key[i], (now[0] & bit) != 0);
}
n = 6;
for (i = 2; i < 8; i++) /* released first, then pressed */
if (g_prev[i] > 0x01 && !held(now + 2, n, g_prev[i])) push(key_for(g_prev[i]), 0);
for (i = 2; i < 8; i++)
if (now[i] > 0x01 && !held(g_prev + 2, n, now[i])) push(key_for(now[i]), 1);
for (i = 0; i < 8; i++) g_prev[i] = now[i];
}
void usb_kbd_reset(void)
{
uint32_t i;
for (i = 0; i < 8; i++) g_prev[i] = 0;
g_leds_sent = 0xFFu;
}
void usb_kbd_set_leds(uint8_t leds) { g_leds_want = leds; }
int usb_kbd_leds_pending(uint8_t *leds)
{
if (g_leds_want == g_leds_sent) return 0;
*leds = g_leds_want;
return 1;
}
void usb_kbd_leds_sent(uint8_t leds) { g_leds_sent = leds; }
int usb_kbd_pop_event(uint16_t *keycode, int *pressed)
{
if (g_count == 0) return 0;
*keycode = g_events[g_head].keycode;
*pressed = g_events[g_head].pressed;
g_head = (g_head + 1u) % KBD_EVENTS;
g_count--;
return 1;
}
+274 -3
View File
@@ -22,6 +22,8 @@
#include "starkernel/pci.h"
#include "starkernel/xhci.h"
#include "starkernel/xhci_driver.h"
#include "starkernel/usb_kbd.h" /* A3: a boot keyboard's reports and lights */
#include "starkernel/console.h" /* A3: "USB keyboard ready" */
#include "starkernel/kmalloc.h"
#include "starkernel/timer.h"
#include "starkernel/arch.h"
@@ -562,6 +564,9 @@ int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
if (!ms->ep0_ring) return -1;
}
ms->in_use = 1;
ms->hid_kbd = 0; /* a new device in this slot: not (yet) a keyboard */
ms->hid_running = 0;
ms->hid_ep_addr = 0;
/* EP0 Transfer Ring: same fixed-ring-plus-Link-TRB pattern as the
* Command Ring (xhci_bringup()'s own comment on why). Freshly
@@ -751,6 +756,86 @@ int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id)
return 0;
}
/* A boot keyboard's Configure Endpoint (2026-10-14, boards/BRINGUP.md A3):
* its one interrupt IN endpoint, in the same shape as the bulk pair above
* -- Device Context grown and EP0 kept, Input Context spanning every DCI
* up to the new one, Slot Context copied with Context Entries raised. The
* interval is the endpoint's bInterval in xHCI's terms (section 6.2.3.6):
* frames (ms) for full and low speed, given as the exponent of a period
* of 2^n x 125 us; already an exponent minus one at high speed and up. */
int xhci_cmd_configure_hid_endpoint(xhci_dev_t *dev, uint32_t slot_id)
{
xhci_msc_slot_t *ms = xhci_msc_slot_for(dev, slot_id);
if (!dev || !dev->cmd_ring || !ms || !ms->input_ctx || !ms->device_ctx) return -1;
if (ms->hid_ep_addr == 0) return -1;
const size_t cs = xhci_ctx_bytes(dev);
if (!ms->hid_ring) {
ms->hid_ring = (xhci_trb_t *)kmalloc_aligned(XHCI_RING_BYTES, 64);
if (!ms->hid_ring) return -1;
}
if (!ms->hid_buf) {
ms->hid_buf = (uint8_t *)kmalloc_aligned(128, 64); /* report [0,64), lights at 64 */
if (!ms->hid_buf) return -1;
}
xhci_init_bulk_ring(ms->hid_ring);
ms->hid_ring_cycle = 1;
ms->hid_ring_enq = 0;
uint32_t dci = XHCI_EP_ADDR_TO_DCI(ms->hid_ep_addr);
size_t old_bytes = 2 * cs;
size_t new_bytes = (1 + (size_t)dci) * cs;
uint8_t *ndctx = (uint8_t *)kmalloc_aligned(new_bytes, 64);
if (!ndctx) return -1;
for (size_t i = 0; i < new_bytes; i++) ndctx[i] = 0;
for (size_t i = 0; i < old_bytes; i++) ndctx[i] = ((const uint8_t *)ms->device_ctx)[i];
ms->device_ctx = ndctx;
((uint64_t *)dev->dcbaa)[slot_id] = (uint64_t)(uintptr_t)ms->device_ctx;
size_t total = cs + (1 + (size_t)dci) * cs;
uint8_t *ictx = (uint8_t *)kmalloc_aligned(total, 64);
if (!ictx) return -1;
for (size_t i = 0; i < total; i++) ictx[i] = 0;
ms->input_ctx = ictx;
xhci_input_ctrl_ctx32_t *ctrl = (xhci_input_ctrl_ctx32_t *)ictx;
ctrl->add_flags = XHCI_INPUT_CTRL_ADD_SLOT | (1u << dci);
xhci_slot_ctx32_t *dev_slot = (xhci_slot_ctx32_t *)ms->device_ctx;
xhci_slot_ctx32_t *in_slot = (xhci_slot_ctx32_t *)(ictx + cs);
uint32_t entries_mask = ~((uint32_t)0x1Fu << XHCI_SLOT_CTX_CONTEXT_ENTRIES_SHIFT);
in_slot->dword0 = (dev_slot->dword0 & entries_mask) | (dci << XHCI_SLOT_CTX_CONTEXT_ENTRIES_SHIFT);
in_slot->dword1 = dev_slot->dword1;
in_slot->dword2 = dev_slot->dword2;
uint32_t speed = (dev_slot->dword0 >> XHCI_SLOT_CTX_SPEED_SHIFT) & 0xFu;
uint32_t interval;
if (speed >= 3) { /* high, super */
interval = ms->hid_interval ? (uint32_t)ms->hid_interval - 1u : 0u;
if (interval > 15) interval = 15;
} else { /* full, low: frames */
uint32_t v = (ms->hid_interval ? ms->hid_interval : 1u) * 8u;
interval = 0;
while ((1u << (interval + 1)) <= v) interval++;
if (interval < 3) interval = 3;
if (interval > 10) interval = 10;
}
xhci_ep_ctx32_t *ep = (xhci_ep_ctx32_t *)(ictx + cs + (size_t)dci * cs);
ep->dword0 = interval << XHCI_EP_CTX_INTERVAL_SHIFT;
ep->dword1 = (3u << XHCI_EP_CTX_CERR_SHIFT) |
(XHCI_EP_CTX_TYPE_INTERRUPT_IN << XHCI_EP_CTX_TYPE_SHIFT) |
((uint32_t)ms->hid_max_packet << XHCI_EP_CTX_MAX_PACKET_SHIFT);
ep->tr_dequeue_ptr = ((uint64_t)(uintptr_t)ms->hid_ring) | 1u; /* DCS = 1 */
ep->dword4 = 8u | ((uint32_t)ms->hid_max_packet << 16); /* avg TRB length, max ESIT payload */
xhci_submit_command(dev, (uint64_t)(uintptr_t)ms->input_ctx, 0,
XHCI_TRB_TYPE_CONFIGURE_ENDPOINT_CMD, slot_id << 24);
log_message(LOG_DEBUG, "xhci: keyboard configure endpoint command submitted");
return 0;
}
/* G.1 / §F.14 stall recovery: Reset Endpoint command. ep_id is a full
* bEndpointAddress (bit 7 = direction) of the stalled bulk endpoint -- the
* xHC is told which endpoint to un-halt via its Endpoint ID (the DCI) in
@@ -1362,6 +1447,96 @@ int xhci_ep0_bot_mass_storage_reset(xhci_dev_t *dev, uint32_t slot_id)
return 0;
}
/* A boot keyboard's class requests (HID 1.11 section 7.2), on EP0, to its
* interface: SET_PROTOCOL(boot) -- a keyboard starts in report protocol,
* whose reports need not be the 8-byte boot ones -- and SET_REPORT for the
* lock lights (one output byte). (2026-10-14, boards/BRINGUP.md A3.) */
int xhci_ep0_hid_set_protocol(xhci_dev_t *dev, uint32_t slot_id)
{
xhci_msc_slot_t *ms = xhci_msc_slot_for(dev, slot_id);
if (!dev || !ms || !ms->ep0_ring) return -1;
usb_setup_packet_t setup = {
.bmRequestType = (USB_REQ_TYPE_CLASS << 5) | USB_RECIP_INTERFACE,
.bRequest = USB_HID_REQ_SET_PROTOCOL,
.wValue = USB_HID_PROTOCOL_BOOT,
.wIndex = ms->hid_iface,
.wLength = 0
};
uint64_t setup_bits;
memcpy(&setup_bits, &setup, sizeof(setup_bits));
xhci_ep0_enqueue_trb(dev, slot_id, setup_bits, 8u,
(XHCI_TRB_TYPE_SETUP_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT) |
XHCI_TRB_CONTROL_IDT |
(XHCI_SETUP_TRT_NO_DATA << XHCI_TRB_CONTROL_TRT_SHIFT));
xhci_ep0_enqueue_trb(dev, slot_id, 0, 0,
(XHCI_TRB_TYPE_STATUS_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT) |
XHCI_TRB_CONTROL_IOC |
XHCI_TRB_CONTROL_DIR_IN);
ms->transfer_purpose = XHCI_XFER_HID_SET_PROTOCOL;
dev->doorbell[slot_id] = XHCI_DB_TARGET(1);
log_message(LOG_DEBUG, "xhci: keyboard set protocol (boot) submitted");
return 0;
}
#define USB_HID_REQ_SET_REPORT 0x09u
#define USB_HID_REPORT_OUTPUT 0x0200u /* wValue: report type Output (2), report ID 0 */
static int xhci_ep0_hid_set_leds(xhci_dev_t *dev, uint32_t slot_id, uint8_t leds)
{
xhci_msc_slot_t *ms = xhci_msc_slot_for(dev, slot_id);
if (!dev || !ms || !ms->ep0_ring || !ms->hid_buf) return -1;
ms->hid_buf[64] = leds;
usb_setup_packet_t setup = {
.bmRequestType = (USB_REQ_TYPE_CLASS << 5) | USB_RECIP_INTERFACE,
.bRequest = USB_HID_REQ_SET_REPORT,
.wValue = USB_HID_REPORT_OUTPUT,
.wIndex = ms->hid_iface,
.wLength = 1
};
uint64_t setup_bits;
memcpy(&setup_bits, &setup, sizeof(setup_bits));
xhci_ep0_enqueue_trb(dev, slot_id, setup_bits, 8u,
(XHCI_TRB_TYPE_SETUP_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT) |
XHCI_TRB_CONTROL_IDT |
(XHCI_SETUP_TRT_OUT_DATA << XHCI_TRB_CONTROL_TRT_SHIFT));
xhci_ep0_enqueue_trb(dev, slot_id, (uint64_t)(uintptr_t)&ms->hid_buf[64], 1u,
(XHCI_TRB_TYPE_DATA_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT));
xhci_ep0_enqueue_trb(dev, slot_id, 0, 0,
(XHCI_TRB_TYPE_STATUS_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT) |
XHCI_TRB_CONTROL_IOC |
XHCI_TRB_CONTROL_DIR_IN);
ms->transfer_purpose = XHCI_XFER_HID_SET_LEDS;
usb_kbd_leds_sent(leds); /* once asked: a keyboard that refuses is not asked again and again */
dev->doorbell[slot_id] = XHCI_DB_TARGET(1);
return 0;
}
/* One Normal TRB on the keyboard's interrupt ring: the next report lands in
* hid_buf. One at a time -- queued again only once the last has been read. */
static void xhci_hid_queue_report(xhci_dev_t *dev, uint32_t slot_id, xhci_msc_slot_t *ms)
{
uint32_t len = ms->hid_max_packet ? ms->hid_max_packet : 8u;
if (len > 64u) len = 64u;
xhci_trb_t *trb = &ms->hid_ring[ms->hid_ring_enq];
trb->parameter = (uint64_t)(uintptr_t)ms->hid_buf;
trb->status = len;
trb->control = (XHCI_TRB_TYPE_NORMAL << XHCI_TRB_CONTROL_TYPE_SHIFT) | XHCI_TRB_CONTROL_IOC |
(ms->hid_ring_cycle ? XHCI_TRB_CONTROL_CYCLE : 0);
ms->hid_ring_enq++;
if (ms->hid_ring_enq == XHCI_RING_TRB_COUNT - 1) {
ms->hid_ring[XHCI_RING_TRB_COUNT - 1].control =
(XHCI_TRB_TYPE_LINK << XHCI_TRB_CONTROL_TYPE_SHIFT) | XHCI_TRB_CONTROL_TC |
(ms->hid_ring_cycle ? XHCI_TRB_CONTROL_CYCLE : 0);
ms->hid_ring_enq = 0;
ms->hid_ring_cycle ^= 1u;
}
dev->doorbell[slot_id] = XHCI_DB_TARGET(XHCI_EP_ADDR_TO_DCI(ms->hid_ep_addr));
}
/* -------------------------------------------------------------------------
* Milestone 2d: Event Ring servicing, polled from sk_repl_idle().
*
@@ -1676,6 +1851,14 @@ static void xhci_handle_bulk_stall(xhci_dev_t *dev, uint32_t slot_id,
}
}
/* How many boot keyboards are running (A3). While any is, the console
* services this event ring on every timer tick as it waits for a key
* (repl.c), not only on its once-a-second idle beat: one report at a time
* is queued, so at one a second fast typing overflowed the keyboard's own
* short queue and keys were lost (2026-10-14). */
static uint32_t g_xhci_keyboards;
int xhci_keyboard_running(void) { return g_xhci_keyboards != 0; }
void xhci_poll_events(void)
{
xhci_dev_t *dev = g_xhci_dev;
@@ -1820,7 +2003,13 @@ void xhci_poll_events(void)
* single-device fields), only in_use clears. */
{
xhci_msc_slot_t *dms2 = xhci_msc_slot_for(dev, dev->pending_disable_slot_id);
if (dms2) dms2->in_use = 0;
if (dms2) {
if (dms2->hid_kbd) usb_kbd_reset(); /* A3: no key stays held */
if (dms2->hid_running && g_xhci_keyboards) g_xhci_keyboards--;
dms2->hid_kbd = 0;
dms2->hid_running = 0;
dms2->in_use = 0;
}
}
log_message(LOG_DEBUG, "xhci: disable slot succeeded");
} else {
@@ -1936,6 +2125,24 @@ void xhci_poll_events(void)
* transfer is independent. */
uint32_t xfer_slot_id = XHCI_EVT_SLOT_ID(trb->control);
xhci_msc_slot_t *xms = xhci_msc_slot_for(dev, xfer_slot_id);
/* A boot keyboard's report, on its interrupt endpoint (A3):
* read it, and queue for the next -- deferred, like every
* doorbell here, until ERDP is updated below. */
if (xms && xms->hid_running && xms->hid_ep_addr &&
XHCI_EVT_EP_ID(trb->control) == XHCI_EP_ADDR_TO_DCI(xms->hid_ep_addr)) {
if (code == XHCI_COMPLETION_CODE_SUCCESS || code == XHCI_COMPLETION_CODE_SHORT_PACKET) {
uint32_t asked = xms->hid_max_packet ? xms->hid_max_packet : 8u;
uint32_t left = trb->status & 0xFFFFFFu;
if (asked > 64u) asked = 64u;
usb_kbd_report(xms->hid_buf, left < asked ? asked - left : 0u);
xms->next_action = XHCI_NEXT_ACTION_HID_POLL;
} else {
console_println("USB keyboard: report failed -- keyboard stopped");
xms->hid_running = 0;
if (g_xhci_keyboards) g_xhci_keyboards--;
}
break;
}
if (xms && xms->transfer_purpose != XHCI_XFER_NONE) {
uint32_t purpose = xms->transfer_purpose;
xms->transfer_purpose = XHCI_XFER_NONE;
@@ -2085,8 +2292,44 @@ void xhci_poll_events(void)
} else {
xms->next_action = XHCI_NEXT_ACTION_CONFIGURE_ENDPOINT;
}
} else if (iface_class == USB_CLASS_HID &&
iface_subclass == USB_SUBCLASS_HID_BOOT &&
iface_protocol == USB_PROTOCOL_HID_KEYBOARD) {
/* A boot keyboard (2026-10-14, A3): its
* interface number, and the first
* interrupt IN endpoint after it. */
uint16_t ep_off = (uint16_t)(off + desc_len);
xms->hid_iface = xms->config_descriptor[off + USB_IFACE_OFF_NUMBER];
while (ep_off + 2 <= len) {
uint8_t ep_desc_len = xms->config_descriptor[ep_off + USB_DESC_OFF_LENGTH];
uint8_t ep_desc_type = xms->config_descriptor[ep_off + USB_DESC_OFF_TYPE];
if (ep_desc_len == 0) break;
if (ep_desc_type == USB_DESC_TYPE_INTERFACE) break;
if (ep_desc_type == USB_DESC_TYPE_ENDPOINT &&
ep_off + USB_EP_OFF_INTERVAL < len) {
uint8_t ep_addr = xms->config_descriptor[ep_off + USB_EP_OFF_ADDRESS];
uint8_t ep_attr = xms->config_descriptor[ep_off + USB_EP_OFF_ATTRIBUTES];
if ((ep_attr & USB_EP_ATTR_TYPE_MASK) == USB_EP_TYPE_INTERRUPT &&
(ep_addr & USB_EP_ADDR_DIR_MASK)) {
xms->hid_ep_addr = ep_addr;
xms->hid_max_packet = (uint16_t)(xms->config_descriptor[ep_off + USB_EP_OFF_MAX_PACKET_SIZE] |
((uint16_t)xms->config_descriptor[ep_off + USB_EP_OFF_MAX_PACKET_SIZE + 1] << 8)) & 0x7FFu;
xms->hid_interval = xms->config_descriptor[ep_off + USB_EP_OFF_INTERVAL];
break;
}
}
ep_off = (uint16_t)(ep_off + ep_desc_len);
}
xms->next_action_config_value =
xms->config_descriptor[USB_CONFIG_OFF_CONFIG_VALUE];
if (xms->hid_ep_addr) {
xms->hid_kbd = 1;
xms->next_action = XHCI_NEXT_ACTION_CONFIGURE_ENDPOINT;
} else {
log_message(LOG_WARN, "xhci: keyboard without an interrupt IN endpoint -- not used");
}
} else {
log_message(LOG_WARN, "xhci: not a Mass Storage/SCSI/BOT device -- not usable as a drive");
log_message(LOG_WARN, "xhci: neither a Mass Storage/SCSI/BOT drive nor a boot keyboard -- not used");
}
found = 1;
break;
@@ -2100,6 +2343,10 @@ void xhci_poll_events(void)
}
case XHCI_XFER_SET_CONFIG: {
log_message(LOG_DEBUG, "xhci: device configured");
if (xms->hid_kbd) { /* A3: a keyboard, not a drive */
xms->next_action = XHCI_NEXT_ACTION_HID_SET_PROTOCOL;
break;
}
/* Milestone 2h: hand off to sk_repl_idle(),
* the only safe place to run the synchronous
* capacity query + block-subsystem attach --
@@ -2111,6 +2358,16 @@ void xhci_poll_events(void)
xms->bot_msc_attach_pending = 1;
break;
}
case XHCI_XFER_HID_SET_PROTOCOL: {
xms->hid_running = 1;
g_xhci_keyboards++;
usb_kbd_reset();
console_println("USB keyboard ready");
xms->next_action = XHCI_NEXT_ACTION_HID_POLL;
break;
}
case XHCI_XFER_HID_SET_LEDS:
break;
case XHCI_XFER_CBW_SENT: {
log_message(LOG_DEBUG, "xhci: CBW send completed");
/* Deferred (see xhci_dev_t's next_action doc
@@ -2397,11 +2654,25 @@ void xhci_poll_events(void)
break;
}
dev->connect_state = XHCI_CONN_AWAIT_CONFIGURE_ENDPOINT;
if (xhci_cmd_configure_endpoint(dev, next_slot_id) != 0) {
if ((nms->hid_kbd ? xhci_cmd_configure_hid_endpoint(dev, next_slot_id)
: xhci_cmd_configure_endpoint(dev, next_slot_id)) != 0) {
log_message(LOG_ERROR, "xhci: deferred configure endpoint request setup failed");
dev->connect_state = XHCI_CONN_IDLE;
}
break;
case XHCI_NEXT_ACTION_HID_SET_PROTOCOL:
if (xhci_ep0_hid_set_protocol(dev, next_slot_id) != 0) {
log_message(LOG_ERROR, "xhci: deferred keyboard set protocol setup failed");
}
break;
case XHCI_NEXT_ACTION_HID_POLL: {
uint8_t leds;
if (!nms->hid_running) break;
xhci_hid_queue_report(dev, next_slot_id, nms);
if (nms->transfer_purpose == XHCI_XFER_NONE && usb_kbd_leds_pending(&leds))
(void)xhci_ep0_hid_set_leds(dev, next_slot_id, leds);
break;
}
case XHCI_NEXT_ACTION_SET_CONFIG:
if (xhci_ep0_set_configuration(dev, next_slot_id, nms->next_action_config_value) != 0) {
log_message(LOG_ERROR, "xhci: deferred set configuration request setup failed");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,70 @@
[=3hBdsDxe: loading Boot0002 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0)
BdsDxe: starting Boot0002 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0)
[=3hStarKernel UEFI Loader
Loading kernel from ESP...
[CKPT 001] Entered efi_main - ConOut live
RAW SERIAL UP
[CKPT 002] Serial (COM1) initialized
Monolithic build - kernel linked directly
Collecting boot information...
CmdLine: parsed OK
[CKPT 004] Command line parsed
[CKPT 005] Kernel stack allocation decided
[CKPT 006] Boot info collected (ACPI table located)
GOP: linear framebuffer found
[CKPT 007] GOP: linear framebuffer found
[CKPT 008] About to enter ExitBootServices retry loop
EBS...
EBS OK
Calling kernel_main (monolithic)...
_____ _ _ __ _
/ ____| | | |/ / | |
| (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| |
\___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ |
____) | || (_| | | | . \ __/ | | | | | __/ |
|_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_|
LithosAnanke v2.1.0 amd64
Architecture: amd64
Build: Oct 14 2026 13:43:44
UEFI BootServices: EXITED
Console: serial line is a 16550 at port 0x00000000000003f8 (COM1, probed)
=== StarKernel Boot Information ===
Memory map entries: 131
Total memory: 1023 MB
Usable memory: 968 MB
===================================
PMM initialized.
PMM statistics:
Total pages: 249318
Free pages : 247795
Used pages : 1523
Total MB : 973
Free MB : 967
Used MB : 5
VMM initialized (mapped RAM, CR3 switched)
VMM initialized (mapped RAM, CR3 switched)
VMM self-test: mapped OK at 0xffff800000000000
VMM self-test complete.
IDT installed.
APIC: init...
APIC: IA32_APIC_BASE MSR=0x00000000fee00900
APIC: stale-ISR drain: 0 EOI(s) issued
APIC: initialized (xAPIC MMIO, TPR=0, SIVR=0x1FF, EOI-clear)
APIC: init done
I/O APIC: init...
I/O APIC: base=0xfec00000, gsi_base=0, overrides=5
override: source=00 gsi=2 flags=0x0000
override: source=05 gsi=5 flags=0x000d
override: source=09 gsi=9 flags=0x000d
override: source=0a gsi=10 flags=0x000d
override: source=0b gsi=11 flags=0x000d
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+6
View File
@@ -23,6 +23,7 @@
#if defined(__STARKERNEL__)
#include "starkernel/console.h"
#include "starkernel/usb_kbd.h" /* usb_kbd_pop_event() -- A3 */
#endif
/* KBD-SCAN ( -- c -1 | 0 ) */
@@ -129,6 +130,11 @@ static void kbw_vdebug(VM *vm)
* codes set bit 7 instead of carrying a separate field). */
int sk_key_event_poll(uint16_t *keycode, int *pressed)
{
#if defined(__STARKERNEL__)
/* A USB boot keyboard, on any architecture (2026-10-14,
* boards/BRINGUP.md A3): already in Linux keycodes (usb_kbd.c). */
if (usb_kbd_pop_event(keycode, pressed)) return 1;
#endif
#if defined(__STARKERNEL__) && defined(ARCH_AMD64)
uint8_t sc;
if (i8042_pop_scancode(&sc)) {