feat: Intel BT firmware download, A2dp & Bluetooth audio progress
This commit is contained in:
@@ -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, ¶ms[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, ¶ms[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, ¶ms[0], 6);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EVT_LINK_KEY_REQUEST: {
|
||||
if (evtParamLen >= 6) {
|
||||
int idx = FindBondIndex(¶ms[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, ¶ms[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, ¶ms[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(¶ms[0], ¶ms[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, ¶m, 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user