feat: Intel BT firmware download, A2dp & Bluetooth audio progress
This commit is contained in:
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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user