From 8a7188164d3671ab9aa6611c3db61049db73ab80 Mon Sep 17 00:00:00 2001 From: David Garske Date: Fri, 20 Mar 2026 14:36:53 -0700 Subject: [PATCH 1/8] Add wolfBoot support for NXP LPC54S018M-EVK --- Makefile | 4 + arch.mk | 5 + config/examples/nxp_lpc54s018m.config | 23 ++ docs/Targets.md | 197 +++++++++++++++++ hal/nxp_lpc54s018m.c | 293 ++++++++++++++++++++++++++ hal/nxp_lpc54s018m.ld | 54 +++++ test-app/ARM-nxp_lpc54s018m.ld | 58 +++++ test-app/Makefile | 10 + test-app/app_nxp_lpc54s018m.c | 133 ++++++++++++ 9 files changed, 777 insertions(+) create mode 100644 config/examples/nxp_lpc54s018m.config create mode 100644 hal/nxp_lpc54s018m.c create mode 100644 hal/nxp_lpc54s018m.ld create mode 100644 test-app/ARM-nxp_lpc54s018m.ld create mode 100644 test-app/app_nxp_lpc54s018m.c diff --git a/Makefile b/Makefile index 0285fbd333..db4ed3d0f7 100644 --- a/Makefile +++ b/Makefile @@ -334,6 +334,10 @@ wolfboot.efi: wolfboot.elf wolfboot.bin: wolfboot.elf @echo "\t[BIN] $@" $(Q)$(OBJCOPY) $(OBJCOPY_FLAGS) -O binary $^ $@ +ifeq ($(TARGET),nxp_lpc54s018m) + @echo "\t[LPC] enhanced boot block" + $(Q)python3 -c "import struct,os;f=open('$@','r+b');sz=os.path.getsize('$@');f.seek(0x24);f.write(struct.pack('<2I',0xEDDC94BD,0x160));f.seek(0x160);f.write(struct.pack('<25I',0xFEEDA5A5,3,0x10000000,sz-4,0,0,0,0,0,0xEDDC94BD,0,0,0,0x001640EF,0,0,0x1301001D,0,0,0,0x00000100,0,0,0x04030050,0x14110D09));f.seek(0);d=f.read(28);w=struct.unpack('<7I',d);s=sum(w)&0xFFFFFFFF;ck=(0x100000000-s)&0xFFFFFFFF;f.seek(0x1C);f.write(struct.pack(' +#include +#include +#include "image.h" + +/* -------------------------------------------------------------------------- */ +/* SPIFI controller registers (base 0x40080000) */ +/* -------------------------------------------------------------------------- */ +#define SPIFI_BASE 0x40080000 +#define SPIFI_CTRL (*(volatile uint32_t *)(SPIFI_BASE + 0x00)) +#define SPIFI_CMD (*(volatile uint32_t *)(SPIFI_BASE + 0x04)) +#define SPIFI_ADDR (*(volatile uint32_t *)(SPIFI_BASE + 0x08)) +#define SPIFI_IDATA (*(volatile uint32_t *)(SPIFI_BASE + 0x0C)) +#define SPIFI_CLIMIT (*(volatile uint32_t *)(SPIFI_BASE + 0x10)) +#define SPIFI_DATA (*(volatile uint32_t *)(SPIFI_BASE + 0x14)) +#define SPIFI_MCMD (*(volatile uint32_t *)(SPIFI_BASE + 0x18)) +#define SPIFI_STAT (*(volatile uint32_t *)(SPIFI_BASE + 0x1C)) + +/* STAT register bits */ +#define SPIFI_STAT_MCINIT (1 << 0) /* Memory command init done */ +#define SPIFI_STAT_CMD (1 << 1) /* Command active */ +#define SPIFI_STAT_RESET (1 << 4) /* Reset in progress */ + +/* CMD register field positions */ +#define SPIFI_CMD_DATALEN(n) ((n) & 0x3FFF) +#define SPIFI_CMD_POLL (1 << 14) +#define SPIFI_CMD_DOUT (1 << 15) /* 1=output, 0=input */ +#define SPIFI_CMD_INTLEN(n) (((n) & 7) << 16) +#define SPIFI_CMD_FIELDFORM(n) (((n) & 3) << 19) +#define SPIFI_CMD_FRAMEFORM(n) (((n) & 7) << 21) +#define SPIFI_CMD_OPCODE(n) (((n) & 0xFF) << 24) + +/* Frame/field format values */ +#define FRAMEFORM_OPCODE_ONLY 1 +#define FRAMEFORM_OPCODE_3ADDR 4 +#define FIELDFORM_ALL_SERIAL 0 +#define FIELDFORM_DATA_QUAD 2 + +/* W25Q32JV flash commands */ +#define W25Q_CMD_WRITE_ENABLE 0x06 +#define W25Q_CMD_READ_STATUS1 0x05 +#define W25Q_CMD_PAGE_PROGRAM 0x02 +#define W25Q_CMD_SECTOR_ERASE 0x20 /* 4KB sector erase */ +#define W25Q_CMD_FAST_READ_QUAD 0x6B /* Quad output fast read */ + +/* W25Q status register bits */ +#define W25Q_STATUS_BUSY 0x01 + +/* Flash geometry */ +#define FLASH_PAGE_SIZE 0x100 /* 256 bytes */ +#define SPIFI_FLASH_BASE 0x10000000 + +static uint8_t flash_page_cache[FLASH_PAGE_SIZE]; + +/* Pre-computed SPIFI CMD register values for each flash operation */ +#define CMD_WRITE_ENABLE \ + (SPIFI_CMD_DOUT | SPIFI_CMD_FRAMEFORM(FRAMEFORM_OPCODE_ONLY) | \ + SPIFI_CMD_OPCODE(W25Q_CMD_WRITE_ENABLE)) + +#define CMD_READ_STATUS \ + (SPIFI_CMD_DATALEN(1) | SPIFI_CMD_POLL | \ + SPIFI_CMD_FRAMEFORM(FRAMEFORM_OPCODE_ONLY) | \ + SPIFI_CMD_OPCODE(W25Q_CMD_READ_STATUS1)) + +#define CMD_SECTOR_ERASE \ + (SPIFI_CMD_DOUT | SPIFI_CMD_FRAMEFORM(FRAMEFORM_OPCODE_3ADDR) | \ + SPIFI_CMD_OPCODE(W25Q_CMD_SECTOR_ERASE)) + +#define CMD_PAGE_PROGRAM \ + (SPIFI_CMD_DATALEN(FLASH_PAGE_SIZE) | SPIFI_CMD_DOUT | \ + SPIFI_CMD_FRAMEFORM(FRAMEFORM_OPCODE_3ADDR) | \ + SPIFI_CMD_OPCODE(W25Q_CMD_PAGE_PROGRAM)) + +/* Memory-mode command: quad output fast read with 1 intermediate (dummy) byte */ +#define MCMD_READ_QUAD \ + (SPIFI_CMD_INTLEN(1) | SPIFI_CMD_FIELDFORM(FIELDFORM_DATA_QUAD) | \ + SPIFI_CMD_FRAMEFORM(FRAMEFORM_OPCODE_3ADDR) | \ + SPIFI_CMD_OPCODE(W25Q_CMD_FAST_READ_QUAD)) + +#ifdef NVM_FLASH_WRITEONCE +# error "wolfBoot LPC54S018M HAL: WRITEONCE not supported on SPIFI flash." +#endif + +/* -------------------------------------------------------------------------- */ +/* Boot-time initialization (runs from flash / XIP) */ +/* -------------------------------------------------------------------------- */ + +#ifdef __WOLFBOOT + +/* Assert hook (in case any remaining SDK code uses assert) */ +void __assert_func(const char *a, int b, const char *c, const char *d) +{ + (void)a; (void)b; (void)c; (void)d; + while (1) + ; +} + +void hal_init(void) +{ + /* The boot ROM has already configured basic clocks and SPIFI for XIP. + * We must NOT reconfigure clocks or SPIFI from flash (XIP) because + * changing the clock source or SPIFI controller while executing from + * SPIFI flash will cause an instruction fetch fault. + * + * Clock and SPIFI reconfiguration can only be done from RAM functions. + * The flash erase/write paths (all RAMFUNCTION) handle SPIFI mode + * switching as needed. + */ +} + +void hal_prepare_boot(void) +{ +} + +#endif /* __WOLFBOOT */ + +/* -------------------------------------------------------------------------- */ +/* SPIFI flash helper functions — all MUST run from RAM */ +/* -------------------------------------------------------------------------- */ + +/* + * Issue a SPIFI command. Exits memory mode if active, waits for ready, + * then writes CMD register. Entirely register-based — no SDK calls. + */ +static void RAMFUNCTION spifi_set_cmd(uint32_t cmd_val) +{ + /* If in memory mode (MCINIT set), reset to exit */ + if (SPIFI_STAT & SPIFI_STAT_MCINIT) { + SPIFI_STAT = SPIFI_STAT_RESET; + while (SPIFI_STAT & SPIFI_STAT_RESET) + ; + } + + /* Wait for any active command to complete */ + while (SPIFI_STAT & SPIFI_STAT_CMD) + ; + + SPIFI_CMD = cmd_val; +} + +/* + * Enter memory-mapped (XIP) mode using quad output fast read. + */ +static void RAMFUNCTION spifi_enter_memmode(void) +{ + /* Wait for any active command */ + while (SPIFI_STAT & SPIFI_STAT_CMD) + ; + + SPIFI_MCMD = MCMD_READ_QUAD; + + /* Wait for memory mode to initialize */ + while (!(SPIFI_STAT & SPIFI_STAT_MCINIT)) + ; +} + +static void RAMFUNCTION spifi_write_enable(void) +{ + spifi_set_cmd(CMD_WRITE_ENABLE); +} + +static void RAMFUNCTION spifi_wait_busy(void) +{ + uint8_t status; + + /* Issue read-status command in poll mode */ + spifi_set_cmd(CMD_READ_STATUS); + do { + status = *(volatile uint8_t *)&SPIFI_DATA; + } while (status & W25Q_STATUS_BUSY); +} + +/* + * Flash write — 256-byte page program via SPIFI + * + * Handles unaligned writes by decomposing into page-aligned operations. + * All flash data goes through a RAM page cache to ensure proper alignment. + */ +int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len) +{ + int idx = 0; + uint32_t page_address; + uint32_t offset; + int size; + int i; + + while (idx < len) { + page_address = ((address + idx) / FLASH_PAGE_SIZE) * FLASH_PAGE_SIZE; + if ((address + idx) > page_address) + offset = (address + idx) - page_address; + else + offset = 0; + size = FLASH_PAGE_SIZE - offset; + if (size > (len - idx)) + size = len - idx; + if (size > 0) { + /* Read current page content (flash is memory-mapped) */ + memcpy(flash_page_cache, (void *)(uintptr_t)page_address, + FLASH_PAGE_SIZE); + memcpy(flash_page_cache + offset, data + idx, size); + + /* Write enable */ + spifi_write_enable(); + + /* Set address and issue page program command */ + SPIFI_ADDR = page_address - SPIFI_FLASH_BASE; + spifi_set_cmd(CMD_PAGE_PROGRAM); + + /* Write page data as 32-bit words */ + for (i = 0; i < FLASH_PAGE_SIZE; i += 4) { + uint32_t word; + memcpy(&word, &flash_page_cache[i], 4); + SPIFI_DATA = word; + } + + /* Wait for program to complete */ + spifi_wait_busy(); + + /* Re-enter memory mode */ + spifi_enter_memmode(); + } + idx += size; + } + return 0; +} + +void RAMFUNCTION hal_flash_unlock(void) +{ +} + +void RAMFUNCTION hal_flash_lock(void) +{ +} + +/* + * Flash erase — 4KB sector erase via SPIFI + * + * Address must be aligned to WOLFBOOT_SECTOR_SIZE (4KB). + * Length must be a multiple of WOLFBOOT_SECTOR_SIZE. + */ +int RAMFUNCTION hal_flash_erase(uint32_t address, int len) +{ + uint32_t end = address + len; + + while (address < end) { + /* Write enable before each sector erase */ + spifi_write_enable(); + + /* Set address and issue sector erase command */ + SPIFI_ADDR = address - SPIFI_FLASH_BASE; + spifi_set_cmd(CMD_SECTOR_ERASE); + + /* Wait for erase to complete */ + spifi_wait_busy(); + + address += WOLFBOOT_SECTOR_SIZE; + } + + /* Re-enter memory mode */ + spifi_enter_memmode(); + + return 0; +} diff --git a/hal/nxp_lpc54s018m.ld b/hal/nxp_lpc54s018m.ld new file mode 100644 index 0000000000..e1d7086edb --- /dev/null +++ b/hal/nxp_lpc54s018m.ld @@ -0,0 +1,54 @@ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x10000000, LENGTH = @BOOTLOADER_PARTITION_SIZE@ + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 160K +} + +SECTIONS +{ + .text : + { + _start_text = .; + KEEP(*(.isr_vector)) + . = ALIGN(0x400); + *(.text*) + *(.rodata*) + *(.init*) + *(.fini*) + . = ALIGN(4); + _end_text = .; + } > FLASH + + .edidx : + { + . = ALIGN(4); + *(.ARM.exidx*) + } > FLASH + + _stored_data = .; + + .data : AT (_stored_data) + { + _start_data = .; + KEEP(*(.data*)) + . = ALIGN(4); + KEEP(*(.ramcode)) + . = ALIGN(4); + _end_data = .; + } > RAM + + .bss (NOLOAD) : + { + _start_bss = .; + __bss_start__ = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _end_bss = .; + __bss_end__ = .; + _end = .; + } > RAM + . = ALIGN(4); +} + +END_STACK = ORIGIN(RAM) + LENGTH(RAM); diff --git a/test-app/ARM-nxp_lpc54s018m.ld b/test-app/ARM-nxp_lpc54s018m.ld new file mode 100644 index 0000000000..b6826d6bcd --- /dev/null +++ b/test-app/ARM-nxp_lpc54s018m.ld @@ -0,0 +1,58 @@ +MEMORY +{ + FLASH (rx) : ORIGIN = @WOLFBOOT_TEST_APP_ADDRESS@, LENGTH = @WOLFBOOT_TEST_APP_SIZE@ + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K +} + +SECTIONS +{ + .text : + { + _start_text = .; + KEEP(*(.isr_vector)) + *(.init) + *(.fini) + *(.text*) + KEEP(*(.rodata*)) + . = ALIGN(4); + _end_text = .; + } > FLASH + + .ARM : + { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } > FLASH + + _stored_data = .; + + .data : AT (_stored_data) + { + _start_data = .; + KEEP(*(.data*)) + . = ALIGN(4); + KEEP(*(.ramcode)) + . = ALIGN(4); + _end_data = .; + } > RAM + + .bss : + { + _start_bss = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _end_bss = .; + _end = .; + } > RAM +} + +_wolfboot_partition_boot_address = @WOLFBOOT_PARTITION_BOOT_ADDRESS@; +_wolfboot_partition_size = @WOLFBOOT_PARTITION_SIZE@; +_wolfboot_partition_update_address = @WOLFBOOT_PARTITION_UPDATE_ADDRESS@; +_wolfboot_partition_swap_address = @WOLFBOOT_PARTITION_SWAP_ADDRESS@; + +PROVIDE(_start_heap = _end); +PROVIDE(end = _end); +PROVIDE(_end_stack = ORIGIN(RAM) + LENGTH(RAM)); diff --git a/test-app/Makefile b/test-app/Makefile index bb50facbab..1b4f1c078b 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -699,6 +699,16 @@ ifeq ($(TARGET),lpc55s69) LDFLAGS+=-Wl,--no-warn-rwx-segments endif +ifeq ($(TARGET),nxp_lpc54s018m) + LSCRIPT_TEMPLATE=ARM-nxp_lpc54s018m.ld + # Enable RAMFUNCTION for test app (flash ops must run from RAM) + CFLAGS+=-DRAM_CODE -D__WOLFBOOT + ifeq (,$(findstring nosys.specs,$(LDFLAGS))) + LDFLAGS+=--specs=nosys.specs + endif + LDFLAGS+=-Wl,--no-warn-rwx-segments +endif + ifeq ($(TARGET),imx_rt) LDFLAGS+=\ -mcpu=cortex-m7 -Wall --specs=nosys.specs -fno-common -ffunction-sections -fdata-sections \ diff --git a/test-app/app_nxp_lpc54s018m.c b/test-app/app_nxp_lpc54s018m.c new file mode 100644 index 0000000000..bf75872d8d --- /dev/null +++ b/test-app/app_nxp_lpc54s018m.c @@ -0,0 +1,133 @@ +/* app_nxp_lpc54s018m.c + * + * Test application for LPC54S018M-EVK + * + * Copyright (C) 2025 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include "target.h" +#include "wolfboot/wolfboot.h" + +/* LPC54S018M-EVK GPIO register definitions */ +/* GPIO base: 0x4008C000 */ +#define GPIO_BASE 0x4008C000 +#define GPIO_DIR(port) (*(volatile uint32_t *)(GPIO_BASE + 0x2000 + (port) * 4)) +#define GPIO_SET(port) (*(volatile uint32_t *)(GPIO_BASE + 0x2200 + (port) * 4)) +#define GPIO_CLR(port) (*(volatile uint32_t *)(GPIO_BASE + 0x2280 + (port) * 4)) + +/* SYSCON base for clock gating */ +#define SYSCON_BASE 0x40000000 +#define SYSCON_AHBCLKCTRL0 (*(volatile uint32_t *)(SYSCON_BASE + 0x200)) + +/* AHB clock control bits for GPIO ports (AHBCLKCTRL[0]) */ +#define AHBCLKCTRL0_GPIO2 (1UL << 16) +#define AHBCLKCTRL0_GPIO3 (1UL << 17) + +/* LPC54S018M-EVK User LEDs (accent LEDs active low) */ +#define LED1_PORT 3 +#define LED1_PIN 14 /* USR_LED1: P3.14 */ +#define LED2_PORT 3 +#define LED2_PIN 3 /* USR_LED2: P3.3 */ +#define LED3_PORT 2 +#define LED3_PIN 2 /* USR_LED3: P2.2 */ + +static void leds_init(void) +{ + /* Enable GPIO port 2 and 3 clocks */ + SYSCON_AHBCLKCTRL0 |= AHBCLKCTRL0_GPIO2 | AHBCLKCTRL0_GPIO3; + + /* Set LED pins as output, initially off (high = off for active-low LEDs) */ + GPIO_SET(LED1_PORT) = (1UL << LED1_PIN); + GPIO_DIR(LED1_PORT) |= (1UL << LED1_PIN); + + GPIO_SET(LED2_PORT) = (1UL << LED2_PIN); + GPIO_DIR(LED2_PORT) |= (1UL << LED2_PIN); + + GPIO_SET(LED3_PORT) = (1UL << LED3_PIN); + GPIO_DIR(LED3_PORT) |= (1UL << LED3_PIN); +} + +static void led_on(int port, int pin) +{ + GPIO_CLR(port) = (1UL << pin); /* Active low */ +} + +static void led_off(int port, int pin) +{ + GPIO_SET(port) = (1UL << pin); +} + +void main(void) +{ + uint32_t boot_ver, update_ver; + uint8_t boot_state, update_state; + + /* Clock and SPIFI already initialized by wolfBoot before jump to app */ + leds_init(); + + boot_ver = wolfBoot_current_firmware_version(); + update_ver = wolfBoot_update_firmware_version(); + if (wolfBoot_get_partition_state(PART_BOOT, &boot_state) != 0) + boot_state = IMG_STATE_NEW; + if (wolfBoot_get_partition_state(PART_UPDATE, &update_state) != 0) + update_state = IMG_STATE_NEW; + + /* Show LED indicator first (before flash writes which may crash if + * RAMFUNCTION isn't working — e.g. SPIFI XIP conflict) */ + if (boot_ver == 1) { + led_on(LED1_PORT, LED1_PIN); + } + else { + led_on(LED2_PORT, LED2_PIN); + } + + /* Confirm boot if state is TESTING or NEW */ + if (boot_ver != 0 && + (boot_state == IMG_STATE_TESTING || boot_state == IMG_STATE_NEW)) + { + wolfBoot_success(); + } + + if (boot_ver == 1 && update_ver != 0) { + /* Update available: LED3 on to indicate update activity */ + led_on(LED3_PORT, LED3_PIN); + wolfBoot_update_trigger(); + } + + while (1) { + __asm__ volatile ("wfi"); + } +} + + +#include "sys/stat.h" +int _getpid(void) { return 1; } +int _kill(int pid, int sig) { (void)pid; (void)sig; return -1; } +void _exit(int status) { _kill(status, -1); while (1) {} } +int _read(int file, char *ptr, int len) + { (void)file; (void)ptr; (void)len; return -1; } +int _write(int file, char *ptr, int len) + { (void)file; (void)ptr; return len; } +int _close(int file) { (void)file; return -1; } +int _isatty(int file) { (void)file; return 1; } +int _lseek(int file, int ptr, int dir) + { (void)file; (void)ptr; (void)dir; return 0; } +int _fstat(int file, struct stat *st) + { (void)file; st->st_mode = S_IFCHR; return 0; } From ecd4e77429a1d11a8d4beb38e8ab390b02ca40dd Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 8 Apr 2026 12:31:52 -0700 Subject: [PATCH 2/8] Fix NXP LPC54S018M SPIFI flash operations and complete boot+update cycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The LPC54S018M HAL had three critical SPIFI controller issues preventing flash write/erase operations from working, which blocked wolfBoot_success(), wolfBoot_update_trigger(), and the firmware swap: 1. Wrong memory-mode read command: Used opcode 0x6B (Quad Output, serial address) but the boot ROM configures 0xEB (Quad I/O, quad address) with MCMD=0xEB930000. The mismatch caused garbled address bits when re-entering XIP after flash operations, crashing on instruction fetch. 2. SPIFI POLL mode not waiting: The boot ROM leaves CLIMIT[7:0]=0x00 which makes the hardware POLL comparison always succeed immediately. Set IDATA=0x00 and CLIMIT[7:0]=0x01 to properly wait for flash BUSY to clear. 3. SPIFI reset clears CTRL/CLIMIT: The reset used to exit memory mode clears the boot ROM's timing config (CTRL=0x600F03E8) and cache limit (CLIMIT=0x08000000). Save and restore both registers around every reset. Additional changes: - Add RAMFUNCTION memcpy (src/string.o) to test app for SPIFI XIP safety - Add bare-metal UART driver for Flexcomm0 (non-blocking, skips if FC0 doesn't respond — observed on some LPC54S018M-EVK boards) - Add DSB+ISB barriers after entering memory mode for pipeline coherency - Enable DEBUG_UART in example config - Update test app to follow LPC55S69 port patterns with LED indicators for boot version and update status Tested: cold boot, ECC256 signature verify, wolfBoot_success(), wolfBoot_update_trigger(), and full v1->v2 firmware swap all working. Swap takes ~60 seconds for the 960KB partition (240 sector operations). --- config/examples/nxp_lpc54s018m.config | 2 +- hal/nxp_lpc54s018m.c | 241 ++++++++++++++++++++++++-- test-app/Makefile | 4 + test-app/app_nxp_lpc54s018m.c | 67 +++++-- 4 files changed, 291 insertions(+), 23 deletions(-) diff --git a/config/examples/nxp_lpc54s018m.config b/config/examples/nxp_lpc54s018m.config index 72485b9d29..11b23a878d 100644 --- a/config/examples/nxp_lpc54s018m.config +++ b/config/examples/nxp_lpc54s018m.config @@ -3,7 +3,7 @@ TARGET?=nxp_lpc54s018m SIGN?=ECC256 HASH?=SHA256 DEBUG?=0 -#DEBUG_UART?=1 +DEBUG_UART?=1 VTOR?=1 NO_ASM?=0 EXT_FLASH?=0 diff --git a/hal/nxp_lpc54s018m.c b/hal/nxp_lpc54s018m.c index 1bcdd3a59f..4a731ceade 100644 --- a/hal/nxp_lpc54s018m.c +++ b/hal/nxp_lpc54s018m.c @@ -71,7 +71,7 @@ #define W25Q_CMD_READ_STATUS1 0x05 #define W25Q_CMD_PAGE_PROGRAM 0x02 #define W25Q_CMD_SECTOR_ERASE 0x20 /* 4KB sector erase */ -#define W25Q_CMD_FAST_READ_QUAD 0x6B /* Quad output fast read */ +#define W25Q_CMD_FAST_READ_QUAD_IO 0xEB /* Quad I/O fast read */ /* W25Q status register bits */ #define W25Q_STATUS_BUSY 0x01 @@ -101,16 +101,167 @@ static uint8_t flash_page_cache[FLASH_PAGE_SIZE]; SPIFI_CMD_FRAMEFORM(FRAMEFORM_OPCODE_3ADDR) | \ SPIFI_CMD_OPCODE(W25Q_CMD_PAGE_PROGRAM)) -/* Memory-mode command: quad output fast read with 1 intermediate (dummy) byte */ +/* Memory-mode command: Quad I/O fast read (0xEB) — must match boot ROM config. + * Boot ROM MCMD = 0xEB930000: + * opcode 0xEB, FRAMEFORM=4 (opcode+3addr), FIELDFORM=2 (addr+data quad), + * INTLEN=3 (3 intermediate/dummy bytes in quad mode) */ #define MCMD_READ_QUAD \ - (SPIFI_CMD_INTLEN(1) | SPIFI_CMD_FIELDFORM(FIELDFORM_DATA_QUAD) | \ + (SPIFI_CMD_INTLEN(3) | SPIFI_CMD_FIELDFORM(FIELDFORM_DATA_QUAD) | \ SPIFI_CMD_FRAMEFORM(FRAMEFORM_OPCODE_3ADDR) | \ - SPIFI_CMD_OPCODE(W25Q_CMD_FAST_READ_QUAD)) + SPIFI_CMD_OPCODE(W25Q_CMD_FAST_READ_QUAD_IO)) #ifdef NVM_FLASH_WRITEONCE # error "wolfBoot LPC54S018M HAL: WRITEONCE not supported on SPIFI flash." #endif +/* -------------------------------------------------------------------------- */ +/* UART via Flexcomm0 (bare-metal, no SDK) */ +/* -------------------------------------------------------------------------- */ +#ifdef DEBUG_UART + +/* SYSCON registers for clock gating and peripheral reset */ +#define SYSCON_BASE 0x40000000 +#define SYSCON_AHBCLKCTRL0 (*(volatile uint32_t *)(SYSCON_BASE + 0x200)) +#define SYSCON_AHBCLKCTRL1 (*(volatile uint32_t *)(SYSCON_BASE + 0x204)) +#define SYSCON_PRESETCTRL1 (*(volatile uint32_t *)(SYSCON_BASE + 0x104)) +#define SYSCON_FCLKSEL0 (*(volatile uint32_t *)(SYSCON_BASE + 0x2B0)) + +#define AHBCLKCTRL0_IOCON (1UL << 13) +#define AHBCLKCTRL1_FC0 (1UL << 11) +#define PRESETCTRL1_FC0 (1UL << 11) + +/* IOCON pin mux registers */ +#define IOCON_BASE 0x40001000 +#define IOCON_PIO0_29 (*(volatile uint32_t *)(IOCON_BASE + 0x074)) +#define IOCON_PIO0_30 (*(volatile uint32_t *)(IOCON_BASE + 0x078)) +#define IOCON_FUNC1 1U +#define IOCON_DIGITAL_EN (1U << 8) + +/* Flexcomm0 USART registers */ +#define FC0_BASE 0x40086000 +#define FC0_CFG (*(volatile uint32_t *)(FC0_BASE + 0x000)) +#define FC0_BRG (*(volatile uint32_t *)(FC0_BASE + 0x020)) +#define FC0_OSR (*(volatile uint32_t *)(FC0_BASE + 0x028)) +#define FC0_FIFOCFG (*(volatile uint32_t *)(FC0_BASE + 0xE00)) +#define FC0_FIFOSTAT (*(volatile uint32_t *)(FC0_BASE + 0xE04)) +#define FC0_FIFOWR (*(volatile uint32_t *)(FC0_BASE + 0xE20)) +#define FC0_PSELID (*(volatile uint32_t *)(FC0_BASE + 0xFF8)) + +/* USART CFG bits */ +#define USART_CFG_ENABLE (1U << 0) +#define USART_CFG_DATALEN8 (1U << 2) /* 8-bit data */ + +/* FIFO bits */ +#define FIFOCFG_ENABLETX (1U << 0) +#define FIFOCFG_ENABLERX (1U << 1) +#define FIFOCFG_EMPTYTX (1U << 16) +#define FIFOCFG_EMPTYRX (1U << 17) +#define FIFOSTAT_TXEMPTY (1U << 3) +#define FIFOSTAT_TXNOTFULL (1U << 4) + +/* Baud rate: FRO 12 MHz / (13 * 8) = 115384 (0.16% error from 115200) */ +#define UART_OSR_VAL 12 /* oversampling = OSR + 1 = 13 */ +#define UART_BRG_VAL 7 /* divisor = BRG + 1 = 8 */ + +/* Timeout for UART FIFO polling */ +#define UART_TX_TIMEOUT 100000 + +/* SYSCON SET/CLR registers for atomic bit manipulation */ +#define SYSCON_PRESETCTRLSET1 (*(volatile uint32_t *)(SYSCON_BASE + 0x124)) +#define SYSCON_PRESETCTRLCLR1 (*(volatile uint32_t *)(SYSCON_BASE + 0x144)) +#define SYSCON_AHBCLKCTRLSET1 (*(volatile uint32_t *)(SYSCON_BASE + 0x224)) + +static int uart_ready; + +void uart_init(void) +{ + volatile int i; + + uart_ready = 0; + + /* Enable IOCON clock */ + SYSCON_AHBCLKCTRL0 |= AHBCLKCTRL0_IOCON; + + /* Pin mux: P0_29 = FC0_RXD, P0_30 = FC0_TXD (function 1, digital) */ + IOCON_PIO0_29 = IOCON_FUNC1 | IOCON_DIGITAL_EN; + IOCON_PIO0_30 = IOCON_FUNC1 | IOCON_DIGITAL_EN; + + /* Select FRO 12 MHz as Flexcomm0 clock source */ + SYSCON_FCLKSEL0 = 0; + + /* Enable Flexcomm0 clock (use atomic SET register) */ + SYSCON_AHBCLKCTRLSET1 = AHBCLKCTRL1_FC0; + + /* Reset Flexcomm0 using atomic CLR/SET registers (NXP SDK pattern) */ + SYSCON_PRESETCTRLCLR1 = PRESETCTRL1_FC0; /* Assert reset */ + while (SYSCON_PRESETCTRL1 & PRESETCTRL1_FC0) /* Wait for assert */ + ; + SYSCON_PRESETCTRLSET1 = PRESETCTRL1_FC0; /* Deassert reset */ + while (!(SYSCON_PRESETCTRL1 & PRESETCTRL1_FC0)) /* Wait for deassert */ + ; + + /* Small delay after reset deassertion for peripheral to stabilize */ + for (i = 0; i < 100; i++) + ; + + /* Select USART mode */ + FC0_PSELID = 1; + + /* Verify Flexcomm0 is accessible — if PSELID reads 0, peripheral is + * not responding (observed on some LPC54S018M boards). Skip UART. */ + if ((FC0_PSELID & 0x71) == 0) { + return; + } + + /* Configure 8N1 (disabled initially) */ + FC0_CFG = USART_CFG_DATALEN8; + + /* Set baud rate */ + FC0_OSR = UART_OSR_VAL; + FC0_BRG = UART_BRG_VAL; + + /* Enable and flush FIFOs */ + FC0_FIFOCFG = FIFOCFG_ENABLETX | FIFOCFG_ENABLERX | + FIFOCFG_EMPTYTX | FIFOCFG_EMPTYRX; + + /* Enable USART */ + FC0_CFG |= USART_CFG_ENABLE; + + uart_ready = 1; +} + +void uart_write(const char *buf, unsigned int sz) +{ + unsigned int i; + uint32_t timeout; + + if (!uart_ready) + return; + + for (i = 0; i < sz; i++) { + if (buf[i] == '\n') { + timeout = UART_TX_TIMEOUT; + while (!(FC0_FIFOSTAT & FIFOSTAT_TXNOTFULL) && --timeout) + ; + if (timeout == 0) + return; + FC0_FIFOWR = '\r'; + } + timeout = UART_TX_TIMEOUT; + while (!(FC0_FIFOSTAT & FIFOSTAT_TXNOTFULL) && --timeout) + ; + if (timeout == 0) + return; + FC0_FIFOWR = (uint32_t)buf[i]; + } + /* Wait for transmit to complete */ + timeout = UART_TX_TIMEOUT; + while (!(FC0_FIFOSTAT & FIFOSTAT_TXEMPTY) && --timeout) + ; +} + +#endif /* DEBUG_UART */ + /* -------------------------------------------------------------------------- */ /* Boot-time initialization (runs from flash / XIP) */ /* -------------------------------------------------------------------------- */ @@ -136,6 +287,10 @@ void hal_init(void) * The flash erase/write paths (all RAMFUNCTION) handle SPIFI mode * switching as needed. */ +#ifdef DEBUG_UART + uart_init(); + uart_write("wolfBoot HAL init\n", 18); +#endif } void hal_prepare_boot(void) @@ -154,11 +309,17 @@ void hal_prepare_boot(void) */ static void RAMFUNCTION spifi_set_cmd(uint32_t cmd_val) { - /* If in memory mode (MCINIT set), reset to exit */ + /* If in memory mode (MCINIT set), reset to exit. + * The SPIFI reset clears CTRL and CLIMIT — save and restore + * the boot ROM's configuration. */ if (SPIFI_STAT & SPIFI_STAT_MCINIT) { + uint32_t ctrl = SPIFI_CTRL; + uint32_t climit = SPIFI_CLIMIT; SPIFI_STAT = SPIFI_STAT_RESET; while (SPIFI_STAT & SPIFI_STAT_RESET) ; + SPIFI_CTRL = ctrl; + SPIFI_CLIMIT = climit; } /* Wait for any active command to complete */ @@ -173,15 +334,29 @@ static void RAMFUNCTION spifi_set_cmd(uint32_t cmd_val) */ static void RAMFUNCTION spifi_enter_memmode(void) { - /* Wait for any active command */ + uint32_t ctrl = SPIFI_CTRL; + uint32_t climit = SPIFI_CLIMIT; + + /* Wait for any active command to complete */ while (SPIFI_STAT & SPIFI_STAT_CMD) ; + /* Reset to clear stale command/POLL state, restore config, enter + * memory mode. */ + SPIFI_STAT = SPIFI_STAT_RESET; + while (SPIFI_STAT & SPIFI_STAT_RESET) + ; + SPIFI_CTRL = ctrl; + SPIFI_CLIMIT = climit; + SPIFI_MCMD = MCMD_READ_QUAD; /* Wait for memory mode to initialize */ while (!(SPIFI_STAT & SPIFI_STAT_MCINIT)) ; + + __asm__ volatile ("dsb"); + __asm__ volatile ("isb"); } static void RAMFUNCTION spifi_write_enable(void) @@ -191,13 +366,55 @@ static void RAMFUNCTION spifi_write_enable(void) static void RAMFUNCTION spifi_wait_busy(void) { - uint8_t status; + /* Use SPIFI POLL mode with properly configured IDATA/CLIMIT. + * + * The boot ROM leaves CLIMIT[7:0]=0x00 which makes the POLL comparison + * always succeed immediately. We must set CLIMIT[7:0] to mask the BUSY + * bit and IDATA[7:0] to the expected value (0 = not busy). + * + * CLIMIT also serves as the cache limit register (upper bits), so we + * preserve those bits and only modify the lower byte used for POLL mask. + */ + uint32_t saved_climit = SPIFI_CLIMIT; + + SPIFI_IDATA = 0x00; /* expect BUSY=0 */ + SPIFI_CLIMIT = (saved_climit & 0xFFFFFF00) | W25Q_STATUS_BUSY; /* mask bit 0 */ + + spifi_set_cmd(CMD_READ_STATUS); /* POLL mode command */ + + /* SPIFI hardware polls flash status internally. + * CMD bit clears when (status & mask) == (IDATA & mask). */ + while (SPIFI_STAT & SPIFI_STAT_CMD) + ; + + SPIFI_CLIMIT = saved_climit; /* restore cache limit */ +} + +/* + * Minimal SPIFI mode-switch test: exit memory mode, immediately re-enter. + * Used to verify the basic exit/enter cycle works before testing flash ops. + */ +void RAMFUNCTION spifi_test_mode_switch(void) +{ + uint32_t ctrl = SPIFI_CTRL; + uint32_t climit = SPIFI_CLIMIT; + + /* Exit memory mode */ + SPIFI_STAT = SPIFI_STAT_RESET; + while (SPIFI_STAT & SPIFI_STAT_RESET) + ; + + /* Restore all config */ + SPIFI_CTRL = ctrl; + SPIFI_CLIMIT = climit; + + /* Re-enter memory mode */ + SPIFI_MCMD = MCMD_READ_QUAD; + while (!(SPIFI_STAT & SPIFI_STAT_MCINIT)) + ; - /* Issue read-status command in poll mode */ - spifi_set_cmd(CMD_READ_STATUS); - do { - status = *(volatile uint8_t *)&SPIFI_DATA; - } while (status & W25Q_STATUS_BUSY); + __asm__ volatile ("dsb"); + __asm__ volatile ("isb"); } /* diff --git a/test-app/Makefile b/test-app/Makefile index 1b4f1c078b..c5e09d6a29 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -703,6 +703,10 @@ ifeq ($(TARGET),nxp_lpc54s018m) LSCRIPT_TEMPLATE=ARM-nxp_lpc54s018m.ld # Enable RAMFUNCTION for test app (flash ops must run from RAM) CFLAGS+=-DRAM_CODE -D__WOLFBOOT + # SPIFI XIP: memcpy/memset must be in RAM for use during flash operations + ifeq ($(DEBUG_UART),) + APP_OBJS+=../src/string.o + endif ifeq (,$(findstring nosys.specs,$(LDFLAGS))) LDFLAGS+=--specs=nosys.specs endif diff --git a/test-app/app_nxp_lpc54s018m.c b/test-app/app_nxp_lpc54s018m.c index bf75872d8d..e681b1f9af 100644 --- a/test-app/app_nxp_lpc54s018m.c +++ b/test-app/app_nxp_lpc54s018m.c @@ -25,6 +25,30 @@ #include "target.h" #include "wolfboot/wolfboot.h" +#ifdef DEBUG_UART +extern void uart_write(const char *buf, unsigned int sz); + +static void print_str(const char *s) +{ + unsigned int len = 0; + while (s[len] != '\0') + len++; + uart_write(s, len); +} + +static void print_hex32(uint32_t val) +{ + static const char hex[] = "0123456789abcdef"; + char buf[10]; + int i; + buf[0] = '0'; + buf[1] = 'x'; + for (i = 0; i < 8; i++) + buf[2 + i] = hex[(val >> (28 - i * 4)) & 0xF]; + uart_write(buf, 10); +} +#endif + /* LPC54S018M-EVK GPIO register definitions */ /* GPIO base: 0x4008C000 */ #define GPIO_BASE 0x4008C000 @@ -74,12 +98,34 @@ static void led_off(int port, int pin) GPIO_SET(port) = (1UL << pin); } +static void check_parts(uint32_t *pboot_ver, uint32_t *pupdate_ver, + uint8_t *pboot_state, uint8_t *pupdate_state) +{ + *pboot_ver = wolfBoot_current_firmware_version(); + *pupdate_ver = wolfBoot_update_firmware_version(); + if (wolfBoot_get_partition_state(PART_BOOT, pboot_state) != 0) + *pboot_state = IMG_STATE_NEW; + if (wolfBoot_get_partition_state(PART_UPDATE, pupdate_state) != 0) + *pupdate_state = IMG_STATE_NEW; + +#ifdef DEBUG_UART + print_str(" boot: ver="); + print_hex32(*pboot_ver); + print_str(" state="); + print_hex32(*pboot_state); + print_str("\n update: ver="); + print_hex32(*pupdate_ver); + print_str(" state="); + print_hex32(*pupdate_state); + print_str("\n"); +#endif +} + void main(void) { uint32_t boot_ver, update_ver; uint8_t boot_state, update_state; - /* Clock and SPIFI already initialized by wolfBoot before jump to app */ leds_init(); boot_ver = wolfBoot_current_firmware_version(); @@ -89,14 +135,8 @@ void main(void) if (wolfBoot_get_partition_state(PART_UPDATE, &update_state) != 0) update_state = IMG_STATE_NEW; - /* Show LED indicator first (before flash writes which may crash if - * RAMFUNCTION isn't working — e.g. SPIFI XIP conflict) */ - if (boot_ver == 1) { - led_on(LED1_PORT, LED1_PIN); - } - else { - led_on(LED2_PORT, LED2_PIN); - } + /* LED1 on immediately to show app is running */ + led_on(LED1_PORT, LED1_PIN); /* Confirm boot if state is TESTING or NEW */ if (boot_ver != 0 && @@ -106,11 +146,18 @@ void main(void) } if (boot_ver == 1 && update_ver != 0) { - /* Update available: LED3 on to indicate update activity */ + /* Update available: LED3 on, trigger update */ led_on(LED3_PORT, LED3_PIN); wolfBoot_update_trigger(); } + else if (boot_ver != 1) { + /* v2+: LED2 on */ + led_on(LED2_PORT, LED2_PIN); + } +#ifdef DEBUG_UART + print_str("App running\n"); +#endif while (1) { __asm__ volatile ("wfi"); } From fdb81639ccd62a54a94eae6e38d648a6b45c22b7 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 8 Apr 2026 15:32:56 -0700 Subject: [PATCH 3/8] Clean up NXP LPC54S018M port and align with LPC55S69 patterns - HAL: Remove debug spifi_test_mode_switch(), use wolfBoot_printf instead of raw uart_write, add printf.h include - Test app: Use check_parts() (was defined but unused), replace custom print_str/print_hex32 with wolfBoot_printf, fix LED logic to show LED1 for v1 and LED2 for v2+ (was always LED1) - Docs: Remove incorrect MCUXpresso SDK section (port is bare-metal), fix typo, add swap timing note, add flash script references - Build: Add --no-warn-rwx-segments to arch.mk, improve Makefile comments - Add tools/scripts/nxp-lpc54s018m-flash.sh following the nxp-s32k142-flash.sh pattern (build, sign, flash via pyocd) Tested: full boot + firmware update cycle (v1 -> v2 swap) verified on LPC54S018M-EVK hardware. --- arch.mk | 1 + docs/Targets.md | 65 ++++--- hal/nxp_lpc54s018m.c | 30 +-- test-app/Makefile | 2 +- test-app/app_nxp_lpc54s018m.c | 75 +++----- tools/scripts/nxp-lpc54s018m-flash.sh | 266 ++++++++++++++++++++++++++ 6 files changed, 329 insertions(+), 110 deletions(-) create mode 100755 tools/scripts/nxp-lpc54s018m-flash.sh diff --git a/arch.mk b/arch.mk index 8ff7fa2eff..8e43d4a0e3 100644 --- a/arch.mk +++ b/arch.mk @@ -1191,6 +1191,7 @@ endif ifeq ($(TARGET),nxp_lpc54s018m) ARCH_FLASH_OFFSET=0x10000000 + LDFLAGS+=-Wl,--no-warn-rwx-segments # Bare-metal HAL — no NXP SDK dependencies endif diff --git a/docs/Targets.md b/docs/Targets.md index 03afd16aac..cb69314f7a 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -2001,33 +2001,15 @@ sudo /usr/local/LinkServer/lpcscrypt/bin/lpcscrypt -d /dev/ttyACMx \ program /usr/local/LinkServer/lpcscrypt/probe_firmware/LPCLink2/LPC432x_CMSIS_DAP_V5_460.bin.hdr BankA ``` -### LPC54S018M: MCUXpresso SDK setup +### LPC54S018M: Toolchain -This requires the NXP MCUXpresso SDK. We tested using -[mcuxsdk-manifests](https://github.com/nxp-mcuxpresso/mcuxsdk-manifests) and -[CMSIS_5](https://github.com/nxp-mcuxpresso/CMSIS_5) placed under "../NXP". +This port uses bare-metal register access and does not require the NXP +MCUXpresso SDK. Only an ARM GCC toolchain (`arm-none-eabi-gcc`) and +[pyocd](https://pyocd.io/) are needed. ```sh -cd ../NXP - -# Install west -python -m venv west-venv -source west-venv/bin/activate -pip install west - -# Set up the repository -west init -m https://github.com/nxp-mcuxpresso/mcuxsdk-manifests.git mcuxpresso-sdk -cd mcuxpresso-sdk -west update - -deactivate -``` - -The CMSIS headers are also needed: - -```sh -cd ../NXP -git clone https://github.com/nxp-mcuxpresso/CMSIS_5.git +pip install pyocd +pyocd pack install LPC54S018J4MET180 ``` ### LPC54S018M: Flash partition layout @@ -2084,15 +2066,32 @@ instead of booting from SPIFI flash. ### LPC54S018M: Testing firmware update +A convenience script automates the full build, sign, and flash process: + +```sh +# Build and flash v1 only +./tools/scripts/nxp-lpc54s018m-flash.sh + +# Build, sign v2, and flash both (full update test) +./tools/scripts/nxp-lpc54s018m-flash.sh --test-update + +# Flash existing images without rebuilding +./tools/scripts/nxp-lpc54s018m-flash.sh --test-update --skip-build +``` + +**Manual steps** (if not using the script): + 1. Build and flash factory.bin (version 1). USR_LED1 (P3.14) lights up. 2. Sign a version 2 update image and load it to the update partition: ```sh # Build update image (version 2) -make test-app/image_v2_signed.bin +./tools/keytools/sign --ecc256 test-app/image.bin wolfboot_signing_private_key.der 2 ``` +**Using JLink:** + ``` JLinkExe -device LPC54S018M -if SWD -speed 4000 loadbin test-app/image_v2_signed.bin 0x10100000 @@ -2100,16 +2099,22 @@ r g ``` +**Using pyocd:** + +```sh +pyocd flash -t LPC54S018J4MET180 test-app/image_v2_signed.bin --base-address 0x10100000 +``` + 3. The test application detects the update, triggers a swap via - `wolfBoot_update_trigger()`, and resets. After the swap, USR_LED2 (P3.3) - lights up indicating version 2 is running. + `wolfBoot_update_trigger()`, and resets. After the swap (~60 seconds), + USR_LED2 (P3.3) lights up indicating version 2 is running. 4. The application calls `wolfBoot_success()` to confirm the update and prevent rollback. ### LPC54S018M: LED indicators -The test application uses three user LEDs (accent LEDs accent active low): +The test application uses three user LEDs (accent LEDs, active low): | LED | GPIO | Meaning | |-----------|--------|----------------------------| @@ -2117,6 +2122,10 @@ The test application uses three user LEDs (accent LEDs accent active low): | USR_LED2 | P3.3 | Version 2+ running | | USR_LED3 | P2.2 | Update activity in progress | +**Note:** The firmware swap takes approximately 60 seconds due to the SPIFI +controller mode-switch overhead for each of the 240 sector operations (960KB +partition with 4KB sectors). + ### LPC54S018M: Debugging with JLink ``` diff --git a/hal/nxp_lpc54s018m.c b/hal/nxp_lpc54s018m.c index 4a731ceade..35c63bb2aa 100644 --- a/hal/nxp_lpc54s018m.c +++ b/hal/nxp_lpc54s018m.c @@ -32,6 +32,7 @@ #include #include #include "image.h" +#include "printf.h" /* -------------------------------------------------------------------------- */ /* SPIFI controller registers (base 0x40080000) */ @@ -289,8 +290,8 @@ void hal_init(void) */ #ifdef DEBUG_UART uart_init(); - uart_write("wolfBoot HAL init\n", 18); #endif + wolfBoot_printf("wolfBoot HAL init\n"); } void hal_prepare_boot(void) @@ -390,33 +391,6 @@ static void RAMFUNCTION spifi_wait_busy(void) SPIFI_CLIMIT = saved_climit; /* restore cache limit */ } -/* - * Minimal SPIFI mode-switch test: exit memory mode, immediately re-enter. - * Used to verify the basic exit/enter cycle works before testing flash ops. - */ -void RAMFUNCTION spifi_test_mode_switch(void) -{ - uint32_t ctrl = SPIFI_CTRL; - uint32_t climit = SPIFI_CLIMIT; - - /* Exit memory mode */ - SPIFI_STAT = SPIFI_STAT_RESET; - while (SPIFI_STAT & SPIFI_STAT_RESET) - ; - - /* Restore all config */ - SPIFI_CTRL = ctrl; - SPIFI_CLIMIT = climit; - - /* Re-enter memory mode */ - SPIFI_MCMD = MCMD_READ_QUAD; - while (!(SPIFI_STAT & SPIFI_STAT_MCINIT)) - ; - - __asm__ volatile ("dsb"); - __asm__ volatile ("isb"); -} - /* * Flash write — 256-byte page program via SPIFI * diff --git a/test-app/Makefile b/test-app/Makefile index c5e09d6a29..d188c94458 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -703,7 +703,7 @@ ifeq ($(TARGET),nxp_lpc54s018m) LSCRIPT_TEMPLATE=ARM-nxp_lpc54s018m.ld # Enable RAMFUNCTION for test app (flash ops must run from RAM) CFLAGS+=-DRAM_CODE -D__WOLFBOOT - # SPIFI XIP: memcpy/memset must be in RAM for use during flash operations + # string.o provides RAMFUNCTION memcpy; already added when DEBUG_UART=1 (line ~108) ifeq ($(DEBUG_UART),) APP_OBJS+=../src/string.o endif diff --git a/test-app/app_nxp_lpc54s018m.c b/test-app/app_nxp_lpc54s018m.c index e681b1f9af..84cc3acc63 100644 --- a/test-app/app_nxp_lpc54s018m.c +++ b/test-app/app_nxp_lpc54s018m.c @@ -24,30 +24,7 @@ #include #include "target.h" #include "wolfboot/wolfboot.h" - -#ifdef DEBUG_UART -extern void uart_write(const char *buf, unsigned int sz); - -static void print_str(const char *s) -{ - unsigned int len = 0; - while (s[len] != '\0') - len++; - uart_write(s, len); -} - -static void print_hex32(uint32_t val) -{ - static const char hex[] = "0123456789abcdef"; - char buf[10]; - int i; - buf[0] = '0'; - buf[1] = 'x'; - for (i = 0; i < 8; i++) - buf[2 + i] = hex[(val >> (28 - i * 4)) & 0xF]; - uart_write(buf, 10); -} -#endif +#include "printf.h" /* LPC54S018M-EVK GPIO register definitions */ /* GPIO base: 0x4008C000 */ @@ -108,17 +85,10 @@ static void check_parts(uint32_t *pboot_ver, uint32_t *pupdate_ver, if (wolfBoot_get_partition_state(PART_UPDATE, pupdate_state) != 0) *pupdate_state = IMG_STATE_NEW; -#ifdef DEBUG_UART - print_str(" boot: ver="); - print_hex32(*pboot_ver); - print_str(" state="); - print_hex32(*pboot_state); - print_str("\n update: ver="); - print_hex32(*pupdate_ver); - print_str(" state="); - print_hex32(*pupdate_state); - print_str("\n"); -#endif + wolfBoot_printf(" boot: ver=0x%lx state=0x%02x\n", + *pboot_ver, *pboot_state); + wolfBoot_printf(" update: ver=0x%lx state=0x%02x\n", + *pupdate_ver, *pupdate_state); } void main(void) @@ -127,37 +97,36 @@ void main(void) uint8_t boot_state, update_state; leds_init(); - - boot_ver = wolfBoot_current_firmware_version(); - update_ver = wolfBoot_update_firmware_version(); - if (wolfBoot_get_partition_state(PART_BOOT, &boot_state) != 0) - boot_state = IMG_STATE_NEW; - if (wolfBoot_get_partition_state(PART_UPDATE, &update_state) != 0) - update_state = IMG_STATE_NEW; - - /* LED1 on immediately to show app is running */ - led_on(LED1_PORT, LED1_PIN); + check_parts(&boot_ver, &update_ver, &boot_state, &update_state); /* Confirm boot if state is TESTING or NEW */ if (boot_ver != 0 && (boot_state == IMG_STATE_TESTING || boot_state == IMG_STATE_NEW)) { + wolfBoot_printf("Calling wolfBoot_success()\n"); wolfBoot_success(); + check_parts(&boot_ver, &update_ver, &boot_state, &update_state); } - if (boot_ver == 1 && update_ver != 0) { - /* Update available: LED3 on, trigger update */ - led_on(LED3_PORT, LED3_PIN); - wolfBoot_update_trigger(); + if (boot_ver == 1) { + /* v1: LED1 on */ + led_on(LED1_PORT, LED1_PIN); + + if (update_ver != 0) { + wolfBoot_printf("Update detected, triggering update...\n"); + wolfBoot_update_trigger(); + check_parts(&boot_ver, &update_ver, &boot_state, &update_state); + /* LED3 on to indicate update triggered */ + led_on(LED3_PORT, LED3_PIN); + wolfBoot_printf("...done. Reboot to apply.\n"); + } } - else if (boot_ver != 1) { + else { /* v2+: LED2 on */ led_on(LED2_PORT, LED2_PIN); } -#ifdef DEBUG_UART - print_str("App running\n"); -#endif + wolfBoot_printf("App running\n"); while (1) { __asm__ volatile ("wfi"); } diff --git a/tools/scripts/nxp-lpc54s018m-flash.sh b/tools/scripts/nxp-lpc54s018m-flash.sh new file mode 100755 index 0000000000..6e54e83a36 --- /dev/null +++ b/tools/scripts/nxp-lpc54s018m-flash.sh @@ -0,0 +1,266 @@ +#!/bin/bash +# +# NXP LPC54S018M-EVK Flash Script +# +# This script automates: +# 1. Configure for NXP LPC54S018M-EVK +# 2. Build factory.bin (or factory + v2 update) +# 3. Flash via pyocd (CMSIS-DAP) +# +# The LPC-Link2 debug probe must have CMSIS-DAP firmware. +# See docs/Targets.md for Link2 probe setup instructions. +# + +set -e + +# Configuration (can be overridden via environment variables) +CONFIG_FILE="${CONFIG_FILE:-config/examples/nxp_lpc54s018m.config}" +PYOCD_TARGET="${PYOCD_TARGET:-lpc54s018j4met180}" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +# Parse command line arguments +SKIP_BUILD=0 +SKIP_FLASH=0 +TEST_UPDATE=0 + +usage() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --test-update Build v2 update image and flash to update partition" + echo " --skip-build Skip the build step (use existing binaries)" + echo " --skip-flash Skip flashing (just build)" + echo " -h, --help Show this help message" + echo "" + echo "Environment variables:" + echo " CONFIG_FILE Override config file (default: config/examples/nxp_lpc54s018m.config)" + echo " PYOCD_TARGET Override pyocd target (default: lpc54s018j4met180)" + echo " CROSS_COMPILE Override toolchain prefix (default: arm-none-eabi-)" + echo "" + echo "Examples:" + echo " $0 # Build and flash factory.bin (v1 only)" + echo " $0 --test-update # Build v1 + v2, flash both partitions" + echo " $0 --skip-flash # Build without flashing" + echo " $0 --test-update --skip-build # Flash existing v1 + v2 images" + echo "" + echo "Requirements:" + echo " - pyocd: pip install pyocd" + echo " - Target pack: pyocd pack install ${PYOCD_TARGET}" + echo " - ARM GCC: arm-none-eabi-gcc" + echo "" + echo "Note: The firmware swap takes ~60 seconds after the update is triggered." + exit 0 +} + +while [[ $# -gt 0 ]]; do + case $1 in + --test-update) + TEST_UPDATE=1 + shift + ;; + --skip-build) + SKIP_BUILD=1 + shift + ;; + --skip-flash) + SKIP_FLASH=1 + shift + ;; + -h|--help) + usage + ;; + *) + echo -e "${RED}Unknown option: $1${NC}" + usage + ;; + esac +done + +echo -e "${GREEN}=== NXP LPC54S018M-EVK Flash Script ===${NC}" + +# Function to parse all needed values from .config file +parse_config() { + local config_file="$1" + if [ ! -f "$config_file" ]; then + echo -e "${RED}Error: Config file not found: ${config_file}${NC}" + exit 1 + fi + + # Helper function to extract config value + get_config_value() { + local key="$1" + grep -E "^${key}" "$config_file" | head -1 | sed -E "s/^${key}\??=//" | tr -d '[:space:]' + } + + # Extract SIGN and HASH + SIGN_VALUE=$(get_config_value "SIGN") + HASH_VALUE=$(get_config_value "HASH") + + # Extract partition layout + WOLFBOOT_PARTITION_BOOT_ADDRESS=$(get_config_value "WOLFBOOT_PARTITION_BOOT_ADDRESS") + WOLFBOOT_PARTITION_UPDATE_ADDRESS=$(get_config_value "WOLFBOOT_PARTITION_UPDATE_ADDRESS") + WOLFBOOT_PARTITION_SIZE=$(get_config_value "WOLFBOOT_PARTITION_SIZE") + WOLFBOOT_SECTOR_SIZE=$(get_config_value "WOLFBOOT_SECTOR_SIZE") + + # Validate required fields + local missing="" + [ -z "$SIGN_VALUE" ] && missing="${missing}SIGN " + [ -z "$HASH_VALUE" ] && missing="${missing}HASH " + [ -z "$WOLFBOOT_PARTITION_BOOT_ADDRESS" ] && missing="${missing}WOLFBOOT_PARTITION_BOOT_ADDRESS " + [ -z "$WOLFBOOT_PARTITION_UPDATE_ADDRESS" ] && missing="${missing}WOLFBOOT_PARTITION_UPDATE_ADDRESS " + [ -z "$WOLFBOOT_PARTITION_SIZE" ] && missing="${missing}WOLFBOOT_PARTITION_SIZE " + [ -z "$WOLFBOOT_SECTOR_SIZE" ] && missing="${missing}WOLFBOOT_SECTOR_SIZE " + + if [ -n "$missing" ]; then + echo -e "${RED}Error: Missing required config values: ${missing}${NC}" + exit 1 + fi + + # Convert SIGN/HASH to lowercase flag format for sign tool + SIGN_FLAG="--$(echo "$SIGN_VALUE" | tr '[:upper:]' '[:lower:]')" + HASH_FLAG="--$(echo "$HASH_VALUE" | tr '[:upper:]' '[:lower:]')" + + # Ensure partition addresses have 0x prefix for bash arithmetic + for var in WOLFBOOT_PARTITION_BOOT_ADDRESS WOLFBOOT_PARTITION_UPDATE_ADDRESS WOLFBOOT_PARTITION_SIZE WOLFBOOT_SECTOR_SIZE; do + eval "val=\$$var" + if [[ ! "$val" =~ ^0x ]]; then + eval "$var=\"0x\${val}\"" + fi + done + + # Compute trailer sector addresses (last sector of each partition) + BOOT_TRAILER_SECTOR=$(printf "0x%X" $(( ${WOLFBOOT_PARTITION_BOOT_ADDRESS} + ${WOLFBOOT_PARTITION_SIZE} - ${WOLFBOOT_SECTOR_SIZE} ))) + UPDATE_TRAILER_SECTOR=$(printf "0x%X" $(( ${WOLFBOOT_PARTITION_UPDATE_ADDRESS} + ${WOLFBOOT_PARTITION_SIZE} - ${WOLFBOOT_SECTOR_SIZE} ))) + + # SPIFI flash base for this target + SPIFI_BASE="0x10000000" + + echo -e "${CYAN}Config: SIGN=${SIGN_VALUE} HASH=${HASH_VALUE}${NC}" + echo -e "${CYAN} BOOT=${WOLFBOOT_PARTITION_BOOT_ADDRESS} UPDATE=${WOLFBOOT_PARTITION_UPDATE_ADDRESS} SIZE=${WOLFBOOT_PARTITION_SIZE}${NC}" +} + +# Change to wolfboot root directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WOLFBOOT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +cd "${WOLFBOOT_ROOT}" +echo -e "${YELLOW}Working directory: ${WOLFBOOT_ROOT}${NC}" + +# Step 1: Configure and Build +if [ $SKIP_BUILD -eq 0 ]; then + echo "" + echo -e "${GREEN}[1/3] Configuring for NXP LPC54S018M-EVK...${NC}" + if [ ! -f "${CONFIG_FILE}" ]; then + echo -e "${RED}Error: Config file not found: ${CONFIG_FILE}${NC}" + exit 1 + fi + # Only copy if source and destination are different + if [ "${CONFIG_FILE}" != ".config" ]; then + cp "${CONFIG_FILE}" .config + echo "Copied ${CONFIG_FILE} to .config" + else + echo "Using existing .config" + fi + + # Parse all configuration values from .config + parse_config .config + + # Step 2: Build + echo "" + if [ $TEST_UPDATE -eq 1 ]; then + echo -e "${GREEN}[2/3] Building factory.bin + v2 update image...${NC}" + else + echo -e "${GREEN}[2/3] Building factory.bin...${NC}" + fi + + make clean + make CROSS_COMPILE=${CROSS_COMPILE:-arm-none-eabi-} + + if [ ! -f factory.bin ]; then + echo -e "${RED}Error: Build failed - factory.bin not found${NC}" + exit 1 + fi + echo -e "${GREEN}factory.bin built successfully${NC}" + + if [ $TEST_UPDATE -eq 1 ]; then + echo "" + echo -e "${CYAN}Signing test-app with version 2...${NC}" + ./tools/keytools/sign ${SIGN_FLAG} ${HASH_FLAG} \ + test-app/image.bin wolfboot_signing_private_key.der 2 + if [ ! -f test-app/image_v2_signed.bin ]; then + echo -e "${RED}Error: Failed to sign v2 image${NC}" + exit 1 + fi + echo -e "${GREEN}image_v2_signed.bin created${NC}" + fi +else + echo "" + echo -e "${YELLOW}[1/3] Skipping configure (--skip-build)${NC}" + echo -e "${YELLOW}[2/3] Skipping build (--skip-build)${NC}" + + # Still need to parse config for flash addresses + if [ -f .config ]; then + parse_config .config + else + parse_config "${CONFIG_FILE}" + fi +fi + +# Step 3: Flash +if [ $SKIP_FLASH -eq 0 ]; then + echo "" + echo -e "${GREEN}[3/3] Flashing to LPC54S018M-EVK...${NC}" + + # Check pyocd is available + if ! command -v pyocd &> /dev/null; then + echo -e "${RED}Error: pyocd not found. Install with: pip install pyocd${NC}" + echo -e "${YELLOW}Then install target pack: pyocd pack install ${PYOCD_TARGET}${NC}" + exit 1 + fi + + # Verify images exist + if [ ! -f factory.bin ]; then + echo -e "${RED}Error: factory.bin not found. Run without --skip-build first.${NC}" + exit 1 + fi + if [ $TEST_UPDATE -eq 1 ] && [ ! -f test-app/image_v2_signed.bin ]; then + echo -e "${RED}Error: test-app/image_v2_signed.bin not found.${NC}" + exit 1 + fi + + # Erase partition trailer sectors for clean boot state + echo -e "${CYAN}Erasing partition trailers for clean state...${NC}" + pyocd erase -t ${PYOCD_TARGET} -s ${BOOT_TRAILER_SECTOR}+${WOLFBOOT_SECTOR_SIZE} + pyocd erase -t ${PYOCD_TARGET} -s ${UPDATE_TRAILER_SECTOR}+${WOLFBOOT_SECTOR_SIZE} + + # Flash factory image (wolfBoot + v1 test app) + echo -e "${CYAN}Flashing factory.bin -> ${SPIFI_BASE}${NC}" + pyocd flash -t ${PYOCD_TARGET} factory.bin --base-address ${SPIFI_BASE} + + if [ $TEST_UPDATE -eq 1 ]; then + echo -e "${CYAN}Flashing image_v2_signed.bin -> ${WOLFBOOT_PARTITION_UPDATE_ADDRESS}${NC}" + pyocd flash -t ${PYOCD_TARGET} test-app/image_v2_signed.bin \ + --base-address ${WOLFBOOT_PARTITION_UPDATE_ADDRESS} + fi + + echo -e "${GREEN}Flash complete!${NC}" +else + echo "" + echo -e "${YELLOW}[3/3] Skipping flash (--skip-flash)${NC}" +fi + +echo "" +echo -e "${GREEN}=== Complete ===${NC}" +echo "" +if [ $TEST_UPDATE -eq 1 ]; then + echo -e "${CYAN}Power cycle the board. Expected sequence:${NC}" + echo " 1st boot: USR_LED1 (v1 running) + USR_LED3 (update triggered)" + echo " 2nd boot: Wait ~60s for swap, then USR_LED2 (v2 running)" +else + echo -e "${CYAN}Power cycle the board. Expected: USR_LED1 (v1 running)${NC}" +fi From 4ac6e9aeb82edb42cea9182d46021d29187b43fe Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 14 Apr 2026 15:55:26 -0700 Subject: [PATCH 4/8] Fixes for clocks and testing updates. --- hal/nxp_lpc54s018m.c | 85 ++++++++++++++++++++++++++++------- test-app/Makefile | 5 +-- test-app/app_nxp_lpc54s018m.c | 18 +------- 3 files changed, 73 insertions(+), 35 deletions(-) diff --git a/hal/nxp_lpc54s018m.c b/hal/nxp_lpc54s018m.c index 35c63bb2aa..2c8505c8dd 100644 --- a/hal/nxp_lpc54s018m.c +++ b/hal/nxp_lpc54s018m.c @@ -115,13 +115,27 @@ static uint8_t flash_page_cache[FLASH_PAGE_SIZE]; # error "wolfBoot LPC54S018M HAL: WRITEONCE not supported on SPIFI flash." #endif +/* -------------------------------------------------------------------------- */ +/* SYSCON registers (shared across clock + UART) */ +/* -------------------------------------------------------------------------- */ +#define SYSCON_BASE 0x40000000 +#define SYSCON_PDRUNCFGCLR0 (*(volatile uint32_t *)(SYSCON_BASE + 0x04C)) +#define SYSCON_MAINCLKSELA (*(volatile uint32_t *)(SYSCON_BASE + 0x280)) +#define SYSCON_MAINCLKSELB (*(volatile uint32_t *)(SYSCON_BASE + 0x284)) +#define SYSCON_AHBCLKDIV (*(volatile uint32_t *)(SYSCON_BASE + 0x380)) +#define SYSCON_FROCTRL (*(volatile uint32_t *)(SYSCON_BASE + 0x550)) + +/* FROCTRL bits */ +#define FROCTRL_SEL_96MHZ (1UL << 14) /* 0=48MHz, 1=96MHz */ +#define FROCTRL_HSPDCLK (1UL << 30) /* Enable FRO high-speed output */ +#define FROCTRL_WRTRIM (1UL << 31) /* Write trim enable */ + /* -------------------------------------------------------------------------- */ /* UART via Flexcomm0 (bare-metal, no SDK) */ /* -------------------------------------------------------------------------- */ #ifdef DEBUG_UART /* SYSCON registers for clock gating and peripheral reset */ -#define SYSCON_BASE 0x40000000 #define SYSCON_AHBCLKCTRL0 (*(volatile uint32_t *)(SYSCON_BASE + 0x200)) #define SYSCON_AHBCLKCTRL1 (*(volatile uint32_t *)(SYSCON_BASE + 0x204)) #define SYSCON_PRESETCTRL1 (*(volatile uint32_t *)(SYSCON_BASE + 0x104)) @@ -193,12 +207,13 @@ void uart_init(void) /* Enable Flexcomm0 clock (use atomic SET register) */ SYSCON_AHBCLKCTRLSET1 = AHBCLKCTRL1_FC0; - /* Reset Flexcomm0 using atomic CLR/SET registers (NXP SDK pattern) */ - SYSCON_PRESETCTRLCLR1 = PRESETCTRL1_FC0; /* Assert reset */ - while (SYSCON_PRESETCTRL1 & PRESETCTRL1_FC0) /* Wait for assert */ + /* Reset Flexcomm0: NXP PRESETCTRL polarity is bit=1 means IN reset, + * bit=0 means OUT of reset. Use SET to assert, CLR to deassert. */ + SYSCON_PRESETCTRLSET1 = PRESETCTRL1_FC0; /* Assert reset (bit→1) */ + while (!(SYSCON_PRESETCTRL1 & PRESETCTRL1_FC0)) /* Wait for bit=1 */ ; - SYSCON_PRESETCTRLSET1 = PRESETCTRL1_FC0; /* Deassert reset */ - while (!(SYSCON_PRESETCTRL1 & PRESETCTRL1_FC0)) /* Wait for deassert */ + SYSCON_PRESETCTRLCLR1 = PRESETCTRL1_FC0; /* Deassert reset (bit→0) */ + while (SYSCON_PRESETCTRL1 & PRESETCTRL1_FC0) /* Wait for bit=0 */ ; /* Small delay after reset deassertion for peripheral to stabilize */ @@ -209,7 +224,7 @@ void uart_init(void) FC0_PSELID = 1; /* Verify Flexcomm0 is accessible — if PSELID reads 0, peripheral is - * not responding (observed on some LPC54S018M boards). Skip UART. */ + * not responding. Skip UART. */ if ((FC0_PSELID & 0x71) == 0) { return; } @@ -277,17 +292,55 @@ void __assert_func(const char *a, int b, const char *c, const char *d) ; } +/* Forward declaration — defined later in the file as RAMFUNCTION */ +static void RAMFUNCTION spifi_enter_memmode(void); + +/* + * Boost main clock from FRO 12MHz to FRO_HF 96MHz (8x speedup). + * Must run from RAM because changing MAINCLK affects the SPIFI XIP clock. + * UART is unaffected: FCLKSEL0=0 selects FRO 12MHz for Flexcomm0 independently. + */ +static void RAMFUNCTION hal_clock_boost(void) +{ + /* Ensure FRO, ROM, and VD6 (OTP) power domains are enabled. + * Boot ROM usually leaves these on, but clearing is idempotent. */ + SYSCON_PDRUNCFGCLR0 = (1UL << 4) | (1UL << 17) | (1UL << 29); + + /* Confirm main clock is FRO 12MHz (safety before frequency change). */ + SYSCON_MAINCLKSELA = 0U; + SYSCON_MAINCLKSELB = 0U; + + /* Enable FRO_HF directly via FROCTRL (bypass ROM API which faults + * on this silicon). Set HSPDCLK + SEL=96MHz with FREQTRIM=0; the FRO + * will operate at nominal 96MHz with reduced accuracy (no OTP trim), + * which is fine for crypto acceleration. */ + SYSCON_FROCTRL = FROCTRL_HSPDCLK | FROCTRL_SEL_96MHZ; + + /* Brief delay for FRO_HF to stabilize */ + { + volatile int i; + for (i = 0; i < 1000; i++) ; + } + + /* AHB divider = /1 (96MHz AHB clock). */ + SYSCON_AHBCLKDIV = 0U; + + /* Switch main clock to FRO_HF. SPIFI clock (SPIFICLKSEL=MAIN_CLK, + * SPIFICLKDIV=/1) auto-scales to 96MHz — within W25Q32JV quad I/O + * limit of 104MHz. Boot ROM's MCMD already has 6 dummy cycles + * (INTLEN=3 in quad mode) which covers the full speed range. */ + SYSCON_MAINCLKSELA = 3U; + + /* Re-enter SPIFI memory mode at new clock. */ + spifi_enter_memmode(); +} + void hal_init(void) { - /* The boot ROM has already configured basic clocks and SPIFI for XIP. - * We must NOT reconfigure clocks or SPIFI from flash (XIP) because - * changing the clock source or SPIFI controller while executing from - * SPIFI flash will cause an instruction fetch fault. - * - * Clock and SPIFI reconfiguration can only be done from RAM functions. - * The flash erase/write paths (all RAMFUNCTION) handle SPIFI mode - * switching as needed. - */ + /* Boost from FRO 12MHz to FRO_HF 96MHz before anything else. + * Runs from RAM because changing MAINCLK affects SPIFI XIP. */ + hal_clock_boost(); + #ifdef DEBUG_UART uart_init(); #endif diff --git a/test-app/Makefile b/test-app/Makefile index d188c94458..d17a3a8db1 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -707,9 +707,8 @@ ifeq ($(TARGET),nxp_lpc54s018m) ifeq ($(DEBUG_UART),) APP_OBJS+=../src/string.o endif - ifeq (,$(findstring nosys.specs,$(LDFLAGS))) - LDFLAGS+=--specs=nosys.specs - endif + # Use shared newlib syscall stubs (routes _write to uart_write) + APP_OBJS+=syscalls.o LDFLAGS+=-Wl,--no-warn-rwx-segments endif diff --git a/test-app/app_nxp_lpc54s018m.c b/test-app/app_nxp_lpc54s018m.c index 84cc3acc63..c786d44e0a 100644 --- a/test-app/app_nxp_lpc54s018m.c +++ b/test-app/app_nxp_lpc54s018m.c @@ -96,7 +96,9 @@ void main(void) uint32_t boot_ver, update_ver; uint8_t boot_state, update_state; + uart_init(); leds_init(); + wolfBoot_printf("Test app (v%lu)\n", wolfBoot_current_firmware_version()); check_parts(&boot_ver, &update_ver, &boot_state, &update_state); /* Confirm boot if state is TESTING or NEW */ @@ -131,19 +133,3 @@ void main(void) __asm__ volatile ("wfi"); } } - - -#include "sys/stat.h" -int _getpid(void) { return 1; } -int _kill(int pid, int sig) { (void)pid; (void)sig; return -1; } -void _exit(int status) { _kill(status, -1); while (1) {} } -int _read(int file, char *ptr, int len) - { (void)file; (void)ptr; (void)len; return -1; } -int _write(int file, char *ptr, int len) - { (void)file; (void)ptr; return len; } -int _close(int file) { (void)file; return -1; } -int _isatty(int file) { (void)file; return 1; } -int _lseek(int file, int ptr, int dir) - { (void)file; (void)ptr; (void)dir; return 0; } -int _fstat(int file, struct stat *st) - { (void)file; st->st_mode = S_IFCHR; return 0; } From 177ab1430c82e55d267aa75a01f0177356ac38f2 Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 14 Apr 2026 16:11:57 -0700 Subject: [PATCH 5/8] Expand documentation for the enhaced boot block --- docs/Targets.md | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/docs/Targets.md b/docs/Targets.md index cb69314f7a..26ede29b15 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -2059,10 +2059,40 @@ pyocd flash -t LPC54S018J4MET180 factory.bin --base-address 0x10000000 pyocd reset -t LPC54S018J4MET180 ``` -**Note:** The LPC54S018M boot ROM validates a vector table checksum at offset -0x1C. The build system automatically computes and patches this checksum into -`wolfboot.bin`. If the checksum is invalid, the boot ROM will enter ISP mode -instead of booting from SPIFI flash. +**Note:** The LPC54S018M boot ROM requires two post-processing steps on +`wolfboot.bin` before the chip can boot from SPIFI flash. Both are applied +automatically by the top-level `Makefile` (see the `wolfboot.bin:` rule, +gated on `TARGET=nxp_lpc54s018m`), so no user action is needed — but they +are documented here because the patched binary will not match the ELF output +and this affects any external flashing or signing workflow. + +1. **Vector table checksum** (offset `0x1C`): + The boot ROM validates that the sum of the first 8 words of the vector + table (SP, Reset, NMI, HardFault, MemManage, BusFault, UsageFault, + checksum) equals zero. The build computes + `ck = (-sum_of_first_7_words) & 0xFFFFFFFF` and writes `ck` at offset + `0x1C`. If this checksum is wrong, the boot ROM enters ISP mode + (USB DFU / UART autobaud) instead of booting from SPIFI. + +2. **Enhanced boot block** (at offset `0x160`, pointed to by offset `0x24`): + A 100-byte structure (25 × uint32) that the boot ROM reads **before** + jumping to the application, to configure the SPIFI controller for + quad I/O fast read XIP. Key fields: + - `0xFEEDA5A5` magic word + - Image type / image load address (`0x10000000`) / image size + - `0xEDDC94BD` signature (matches the pointer at offset `0x24`) + - SPIFI device configuration words (`0x001640EF`, `0x1301001D`, + `0x04030050`, `0x14110D09`) — these describe the W25Q32JV command + set, dummy cycles, and timing + - Offset `0x24` contains `{0xEDDC94BD, 0x160}` — the marker plus the + pointer to the block itself + + Without this block the boot ROM leaves SPIFI in slow single-lane read + mode (or unconfigured), and XIP either fails or runs far below spec. + +The build prints both `[LPC] enhanced boot block` and +`vector checksum: 0xXXXXXXXX` lines when these steps run — absence of +either message means the binary is not bootable on this chip. ### LPC54S018M: Testing firmware update From e2edbe9f47f39b4c1466a36fb622da86ae095408 Mon Sep 17 00:00:00 2001 From: David Garske Date: Wed, 15 Apr 2026 10:46:46 -0700 Subject: [PATCH 6/8] Add tests and improve docs --- .github/workflows/test-configs.yml | 6 ++++ config/examples/nxp_lpc54s018m.config | 30 ++++++++++++++++ docs/Targets.md | 51 ++++++++++++++++++++------- hal/nxp_lpc54s018m.c | 16 ++++++--- tools/scripts/nxp-lpc54s018m-flash.sh | 30 ++++++++++++---- 5 files changed, 110 insertions(+), 23 deletions(-) diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 11159bf3c2..b3189f5dec 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -211,6 +211,12 @@ jobs: arch: ppc config-file: ./config/examples/nxp-t2080.config + nxp_lpc54s018m_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + config-file: ./config/examples/nxp_lpc54s018m.config + nxp_ls1028a_test: uses: ./.github/workflows/test-build.yml with: diff --git a/config/examples/nxp_lpc54s018m.config b/config/examples/nxp_lpc54s018m.config index 11b23a878d..d238b184b5 100644 --- a/config/examples/nxp_lpc54s018m.config +++ b/config/examples/nxp_lpc54s018m.config @@ -1,21 +1,51 @@ +# wolfBoot configuration for NXP LPCXpresso54S018M-EVK +# +# Target: NXP LPC54S018M (Cortex-M4F, 180 MHz) +# Boot: ROM boot loads wolfBoot from external SPIFI QSPI flash at 0x10000000 +# HAL: Bare-metal (hal/nxp_lpc54s018m.c) — no NXP MCUXpresso SDK required +# +# Flash layout (SPIFI QSPI, 16 MB total): +# 0x10000000 wolfBoot (up to BOOT partition base) +# 0x10010000 BOOT partition (960 KB, signed application) +# 0x10100000 UPDATE partition (960 KB) +# 0x101F0000 SWAP sector (4 KB) + ARCH?=ARM TARGET?=nxp_lpc54s018m + +# Signature and hash algorithms SIGN?=ECC256 HASH?=SHA256 + DEBUG?=0 DEBUG_UART?=1 + +# Vector table relocation is required — ROM boot leaves VTOR at 0 VTOR?=1 + NO_ASM?=0 + +# Single-flash layout (all partitions in internal SPIFI region) EXT_FLASH?=0 SPI_FLASH?=0 + ALLOW_DOWNGRADE?=0 NVM_FLASH_WRITEONCE?=0 WOLFBOOT_VERSION?=0 V?=0 + +# Single-precision math (reduced footprint, no ASM) SPMATH?=1 + +# RAM_CODE required: flash erase/program routines must execute from SRAM +# because SPIFI cannot service instruction fetches while being written RAM_CODE?=1 + DUALBANK_SWAP?=0 + +# Enable NXP LPC PKA peripheral for ECC acceleration PKA?=1 + WOLFBOOT_PARTITION_SIZE?=0xF0000 WOLFBOOT_SECTOR_SIZE?=0x1000 WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x10010000 diff --git a/docs/Targets.md b/docs/Targets.md index 26ede29b15..649a28f1aa 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -20,8 +20,8 @@ This README describes configuration of supported targets. * [Nordic nRF54L15](#nordic-nrf54l15) * [NXP iMX-RT](#nxp-imx-rt) * [NXP Kinetis](#nxp-kinetis) -* [NXP LPC54xxx](#nxp-lpc54xxx) -* [NXP LPC54S018M](#nxp-lpc54s018m) +* [NXP LPC546xx](#nxp-lpc546xx) +* [NXP LPC540xx / LPC54S0xx (SPIFI boot)](#nxp-lpc540xx--lpc54s0xx-spifi-boot) * [NXP LPC55S69](#nxp-lpc55s69) * [NXP LS1028A](#nxp-ls1028a) * [NXP MCXA153](#nxp-mcxa153) @@ -1897,11 +1897,17 @@ c ``` -## NXP LPC54xxx +## NXP LPC546xx + +This covers the LPC546xx series (Cortex-M4F with internal NOR flash), using the +NXP MCUXpresso SDK. Tested on LPC54606J512. + +For the LPC540xx / LPC54S0xx SPIFI-boot series (no internal flash), see the +[next section](#nxp-lpc540xx--lpc54s0xx-spifi-boot). ### Build Options -The LPC54xxx build can be obtained by specifying the CPU type and the MCUXpresso SDK path at compile time. +The build can be obtained by specifying the CPU type and the MCUXpresso SDK path at compile time. The following configuration has been tested against LPC54606J512BD208: @@ -1938,15 +1944,22 @@ arm-none-eabi-gdb wolfboot.elf -ex "target remote localhost:3333" ``` -## NXP LPC54S018M +## NXP LPC540xx / LPC54S0xx (SPIFI boot) -The NXP LPC54S018M is a Cortex-M4 microcontroller running at 180MHz. Unlike the -LPC54606 which has internal flash, the LPC54S018M has **no internal NOR flash** — -all code executes from on-package SPIFI-mapped QSPI flash (Winbond W25Q32JV, 4MB) -at address `0x10000000`. +This section covers the LPC540xx and LPC54S0xx family (LPC54005, LPC54016, +LPC54018, LPC54S005, LPC54S016, LPC54S018, and the "M" in-package-flash +variants LPC54018M / LPC54S018M). These are Cortex-M4F parts at 180 MHz with +**no internal NOR flash** — all code executes from SPIFI-mapped QSPI flash at +address `0x10000000`. The boot ROM loads the image from SPIFI via an +"enhanced boot block" descriptor embedded in the vector table area. -This has been tested on the LPC54S018M-EVK board, which includes an on-board -Link2 debug probe (CMSIS-DAP / J-Link compatible) and a VCOM UART via Flexcomm0. +The wolfBoot HAL (`hal/nxp_lpc54s018m.c`) is bare-metal (no NXP SDK +dependency) and targets this whole SPIFI-boot subseries. It has been +verified on the LPC54S018M-EVK, which uses an on-package Winbond W25Q32JV +(4MB) and provides an on-board Link2 debug probe (CMSIS-DAP / J-Link) with +a VCOM UART on Flexcomm0. Other members of the family should work after +adjusting the SPIFI device configuration words to match the attached QSPI +part and sector size. Because flash erase/write operations disable XIP (execute-in-place), all flash programming functions must run from RAM. The configuration uses `RAM_CODE=1` to @@ -2096,7 +2109,21 @@ either message means the binary is not bootable on this chip. ### LPC54S018M: Testing firmware update -A convenience script automates the full build, sign, and flash process: +The helper script [`tools/scripts/nxp-lpc54s018m-flash.sh`](../tools/scripts/nxp-lpc54s018m-flash.sh) +automates the full **build → sign → flash** cycle for the LPC54S018M-EVK: + +1. Copies `config/examples/nxp_lpc54s018m.config` to `.config` +2. Runs `make` to produce `factory.bin` (wolfBoot + signed v1 test-app) +3. Parses the active `.config` to resolve partition and trailer addresses +4. Erases the BOOT and UPDATE partition trailer sectors (clean boot state) +5. Flashes `factory.bin` to SPIFI at `0x10000000` via `pyocd` +6. Optionally signs a v2 test-app and flashes it to the update partition + to exercise the swap-and-confirm update flow + +It drives [pyocd](https://pyocd.io/) with CMSIS-DAP firmware on the on-board +Link2 probe. Override `CONFIG_FILE`, `PYOCD_TARGET`, or `CROSS_COMPILE` via +environment variables to adapt the script to other LPC540xx/LPC54S0xx +boards. Run with `--help` for the full option list. ```sh # Build and flash v1 only diff --git a/hal/nxp_lpc54s018m.c b/hal/nxp_lpc54s018m.c index 2c8505c8dd..c17eec3fdd 100644 --- a/hal/nxp_lpc54s018m.c +++ b/hal/nxp_lpc54s018m.c @@ -18,12 +18,18 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA * - * NXP LPC54S018M HAL for wolfBoot + * NXP LPC540xx / LPC54S0xx (SPIFI-boot) HAL for wolfBoot * - * The LPC54S018M has no internal NOR flash. All code executes from - * on-package SPIFI QSPI flash (W25Q32JV, 4MB) mapped at 0x10000000. - * Flash operations MUST run from RAM since XIP is disabled during - * erase/write. + * Covers the LPC540xx and LPC54S0xx subseries (LPC54005/54016/54018, + * LPC54S005/54S016/54S018, and the in-package flash "M" variants + * LPC54018M / LPC54S018M). None of these parts have internal NOR flash — + * all code executes from external QSPI flash mapped via SPIFI at + * address 0x10000000. Flash operations MUST run from RAM since XIP is + * disabled during erase/write. + * + * Verified on the LPC54S018M-EVK (Winbond W25Q32JV, 4 MB). Other family + * members should work after adjusting the SPIFI device configuration words + * and sector/partition sizes to match the attached QSPI part. * * This HAL uses bare-metal register access — no NXP SDK dependencies. */ diff --git a/tools/scripts/nxp-lpc54s018m-flash.sh b/tools/scripts/nxp-lpc54s018m-flash.sh index 6e54e83a36..ff361c4345 100755 --- a/tools/scripts/nxp-lpc54s018m-flash.sh +++ b/tools/scripts/nxp-lpc54s018m-flash.sh @@ -2,13 +2,31 @@ # # NXP LPC54S018M-EVK Flash Script # -# This script automates: -# 1. Configure for NXP LPC54S018M-EVK -# 2. Build factory.bin (or factory + v2 update) -# 3. Flash via pyocd (CMSIS-DAP) +# End-to-end helper to build wolfBoot, sign the test application, and +# program the LPC54S018M-EVK SPIFI flash via pyocd. Also optionally +# exercises the A/B update flow by signing a v2 image and loading it +# into the update partition so wolfBoot will swap on the next boot. # -# The LPC-Link2 debug probe must have CMSIS-DAP firmware. -# See docs/Targets.md for Link2 probe setup instructions. +# Flow: +# 1. Copy config/examples/nxp_lpc54s018m.config to .config +# 2. make -> factory.bin (wolfBoot + signed v1 test-app) +# 3. Parse .config to derive partition/trailer addresses +# 4. Erase BOOT and UPDATE partition trailer sectors (clean boot state) +# 5. pyocd flash factory.bin @ 0x10000000 (SPIFI base) +# 6. With --test-update: sign v2, flash at WOLFBOOT_PARTITION_UPDATE_ADDRESS +# +# Requirements: +# - pyocd + LPC54S018J4MET180 target pack +# - arm-none-eabi-gcc toolchain +# - LPC-Link2 probe running CMSIS-DAP firmware (see docs/Targets.md: +# "LPC54S018M: Link2 debug probe setup") +# +# Customization (LPC540xx / LPC54S0xx family): +# Override CONFIG_FILE, PYOCD_TARGET, or CROSS_COMPILE via environment to +# reuse this script for other LPC540xx / LPC54S0xx boards. +# +# See also: docs/Targets.md section +# "NXP LPC540xx / LPC54S0xx (SPIFI boot) -> LPC54S018M: Testing firmware update" # set -e From 821ca804dfab8a7a8f206125e2c45652bb6afe05 Mon Sep 17 00:00:00 2001 From: David Garske Date: Wed, 15 Apr 2026 10:57:02 -0700 Subject: [PATCH 7/8] Rename LPC54S018M to LPC54S0XX --- .github/workflows/test-configs.yml | 4 ++-- Makefile | 2 +- arch.mk | 2 +- ...xp_lpc54s018m.config => nxp_lpc54s0xx.config} | 4 ++-- docs/Targets.md | 16 ++++++++-------- hal/{nxp_lpc54s018m.c => nxp_lpc54s0xx.c} | 2 +- hal/{nxp_lpc54s018m.ld => nxp_lpc54s0xx.ld} | 0 ...RM-nxp_lpc54s018m.ld => ARM-nxp_lpc54s0xx.ld} | 0 test-app/Makefile | 4 ++-- ...{app_nxp_lpc54s018m.c => app_nxp_lpc54s0xx.c} | 2 +- ...pc54s018m-flash.sh => nxp-lpc54s0xx-flash.sh} | 6 +++--- 11 files changed, 21 insertions(+), 21 deletions(-) rename config/examples/{nxp_lpc54s018m.config => nxp_lpc54s0xx.config} (92%) rename hal/{nxp_lpc54s018m.c => nxp_lpc54s0xx.c} (99%) rename hal/{nxp_lpc54s018m.ld => nxp_lpc54s0xx.ld} (100%) rename test-app/{ARM-nxp_lpc54s018m.ld => ARM-nxp_lpc54s0xx.ld} (100%) rename test-app/{app_nxp_lpc54s018m.c => app_nxp_lpc54s0xx.c} (99%) rename tools/scripts/{nxp-lpc54s018m-flash.sh => nxp-lpc54s0xx-flash.sh} (98%) diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index b3189f5dec..4a8c1e2009 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -211,11 +211,11 @@ jobs: arch: ppc config-file: ./config/examples/nxp-t2080.config - nxp_lpc54s018m_test: + nxp_lpc54s0xx_test: uses: ./.github/workflows/test-build.yml with: arch: arm - config-file: ./config/examples/nxp_lpc54s018m.config + config-file: ./config/examples/nxp_lpc54s0xx.config nxp_ls1028a_test: uses: ./.github/workflows/test-build.yml diff --git a/Makefile b/Makefile index db4ed3d0f7..c34daf9f43 100644 --- a/Makefile +++ b/Makefile @@ -334,7 +334,7 @@ wolfboot.efi: wolfboot.elf wolfboot.bin: wolfboot.elf @echo "\t[BIN] $@" $(Q)$(OBJCOPY) $(OBJCOPY_FLAGS) -O binary $^ $@ -ifeq ($(TARGET),nxp_lpc54s018m) +ifeq ($(TARGET),nxp_lpc54s0xx) @echo "\t[LPC] enhanced boot block" $(Q)python3 -c "import struct,os;f=open('$@','r+b');sz=os.path.getsize('$@');f.seek(0x24);f.write(struct.pack('<2I',0xEDDC94BD,0x160));f.seek(0x160);f.write(struct.pack('<25I',0xFEEDA5A5,3,0x10000000,sz-4,0,0,0,0,0,0xEDDC94BD,0,0,0,0x001640EF,0,0,0x1301001D,0,0,0,0x00000100,0,0,0x04030050,0x14110D09));f.seek(0);d=f.read(28);w=struct.unpack('<7I',d);s=sum(w)&0xFFFFFFFF;ck=(0x100000000-s)&0xFFFFFFFF;f.seek(0x1C);f.write(struct.pack(' factory.bin (wolfBoot + signed v1 test-app) # 3. Parse .config to derive partition/trailer addresses # 4. Erase BOOT and UPDATE partition trailer sectors (clean boot state) @@ -32,7 +32,7 @@ set -e # Configuration (can be overridden via environment variables) -CONFIG_FILE="${CONFIG_FILE:-config/examples/nxp_lpc54s018m.config}" +CONFIG_FILE="${CONFIG_FILE:-config/examples/nxp_lpc54s0xx.config}" PYOCD_TARGET="${PYOCD_TARGET:-lpc54s018j4met180}" # Colors for output @@ -57,7 +57,7 @@ usage() { echo " -h, --help Show this help message" echo "" echo "Environment variables:" - echo " CONFIG_FILE Override config file (default: config/examples/nxp_lpc54s018m.config)" + echo " CONFIG_FILE Override config file (default: config/examples/nxp_lpc54s0xx.config)" echo " PYOCD_TARGET Override pyocd target (default: lpc54s018j4met180)" echo " CROSS_COMPILE Override toolchain prefix (default: arm-none-eabi-)" echo "" From 875199486a6920975b809aeca383c6e54223f8b5 Mon Sep 17 00:00:00 2001 From: David Garske Date: Wed, 15 Apr 2026 13:10:27 -0700 Subject: [PATCH 8/8] Peer review fixes --- Makefile | 2 +- config/examples/nxp_lpc54s0xx.config | 2 +- hal/nxp_lpc54s0xx.c | 3 + test-app/syscalls.c | 6 ++ tools/scripts/lpc54s0xx_patch_boot_block.py | 86 +++++++++++++++++++++ tools/scripts/nxp-lpc54s0xx-flash.sh | 14 +++- 6 files changed, 107 insertions(+), 6 deletions(-) create mode 100755 tools/scripts/lpc54s0xx_patch_boot_block.py diff --git a/Makefile b/Makefile index c34daf9f43..e81e05106f 100644 --- a/Makefile +++ b/Makefile @@ -336,7 +336,7 @@ wolfboot.bin: wolfboot.elf $(Q)$(OBJCOPY) $(OBJCOPY_FLAGS) -O binary $^ $@ ifeq ($(TARGET),nxp_lpc54s0xx) @echo "\t[LPC] enhanced boot block" - $(Q)python3 -c "import struct,os;f=open('$@','r+b');sz=os.path.getsize('$@');f.seek(0x24);f.write(struct.pack('<2I',0xEDDC94BD,0x160));f.seek(0x160);f.write(struct.pack('<25I',0xFEEDA5A5,3,0x10000000,sz-4,0,0,0,0,0,0xEDDC94BD,0,0,0,0x001640EF,0,0,0x1301001D,0,0,0,0x00000100,0,0,0x04030050,0x14110D09));f.seek(0);d=f.read(28);w=struct.unpack('<7I',d);s=sum(w)&0xFFFFFFFF;ck=(0x100000000-s)&0xFFFFFFFF;f.seek(0x1C);f.write(struct.pack(' +#include #include #include +/* Forward declaration of vsnprintf. We intentionally do not include + * because this file redefines stdout/stderr/fputs/fflush with bare-metal + * (void *) stubs that collide with the libc FILE-based prototypes. */ +extern int vsnprintf(char *str, size_t size, const char *fmt, va_list ap); + /* Provide our own errno for bare-metal. * Using the libc errno via can conflict with TLS-based errno * on cross-toolchains (e.g. powerpc-linux-gnu glibc). */ diff --git a/tools/scripts/lpc54s0xx_patch_boot_block.py b/tools/scripts/lpc54s0xx_patch_boot_block.py new file mode 100755 index 0000000000..ce0450552a --- /dev/null +++ b/tools/scripts/lpc54s0xx_patch_boot_block.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# lpc54s0xx_patch_boot_block.py +# +# Patch a wolfBoot binary for the NXP LPC540xx / LPC54S0xx SPIFI (XIP) boot +# ROM. The ROM expects an "enhanced boot block": +# - offset 0x1C: vector table checksum (negated sum of the first 7 words) +# - offset 0x24: boot block marker + offset to descriptor +# - offset 0x160: 25-word descriptor (magic, mode, image base, image size, ...) +# +# Usage: lpc54s0xx_patch_boot_block.py +# +# Copyright (C) 2025 wolfSSL Inc. +# This file is part of wolfBoot (GPL-2.0-or-later). + +import os +import struct +import sys + +HEADER_MARKER_OFFSET = 0x24 +BOOT_BLOCK_OFFSET = 0x160 +VECTOR_CHECKSUM_OFFSET = 0x1C +IMAGE_BASE_ADDR = 0x10000000 # SPIFI XIP base + +HEADER_MARKER_FMT = "<2I" # 0xEDDC94BD, 0x160 +BOOT_BLOCK_FMT = "<25I" +VECTOR_TABLE_FMT = "<7I" # first 7 words covered by checksum + + +def patch(path): + size = os.path.getsize(path) + header_marker_size = struct.calcsize(HEADER_MARKER_FMT) + boot_block_size = struct.calcsize(BOOT_BLOCK_FMT) + vector_table_size = struct.calcsize(VECTOR_TABLE_FMT) + + min_size = max( + vector_table_size, + HEADER_MARKER_OFFSET + header_marker_size, + BOOT_BLOCK_OFFSET + boot_block_size, + ) + if size < min_size: + raise SystemExit( + "error: %s is too small for LPC54S0xx boot block patching " + "(size=%d, need at least %d bytes)" % (path, size, min_size) + ) + + with open(path, "r+b") as f: + f.seek(HEADER_MARKER_OFFSET) + f.write(struct.pack(HEADER_MARKER_FMT, 0xEDDC94BD, BOOT_BLOCK_OFFSET)) + + f.seek(BOOT_BLOCK_OFFSET) + f.write(struct.pack( + BOOT_BLOCK_FMT, + 0xFEEDA5A5, # magic + 3, # image type + IMAGE_BASE_ADDR, # image base + size - 4, # image size (minus CRC slot) + 0, 0, 0, 0, 0, + 0xEDDC94BD, # header marker echo + 0, 0, 0, + 0x001640EF, # SPIFI config + 0, 0, + 0x1301001D, # clock/flash timing word + 0, 0, 0, + 0x00000100, # options + 0, 0, + 0x04030050, # PLL config + 0x14110D09, # clock divider config + )) + + f.seek(0) + words = struct.unpack(VECTOR_TABLE_FMT, f.read(vector_table_size)) + checksum = (0x100000000 - (sum(words) & 0xFFFFFFFF)) & 0xFFFFFFFF + f.seek(VECTOR_CHECKSUM_OFFSET) + f.write(struct.pack("" % argv[0]) + patch(argv[1]) + + +if __name__ == "__main__": + main(sys.argv) diff --git a/tools/scripts/nxp-lpc54s0xx-flash.sh b/tools/scripts/nxp-lpc54s0xx-flash.sh index b393686f0c..fbc69d34b4 100755 --- a/tools/scripts/nxp-lpc54s0xx-flash.sh +++ b/tools/scripts/nxp-lpc54s0xx-flash.sh @@ -30,6 +30,7 @@ # set -e +set -o pipefail # Configuration (can be overridden via environment variables) CONFIG_FILE="${CONFIG_FILE:-config/examples/nxp_lpc54s0xx.config}" @@ -110,10 +111,15 @@ parse_config() { exit 1 fi - # Helper function to extract config value + # Helper function to extract config value. + # Anchor the regex to `KEY=` or `KEY?=` so e.g. SIGN does not match SIGN_ALG. + # grep with --max-count=1 keeps the pipeline single-stage so pipefail catches + # a truly missing key (exit 1) rather than relying on `head` to mask it. get_config_value() { local key="$1" - grep -E "^${key}" "$config_file" | head -1 | sed -E "s/^${key}\??=//" | tr -d '[:space:]' + local line + line=$(grep -E "^${key}\\??=" "$config_file" --max-count=1) || return 0 + printf '%s' "${line#*=}" | tr -d '[:space:]' } # Extract SIGN and HASH @@ -146,9 +152,9 @@ parse_config() { # Ensure partition addresses have 0x prefix for bash arithmetic for var in WOLFBOOT_PARTITION_BOOT_ADDRESS WOLFBOOT_PARTITION_UPDATE_ADDRESS WOLFBOOT_PARTITION_SIZE WOLFBOOT_SECTOR_SIZE; do - eval "val=\$$var" + local val="${!var}" if [[ ! "$val" =~ ^0x ]]; then - eval "$var=\"0x\${val}\"" + printf -v "$var" '0x%s' "$val" fi done