mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-31 13:00:49 +00:00
237 lines
9.2 KiB
C
237 lines
9.2 KiB
C
// Load NXP BLE/802.15.4 combo firmware to CPU2 and start it.
|
|
// Port of NXP mcux-component conn_fwloader: fsl_loader_utils.c (SB3 loader,
|
|
// load_service_monolithic) and nboot_hal.c (manifest parameters).
|
|
// Copyright 2024 NXP
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
#include "hal.h"
|
|
#include "fsl_ocotp.h"
|
|
#include "fsl_power.h"
|
|
#include "fsl_romapi_nboot.h"
|
|
#include "fsl_romapi_otp.h"
|
|
#include "fsl_kb_api.h"
|
|
#include "fsl_sbloader_v3.h"
|
|
|
|
// The firmware image, linked into our flash. Signed SB3 container, see
|
|
// https://github.com/NXP/wifi_nb_fw, rw61x/rw61x_sb_ble_15d4_combo_a2.bin
|
|
__asm__(
|
|
".section .rodata.radio_fw,\"a\"\n"
|
|
".balign 16\n"
|
|
"radio_fw:\n"
|
|
".incbin \"" RADIO_FW "\"\n"
|
|
".balign 4\n"
|
|
"radio_fw_end:\n"
|
|
".previous\n");
|
|
extern const uint8_t radio_fw[], radio_fw_end[];
|
|
|
|
#define CIU2_RST_SW3 (*(volatile uint32_t *) 0x4424011cU)
|
|
#define CIU2_RST_SW3_CPU2_RST BIT(4)
|
|
#define CPU2_MAGIC (*(volatile uint32_t *) 0x443c0000U) // Set by firmware
|
|
#define CPU2_MAGIC_VALUE 0xabcdef89U
|
|
#define OTP_LCS_FUSE_IDX 45U // Life cycle state, and its redundant copy
|
|
#define OTP_PCK_BLOB_FUSE_IDX 116U // Part common key blob, 48 bytes
|
|
|
|
// SB3 loader state. See fsl_ldr_Context_v3_t
|
|
struct sb3 {
|
|
nboot_context_t nboot; // ROM nboot context
|
|
uint8_t buf[MAX(NBOOT_SB3_MANIFEST_MAX_SIZE_IN_BYTES,
|
|
NBOOT_SB3_BLOCK_MAX_SIZE_IN_BYTES)]; // Block being read
|
|
uint32_t buf_size; // Bytes needed for the current block
|
|
uint32_t buf_pos; // Bytes in buf so far
|
|
bool header_done; // Manifest (block 0) processed
|
|
uint32_t data_offset; // Data offset within a block
|
|
uint32_t blocks; // Data blocks processed
|
|
uint8_t *data; // Data of the current block
|
|
uint32_t pos; // Position in data
|
|
bool in_section; // Inside a data section
|
|
uint32_t section_done; // Bytes of the section handled
|
|
sb3_section_header_t section;
|
|
bool in_range; // Inside a data range
|
|
uint32_t range_gap; // Padding after the range, 16 byte alignment
|
|
sb3_data_range_header_t range;
|
|
bool has_expansion; // Range expansion read
|
|
bool executed; // Execute command seen: firmware loaded
|
|
};
|
|
static struct sb3 s_sb3;
|
|
|
|
static uint32_t antipole(uint32_t v) {
|
|
return (v & 0xffffU) | (~(v & 0xffffU) << 16);
|
|
}
|
|
|
|
// Manifest parameters: root of trust for NXP signed images, rev A2 chips.
|
|
// See nboot_hal_get_sb3_manifest_params(), nboot_hal.c
|
|
static bool manifest_params(nboot_sb3_load_manifest_parms_t *p) {
|
|
static const uint32_t rkh_a2[8] = {0xE7C7E9BB, 0x12C8C535, 0x37E61148,
|
|
0x2BE7F18C, 0x8F0E3094, 0xB2BA7F32,
|
|
0xEC9B4ECB, 0xAD9FC941};
|
|
uint32_t i, lcs = OCOTP->OTP_SHADOW[OTP_LCS_FUSE_IDX];
|
|
memset(p, 0, sizeof(*p));
|
|
if ((lcs & 0xff) != ((lcs >> 8) & 0xff)) return false; // Corrupt LCS
|
|
for (i = 0; i < NBOOT_ROOT_CERT_COUNT; i++) {
|
|
p->soc_RoTNVM.soc_rootKeyRevocation[i] = kNBOOT_RootKey_Enabled;
|
|
}
|
|
memcpy(p->soc_RoTNVM.soc_rkh, rkh_a2, sizeof(rkh_a2));
|
|
p->soc_RoTNVM.soc_numberOfRootKeys = 1;
|
|
for (i = 1; i < NBOOT_ROOT_CERT_COUNT; i++) {
|
|
p->soc_RoTNVM.soc_rootKeyUsage[i] = kNBOOT_RootKeyUsage_Unused;
|
|
}
|
|
p->soc_RoTNVM.soc_rootKeyTypeAndLength = kNBOOT_RootKey_Ecdsa_P256;
|
|
p->soc_RoTNVM.soc_lifecycle = (nboot_soc_lifecycle_t) antipole(lcs & 0xff);
|
|
for (i = 0; i < sizeof(p->pckBlob) / 4; i++) {
|
|
if (OCOTP_OtpFuseRead(OTP_PCK_BLOB_FUSE_IDX + i,
|
|
(uint32_t *) &p->pckBlob[4 * i]) != kStatus_Success) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Manifest (block 0): verify, learn the block layout. See ldr_DoHeader_v3()
|
|
static bool sb3_header(struct sb3 *s) {
|
|
nboot_sb3_header_t *h = (nboot_sb3_header_t *) s->buf;
|
|
nboot_sb3_load_manifest_parms_t parms;
|
|
if (s->buf_size == sizeof(nboot_sb3_header_t)) { // Header only so far
|
|
s->buf_size = h->imageTotalLength;
|
|
s->buf_pos = sizeof(nboot_sb3_header_t);
|
|
return s->buf_size <= sizeof(s->buf);
|
|
}
|
|
if (!manifest_params(&parms)) return false;
|
|
// Protected status: the status code is in the low 32 bits
|
|
if ((nboot_status_t) nboot_sb3_load_manifest(&s->nboot, (uint32_t *) h,
|
|
&parms) != kStatus_NBOOT_Success) {
|
|
return false;
|
|
}
|
|
s->data_offset = 4 + h->certificateBlockOffset - sizeof(nboot_sb3_header_t);
|
|
s->buf_size = s->data_offset + NBOOT_SB3_CHUNK_SIZE_IN_BYTES;
|
|
s->header_done = true;
|
|
return true;
|
|
}
|
|
|
|
// Copy (part of) the current load range to its destination
|
|
static void sb3_load(struct sb3 *s) {
|
|
uint32_t avail = NBOOT_SB3_CHUNK_SIZE_IN_BYTES - s->pos;
|
|
uint32_t n = s->range.length < avail ? s->range.length : avail;
|
|
memcpy((void *) s->range.startAddress, &s->data[s->pos], n);
|
|
s->pos += n, s->section_done += n;
|
|
s->range.startAddress += n, s->range.length -= n;
|
|
if (s->range.length == 0) { // Range done, skip alignment padding
|
|
s->in_range = false;
|
|
s->pos += s->range_gap, s->section_done += s->range_gap;
|
|
}
|
|
}
|
|
|
|
// Process one decrypted data block. See ldr_DoBlock()
|
|
static bool sb3_block(struct sb3 *s) {
|
|
while (s->pos < NBOOT_SB3_CHUNK_SIZE_IN_BYTES) {
|
|
if (!s->in_section) { // New section
|
|
memcpy(&s->section, &s->data[s->pos], sizeof(s->section));
|
|
if (s->section.sectionType == kSectionNone) break; // Rest is unused
|
|
if (s->section.sectionType != kSectionDataRange) return false;
|
|
s->in_section = true, s->section_done = 0, s->in_range = false;
|
|
s->pos += sizeof(sb3_section_header_t);
|
|
}
|
|
if (!s->in_range) { // New range
|
|
memcpy(&s->range, &s->data[s->pos], sizeof(s->range));
|
|
if (s->range.tag != SB3_DATA_RANGE_HEADER_TAG) return false;
|
|
s->in_range = true, s->has_expansion = false, s->range_gap = 0;
|
|
s->pos += sizeof(sb3_data_range_header_t);
|
|
s->section_done += sizeof(sb3_data_range_header_t);
|
|
if (s->range.cmd == kSB3_CmdLoad) {
|
|
s->range_gap = (SB3_DATA_ALIGNMENT_SIZE_IN_BYTE -
|
|
(s->range.length & 15U)) & 15U;
|
|
}
|
|
}
|
|
if (s->range.cmd == kSB3_CmdLoad) {
|
|
if (!s->has_expansion) {
|
|
if (s->pos == NBOOT_SB3_CHUNK_SIZE_IN_BYTES) break; // Next block
|
|
s->has_expansion = true;
|
|
s->pos += sizeof(sb3_data_range_expansion_t);
|
|
s->section_done += sizeof(sb3_data_range_expansion_t);
|
|
}
|
|
if (s->pos < NBOOT_SB3_CHUNK_SIZE_IN_BYTES) sb3_load(s);
|
|
} else if (s->range.cmd == kSB3_CmdExecute) {
|
|
s->executed = true;
|
|
return true;
|
|
} else {
|
|
s->in_range = false; // Other commands: nothing to do
|
|
}
|
|
if (s->section_done == s->section.length) s->in_section = false;
|
|
if (s->section_done > s->section.length) return false;
|
|
}
|
|
s->pos = 0;
|
|
return true;
|
|
}
|
|
|
|
// Feed image bytes, process complete blocks. See fsl_sbloader_pump()
|
|
static bool sb3_pump(struct sb3 *s, const uint8_t *p, uint32_t len) {
|
|
while (len > 0 && !s->executed) {
|
|
uint32_t n = s->buf_size - s->buf_pos;
|
|
if (n > len) n = len;
|
|
memcpy(&s->buf[s->buf_pos], p, n);
|
|
s->buf_pos += n, p += n, len -= n;
|
|
if (s->buf_pos < s->buf_size) break;
|
|
s->buf_pos = 0;
|
|
if (!s->header_done) {
|
|
if (!sb3_header(s)) return false;
|
|
} else {
|
|
if (s->blocks >= s->nboot.totalBlocks) return false;
|
|
if ((nboot_status_t) nboot_sb3_load_block(
|
|
&s->nboot, (uint32_t *) s->buf) != kStatus_NBOOT_Success) {
|
|
return false;
|
|
}
|
|
s->blocks++;
|
|
s->data = &s->buf[s->data_offset];
|
|
if (!sb3_block(s)) return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Authenticate the image with the ROM, copy it to CPU2 memory.
|
|
// See loader_process_sb_file()
|
|
static bool sb3_process(const uint8_t *img, size_t len) {
|
|
struct sb3 *s = &s_sb3;
|
|
bool ok = false;
|
|
memset(s, 0, sizeof(*s));
|
|
s->buf_size = sizeof(nboot_sb3_header_t);
|
|
// Start ELS only if it is off: the ROM keeps keys in it after boot
|
|
if ((CLKCTL0->PSCCTL0 & CLKCTL0_PSCCTL0_ELS_MASK) == 0 ||
|
|
(CLKCTL0->PSCCTL1 & CLKCTL0_PSCCTL1_ELS_APB_MASK) == 0 ||
|
|
(RSTCTL0->PRSTCTL0 & RSTCTL0_PRSTCTL0_ELS_MASK) != 0) {
|
|
CLOCK_EnableClock(kCLOCK_Els);
|
|
CLOCK_EnableClock(kCLOCK_ElsApb);
|
|
RESET_PeripheralReset(kELS_RST_SHIFT_RSTn);
|
|
}
|
|
if (nboot_context_init(&s->nboot) == kStatus_NBOOT_Success) {
|
|
ok = sb3_pump(s, img, (uint32_t) len) && s->executed;
|
|
(void) nboot_context_deinit(&s->nboot);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
// Load the radio firmware to CPU2 and start it. See sb3_fw_reset() and
|
|
// load_service_monolithic(), fsl_loader_utils.c. Call before changing the
|
|
// boot clock: the ROM trims the ELS glitch detector for it, and after the
|
|
// clock change ELS reports glitch events. Interrupts may be off.
|
|
// Return 0 on success, or the failed step: 1 chip revision, 2 OTP, 3 image
|
|
// load, 4 firmware start
|
|
int hal_radio_init(void) {
|
|
uint32_t n = 100000000; // Firmware start timeout, loop iterations
|
|
bool ok;
|
|
if ((SOCCTRL->CHIP_INFO & 15U) != 2U) return 1; // Image is for rev A2
|
|
POWER_PowerOffBle();
|
|
spin(100000);
|
|
if (otp_init(0) != kStatus_Success) return 2;
|
|
POWER_PowerOnBle();
|
|
spin(100000);
|
|
CPU2_MAGIC = 0;
|
|
ok = sb3_process(radio_fw, (size_t) (radio_fw_end - radio_fw));
|
|
(void) otp_deinit();
|
|
if (!ok) return 3;
|
|
CIU2_RST_SW3 |= CIU2_RST_SW3_CPU2_RST;
|
|
while (CPU2_MAGIC != CPU2_MAGIC_VALUE) {
|
|
if (--n == 0) return 4; // Firmware did not start
|
|
}
|
|
return 0;
|
|
}
|