/* * IntelFirmware.cpp * Intel Bluetooth bootloader firmware download path * * Ports the btintel "TLV" bootloader sequence: read the version, derive the * ibt--.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 #include "Hci.hpp" #include #include #include #include #include #include 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-- 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--." 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. // A rejection event arrives within milliseconds; on SUCCESS the // controller stays silent until the END of the whole download (traced // on the AX211), so a long timeout here only burns boot time. Hci::ClearSecureSendResult(); if (!Hci::IntelSecureSend(0x00, fw + cssOff, 128)) return false; if (Hci::WaitSecureSendResult(250, &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. Same rule: silence means accepted; a // rejected header also fails the payload phase immediately, so the // short window loses nothing. Hci::ClearSecureSendResult(); if (!Hci::IntelSecureSend(0x03, fw + pkeyOff, pkeyLen)) return false; if (!Hci::IntelSecureSend(0x02, fw + sigOff, sigLen)) return false; if (Hci::WaitSecureSendResult(250, &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) { // Payload goes over the bulk endpoint, pipelined (the btusb // bootloader path) -- EP0's synchronous 3-stage transfers made // the 720 KB image the dominant boot cost. if (!Hci::IntelSecureSendBulk(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)"; } } } // The bulk pipeline may still hold up to 7 in-flight fragments; make // sure every byte reached the controller before the caller waits for // the download-complete result. if (!Hci::DrainBulkTx(3000)) { KernelLogStream(ERROR, "BT-FW") << "Bulk TX drain timed out at end of payload"; return false; } 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); Arch::CompilerBarrier(); // 4. Run the secure download + boot sequence. bool ok = DownloadImage(fw, size, ver, cnvi, cnvr); Memory::g_heap->Free(fw); return ok; } }