Files
mongoose/tutorials/nxp/frdm-rw612/eth_radio_bridge/radio.c
T
2026-10-20 21:48:34 +01:00

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;
}