feat(usb): every xHCI controller is driven, not only the first

boards/BRINGUP.md A1, FABRIC-3 SXLII.  xhci.c keeps a table of controllers
(up to 8) and services each; kernel_main brings up index 0, 1, ... until
there is none.  repl.c's and blkio_usb.c's per-slot state is per
controller now (a slot ID is a controller's own), and each USB keyboard
keeps its own last report and lights; an unplugged keyboard releases what
it held.

Test first: keyboard and identity on a second controller behind a bridge,
Zuse on the first -- before, only Zuse; after, all three, on all three
ISAs.  Hot-plug and unplug on the second controller.  Acceptance unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-14 14:27:38 -04:00
co-authored by Claude Opus 5.5
parent 739cb9c163
commit e729c64ba3
27 changed files with 81701 additions and 260 deletions
+2 -2
View File
@@ -23,9 +23,9 @@ None has been fixed: each needs your word.
| # | What | Where | Effect on hardware |
|---|---|---|---|
| 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). |
| A1 | **Fixed 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. Every controller is driven, up to 8 (since 2026-10-14, `FABRIC-3.md` XLII): a stick or keyboard may be in any port. | `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 | **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. |
| 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: a keyboard whose first interface is not the keyboard is not used; no key repeat. Several keyboards, on any controllers, each its own. |
| 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. |
+30
View File
@@ -5823,3 +5823,33 @@ not reach the device, records the enumeration working).
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.
## XLII. Every USB controller, not the first (`boards/BRINGUP.md` A1, the rest) (2026-10-14)
Captain Bob, 2026-10-14: "do multiple controllers" -- §XL's open item. A PC has several xHCI
controllers, each with its own ports; only the first found was driven, so a stick or keyboard in
another's ports was never seen.
**What assumed one controller, and now does not:**
- `xhci.c`: one latched `g_xhci_dev` → a table of up to `XHCI_MAX_CONTROLLERS` (8);
`xhci_poll_events()` services each in turn; `xhci_get_dev()` → `xhci_dev_count()` /
`xhci_dev_at(i)`; `xhci_find_and_map(dev, index)` finds the index-th controller (1 when there is
none). Everything per device was already per controller (`msc_slots`, the rings, the connect
state machine, the BOT state).
- `kernel_main.c`: brings up controller 0, 1, ... until there is none; one that fails is skipped.
- `repl.c` and `blkio_usb.c`: their per-slot arrays were indexed by slot ID alone -- but a slot ID is
a controller's own, so slot 1 on two controllers would have been one entry. Now one set per
controller (`g_usb_ctrl[]`; `blkio_usb.c` finds its set by the controller's pointer); the attach
acknowledgement looks the device up across all of them.
- `usb_kbd.c`: one "previous report" and one "lights sent" for every keyboard would have diffed two
keyboards against each other. Both are each keyboard's own now (in its slot); a keyboard that
goes releases whatever it still held (no Shift stuck down after unplugging).
**Verified.** Test first: amd64, no PS/2, Zuse's drive on the usual controller, a second (NEC,
behind a root port) with the USB keyboard and identity `00` -- before, only Zuse attached
(`logs/20261014-140826`); after, the keyboard types (`3`, `3`, `AB`), `00` attaches, Zuse is confirmed
(`-141442`) -- both controllers with a device in slot 1, the collision case. Four identities
hot-plugged onto the second controller all attach, and unplugging one detaches that one
(`-141621`). aarch64 and riscv64, the same two-controller boot: all the same results (`-141756`,
`-141953`). Standard acceptance, all three: POST 1052, hash unchanged (`-142120`, `-142237`,
`-142449`); five identities hot-plugged on the first controller, all attached (`-142606`).
+11 -9
View File
@@ -20,25 +20,27 @@
#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);
* up to six keys held (HID usage IDs). `len` below 3 is ignored. `prev`
* is that keyboard's own last report, 8 bytes, kept by the caller -- each
* keyboard is compared with itself, never with another. */
void usb_kbd_report(uint8_t prev[8], const uint8_t *report, uint32_t len);
/* Forgets the keys held: the keyboard has gone. */
void usb_kbd_reset(void);
/* The keyboard has gone: every key it still held is released, and its
* last report forgotten -- no Shift stays down after it. */
void usb_kbd_forget(uint8_t prev[8]);
/* 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
* console says which locks are on; the xHCI driver sends them to each
* 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);
/* The lights every keyboard should show. Each keyboard's driver state
* holds what that keyboard was last sent. */
uint8_t usb_kbd_leds_wanted(void);
#endif /* STARKERNEL_USB_KBD_H */
+23 -16
View File
@@ -147,6 +147,8 @@ typedef struct {
uint32_t hid_ring_cycle;
uint32_t hid_ring_enq;
uint8_t *hid_buf;
uint8_t hid_prev[8]; /* this keyboard's last report (usb_kbd.c) */
uint8_t hid_leds_sent; /* lights last sent to it; 0xFF = none yet */
uint8_t bot_msc_attach_pending;
uint8_t bot_msc_attached;
@@ -419,17 +421,24 @@ typedef struct {
} xhci_dev_t;
/*
* xhci_find_and_map — locate the xHCI controller on PCI bus 0, enable it
* (I/O+MEM+bus-master), map its BAR0 MMIO region, and
* fill in the four register-region pointers in *dev.
* xhci_find_and_map — locate the index-th xHCI controller (0 = the first
* found, by PCI class, on any bus), enable it
* (I/O+MEM+bus-master), take it from the firmware, map
* its BAR0 MMIO region, and fill in the four
* register-region pointers in *dev. Every controller
* is driven since 2026-10-14 (kernel_main.c brings up
* index 0, 1, ... until there is none), each its own
* xhci_dev_t.
*
* dev must point to a zero-initialised xhci_dev_t.
*
* Returns 0 on success.
* Returns -1 if no xHCI device was found on the PCI bus.
* Returns -2 if the BAR0 mapping failed.
* Returns 1 if there is no index-th controller (there are fewer).
* Returns -1 if this one's BAR0 cannot be read, -2 if it cannot be mapped
* (the next controller may still be fine).
*/
int xhci_find_and_map(xhci_dev_t *dev);
#define XHCI_MAX_CONTROLLERS 8u
int xhci_find_and_map(xhci_dev_t *dev, unsigned index);
/*
* xhci_bringup — reset the controller, allocate and program the DCBAA,
@@ -456,17 +465,15 @@ int xhci_find_and_map(xhci_dev_t *dev);
int xhci_bringup(xhci_dev_t *dev);
/*
* xhci_get_dev — return the module-static xhci_dev_t* xhci_poll_events()
* itself reads (only one controller is supported), or NULL
* if xhci_bringup() has not completed successfully yet.
*
* Milestone 2h: callers outside this driver (sk_repl_idle(), eventually
* the block-subsystem attach glue) have no other way to reach the device
* handle -- every earlier caller of this driver's API already had one in
* hand (kernel_main.c's own local xhci_dev_t), which doesn't help code
* that only runs later, on a hotplug event it wasn't the one to observe.
* xhci_dev_count / xhci_dev_at — the controllers xhci_bringup() has
* brought up, in the order found: how many, and the i-th
* (NULL past the end). Callers outside this driver
* (sk_repl_idle(), the block-subsystem attach glue) reach
* each controller this way. Replaced xhci_get_dev(), which
* returned the one controller there used to be (2026-10-14).
*/
xhci_dev_t *xhci_get_dev(void);
uint32_t xhci_dev_count(void); /* controllers brought up */
xhci_dev_t *xhci_dev_at(uint32_t i); /* the i-th, or NULL */
/*
* xhci_msc_slot_for — bounds-checked lookup into dev->msc_slots[slot_id]
+10 -3
View File
@@ -793,9 +793,16 @@ static void kernel_main_deep(BootInfo *boot_info) {
* xhci_poll_events()'s own doc comment for why this driver is polled
* rather than interrupt-driven. */
{
static xhci_dev_t xhci_dev;
(void)(xhci_find_and_map(&xhci_dev) == 0 &&
xhci_bringup(&xhci_dev) == 0);
/* Every xHCI controller, not only the first (2026-10-14): a PC has
* several, each with its own ports, and a stick or keyboard may be
* in any of them. One that fails is skipped; the next is tried. */
static xhci_dev_t xhci_devs[XHCI_MAX_CONTROLLERS];
unsigned xi;
for (xi = 0; xi < XHCI_MAX_CONTROLLERS; xi++) {
int rc = xhci_find_and_map(&xhci_devs[xi], xi);
if (rc == 1) break;
if (rc == 0) (void)xhci_bringup(&xhci_devs[xi]);
}
}
/* FABRIC-0.md item 4.4g (decided 2026-08-11): console_fb_init() moved here,
+202 -186
View File
@@ -218,8 +218,6 @@ blkio_dev_t *sk_repl_get_artemis_usb_dev(void) {
* sk_word_blk_attach_ack() below (a real dictionary word, called from a
* completely different call stack than the idle loop) can resolve an
* incoming ack's raw pointer back to the slot it belongs to. */
static blkio_dev_t *g_usb_blk_dev_slots = (void *)0;
static uint32_t g_usb_blk_dev_slot_count = 0;
/* One pending entry per slot, indexed the same way msc_slots[]/
* usb_blk_dev_slots[] already are (index 0 unused, matches precedent).
@@ -240,7 +238,17 @@ typedef struct {
* virtio-blk path already does synchronously. */
int is_artemis;
} sk_blk_attach_pending_t;
static sk_blk_attach_pending_t *g_blk_attach_pending = (void *)0;
/* Per xHCI controller (2026-10-14): slot IDs are a controller's own, so
* slot 1 on one and slot 1 on another are different devices. Each entry
* holds that controller's blkio_dev_t per slot and its pending attaches,
* the controller being xhci_dev_at() of the same index. */
typedef struct {
blkio_dev_t *slots;
uint32_t count;
sk_blk_attach_pending_t *pending;
} sk_usb_ctrl_t;
static sk_usb_ctrl_t g_usb_ctrl[XHCI_MAX_CONTROLLERS];
/* BLK-ATTACH-ACK ( dev-addr ok? -- ): VM-EXEC'd into Hera by Artemis's
* own MSG-TICK once HERA-BLK-ATTACH-REQ's BLK-ATTACH call resolves.
@@ -270,21 +278,24 @@ static void sk_word_blk_attach_ack(VM *vm) {
return;
}
if (!g_usb_blk_dev_slots || !g_blk_attach_pending) return;
uint32_t found_slot = 0;
for (uint32_t i = 1; i < g_usb_blk_dev_slot_count; i++) {
if (&g_usb_blk_dev_slots[i] == dev) { found_slot = i; break; }
uint32_t found_ctrl = 0, found_slot = 0;
for (uint32_t c = 0; c < XHCI_MAX_CONTROLLERS && found_slot == 0; c++) {
if (!g_usb_ctrl[c].slots || !g_usb_ctrl[c].pending) continue;
for (uint32_t i = 1; i < g_usb_ctrl[c].count; i++) {
if (&g_usb_ctrl[c].slots[i] == dev) { found_ctrl = c; found_slot = i; break; }
}
}
if (found_slot == 0 || !g_blk_attach_pending[found_slot].pending) return;
g_blk_attach_pending[found_slot].pending = 0;
if (found_slot == 0) return;
sk_blk_attach_pending_t *pending = g_usb_ctrl[found_ctrl].pending;
if (!pending[found_slot].pending) return;
pending[found_slot].pending = 0;
if (!ok_flag) {
log_message(LOG_ERROR, "xhci: USB MSC block-subsystem attach failed");
return;
}
xhci_dev_t *xdev = xhci_get_dev();
xhci_dev_t *xdev = xhci_dev_at(found_ctrl);
xhci_msc_slot_t *ms = xdev ? xhci_msc_slot_for(xdev, found_slot) : (void *)0;
if (ms) ms->bot_msc_attached = 1;
g_attached_blk_dev = dev;
@@ -329,9 +340,9 @@ static void sk_word_blk_attach_ack(VM *vm) {
console_println(line);
}
homeblocks_sig_result_t sig_rc = g_blk_attach_pending[found_slot].sig_rc;
homeblocks_sig_t sig = g_blk_attach_pending[found_slot].sig;
int is_artemis = g_blk_attach_pending[found_slot].is_artemis;
homeblocks_sig_result_t sig_rc = pending[found_slot].sig_rc;
homeblocks_sig_t sig = pending[found_slot].sig;
int is_artemis = pending[found_slot].is_artemis;
if (is_artemis) {
/* FABRIC-3.md §XXVI follow-on: bus-agnostic Artemis discovery.
@@ -567,185 +578,189 @@ static void sk_repl_idle(VM *active_vm)
* own doc comment in xhci_driver.h for why: xhci_bot_wait_for_idle()'s
* busy-wait -- which blkio_usb_open_msc() uses internally -- must
* never run from inside xhci_poll_events()'s own call frame). */
xhci_dev_t *xdev = xhci_get_dev();
/* FABRIC-3.md §VII (2026-09-05): was `static blkio_dev_t usb_blk_dev`
* ("single-device scope, matching the xHCI driver's own") -- now a
* per-slot registry, same sizing/allocation precedent as xhci_dev_t's
* own msc_slots[] (sized off xdev->max_slots, allocated once on first
* idle tick after xdev is known, since this file has no bringup-time
* hook of its own). Every slot with a pending attach/detach flag is
* serviced this tick, not just one -- a single `if` here used to mean
* a second device's pending flag would sit unnoticed until the first's
* flag was consumed and cleared. */
if (xdev && (!g_usb_blk_dev_slots || g_usb_blk_dev_slot_count < xdev->max_slots + 1)) {
size_t bytes = (size_t)(xdev->max_slots + 1) * sizeof(blkio_dev_t);
blkio_dev_t *fresh = (blkio_dev_t *)kmalloc_aligned(bytes, 64);
size_t pending_bytes = (size_t)(xdev->max_slots + 1) * sizeof(sk_blk_attach_pending_t);
sk_blk_attach_pending_t *fresh_pending = (sk_blk_attach_pending_t *)kmalloc_aligned(pending_bytes, 64);
if (fresh && fresh_pending) {
memset(fresh, 0, bytes);
memset(fresh_pending, 0, pending_bytes);
g_usb_blk_dev_slots = fresh;
g_usb_blk_dev_slot_count = xdev->max_slots + 1;
g_blk_attach_pending = fresh_pending;
/* Every controller in turn (2026-10-14): each has its own slots. */
for (uint32_t xc = 0; xc < xhci_dev_count() && xc < XHCI_MAX_CONTROLLERS; xc++) {
xhci_dev_t *xdev = xhci_dev_at(xc);
sk_usb_ctrl_t *uc = &g_usb_ctrl[xc];
/* FABRIC-3.md §VII (2026-09-05): was `static blkio_dev_t usb_blk_dev`
* ("single-device scope, matching the xHCI driver's own") -- now a
* per-slot registry, same sizing/allocation precedent as xhci_dev_t's
* own msc_slots[] (sized off xdev->max_slots, allocated once on first
* idle tick after xdev is known, since this file has no bringup-time
* hook of its own). Every slot with a pending attach/detach flag is
* serviced this tick, not just one -- a single `if` here used to mean
* a second device's pending flag would sit unnoticed until the first's
* flag was consumed and cleared. */
if (xdev && (!uc->slots || uc->count < xdev->max_slots + 1)) {
size_t bytes = (size_t)(xdev->max_slots + 1) * sizeof(blkio_dev_t);
blkio_dev_t *fresh = (blkio_dev_t *)kmalloc_aligned(bytes, 64);
size_t pending_bytes = (size_t)(xdev->max_slots + 1) * sizeof(sk_blk_attach_pending_t);
sk_blk_attach_pending_t *fresh_pending = (sk_blk_attach_pending_t *)kmalloc_aligned(pending_bytes, 64);
if (fresh && fresh_pending) {
memset(fresh, 0, bytes);
memset(fresh_pending, 0, pending_bytes);
uc->slots = fresh;
uc->count = xdev->max_slots + 1;
uc->pending = fresh_pending;
}
}
}
for (uint32_t slot_id = 1; xdev && g_usb_blk_dev_slots && slot_id <= xdev->max_slots; slot_id++) {
xhci_msc_slot_t *ms = xhci_msc_slot_for(xdev, slot_id);
if (!ms || !ms->bot_msc_attach_pending) continue;
ms->bot_msc_attach_pending = 0;
blkio_dev_t *usb_blk_dev = &g_usb_blk_dev_slots[slot_id];
for (uint32_t slot_id = 1; xdev && uc->slots && slot_id <= xdev->max_slots; slot_id++) {
xhci_msc_slot_t *ms = xhci_msc_slot_for(xdev, slot_id);
if (!ms || !ms->bot_msc_attach_pending) continue;
ms->bot_msc_attach_pending = 0;
blkio_dev_t *usb_blk_dev = &uc->slots[slot_id];
int rc = blkio_usb_open_msc(usb_blk_dev, xdev, slot_id);
if (rc == 0) {
/* FABRIC-2.md Milestone 4: warn on blank/foreign/unrecognized
* media -- the "warn" half. No "refuse" half yet: blkio_usb.c
* has no SCSI WRITE(10) support at all (Milestone 2's biggest
* open item), so there is no write path today to refuse --
* only read-only attach, which is also the general-purpose USB
* block I/O path this repo already relies on for unrelated
* testing, not exclusively a home-blocks identity workflow.
* Refusing attach on blank media here would break that
* legitimate use without protecting anything real yet. Refuse
* belongs on the write path, once WRITE(10) gives it something
* to gate.
*
* HOMEBLOCKS_SIG_START_FBLOCK (devblock 1): the real, final
* location -- GPT was dropped permanently, this is not an
* interim value (FABRIC-2.md §F.8/§F.13). */
homeblocks_sig_t sig;
homeblocks_sig_result_t sig_rc =
homeblocks_sig_check(usb_blk_dev, HOMEBLOCKS_SIG_START_FBLOCK, &sig);
switch (sig_rc) {
case HOMEBLOCKS_SIG_OK:
log_message(LOG_DEBUG, "xhci: USB drive recognized as a home-blocks drive");
/* FABRIC-3.md §VII (2026-09-05): g_homeblocks_dev/
* g_attached_blk_dev stay single "most recently
* attached" pointers by deliberate, scoped choice --
* the multi-device fix's target was the driver/backend
* corrupting each other's live state when two devices
* are attached at once (fixed above and in xhci.c/
* blkio_usb.c), not making every console-facing FORTH
* word (RUNCAP et al, mama_forth_words.c) multi-device
* aware -- the console still interacts with one device
* at a time, matching its own single-active-REPL
* design. Revisit if a real use case needs otherwise. */
g_homeblocks_dev = usb_blk_dev;
g_homeblocks_sig = sig;
g_homeblocks_sig_valid = 1;
break;
case HOMEBLOCKS_SIG_BLANK:
log_message(LOG_DEBUG, "xhci: USB drive not recognized (blank or foreign media) -- read-only general use only");
break;
case HOMEBLOCKS_SIG_BAD_VERSION:
log_message(LOG_WARN, "xhci: USB drive has a home-blocks header of an unrecognized version -- read-only general use only");
break;
case HOMEBLOCKS_SIG_BAD_CRC:
log_message(LOG_WARN, "xhci: USB drive has a home-blocks header that fails its checksum (corrupt or tampered) -- read-only general use only");
break;
case HOMEBLOCKS_SIG_READ_ERROR:
log_message(LOG_ERROR, "xhci: USB drive signature check failed to read the device -- read-only general use only");
break;
}
/* FABRIC-3.md §XXVI follow-on: a device isn't an identity
* thumbdrive (sig_rc above came back BLANK, i.e. no 'LAHB'
* magic) -- check whether it's Artemis's own disk instead
* (distinct 'ARTM' magic, same devblock-1 convention). Only
* attempted on BLANK, not on every device: a drive that
* already checked out as home-blocks (or failed a home-blocks
* version/CRC check) can't also be Artemis's disk, and
* skipping the second read keeps the common identity-drive
* case down to one signature check per attach, same as
* before this feature existed. */
int is_artemis_disk = 0;
if (sig_rc == HOMEBLOCKS_SIG_BLANK) {
artemis_sig_t asig;
artemis_sig_result_t art_rc = artemis_sig_check(usb_blk_dev, &asig);
if (art_rc == ARTEMIS_SIG_OK) {
log_message(LOG_DEBUG, "xhci: USB drive recognized as Artemis's own disk");
is_artemis_disk = 1;
int rc = blkio_usb_open_msc(usb_blk_dev, xdev, slot_id);
if (rc == 0) {
/* FABRIC-2.md Milestone 4: warn on blank/foreign/unrecognized
* media -- the "warn" half. No "refuse" half yet: blkio_usb.c
* has no SCSI WRITE(10) support at all (Milestone 2's biggest
* open item), so there is no write path today to refuse --
* only read-only attach, which is also the general-purpose USB
* block I/O path this repo already relies on for unrelated
* testing, not exclusively a home-blocks identity workflow.
* Refusing attach on blank media here would break that
* legitimate use without protecting anything real yet. Refuse
* belongs on the write path, once WRITE(10) gives it something
* to gate.
*
* HOMEBLOCKS_SIG_START_FBLOCK (devblock 1): the real, final
* location -- GPT was dropped permanently, this is not an
* interim value (FABRIC-2.md §F.8/§F.13). */
homeblocks_sig_t sig;
homeblocks_sig_result_t sig_rc =
homeblocks_sig_check(usb_blk_dev, HOMEBLOCKS_SIG_START_FBLOCK, &sig);
switch (sig_rc) {
case HOMEBLOCKS_SIG_OK:
log_message(LOG_DEBUG, "xhci: USB drive recognized as a home-blocks drive");
/* FABRIC-3.md §VII (2026-09-05): g_homeblocks_dev/
* g_attached_blk_dev stay single "most recently
* attached" pointers by deliberate, scoped choice --
* the multi-device fix's target was the driver/backend
* corrupting each other's live state when two devices
* are attached at once (fixed above and in xhci.c/
* blkio_usb.c), not making every console-facing FORTH
* word (RUNCAP et al, mama_forth_words.c) multi-device
* aware -- the console still interacts with one device
* at a time, matching its own single-active-REPL
* design. Revisit if a real use case needs otherwise. */
g_homeblocks_dev = usb_blk_dev;
g_homeblocks_sig = sig;
g_homeblocks_sig_valid = 1;
break;
case HOMEBLOCKS_SIG_BLANK:
log_message(LOG_DEBUG, "xhci: USB drive not recognized (blank or foreign media) -- read-only general use only");
break;
case HOMEBLOCKS_SIG_BAD_VERSION:
log_message(LOG_WARN, "xhci: USB drive has a home-blocks header of an unrecognized version -- read-only general use only");
break;
case HOMEBLOCKS_SIG_BAD_CRC:
log_message(LOG_WARN, "xhci: USB drive has a home-blocks header that fails its checksum (corrupt or tampered) -- read-only general use only");
break;
case HOMEBLOCKS_SIG_READ_ERROR:
log_message(LOG_ERROR, "xhci: USB drive signature check failed to read the device -- read-only general use only");
break;
}
}
/* FABRIC-2.md §F.20/§F.21 / §F.5/§F.23 (WIREBIND): Zuse
* genesis-mint/attach-authenticate and regular-identity
* verify-then-birth-then-pair both used to run synchronously,
* right here, before storage was even registered. Moved
* (Bob, 2026-09-07, "wait for ack, safer for identity data")
* to sk_word_blk_attach_ack() above, run only once Artemis
* confirms the storage-attach succeeded -- identity birth
* no longer happens on top of storage that might not have
* registered. Stash what that deferred call needs. */
if (g_blk_attach_pending) {
g_blk_attach_pending[slot_id].pending = 1;
g_blk_attach_pending[slot_id].sig_rc = sig_rc;
g_blk_attach_pending[slot_id].sig = sig;
g_blk_attach_pending[slot_id].is_artemis = is_artemis_disk;
}
/* FABRIC-3.md §XXVI follow-on: a device isn't an identity
* thumbdrive (sig_rc above came back BLANK, i.e. no 'LAHB'
* magic) -- check whether it's Artemis's own disk instead
* (distinct 'ARTM' magic, same devblock-1 convention). Only
* attempted on BLANK, not on every device: a drive that
* already checked out as home-blocks (or failed a home-blocks
* version/CRC check) can't also be Artemis's disk, and
* skipping the second read keeps the common identity-drive
* case down to one signature check per attach, same as
* before this feature existed. */
int is_artemis_disk = 0;
if (sig_rc == HOMEBLOCKS_SIG_BLANK) {
artemis_sig_t asig;
artemis_sig_result_t art_rc = artemis_sig_check(usb_blk_dev, &asig);
if (art_rc == ARTEMIS_SIG_OK) {
log_message(LOG_DEBUG, "xhci: USB drive recognized as Artemis's own disk");
is_artemis_disk = 1;
}
}
/* Storage-attach registration (blk_subsys_attach_device(),
* BLK-ATTACH C primitive) is Artemis's own domain now, not
* Hera's -- she keeps polling/sig-checking but no longer
* performs this step herself. Hera can't use her own
* MSG-SEND (see g_usb_blk_dev_slots's own doc comment
* above for why), so this is a direct VM-EXEC into
* Artemis's dictionary -- the same mechanism the MSG-TICK
* pump below already uses -- rather than a real enqueued
* message. HERA-BLK-ATTACH-REQ (capsules/artemis/init.4th)
* runs BLK-ATTACH then replies via her own real MSG-SEND,
* delivered back to Hera by the ordinary MSG-TICK pump. */
{
char cmd[96];
int n = snprintf(cmd, sizeof(cmd),
"S\" %llu HERA-BLK-ATTACH-REQ\" S\" Artemis\" VM-EXEC",
(unsigned long long)(uintptr_t)usb_blk_dev);
if (n > 0 && (size_t)n < sizeof(cmd)) {
vm_interpret((VM *)sk_get_mama_vm(), cmd);
/* FABRIC-2.md §F.20/§F.21 / §F.5/§F.23 (WIREBIND): Zuse
* genesis-mint/attach-authenticate and regular-identity
* verify-then-birth-then-pair both used to run synchronously,
* right here, before storage was even registered. Moved
* (Bob, 2026-09-07, "wait for ack, safer for identity data")
* to sk_word_blk_attach_ack() above, run only once Artemis
* confirms the storage-attach succeeded -- identity birth
* no longer happens on top of storage that might not have
* registered. Stash what that deferred call needs. */
if (uc->pending) {
uc->pending[slot_id].pending = 1;
uc->pending[slot_id].sig_rc = sig_rc;
uc->pending[slot_id].sig = sig;
uc->pending[slot_id].is_artemis = is_artemis_disk;
}
/* Storage-attach registration (blk_subsys_attach_device(),
* BLK-ATTACH C primitive) is Artemis's own domain now, not
* Hera's -- she keeps polling/sig-checking but no longer
* performs this step herself. Hera can't use her own
* MSG-SEND (see uc->slots's own doc comment
* above for why), so this is a direct VM-EXEC into
* Artemis's dictionary -- the same mechanism the MSG-TICK
* pump below already uses -- rather than a real enqueued
* message. HERA-BLK-ATTACH-REQ (capsules/artemis/init.4th)
* runs BLK-ATTACH then replies via her own real MSG-SEND,
* delivered back to Hera by the ordinary MSG-TICK pump. */
{
char cmd[96];
int n = snprintf(cmd, sizeof(cmd),
"S\" %llu HERA-BLK-ATTACH-REQ\" S\" Artemis\" VM-EXEC",
(unsigned long long)(uintptr_t)usb_blk_dev);
if (n > 0 && (size_t)n < sizeof(cmd)) {
vm_interpret((VM *)sk_get_mama_vm(), cmd);
}
}
}
}
}
/* Milestone 2h hot-detach: the device disconnected (PORTSC, inside the
* xhci_poll_events() call above) after having actually attached.
* blk_subsys_detach_device() is local block_subsystem.c bookkeeping --
* no device round-trip, so it wouldn't strictly need to run outside
* xhci_poll_events()'s own call frame -- but handling it here anyway
* matches the attach path's shape and keeps xhci.c decoupled from
* block_subsystem.c (see bot_msc_detach_pending's own doc comment).
* Per-slot loop now (FABRIC-3.md §VII, 2026-09-05), same reasoning as
* the attach loop above. */
for (uint32_t slot_id = 1; xdev && g_usb_blk_dev_slots && slot_id <= xdev->max_slots; slot_id++) {
xhci_msc_slot_t *ms = xhci_msc_slot_for(xdev, slot_id);
if (!ms || !ms->bot_msc_detach_pending) continue;
ms->bot_msc_detach_pending = 0;
blkio_dev_t *usb_blk_dev = &g_usb_blk_dev_slots[slot_id];
/* Milestone 2h hot-detach: the device disconnected (PORTSC, inside the
* xhci_poll_events() call above) after having actually attached.
* blk_subsys_detach_device() is local block_subsystem.c bookkeeping --
* no device round-trip, so it wouldn't strictly need to run outside
* xhci_poll_events()'s own call frame -- but handling it here anyway
* matches the attach path's shape and keeps xhci.c decoupled from
* block_subsystem.c (see bot_msc_detach_pending's own doc comment).
* Per-slot loop now (FABRIC-3.md §VII, 2026-09-05), same reasoning as
* the attach loop above. */
for (uint32_t slot_id = 1; xdev && uc->slots && slot_id <= xdev->max_slots; slot_id++) {
xhci_msc_slot_t *ms = xhci_msc_slot_for(xdev, slot_id);
if (!ms || !ms->bot_msc_detach_pending) continue;
ms->bot_msc_detach_pending = 0;
blkio_dev_t *usb_blk_dev = &uc->slots[slot_id];
blk_subsys_detach_device(usb_blk_dev);
if (g_homeblocks_dev == usb_blk_dev) {
g_homeblocks_dev = (void *)0;
g_homeblocks_sig_valid = 0;
blk_subsys_detach_device(usb_blk_dev);
if (g_homeblocks_dev == usb_blk_dev) {
g_homeblocks_dev = (void *)0;
g_homeblocks_sig_valid = 0;
}
if (g_attached_blk_dev == usb_blk_dev) {
g_attached_blk_dev = (void *)0;
}
/* FABRIC-2.md §F.10 decision 2 (UNCLEAN, closed alongside EJECT):
* the device is already gone -- no-op if WIREBIND never had
* anything tracked (general-purpose USB use, not a home-blocks
* identity drive), OR if the device that left wasn't the one
* WIREBIND tracks (FABRIC-3.md §VII follow-on, 2026-09-06 --
* genuine multi-device attach means it might be a different
* device leaving while a WIREBIND user's own stays attached). */
capsule_wirebind_unclean_detach(usb_blk_dev);
/* FABRIC-2.md §I.8, re-scoped 2026-09-04: Zuse logs out on device
* removal exactly like a WIREBIND user -- no-op if the device
* that just left wasn't hers (FABRIC-3.md §VII follow-on,
* 2026-09-06: that no-op is now real, see capsule_zuse_boot_
* logout()'s own updated doc comment). */
capsule_zuse_boot_logout((VM *)sk_get_mama_vm(), usb_blk_dev);
}
if (g_attached_blk_dev == usb_blk_dev) {
g_attached_blk_dev = (void *)0;
}
/* FABRIC-2.md §F.10 decision 2 (UNCLEAN, closed alongside EJECT):
* the device is already gone -- no-op if WIREBIND never had
* anything tracked (general-purpose USB use, not a home-blocks
* identity drive), OR if the device that left wasn't the one
* WIREBIND tracks (FABRIC-3.md §VII follow-on, 2026-09-06 --
* genuine multi-device attach means it might be a different
* device leaving while a WIREBIND user's own stays attached). */
capsule_wirebind_unclean_detach(usb_blk_dev);
/* FABRIC-2.md §I.8, re-scoped 2026-09-04: Zuse logs out on device
* removal exactly like a WIREBIND user -- no-op if the device
* that just left wasn't hers (FABRIC-3.md §VII follow-on,
* 2026-09-06: that no-op is now real, see capsule_zuse_boot_
* logout()'s own updated doc comment). */
capsule_zuse_boot_logout((VM *)sk_get_mama_vm(), usb_blk_dev);
}
/* FABRIC-0.md/FABRIC-1.md Section V item 6: "a cheap 'anything dirty?
@@ -779,7 +794,7 @@ static void sk_repl_idle(VM *active_vm)
* based relocation) vs. deliberately left open (overflow-triggered
* migration, needs a call site threaded from WIREBIND).
*
* FABRIC-3.md, 2026-09-09: skipped while any g_blk_attach_pending
* FABRIC-3.md, 2026-09-09: skipped while any g_usb_ctrl[].pending
* entry is still pending -- this scan and the storage-attach message
* round-trip (HERA-BLK-ATTACH-REQ/BLK-ATTACH-ACK) both touch the
* block subsystem/Artemis's own virtio-backed storage, and live-
@@ -791,10 +806,11 @@ static void sk_repl_idle(VM *active_vm)
* with nothing to do. */
{
int attach_in_flight = 0;
if (g_blk_attach_pending) {
for (uint32_t c = 0; c < XHCI_MAX_CONTROLLERS && !attach_in_flight; c++) {
uint32_t pi;
for (pi = 0; pi < g_usb_blk_dev_slot_count; pi++) {
if (g_blk_attach_pending[pi].pending) { attach_in_flight = 1; break; }
if (!g_usb_ctrl[c].pending) continue;
for (pi = 0; pi < g_usb_ctrl[c].count; pi++) {
if (g_usb_ctrl[c].pending[pi].pending) { attach_in_flight = 1; break; }
}
}
if (!attach_in_flight) blk_migration_idle_check();
+19 -6
View File
@@ -30,20 +30,33 @@ typedef struct {
* controller's own max_slots, allocated once on first use here since this
* file has no bringup-time hook of its own to do it earlier). Indexed
* directly by xHCI slot ID, index 0 unused, matching msc_slots[]. */
static BlkioUsbState *g_usb_blk_slots = NULL;
static uint32_t g_usb_blk_slot_count = 0;
/* One registry per controller (2026-10-14: every xHCI controller is now
* driven, and a slot ID is a controller's own -- slot 1 on two
* controllers is two devices). Found by the controller's own pointer. */
static struct {
xhci_dev_t *xdev;
BlkioUsbState *slots;
uint32_t count;
} g_usb_blk[XHCI_MAX_CONTROLLERS];
static BlkioUsbState *usb_blk_state_for(xhci_dev_t *xdev, uint32_t slot_id) {
uint32_t c;
if (!xdev || slot_id < 1 || slot_id > xdev->max_slots) return NULL;
if (!g_usb_blk_slots || g_usb_blk_slot_count < xdev->max_slots + 1) {
for (c = 0; c < XHCI_MAX_CONTROLLERS; c++) {
if (g_usb_blk[c].xdev == xdev || !g_usb_blk[c].xdev) break;
}
if (c == XHCI_MAX_CONTROLLERS) return NULL;
if (!g_usb_blk[c].slots || g_usb_blk[c].count < xdev->max_slots + 1) {
size_t bytes = (size_t)(xdev->max_slots + 1) * sizeof(BlkioUsbState);
BlkioUsbState *fresh = (BlkioUsbState *)kmalloc_aligned(bytes, 64);
if (!fresh) return NULL;
memset(fresh, 0, bytes);
g_usb_blk_slots = fresh;
g_usb_blk_slot_count = xdev->max_slots + 1;
g_usb_blk[c].xdev = xdev;
g_usb_blk[c].slots = fresh;
g_usb_blk[c].count = xdev->max_slots + 1;
}
return &g_usb_blk_slots[slot_id];
return &g_usb_blk[c].slots[slot_id];
}
static int usb_blk_open(blkio_dev_t *dev, const blkio_params_t *p) {
+10 -21
View File
@@ -62,11 +62,8 @@ 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. */
/* Num Lock on to start, matching the console (repl.c). */
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)
{
@@ -89,7 +86,7 @@ static int held(const uint8_t *keys, uint32_t n, uint8_t usage)
return 0;
}
void usb_kbd_report(const uint8_t *report, uint32_t len)
void usb_kbd_report(uint8_t prev[8], const uint8_t *report, uint32_t len)
{
uint8_t now[8] = {0};
uint32_t i, n;
@@ -101,34 +98,26 @@ void usb_kbd_report(const uint8_t *report, uint32_t len)
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);
if ((now[0] & bit) != (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);
if (prev[i] > 0x01 && !held(now + 2, n, prev[i])) push(key_for(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);
if (now[i] > 0x01 && !held(prev + 2, n, now[i])) push(key_for(now[i]), 1);
for (i = 0; i < 8; i++) g_prev[i] = now[i];
for (i = 0; i < 8; i++) prev[i] = now[i];
}
void usb_kbd_reset(void)
void usb_kbd_forget(uint8_t prev[8])
{
uint32_t i;
for (i = 0; i < 8; i++) g_prev[i] = 0;
g_leds_sent = 0xFFu;
static const uint8_t none[8] = {0};
usb_kbd_report(prev, none, 8); /* everything held, released */
}
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; }
uint8_t usb_kbd_leds_wanted(void) { return g_leds_want; }
int usb_kbd_pop_event(uint16_t *keycode, int *pressed)
{
+33 -17
View File
@@ -88,15 +88,14 @@ static void xhci_take_from_bios(xhci_dev_t *dev)
}
}
int xhci_find_and_map(xhci_dev_t *dev)
int xhci_find_and_map(xhci_dev_t *dev, unsigned index)
{
uint64_t map_size;
if (!dev) return -1;
if (pci_find_class(XHCI_PCI_CLASS, XHCI_PCI_SUBCLASS, XHCI_PCI_PROG_IF, 0, &dev->pci) != 0) {
log_message(LOG_DEBUG, "xhci: no xHCI controller on any PCI bus");
return -1;
if (pci_find_class(XHCI_PCI_CLASS, XHCI_PCI_SUBCLASS, XHCI_PCI_PROG_IF, index, &dev->pci) != 0) {
return 1; /* no index-th controller: there are fewer */
}
xhci_log_hex32("xhci: controller vendor/device=",
((uint32_t)dev->pci.vendor_id << 16) | dev->pci.device_id);
@@ -188,10 +187,12 @@ static void xhci_log_hex32(const char *label, uint32_t val)
log_message(LOG_DEBUG, "%s%s", label, buf);
}
/* Only one controller is supported (matches xhci_dev_t's own doc comment);
* latched at the end of a successful xhci_bringup() for
* xhci_poll_events()'s use. */
static xhci_dev_t *g_xhci_dev = NULL;
/* Every controller brought up, in the order found (2026-10-14: a PC has
* several, and a stick or keyboard may be in any of their ports -- until
* then only the first was ever driven). Latched at the end of a
* successful xhci_bringup(); xhci_poll_events() services each in turn. */
static xhci_dev_t *g_xhci_devs[XHCI_MAX_CONTROLLERS];
static uint32_t g_xhci_dev_count;
/* Forward declaration -- xhci_bringup() below issues one Enable Slot as a
* command-ring smoke test; the implementation lives after xhci_bringup()
@@ -428,7 +429,7 @@ int xhci_bringup(xhci_dev_t *dev)
? "xhci: context size = 64 bytes"
: "xhci: context size = 32 bytes");
g_xhci_dev = dev;
if (g_xhci_dev_count < XHCI_MAX_CONTROLLERS) g_xhci_devs[g_xhci_dev_count++] = dev;
/* Catch a device already connected at launch -- xhci_poll_events() is
* purely event-ring-driven (Port Status Change events only), and a
@@ -442,9 +443,14 @@ int xhci_bringup(xhci_dev_t *dev)
return 0;
}
xhci_dev_t *xhci_get_dev(void)
uint32_t xhci_dev_count(void)
{
return g_xhci_dev;
return g_xhci_dev_count;
}
xhci_dev_t *xhci_dev_at(uint32_t i)
{
return i < g_xhci_dev_count ? g_xhci_devs[i] : NULL;
}
xhci_msc_slot_t *xhci_msc_slot_for(xhci_dev_t *dev, uint32_t slot_id)
@@ -1510,7 +1516,7 @@ static int xhci_ep0_hid_set_leds(xhci_dev_t *dev, uint32_t slot_id, uint8_t leds
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 */
ms->hid_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;
}
@@ -1859,9 +1865,17 @@ static void xhci_handle_bulk_stall(xhci_dev_t *dev, uint32_t slot_id,
static uint32_t g_xhci_keyboards;
int xhci_keyboard_running(void) { return g_xhci_keyboards != 0; }
static void xhci_poll_one(xhci_dev_t *dev);
void xhci_poll_events(void)
{
xhci_dev_t *dev = g_xhci_dev;
uint32_t i;
for (i = 0; i < g_xhci_dev_count; i++) xhci_poll_one(g_xhci_devs[i]);
}
/* One controller's Event Ring -- everything below, per controller. */
static void xhci_poll_one(xhci_dev_t *dev)
{
if (!dev) return;
uint32_t evt_processed = 0;
@@ -2004,7 +2018,7 @@ void xhci_poll_events(void)
{
xhci_msc_slot_t *dms2 = xhci_msc_slot_for(dev, dev->pending_disable_slot_id);
if (dms2) {
if (dms2->hid_kbd) usb_kbd_reset(); /* A3: no key stays held */
if (dms2->hid_kbd) usb_kbd_forget(dms2->hid_prev); /* A3: no key stays held */
if (dms2->hid_running && g_xhci_keyboards) g_xhci_keyboards--;
dms2->hid_kbd = 0;
dms2->hid_running = 0;
@@ -2134,7 +2148,7 @@ void xhci_poll_events(void)
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);
usb_kbd_report(xms->hid_prev, 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");
@@ -2361,7 +2375,8 @@ void xhci_poll_events(void)
case XHCI_XFER_HID_SET_PROTOCOL: {
xms->hid_running = 1;
g_xhci_keyboards++;
usb_kbd_reset();
memset(xms->hid_prev, 0, sizeof(xms->hid_prev));
xms->hid_leds_sent = 0xFFu; /* tell it the lights at once */
console_println("USB keyboard ready");
xms->next_action = XHCI_NEXT_ACTION_HID_POLL;
break;
@@ -2669,7 +2684,8 @@ void xhci_poll_events(void)
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))
leds = usb_kbd_leds_wanted();
if (nms->transfer_purpose == XHCI_XFER_NONE && leds != nms->hid_leds_sent)
(void)xhci_ep0_hid_set_leds(dev, next_slot_id, leds);
break;
}
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
File diff suppressed because it is too large Load Diff