feat: A2DP bluetooth audio working

This commit is contained in:
2026-06-10 16:00:42 +02:00
parent 4eb4ba4073
commit e0c8787727
18 changed files with 1312 additions and 4340 deletions
+20 -6
View File
@@ -5,6 +5,7 @@
*/
#include "Xhci.hpp"
#include <atomic>
#include "UsbDevice.hpp"
#include "HidKeyboard.hpp"
#include "HidMouse.hpp"
@@ -112,7 +113,17 @@ namespace Drivers::USB::Xhci {
// 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;
//
// MUST be a real atomic claimed with compare-exchange, not a plain
// volatile bool: PollEvents runs from the MSI handler (BSP) AND from
// syscall-context pumps (A2DP WriteAudio/StartSource on whatever core the
// app runs on) AND from idle cores (ProcessDeferredWork). A check-then-set
// on a volatile is a cross-core TOCTOU: two cores both pass the check,
// both drain the shared event ring, g_evtRingDequeue/g_evtRingCCS desync,
// and completions get double-delivered (duplicate ACL packets) or lost
// (missed AVDTP responses) -- both observed on HW once audio streaming
// started pumping from syscall context.
static std::atomic<bool> g_pollActive{false};
// Interrupt transfer data buffers (per slot)
static uint8_t* g_interruptDataBuf[MAX_SLOTS + 1] = {};
@@ -377,7 +388,7 @@ namespace Drivers::USB::Xhci {
// 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;
return g_pollActive.load(std::memory_order_relaxed);
}
// -------------------------------------------------------------------------
@@ -396,8 +407,11 @@ namespace Drivers::USB::Xhci {
// 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;
bool expected = false;
if (!g_pollActive.compare_exchange_strong(expected, true,
std::memory_order_acquire)) {
return; // another context is draining; it will reap our events
}
// Bound the work per call so a flooding/wedged device can never spin
// here forever; the outer wall-clock timeouts then fire instead of
@@ -566,7 +580,7 @@ namespace Drivers::USB::Xhci {
}
}
g_pollActive = false;
g_pollActive.store(false, std::memory_order_release);
}
// -------------------------------------------------------------------------
@@ -726,7 +740,7 @@ namespace Drivers::USB::Xhci {
// 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) {
if (g_pollActive.load(std::memory_order_relaxed)) {
return CC_SUCCESS;
}