/* * Hci.hpp * Bluetooth HCI (Host Controller Interface) layer * HCI transport over USB (bulk/interrupt/control endpoints) or a UART (H4) * Copyright (c) 2026 Daniel Hammer */ #pragma once #include namespace Drivers::Bluetooth::Hci { // ========================================================================= // HCI transports // ========================================================================= // USB transport uses different endpoints for each packet type: // Commands -> Control EP0 (class request) // ACL data -> Bulk OUT / Bulk IN // Events -> Interrupt IN // // The UART transport (H4, Bluetooth Core spec Vol 4 Part A) carries all // of them on one byte stream, each packet preceded by its type. constexpr uint8_t H4_COMMAND = 0x01; constexpr uint8_t H4_ACL = 0x02; constexpr uint8_t H4_SCO = 0x03; constexpr uint8_t H4_EVENT = 0x04; enum class Transport : uint8_t { None, Usb, Uart }; // Controller vendor, for the vendor-specific commands (BD_ADDR write). enum class Vendor : uint8_t { Unknown, Intel, Broadcom }; // ========================================================================= // HCI command opcodes (OGF << 10 | OCF) // ========================================================================= // Link Control (OGF 0x01) constexpr uint16_t OP_INQUIRY = 0x0401; constexpr uint16_t OP_INQUIRY_CANCEL = 0x0402; constexpr uint16_t OP_CREATE_CONNECTION = 0x0405; constexpr uint16_t OP_DISCONNECT = 0x0406; constexpr uint16_t OP_ACCEPT_CONN_REQ = 0x0409; constexpr uint16_t OP_REJECT_CONN_REQ = 0x040A; constexpr uint16_t OP_LINK_KEY_REQ_REPLY = 0x040B; constexpr uint16_t OP_LINK_KEY_REQ_NEG_REPLY = 0x040C; constexpr uint16_t OP_AUTH_REQUESTED = 0x0411; constexpr uint16_t OP_SET_CONN_ENCRYPT = 0x0413; constexpr uint16_t OP_IO_CAPABILITY_REPLY = 0x042B; constexpr uint16_t OP_USER_CONFIRM_REPLY = 0x042C; // Link Policy (OGF 0x02) constexpr uint16_t OP_WRITE_DEFAULT_LP = 0x080F; constexpr uint16_t OP_SNIFF_MODE = 0x0803; // Controller & Baseband (OGF 0x03) constexpr uint16_t OP_RESET = 0x0C03; constexpr uint16_t OP_SET_EVENT_FILTER = 0x0C05; constexpr uint16_t OP_WRITE_LOCAL_NAME = 0x0C13; constexpr uint16_t OP_READ_LOCAL_NAME = 0x0C14; constexpr uint16_t OP_WRITE_SCAN_ENABLE = 0x0C1A; constexpr uint16_t OP_WRITE_CLASS_OF_DEVICE = 0x0C24; constexpr uint16_t OP_WRITE_SSP_MODE = 0x0C56; constexpr uint16_t OP_WRITE_SC_HOST_SUPPORT = 0x0C7A; constexpr uint16_t OP_WRITE_INQUIRY_MODE = 0x0C45; constexpr uint16_t OP_WRITE_PAGE_TIMEOUT = 0x0C18; constexpr uint16_t OP_WRITE_AUTH_ENABLE = 0x0C20; constexpr uint16_t OP_SET_EVENT_MASK = 0x0C01; // Informational Parameters (OGF 0x04) constexpr uint16_t OP_READ_BD_ADDR = 0x1009; constexpr uint16_t OP_READ_LOCAL_VERSION = 0x1001; constexpr uint16_t OP_READ_LOCAL_FEATURES = 0x1003; constexpr uint16_t OP_READ_BUFFER_SIZE = 0x1005; // Broadcom vendor commands (OGF 0x3F), from Linux btbcm/hci_bcm constexpr uint16_t OP_BCM_WRITE_BD_ADDR = 0xFC01; constexpr uint16_t OP_BCM_UPDATE_BAUD_RATE = 0xFC18; constexpr uint16_t OP_BCM_DOWNLOAD_MINIDRV = 0xFC2E; constexpr uint16_t OP_BCM_WRITE_RAM = 0xFC4C; constexpr uint16_t OP_BCM_LAUNCH_RAM = 0xFC4E; // Intel vendor commands (OGF 0x3F) constexpr uint16_t OP_INTEL_READ_VERSION = 0xFC05; constexpr uint16_t OP_INTEL_RESET = 0xFC01; constexpr uint16_t OP_INTEL_SET_EVENT_MASK = 0xFC52; constexpr uint16_t OP_INTEL_DDC_CONFIG_WRITE = 0xFC8B; constexpr uint16_t OP_INTEL_SECURE_SEND = 0xFC09; constexpr uint16_t OP_INTEL_WRITE_BD_ADDR = 0xFC31; // set adapter BD_ADDR // ========================================================================= // HCI event codes // ========================================================================= constexpr uint8_t EVT_INQUIRY_COMPLETE = 0x01; constexpr uint8_t EVT_INQUIRY_RESULT = 0x02; constexpr uint8_t EVT_CONNECTION_COMPLETE = 0x03; constexpr uint8_t EVT_CONNECTION_REQUEST = 0x04; constexpr uint8_t EVT_DISCONNECTION_COMPLETE = 0x05; constexpr uint8_t EVT_AUTH_COMPLETE = 0x06; constexpr uint8_t EVT_ENCRYPT_CHANGE = 0x08; constexpr uint8_t EVT_LINK_KEY_REQUEST = 0x17; constexpr uint8_t EVT_LINK_KEY_NOTIFICATION = 0x18; constexpr uint8_t EVT_COMMAND_COMPLETE = 0x0E; constexpr uint8_t EVT_COMMAND_STATUS = 0x0F; constexpr uint8_t EVT_NUM_COMPLETED_PACKETS = 0x13; constexpr uint8_t EVT_IO_CAPABILITY_REQUEST = 0x31; constexpr uint8_t EVT_IO_CAPABILITY_RESPONSE = 0x32; constexpr uint8_t EVT_USER_CONFIRM_REQUEST = 0x33; constexpr uint8_t EVT_SIMPLE_PAIRING_COMPLETE = 0x36; constexpr uint8_t EVT_INQUIRY_RESULT_RSSI = 0x22; constexpr uint8_t EVT_EXTENDED_INQUIRY_RESULT = 0x2F; constexpr uint8_t EVT_VENDOR_SPECIFIC = 0xFF; // ========================================================================= // Inquiry result storage // ========================================================================= struct InquiryDevice { uint8_t BdAddr[6]; uint8_t _pad[2]; uint32_t ClassOfDevice; int8_t Rssi; uint8_t _pad2[3]; char Name[64]; // From Extended Inquiry Result or Remote Name Request }; constexpr int MAX_INQUIRY_RESULTS = 16; // ========================================================================= // HCI packet headers // ========================================================================= struct CommandHeader { uint16_t Opcode; uint8_t ParamLength; } __attribute__((packed)); struct EventHeader { uint8_t EventCode; uint8_t ParamLength; } __attribute__((packed)); struct AclHeader { uint16_t HandleFlags; // bits 11:0 = handle, 13:12 = PB flag, 15:14 = BC flag uint16_t DataLength; } __attribute__((packed)); // ACL PB (Packet Boundary) flag values constexpr uint16_t ACL_PB_FIRST_NON_FLUSH = 0x0000; // First non-auto-flushable constexpr uint16_t ACL_PB_CONTINUING = 0x1000; // Continuing fragment constexpr uint16_t ACL_PB_FIRST_FLUSH = 0x2000; // First auto-flushable // ========================================================================= // HCI connection info // ========================================================================= struct ConnectionInfo { bool Active; uint16_t Handle; uint8_t BdAddr[6]; uint8_t LinkType; // 0x01 = ACL bool Encrypted; }; constexpr int MAX_CONNECTIONS = 4; // A persisted (bonded/paired) device, as stored in the link-key store. struct BondInfo { uint8_t Addr[6]; }; // ========================================================================= // Intel Bluetooth version info // ========================================================================= struct IntelVersion { uint8_t Status; uint8_t HwPlatform; uint8_t HwVariant; uint8_t HwRevision; uint8_t FwVariant; // 0x06 = bootloader, 0x23 = operational uint8_t FwRevision; uint8_t FwBuildNum; uint8_t FwBuildWw; uint8_t FwBuildYy; uint8_t FwPatchNum; } __attribute__((packed)); // ========================================================================= // Public API // ========================================================================= // Initialize HCI transport over USB for the given slot void Initialize(uint8_t slotId); // Initialize HCI transport over the Bluetooth UART (Serial::BtUart, // already initialized by the caller). void InitializeUart(); Transport GetTransport(); // Bytes per second the host-to-controller link can carry: the UART's // line rate (8N1), or 0 when the transport is not the bottleneck (USB). uint32_t TransportBytesPerSecond(); void SetVendor(Vendor vendor); Vendor GetVendor(); // Move received bytes from the transport into the HCI queues: the xHCI // event ring for USB, the UART receive ring (H4 deframing) for UART. // Every wait loop calls this before DrainEvents. Non-reentrant; a // nested or concurrent call returns at once. void Pump(); // True while running nested under Pump (USB transfer callbacks), where // a command cannot be waited for. bool InPumpContext(); // Received transport bytes are waiting for Pump (UART only; USB signals // through the xHCI deferred-work flag). bool TransportHasRxData(); // Discard all received UART input, queued bytes and any partial packet // (after a power cycle or a baud-rate change). void ResetUartReceiver(); // Start receiving HCI events and ACL data (call after HCI init sequence). // Arms both the interrupt IN and the bulk IN; the bulk IN must stay armed // through the firmware download (it absorbs the device's ~635 KB cc=4 glitch // and keeps the event pipe alive -- see the definition). void StartEventPipe(); // Enable operational ACL receive framing after firmware/HCI setup. Bulk IN // is armed earlier to protect Intel firmware loading; pre-operational runts // are intentionally ignored and any endpoint error is recovered here. void EnableAclDataReception(); // Send an HCI command (USB: control transfer on EP0; UART: H4 packet) bool SendCommand(uint16_t opcode, const uint8_t* params, uint8_t paramLen); // Wait for a Command Complete event matching the given opcode // Returns true if received within timeout, fills outParams (excluding status byte) bool WaitCommandComplete(uint16_t opcode, uint8_t* outParams = nullptr, uint8_t maxLen = 0, uint32_t timeoutMs = 2000); // Wait for a Command Status event matching the given opcode bool WaitCommandStatus(uint16_t opcode, uint32_t timeoutMs = 2000); // Send ACL data (USB: bulk OUT; UART: H4 packet) bool SendAcl(uint16_t handle, uint16_t pbFlag, const uint8_t* data, uint16_t len); // Process an HCI event received on the interrupt IN endpoint void ProcessEvent(const uint8_t* data, uint32_t len); // Process ACL data received on the bulk IN endpoint void ProcessAcl(const uint8_t* data, uint32_t len); // Get connection info ConnectionInfo* GetConnection(uint16_t handle); ConnectionInfo* GetActiveConnection(); ConnectionInfo* GetConnectionByIndex(int index); // 0..MAX_CONNECTIONS-1 // HCI Reset command bool Reset(); // Read local BD_ADDR bool ReadBdAddr(uint8_t* addr); // Read standard HCI local version info struct LocalVersion { uint8_t Status; uint8_t HciVersion; uint16_t HciRevision; uint8_t LmpVersion; uint16_t Manufacturer; uint16_t LmpSubversion; } __attribute__((packed)); bool ReadLocalVersion(LocalVersion* ver); // Read Intel-specific version info bool ReadIntelVersion(IntelVersion* ver); // ========================================================================= // Intel firmware download primitives (bootloader mode) // ========================================================================= // Read the Intel version response in TLV format (0xFC05 with parameter // 0xFF). Copies the raw return parameters (byte 0 = status, followed by // the TLV stream) into outBuf, bounded by the number of bytes actually // received from the controller. Returns that length, or -1 on failure. int ReadIntelVersionTlv(uint8_t* outBuf, int maxLen); // Intel "Secure Send" (0xFC09): pushes one logical fragment to the // bootloader, split into <=252-byte chunks each prefixed with the // fragment type (0x00 CSS init, 0x01 firmware data, 0x02 signature, // 0x03 public key). The bootloader does not Command-Complete these; pacing // is by USB transfer completion and the result arrives asynchronously as a // 0xFF/0x06 secure-send result event (see WaitSecureSendResult). bool IntelSecureSend(uint8_t fragmentType, const uint8_t* data, uint32_t len); // Pipelined bulk-OUT variant for the firmware payload (btusb bootloader // path: 0xFC09 packets on the bulk endpoint, several in flight). Call // DrainBulkTx() after the last fragment to ensure everything reached the // controller before waiting for the download-complete result. bool IntelSecureSendBulk(uint8_t fragmentType, const uint8_t* data, uint32_t len); bool DrainBulkTx(uint32_t timeoutMs); // Reset / await the Intel "secure send result" vendor event (0xFF/0x06). // Call ClearSecureSendResult() before a download phase, then // WaitSecureSendResult() to read the outcome (result/status, 0 = success). void ClearSecureSendResult(); bool WaitSecureSendResult(uint32_t timeoutMs, uint8_t* outResult, uint8_t* outStatus); // Bonded-device link key persistence (so pairings survive reboots). // LoadLinkKeys(): read the on-disk store once VFS is up. // FlushLinkKeys(): write the store to disk if it changed -- call from // process context (NOT an event handler), since it does blocking disk I/O. void LoadLinkKeys(); void FlushLinkKeys(); // Enumerate the stored bonds (paired devices) into buf; returns the count // written (<= maxCount). Used to list paired-but-disconnected devices. int ListBonds(BondInfo* buf, int maxCount); // Remove a stored bond by BD_ADDR and persist the removal (process context). // Returns true if a matching bond was found and forgotten. bool ForgetBond(const uint8_t* addr); // Toggle the bounded firmware-phase interrupt-IN trace (one ring record // per completion, capped). Recording is IRQ-safe (lock-free ring, no // logging); DumpFwTrace() prints pending records and MUST only be called // from process context (terminal lock is a non-IRQ-safe Mutex). void SetFwTrace(bool on); void DumpFwTrace(); // Non-blocking peek at the most recent 0xFF/0x06 secure-send result without // consuming it. Returns true if one has arrived since the last // ClearSecureSendResult(). The payload loop uses this to catch a mid-stream // rejection -- a healthy bootloader stays silent until the final fragment. bool PeekSecureSendResult(uint8_t* outResult, uint8_t* outStatus); // Reset the controller into operational firmware at bootAddr (0xFC01) and // wait for the Intel "bootup" vendor event. Returns true once booted. bool IntelBootFirmware(uint32_t bootAddr, uint32_t timeoutMs = 5000); // Apply one DDC parameter record (record[0] = payload length) via 0xFC8B. bool IntelWriteDdcRecord(const uint8_t* record, uint8_t recordLen); // Configure the Intel vendor event mask (0xFC52). bool IntelSetEventMask(); // Set local name bool WriteLocalName(const char* name); // Set class of device bool WriteClassOfDevice(uint32_t cod); // Enable scan (inquiry + page) bool WriteScanEnable(uint8_t mode); // Write Simple Secure Pairing mode bool WriteSSPMode(uint8_t mode); // Select inquiry result format: 0=standard, 1=RSSI, 2=extended/EIR. bool WriteInquiryMode(uint8_t mode); // Accept an incoming connection bool AcceptConnection(const uint8_t* bdAddr, uint8_t role); // Request authentication on an ACL link (we are the connection initiator). // Drives Link Key Request -> pairing; needed for bonded-device reconnects. bool AuthenticateLink(uint16_t handle); // Set the adapter's BD_ADDR (vendor command: Intel 0xFC31, Broadcom // 0xFC01). Used to dodge a remote that holds a stale, un-clearable bond // to our real address, and to give Broadcom parts without a programmed // address a unique one. bool SetBdAddr(const uint8_t* addr); // Send any queued pairing replies (IO-cap / user-confirm / link-key) with // real confirmed transfers. Call from top-level (e.g. the connect loop), // NOT from an event handler -- event handlers only enqueue. void ProcessPendingCommands(); bool HasPendingCommands(); // ACL TX flow control: outstanding (un-acked) ACL packets, and the // controller's ACL buffer count (Number-Of-Completed-Packets credits). The // media writer throttles on these so it never overruns the controller. uint16_t AclPendingCount(); uint16_t AclMaxPackets(); // ACL packets handed to the xHCI whose bulk OUT completion has not been // reaped yet (each one still owns a TX DMA ring slot). On UART, the // transmit ring's backlog in maximum-size packets (0 once drained). uint32_t AclTxInFlight(); // True when SendAcl can be called without overrunning either the // controller's ACL buffers (NOCP credits) or the TX DMA ring. The media // writer polls events until this is true before sending each frame. bool AclTxReady(); // Lost-NOCP recovery: zero the credit count after a prolonged stall with // the USB side drained (completions presumed lost). void AclResetCredits(); // Disconnect a connection bool Disconnect(uint16_t handle, uint8_t reason); // Read ACL buffer size from controller bool ReadBufferSize(uint16_t* aclLen, uint8_t* scoLen, uint16_t* aclNum, uint16_t* scoNum); // Inquiry (device discovery) bool StartInquiry(uint8_t durationUnits); // duration in 1.28s units (e.g., 8 = ~10s) bool CancelInquiry(); int GetInquiryResults(InquiryDevice* buf, int maxCount); void ClearInquiryResults(); bool IsInquiryActive(); // Create ACL connection to a remote device bool CreateConnection(const uint8_t* bdAddr); // Drain any pending HCI events (call in poll loops that aren't inside // WaitCommandComplete/WaitCommandStatus) void DrainEvents(); }