feat: Intel BT firmware download, A2dp & Bluetooth audio progress
This commit is contained in:
@@ -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
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user