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