diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 11159bf3c2..4a8c1e2009 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_lpc54s0xx_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + config-file: ./config/examples/nxp_lpc54s0xx.config + nxp_ls1028a_test: uses: ./.github/workflows/test-build.yml with: diff --git a/Makefile b/Makefile index 0285fbd333..e81e05106f 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_lpc54s0xx) + @echo "\t[LPC] enhanced boot block" + $(Q)python3 tools/scripts/lpc54s0xx_patch_boot_block.py $@ +endif @echo @echo "\t[SIZE]" $(Q)$(SIZE) wolfboot.elf diff --git a/arch.mk b/arch.mk index f56e3444f7..58957fc682 100644 --- a/arch.mk +++ b/arch.mk @@ -1189,6 +1189,12 @@ ifeq ($(TARGET),lpc) endif endif +ifeq ($(TARGET),nxp_lpc54s0xx) + ARCH_FLASH_OFFSET=0x10000000 + LDFLAGS+=-Wl,--no-warn-rwx-segments + # Bare-metal HAL — no NXP SDK dependencies +endif + ifeq ($(TARGET),lpc55s69) ifneq ($(TZEN),1) LSCRIPT_IN=hal/$(TARGET)-ns.ld diff --git a/config/examples/nxp_lpc54s0xx.config b/config/examples/nxp_lpc54s0xx.config new file mode 100644 index 0000000000..b0291b0d47 --- /dev/null +++ b/config/examples/nxp_lpc54s0xx.config @@ -0,0 +1,53 @@ +# 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_lpc54s0xx.c) — no NXP MCUXpresso SDK required +# +# Flash layout (SPIFI QSPI, 4 MB total — Winbond W25Q32JV): +# 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_lpc54s0xx + +# 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 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x10100000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x101F0000 diff --git a/docs/Targets.md b/docs/Targets.md index fbc02e6181..28ce154e84 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -20,7 +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 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) @@ -1896,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: @@ -1937,6 +1944,262 @@ arm-none-eabi-gdb wolfboot.elf -ex "target remote localhost:3333" ``` +## NXP LPC540xx / LPC54S0xx (SPIFI boot) + +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. + +The wolfBoot HAL (`hal/nxp_lpc54s0xx.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 +ensure this. + +### LPC54S018M: Link2 debug probe setup + +The LPC54S018M-EVK has an on-board LPC-Link2 debug probe (LPC4322). The probe +firmware determines the debug protocol: CMSIS-DAP or J-Link. J-Link firmware is +recommended for use with wolfBoot. + +**Jumper JP5** controls the Link2 boot mode: +- **Installed (normal):** Link2 runs from its internal flash (debug probe mode) +- **Removed (DFU):** Link2 enters DFU mode for firmware programming + +To program J-Link firmware onto the Link2: + +1. Remove JP5 and power cycle the board. The Link2 enters DFU mode + (USB `1fc9:000c`). + +2. Install [NXP LinkServer](https://www.nxp.com/design/design-center/software/development-software/mcuxpresso-software-and-tools-/linkserver-for-microcontrollers:LINKERSERVER) + which includes LPCScrypt. + +3. Boot LPCScrypt onto the Link2 (requires sudo or udev rules): + +```sh +sudo /usr/local/LinkServer/lpcscrypt/scripts/boot_lpcscrypt +``` + +4. Identify the LPCScrypt serial port and program J-Link firmware: + +```sh +# Find the new ttyACM device created after boot_lpcscrypt +ls -lt /dev/ttyACM* + +# Program J-Link firmware (replace /dev/ttyACMx with the correct port) +sudo /usr/local/LinkServer/lpcscrypt/bin/lpcscrypt -d /dev/ttyACMx \ + program /usr/local/LinkServer/lpcscrypt/probe_firmware/LPCLink2/Firmware_JLink_LPC-Link2_20230502.bin BankA +``` + +5. Re-install JP5 and power cycle the board. The Link2 should now enumerate + as a Segger J-Link USB device. + +**Note:** If `uart-monitor` or another tool has the serial port open, you must +release it first (e.g., `uart-monitor yield /dev/ttyACMx`) before running +lpcscrypt. + +To program CMSIS-DAP firmware instead (for use with pyocd/OpenOCD): + +```sh +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: Toolchain + +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 +pip install pyocd +pyocd pack install LPC54S018J4MET180 +``` + +### LPC54S018M: Flash partition layout + +The 4MB SPIFI flash is partitioned as follows: + +| Region | Address | Size | +|--------------|-------------|--------| +| wolfBoot | 0x10000000 | 64KB | +| Boot (app) | 0x10010000 | 960KB | +| Update | 0x10100000 | 960KB | +| Swap sector | 0x101F0000 | 4KB | + +The sector size is 4KB, matching the W25Q32JV minimum erase size. + +### LPC54S018M: Configuring and compiling + +Copy the example configuration file and build with make: + +```sh +cp config/examples/nxp_lpc54s0xx.config .config +make +``` + +This produces `factory.bin` containing wolfBoot + the signed test application. + +### LPC54S018M: Loading the firmware + +The on-board Link2 debugger supports both CMSIS-DAP and J-Link protocols. +See [Link2 debug probe setup](#lpc54s018m-link2-debug-probe-setup) for +programming the probe firmware. + +**Using JLink** (Link2 with J-Link firmware): + +``` +JLinkExe -device LPC54S018M -if SWD -speed 4000 +loadbin factory.bin 0x10000000 +r +g +``` + +**Using pyocd** (Link2 with CMSIS-DAP firmware): + +```sh +pyocd pack install LPC54S018J4MET180 +pyocd flash -t LPC54S018J4MET180 factory.bin --base-address 0x10000000 +pyocd reset -t LPC54S018J4MET180 +``` + +**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_lpc54s0xx`), 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 + +The helper script [`tools/scripts/nxp-lpc54s0xx-flash.sh`](../tools/scripts/nxp-lpc54s0xx-flash.sh) +automates the full **build → sign → flash** cycle for the LPC54S018M-EVK: + +1. Copies `config/examples/nxp_lpc54s0xx.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 +./tools/scripts/nxp-lpc54s0xx-flash.sh + +# Build, sign v2, and flash both (full update test) +./tools/scripts/nxp-lpc54s0xx-flash.sh --test-update + +# Flash existing images without rebuilding +./tools/scripts/nxp-lpc54s0xx-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) +./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 +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 (~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, active low): + +| LED | GPIO | Meaning | +|-----------|--------|----------------------------| +| USR_LED1 | P3.14 | Version 1 running | +| 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 + +``` +JLinkGDBServer -device LPC54S018M -if SWD -speed 4000 -port 3333 +``` + +Then, from another console: + +``` +arm-none-eabi-gdb wolfboot.elf -ex "target remote localhost:3333" +(gdb) add-symbol-file test-app/image.elf 0x10010100 +``` + +Note: The image.elf symbol offset is the boot partition address (0x10010000) plus +the wolfBoot image header size (0x100). + + ## NXP LPC55S69 The NXP LPC55S69 is a dual-core Cortex-M33 microcontroller. The support has been diff --git a/hal/nxp_lpc54s0xx.c b/hal/nxp_lpc54s0xx.c new file mode 100644 index 0000000000..f463424f4d --- /dev/null +++ b/hal/nxp_lpc54s0xx.c @@ -0,0 +1,546 @@ +/* nxp_lpc54s0xx.c + * + * 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 + * + * NXP LPC540xx / LPC54S0xx (SPIFI-boot) HAL for wolfBoot + * + * 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. + */ + +#include +#include +#include +#include "image.h" +#include "printf.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_IO 0xEB /* Quad I/O 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 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(3) | SPIFI_CMD_FIELDFORM(FIELDFORM_DATA_QUAD) | \ + SPIFI_CMD_FRAMEFORM(FRAMEFORM_OPCODE_3ADDR) | \ + SPIFI_CMD_OPCODE(W25Q_CMD_FAST_READ_QUAD_IO)) + +#ifdef NVM_FLASH_WRITEONCE +# 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_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: 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_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 */ + for (i = 0; i < 100; i++) + ; + + /* Select USART mode */ + FC0_PSELID = 1; + + /* Verify Flexcomm0 is accessible — if PSELID reads 0, peripheral is + * not responding. 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) */ +/* -------------------------------------------------------------------------- */ + +#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) + ; +} + +/* 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) +{ + /* 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 + wolfBoot_printf("wolfBoot HAL init\n"); +} + +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. + * 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 */ + 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) +{ + 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) +{ + spifi_set_cmd(CMD_WRITE_ENABLE); +} + +static void RAMFUNCTION spifi_wait_busy(void) +{ + /* 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 */ + + /* Callers (hal_flash_write / hal_flash_erase) always issue a non-MCMD + * command before reaching here, so MCINIT is clear and the reset path in + * spifi_set_cmd() does not run — IDATA/CLIMIT programmed above survive. */ + 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 */ +} + +/* + * 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_lpc54s0xx.ld b/hal/nxp_lpc54s0xx.ld new file mode 100644 index 0000000000..e1d7086edb --- /dev/null +++ b/hal/nxp_lpc54s0xx.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_lpc54s0xx.ld b/test-app/ARM-nxp_lpc54s0xx.ld new file mode 100644 index 0000000000..b6826d6bcd --- /dev/null +++ b/test-app/ARM-nxp_lpc54s0xx.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..0c40f55042 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -699,6 +699,19 @@ ifeq ($(TARGET),lpc55s69) LDFLAGS+=-Wl,--no-warn-rwx-segments endif +ifeq ($(TARGET),nxp_lpc54s0xx) + LSCRIPT_TEMPLATE=ARM-nxp_lpc54s0xx.ld + # Enable RAMFUNCTION for test app (flash ops must run from RAM) + CFLAGS+=-DRAM_CODE -D__WOLFBOOT + # string.o provides RAMFUNCTION memcpy; already added when DEBUG_UART=1 (line ~108) + ifeq ($(DEBUG_UART),) + APP_OBJS+=../src/string.o + endif + # Use shared newlib syscall stubs (routes _write to uart_write) + APP_OBJS+=syscalls.o + 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_lpc54s0xx.c b/test-app/app_nxp_lpc54s0xx.c new file mode 100644 index 0000000000..97ddd47782 --- /dev/null +++ b/test-app/app_nxp_lpc54s0xx.c @@ -0,0 +1,135 @@ +/* app_nxp_lpc54s0xx.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" +#include "printf.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); +} + +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; + + 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) +{ + 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 */ + 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) { + /* 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 { + /* v2+: LED2 on */ + led_on(LED2_PORT, LED2_PIN); + } + + wolfBoot_printf("App running\n"); + while (1) { + __asm__ volatile ("wfi"); + } +} diff --git a/test-app/syscalls.c b/test-app/syscalls.c index a167c3c8bb..214c1e67fb 100644 --- a/test-app/syscalls.c +++ b/test-app/syscalls.c @@ -22,9 +22,15 @@ */ #include +#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 new file mode 100755 index 0000000000..fbc69d34b4 --- /dev/null +++ b/tools/scripts/nxp-lpc54s0xx-flash.sh @@ -0,0 +1,290 @@ +#!/bin/bash +# +# NXP LPC54S018M-EVK Flash Script +# +# 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. +# +# Flow: +# 1. Copy config/examples/nxp_lpc54s0xx.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 +set -o pipefail + +# Configuration (can be overridden via environment variables) +CONFIG_FILE="${CONFIG_FILE:-config/examples/nxp_lpc54s0xx.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_lpc54s0xx.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. + # 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" + local line + line=$(grep -E "^${key}\\??=" "$config_file" --max-count=1) || return 0 + printf '%s' "${line#*=}" | 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 + local val="${!var}" + if [[ ! "$val" =~ ^0x ]]; then + printf -v "$var" '0x%s' "$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