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

This commit is contained in:
2026-06-03 18:05:17 +02:00
parent 52b01a7d73
commit c119a70d5b
22 changed files with 6452 additions and 273 deletions
+590 -34
View File
@@ -6,6 +6,7 @@
#include "Hci.hpp"
#include "L2cap.hpp"
#include <Fs/Vfs.hpp>
#include <Drivers/USB/Xhci.hpp>
#include <Drivers/USB/UsbDevice.hpp>
#include <Terminal/Terminal.hpp>
@@ -31,14 +32,27 @@ namespace Drivers::USB::Bluetooth::Hci {
static volatile uint32_t g_eventLen = 0;
static volatile bool g_eventReady = false;
// ACL receive buffer
static uint8_t g_aclRxBuf[1024] = {};
static volatile uint32_t g_aclRxLen = 0;
static volatile bool g_aclRxReady = false;
// ACL receive ring buffer. The bulk-IN callback (nested under PollEvents)
// only copies an incoming packet into a slot; DrainEvents() processes them
// at top level. A ring (not a single buffer) is required because the headset
// bursts many ACL packets at once -- the single buffer was overwriting and
// dropping the L2CAP Config Response, leaving our channel half-configured.
static constexpr int ACL_RX_SLOTS = 32;
static constexpr int ACL_RX_SLOT_SIZE = 1024;
static uint8_t g_aclRxRing[ACL_RX_SLOTS][ACL_RX_SLOT_SIZE] = {};
static volatile uint16_t g_aclRxLens[ACL_RX_SLOTS] = {};
static volatile uint8_t g_aclRxHead = 0;
static volatile uint8_t g_aclRxTail = 0;
// ACL transmit DMA buffer
static uint8_t* g_aclTxBuf = nullptr;
static uint64_t g_aclTxBufPhys = 0;
// ACL transmit DMA buffers (a ring, not one): SendAcl queues an async bulk
// OUT transfer, so two sends in quick succession (e.g. our Config Request
// then a Config Response, both fired while DrainEvents processes a burst)
// would have the second overwrite the first buffer before it is DMA'd to the
// wire -- corrupting the first packet. Rotate buffers to avoid that.
static constexpr int ACL_TX_SLOTS = 8;
static uint8_t* g_aclTxRing[ACL_TX_SLOTS] = {};
static uint64_t g_aclTxRingPhys[ACL_TX_SLOTS] = {};
static uint8_t g_aclTxSlot = 0;
// HCI command DMA buffer (separate from ACL to avoid conflicts)
static uint8_t* g_cmdDmaBuf = nullptr;
@@ -52,11 +66,179 @@ namespace Drivers::USB::Bluetooth::Hci {
static uint16_t g_aclMaxNum = 0;
static volatile uint16_t g_aclPendingCount = 0;
// Diagnostic ACL data-path counters: TX submitted, TX completed (bulk OUT
// completion), RX received (bulk IN). Used to tell whether L2CAP signaling
// actually flows over ACL after encryption is enabled.
static volatile uint32_t g_aclTxCount = 0;
static volatile uint32_t g_aclTxDoneCount = 0;
static volatile uint32_t g_aclRxCount = 0;
// Incremented when an incoming ACL packet is dropped because the RX ring was
// full -- a nonzero value means a burst overran the ring (and could have
// dropped an L2CAP Config Response). Surfaced by DumpAclStats().
static volatile uint32_t g_aclRxDropCount = 0;
// Inquiry results
static InquiryDevice g_inquiryResults[MAX_INQUIRY_RESULTS] = {};
static volatile int g_inquiryResultCount = 0;
static volatile bool g_inquiryActive = false;
// Set when the Intel "bootup" vendor event arrives after a firmware boot.
// Written from the USB transfer callback (ProcessEvent), polled by
// IntelBootFirmware, hence volatile.
static volatile bool g_intelBootup = false;
// Latest Intel "secure send result" vendor event (0xFF sub-opcode 0x06).
// The bootloader uses this, not Command Complete, to report the outcome of
// secure-send (0xFC09) firmware download. Written from ProcessEvent.
static volatile bool g_secureResultValid = false;
static volatile uint8_t g_secureResult = 0; // 0 = success
static volatile uint8_t g_secureStatus = 0; // 0 = success
// Firmware-download diagnostics. g_ssBytesSent / g_ssFragsSent accumulate
// the bytes / 0xFC09 fragments handed to the controller since the last
// ClearSecureSendResult(), so an async 0xFF/0x06 result or a fragment error
// can be pinned to an exact upload position. g_lastControlCC is the xHCI
// completion code of the most recent SendCommand() control transfer.
static volatile uint64_t g_ssBytesSent = 0;
static volatile uint32_t g_ssFragsSent = 0;
static volatile uint32_t g_lastControlCC = 0;
// Lockless HCI-event trace for diagnosing the pairing/SSP sequence.
// ProcessEvent (which may run from the xHCI IRQ) only does an array write
// here -- no lock, no terminal I/O, so it cannot deadlock against g_termLock.
// DumpEventTrace() prints it from top-level (process) context.
static constexpr int EVT_TRACE_MAX = 48;
static volatile uint8_t g_evtTrace[EVT_TRACE_MAX] = {};
static volatile uint8_t g_evtTraceCount = 0;
// Status byte of the last Simple Pairing Complete (0x36) / Auth Complete
// (0x06) / Disconnection (0x05) -- captured to find WHY pairing fails.
static volatile uint8_t g_lastSppStatus = 0xEE;
static volatile uint8_t g_lastAuthStatus = 0xEE;
static volatile uint8_t g_lastDiscReason = 0xEE;
// Pending-command queue. Pairing replies (IO-cap / user-confirm / link-key)
// are triggered from event handlers running NESTED under PollEvents, where a
// command can only be fire-and-forget (the reentrancy guard makes a nested
// wait a no-op). Fire-and-forget proved unreliable for SSP -- the IO-cap
// value feeds the authentication confirmation, so a late/garbled reply makes
// the DHKey check fail (Simple Pairing Complete status 0x05). Instead the
// handlers ENQUEUE the reply here; ProcessPendingCommands() sends it later
// from top-level (process) context with a real, confirmed transfer.
struct PendingHciCmd { uint16_t opcode; uint8_t len; uint8_t params[16]; };
static PendingHciCmd g_pending[16] = {};
static volatile uint8_t g_pendingHead = 0;
static volatile uint8_t g_pendingTail = 0;
static void EnqueueHciCmd(uint16_t opcode, const uint8_t* params, uint8_t len) {
uint8_t next = (uint8_t)((g_pendingHead + 1) & 15);
if (next == g_pendingTail) return; // full -> drop (should never happen)
if (len > 16) len = 16;
g_pending[g_pendingHead].opcode = opcode;
g_pending[g_pendingHead].len = len;
for (uint8_t i = 0; i < len; i++) g_pending[g_pendingHead].params[i] = params[i];
g_pendingHead = next;
}
// =========================================================================
// Bonded-device link key store
// =========================================================================
// Persisted to disk so a pairing survives reboots. Without it a once-paired
// device challenges us for the link key on reconnect, we have nothing to
// answer with, and authentication fails -> the remote drops the link with
// disconnect reason 0x05. On EVT_LINK_KEY_NOTIFICATION we cache the new key
// (RAM, marked dirty); FlushLinkKeys() writes it from process context so the
// disk I/O never blocks mid-pairing while nested under PollEvents.
static constexpr int MAX_BONDS = 8;
static constexpr uint32_t LINK_KEY_MAGIC = 0x314B5442; // 'BTK1'
struct StoredLinkKey {
uint8_t addr[6];
uint8_t key[16];
bool valid;
};
static StoredLinkKey g_bonds[MAX_BONDS] = {};
static bool g_bondsDirty = false;
static bool AddrEq(const uint8_t* a, const uint8_t* b) {
for (int i = 0; i < 6; i++) if (a[i] != b[i]) return false;
return true;
}
static int FindBondIndex(const uint8_t* addr) {
for (int i = 0; i < MAX_BONDS; i++) {
if (g_bonds[i].valid && AddrEq(g_bonds[i].addr, addr)) return i;
}
return -1;
}
static void StoreLinkKey(const uint8_t* addr, const uint8_t* key) {
int idx = FindBondIndex(addr);
if (idx < 0) {
for (int i = 0; i < MAX_BONDS; i++) {
if (!g_bonds[i].valid) { idx = i; break; }
}
if (idx < 0) idx = 0; // table full: recycle the first slot
}
memcpy(g_bonds[idx].addr, addr, 6);
memcpy(g_bonds[idx].key, key, 16);
g_bonds[idx].valid = true;
g_bondsDirty = true; // FlushLinkKeys() persists from safe context
}
// On-disk layout: [magic u32][MAX_BONDS x { addr[6], key[16], valid[1] }].
static constexpr uint64_t LINK_KEY_BLOB_SIZE = 4 + MAX_BONDS * 23;
void LoadLinkKeys() {
Fs::Vfs::BackendFile f;
if (Fs::Vfs::OpenBackendFile("0:/os/btkeys.bin", f) < 0) return; // none stored yet
uint64_t size = Fs::Vfs::GetBackendFileSize(f);
if (size < LINK_KEY_BLOB_SIZE) { Fs::Vfs::CloseBackendFile(f); return; }
uint8_t blob[LINK_KEY_BLOB_SIZE];
Fs::Vfs::ReadBackendFile(f, blob, 0, LINK_KEY_BLOB_SIZE);
Fs::Vfs::CloseBackendFile(f);
uint32_t magic = (uint32_t)blob[0] | ((uint32_t)blob[1] << 8)
| ((uint32_t)blob[2] << 16) | ((uint32_t)blob[3] << 24);
if (magic != LINK_KEY_MAGIC) return;
int off = 4, n = 0;
for (int i = 0; i < MAX_BONDS; i++) {
memcpy(g_bonds[i].addr, &blob[off], 6); off += 6;
memcpy(g_bonds[i].key, &blob[off], 16); off += 16;
g_bonds[i].valid = blob[off++] != 0;
if (g_bonds[i].valid) n++;
}
g_bondsDirty = false;
KernelLogStream(INFO, "BT-HCI") << "Loaded " << (uint64_t)n << " bonded device key(s)";
}
void FlushLinkKeys() {
if (!g_bondsDirty) return;
uint8_t blob[LINK_KEY_BLOB_SIZE] = {};
blob[0] = (uint8_t)(LINK_KEY_MAGIC);
blob[1] = (uint8_t)(LINK_KEY_MAGIC >> 8);
blob[2] = (uint8_t)(LINK_KEY_MAGIC >> 16);
blob[3] = (uint8_t)(LINK_KEY_MAGIC >> 24);
int off = 4;
for (int i = 0; i < MAX_BONDS; i++) {
memcpy(&blob[off], g_bonds[i].addr, 6); off += 6;
memcpy(&blob[off], g_bonds[i].key, 16); off += 16;
blob[off++] = g_bonds[i].valid ? 1 : 0;
}
Fs::Vfs::BackendFile f;
if (Fs::Vfs::CreateBackendFile("0:/os/btkeys.bin", f) < 0) {
KernelLogStream(WARNING, "BT-HCI") << "Could not open link key store for writing";
return;
}
Fs::Vfs::WriteBackendFile(f, blob, 0, LINK_KEY_BLOB_SIZE);
Fs::Vfs::CloseBackendFile(f);
g_bondsDirty = false;
KernelLogStream(OK, "BT-HCI") << "Link key store persisted";
}
// =========================================================================
// USB transfer callback
// =========================================================================
@@ -93,18 +275,38 @@ namespace Drivers::USB::Bluetooth::Hci {
// Re-queue interrupt transfer for next event
Xhci::QueueInterruptTransfer(slotId);
} else if (epDci == bulkInDci && data && length > 0) {
// ACL data received on bulk IN
uint32_t copyLen = length;
if (copyLen > sizeof(g_aclRxBuf)) copyLen = sizeof(g_aclRxBuf);
memcpy(g_aclRxBuf, data, copyLen);
g_aclRxLen = copyLen;
g_aclRxReady = true;
} else if (epDci == bulkInDci) {
// ACL data received on bulk IN -> copy into the ring; processed by
// DrainEvents() at top level (do NOT process here, nested).
// Only enqueue a packet big enough to carry an L2CAP header (ACL
// header + L2CAP header = 8 bytes). Smaller completions are ZLP /
// re-arm artifacts and the firmware-phase bulk-IN runts (4-7 bytes,
// the old "rx flood") -- ProcessPacket would reject them anyway, and
// dropping them here keeps the ring + rx stats clean. We STILL
// re-arm on every completion below (the bulk IN must keep cycling to
// absorb the device's ~635 KB cc=4 glitch during firmware download).
if (data && length >= sizeof(AclHeader) + 4) {
g_aclRxCount++;
uint8_t next = (uint8_t)((g_aclRxHead + 1) % ACL_RX_SLOTS);
if (next != g_aclRxTail) { // ring not full
uint32_t copyLen = length;
if (copyLen > ACL_RX_SLOT_SIZE) copyLen = ACL_RX_SLOT_SIZE;
memcpy(g_aclRxRing[g_aclRxHead], data, copyLen);
g_aclRxLens[g_aclRxHead] = (uint16_t)copyLen;
g_aclRxHead = next;
} else {
g_aclRxDropCount++; // ring overran -> a packet was lost
}
}
// Re-queue bulk IN transfer
Xhci::QueueBulkInTransfer(slotId, nullptr, 0, dev->BulkInMaxPacket);
// Re-queue bulk IN transfer (only on a real success/short completion;
// the error path passes data==nullptr and is handled elsewhere).
if (data) {
Xhci::QueueBulkInTransfer(slotId, nullptr, 0, dev->BulkInMaxPacket);
}
} else if (epDci == (dev->BulkOutEpNum ? (uint8_t)(dev->BulkOutEpNum * 2) : (uint8_t)0)) {
// Bulk OUT completion — decrement pending count
g_aclTxDoneCount++;
if (g_aclPendingCount > 0) g_aclPendingCount--;
}
}
@@ -145,8 +347,10 @@ namespace Drivers::USB::Bluetooth::Hci {
g_cmdDmaBuf = (uint8_t*)Memory::g_pfa->AllocateZeroed();
g_cmdDmaBufPhys = Memory::SubHHDM(g_cmdDmaBuf);
g_aclTxBuf = (uint8_t*)Memory::g_pfa->AllocateZeroed();
g_aclTxBufPhys = Memory::SubHHDM(g_aclTxBuf);
for (int i = 0; i < ACL_TX_SLOTS; i++) {
g_aclTxRing[i] = (uint8_t*)Memory::g_pfa->AllocateZeroed();
g_aclTxRingPhys[i] = Memory::SubHHDM(g_aclTxRing[i]);
}
// NOTE: Do NOT queue interrupt IN or bulk IN transfers here.
// The BT controller is not yet HCI-initialized and may misbehave.
@@ -156,7 +360,16 @@ namespace Drivers::USB::Bluetooth::Hci {
KernelLogStream(OK, "BT-HCI") << "HCI transport initialized on slot " << (uint64_t)slotId;
}
// Start receiving HCI events and ACL data — call after HCI init sequence
// Start receiving HCI events and ACL data — call after HCI init sequence.
// Arms BOTH the interrupt IN (events) and the bulk IN (ACL). The bulk IN
// MUST stay armed across the firmware download: on this controller the device
// glitches a USB transaction error (cc=4) near ~635 KB of the upload, and an
// armed bulk IN ABSORBS it (a benign cc=4 on the bulk endpoint) so the
// interrupt-IN event pipe survives and the download completes. Deferring the
// bulk-IN arm (build 40) moved that cc=4 onto the interrupt IN and wedged the
// download at 635 KB -- reverted. The harmless firmware-phase bulk-IN runts
// (4-7 byte completions) are filtered at enqueue in TransferCallback so they
// never pollute the RX ring.
void StartEventPipe() {
if (!g_initialized) return;
@@ -207,6 +420,8 @@ namespace Drivers::USB::Bluetooth::Hci {
g_cmdDmaBuf,
false); // dirIn = false (host to device)
g_lastControlCC = cc;
if (cc != Xhci::CC_SUCCESS) {
KernelLogStream(WARNING, "BT-HCI") << "SendCommand failed, opcode="
<< base::hex << (uint64_t)opcode << " cc=" << base::dec << (uint64_t)cc;
@@ -222,6 +437,12 @@ namespace Drivers::USB::Bluetooth::Hci {
bool WaitCommandComplete(uint16_t opcode, uint8_t* outParams,
uint8_t maxLen, uint32_t timeoutMs) {
// Nested inside PollEvents (an event handler issued this command): we
// cannot wait -- a nested PollEvents is a no-op, so the Command Complete
// is reaped by the active PollEvents after we return. The command was
// already submitted (fire-and-forget); report success.
if (Xhci::InPollContext()) return true;
uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
@@ -275,6 +496,9 @@ namespace Drivers::USB::Bluetooth::Hci {
// =========================================================================
bool WaitCommandStatus(uint16_t opcode, uint32_t timeoutMs) {
// See WaitCommandComplete: cannot wait when nested under PollEvents.
if (Xhci::InPollContext()) return true;
uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
@@ -311,25 +535,44 @@ namespace Drivers::USB::Bluetooth::Hci {
// =========================================================================
bool SendAcl(uint16_t handle, uint16_t pbFlag, const uint8_t* data, uint16_t len) {
if (!g_initialized || !g_aclTxBuf) return false;
if (!g_initialized || !g_aclTxRing[0]) return false;
if (len + sizeof(AclHeader) > 4096) return false; // Single page DMA buffer
// Use the next TX ring slot so a rapid second send can't overwrite this
// packet before its bulk OUT transfer DMAs it to the wire.
uint8_t* txBuf = g_aclTxRing[g_aclTxSlot];
uint64_t txPhys = g_aclTxRingPhys[g_aclTxSlot];
g_aclTxSlot = (uint8_t)((g_aclTxSlot + 1) % ACL_TX_SLOTS);
// Build ACL packet in DMA buffer
auto* hdr = (AclHeader*)g_aclTxBuf;
auto* hdr = (AclHeader*)txBuf;
hdr->HandleFlags = (handle & 0x0FFF) | pbFlag;
hdr->DataLength = len;
if (data && len > 0) {
memcpy(g_aclTxBuf + sizeof(AclHeader), data, len);
memcpy(txBuf + sizeof(AclHeader), data, len);
}
uint32_t totalLen = sizeof(AclHeader) + len;
g_aclPendingCount++;
Xhci::QueueBulkOutTransfer(g_slotId, g_aclTxBuf, g_aclTxBufPhys, totalLen);
g_aclTxCount++;
Xhci::QueueBulkOutTransfer(g_slotId, txBuf, txPhys, totalLen);
return true;
}
uint16_t AclPendingCount() { return g_aclPendingCount; }
uint16_t AclMaxPackets() { return g_aclMaxNum; }
void DumpAclStats() {
KernelLogStream(INFO, "BT-HCI") << "ACL stats: tx=" << (uint64_t)g_aclTxCount
<< " txDone=" << (uint64_t)g_aclTxDoneCount
<< " rx=" << (uint64_t)g_aclRxCount
<< " rxDrop=" << (uint64_t)g_aclRxDropCount
<< " pending=" << (uint64_t)g_aclPendingCount
<< " bufNum=" << (uint64_t)g_aclMaxNum;
}
// =========================================================================
// ProcessEvent — handle HCI events
// =========================================================================
@@ -341,6 +584,17 @@ namespace Drivers::USB::Bluetooth::Hci {
uint8_t evtParamLen = data[1];
const uint8_t* params = data + 2;
// Record into the lockless trace (safe from the IRQ path: array write
// only, no lock / no terminal I/O). DumpEventTrace() prints it later
// from top-level context.
if (evtCode != EVT_NUM_COMPLETED_PACKETS && g_evtTraceCount < EVT_TRACE_MAX) {
g_evtTrace[g_evtTraceCount++] = evtCode;
}
// status byte = params[0] for these (Disconnection: status,handle,reason)
if (evtCode == EVT_SIMPLE_PAIRING_COMPLETE && evtParamLen >= 1) g_lastSppStatus = params[0];
if (evtCode == EVT_AUTH_COMPLETE && evtParamLen >= 1) g_lastAuthStatus = params[0];
if (evtCode == EVT_DISCONNECTION_COMPLETE && evtParamLen >= 4) g_lastDiscReason = params[3];
switch (evtCode) {
case EVT_CONNECTION_COMPLETE: {
if (evtParamLen >= 11) {
@@ -430,18 +684,68 @@ namespace Drivers::USB::Bluetooth::Hci {
memcpy(reply, &params[0], 6); // BD_ADDR
reply[6] = 0x03; // IO Capability: NoInputNoOutput
reply[7] = 0x00; // OOB data not present
reply[8] = 0x00; // Authentication requirements: MITM not required
SendCommand(OP_IO_CAPABILITY_REPLY, reply, 9);
WaitCommandComplete(OP_IO_CAPABILITY_REPLY, nullptr, 0, 1000);
reply[8] = 0x04; // Auth req: MITM not required, General Bonding
// (0x04, not 0x00 "No Bonding") so a
// persistent link key is created -> the bond
// survives reboots via the link-key store.
// Queue for reliable top-level delivery (see EnqueueHciCmd):
// the IO-cap value feeds the SSP confirmation, so it must be
// sent intact, not fire-and-forget.
EnqueueHciCmd(OP_IO_CAPABILITY_REPLY, reply, 9);
}
break;
}
case EVT_USER_CONFIRM_REQUEST: {
if (evtParamLen >= 6) {
// Auto-confirm
SendCommand(OP_USER_CONFIRM_REPLY, &params[0], 6);
WaitCommandComplete(OP_USER_CONFIRM_REPLY, nullptr, 0, 1000);
// Auto-confirm (Just Works). Queue for reliable top-level
// delivery -- a late confirm makes pairing fail (spp=0x05).
EnqueueHciCmd(OP_USER_CONFIRM_REPLY, &params[0], 6);
}
break;
}
case EVT_LINK_KEY_REQUEST: {
if (evtParamLen >= 6) {
int idx = FindBondIndex(&params[0]);
if (idx >= 0) {
// We remember this device: hand back the stored key so
// authentication succeeds without re-pairing.
uint8_t reply[22];
memcpy(reply, &params[0], 6);
memcpy(&reply[6], g_bonds[idx].key, 16);
EnqueueHciCmd(OP_LINK_KEY_REQ_REPLY, reply, 22);
} else {
// Unknown device: tell the controller we have no key so
// the remote falls back to fresh Secure Simple Pairing
// (Just Works) instead of failing auth (reason 0x05).
EnqueueHciCmd(OP_LINK_KEY_REQ_NEG_REPLY, &params[0], 6);
}
}
break;
}
case EVT_LINK_KEY_NOTIFICATION: {
// Pairing produced a new link key: BD_ADDR[6] + key[16] + type[1].
// Cache it now (fast); FlushLinkKeys() persists it to disk from
// process context so the disk write never stalls this nested
// event handler mid-pairing.
if (evtParamLen >= 22) {
StoreLinkKey(&params[0], &params[6]);
KernelLogStream(INFO, "BT-HCI") << "Link key notification (new bond cached)";
}
break;
}
case EVT_AUTH_COMPLETE: {
// Authentication succeeded -> turn on encryption. The link must
// be encrypted before A2DP; without this the headset finishes
// pairing, waits for encryption that never comes, and drops us
// (reason 0x05). Set Connection Encryption = handle(2) + 0x01.
// Queue for reliable top-level delivery.
if (evtParamLen >= 3 && params[0] == 0) {
uint8_t enc[3] = { params[1], params[2], 0x01 };
EnqueueHciCmd(OP_SET_CONN_ENCRYPT, enc, 3);
}
break;
}
@@ -542,6 +846,31 @@ namespace Drivers::USB::Bluetooth::Hci {
break;
}
case EVT_VENDOR_SPECIFIC: {
// Intel vendor events carry a sub-opcode in the first byte.
// 0x02 = "bootup" (operational firmware booted after 0xFC01)
// 0x06 = "secure send result": result(1) opcode(2) status(1)
uint8_t sub = (evtParamLen >= 1) ? params[0] : 0xFF;
if (sub == 0x02) {
g_intelBootup = true;
} else if (sub == 0x06 && evtParamLen >= 5) {
g_secureResult = params[1];
g_secureStatus = params[4];
g_secureResultValid = true;
// A healthy bootloader stays silent until the final
// fragment, so the byte/frag position here pins exactly
// where it reacted -- and a non-zero result/status mid
// upload is the signature of an active rejection.
bool err = (params[1] != 0) || (params[4] != 0);
KernelLogStream(err ? ERROR : INFO, "BT-HCI") << "secure-send result="
<< base::hex << (uint64_t)params[1] << " status="
<< (uint64_t)params[4] << base::dec
<< " @ byte " << g_ssBytesSent
<< " frag " << (uint64_t)g_ssFragsSent;
}
break;
}
default:
break;
}
@@ -658,6 +987,195 @@ namespace Drivers::USB::Bluetooth::Hci {
return true;
}
// =========================================================================
// Intel firmware download primitives
// =========================================================================
int ReadIntelVersionTlv(uint8_t* outBuf, int maxLen) {
if (!outBuf || maxLen <= 0) return -1;
uint8_t param = 0xFF;
if (!SendCommand(OP_INTEL_READ_VERSION, &param, 1)) return -1;
// Wait for the matching Command Complete and copy the *actually
// received* return parameters. The TLV version response can exceed a
// single interrupt packet; WaitCommandComplete trusts the event's
// declared length, so we read g_eventBuf/g_eventLen directly to avoid
// copying past what the controller delivered.
uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < 2000) {
Xhci::PollEvents();
if (g_eventReady) {
g_eventReady = false;
if (g_eventLen >= 5 && g_eventBuf[0] == EVT_COMMAND_COMPLETE) {
uint16_t op = (uint16_t)g_eventBuf[3] | ((uint16_t)g_eventBuf[4] << 8);
if (op == OP_INTEL_READ_VERSION) {
// Return params begin at byte 5 (status, then TLVs).
int avail = (int)g_eventLen - 5;
if (avail < 0) avail = 0;
int n = (avail < maxLen) ? avail : maxLen;
memcpy(outBuf, &g_eventBuf[5], n);
return n;
}
}
}
for (int j = 0; j < 100; j++) asm volatile("" ::: "memory");
}
KernelLogStream(WARNING, "BT-HCI") << "ReadIntelVersionTlv timeout";
return -1;
}
void ClearSecureSendResult() {
g_secureResultValid = false;
g_ssBytesSent = 0;
g_ssFragsSent = 0;
}
bool PeekSecureSendResult(uint8_t* outResult, uint8_t* outStatus) {
if (!g_secureResultValid) return false;
if (outResult) *outResult = g_secureResult;
if (outStatus) *outStatus = g_secureStatus;
return true;
}
void ResetEventTrace() {
g_evtTraceCount = 0;
g_lastSppStatus = 0xEE;
g_lastAuthStatus = 0xEE;
g_lastDiscReason = 0xEE;
}
void DumpEventTrace() {
// Top-level only (acquires g_termLock). Shows the HCI-event sequence so
// a stalled pairing/SSP flow is visible, e.g. whether an IO Capability
// Request (0x31) / User Confirm (0x33) arrive after a link-key reply, or
// the link just goes 0x03 (connect) -> 0x17 (link-key req) -> 0x05
// (disconnect/auth-fail) with no pairing in between.
KernelLogStream s(INFO, "BT-HCI");
s << "event trace:";
uint8_t n = g_evtTraceCount;
if (n > EVT_TRACE_MAX) n = EVT_TRACE_MAX;
for (uint8_t i = 0; i < n; i++) {
s << " " << base::hex << (uint64_t)g_evtTrace[i] << base::dec;
}
s << " | spp=" << base::hex << (uint64_t)g_lastSppStatus
<< " auth=" << (uint64_t)g_lastAuthStatus
<< " disc=" << (uint64_t)g_lastDiscReason << base::dec;
}
void ProcessPendingCommands() {
// Top-level only: drains queued pairing replies with real, confirmed
// control transfers. Must NOT be called from inside PollEvents.
if (Xhci::InPollContext()) return;
while (g_pendingTail != g_pendingHead) {
PendingHciCmd c = g_pending[g_pendingTail];
g_pendingTail = (uint8_t)((g_pendingTail + 1) & 15);
SendCommand(c.opcode, c.params, c.len);
// Set Connection Encryption returns Command Status (not Complete),
// so wait on that instead of burning a 1s timeout that delays A2DP.
if (c.opcode == OP_SET_CONN_ENCRYPT) {
WaitCommandStatus(c.opcode, 1000);
} else {
WaitCommandComplete(c.opcode, nullptr, 0, 1000);
}
}
}
bool WaitSecureSendResult(uint32_t timeoutMs, uint8_t* outResult, uint8_t* outStatus) {
uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
Xhci::PollEvents();
if (g_secureResultValid) {
if (outResult) *outResult = g_secureResult;
if (outStatus) *outStatus = g_secureStatus;
return true;
}
for (int j = 0; j < 100; j++) asm volatile("" ::: "memory");
}
return false;
}
bool IntelSecureSend(uint8_t fragmentType, const uint8_t* data, uint32_t len) {
uint32_t off = 0;
while (len > 0) {
uint8_t frag = (len > 252) ? 252 : (uint8_t)len;
// Fragment: [type][up to 252 data bytes]
uint8_t buf[253];
buf[0] = fragmentType;
memcpy(&buf[1], data + off, frag);
// The Intel bootloader does NOT return a Command Complete for
// 0xFC09 (Linux's btusb injects a fake one). Pacing comes from the
// synchronous USB transfer in SendCommand; the download outcome is
// reported asynchronously via the 0xFF/0x06 secure-send result
// event. So: send, drain events, and move on -- do not block per
// fragment waiting for a reply that never arrives.
if (!SendCommand(OP_INTEL_SECURE_SEND, buf, (uint8_t)(frag + 1))) {
KernelLogStream(ERROR, "BT-HCI") << "Secure send transport error: type="
<< base::hex << (uint64_t)fragmentType << " cc=" << (uint64_t)g_lastControlCC
<< base::dec << " at frag #" << (uint64_t)g_ssFragsSent
<< " byte " << g_ssBytesSent;
return false;
}
Xhci::PollEvents();
g_ssBytesSent += frag;
g_ssFragsSent++;
len -= frag;
off += frag;
}
return true;
}
bool IntelBootFirmware(uint32_t bootAddr, uint32_t timeoutMs) {
g_intelBootup = false;
// intel_reset: reset_type=0x00, patch_enable=0x01, ddc_reload=0x00,
// boot_option=0x01 (boot at specified address), boot_param (LE32).
uint8_t params[8] = {
0x00, 0x01, 0x00, 0x01,
(uint8_t)(bootAddr & 0xFF),
(uint8_t)((bootAddr >> 8) & 0xFF),
(uint8_t)((bootAddr >> 16) & 0xFF),
(uint8_t)((bootAddr >> 24) & 0xFF),
};
// Fire and forget: the controller reboots into operational firmware
// and signals readiness via the Intel bootup vendor event rather than
// a Command Complete for 0xFC01.
if (!SendCommand(OP_INTEL_RESET, params, sizeof(params))) return false;
uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
Xhci::PollEvents();
if (g_intelBootup) return true;
for (int j = 0; j < 100; j++) asm volatile("" ::: "memory");
}
KernelLogStream(ERROR, "BT-HCI") << "Timed out waiting for Intel bootup event";
return false;
}
bool IntelWriteDdcRecord(const uint8_t* record, uint8_t recordLen) {
if (!record || recordLen == 0) return false;
if (!SendCommand(OP_INTEL_DDC_CONFIG_WRITE, record, recordLen)) return false;
uint8_t st[4] = {};
if (!WaitCommandComplete(OP_INTEL_DDC_CONFIG_WRITE, st, sizeof(st), 2000)) return false;
return st[0] == 0;
}
bool IntelSetEventMask() {
// Enables the Intel vendor events used during/after firmware load.
uint8_t mask[8] = { 0x87, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
if (!SendCommand(OP_INTEL_SET_EVENT_MASK, mask, sizeof(mask))) return false;
return WaitCommandComplete(OP_INTEL_SET_EVENT_MASK, nullptr, 0, 2000);
}
bool WriteLocalName(const char* name) {
uint8_t params[248] = {};
int i = 0;
@@ -695,6 +1213,24 @@ namespace Drivers::USB::Bluetooth::Hci {
return WaitCommandStatus(OP_ACCEPT_CONN_REQ);
}
bool AuthenticateLink(uint16_t handle) {
// Request authentication on an established ACL link. As the connection
// initiator we must drive this: a bonded headset will not start pairing
// on its own for a device it believes it already knows, so the link just
// sits unauthenticated until it drops (reason 0x05). This kicks off the
// flow -- the controller raises a Link Key Request (we answer with a
// stored key, or negatively to force fresh Secure Simple Pairing).
uint8_t params[2] = { (uint8_t)(handle & 0xFF), (uint8_t)(handle >> 8) };
if (!SendCommand(OP_AUTH_REQUESTED, params, 2)) return false;
return WaitCommandStatus(OP_AUTH_REQUESTED, 2000);
}
bool SetBdAddr(const uint8_t* addr) {
// Intel 0xFC31: 6-byte BD_ADDR, little-endian (same order as ReadBdAddr).
if (!SendCommand(OP_INTEL_WRITE_BD_ADDR, addr, 6)) return false;
return WaitCommandComplete(OP_INTEL_WRITE_BD_ADDR, nullptr, 0, 2000);
}
bool Disconnect(uint16_t handle, uint8_t reason) {
uint8_t params[3] = {
(uint8_t)(handle & 0xFF),
@@ -786,12 +1322,32 @@ namespace Drivers::USB::Bluetooth::Hci {
g_eventReady = false;
}
// Drain ACL data
if (g_aclRxReady) {
g_aclRxReady = false;
if (g_aclRxLen > 0) {
ProcessAcl(g_aclRxBuf, g_aclRxLen);
// Drain all queued ACL packets (process the whole ring, not just one,
// so a burst is never left unhandled).
while (g_aclRxTail != g_aclRxHead) {
uint8_t slot = g_aclRxTail;
uint16_t pl = g_aclRxLens[slot];
// Diagnostic (top-level, safe to log -- not the IRQ path): trace the
// first few ACL packets so the rx flood / missing Config Response is
// identifiable in ONE boot. Layout: ACL header(4) + L2CAP header(4),
// so the L2CAP CID is at bytes 6-7; on the signaling channel (CID 1)
// byte 8 is the command code (0x03 CONN_RSP, 0x04 CONFIG_REQ,
// 0x05 CONFIG_RSP). A flood of len==maxpacket junk CIDs vs real
// cid=0001 code=05 packets tells the two failure modes apart.
static uint32_t s_rxTraced = 0;
if (s_rxTraced < 24 && pl >= 8) {
const uint8_t* d = g_aclRxRing[slot];
uint16_t cid = (uint16_t)d[6] | ((uint16_t)d[7] << 8);
uint8_t code = (pl >= 9) ? d[8] : 0;
KernelLogStream(INFO, "BT-HCI") << "rx[" << (uint64_t)s_rxTraced
<< "] len=" << (uint64_t)pl << " cid=" << base::hex << (uint64_t)cid
<< " code=" << (uint64_t)code << base::dec;
s_rxTraced++;
}
ProcessAcl(g_aclRxRing[slot], pl);
g_aclRxTail = (uint8_t)((g_aclRxTail + 1) % ACL_RX_SLOTS);
}
}