feat: Raspberry Pi 4/400 Bluetooth over mini UART (H4, Broadcom patchram, firmware mailbox BT_ON); move BT stack to Drivers/Bluetooth
This commit is contained in:
1 parent
808e1d66fd
commit
ffee9002a6
38 files changed
+2176
-141
No files matched your search
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
* 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 <cstdint>
|
||||
|
||||
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();
|
||||
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();
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user