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

This commit is contained in:
daniel committed 2026-06-03 18:05:17 +02:00
1 parent ee6d1a388e
commit 0f16785c9f
22 files changed
+6452 -273

No files matched your search

@@ -0,0 +1,470 @@
/*
* IntelFirmware.cpp
* Intel Bluetooth bootloader firmware download path
*
* Ports the btintel "TLV" bootloader sequence: read the version, derive the
* ibt-<cnvi>-<cnvr>.sfi / .ddc file names, secure-send the signed RSA/ECDSA
* header and command-buffer payload, boot the operational image and apply
* the DDC parameters. The .sfi/.ddc files are staged on the ramdisk at
* 0:/os/firmware/intel/ by the userspace build.
*
* Copyright (c) 2026 Daniel Hammer
*/
#include "IntelFirmware.hpp"
#include "Hci.hpp"
#include <Fs/Vfs.hpp>
#include <Memory/Heap.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Libraries/Memory.hpp>
#include <Timekeeping/ApicTimer.hpp>
using namespace Kt;
namespace Drivers::USB::Bluetooth {
// =========================================================================
// .sfi / TLV format constants (from drivers/bluetooth/btintel.{c,h})
// =========================================================================
// CSS secure-boot header layout.
static constexpr uint32_t RSA_HEADER_LEN = 644; // RSA header + key + sig
static constexpr uint32_t ECDSA_HEADER_LEN = 320; // follows the RSA header
static constexpr uint32_t ECDSA_OFFSET = 644;
static constexpr uint32_t CSS_HEADER_OFFSET = 8; // version field offset
static constexpr uint32_t RSA_HEADER_VER = 0x00010000;
static constexpr uint32_t HYBRID_HEADER_VER = 0x00069700;
static constexpr uint32_t ECDSA_HEADER_VER = 0x00020000;
// The operational image embeds its reset/boot address in a final
// CMD_WRITE_BOOT_PARAMS HCI command.
static constexpr uint16_t CMD_WRITE_BOOT_PARAMS = 0xFC0E;
// Image type (INTEL_TLV_IMAGE_TYPE value): 0x01 bootloader, 0x02
// intermediate loader, 0x03 operational.
static constexpr uint8_t IMG_BOOTLOADER = 0x01;
static constexpr uint8_t IMG_OPERATIONAL = 0x03;
// Version TLV type ids (enum INTEL_TLV_* starts at 0x10).
static constexpr uint8_t TLV_CNVI_TOP = 0x10;
static constexpr uint8_t TLV_CNVR_TOP = 0x11;
static constexpr uint8_t TLV_CNVI_BT = 0x12;
static constexpr uint8_t TLV_IMAGE_TYPE = 0x1C;
static constexpr uint8_t TLV_MIN_FW = 0x2D;
static constexpr uint8_t TLV_SBE_TYPE = 0x2F;
// =========================================================================
// Helpers
// =========================================================================
static uint32_t Rd32(const uint8_t* p) {
return (uint32_t)p[0] | ((uint32_t)p[1] << 8)
| ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
struct IntelTlvVersion {
uint32_t CnviTop = 0;
uint32_t CnvrTop = 0;
uint32_t CnviBt = 0;
uint8_t ImgType = 0;
uint8_t SbeType = 0xFF; // 0x00 RSA, 0x01 ECDSA, 0xFF unknown
bool SbeValid = false;
bool Valid = false;
};
static void ParseTlv(const uint8_t* buf, int len, IntelTlvVersion* v) {
int i = 0;
while (i + 2 <= len) {
uint8_t type = buf[i];
uint8_t l = buf[i + 1];
if (i + 2 + l > len) break;
const uint8_t* val = &buf[i + 2];
switch (type) {
case TLV_CNVI_TOP: if (l >= 4) v->CnviTop = Rd32(val); break;
case TLV_CNVR_TOP: if (l >= 4) v->CnvrTop = Rd32(val); break;
case TLV_CNVI_BT: if (l >= 4) v->CnviBt = Rd32(val); break;
case TLV_IMAGE_TYPE: if (l >= 1) v->ImgType = val[0]; break;
case TLV_SBE_TYPE: if (l >= 1) { v->SbeType = val[0]; v->SbeValid = true; } break;
default: break;
}
i += 2 + l;
}
// CNVI top is always present on a real TLV response.
v->Valid = (v->CnviTop != 0);
}
// ibt-<cnvi>-<cnvr> packing: __swab16(((top & 0xfff) << 4) | ((top >> 24) & 0xf))
static uint16_t CnvxPack(uint32_t top) {
uint16_t t = (uint16_t)(top & 0x0FFF);
uint8_t s = (uint8_t)((top >> 24) & 0x0F);
uint16_t packed = (uint16_t)((t << 4) | s);
return (uint16_t)((packed >> 8) | (packed << 8)); // __swab16
}
static uint8_t HwVariant(uint32_t cnviBt) {
return (uint8_t)((cnviBt >> 16) & 0x3F);
}
static char HexDigit(uint8_t n) {
return (char)(n < 10 ? ('0' + n) : ('a' + n - 10));
}
// Build "0:/os/firmware/intel/ibt-<cnvi>-<cnvr>.<ext>" into out (>= 48 bytes).
static void BuildFwPath(char* out, uint16_t cnvi, uint16_t cnvr, const char* ext) {
const char* prefix = "0:/os/firmware/intel/ibt-";
char* p = out;
while (*prefix) *p++ = *prefix++;
for (int sh = 12; sh >= 0; sh -= 4) *p++ = HexDigit((cnvi >> sh) & 0xF);
*p++ = '-';
for (int sh = 12; sh >= 0; sh -= 4) *p++ = HexDigit((cnvr >> sh) & 0xF);
*p++ = '.';
while (*ext) *p++ = *ext++;
*p = '\0';
}
// =========================================================================
// Secure-boot header transmission
// =========================================================================
// Try one secure-boot header type. Layout:
// RSA : CSS 128 @0, pkey 256 @128, sig 256 @388
// ECDSA: CSS 128 @644, pkey 96 @772, sig 96 @868 (after the RSA hdr)
//
// The CSS init fragment is sent and checked FIRST: a non-zero secure-send
// result (0xFF/0x06) means the controller's secure-boot engine rejected
// this header type, so we bail before blasting the ~200-512 byte key and
// signature -- sending those after a rejected CSS wedges EP0 and (since
// this runs on the boot path) freezes the kernel. Returns true only if the
// full header is accepted (or the controller stays silent, in which case
// the payload + bootup event remain the final gate).
static bool TryHeader(const uint8_t* fw, bool ecdsa) {
const uint32_t cssOff = ecdsa ? 644 : 0;
const uint32_t pkeyOff = ecdsa ? 644 + 128 : 128;
const uint32_t pkeyLen = ecdsa ? 96 : 256;
const uint32_t sigOff = ecdsa ? 644 + 224 : 388;
const uint32_t sigLen = ecdsa ? 96 : 256;
const char* name = ecdsa ? "ECDSA" : "RSA";
KernelLogStream(INFO, "BT-FW") << "Trying " << name << " secure-boot header";
uint8_t result = 0, status = 0;
// 1. CSS header init -- check the result before committing key/sig.
Hci::ClearSecureSendResult();
if (!Hci::IntelSecureSend(0x00, fw + cssOff, 128)) return false;
if (Hci::WaitSecureSendResult(1500, &result, &status) && (result || status)) {
KernelLogStream(WARNING, "BT-FW") << name << " CSS rejected (result="
<< base::hex << (uint64_t)result << " status=" << (uint64_t)status
<< base::dec << ")";
return false;
}
// 2. Public key + signature.
Hci::ClearSecureSendResult();
if (!Hci::IntelSecureSend(0x03, fw + pkeyOff, pkeyLen)) return false;
if (!Hci::IntelSecureSend(0x02, fw + sigOff, sigLen)) return false;
if (Hci::WaitSecureSendResult(2000, &result, &status) && (result || status)) {
KernelLogStream(WARNING, "BT-FW") << name << " header rejected (result="
<< base::hex << (uint64_t)result << " status=" << (uint64_t)status
<< base::dec << ")";
return false;
}
KernelLogStream(OK, "BT-FW") << name << " secure-boot header accepted";
return true;
}
// Push the command-buffer payload starting at `offset`. Fragments are cut
// on HCI command boundaries and only flushed when 4-byte aligned, matching
// btintel_download_firmware_payload().
static bool SendPayload(const uint8_t* fw, uint64_t size, uint64_t offset) {
uint64_t fwPtr = offset;
uint32_t fragLen = 0;
uint32_t sends = 0;
uint64_t start = Timekeeping::GetMilliseconds();
while (fwPtr < size) {
// Safety net: never let a stalled upload freeze the boot path.
if (Timekeeping::GetMilliseconds() - start > 30000) {
KernelLogStream(ERROR, "BT-FW") << "Payload upload stalled at " << fwPtr - offset
<< "/" << (size - offset) << " bytes; aborting";
return false;
}
uint64_t cmd = fwPtr + fragLen;
if (cmd + 3 > size) break; // truncated header
uint8_t plen = fw[cmd + 2];
fragLen += 3 + plen;
if (fwPtr + fragLen > size) {
KernelLogStream(ERROR, "BT-FW") << "Firmware payload command overruns file";
return false;
}
if ((fragLen % 4) == 0) {
if (!Hci::IntelSecureSend(0x01, fw + fwPtr, fragLen)) {
KernelLogStream(ERROR, "BT-FW") << "Payload secure-send failed at offset "
<< fwPtr;
return false;
}
fwPtr += fragLen;
fragLen = 0;
// A healthy bootloader only emits an 0xFF/0x06 result on the
// final fragment. One arriving mid-stream with a non-zero
// result/status means it rejected what we just sent -- surface
// the exact offset instead of blasting the rest of the image
// into a wedged device (which then NAKs to a cc=4 on EP0).
uint8_t r = 0, s = 0;
if (Hci::PeekSecureSendResult(&r, &s) && (r || s)) {
KernelLogStream(ERROR, "BT-FW") << "Bootloader rejected payload at byte "
<< (fwPtr - offset) << "/" << (size - offset) << " (result="
<< base::hex << (uint64_t)r << " status=" << (uint64_t)s
<< base::dec << ")";
return false;
}
// Coarse progress so a slow/stalled upload is distinguishable
// from steady progress (one line per ~512 fragments).
if ((++sends % 512) == 0) {
KernelLogStream(INFO, "BT-FW") << " payload " << fwPtr - offset
<< "/" << (size - offset) << " bytes (" << (uint64_t)sends << " frags)";
}
}
}
return true;
}
// Walk the command stream from the start of the image to recover the boot
// address embedded in the CMD_WRITE_BOOT_PARAMS command.
static bool FindBootAddress(const uint8_t* fw, uint64_t size, uint32_t* outAddr) {
uint64_t p = 0;
while (p + 3 <= size) {
uint16_t op = (uint16_t)fw[p] | ((uint16_t)fw[p + 1] << 8);
uint8_t plen = fw[p + 2];
if (op == CMD_WRITE_BOOT_PARAMS) {
if (p + 3 + 4 > size) return false;
*outAddr = Rd32(&fw[p + 3]);
KernelLogStream(INFO, "BT-FW") << "Boot address: " << base::hex
<< (uint64_t)*outAddr << base::dec
<< " (fw build " << (uint64_t)fw[p + 7] << "-"
<< (uint64_t)fw[p + 8] << "." << (uint64_t)fw[p + 9] << ")";
return true;
}
p += 3 + plen;
}
return false;
}
// =========================================================================
// DDC parameter application (best effort)
// =========================================================================
static void ApplyDdc(uint16_t cnvi, uint16_t cnvr) {
char path[48];
BuildFwPath(path, cnvi, cnvr, "ddc");
Fs::Vfs::BackendFile file;
if (Fs::Vfs::OpenBackendFile(path, file) < 0) {
KernelLogStream(INFO, "BT-FW") << "No DDC parameters (" << path << ")";
return;
}
uint64_t size = Fs::Vfs::GetBackendFileSize(file);
if (size == 0 || size > 4096) {
Fs::Vfs::CloseBackendFile(file);
return;
}
uint8_t* ddc = (uint8_t*)Memory::g_heap->Request(size);
if (!ddc) {
Fs::Vfs::CloseBackendFile(file);
return;
}
Fs::Vfs::ReadBackendFile(file, ddc, 0, size);
Fs::Vfs::CloseBackendFile(file);
// Each record is [len][len bytes of parameters]; the whole record is
// sent as the 0xFC8B payload.
uint64_t p = 0;
int applied = 0;
while (p < size) {
uint8_t total = (uint8_t)(ddc[p] + 1);
if (p + total > size) break;
if (Hci::IntelWriteDdcRecord(&ddc[p], total)) applied++;
p += total;
}
Memory::g_heap->Free(ddc);
KernelLogStream(OK, "BT-FW") << "Applied " << (uint64_t)applied << " DDC record(s)";
}
// =========================================================================
// Download orchestration
// =========================================================================
static bool DownloadImage(const uint8_t* fw, uint64_t size,
const IntelTlvVersion& ver,
uint16_t cnvi, uint16_t cnvr) {
if (size < RSA_HEADER_LEN + 16) {
KernelLogStream(ERROR, "BT-FW") << "Firmware image too small (" << size << " bytes)";
return false;
}
uint32_t cssVer = Rd32(&fw[CSS_HEADER_OFFSET]);
if (cssVer != RSA_HEADER_VER && cssVer != HYBRID_HEADER_VER) {
KernelLogStream(ERROR, "BT-FW") << "Invalid CSS header version: "
<< base::hex << (uint64_t)cssVer << base::dec;
return false;
}
uint32_t bootAddr = 0;
if (!FindBootAddress(fw, size, &bootAddr)) {
KernelLogStream(ERROR, "BT-FW") << "Could not locate boot address in firmware";
return false;
}
uint8_t hw = HwVariant(ver.CnviBt);
uint64_t payloadOffset;
bool headerOk = false;
if (hw <= 0x14) {
// RSA-only parts: RSA header then command buffer.
headerOk = TryHeader(fw, false);
payloadOffset = RSA_HEADER_LEN;
} else {
// 0x17+ parts ship RSA(644) + ECDSA(320) headers; the command
// buffer follows both. sbe_type selects which header the part's
// secure-boot engine expects.
if (size < RSA_HEADER_LEN + ECDSA_HEADER_LEN || fw[ECDSA_OFFSET] != 0x06) {
KernelLogStream(ERROR, "BT-FW") << "Missing ECDSA header for hw variant "
<< base::hex << (uint64_t)hw << base::dec;
return false;
}
payloadOffset = RSA_HEADER_LEN + ECDSA_HEADER_LEN;
// Honor sbe_type if the version reply included it; otherwise (the
// common case here -- the reply is truncated to one USB packet so
// sbe_type is missing) try ECDSA first. These newer parts
// (AX210/AX211, hw_variant >= 0x17) use the ECDSA secure-boot
// engine, and TryHeader checks the CSS result before committing the
// key/signature, so a wrong guess fails cleanly instead of wedging.
bool order[2];
int n;
if (ver.SbeValid && ver.SbeType == 0x00) { order[0] = false; n = 1; } // RSA
else if (ver.SbeValid && ver.SbeType == 0x01) { order[0] = true; n = 1; } // ECDSA
else { order[0] = true; order[1] = false; n = 2; } // ECDSA, then RSA
for (int k = 0; k < n && !headerOk; k++) {
headerOk = TryHeader(fw, order[k]);
}
}
if (!headerOk) {
KernelLogStream(ERROR, "BT-FW") << "Secure-boot header rejected";
return false;
}
KernelLogStream(INFO, "BT-FW") << "Downloading firmware payload ("
<< (size - payloadOffset) << " bytes)";
Hci::ClearSecureSendResult(); // discard header-era result
if (!SendPayload(fw, size, payloadOffset)) {
KernelLogStream(ERROR, "BT-FW") << "Firmware payload download failed";
return false;
}
// The bootloader signals download completion with a final 0xFF/0x06
// secure-send result. Wait for it (and check it) before booting.
uint8_t r = 0, s = 0;
if (Hci::WaitSecureSendResult(5000, &r, &s)) {
if (r != 0 || s != 0) {
KernelLogStream(ERROR, "BT-FW") << "Firmware download failed (result="
<< base::hex << (uint64_t)r << " status=" << (uint64_t)s
<< base::dec << ")";
return false;
}
KernelLogStream(OK, "BT-FW") << "Firmware download complete";
} else {
KernelLogStream(INFO, "BT-FW") << "No download-complete event; booting anyway";
}
KernelLogStream(INFO, "BT-FW") << "Booting operational firmware";
if (!Hci::IntelBootFirmware(bootAddr)) return false;
KernelLogStream(OK, "BT-FW") << "Operational firmware booted";
// DDC parameters are applied against the now-running firmware.
ApplyDdc(cnvi, cnvr);
return true;
}
bool DownloadIntelFirmware() {
// 1. Read the TLV version to learn the SKU and current image type.
uint8_t raw[256];
int rawLen = Hci::ReadIntelVersionTlv(raw, sizeof(raw));
if (rawLen < 1 || raw[0] != 0) {
KernelLogStream(WARNING, "BT-FW") << "Could not read Intel TLV version";
return false;
}
IntelTlvVersion ver;
ParseTlv(raw + 1, rawLen - 1, &ver);
if (!ver.Valid) {
KernelLogStream(WARNING, "BT-FW") << "Unrecognized Intel version response";
return false;
}
uint16_t cnvi = CnvxPack(ver.CnviTop);
uint16_t cnvr = CnvxPack(ver.CnvrTop);
KernelLogStream(INFO, "BT-FW") << "Intel CNVi=" << base::hex << (uint64_t)cnvi
<< " CNVr=" << (uint64_t)cnvr << " hw_variant=" << (uint64_t)HwVariant(ver.CnviBt)
<< " img_type=" << (uint64_t)ver.ImgType << base::dec;
// Diagnostic: how much of the TLV version response we actually received
// (a value near 60 means the controller's reply was truncated to one
// interrupt packet), and whether sbe_type made it through.
KernelLogStream(INFO, "BT-FW") << "TLV bytes=" << (uint64_t)rawLen
<< " sbe_valid=" << (uint64_t)(ver.SbeValid ? 1 : 0)
<< " sbe_type=" << base::hex << (uint64_t)ver.SbeType << base::dec;
// 2. If the operational image is already running, nothing to do.
if (ver.ImgType == IMG_OPERATIONAL) {
KernelLogStream(OK, "BT-FW") << "Operational firmware already loaded";
return true;
}
if (ver.ImgType != IMG_BOOTLOADER) {
KernelLogStream(WARNING, "BT-FW") << "Unexpected image type, attempting download anyway";
}
// 3. Load the .sfi image from the ramdisk.
char sfiPath[48];
BuildFwPath(sfiPath, cnvi, cnvr, "sfi");
KernelLogStream(INFO, "BT-FW") << "Loading firmware: " << sfiPath;
Fs::Vfs::BackendFile file;
if (Fs::Vfs::OpenBackendFile(sfiPath, file) < 0) {
KernelLogStream(ERROR, "BT-FW") << "Firmware not found: " << sfiPath;
KernelLogStream(WARNING, "BT-FW") << "Bluetooth stays in bootloader mode (no firmware)";
return false;
}
uint64_t size = Fs::Vfs::GetBackendFileSize(file);
uint8_t* fw = (uint8_t*)Memory::g_heap->Request(size);
if (!fw) {
KernelLogStream(ERROR, "BT-FW") << "Failed to allocate " << size << " bytes for firmware";
Fs::Vfs::CloseBackendFile(file);
return false;
}
Fs::Vfs::ReadBackendFile(file, fw, 0, size);
Fs::Vfs::CloseBackendFile(file);
asm volatile("" ::: "memory");
// 4. Run the secure download + boot sequence.
bool ok = DownloadImage(fw, size, ver, cnvi, cnvr);
Memory::g_heap->Free(fw);
return ok;
}
}