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:
co-authored by
Claude Opus 5.5
parent
080b7012ba
commit
739cb9c163
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user