feat: Intel BT firmware download, A2dp & Bluetooth audio progress

This commit is contained in:
2026-06-03 18:05:17 +02:00
parent ee6d1a388e
commit 0f16785c9f
22 changed files with 6452 additions and 273 deletions
+125 -8
View File
@@ -107,6 +107,13 @@ namespace Drivers::USB::Xhci {
// Per-device info
static UsbDeviceInfo g_devices[MAX_SLOTS + 1] = {};
// True while PollEvents() is draining the event ring. Submitters that are
// reached from inside an event callback (e.g. an HCI reply sent from a
// Bluetooth event handler) see this and must NOT wait for completion --
// PollEvents is non-reentrant, so the wait would never observe the
// completion. Used by InPollContext() / ControlTransfer().
static volatile bool g_pollActive = false;
// Interrupt transfer data buffers (per slot)
static uint8_t* g_interruptDataBuf[MAX_SLOTS + 1] = {};
static uint64_t g_interruptDataBufPhys[MAX_SLOTS + 1] = {};
@@ -366,12 +373,40 @@ namespace Drivers::USB::Xhci {
return true;
}
// True when the caller is running inside PollEvents() (e.g. an HCI reply
// sent from a Bluetooth event handler). Such callers must fire-and-forget,
// not wait, since a nested PollEvents is a no-op.
bool InPollContext() {
return g_pollActive;
}
// -------------------------------------------------------------------------
// PollEvents - process event ring
// -------------------------------------------------------------------------
void PollEvents() {
while (true) {
// PollEvents runs both from the synchronous poll loops (ControlTransfer,
// SendCommand, firmware download) and from the xHCI MSI handler. On a
// single core the IRQ can preempt a poll loop mid-drain; if both advance
// g_evtRingDequeue / g_evtRingCCS the ring tracking desyncs and the
// cycle-bit check can start matching stale entries forever -> the boot
// freezes (observed wedging the Bluetooth firmware download at ~635 KB,
// where the dying device floods the event ring). Guard against re-entry:
// the interrupt is already acked (IMAN.IP cleared in HandleInterrupt) and
// the active poll loop drains these events itself. g_pollActive is also
// read by InPollContext() so command submitters (ControlTransfer) can
// tell they are nested and must fire-and-forget instead of waiting.
if (g_pollActive) return;
g_pollActive = true;
// Bound the work per call so a flooding/wedged device can never spin
// here forever; the outer wall-clock timeouts then fire instead of
// freezing. 4x the ring size is far above any legitimate burst.
constexpr uint32_t MAX_EVENTS_PER_CALL = EVT_RING_SIZE * 4;
uint32_t processed = 0, portEvts = 0, xferEvts = 0;
uint32_t lastType = 0, lastSlot = 0, lastEp = 0, lastCC = 0;
while (processed < MAX_EVENTS_PER_CALL) {
TRB& evt = g_evtRing[g_evtRingDequeue];
// Check if the cycle bit matches our expected cycle state
@@ -381,6 +416,7 @@ namespace Drivers::USB::Xhci {
}
uint32_t trbType = (evt.Control & TRB_TYPE_MASK) >> TRB_TYPE_SHIFT;
lastType = trbType;
switch (trbType) {
case TRB_COMMAND_COMPLETION: {
@@ -393,6 +429,7 @@ namespace Drivers::USB::Xhci {
}
case TRB_PORT_STATUS_CHANGE: {
portEvts++;
uint32_t portId = (evt.Parameter0 >> 24) & 0xFF;
uint32_t portsc = ReadOp(OP_PORTSC_BASE + (portId - 1) * OP_PORTSC_STRIDE);
// Clear change bits (write-1-to-clear)
@@ -412,6 +449,8 @@ namespace Drivers::USB::Xhci {
uint32_t slotId = (evt.Control >> 24) & 0xFF;
uint32_t epDci = (evt.Control >> 16) & 0x1F;
uint32_t residual = evt.Status & 0x00FFFFFF;
xferEvts++;
lastSlot = slotId; lastEp = epDci; lastCC = completionCode;
if (epDci == 1) {
// EP0 (DCI 1) - control transfer completion
@@ -437,9 +476,18 @@ namespace Drivers::USB::Xhci {
uint8_t intDci = dev.InterruptEpNum ? (dev.InterruptEpNum * 2 + 1) : 0;
if (epDci == bulkInDci && g_transferCallbacks[slotId]) {
// Bulk IN — dispatch via registered callback
uint16_t len = dev.BulkInMaxPacket;
if (residual < len) len = dev.BulkInMaxPacket - (uint16_t)residual;
// Bulk IN — dispatch via registered callback.
// len = actually-transferred bytes (requested -
// residual). A 0-byte / ZLP completion has
// residual == requested, so len MUST be 0: the old
// code left len at the full max-packet and handed
// the callback a slice of STALE DMA buffer, which
// the BT driver then counted as a bogus ACL packet
// (the constant rx flood) and pushed into its RX
// ring, crowding out the real Config Response.
uint32_t reqLen = dev.BulkInMaxPacket;
uint16_t len = (residual < reqLen)
? (uint16_t)(reqLen - residual) : 0;
g_transferCallbacks[slotId](slotId, epDci,
g_bulkInDataBuf[slotId], len, completionCode);
} else if (epDci == bulkOutDci && g_transferCallbacks[slotId]) {
@@ -495,6 +543,7 @@ namespace Drivers::USB::Xhci {
g_evtRingDequeue = 0;
g_evtRingCCS = !g_evtRingCCS;
}
processed++;
}
// Update ERDP to tell the controller we have processed events
@@ -504,6 +553,20 @@ namespace Drivers::USB::Xhci {
WriteRt(IR0_ERDP, (uint32_t)(erdp & 0xFFFFFFFF));
WriteRt(IR0_ERDP + 4, (uint32_t)(erdp >> 32));
// A full batch means the ring is being flooded -- surface the dominant
// event source (rate-limited) so a wedge is diagnosable, not silent.
if (processed >= MAX_EVENTS_PER_CALL) {
static uint32_t stormLogs = 0;
if (stormLogs < 8) {
stormLogs++;
KernelLogStream(WARNING, "xHCI") << "Event storm: " << (uint64_t)processed
<< "/call (port=" << (uint64_t)portEvts << " xfer=" << (uint64_t)xferEvts
<< " lastType=" << (uint64_t)lastType << " slot=" << (uint64_t)lastSlot
<< " ep=" << (uint64_t)lastEp << " cc=" << (uint64_t)lastCC << ")";
}
}
g_pollActive = false;
}
// -------------------------------------------------------------------------
@@ -559,8 +622,11 @@ namespace Drivers::USB::Xhci {
g_cmdCompleted = false;
WriteDoorbell(0, 0);
// Poll until command completes (with timeout)
for (uint32_t i = 0; i < 100000; i++) {
// Poll until command completes. Wall-clock bounded so a storm/wedge
// can't stretch this into a multi-second freeze; normal commands
// complete in well under a millisecond.
uint64_t cmdStart = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - cmdStart < 2000) {
PollEvents();
if (g_cmdCompleted) {
return g_cmdCompletionCode;
@@ -654,8 +720,22 @@ namespace Drivers::USB::Xhci {
g_xferCompleted = false;
WriteDoorbell(slotId, 1);
// Poll until transfer completes
for (uint32_t i = 0; i < 100000; i++) {
// If we are nested inside PollEvents (e.g. an HCI reply sent from a
// Bluetooth event handler), we cannot wait for completion here: the
// reentrancy guard makes a nested PollEvents a no-op, so the completion
// would never be observed and the timeout+recovery path would corrupt
// the EP0 ring. The transfer is submitted (doorbell rung); let the
// active PollEvents reap its completion. Fire-and-forget.
if (g_pollActive) {
return CC_SUCCESS;
}
// Poll until transfer completes. Wall-clock bounded (not iteration
// bounded) so a wedged device fails in ~2s and reports its cc, instead
// of the per-iteration cost ballooning under an event storm into a
// multi-second freeze with no output.
uint64_t xferStart = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - xferStart < 2000) {
PollEvents();
if (g_xferCompleted) {
return g_xferCompletionCode;
@@ -730,6 +810,43 @@ namespace Drivers::USB::Xhci {
WriteDoorbell(slotId, target);
}
// -------------------------------------------------------------------------
// ResetInterruptEndpoint - clear a halted interrupt IN endpoint and re-arm
// -------------------------------------------------------------------------
// A USB transaction error (cc=4) halts the endpoint; the host must issue
// Reset Endpoint + Set TR Dequeue before it will accept transfers again.
// Used by the Bluetooth firmware-download path, where a glitch on the event
// pipe near the end of a large upload otherwise kills event reception for
// good (no Command Complete / bootup events).
void ResetInterruptEndpoint(uint8_t slotId) {
if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) return;
UsbDeviceInfo& dev = g_devices[slotId];
if (dev.InterruptEpNum == 0 || !dev.InterruptRing) return;
uint8_t dci = dev.InterruptEpNum * 2 + 1;
TRB resetTrb = {};
resetTrb.Control = (TRB_RESET_ENDPOINT << TRB_TYPE_SHIFT)
| ((uint32_t)slotId << 24)
| ((uint32_t)dci << 16);
SendCommand(resetTrb);
uint64_t newDeq = dev.InterruptRingPhys
+ (uint64_t)dev.InterruptRingEnqueue * sizeof(TRB);
if (dev.InterruptRingCCS) newDeq |= 1; // DCS bit
TRB deqTrb = {};
deqTrb.Parameter0 = (uint32_t)(newDeq & 0xFFFFFFFF);
deqTrb.Parameter1 = (uint32_t)(newDeq >> 32);
deqTrb.Control = (TRB_SET_TR_DEQUEUE << TRB_TYPE_SHIFT)
| ((uint32_t)slotId << 24)
| ((uint32_t)dci << 16);
SendCommand(deqTrb);
// Re-arm reception.
QueueInterruptTransfer(slotId);
}
// -------------------------------------------------------------------------
// QueueBulkInTransfer
// -------------------------------------------------------------------------