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
+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;
}