feat: fully async Intel BT firmware bring-up + HCI multi-packet event reassembly
The BT firmware download now runs from the idle loop after boot (zero boot stall), completing the async goal. What made every earlier deferral attempt fail was a months-latent HCI-layer bug, not the deferred environment: WaitCommandComplete returned after the FIRST USB packet of an event, but events larger than the 64-byte interrupt max-packet (like the AX211's 96-byte FC05 TLV version response) span several packets. Sending the next command while the tail of the previous response was still in flight wedges the AX211 bootloader into permanently ignoring commands. Boot-time flanterm rendering added milliseconds between commands and accidentally paced the protocol past the race -- which is why the synchronous bring-up always worked and every log-suppressed (deferred) bring-up went mute at FC05 #2, regardless of scheduling/MSI/xHCI fixes. Fix: reassemble multi-packet Command Complete/Status events in the transfer callback; the mailbox is marked ready only when the declared event length has fully arrived. This inherently paces command flow and, as a bonus, the TLV version read now sees the full response (sbe_type present -> ECDSA/RSA selection is no longer a guess). Also: per-slot EP0 completion tracking in the xHCI (a waiting ControlTransfer can no longer be released early by another device's EP0 completion). Verified on the AX211: instant boot, background download, real BD_ADDR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
72f6c4449c
commit
77940cf896
4 files changed
+74
-23
No files matched your search
@@ -94,9 +94,16 @@ namespace Drivers::USB::Xhci {
|
||||
static volatile uint32_t g_cmdCompletionCode = 0;
|
||||
volatile uint32_t g_cmdCompletionSlotId = 0; // not static: accessed by UsbDevice.cpp
|
||||
|
||||
// Transfer completion tracking (for EP0 control transfers during init)
|
||||
static volatile bool g_xferCompleted = false;
|
||||
static volatile uint32_t g_xferCompletionCode = 0;
|
||||
// EP0 control-transfer completion tracking -- PER SLOT. This was a single
|
||||
// global, which aliased completions across devices: once userspace is up,
|
||||
// another device's EP0 completion (HID idle/LED traffic, a hotplug rescan)
|
||||
// released a waiting ControlTransfer EARLY, before its own status stage
|
||||
// finished; the next command then rewrote TRBs over a still-executing TD
|
||||
// and garbled the wire. Observed as the AX211 BT bootloader going
|
||||
// permanently mute after the first FC05 whenever its bring-up ran
|
||||
// post-boot (deferred) instead of during single-user early boot.
|
||||
static volatile bool g_xferCompleted[MAX_SLOTS + 1] = {};
|
||||
static volatile uint32_t g_xferCompletionCode[MAX_SLOTS + 1] = {};
|
||||
|
||||
// Synchronous bulk transfer tracking. Only one storage-style blocking bulk
|
||||
// transfer is active at a time.
|
||||
@@ -514,9 +521,13 @@ namespace Drivers::USB::Xhci {
|
||||
lastSlot = slotId; lastEp = epDci; lastCC = completionCode;
|
||||
|
||||
if (epDci == 1) {
|
||||
// EP0 (DCI 1) - control transfer completion
|
||||
g_xferCompletionCode = completionCode;
|
||||
g_xferCompleted = true;
|
||||
// EP0 (DCI 1) - control transfer completion, attributed
|
||||
// to the slot it belongs to (see the per-slot note at
|
||||
// the declaration).
|
||||
if (slotId <= MAX_SLOTS) {
|
||||
g_xferCompletionCode[slotId] = completionCode;
|
||||
g_xferCompleted[slotId] = true;
|
||||
}
|
||||
} else if (slotId > 0 && slotId <= MAX_SLOTS && g_devices[slotId].Active) {
|
||||
UsbDeviceInfo& dev = g_devices[slotId];
|
||||
|
||||
@@ -814,7 +825,7 @@ namespace Drivers::USB::Xhci {
|
||||
AdvanceEP0Ring(dev);
|
||||
|
||||
// Ring doorbell for this slot, target EP0 (DCI 1)
|
||||
g_xferCompleted = false;
|
||||
g_xferCompleted[slotId] = false;
|
||||
WriteDoorbell(slotId, 1);
|
||||
|
||||
// If we are nested inside THIS core's PollEvents (e.g. an HCI reply sent
|
||||
@@ -835,9 +846,9 @@ namespace Drivers::USB::Xhci {
|
||||
uint64_t xferStart = Timekeeping::GetMilliseconds();
|
||||
while (Timekeeping::GetMilliseconds() - xferStart < 2000) {
|
||||
PollEvents();
|
||||
if (g_xferCompleted) {
|
||||
if (g_xferCompleted[slotId]) {
|
||||
g_controlXferLock.Release();
|
||||
return g_xferCompletionCode;
|
||||
return g_xferCompletionCode[slotId];
|
||||
}
|
||||
for (int j = 0; j < 100; j++) {
|
||||
asm volatile("" ::: "memory");
|
||||
|
||||
Reference in new issue
Block a user