diff --git a/.github/workflows/lpc55s69-emulator-tests.yml b/.github/workflows/lpc55s69-emulator-tests.yml new file mode 100644 index 0000000000..eff1d96879 --- /dev/null +++ b/.github/workflows/lpc55s69-emulator-tests.yml @@ -0,0 +1,81 @@ +name: lpc55s69-emulator-tests + +on: + push: + pull_request: + +jobs: + # Boot, update and rollback on the m33mu LPC55S69 model, plus a bootloader + # self-update on the plain config: the TrustZone wolfBoot does not fit an + # update partition, and its non-secure app updates through the NSC veneers. + lpc55s69-emulator-tests: + runs-on: ubuntu-latest + container: + image: ghcr.io/wolfssl/wolfboot-ci-m33mu:latest + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + include: + - config: lpc55s69 + scenarios: A,B,C,S + - config: lpc55s69-tz + scenarios: A,B,C + steps: + - uses: actions/checkout@v4 + + - name: Init submodules + run: | + git config --global --add safe.directory "$GITHUB_WORKSPACE" + git submodule update --init --single-branch + + - uses: actions/checkout@v4 + with: + repository: nxp-mcuxpresso/CMSIS_5 + path: CMSIS_5 + + - name: Cache MCUXpresso SDK + id: cache-mcuxpresso + uses: actions/cache@v4 + with: + path: | + mcuxpresso-sdk + venv + key: mcuxpresso-sdk-lpcxpresso55s69-emu-${{ hashFiles('.github/workflows/lpc55s69-emulator-tests.yml') }} + + - name: Setup MCUXpresso SDK + if: steps.cache-mcuxpresso.outputs.cache-hit != 'true' + run: | + python3 -m venv venv + . venv/bin/activate + pip install west + west init -m https://github.com/nxp-mcuxpresso/mcuxsdk-manifests.git mcuxpresso-sdk + cd mcuxpresso-sdk + sed -i '/se_hostlib\|emwin/d' manifests/boards/lpcxpresso55s69.yml + west update_board --set board lpcxpresso55s69 + + - name: Use the MCUXpresso SDK for the wolfBoot build + run: | + echo "MCUXSDK=1" >> "$GITHUB_ENV" + echo "MCUXPRESSO=$GITHUB_WORKSPACE/mcuxpresso-sdk/mcuxsdk" >> "$GITHUB_ENV" + echo "MCUXPRESSO_CMSIS=$GITHUB_WORKSPACE/CMSIS_5/CMSIS" >> "$GITHUB_ENV" + + - name: Configure ${{ matrix.config }} and build wolfboot + run: | + cp config/examples/${{ matrix.config }}.config .config + make include/target.h + make -C tools/keytools + make -C tools/bin-assemble + make wolfboot.bin + + - name: Run emu test (${{ matrix.config }}) + working-directory: test-app/emu-test-apps + run: | + SCENARIOS=${{ matrix.scenarios }} BOOT_TIMEOUT=30 UPDATE_TIMEOUT=60 REBOOT_TIMEOUT=30 ./test.sh + + - name: Upload emulator logs on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: lpc55s69-emu-logs-${{ matrix.config }} + path: test-app/emu-test-apps/lpc55s69/*.log diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index d1cad5f507..4a21eaf6d1 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -185,6 +185,14 @@ jobs: config-file: ./config/examples/lpc55s69-tz-psa.config board-name: lpcxpresso55s69 + lpc55s69_hwpuf_test: + uses: ./.github/workflows/test-build-mcux-sdk-manifests.yml + with: + arch: arm + config-file: ./config/examples/lpc55s69.config + board-name: lpcxpresso55s69 + make-args: HWPUF=1 + lpc55s69_benchmark_test: uses: ./.github/workflows/test-build-mcux-sdk-manifests.yml with: @@ -192,6 +200,48 @@ jobs: config-file: ./config/examples/lpc55s69-benchmark.config board-name: lpcxpresso55s69 + lpc55s69_no_uart_test: + uses: ./.github/workflows/test-build-mcux-sdk-manifests.yml + with: + arch: arm + config-file: ./config/examples/lpc55s69.config + board-name: lpcxpresso55s69 + make-args: DEBUG_UART=0 + + lpc55s69_boot_benchmark_test: + uses: ./.github/workflows/test-build-mcux-sdk-manifests.yml + with: + arch: arm + config-file: ./config/examples/lpc55s69.config + board-name: lpcxpresso55s69 + make-args: BOOT_BENCHMARK=1 + + lpc55s69_pka_test: + uses: ./.github/workflows/test-build-mcux-sdk-manifests.yml + with: + arch: arm + config-file: ./config/examples/lpc55s69.config + board-name: lpcxpresso55s69 + make-args: PKA=1 + + # TrustZone without the wolfCrypt NSC API + lpc55s69_tz_no_wolfcrypt_test: + uses: ./.github/workflows/test-build-mcux-sdk-manifests.yml + with: + arch: arm + config-file: ./config/examples/lpc55s69-tz.config + board-name: lpcxpresso55s69 + make-args: "WOLFCRYPT_TZ=0 WOLFCRYPT_TZ_PKCS11=0" + + # Without the console the secure RNG still needs the reset driver linked in + lpc55s69_tz_no_uart_test: + uses: ./.github/workflows/test-build-mcux-sdk-manifests.yml + with: + arch: arm + config-file: ./config/examples/lpc55s69-tz.config + board-name: lpcxpresso55s69 + make-args: DEBUG_UART=0 + m2354_test: uses: ./.github/workflows/test-build.yml with: diff --git a/.github/workflows/trustzone-emulator-tests.yml b/.github/workflows/trustzone-emulator-tests.yml index fc22b2ebb7..9c42ae03f7 100644 --- a/.github/workflows/trustzone-emulator-tests.yml +++ b/.github/workflows/trustzone-emulator-tests.yml @@ -169,10 +169,10 @@ jobs: m33mu tools/keytools/otp/otp-keystore-primer.bin --persist --timeout 10 || true m33mu wolfboot.bin test-app/image_v1_signed.bin:0x60000 --uart-stdout --expect-bkpt 0x7f --timeout 600 --persist - # LPC55S69 DICE attestation + HW PUF. The PUF root key is provisioned - # at first boot (WOLFBOOT_HWPUF_PROVISION=1) and used for DICE key - # derivation on subsequent boots. Requires m33mu with lpc55s69 PUF/RNG - # models (wolfboot-ci >= 1.22). + # LPC55S69 DICE attestation + HW PUF. The first boot enrolls the PUF + # and stores the record (WOLFBOOT_HWPUF_PROVISION=1); the second boot + # must reuse it and report the same UEID. Needs the m33mu lpc55s69 + # PUF/RNG models and --persist (m33mu c2d903e, wolfboot-ci >= 1.28). # The lpc55s69 HAL needs MCUXpresso SDK headers (fsl_common.h); same # west-based setup as the lpc55s69 jobs in test-configs.yml. - name: Checkout CMSIS (lpc55s69) @@ -192,8 +192,9 @@ jobs: sed -i '/se_hostlib\|emwin/d' manifests/boards/lpcxpresso55s69.yml west update_board --set board lpcxpresso55s69 - - name: Clean and build test with DICE attestation + HW PUF (lpc55s69) + - name: Build and run DICE attestation + HW PUF across two boots (lpc55s69) run: | + set -o pipefail make clean distclean cp config/examples/lpc55s69-tz-psa.config .config sed -i 's/^HWPUF?=0/HWPUF?=1/' .config @@ -203,19 +204,42 @@ jobs: grep -q '^WOLFBOOT_HWPUF_PROVISION?=1' .config grep -q '^WOLFBOOT_UDS_UID_FALLBACK_FORTEST?=0' .config make MCUXSDK=1 MCUXPRESSO="$GITHUB_WORKSPACE/mcuxpresso-sdk/mcuxsdk" MCUXPRESSO_CMSIS="$GITHUB_WORKSPACE/CMSIS_5/CMSIS" - - # Single boot: enroll the PUF, store the record, attest. - # TODO: add a second --persist boot checking the stored record is reused - # (same UEID) once the CI image ships an m33mu with lpc55s69 --persist - # support (m33mu c2d903e, after v1.23). - - name: Run DICE attestation + HW PUF (lpc55s69) + BOOT_ADDR=$(sed -n 's/^WOLFBOOT_PARTITION_BOOT_ADDRESS?*=//p' .config) + rm -rf /tmp/m33mu-lpc55s69-hwpuf-persist + mkdir -p /tmp/m33mu-lpc55s69-hwpuf-persist + cp wolfboot.bin test-app/image_v1_signed.bin /tmp/m33mu-lpc55s69-hwpuf-persist/ + cd /tmp/m33mu-lpc55s69-hwpuf-persist + for boot in first second; do + m33mu wolfboot.bin image_v1_signed.bin:$BOOT_ADDR --cpu lpc55s69 --persist --uart-stdout --expect-bkpt 0x7f --timeout 300 \ + | tee /tmp/m33mu-lpc55s69-dice-hwpuf-$boot.log + grep -q "Booting version:" /tmp/m33mu-lpc55s69-dice-hwpuf-$boot.log + grep -q "Checking integrity...done" /tmp/m33mu-lpc55s69-dice-hwpuf-$boot.log + grep -q "Verifying signature...done" /tmp/m33mu-lpc55s69-dice-hwpuf-$boot.log + grep -q "attest: token size" /tmp/m33mu-lpc55s69-dice-hwpuf-$boot.log + grep -q "state=0x00" /tmp/m33mu-lpc55s69-dice-hwpuf-$boot.log + grep -q "\\[EXPECT BKPT\\] Success" /tmp/m33mu-lpc55s69-dice-hwpuf-$boot.log + done + # The second boot starts confirmed, so it attested from the stored record + grep -q "boot: ver=0x1 state=0x00" /tmp/m33mu-lpc55s69-dice-hwpuf-second.log + first=$(python3 $GITHUB_WORKSPACE/tools/scripts/eat_ueid.py /tmp/m33mu-lpc55s69-dice-hwpuf-first.log) + second=$(python3 $GITHUB_WORKSPACE/tools/scripts/eat_ueid.py /tmp/m33mu-lpc55s69-dice-hwpuf-second.log) + echo "UEID first boot: $first" + echo "UEID second boot: $second" + test -n "$first" + test "$first" = "$second" + + - name: Build and run persistent PKCS11 test (lpc55s69) run: | set -o pipefail - m33mu wolfboot.bin test-app/image_v1_signed.bin:0x48000 --cpu lpc55s69 --uart-stdout --expect-bkpt 0x7f --timeout 300 \ - | tee /tmp/m33mu-lpc55s69-dice-hwpuf.log - grep -q "Booting version:" /tmp/m33mu-lpc55s69-dice-hwpuf.log - grep -q "Checking integrity...done" /tmp/m33mu-lpc55s69-dice-hwpuf.log - grep -q "Verifying signature...done" /tmp/m33mu-lpc55s69-dice-hwpuf.log - grep -q "attest: token size" /tmp/m33mu-lpc55s69-dice-hwpuf.log - grep -q "state=0x00" /tmp/m33mu-lpc55s69-dice-hwpuf.log - grep -q "\\[EXPECT BKPT\\] Success" /tmp/m33mu-lpc55s69-dice-hwpuf.log + make clean distclean + cp config/examples/lpc55s69-tz.config .config + make MCUXSDK=1 MCUXPRESSO="$GITHUB_WORKSPACE/mcuxpresso-sdk/mcuxsdk" MCUXPRESSO_CMSIS="$GITHUB_WORKSPACE/CMSIS_5/CMSIS" PKCS11_TESTAPP=1 + BOOT_ADDR=$(sed -n 's/^WOLFBOOT_PARTITION_BOOT_ADDRESS?*=//p' .config) + m33mu wolfboot.bin test-app/image_v1_signed.bin:$BOOT_ADDR --cpu lpc55s69 --persist --uart-stdout --timeout 300 --expect-bkpt 0x7d \ + | tee /tmp/m33mu-lpc55s69-pkcs11-first.log + grep -q "pkcs11: created persistent PKCS11 objects" /tmp/m33mu-lpc55s69-pkcs11-first.log + grep -q "pkcs11: success" /tmp/m33mu-lpc55s69-pkcs11-first.log + m33mu wolfboot.bin test-app/image_v1_signed.bin:$BOOT_ADDR --cpu lpc55s69 --persist --uart-stdout --timeout 300 --expect-bkpt 0x7f \ + | tee /tmp/m33mu-lpc55s69-pkcs11-second.log + grep -q "pkcs11: restored persistent PKCS11 objects" /tmp/m33mu-lpc55s69-pkcs11-second.log + grep -q "pkcs11: success" /tmp/m33mu-lpc55s69-pkcs11-second.log diff --git a/.gitignore b/.gitignore index a5f8e688e3..b66ea345a6 100644 --- a/.gitignore +++ b/.gitignore @@ -252,6 +252,10 @@ tools/unit-tests/unit-flash-write-mcxa tools/unit-tests/unit-rw612-flash tools/unit-tests/unit-rw612-tz tools/unit-tests/unit-rw612-uart +tools/unit-tests/unit-lpc55s69-flash +tools/unit-tests/unit-lpc55s69-tz +tools/unit-tests/unit-lpc55s69-uart +tools/unit-tests/unit-lpc55s69-puf tools/unit-tests/unit-flash-write-nrf52 tools/unit-tests/unit-flash-write-same51 tools/unit-tests/unit-flash-write-samr21 diff --git a/arch.mk b/arch.mk index ba161f83eb..10fc2c8790 100644 --- a/arch.mk +++ b/arch.mk @@ -2161,7 +2161,7 @@ ifeq ($(TARGET),lpc55s69) CFLAGS+=-DCPU_$(MCUXPRESSO_CPU) CFLAGS+=-DFSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL=1 CFLAGS+=-DFSL_SDK_DISABLE_DRIVER_RESET_CONTROL=1 - CFLAGS+=-mcpu=cortex-m33 -DCORTEX_M33 -U__ARM_FEATURE_DSP + CFLAGS+=-mcpu=cortex-m33 -DCORTEX_M33 LDFLAGS+=-mcpu=cortex-m33 -Wl,--no-warn-rwx-segments OBJS+=\ $(MCUXPRESSO_PROJECT_TEMPLATE)/clock_config.o \ diff --git a/config/examples/lpc55s69-benchmark.config b/config/examples/lpc55s69-benchmark.config index c22fddd540..522a689821 100644 --- a/config/examples/lpc55s69-benchmark.config +++ b/config/examples/lpc55s69-benchmark.config @@ -3,6 +3,7 @@ TZEN?=0 TARGET?=lpc55s69 SIGN?=ECC384 HASH?=SHA256 +IMAGE_HEADER_SIZE?=512 MCUXSDK?=1 MCUXPRESSO?=$(PWD)/../NXP/mcuxpresso-sdk/mcuxsdk MCUXPRESSO_CMSIS?=$(PWD)/../NXP/CMSIS_5/CMSIS @@ -20,7 +21,8 @@ EXT_FLASH?=0 SPI_FLASH?=0 ALLOW_DOWNGRADE?=0 NVM_FLASH_WRITEONCE?=1 -NO_ARM_ASM=1 +# The Thumb-2 SHA assembly is slower than C on this part; the SP math uses DSP +NO_ARM_ASM?=1 WOLFBOOT_VERSION?=0 V?=0 SPMATH?=1 diff --git a/config/examples/lpc55s69-tz-psa.config b/config/examples/lpc55s69-tz-psa.config index eb3a9e5bbf..ca1887e506 100644 --- a/config/examples/lpc55s69-tz-psa.config +++ b/config/examples/lpc55s69-tz-psa.config @@ -3,6 +3,7 @@ TZEN?=1 TARGET?=lpc55s69 SIGN?=ECC384 HASH?=SHA256 +IMAGE_HEADER_SIZE?=512 MCUXSDK?=1 MCUXPRESSO?=$(PWD)/../NXP/mcuxpresso-sdk/mcuxsdk MCUXPRESSO_CMSIS?=$(PWD)/../NXP/CMSIS_5/CMSIS @@ -20,7 +21,8 @@ EXT_FLASH?=0 SPI_FLASH?=0 ALLOW_DOWNGRADE?=0 NVM_FLASH_WRITEONCE?=1 -NO_ARM_ASM=1 +# The Thumb-2 SHA assembly is slower than C on this part; the SP math uses DSP +NO_ARM_ASM?=1 WOLFBOOT_VERSION?=0 V?=0 SPMATH?=1 @@ -41,12 +43,13 @@ WOLFBOOT_HWPUF_PROVISION?=0 WOLFBOOT_SECTOR_SIZE?=0x200 -# 200KB boot, 80KB keyvault, 8KB NSC, 56KB partitions, 512 swap -WOLFBOOT_KEYVAULT_ADDRESS?=0x10032000 -WOLFBOOT_KEYVAULT_SIZE?=0x14000 -WOLFBOOT_NSC_ADDRESS?=0x10046000 +# 248KB wolfBoot, 8KB NSC, 128KB partitions on the 32KB AHB secure rule grid, +# 0x80000-0x80FFF free for the HWPUF record, 512B swap, 88KB keyvault +WOLFBOOT_NSC_ADDRESS?=0x1003E000 WOLFBOOT_NSC_SIZE?=0x2000 -WOLFBOOT_PARTITION_SIZE?=0xE000 -WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x00048000 -WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x00056000 -WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x00064000 +WOLFBOOT_PARTITION_SIZE?=0x20000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x00040000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x00060000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x00081000 +WOLFBOOT_KEYVAULT_ADDRESS?=0x10082000 +WOLFBOOT_KEYVAULT_SIZE?=0x16000 diff --git a/config/examples/lpc55s69-tz.config b/config/examples/lpc55s69-tz.config index 6214a6f942..32eef5f997 100644 --- a/config/examples/lpc55s69-tz.config +++ b/config/examples/lpc55s69-tz.config @@ -3,6 +3,7 @@ TZEN?=1 TARGET?=lpc55s69 SIGN?=ECC384 HASH?=SHA256 +IMAGE_HEADER_SIZE?=512 MCUXSDK?=1 MCUXPRESSO?=$(PWD)/../NXP/mcuxpresso-sdk/mcuxsdk MCUXPRESSO_CMSIS?=$(PWD)/../NXP/CMSIS_5/CMSIS @@ -20,7 +21,8 @@ EXT_FLASH?=0 SPI_FLASH?=0 ALLOW_DOWNGRADE?=0 NVM_FLASH_WRITEONCE?=1 -NO_ARM_ASM=1 +# The Thumb-2 SHA assembly is slower than C on this part; the SP math uses DSP +NO_ARM_ASM?=1 WOLFBOOT_VERSION?=0 V?=0 SPMATH?=1 @@ -37,12 +39,13 @@ HWPUF?=0 WOLFBOOT_SECTOR_SIZE?=0x200 -# 200KB boot, 80KB keyvault, 8KB NSC, 56KB partitions, 512 swap -WOLFBOOT_KEYVAULT_ADDRESS?=0x10032000 -WOLFBOOT_KEYVAULT_SIZE?=0x14000 -WOLFBOOT_NSC_ADDRESS?=0x10046000 +# 248KB wolfBoot, 8KB NSC, 128KB partitions on the 32KB AHB secure rule grid, +# 0x80000-0x80FFF free for the HWPUF record, 512B swap, 88KB keyvault +WOLFBOOT_NSC_ADDRESS?=0x1003E000 WOLFBOOT_NSC_SIZE?=0x2000 -WOLFBOOT_PARTITION_SIZE?=0xE000 -WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x00048000 -WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x00056000 -WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x00064000 +WOLFBOOT_PARTITION_SIZE?=0x20000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x00040000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x00060000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x00081000 +WOLFBOOT_KEYVAULT_ADDRESS?=0x10082000 +WOLFBOOT_KEYVAULT_SIZE?=0x16000 diff --git a/config/examples/lpc55s69.config b/config/examples/lpc55s69.config index 9556b039b7..643b655c98 100644 --- a/config/examples/lpc55s69.config +++ b/config/examples/lpc55s69.config @@ -3,6 +3,7 @@ TZEN?=0 TARGET?=lpc55s69 SIGN?=ECC384 HASH?=SHA256 +IMAGE_HEADER_SIZE?=512 MCUXSDK?=1 MCUXPRESSO?=$(PWD)/../NXP/mcuxpresso-sdk/mcuxsdk MCUXPRESSO_CMSIS?=$(PWD)/../NXP/CMSIS_5/CMSIS @@ -20,7 +21,8 @@ EXT_FLASH?=0 SPI_FLASH?=0 ALLOW_DOWNGRADE?=0 NVM_FLASH_WRITEONCE?=1 -NO_ARM_ASM=1 +# The Thumb-2 SHA assembly is slower than C on this part; the SP math uses DSP +NO_ARM_ASM?=1 WOLFBOOT_VERSION?=0 V?=0 SPMATH?=1 @@ -35,9 +37,9 @@ HWPUF?=0 WOLFBOOT_SECTOR_SIZE?=0x200 -# Default configuration -# 44KB boot, 52KB partitions, 512 swap -WOLFBOOT_PARTITION_SIZE?=0xB000 -WOLFBOOT_PARTITION_BOOT_ADDRESS?=0xD000 -WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x18000 -WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x23000 +# 64KB wolfBoot, 192KB partitions, 512B swap; 0x80000-0x80FFF stays free +# for the HWPUF provisioning record +WOLFBOOT_PARTITION_SIZE?=0x30000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x10000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x40000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x70000 diff --git a/docs/Targets.md b/docs/Targets.md index 059a6af9c2..c5f402cff0 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -3389,9 +3389,21 @@ the wolfBoot image header size (0x100). ## NXP LPC55S69 -The NXP LPC55S69 is a dual-core Cortex-M33 microcontroller. The support has been -tested on the LPCXpresso55S69 board (LPC55S69-EVK), with the on-board LINK2 configured in -the default CMSIS-DAP mode. +The NXP LPC55S69 is a dual-core Cortex-M33 microcontroller with TrustZone, 640KB +of internal flash (630KB usable below the protected flash region) and 320KB of +SRAM. wolfBoot runs on core 0, which has the DSP and FPU extensions: the +public-key math uses the Cortex-M assembly with the DSP instructions +(`NO_ASM=0`), while the hashes stay in C (`NO_ARM_ASM=1`), which measured faster +than the Thumb-2 hash assembly on this part. The support has been tested on the +LPCXpresso55S69 board (LPC55S69-EVK), with the on-board LPC-Link2 in its default +CMSIS-DAP mode. + +The flash is programmed in 512-byte pages with ECC: a page that is erased but +never programmed faults on read. The HAL therefore programs 0xFF over erased +pages that wolfBoot reads (the bootloader region, the partitions and the +keyvault) at boot, and `hal_flash_erase()` programs 0xFF after each erase. +wolfBoot runs the core at 96MHz, since the ROM flash API refuses to program or +erase above 100MHz. This requires the NXP MCUXpresso SDK. We tested using [mcuxsdk-manifests](https://github.com/nxp-mcuxpresso/mcuxsdk-manifests) and @@ -3413,6 +3425,9 @@ cd mcuxpresso-sdk west update_board --set board lpcxpresso55s69 deactivate + +cd .. +git clone https://github.com/nxp-mcuxpresso/CMSIS_5.git ``` ### LPC55S69: Hardware Acceleration @@ -3421,6 +3436,8 @@ wolfBoot / wolfCrypt provide support for the LPC55S69's hardware acceleration blocks, to improve performance of various crypto algorithms. By default, this support is turned off in the config files, and all crypto is software based. To turn on hardware acceleration, set PKA=1 in the config file. +Hardware acceleration is not available with TrustZone (`TZEN=1`): the AHB +secure controller only lets the HASHCRYPT bus master read non-secure memory. Basic hardware acceleration supported: - SHA1 @@ -3442,12 +3459,17 @@ generation & storage, without the need to expose keys during manufacturing. Keys of size 128, 192, and 256 bits are supported. To turn on HWPUF, set HWPUF=1 in the config file (see below). +The PUF provisioning record is stored at flash address `0x80000` (4KB). Every +example layout keeps that area free, and erasing the whole flash removes the +record, so the next boot provisions a new device identity. + ### LPC55S69: Example Configurations #### The following example configurations are provided: - [lpc55s69.config](/config/examples/lpc55s69.config): - - Both wolfBoot and the test-app live in the non-secure realm + - TrustZone is not used: wolfBoot and the test-app both run in the secure + state - Set PKA=1 in the config file to turn on hardware acceleration (off by default) @@ -3459,7 +3481,11 @@ To turn on HWPUF, set HWPUF=1 in the config file (see below). - [lpc55s69-tz.config](/config/examples/lpc55s69-tz.config): - wolfBoot lives in the secure realm, test-app lives in the non-secure realm - Provides a standard PKCS #11 api to interface with crypto algs in the secure - realm + realm. To build the TrustZone split without the wolfCrypt secure API, use + `make WOLFCRYPT_TZ=0 WOLFCRYPT_TZ_PKCS11=0` + - `make PKCS11_TESTAPP=1` builds a test-app that checks keyvault persistence: + the first boot provisions a token and creates a key pair, the next boot + restores them and verifies a signature - [lpc55s69-tz-psa.config](/config/examples/lpc55s69-tz-psa.config): - wolfBoot lives in the secure realm, test-app lives in the non-secure realm @@ -3471,6 +3497,9 @@ To turn on HWPUF, set HWPUF=1 in the config file (see below). - WOLFBOOT_UDS_UID_FALLBACK_FORTEST=0 - HWPUF=1 - WOLFBOOT_HWPUF_PROVISION=1 + - The emulator CI boots this configuration twice with `--persist` and checks + that the second boot reuses the stored PUF record (same UEID in the token); + `tools/unit-tests/unit-lpc55s69-puf.c` covers the record states #### Summary of configurables applicable to all lpc55s69 example configurations: - `PKA` : Turn on/off hardware acceleration of crypto algs @@ -3479,10 +3508,13 @@ To turn on HWPUF, set HWPUF=1 in the config file (see below). first boot. Performs PUF enrollment and generates a UDS key for device attestation. Stores PUF keycodes in a known flash location and uses them when necessary. +- `BOOT_BENCHMARK=1` : print the time wolfBoot spends on the integrity and +signature checks (SysTick on the 1MHz FRO) +- `DEBUG_UART=0` : build wolfBoot without the console - `WOLFBOOT_HWPUF_PROV_ADDRESS` : Flash address of the 4 KB PUF provisioning record (default `0x80000`, page aligned). The build fails if it overlaps the -boot, update or swap partition or the protected flash region (`0x9DE00` and -up), or if it lies below the boot partition (wolfBoot, keyvault and NSC). +boot, update or swap partition, the keyvault or the protected flash region +(`0x9DE00` and up), or if it lies below the boot partition (wolfBoot and NSC). The activation code and key code are written first and verified, then a commit word is written in the last page of the record. Provisioning only runs when that commit page is erased (or all `0xFF`), so a provisioning @@ -3496,6 +3528,47 @@ programmed. `TZEN=1` the PUF is reachable only from the secure world (it is excluded from the non-secure SAU regions and is not linked into the non-secure test-app). +#### Flash layout + +`lpc55s69.config` and `lpc55s69-benchmark.config`: + +| Region | Address | Size | +| --- | --- | --- | +| wolfBoot | `0x00000` | 64KB | +| Boot partition | `0x10000` | 192KB | +| Update partition | `0x40000` | 192KB | +| Swap sector | `0x70000` | 512B | +| HWPUF provisioning record | `0x80000` | 4KB | + +`lpc55s69-tz.config` and `lpc55s69-tz-psa.config`: + +| Region | Address | Size | +| --- | --- | --- | +| wolfBoot (secure) | `0x00000` | 248KB | +| Non-secure callable veneers | `0x3E000` | 8KB | +| Boot partition (non-secure) | `0x40000` | 128KB | +| Update partition | `0x60000` | 128KB | +| HWPUF provisioning record | `0x80000` | 4KB | +| Swap sector | `0x81000` | 512B | +| Keyvault | `0x82000` | 88KB | + +#### TrustZone + +With `TZEN=1`, the non-secure application can reach only the boot partition, +SRAM2 (`0x20020000`-`0x2002FFFF`), the FLEXCOMM0 console and the GPIO block. +wolfBoot muxes the LED pins for the application before it boots, so the pin +mux (IOCON) stays secure, as do clocks and resets (SYSCON), the flash +controller, DMA, the crypto blocks, the RNG and PUF, the update and swap +partitions, the keyvault and the rest of the SRAM. Only the FLEXCOMM0 interrupt +targets the non-secure state. + +The same split is applied on the bus by the AHB secure controller, so DMA and +the second core cannot reach secure memory either; the boot partition is +therefore aligned to the controller's 32KB flash rules. Secure checking is +enabled and both the controller and the SAU are write-locked until the next +reset. To give the application another peripheral, add it to `hal_sau_init()` +and `hal_ahbsc_init()` in [hal/lpc55s69.c](/hal/lpc55s69.c). + ### LPC55S69: Configuring and compiling Copy the example configuration file and build with make: @@ -3505,44 +3578,49 @@ cp config/examples/lpc55s69.config .config make ``` -### LPC55S69: Loading the firmware - -Download and install the LinkServer tool: -[@NXP: LinkServer for microcontrollers](https://www.nxp.com/design/design-center/software/development-software/mcuxpresso-software-and-tools-/linkserver-for-microcontrollers:LINKERSERVER#downloads) - -NOTE: The LPCXpresso55S69's on-board LINK2 debugger comes loaded with CMSIS-DAP protocol, but it can be -optionally updated to use JLink protocol instead. See the EVK user manual for how to do this, if desired. -The below examples were tested with the default CMSIS-DAP protocol. CMSIS-DAP is supported by default in -the MCUXpresso IDE for debugging purposes. - -Connect a USB cable from your development PC to P6 on the dev board. +`make` builds `factory.bin`, which holds wolfBoot and version 1 of the test-app. -Open a terminal to the virtual COM port with putty or similar app, settings 115200-N-8-1. +### LPC55S69: Loading the firmware -### LPC55S69: Testing firmware factory.bin +Connect a USB cable from your development PC to P6 (Debug Link) on the board, +and open a terminal on the board's virtual COM port at 115200-N-8-1. -1) Erase the entire flash: +The firmware can be loaded with [pyOCD](https://pyocd.io), which supports the +LPC55S69 without a device pack: ```sh -LinkServer flash LPC55S69 erase +pip install pyocd +pyocd erase -t lpc55s69 --chip +pyocd flash -t lpc55s69 factory.bin ``` -2) Program the factory.bin, which contains both wolfBoot and the test-app version 1: +pyOCD erases this flash in 32KB sectors and keeps the data around a write that +falls in the same sector. + +NXP's [LinkServer](https://www.nxp.com/design/design-center/software/development-software/mcuxpresso-software-and-tools-/linkserver-for-microcontrollers:LINKERSERVER#downloads) +can be used instead: ```sh +LinkServer flash LPC55S69 erase LinkServer flash LPC55S69 load factory.bin:0 ``` -NOTE: See [tools/scripts/lpc55s69/lpc55s69_flash_factory_bin.cmm](/tools/scripts/lpc55s69/lpc55s69_flash_factory_bin.cmm) for the lauterbach equivalent of 1 and 2 combined. -3) The LED will light up blue to indicate version 1 of the firmware is running. You should also see output -like this in the terminal window: +NOTE: The LPCXpresso55S69's on-board LPC-Link2 can optionally be updated to use +the J-Link protocol instead of CMSIS-DAP. See the EVK user manual for how to do +this. See +[tools/scripts/lpc55s69/lpc55s69_flash_factory_bin.cmm](/tools/scripts/lpc55s69/lpc55s69_flash_factory_bin.cmm) +for the Lauterbach equivalent. -```sh -lpc55s69 init -Boot partition: 0xD000 (sz 22460, ver 0x1, type 0x601) -Partition 1 header magic 0xFFFFFFFF invalid at 0x18000 -Boot partition: 0xD000 (sz 22460, ver 0x1, type 0x601) +The LED lights up blue to indicate version 1 of the firmware is running, and +the console shows: + +``` +Boot partition: 0x10000 (sz 22380, ver 0x1, type 0x601) +Partition 1 header magic 0xFFFFFFFF invalid at 0x40000 +Boot partition: 0x10000 (sz 22380, ver 0x1, type 0x601) Booting version: 0x1 +Checking integrity...done +Verifying signature...done ================================== LPC55S69 wolfBoot demo Application @@ -3555,6 +3633,11 @@ Calling wolfBoot_success() update: ver=0x0 state=0xFF ``` +With `lpc55s69-tz.config` the application also prints a random number it +obtained from the secure RNG through the non-secure callable API +(`Today's lucky number: 0x39`), and confirms the boot with +`wolfBoot_nsc_success()`. + ### LPC55S69: Testing firmware update 1) Sign the test-app with version 2: @@ -3563,23 +3646,20 @@ Calling wolfBoot_success() ./tools/keytools/sign --ecc384 --sha256 test-app/image.bin wolfboot_signing_private_key.der 2 ``` -2) Flash v2 update binary to your `.config`'s `WOLFBOOT_PARTITION_UPDATE_ADDRESS` +2) Flash the v2 image to your `.config`'s `WOLFBOOT_PARTITION_UPDATE_ADDRESS` +(`0x60000` for the TrustZone configurations): -Example: ```sh -LinkServer flash LPC55S69 load test-app/image_v2_signed.bin:0x18000 +pyocd flash -t lpc55s69 -a 0x40000 test-app/image_v2_signed.bin ``` -NOTE: See [tools/scripts/lpc55s69/lpc55s69_flash_update.cmm](/tools/scripts/lpc55s69/lpc55s69_flash_update.cmm) for the lauterbach equivalent. -3) You should see output like this in the terminal window: +or `LinkServer flash LPC55S69 load test-app/image_v2_signed.bin:0x40000`. See +[tools/scripts/lpc55s69/lpc55s69_flash_update.cmm](/tools/scripts/lpc55s69/lpc55s69_flash_update.cmm) +for the Lauterbach equivalent. -```sh -lpc55s69 init -Boot partition: 0xD000 (sz 22460, ver 0x1, type 0x601) -Update partition: 0x18000 (sz 22460, ver 0x2, type 0x601) -Boot partition: 0xD000 (sz 22460, ver 0x1, type 0x601) -Booting version: 0x1 +3) Version 1 finds the update and triggers it: +``` ================================== LPC55S69 wolfBoot demo Application Copyright 2026 wolfSSL Inc @@ -3593,164 +3673,24 @@ Triggering update... ...done. Reboot to apply. ``` -4) Press the RESET button to reboot - -5) The LED will light up green to indicate version 2 of the firmware is running. You should also see output -like this in the terminal window: +4) Press the RESET button (or run `pyocd reset -t lpc55s69`). wolfBoot swaps the +partitions, the LED lights up green to indicate version 2 is running, and the +new version confirms itself: -```sh -lpc55s69 init -Update partition: 0x18000 (sz 22460, ver 0x2, type 0x601) -Boot partition: 0xD000 (sz 22460, ver 0x1, type 0x601) -Update partition: 0x18000 (sz 22460, ver 0x2, type 0x601) +``` Starting Update (fallback allowed 0) -Update partition: 0x18000 (sz 22460, ver 0x2, type 0x601) -Boot partition: 0xD000 (sz 22460, ver 0x1, type 0x601) +Update partition: 0x40000 (sz 22380, ver 0x2, type 0x601) +Boot partition: 0x10000 (sz 22380, ver 0x1, type 0x601) Versions: Current 0x1, Update 0x2 Copy sector 0 (part 1->2) Copy sector 0 (part 0->1) Copy sector 0 (part 2->0) -Boot partition: 0xD000 (sz 22460, ver 0x2, type 0x601) -Update partition: 0x18000 (sz 22460, ver 0x1, type 0x601) -Copy sector 1 (part 1->2) -Copy sector 1 (part 0->1) -Copy sector 1 (part 2->0) -Copy sector 2 (part 1->2) -Copy sector 2 (part 0->1) -Copy sector 2 (part 2->0) -Copy sector 3 (part 1->2) -Copy sector 3 (part 0->1) -Copy sector 3 (part 2->0) -Copy sector 4 (part 1->2) -Copy sector 4 (part 0->1) -Copy sector 4 (part 2->0) -Copy sector 5 (part 1->2) -Copy sector 5 (part 0->1) -Copy sector 5 (part 2->0) -Copy sector 6 (part 1->2) -Copy sector 6 (part 0->1) -Copy sector 6 (part 2->0) -Copy sector 7 (part 1->2) -Copy sector 7 (part 0->1) -Copy sector 7 (part 2->0) -Copy sector 8 (part 1->2) -Copy sector 8 (part 0->1) -Copy sector 8 (part 2->0) -Copy sector 9 (part 1->2) -Copy sector 9 (part 0->1) -Copy sector 9 (part 2->0) -Copy sector 10 (part 1->2) -Copy sector 10 (part 0->1) -Copy sector 10 (part 2->0) -Copy sector 11 (part 1->2) -Copy sector 11 (part 0->1) -Copy sector 11 (part 2->0) -Copy sector 12 (part 1->2) -Copy sector 12 (part 0->1) -Copy sector 12 (part 2->0) -Copy sector 13 (part 1->2) -Copy sector 13 (part 0->1) -Copy sector 13 (part 2->0) -Copy sector 14 (part 1->2) -Copy sector 14 (part 0->1) -Copy sector 14 (part 2->0) -Copy sector 15 (part 1->2) -Copy sector 15 (part 0->1) -Copy sector 15 (part 2->0) -Copy sector 16 (part 1->2) -Copy sector 16 (part 0->1) -Copy sector 16 (part 2->0) -Copy sector 17 (part 1->2) -Copy sector 17 (part 0->1) -Copy sector 17 (part 2->0) -Copy sector 18 (part 1->2) -Copy sector 18 (part 0->1) -Copy sector 18 (part 2->0) -Copy sector 19 (part 1->2) -Copy sector 19 (part 0->1) -Copy sector 19 (part 2->0) -Copy sector 20 (part 1->2) -Copy sector 20 (part 0->1) -Copy sector 20 (part 2->0) -Copy sector 21 (part 1->2) -Copy sector 21 (part 0->1) -Copy sector 21 (part 2->0) -Copy sector 22 (part 1->2) -Copy sector 22 (part 0->1) -Copy sector 22 (part 2->0) -Copy sector 23 (part 1->2) -Copy sector 23 (part 0->1) -Copy sector 23 (part 2->0) -Copy sector 24 (part 1->2) -Copy sector 24 (part 0->1) -Copy sector 24 (part 2->0) -Copy sector 25 (part 1->2) -Copy sector 25 (part 0->1) -Copy sector 25 (part 2->0) -Copy sector 26 (part 1->2) -Copy sector 26 (part 0->1) -Copy sector 26 (part 2->0) -Copy sector 27 (part 1->2) -Copy sector 27 (part 0->1) -Copy sector 27 (part 2->0) -Copy sector 28 (part 1->2) -Copy sector 28 (part 0->1) -Copy sector 28 (part 2->0) -Copy sector 29 (part 1->2) -Copy sector 29 (part 0->1) -Copy sector 29 (part 2->0) -Copy sector 30 (part 1->2) -Copy sector 30 (part 0->1) -Copy sector 30 (part 2->0) -Copy sector 31 (part 1->2) -Copy sector 31 (part 0->1) -Copy sector 31 (part 2->0) -Copy sector 32 (part 1->2) -Copy sector 32 (part 0->1) -Copy sector 32 (part 2->0) -Copy sector 33 (part 1->2) -Copy sector 33 (part 0->1) -Copy sector 33 (part 2->0) -Copy sector 34 (part 1->2) -Copy sector 34 (part 0->1) -Copy sector 34 (part 2->0) -Copy sector 35 (part 1->2) -Copy sector 35 (part 0->1) -Copy sector 35 (part 2->0) -Copy sector 36 (part 1->2) -Copy sector 36 (part 0->1) -Copy sector 36 (part 2->0) -Copy sector 37 (part 1->2) -Copy sector 37 (part 0->1) -Copy sector 37 (part 2->0) -Copy sector 38 (part 1->2) -Copy sector 38 (part 0->1) -Copy sector 38 (part 2->0) -Copy sector 39 (part 1->2) -Copy sector 39 (part 0->1) -Copy sector 39 (part 2->0) -Copy sector 40 (part 1->2) -Copy sector 40 (part 0->1) -Copy sector 40 (part 2->0) -Copy sector 41 (part 1->2) -Copy sector 41 (part 0->1) -Copy sector 41 (part 2->0) -Copy sector 42 (part 1->2) -Copy sector 42 (part 0->1) -Copy sector 42 (part 2->0) -Copy sector 43 (part 1->2) -Copy sector 43 (part 0->1) -Copy sector 43 (part 2->0) -Copy sector 44 (part 1->2) -Copy sector 44 (part 0->1) -Copy sector 44 (part 2->0) -Erasing remainder of partition (41 sectors)... -Boot partition: 0xD000 (sz 22460, ver 0x2, type 0x601) -Update partition: 0x18000 (sz 22460, ver 0x1, type 0x601) -Copy sector 85 (part 0->2) +... Copied boot sector to swap -Boot partition: 0xD000 (sz 22460, ver 0x2, type 0x601) +Boot partition: 0x10000 (sz 22380, ver 0x2, type 0x601) Booting version: 0x2 +Checking integrity...done +Verifying signature...done ================================== LPC55S69 wolfBoot demo Application @@ -3763,13 +3703,109 @@ Calling wolfBoot_success() update: ver=0x1 state=0xFF ``` +If version 2 had not called `wolfBoot_success()`, the next reset would roll +back to version 1. + +### LPC55S69: Bootloader self-update + +The configurations without TrustZone keep `RAM_CODE=1`, so wolfBoot can replace +itself: the flash driver and the SDK ROM API wrappers it calls run from RAM +while the bootloader region is rewritten. Build the new wolfBoot with a higher +`WOLFBOOT_VERSION` than the running one, keeping the same keys, then sign it +and place the update trigger at `WOLFBOOT_PARTITION_SIZE - 5`: + +```sh +./tools/keytools/sign --ecc384 --sha256 --wolfboot-update wolfboot.bin wolfboot_signing_private_key.der 2 +printf "pBOOT" > trigger_magic.bin +./tools/bin-assemble/bin-assemble update.bin 0x0 wolfboot_v2_signed.bin 0x2FFFB trigger_magic.bin +pyocd flash -t lpc55s69 -a 0x40000 update.bin +``` + +On the next reset wolfBoot verifies the new image, rewrites the bootloader +region and boots the application with the new bootloader. With TrustZone the +signed bootloader image spans up to the non-secure callable veneers and does +not fit in an update partition, so self-update is not available there. + +### LPC55S69: Provisioning the hardware PUF + +`lpc55s69-tz-psa.config` can derive the attestation identity from the SRAM PUF +instead of the chip UID test fallback. Turn on the PUF and first-boot +provisioning, turn off the fallback, then build and load as above: + +```sh +cp config/examples/lpc55s69-tz-psa.config .config +sed -i 's/^HWPUF?=0/HWPUF?=1/' .config +sed -i 's/^WOLFBOOT_HWPUF_PROVISION?=0/WOLFBOOT_HWPUF_PROVISION?=1/' .config +sed -i 's/^WOLFBOOT_UDS_UID_FALLBACK_FORTEST?=1/WOLFBOOT_UDS_UID_FALLBACK_FORTEST?=0/' .config +make +pyocd erase -t lpc55s69 --chip +pyocd flash -t lpc55s69 factory.bin +``` + +On the first boot wolfBoot enrolls the PUF, generates the UDS key and stores +the activation code and key code at `WOLFBOOT_HWPUF_PROV_ADDRESS` (`0x80000`) +before it starts the application. The application asks the secure side for an +attestation token and prints it: + +``` +Boot partition: 0x40000 (sz 30312, ver 0x1, type 0x601) +Partition 1 header magic 0xFFFFFFFF invalid at 0x60000 +Boot partition: 0x40000 (sz 30312, ver 0x1, type 0x601) +Booting version: 0x1 +Checking integrity...done +Verifying signature...done + +================================== +LPC55S69 wolfBoot demo Application +Copyright 2026 wolfSSL Inc +================================== +Today's lucky number: 0x14 + boot: ver=0x1 state=0xFF + update: ver=0x0 state=0xFF +Calling wolfBoot_nsc_success() + boot: ver=0x1 state=0x00 + update: ver=0x0 state=0xFF +[ATTEST-NS] get_token: chal=64 buf=1024 +[ATTEST] GET_TOKEN: challenge_len=64 out_len=1024 +Boot partition: 0x40000 (sz 30312, ver 0x1, type 0x601) +Boot partition: 0x40000 (sz 30312, ver 0x1, type 0x601) +[ATTEST] GET_TOKEN: dice_rc=0 token_len=404 +[ATTEST-NS] get_token: status=0 len=404 +attest: token size 404 bytes + 84 43 A1 01 28 A0 59 01 49 A4 0A 58 40 00 01 02 | .C..(.Y.I..X@... + 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 | ................ +... +``` + +The UEID claim in the token is the device identity derived from the PUF key. +[tools/scripts/eat_ueid.py](/tools/scripts/eat_ueid.py) reads it from a saved +console log, and it stays the same across resets: + +```sh +python3 tools/scripts/eat_ueid.py console.log +01b57481835d1eacad376f1f5f31c431f3bac16c118c3f5ef8db2ba6fdc0aabe05 +``` + +The stored record is not secret (the key never leaves the PUF), but it is the +device identity, so wolfBoot never replaces a committed record. To provision a +new identity, erase the record and reset. pyOCD erases whole 32KB sectors, so +this also clears the swap sector and the start of the keyvault: + +```sh +pyocd erase -t lpc55s69 --sector 0x80000 +pyocd reset -t lpc55s69 +``` + +A provisioning that fails, or loses power before the commit word is written, +prints `hwpuf provision failed` and runs again on the next boot. + ### LPC55S69: Debugging Debugging with GDB: Note: We include a `.gdbinit` in the wolfBoot root that loads the wolfboot and test-app elf files. -In one terminal: `LinkServer gdbserver LPC55S69` +In one terminal: `pyocd gdbserver -t lpc55s69` (or `LinkServer gdbserver LPC55S69`) In another terminal use `gdb`: @@ -3780,7 +3816,6 @@ c ``` NOTE: See [tools/scripts/lpc55s69/lpc55s69_debug.cmm](/tools/scripts/lpc55s69/lpc55s69_debug.cmm) for the lauterbach equivalent. - ### LPC55S69 Test and Benchmark Here is an example of how to run wolfCrypt test and benchmarking on actual hardware. @@ -3795,10 +3830,9 @@ make 2. Flash the factory.bin and reboot... ```sh -lpc55s69 init -Boot partition: 0x10000 (sz 162932, ver 0x1, type 0x201) -Partition 1 header magic 0xFFFFFFFF invalid at 0x3B000 -Boot partition: 0x10000 (sz 162932, ver 0x1, type 0x201) +Boot partition: 0x10000 (sz 162932, ver 0x1, type 0x601) +Partition 1 header magic 0xFFFFFFFF invalid at 0x40000 +Boot partition: 0x10000 (sz 162932, ver 0x1, type 0x601) Booting version: 0x1 ================================== @@ -3907,12 +3941,11 @@ Benchmark complete Benchmarks complete. ``` -2. Now try changing to PKA?=1 in the config file, rebuild, and reflash... +3. Now try changing to PKA?=1 in the config file, rebuild, and reflash... ```sh -lpc55s69 init -Boot partition: 0x10000 (sz 163148, ver 0x1, type 0x201) -Partition 1 header magic 0xFFFFFFFF invalid at 0x3B000 -Boot partition: 0x10000 (sz 163148, ver 0x1, type 0x201) +Boot partition: 0x10000 (sz 163148, ver 0x1, type 0x601) +Partition 1 header magic 0xFFFFFFFF invalid at 0x40000 +Boot partition: 0x10000 (sz 163148, ver 0x1, type 0x601) Booting version: 0x1 ================================== diff --git a/hal/lpc55s69-ns.ld b/hal/lpc55s69-ns.ld index b6e14cd3bd..b21b9269cb 100644 --- a/hal/lpc55s69-ns.ld +++ b/hal/lpc55s69-ns.ld @@ -1,7 +1,7 @@ MEMORY { FLASH (rx) : ORIGIN = 0x00000000, LENGTH = @WOLFBOOT_PARTITION_BOOT_ADDRESS@ - RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K } SECTIONS @@ -12,8 +12,8 @@ SECTIONS KEEP(*(.isr_vector)) . = 0x200; *(.keystore*) - *(.text*) - *(.rodata*) + *(EXCLUDE_FILE(*fsl_iap.o *fsl_clock.o) .text*) + *(EXCLUDE_FILE(*fsl_iap.o *fsl_clock.o) .rodata*) *(.init*) *(.fini*) . = ALIGN(4); @@ -28,12 +28,15 @@ SECTIONS _stored_data = .; + /* The ROM flash API wrappers run from RAM with the RAMFUNCTIONs */ .data : AT (_stored_data) { _start_data = .; - KEEP(*(.data*)) + KEEP(*(.ramcode*)) + *fsl_iap.o(.text* .rodata*) + *fsl_clock.o(.text* .rodata*) . = ALIGN(4); - KEEP(*(.ramcode)) + KEEP(*(.data*)) . = ALIGN(4); _end_data = .; } > RAM @@ -53,3 +56,8 @@ SECTIONS } END_STACK = ORIGIN(RAM) + LENGTH(RAM); + +/* A self-update erases the flash these run from */ +ASSERT(FLASH_Erase >= ORIGIN(RAM) && FLASH_Program >= ORIGIN(RAM) && + FLASH_VerifyErase >= ORIGIN(RAM) && CLOCK_GetFreq >= ORIGIN(RAM), + "LPC55S69 ROM flash API wrappers must run from RAM") diff --git a/hal/lpc55s69.c b/hal/lpc55s69.c index 299247c8d9..be921f51e9 100644 --- a/hal/lpc55s69.c +++ b/hal/lpc55s69.c @@ -37,6 +37,7 @@ #include "hal.h" #include "loader.h" #include "printf.h" +#include "lpc55s69.h" #include @@ -44,6 +45,15 @@ #include "hal/armv8m_tz.h" #endif +#ifdef WOLFSSL_NXP_LPC55S6X +#include +#include +#endif + +#if (WOLFBOOT_SECTOR_SIZE % LPC55S69_FLASH_PAGE_SIZE) != 0 +#error WOLFBOOT_SECTOR_SIZE must be a multiple of the 512-byte flash page +#endif + #ifdef WOLFSSL_NXP_HWPUF #include #endif @@ -66,18 +76,24 @@ #endif /* WOLFCRYPT_TZ_PSA */ static flash_config_t pflash; -static const uint32_t pflash_page_size = 512U; +static int flash_ready; + +#if defined(TZEN) && defined(__WOLFBOOT) && !defined(LPC55S69_KEYVAULT_ADDR) +extern uint32_t _flash_keyvault; +extern uint32_t _flash_keyvault_size; +#define LPC55S69_KEYVAULT_ADDR ((uint32_t)(uintptr_t)&_flash_keyvault) +#define LPC55S69_KEYVAULT_LEN ((uint32_t)(uintptr_t)&_flash_keyvault_size) +#endif uint32_t SystemCoreClock; /* set in clock_config.c */ static void flashInit(void) { - static int initialized = 0; - - if (!initialized) { + if (!flash_ready) { XMEMSET(&pflash, 0, sizeof(pflash)); - FLASH_Init(&pflash); - FFR_Init(&pflash); - initialized = 1; + if (FLASH_Init(&pflash) == kStatus_FLASH_Success) { + (void)FFR_Init(&pflash); + flash_ready = 1; + } } } @@ -130,6 +146,12 @@ static NOINLINEFUNCTION void hal_zeroize(void *ptr, size_t len) #if (WOLFBOOT_HWPUF_PROV_ADDRESS + HWPUF_PROV_FLASH_LEN) > 0x9DE00ul #error "WOLFBOOT_HWPUF_PROV_ADDRESS overlaps the protected flash region" #endif +#ifdef WOLFBOOT_KEYVAULT_ADDRESS +#if HWPUF_PROV_OVERLAPS(WOLFBOOT_KEYVAULT_ADDRESS & ~LPC55S69_FLASH_SECURE_ALIAS, \ + WOLFBOOT_KEYVAULT_SIZE) +#error "WOLFBOOT_HWPUF_PROV_ADDRESS overlaps the keyvault" +#endif +#endif typedef struct hwpuf_prov { byte ac[PUF_ACTIVATION_CODE_SIZE]; @@ -141,11 +163,19 @@ typedef char hwpuf_prov_size_check[ (sizeof(hwpuf_prov) <= HWPUF_PROV_FLASH_LEN - HWPUF_PROV_PAGE_SIZE) ? 1 : -1]; -/* prov holds no secure information, so does not need aggressive zeroing */ -static hwpuf_prov prov; +/* Holds no secret; word aligned because the PUF driver streams it by word */ +static hwpuf_prov prov __attribute__((aligned(4))); #define HWPUF_PROV_BLANK 1 +static uint32_t hwpuf_commit_word(void) +{ + const uint8_t *p = LPC55S69_FLASH_PTR(HWPUF_PROV_COMMIT_ADDRESS); + + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | + ((uint32_t)p[3] << 24); +} + /* Load the stored record into rec. * Returns 0 if a committed record was loaded, HWPUF_PROV_BLANK if the commit * page is erased or all 0xFF (never provisioned, or interrupted before the @@ -176,7 +206,7 @@ static int hwpuf_provision_get(hwpuf_prov *rec) if (FLASH_VerifyProgram(&pflash, HWPUF_PROV_COMMIT_ADDRESS, HWPUF_PROV_PAGE_SIZE, (const uint8_t *)commit_page, &failed_addr, &failed_data) == kStatus_FLASH_Success && - *(volatile uint32_t *)HWPUF_PROV_COMMIT_ADDRESS == 0xFFFFFFFFul) + hwpuf_commit_word() == 0xFFFFFFFFul) return HWPUF_PROV_BLANK; commit_page[0] = HWPUF_PROV_MAGIC; @@ -185,7 +215,7 @@ static int hwpuf_provision_get(hwpuf_prov *rec) &failed_addr, &failed_data) != kStatus_FLASH_Success) return -1; - if (*(volatile uint32_t *)HWPUF_PROV_COMMIT_ADDRESS != HWPUF_PROV_MAGIC) + if (hwpuf_commit_word() != HWPUF_PROV_MAGIC) return -1; for (addr = WOLFBOOT_HWPUF_PROV_ADDRESS; addr < end; @@ -195,7 +225,8 @@ static int hwpuf_provision_get(hwpuf_prov *rec) return -1; } - XMEMCPY(rec, (const void *)WOLFBOOT_HWPUF_PROV_ADDRESS, sizeof(*rec)); + XMEMCPY(rec, LPC55S69_FLASH_PTR(WOLFBOOT_HWPUF_PROV_ADDRESS), + sizeof(*rec)); return 0; } @@ -244,13 +275,13 @@ static int hwpuf_provision_set(void) if (hal_flash_write(WOLFBOOT_HWPUF_PROV_ADDRESS, (const uint8_t *)&prov, sizeof(prov)) != 0) goto unlock_out; - if (XMEMCMP((const void *)WOLFBOOT_HWPUF_PROV_ADDRESS, &prov, + if (XMEMCMP(LPC55S69_FLASH_PTR(WOLFBOOT_HWPUF_PROV_ADDRESS), &prov, sizeof(prov)) != 0) goto unlock_out; if (hal_flash_write(HWPUF_PROV_COMMIT_ADDRESS, (const uint8_t *)&magic, sizeof(magic)) != 0) goto unlock_out; - if (*(volatile uint32_t *)HWPUF_PROV_COMMIT_ADDRESS != HWPUF_PROV_MAGIC) + if (hwpuf_commit_word() != HWPUF_PROV_MAGIC) goto unlock_out; ret = 0; @@ -397,115 +428,276 @@ int hal_uds_derive_key(uint8_t *out, size_t out_len) } #endif /* __WOLFBOOT && WOLFCRYPT_TZ_PSA */ -#ifdef TZEN -/* AHB_SECURE_CTRL (AHBSC) gates SRAM at the bus level on this part: the SAU - * alone does not make an SRAM block non-secure. The boot ROM leaves every - * 4 KB block secure+privileged; hand SRAM2 (0x20020000..0x2002FFFF, the NS - * app RAM) to the NS world as non-secure+privileged before the NS jump. - * Unprivileged NS code is intentionally kept out of SRAM2: the NS test-app - * runs privileged. */ -#define AHBSC_SRAM2_RULE0 (*(volatile uint32_t *)(AHB_SECURE_CTRL_BASE + 0xA0u)) -#define AHBSC_SRAM2_RULE1 (*(volatile uint32_t *)(AHB_SECURE_CTRL_BASE + 0xA4u)) +#if defined(TZEN) && defined(WOLFSSL_NXP_LPC55S6X) +/* The HASHCRYPT bus master reaches SRAM only through the non-secure alias */ +#error PKA=1 is not supported with TZEN=1 +#endif + +#if defined(TZEN) && defined(__WOLFBOOT) +#if ((WOLFBOOT_PARTITION_BOOT_ADDRESS % LPC55S69_AHBSC_FLASH_REGION) != 0) || \ + ((WOLFBOOT_PARTITION_SIZE % LPC55S69_AHBSC_FLASH_REGION) != 0) +#error TZEN needs the boot partition aligned to the 32KB AHB secure flash rules +#endif +/* Every rule field set to secure and privileged */ +#define LPC55S69_AHBSC_SECURE 0x33333333U +#define LPC55S69_AHBSC_RULE_S 0x3U + +/* The SAU only filters the CPU; these AHB rules apply the same split to DMA + * and the second core. The controller is write-locked until reset. */ static void hal_ahbsc_init(void) { - AHBSC_SRAM2_RULE0 = 0x11111111u; /* blocks 0-7: NS + privileged */ - AHBSC_SRAM2_RULE1 = 0x11111111u; /* blocks 8-15: NS + privileged */ + uint32_t boot = WOLFBOOT_PARTITION_BOOT_ADDRESS & + ~LPC55S69_FLASH_SECURE_ALIAS; + uint32_t region; + uint32_t start; + uint32_t rule; + uint32_t misc; + uint32_t i; + + for (i = 0; i < AHB_SECURE_CTRL_SEC_CTRL_FLASH_ROM_SEC_CTRL_FLASH_MEM_RULE_COUNT; + i++) { + rule = 0; + for (region = 0; region < 8U; region++) { + start = ((i * 8U) + region) * LPC55S69_AHBSC_FLASH_REGION; + if ((start < boot) || (start >= boot + WOLFBOOT_PARTITION_SIZE)) { + rule |= LPC55S69_AHBSC_RULE_S << (region * 4U); + } + } + AHB_SECURE_CTRL->SEC_CTRL_FLASH_ROM[0].SEC_CTRL_FLASH_MEM_RULE[i] = rule; + } + for (i = 0; i < AHB_SECURE_CTRL_SEC_CTRL_FLASH_ROM_SEC_CTRL_ROM_MEM_RULE_COUNT; + i++) { + AHB_SECURE_CTRL->SEC_CTRL_FLASH_ROM[0].SEC_CTRL_ROM_MEM_RULE[i] = + LPC55S69_AHBSC_SECURE; + } + AHB_SECURE_CTRL->SEC_CTRL_FLASH_ROM[0].SLAVE_RULE = + AHB_SECURE_CTRL_SEC_CTRL_FLASH_ROM_SLAVE_RULE_ROM_RULE( + LPC55S69_AHBSC_RULE_S); + + AHB_SECURE_CTRL->SEC_CTRL_RAMX[0].SLAVE_RULE = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_RAMX[0].MEM_RULE[0] = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_RAM0[0].SLAVE_RULE = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_RAM0[0].MEM_RULE[0] = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_RAM0[0].MEM_RULE[1] = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_RAM1[0].SLAVE_RULE = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_RAM1[0].MEM_RULE[0] = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_RAM1[0].MEM_RULE[1] = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_RAM2[0].SLAVE_RULE = 0U; + AHB_SECURE_CTRL->SEC_CTRL_RAM2[0].MEM_RULE[0] = 0U; + AHB_SECURE_CTRL->SEC_CTRL_RAM2[0].MEM_RULE[1] = 0U; + AHB_SECURE_CTRL->SEC_CTRL_RAM3[0].SLAVE_RULE = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_RAM3[0].MEM_RULE[0] = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_RAM3[0].MEM_RULE[1] = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_RAM4[0].SLAVE_RULE = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_RAM4[0].MEM_RULE[0] = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_USB_HS[0].SLAVE_RULE = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_USB_HS[0].MEM_RULE[0] = LPC55S69_AHBSC_SECURE; + + AHB_SECURE_CTRL->SEC_CTRL_APB_BRIDGE[0].SLAVE_RULE = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_APB_BRIDGE[0].SEC_CTRL_APB_BRIDGE0_MEM_CTRL0 = + LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_APB_BRIDGE[0].SEC_CTRL_APB_BRIDGE0_MEM_CTRL1 = + LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_APB_BRIDGE[0].SEC_CTRL_APB_BRIDGE0_MEM_CTRL2 = + LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_APB_BRIDGE[0].SEC_CTRL_APB_BRIDGE1_MEM_CTRL0 = + LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_APB_BRIDGE[0].SEC_CTRL_APB_BRIDGE1_MEM_CTRL1 = + LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_APB_BRIDGE[0].SEC_CTRL_APB_BRIDGE1_MEM_CTRL2 = + LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_APB_BRIDGE[0].SEC_CTRL_APB_BRIDGE1_MEM_CTRL3 = + LPC55S69_AHBSC_SECURE; + + AHB_SECURE_CTRL->SEC_CTRL_AHB_PORT8_SLAVE0_RULE = LPC55S69_AHBSC_SECURE & + ~AHB_SECURE_CTRL_SEC_CTRL_AHB_PORT8_SLAVE0_RULE_FLEXCOMM0_RULE_MASK; + AHB_SECURE_CTRL->SEC_CTRL_AHB_PORT8_SLAVE1_RULE = LPC55S69_AHBSC_SECURE & + ~AHB_SECURE_CTRL_SEC_CTRL_AHB_PORT8_SLAVE1_RULE_GPIO0_RULE_MASK; + AHB_SECURE_CTRL->SEC_CTRL_AHB_PORT9_SLAVE0_RULE = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_AHB_PORT9_SLAVE1_RULE = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_AHB_PORT10[0].SLAVE0_RULE = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_AHB_PORT10[0].SLAVE1_RULE = LPC55S69_AHBSC_SECURE; + AHB_SECURE_CTRL->SEC_CTRL_AHB_PORT10[0].SEC_CTRL_AHB_SEC_CTRL_MEM_RULE[0] = + LPC55S69_AHBSC_SECURE; + + /* Checking on (0b01); the write lock (0b01) takes effect from the DP copy */ + misc = (AHB_SECURE_CTRL->MISC_CTRL_REG & + ~(AHB_SECURE_CTRL_MISC_CTRL_REG_WRITE_LOCK_MASK | + AHB_SECURE_CTRL_MISC_CTRL_REG_ENABLE_SECURE_CHECKING_MASK)) | + AHB_SECURE_CTRL_MISC_CTRL_REG_WRITE_LOCK(2U) | + AHB_SECURE_CTRL_MISC_CTRL_REG_ENABLE_SECURE_CHECKING(1U); + AHB_SECURE_CTRL->MISC_CTRL_REG = misc; + AHB_SECURE_CTRL->MISC_CTRL_DP_REG = + (misc & ~AHB_SECURE_CTRL_MISC_CTRL_REG_WRITE_LOCK_MASK) | + AHB_SECURE_CTRL_MISC_CTRL_REG_WRITE_LOCK(1U); } static void hal_sau_init(void) { - hal_ahbsc_init(); - /* Non-secure callable area */ sau_init_region(0, WOLFBOOT_NSC_ADDRESS, WOLFBOOT_NSC_ADDRESS + WOLFBOOT_NSC_SIZE - 1, 1); - /* Non-secure: application flash area (boot+update partition) */ + /* Non-secure: application flash area (boot partition) */ sau_init_region(1, WOLFBOOT_PARTITION_BOOT_ADDRESS, - WOLFBOOT_PARTITION_BOOT_ADDRESS + (WOLFBOOT_PARTITION_SIZE * 2) - 1, - 0); + WOLFBOOT_PARTITION_BOOT_ADDRESS + WOLFBOOT_PARTITION_SIZE - 1, 0); - /* Non-secure RAM */ - sau_init_region(2, 0x20020000, 0x2002FFFF, 0); + /* Non-secure RAM, above the SRAM used by wolfBoot */ + sau_init_region(2, LPC55S69_NS_SRAM_START, LPC55S69_NS_SRAM_END, 0); - /* Peripherals */ - sau_init_region(3, 0x40086000, 0x40086FFF, 0); /* uart on flexcomm0 */ - sau_init_region(4, 0x4008c000, 0x4008FFFF, 0); /* gpio for leds */ + /* Only the console and GPIO; clocks, pin mux, flash, DMA and crypto stay + * secure */ + sau_init_region(3, LPC55S69_FLEXCOMM0_NS_BASE, + LPC55S69_FLEXCOMM0_NS_BASE + LPC55S69_FLEXCOMM0_NS_SIZE - 1, 0); + sau_init_region(4, LPC55S69_GPIO_NS_BASE, + LPC55S69_GPIO_NS_BASE + LPC55S69_GPIO_NS_SIZE - 1, 0); - /* Enable SAU */ - SAU_CTRL = SAU_INIT_CTRL_ENABLE; + NVIC_SetTargetState(FLEXCOMM0_IRQn); - /* Enable securefault handler */ + SAU_CTRL = SAU_INIT_CTRL_ENABLE; SCB_SHCSR |= SCB_SHCSR_SECUREFAULT_EN; + __DSB(); + __ISB(); + + AHB_SECURE_CTRL->CPU0_LOCK_REG = (AHB_SECURE_CTRL->CPU0_LOCK_REG & + ~(AHB_SECURE_CTRL_CPU0_LOCK_REG_LOCK_SAU_MASK | + AHB_SECURE_CTRL_CPU0_LOCK_REG_CPU0_LOCK_REG_LOCK_MASK)) | + AHB_SECURE_CTRL_CPU0_LOCK_REG_LOCK_SAU(1U) | + AHB_SECURE_CTRL_CPU0_LOCK_REG_CPU0_LOCK_REG_LOCK(1U); + + hal_ahbsc_init(); } +/* Hand the LED pins to the non-secure application: pin mux stays secure */ static void periph_unsecure(void) { + const uint32_t led_mux = IOCON_FUNC0 | IOCON_MODE_PULLUP | IOCON_DIGITAL_EN; + + CLOCK_EnableClock(kCLOCK_Iocon); + CLOCK_EnableClock(kCLOCK_Gpio1); + IOCON_PinMuxSet(IOCON, LPC55S69_LED_PORT, LPC55S69_LED_BLUE_PIN, led_mux); + IOCON_PinMuxSet(IOCON, LPC55S69_LED_PORT, LPC55S69_LED_RED_PIN, led_mux); + IOCON_PinMuxSet(IOCON, LPC55S69_LED_PORT, LPC55S69_LED_GREEN_PIN, led_mux); } #endif /* TZEN */ -static void hal_flash_fix_ecc(void) +/* PRIMASK saved per ROM call so the caller's interrupt state survives */ +static int RAMFUNCTION lpc55s69_page_erase(uint32_t offset) { - uint8_t page_buf[512]; - uint32_t addr; - uint32_t start = ARCH_FLASH_OFFSET; - uint32_t end = WOLFBOOT_PARTITION_SWAP_ADDRESS + WOLFBOOT_SECTOR_SIZE; + uint32_t primask = __get_PRIMASK(); + status_t st; + + __disable_irq(); + st = FLASH_Erase(&pflash, offset, LPC55S69_FLASH_PAGE_SIZE, + kFLASH_ApiEraseKey); + __set_PRIMASK(primask); + return (st == kStatus_FLASH_Success) ? 0 : -1; +} - XMEMSET(page_buf, 0xFF, sizeof(page_buf)); +static int RAMFUNCTION lpc55s69_page_program(uint32_t offset, + const uint8_t *page) +{ + uint32_t primask = __get_PRIMASK(); + status_t st; - for (addr = start; addr < end; addr += pflash_page_size) { - if (FLASH_VerifyErase(&pflash, addr, pflash_page_size) - == kStatus_FLASH_Success) { - FLASH_Program(&pflash, addr, page_buf, pflash_page_size); + __disable_irq(); + st = FLASH_Program(&pflash, offset, page, LPC55S69_FLASH_PAGE_SIZE); + __set_PRIMASK(primask); + return (st == kStatus_FLASH_Success) ? 0 : -1; +} + +static int RAMFUNCTION lpc55s69_page_is_blank(uint32_t offset) +{ + uint32_t primask = __get_PRIMASK(); + status_t st; + + __disable_irq(); + st = FLASH_VerifyErase(&pflash, offset, LPC55S69_FLASH_PAGE_SIZE); + __set_PRIMASK(primask); + return (st == kStatus_FLASH_Success); +} + +/* Accepts the secure (0x1xxxxxxx) and non-secure flash aliases */ +static int RAMFUNCTION lpc55s69_flash_offset(uint32_t address, uint32_t len, + uint32_t *offset) +{ + uint32_t off = address; + + if ((off >= LPC55S69_FLASH_SECURE_ALIAS) && + (off < LPC55S69_FLASH_SECURE_ALIAS + LPC55S69_FLASH_SIZE)) { + off -= LPC55S69_FLASH_SECURE_ALIAS; + } + if ((off >= LPC55S69_FLASH_SIZE) || (len > LPC55S69_FLASH_SIZE - off)) { + return -1; + } + *offset = off; + return 0; +} + +static int hal_flash_fix_ecc(uint32_t address, uint32_t len) +{ + uint8_t page_buf[LPC55S69_FLASH_PAGE_SIZE]; + uint32_t offset = 0; + uint32_t end; + uint32_t i; + int ret; + + ret = lpc55s69_flash_offset(address, len, &offset); + if (ret == 0) { + for (i = 0; i < sizeof(page_buf); i++) { + page_buf[i] = 0xFFU; + } + end = offset + len; + while ((ret == 0) && (offset < end)) { + if (lpc55s69_page_is_blank(offset)) { + ret = lpc55s69_page_program(offset, page_buf); + } + offset += LPC55S69_FLASH_PAGE_SIZE; } } + return ret; } +/* Fill every erased page wolfBoot may read, so reads never hit an ECC fault; + * DICE attestation hashes the whole bootloader region */ +static int hal_flash_fix_ecc_all(void) +{ + int ret = 0; + #ifdef __WOLFBOOT -#define IOCON_PIO_FUNC0 0x00u /*!<@brief Selects pin function 0 */ -#define IOCON_PIO_MODE_PULLUP 0x20u /*!<@brief Selects pull-up function */ -#define IOCON_PIO_SLEW_STANDARD 0x00u /*!<@brief Standard mode, output slew rate control is enabled */ -#define IOCON_PIO_INV_DI 0x00u /*!<@brief Input function is not inverted */ -#define IOCON_PIO_DIGITAL_EN 0x0100u /*!<@brief Enables digital function */ -#define IOCON_PIO_OPENDRAIN_DI 0x00u /*!<@brief Open drain is disabled */ - -#define RED_LED 6 -#define GREEN_LED 7 -#define BLUE_LED 4 - -static void leds_pin_init(void) -{ - /* red output off */ - GPIO->SET[1] = 1UL << RED_LED; - GPIO->DIR[1] |= 1UL << RED_LED; - /* green output off */ - GPIO->SET[1] = 1UL << GREEN_LED; - GPIO->DIR[1] |= 1UL << GREEN_LED; - /* blue output off */ - GPIO->SET[1] = 1UL << BLUE_LED; - GPIO->DIR[1] |= 1UL << BLUE_LED; - - const uint32_t LED_PINMUX_CONFIG = (/* Pin is configured as PIO */ - IOCON_PIO_FUNC0 | - /* Selects pull-up function */ - IOCON_PIO_MODE_PULLUP | - /* Standard mode, output slew rate control is enabled */ - IOCON_PIO_SLEW_STANDARD | - /* Input function is not inverted */ - IOCON_PIO_INV_DI | - /* Enables digital function */ - IOCON_PIO_DIGITAL_EN | - /* Open drain is disabled */ - IOCON_PIO_OPENDRAIN_DI); - IOCON_PinMuxSet(IOCON, 1, RED_LED, LED_PINMUX_CONFIG); - IOCON_PinMuxSet(IOCON, 1, GREEN_LED, LED_PINMUX_CONFIG); - IOCON_PinMuxSet(IOCON, 1, BLUE_LED, LED_PINMUX_CONFIG); + ret = hal_flash_fix_ecc(ARCH_FLASH_OFFSET, + WOLFBOOT_PARTITION_BOOT_ADDRESS - ARCH_FLASH_OFFSET); +#endif + if (ret == 0) { + ret = hal_flash_fix_ecc(WOLFBOOT_PARTITION_BOOT_ADDRESS, + WOLFBOOT_PARTITION_SIZE); + } + if (ret == 0) { + ret = hal_flash_fix_ecc(WOLFBOOT_PARTITION_UPDATE_ADDRESS, + WOLFBOOT_PARTITION_SIZE); + } + if (ret == 0) { + ret = hal_flash_fix_ecc(WOLFBOOT_PARTITION_SWAP_ADDRESS, + WOLFBOOT_SECTOR_SIZE); + } +#if defined(TZEN) && defined(__WOLFBOOT) + if (ret == 0) { + ret = hal_flash_fix_ecc(LPC55S69_KEYVAULT_ADDR, LPC55S69_KEYVAULT_LEN); + } +#endif + return ret; } -#endif /* __WOLFBOOT */ -extern int wc_hashcrypt_init(void); -extern int wc_casper_init(void); +#if !defined(__WOLFBOOT) && !defined(TZEN) +/* The test-app seeds wolfCrypt from the RNG; wolfBoot itself does not */ +static void lpc55s69_app_rng_init(void) +{ + CLOCK_EnableClock(kCLOCK_Rng); + RESET_PeripheralReset(kRNG_RST_SHIFT_RSTn); + RNG_Init(RNG); +} +#endif void hal_init(void) { @@ -513,28 +705,22 @@ void hal_init(void) /* lpc55s69 must run < 100 MHz for flash write/erase to work */ BOARD_BootClockFROHF96M(); - CLOCK_EnableClock(kCLOCK_Iocon); - CLOCK_EnableClock(kCLOCK_Gpio1); - # ifdef DEBUG_UART uart_init(); - uart_write("lpc55s69 init\n", 14); # endif # ifdef WOLFSSL_NXP_LPC55S6X CLOCK_EnableClock(kCLOCK_HashCrypt); - wc_hashcrypt_init(); + (void)wc_hashcrypt_init(); CLOCK_EnableClock(kCLOCK_Casper); - wc_casper_init(); + (void)wc_casper_init(); # endif - - CLOCK_EnableClock(kCLOCK_Rng); - RESET_PeripheralReset(kRNG_RST_SHIFT_RSTn); - RNG_Init(RNG); - - leds_pin_init(); #endif /* __WOLFBOOT */ +#if !defined(__WOLFBOOT) && !defined(TZEN) + lpc55s69_app_rng_init(); +#endif + #if defined(WOLFSSL_NXP_HWPUF) && (defined(__WOLFBOOT) || !defined(TZEN)) /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL: PUF_Init() won't do this */ CLOCK_EnableClock(kCLOCK_Puf); @@ -542,10 +728,12 @@ void hal_init(void) #if defined(__WOLFBOOT) || !defined(TZEN) flashInit(); - hal_flash_fix_ecc(); + if ((flash_ready == 0) || (hal_flash_fix_ecc_all() != 0)) { + wolfBoot_printf("lpc55s69: flash init failed\n"); + } #endif -#if defined(TZEN) && !defined(NONSECURE_APP) +#if defined(TZEN) && defined(__WOLFBOOT) hal_sau_init(); #endif @@ -562,9 +750,35 @@ void hal_init(void) #endif } +#if defined(BOOT_BENCHMARK) && defined(__WOLFBOOT) +/* DWT CYCCNT stops without a debugger; SysTick on the FRO 1MHz ticks in us */ +uint64_t hal_get_timer_us(void) +{ + static uint64_t elapsed_us; + static uint32_t last; + static int started; + uint32_t now; + + if (!started) { + SYSCON->CLOCK_CTRL |= SYSCON_CLOCK_CTRL_FRO1MHZ_CLK_ENA_MASK; + CLOCK_AttachClk(kFRO1M_to_SYSTICK0); + SysTick->LOAD = SysTick_LOAD_RELOAD_Msk; + SysTick->VAL = 0U; + SysTick->CTRL = SysTick_CTRL_ENABLE_Msk; + last = SysTick->VAL; + started = 1; + } + now = SysTick->VAL; + elapsed_us += (last - now) & SysTick_LOAD_RELOAD_Msk; + last = now; + return elapsed_us; +} +#endif + #ifdef __WOLFBOOT /* Assert hook needed by SDK assert() macro. */ -void __assert_func(const char *a, int b, const char *c, const char *d) +void RAMFUNCTION __assert_func(const char *a, int b, const char *c, + const char *d) { (void)a; (void)b; @@ -576,6 +790,9 @@ void __assert_func(const char *a, int b, const char *c, const char *d) void hal_prepare_boot(void) { +#ifdef BOOT_BENCHMARK + SysTick->CTRL = 0U; +#endif #ifdef TZEN periph_unsecure(); #endif @@ -584,41 +801,63 @@ void hal_prepare_boot(void) int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len) { - uint8_t page_buf[512]; - uint32_t page_addr; - uint32_t offset; + uint8_t page_buf[LPC55S69_FLASH_PAGE_SIZE]; + const uint8_t *page_src; + uint32_t offset = 0; + uint32_t page_off; + uint32_t in_page; uint32_t chunk; + uint32_t i; + int blank; + int ret = 0; - while (len > 0) { - page_addr = address & ~(pflash_page_size - 1); - offset = address - page_addr; - chunk = pflash_page_size - offset; - if ((uint32_t)len < chunk) - chunk = (uint32_t)len; - - if (FLASH_VerifyErase(&pflash, page_addr, pflash_page_size) - == kStatus_FLASH_Success) { - XMEMSET(page_buf, 0xFF, pflash_page_size); - } else { - XMEMCPY(page_buf, (void *)page_addr, pflash_page_size); + if (len == 0) { + return 0; + } + if ((len < 0) || (data == NULL) || (flash_ready == 0) || + (lpc55s69_flash_offset(address, (uint32_t)len, &offset) != 0)) { + return -1; + } - if (FLASH_Erase(&pflash, page_addr, pflash_page_size, - kFLASH_ApiEraseKey) != kStatus_FLASH_Success) - return -1; + while ((ret == 0) && (len > 0)) { + page_off = offset & ~(LPC55S69_FLASH_PAGE_SIZE - 1U); + in_page = offset - page_off; + chunk = LPC55S69_FLASH_PAGE_SIZE - in_page; + if ((uint32_t)len < chunk) { + chunk = (uint32_t)len; } - XMEMCPY(page_buf + offset, data, chunk); + blank = lpc55s69_page_is_blank(page_off); + if ((chunk != LPC55S69_FLASH_PAGE_SIZE) && blank) { + for (i = 0; i < LPC55S69_FLASH_PAGE_SIZE; i++) { + page_buf[i] = 0xFFU; + } + } + else if (chunk != LPC55S69_FLASH_PAGE_SIZE) { + /* Merge with the page as the caller addresses it (S or NS) */ + page_src = LPC55S69_FLASH_PTR(address - in_page); + for (i = 0; i < LPC55S69_FLASH_PAGE_SIZE; i++) { + page_buf[i] = page_src[i]; + } + } + for (i = 0; i < chunk; i++) { + page_buf[in_page + i] = data[i]; + } - if (FLASH_Program(&pflash, page_addr, page_buf, pflash_page_size) - != kStatus_FLASH_Success) - return -1; + if (!blank) { + ret = lpc55s69_page_erase(page_off); + } + if (ret == 0) { + ret = lpc55s69_page_program(page_off, page_buf); + } address += chunk; + offset += chunk; data += chunk; len -= (int)chunk; } - return 0; + return ret; } void RAMFUNCTION hal_flash_unlock(void) @@ -629,121 +868,100 @@ void RAMFUNCTION hal_flash_lock(void) { } +/* Erased pages are programmed with 0xFF so that later reads do not fault */ int RAMFUNCTION hal_flash_erase(uint32_t address, int len) { - uint8_t page_buf[512]; - uint32_t pos; + uint8_t page_buf[LPC55S69_FLASH_PAGE_SIZE]; + uint32_t offset = 0; + uint32_t end; + uint32_t i; + int ret = 0; - if (address % pflash_page_size != 0 || len % pflash_page_size != 0) + if (len == 0) { + return 0; + } + if ((len < 0) || (flash_ready == 0) || + ((address % LPC55S69_FLASH_PAGE_SIZE) != 0U) || + (((uint32_t)len % LPC55S69_FLASH_PAGE_SIZE) != 0U) || + (lpc55s69_flash_offset(address, (uint32_t)len, &offset) != 0)) { return -1; + } - XMEMSET(page_buf, 0xFF, sizeof(page_buf)); - - for (pos = address; pos < address + (uint32_t)len; pos += pflash_page_size) { - if (FLASH_Erase(&pflash, pos, pflash_page_size, kFLASH_ApiEraseKey) - != kStatus_FLASH_Success) - return -1; - - if (FLASH_Program(&pflash, pos, page_buf, pflash_page_size) - != kStatus_FLASH_Success) - return -1; + for (i = 0; i < sizeof(page_buf); i++) { + page_buf[i] = 0xFFU; + } + end = offset + (uint32_t)len; + while ((ret == 0) && (offset < end)) { + ret = lpc55s69_page_erase(offset); + if (ret == 0) { + ret = lpc55s69_page_program(offset, page_buf); + } + offset += LPC55S69_FLASH_PAGE_SIZE; } - return 0; + return ret; } -#ifdef WOLFCRYPT_SECURE_MODE +#if defined(WOLFCRYPT_SECURE_MODE) && !defined(NONSECURE_APP) +static int trng_ready; + void hal_trng_init(void) { - /* handled in hal_init() regardless */ + CLOCK_EnableClock(kCLOCK_Rng); + RESET_PeripheralReset(kRNG_RST_SHIFT_RSTn); + /* Runs the RNG health check; it has no failure return */ + RNG_Init(RNG); + trng_ready = 1; } void hal_trng_fini(void) { + trng_ready = 0; } int hal_trng_get_entropy(unsigned char *out, unsigned int len) { - if (RNG_GetRandomData(RNG, out, len) == kStatus_Success) - return 0; - - return -1; + if ((trng_ready == 0) || (out == NULL)) { + return -1; + } + return (RNG_GetRandomData(RNG, out, len) == kStatus_Success) ? 0 : -1; } #endif /* WOLFCRYPT_SECURE_MODE */ - -#define IOCON_PIO_DIGITAL_EN 0x0100u /*!<@brief Enables digital function */ -#define IOCON_PIO_FUNC1 0x01u /*!<@brief Selects pin function 1 */ -#define IOCON_PIO_INV_DI 0x00u /*!<@brief Input function is not inverted */ -#define IOCON_PIO_MODE_INACT 0x00u /*!<@brief No addition pin function */ -#define IOCON_PIO_OPENDRAIN_DI 0x00u /*!<@brief Open drain is disabled */ -#define IOCON_PIO_SLEW_STANDARD 0x00u /*!<@brief Standard mode, output slew rate control is enabled */ - #ifdef DEBUG_UART void uart_init(void) { - const uint32_t port0_pin29_config = (/* Pin is configured as FC0_RXD_SDA_MOSI_DATA */ - IOCON_PIO_FUNC1 | - /* No addition pin function */ - IOCON_PIO_MODE_INACT | - /* Standard mode, output slew rate control is enabled */ - IOCON_PIO_SLEW_STANDARD | - /* Input function is not inverted */ - IOCON_PIO_INV_DI | - /* Enables digital function */ - IOCON_PIO_DIGITAL_EN | - /* Open drain is disabled */ - IOCON_PIO_OPENDRAIN_DI); - /* PORT0 PIN29 (coords: 92) is configured as FC0_RXD_SDA_MOSI_DATA */ - IOCON_PinMuxSet(IOCON, 0U, 29U, port0_pin29_config); - const uint32_t port0_pin30_config = (/* Pin is configured as FC0_TXD_SCL_MISO_WS */ - IOCON_PIO_FUNC1 | - /* No addition pin function */ - IOCON_PIO_MODE_INACT | - /* Standard mode, output slew rate control is enabled */ - IOCON_PIO_SLEW_STANDARD | - /* Input function is not inverted */ - IOCON_PIO_INV_DI | - /* Enables digital function */ - IOCON_PIO_DIGITAL_EN | - /* Open drain is disabled */ - IOCON_PIO_OPENDRAIN_DI); - /* PORT0 PIN30 (coords: 94) is configured as FC0_TXD_SCL_MISO_WS */ - IOCON_PinMuxSet(IOCON, 0U, 30U, port0_pin30_config); - - /* attach 12 MHz clock to FLEXCOMM0 (debug console) */ + const uint32_t uart_mux = IOCON_FUNC1 | IOCON_MODE_INACT | + IOCON_DIGITAL_EN; + usart_config_t config; + + CLOCK_EnableClock(kCLOCK_Iocon); + IOCON_PinMuxSet(IOCON, LPC55S69_UART_PORT, LPC55S69_UART_RX_PIN, uart_mux); + IOCON_PinMuxSet(IOCON, LPC55S69_UART_PORT, LPC55S69_UART_TX_PIN, uart_mux); + CLOCK_AttachClk(kFRO12M_to_FLEXCOMM0); CLOCK_EnableClock(kCLOCK_FlexComm0); RESET_ClearPeripheralReset(kFC0_RST_SHIFT_RSTn); - usart_config_t config; USART_GetDefaultConfig(&config); - config.baudRate_Bps = 115200U; + config.baudRate_Bps = LPC55S69_UART_BAUD; config.enableTx = true; config.enableRx = true; - (void)USART_Init(USART0, &config, 12000000U); + (void)USART_Init(USART0, &config, LPC55S69_UART_CLK_HZ); } void uart_write(const char *buf, unsigned int sz) { - const char *line; - unsigned int line_sz; + unsigned int i; - while (sz > 0) - { - line = memchr(buf, '\n', sz); - if (line == NULL) { - (void)USART_WriteBlocking(USART0, (const uint8_t *)buf, sz); - break; - } - line_sz = (unsigned int)(line - buf); - if (line_sz > sz - 1U) { - line_sz = sz - 1U; + if (buf == NULL) { + return; + } + for (i = 0; i < sz; i++) { + if (buf[i] == '\n') { + (void)USART_WriteBlocking(USART0, (const uint8_t *)"\r", 1U); } - (void)USART_WriteBlocking(USART0, (const uint8_t *)buf, line_sz); - (void)USART_WriteBlocking(USART0, (const uint8_t *)"\r\n", 2U); - buf = line + 1; - sz -= line_sz + 1U; + (void)USART_WriteBlocking(USART0, (const uint8_t *)&buf[i], 1U); } } #endif /* DEBUG_UART */ diff --git a/hal/lpc55s69.h b/hal/lpc55s69.h new file mode 100644 index 0000000000..e12be17731 --- /dev/null +++ b/hal/lpc55s69.h @@ -0,0 +1,59 @@ +/* lpc55s69.h + * + * Copyright (C) 2026 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 + */ + +#ifndef LPC55S69_H +#define LPC55S69_H + +/* Flash programs and erases in 512-byte pages; an erased page that was + * never programmed faults on read (ECC), so erased pages are filled 0xFF */ +#define LPC55S69_FLASH_PAGE_SIZE 512U +#define LPC55S69_FLASH_SECURE_ALIAS 0x10000000U +/* 630KB of user flash below the protected flash region (PFR) */ +#define LPC55S69_FLASH_SIZE 0x9DE00U +#ifndef LPC55S69_FLASH_PTR +#define LPC55S69_FLASH_PTR(a) ((const uint8_t *)(uintptr_t)(a)) +#endif + +/* AHB secure controller flash rules cover 32KB each */ +#define LPC55S69_AHBSC_FLASH_REGION 0x8000U + +/* SRAM2, the non-secure application RAM */ +#define LPC55S69_NS_SRAM_START 0x20020000U +#define LPC55S69_NS_SRAM_END 0x2002FFFFU +/* Non-secure aliases of the peripherals the application keeps */ +#define LPC55S69_FLEXCOMM0_NS_BASE 0x40086000U +#define LPC55S69_FLEXCOMM0_NS_SIZE 0x1000U +#define LPC55S69_GPIO_NS_BASE 0x4008C000U +#define LPC55S69_GPIO_NS_SIZE 0x4000U + +#define LPC55S69_UART_BAUD 115200U +#define LPC55S69_UART_CLK_HZ 12000000U +#define LPC55S69_UART_PORT 0U +#define LPC55S69_UART_RX_PIN 29U +#define LPC55S69_UART_TX_PIN 30U + +/* LPCXpresso55S69 RGB LED on port 1, active low */ +#define LPC55S69_LED_PORT 1U +#define LPC55S69_LED_BLUE_PIN 4U +#define LPC55S69_LED_RED_PIN 6U +#define LPC55S69_LED_GREEN_PIN 7U + +#endif /* LPC55S69_H */ diff --git a/hal/lpc55s69.ld b/hal/lpc55s69.ld index bb7a7fce14..2d12d9bd3d 100644 --- a/hal/lpc55s69.ld +++ b/hal/lpc55s69.ld @@ -1,6 +1,6 @@ MEMORY { - FLASH (rx) : ORIGIN = 0x10000000, LENGTH = @WOLFBOOT_KEYVAULT_ADDRESS@ - @ARCH_FLASH_OFFSET@ + FLASH (rx) : ORIGIN = 0x10000000, LENGTH = @WOLFBOOT_NSC_ADDRESS@ - 0x10000000 RAM (rwx) : ORIGIN = 0x30000000, LENGTH = 0x10000 /* 64K */ RAM_HEAP (rwx) : ORIGIN = 0x30010000, LENGTH = 0xE000 /* 56K */ FLASH_KEYVAULT (rw) : ORIGIN = @WOLFBOOT_KEYVAULT_ADDRESS@, LENGTH = @WOLFBOOT_KEYVAULT_SIZE@ @@ -16,8 +16,8 @@ SECTIONS KEEP(*(.isr_vector)) . = 0x200; *(.keystore*) - *(.text*) - *(.rodata*) + *(EXCLUDE_FILE(*fsl_iap.o *fsl_clock.o) .text*) + *(EXCLUDE_FILE(*fsl_iap.o *fsl_clock.o) .rodata*) *(.init*) *(.fini*) . = ALIGN(4); @@ -30,19 +30,16 @@ SECTIONS *(.ARM.exidx*) } > FLASH - .gnu.sgstubs ORIGIN(FLASH_NSC) : - { - . += 0x400; - . = ALIGN(4); - *(.gnu.sgstubs*) /* Secure Gateway Stubs */ - . = ALIGN(4); - } > FLASH_NSC - _stored_data = .; + /* The ROM flash API wrappers run from RAM with the RAMFUNCTIONs */ .data : AT (_stored_data) { _start_data = .; + KEEP(*(.ramcode*)) + *fsl_iap.o(.text* .rodata*) + *fsl_clock.o(.text* .rodata*) + . = ALIGN(4); KEEP(*(.data*)) . = ALIGN(4); _end_data = .; @@ -59,11 +56,24 @@ SECTIONS __bss_end__ = .; _end = .; } > RAM + + .gnu.sgstubs ORIGIN(FLASH_NSC) : + { + . += 0x400; + . = ALIGN(4); + *(.gnu.sgstubs*) /* Secure Gateway Stubs */ + . = ALIGN(4); + } > FLASH_NSC . = ALIGN(4); } END_STACK = ORIGIN(RAM) + LENGTH(RAM); +/* A self-update erases the flash these run from */ +ASSERT(FLASH_Erase >= ORIGIN(RAM) && FLASH_Program >= ORIGIN(RAM) && + FLASH_VerifyErase >= ORIGIN(RAM) && CLOCK_GetFreq >= ORIGIN(RAM), + "LPC55S69 ROM flash API wrappers must run from RAM") + _flash_keyvault = ORIGIN(FLASH_KEYVAULT); _flash_keyvault_size = LENGTH(FLASH_KEYVAULT); diff --git a/options.mk b/options.mk index 89b4cdcfa0..23ce44d926 100644 --- a/options.mk +++ b/options.mk @@ -1313,6 +1313,10 @@ ifeq ($(HWPUF),1) ifneq ($(WOLFBOOT_HWPUF_PROV_ADDRESS),) CFLAGS+=-DWOLFBOOT_HWPUF_PROV_ADDRESS=$(WOLFBOOT_HWPUF_PROV_ADDRESS) endif + ifneq ($(WOLFBOOT_KEYVAULT_ADDRESS),) + CFLAGS+=-DWOLFBOOT_KEYVAULT_ADDRESS=$(WOLFBOOT_KEYVAULT_ADDRESS) + CFLAGS+=-DWOLFBOOT_KEYVAULT_SIZE=$(WOLFBOOT_KEYVAULT_SIZE) + endif endif ifeq ($(PKCS11_STORE_STATS),1) diff --git a/test-app/ARM-lpc55s69-ns.ld b/test-app/ARM-lpc55s69-ns.ld index ea288a69a2..3de2ba1567 100644 --- a/test-app/ARM-lpc55s69-ns.ld +++ b/test-app/ARM-lpc55s69-ns.ld @@ -1,4 +1,4 @@ -_Min_Heap_Size = 0x00000100; /* minimal heap (not using malloc) */ +_Min_Heap_Size = 0x00002000; /* newlib stdio buffers: the attestation client uses printf */ _Min_Stack_Size = 0x00008000; /* required amount of stack */ MEMORY diff --git a/test-app/ARM-lpc55s69.ld b/test-app/ARM-lpc55s69.ld index 078c125e3e..cbd93ec351 100644 --- a/test-app/ARM-lpc55s69.ld +++ b/test-app/ARM-lpc55s69.ld @@ -4,7 +4,7 @@ _Min_Stack_Size = 0x00008000; /* required amount of stack */ MEMORY { FLASH (rx) : ORIGIN = @WOLFBOOT_TEST_APP_ADDRESS@, LENGTH = @WOLFBOOT_TEST_APP_SIZE@ - RAM (rwx) : ORIGIN = 0x20020000, LENGTH = 160K + RAM (rwx) : ORIGIN = 0x20020000, LENGTH = 144K /* to the end of SRAM4 */ } SECTIONS diff --git a/test-app/Makefile b/test-app/Makefile index 38fb4fc7f2..1c245fca5d 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -1173,22 +1173,26 @@ endif ifeq ($(TARGET),lpc55s69) CFLAGS+=-ffunction-sections -fdata-sections CFLAGS+=-DWOLFSSL_NXP_RNG_1 + LPC55S69_SDK_SRCS:=\ + $(MCUXPRESSO_PROJECT_TEMPLATE)/clock_config.c \ + $(MCUXPRESSO_DRIVERS)/drivers/fsl_clock.c \ + $(MCUXPRESSO_DRIVERS)/drivers/fsl_reset.c \ + $(MCUXPRESSO)/drivers/common/fsl_common_arm.c \ + $(MCUXPRESSO)/drivers/flexcomm/fsl_flexcomm.c \ + $(MCUXPRESSO)/drivers/flexcomm/usart/fsl_usart.c \ + $(MCUXPRESSO)/drivers/iap1/fsl_iap.c \ + $(MCUXPRESSO)/drivers/lpc_gpio/fsl_gpio.c \ + $(MCUXPRESSO)/drivers/rng_1/fsl_rng.c ifeq ($(TZEN),1) LSCRIPT_TEMPLATE=ARM-lpc55s69-ns.ld CFLAGS:=$(filter-out -mcmse, $(CFLAGS)) + # wolfBoot's copies are built with -mcmse, which selects secure bases + APP_OBJS+=$(addprefix lpc55s69_ns/,$(notdir $(LPC55S69_SDK_SRCS:.c=.o))) + vpath %.c $(sort $(dir $(LPC55S69_SDK_SRCS))) else LSCRIPT_TEMPLATE=ARM-lpc55s69.ld + APP_OBJS+=$(LPC55S69_SDK_SRCS:.c=.o) endif - APP_OBJS+=\ - $(MCUXPRESSO_PROJECT_TEMPLATE)/clock_config.o \ - $(MCUXPRESSO_DRIVERS)/drivers/fsl_clock.o \ - $(MCUXPRESSO_DRIVERS)/drivers/fsl_reset.o \ - $(MCUXPRESSO)/drivers/common/fsl_common_arm.o \ - $(MCUXPRESSO)/drivers/flexcomm/fsl_flexcomm.o \ - $(MCUXPRESSO)/drivers/flexcomm/usart/fsl_usart.o \ - $(MCUXPRESSO)/drivers/iap1/fsl_iap.o \ - $(MCUXPRESSO)/drivers/lpc_gpio/fsl_gpio.o \ - $(MCUXPRESSO)/drivers/rng_1/fsl_rng.o ifeq ($(PKA),1) CFLAGS+=-DWOLFSSL_NXP_LPC55S6X APP_OBJS+=\ @@ -1735,6 +1739,11 @@ rw612_ns/%.o: %.c $(Q)mkdir -p $(dir $@) $(Q)$(CC) $(CFLAGS) -c $(OUTPUT_FLAG) $@ $< +lpc55s69_ns/%.o: %.c + @echo "\t[CC-$(ARCH)] $@" + $(Q)mkdir -p $(dir $@) + $(Q)$(CC) $(CFLAGS) -c $(OUTPUT_FLAG) $@ $< + board_$(BOARD).o: ../hal/boards/$(BOARD)/wolfHAL_board.c @echo "\t[CC-$(ARCH)] $@" $(Q)$(CC) $(CFLAGS) -DDEBUG_UART -c $(OUTPUT_FLAG) $@ $< @@ -1792,7 +1801,7 @@ wolfip_tftp_test.o: wolfip_tftp_test.c clean: $(Q)rm -f *.bin *.elf tags *.o $(LSCRIPT) $(APP_OBJS) wcs/*.o $(Q)rm -rf $(WOLFSSL_LOCAL_OBJDIR) $(WOLFTPM_LOCAL_OBJDIR) \ - $(WOLFHSM_LOCAL_OBJDIR) $(WOLFIP_LOCAL_OBJDIR) mcxw_ns + $(WOLFHSM_LOCAL_OBJDIR) $(WOLFIP_LOCAL_OBJDIR) mcxw_ns lpc55s69_ns $(LSCRIPT): $(LSCRIPT_TEMPLATE) FORCE $(Q)printf "%d" $(WOLFBOOT_PARTITION_BOOT_ADDRESS) > .wolfboot-offset diff --git a/test-app/app_lpc55s69.c b/test-app/app_lpc55s69.c index 1901134b6d..11f5a9c663 100644 --- a/test-app/app_lpc55s69.c +++ b/test-app/app_lpc55s69.c @@ -20,11 +20,14 @@ */ #include +#include "fsl_clock.h" #include "fsl_gpio.h" +#include "fsl_iocon.h" #include "target.h" #include "wolfboot/wolfboot.h" #include "printf.h" +#include "hal/lpc55s69.h" /* wolfCrypt test/benchmark support */ #ifdef WOLFCRYPT_TEST @@ -39,15 +42,23 @@ int wolfcrypt_test(void *args); int benchmark_test(void *args); #endif +#ifdef WOLFCRYPT_SECURE_MODE +#include "wolfboot/wc_secure.h" +#endif + #ifdef WOLFCRYPT_TZ_PSA #include "psa/crypto.h" #include "psa/error.h" #include "psa/initial_attestation.h" #endif -#define RED_LED 6 -#define GREEN_LED 7 -#define BLUE_LED 4 +#ifdef WOLFBOOT_PKCS11_TESTAPP +#include "test_pkcs11.h" +#endif + +#define RED_LED LPC55S69_LED_RED_PIN +#define GREEN_LED LPC55S69_LED_GREEN_PIN +#define BLUE_LED LPC55S69_LED_BLUE_PIN extern void hal_init(void); @@ -68,23 +79,48 @@ static void systick_init(void) static void leds_init(void) { - /* red output off */ - GPIO->SET[1] = 1UL << RED_LED; - GPIO->DIR[1] |= 1UL << RED_LED; - /* green output off */ - GPIO->SET[1] = 1UL << GREEN_LED; - GPIO->DIR[1] |= 1UL << GREEN_LED; - /* blue output off */ - GPIO->SET[1] = 1UL << BLUE_LED; - GPIO->DIR[1] |= 1UL << BLUE_LED; + const uint32_t led_mux = IOCON_FUNC0 | IOCON_MODE_PULLUP | IOCON_DIGITAL_EN; + +#ifndef TZEN + /* With TZEN the secure side owns the pin mux and sets it before boot */ + CLOCK_EnableClock(kCLOCK_Iocon); + CLOCK_EnableClock(kCLOCK_Gpio1); + IOCON_PinMuxSet(IOCON, LPC55S69_LED_PORT, RED_LED, led_mux); + IOCON_PinMuxSet(IOCON, LPC55S69_LED_PORT, GREEN_LED, led_mux); + IOCON_PinMuxSet(IOCON, LPC55S69_LED_PORT, BLUE_LED, led_mux); +#else + (void)led_mux; +#endif + + /* All three off (active low), then outputs */ + GPIO->SET[LPC55S69_LED_PORT] = (1UL << RED_LED) | (1UL << GREEN_LED) | + (1UL << BLUE_LED); + GPIO->DIR[LPC55S69_LED_PORT] |= (1UL << RED_LED) | (1UL << GREEN_LED) | + (1UL << BLUE_LED); } +#ifdef WOLFCRYPT_SECURE_MODE +static void print_random_number(void) +{ + uint8_t rnd; + int ret; + + ret = wcs_get_random(&rnd, sizeof(rnd)); + if (ret != 0) { + wolfBoot_printf("Random number: generate failed (%d)\n", ret); + } + else { + wolfBoot_printf("Today's lucky number: 0x%02x\n", rnd); + } +} +#endif + static void check_parts( uint32_t *pboot_ver, uint32_t *pupdate_ver, uint8_t *pboot_state, uint8_t *pupdate_state ) { -#ifdef WOLFCRYPT_SECURE_MODE +#ifdef TZEN *pboot_ver = wolfBoot_nsc_current_firmware_version(); *pupdate_ver = wolfBoot_nsc_update_firmware_version(); if (wolfBoot_nsc_get_partition_state(PART_BOOT, pboot_state) != 0) @@ -172,6 +208,9 @@ void main(void) { uint32_t boot_ver, update_ver; uint8_t boot_state, update_state; +#ifdef WOLFBOOT_PKCS11_TESTAPP + int ret; +#endif hal_init(); leds_init(); @@ -185,6 +224,9 @@ void main(void) wolfBoot_printf("Copyright 2026 wolfSSL Inc\n"); wolfBoot_printf("==================================\n"); +#ifdef WOLFCRYPT_SECURE_MODE + print_random_number(); +#endif check_parts(&boot_ver, &update_ver, &boot_state, &update_state); if ( @@ -192,7 +234,7 @@ void main(void) (boot_state == IMG_STATE_TESTING || boot_state == IMG_STATE_NEW) ) { -#ifdef WOLFCRYPT_SECURE_MODE +#ifdef TZEN wolfBoot_printf("Calling wolfBoot_nsc_success()\n"); wolfBoot_nsc_success(); #else @@ -205,12 +247,12 @@ void main(void) if (boot_ver == 1) { /* blue on */ - GPIO_PinWrite(GPIO, 1, BLUE_LED, 0); + GPIO_PinWrite(GPIO, LPC55S69_LED_PORT, BLUE_LED, 0); if (update_ver != 0) { wolfBoot_printf("Update detected, version: 0x%lx\n", update_ver); wolfBoot_printf("Triggering update...\n"); -#ifdef WOLFCRYPT_SECURE_MODE +#ifdef TZEN wolfBoot_nsc_update_trigger(); #else wolfBoot_update_trigger(); @@ -221,7 +263,7 @@ void main(void) } else { /* green on */ - GPIO_PinWrite(GPIO, 1, GREEN_LED, 0); + GPIO_PinWrite(GPIO, LPC55S69_LED_PORT, GREEN_LED, 0); } #if defined(WOLFBOOT_ATTESTATION_TEST) && defined(WOLFCRYPT_TZ_PSA) @@ -235,6 +277,19 @@ void main(void) } #endif +#ifdef WOLFBOOT_PKCS11_TESTAPP + ret = test_pkcs11_start(); + if (ret == PKCS11_TEST_FIRST_BOOT_OK) { + __asm__ volatile ("bkpt #0x7d"); + } + else if (ret == PKCS11_TEST_SECOND_BOOT_OK) { + __asm__ volatile ("bkpt #0x7f"); + } + else { + __asm__ volatile ("bkpt #0x7e"); + } +#endif + #if defined(WOLFCRYPT_TEST) || defined(WOLFCRYPT_BENCHMARK) wolfCrypt_Init(); @@ -299,7 +354,11 @@ int WEAKFUNCTION _read(int file, char *ptr, int len) int WEAKFUNCTION _write(int file, char *ptr, int len) { (void)file; +#ifdef DEBUG_UART + uart_write(ptr, (unsigned int)len); +#else (void)ptr; +#endif return len; } diff --git a/test-app/emu-test-apps/Makefile b/test-app/emu-test-apps/Makefile index f70d868502..8c32b8ddd6 100644 --- a/test-app/emu-test-apps/Makefile +++ b/test-app/emu-test-apps/Makefile @@ -85,13 +85,16 @@ else ifeq ($(TARGET),m2354) else ifeq ($(TARGET),imx_rt7xx) EMU_DIR=imxrt700 EMU_CPU=imxrt700 +else ifeq ($(TARGET),lpc55s69) + EMU_DIR=lpc55s69 + EMU_CPU=lpc55s69 else EMU_DIR= EMU_CPU= endif ifeq ($(strip $(EMU_DIR)),) - $(error Unsupported or unset TARGET=$(TARGET). Use TARGET=stm32h563|stm32h5|stm32u585|stm32u5|stm32l552|stm32l5|nrf5340|mcxw|m2354|imx_rt7xx) + $(error Unsupported or unset TARGET=$(TARGET). Use TARGET=stm32h563|stm32h5|stm32u585|stm32u5|stm32l552|stm32l5|nrf5340|mcxw|m2354|imx_rt7xx|lpc55s69) endif EMU_PATH=$(WOLFBOOT_ROOT)/test-app/emu-test-apps/$(EMU_DIR) diff --git a/test-app/emu-test-apps/common/emu_hal.c b/test-app/emu-test-apps/common/emu_hal.c index 9db01a921b..81f30165d1 100644 --- a/test-app/emu-test-apps/common/emu_hal.c +++ b/test-app/emu-test-apps/common/emu_hal.c @@ -196,6 +196,105 @@ static int imxrt700_flash_erase(uint32_t address, int len) } #endif +#if defined(EMU_LPC55S69) +/* LPC55S69 flash changes only through the boot ROM flash API (iap1 version 1 + * driver), reached with TZEN=0 only. An erased page that was never programmed + * faults on read (ECC), so erase programs 0xFF back, as the wolfBoot HAL does. */ +#define LPC55_ROM_API_TREE 0x130010F0u +#define LPC55_FLASH_PAGE_SIZE 512u +#define LPC55_FLASH_ERASE_KEY 0x6B65666Cu +#define LPC55_FLASH_CFG_WORDS 15u +#define LPC55_FLASH_CFG_FREQ 10u +#define LPC55_FLASH_FREQ_MHZ 96u + +typedef struct { + uint32_t version; + int (*init)(uint32_t *config); + int (*erase)(uint32_t *config, uint32_t start, uint32_t len, uint32_t key); + int (*program)(uint32_t *config, uint32_t start, const uint8_t *src, + uint32_t len); +} lpc55_flash_api_t; + +typedef struct { + void (*run_bootloader)(void *arg); + uint32_t version; + const char *copyright; + uint32_t reserved; + const lpc55_flash_api_t *flash; +} lpc55_rom_tree_t; + +static uint32_t lpc55_flash_cfg[LPC55_FLASH_CFG_WORDS]; +static uint8_t lpc55_page[LPC55_FLASH_PAGE_SIZE]; +static const lpc55_flash_api_t *lpc55_flash; + +static int lpc55s69_flash_init(void) +{ + if (lpc55_flash == 0) { + lpc55_flash = ((const lpc55_rom_tree_t *)LPC55_ROM_API_TREE)->flash; + lpc55_flash_cfg[LPC55_FLASH_CFG_FREQ] = LPC55_FLASH_FREQ_MHZ; + if (lpc55_flash->init(lpc55_flash_cfg) != 0) { + lpc55_flash = 0; + return -1; + } + } + return 0; +} + +static int lpc55s69_page_write(uint32_t page, const uint8_t *buf) +{ + if (lpc55_flash->erase(lpc55_flash_cfg, page, LPC55_FLASH_PAGE_SIZE, + LPC55_FLASH_ERASE_KEY) != 0) { + return -1; + } + if (lpc55_flash->program(lpc55_flash_cfg, page, buf, + LPC55_FLASH_PAGE_SIZE) != 0) { + return -1; + } + return 0; +} + +static int lpc55s69_flash_write(uint32_t address, const uint8_t *data, int len) +{ + uint32_t page; + uint32_t off; + uint32_t chunk; + int ret; + + ret = lpc55s69_flash_init(); + while ((ret == 0) && (len > 0)) { + page = address & ~(LPC55_FLASH_PAGE_SIZE - 1u); + off = address - page; + chunk = LPC55_FLASH_PAGE_SIZE - off; + if (chunk > (uint32_t)len) { + chunk = (uint32_t)len; + } + memcpy(lpc55_page, (const void *)page, LPC55_FLASH_PAGE_SIZE); + memcpy(lpc55_page + off, data, chunk); + ret = lpc55s69_page_write(page, lpc55_page); + address += chunk; + data += chunk; + len -= (int)chunk; + } + return ret; +} + +static int lpc55s69_flash_erase(uint32_t address, int len) +{ + uint32_t p; + uint32_t end; + int ret; + + ret = lpc55s69_flash_init(); + memset(lpc55_page, 0xFF, LPC55_FLASH_PAGE_SIZE); + end = address + (uint32_t)len; + for (p = address & ~(LPC55_FLASH_PAGE_SIZE - 1u); (ret == 0) && (p < end); + p += LPC55_FLASH_PAGE_SIZE) { + ret = lpc55s69_page_write(p, lpc55_page); + } + return ret; +} +#endif + int hal_flash_write(uint32_t address, const uint8_t *data, int len) { if (data == 0 || len <= 0) { @@ -222,6 +321,8 @@ int hal_flash_write(uint32_t address, const uint8_t *data, int len) } #elif defined(EMU_IMXRT700) return imxrt700_flash_write(address, data, len); +#elif defined(EMU_LPC55S69) + return lpc55s69_flash_write(address, data, len); #else memcpy((void *)address, data, (size_t)len); #endif @@ -253,6 +354,11 @@ int hal_flash_erase(uint32_t address, int len) return 0; } return imxrt700_flash_erase(address, len); +#elif defined(EMU_LPC55S69) + if (len <= 0) { + return 0; + } + return lpc55s69_flash_erase(address, len); #else uint32_t end; #if defined(EMU_STM32) diff --git a/test-app/emu-test-apps/lpc55s69/Makefile b/test-app/emu-test-apps/lpc55s69/Makefile new file mode 100644 index 0000000000..d49754adbc --- /dev/null +++ b/test-app/emu-test-apps/lpc55s69/Makefile @@ -0,0 +1,60 @@ +# Makefile +# +# Copyright (C) 2026 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 + +CC=arm-none-eabi-gcc +OBJCOPY ?= arm-none-eabi-objcopy + +CFLAGS := -mcpu=cortex-m33 -mthumb -mcmse -Os -ffreestanding -fdata-sections -ffunction-sections -g -ggdb +CFLAGS += -I. -I../common -I../../../include -DEMU_LPC55S69 +CFLAGS += -DIMAGE_HEADER_SIZE=$(IMAGE_HEADER_SIZE) +LDFLAGS := -nostdlib -T target.ld -Wl,-gc-sections +LDLIBS := -Wl,--start-group -lc -lm -lgcc -lnosys -Wl,--end-group +NSC_OBJ := +ifeq ($(TZEN),1) + CFLAGS += -DTZEN + CFLAGS += -DNONSECURE_APP + NSC_OBJ := ../../../src/wolfboot_tz_nsc.o +endif + +VPATH := ../common + +COMMON_SRCS := emu_ivt.c emu_startup.c emu_syscalls.c emu_update.c emu_hal.c +LIBWOLFBOOT_SRC := ../../../src/libwolfboot.c +SRCS := $(COMMON_SRCS) uart.c +OBJS := $(SRCS:.c=.o) libwolfboot.o $(NSC_OBJ) + +all: app.bin + +app.elf: $(OBJS) target.ld + $(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) $(LDLIBS) -o $@ + +app.bin: app.elf + $(OBJCOPY) -O binary $< $@ + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +libwolfboot.o: $(LIBWOLFBOOT_SRC) + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f $(SRCS:.c=.o) libwolfboot.o app.elf app.bin + +.PHONY: all clean diff --git a/test-app/emu-test-apps/lpc55s69/target.ld.in b/test-app/emu-test-apps/lpc55s69/target.ld.in new file mode 100644 index 0000000000..2c4462ae83 --- /dev/null +++ b/test-app/emu-test-apps/lpc55s69/target.ld.in @@ -0,0 +1,87 @@ +/* target.ld.in + * + * Copyright (C) 2026 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 + */ + +/* Minimal linker script for the LPC55S69 emulator test app. RAM is SRAM2, the + * only SRAM a TZEN=1 wolfBoot leaves non-secure. @FLASH_ORIGIN@ is the boot + * partition plus IMAGE_HEADER_SIZE, @FLASH_SIZE@ the partition minus it. */ +MEMORY +{ + FLASH (rx) : ORIGIN = @FLASH_ORIGIN@, LENGTH = @FLASH_SIZE@ + RAM (rwx) : ORIGIN = 0x20020000, LENGTH = 0x00010000 +} + +_estack = ORIGIN(RAM) + LENGTH(RAM); +_sidata = LOADADDR(.data); + +SECTIONS +{ + .isr_vector : + { + KEEP(*(.isr_vector)) + } > FLASH + + .text : + { + *(.text*) + *(.rodata*) + *(.ARM.extab* .gnu.linkonce.armextab.*) + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + *(.glue_7) + *(.glue_7t) + *(.eh_frame) + } > FLASH + + .preinit_array : + { + __preinit_array_start = .; + KEEP(*(.preinit_array*)) + __preinit_array_end = .; + } > FLASH + + .init_array : + { + __init_array_start = .; + KEEP(*(.init_array*)) + __init_array_end = .; + } > FLASH + + .fini_array : + { + __fini_array_start = .; + KEEP(*(.fini_array*)) + __fini_array_end = .; + } > FLASH + + .data : + { + _sdata = .; + *(.data*) + _edata = .; + } > RAM AT > FLASH + + .bss (NOLOAD) : + { + _sbss = .; + *(.bss*) + *(COMMON) + _ebss = .; + } > RAM +} diff --git a/test-app/emu-test-apps/lpc55s69/uart.c b/test-app/emu-test-apps/lpc55s69/uart.c new file mode 100644 index 0000000000..79aaf9644b --- /dev/null +++ b/test-app/emu-test-apps/lpc55s69/uart.c @@ -0,0 +1,61 @@ +/* uart.c + * + * Copyright (C) 2026 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 "emu_app.h" + +/* FLEXCOMM0 USART, non-secure alias. wolfBoot (DEBUG_UART) leaves it clocked, + * pinned out and running at 115200 with both FIFOs on; with TZEN=1 the clock + * and pin mux stay secure, so the app only touches the FIFO registers. */ +#define FC0_BASE 0x40086000u +#define FC0_FIFOSTAT (*(volatile uint32_t *)(FC0_BASE + 0xE04u)) +#define FC0_FIFOWR (*(volatile uint32_t *)(FC0_BASE + 0xE20u)) +#define FC0_FIFORD (*(volatile uint32_t *)(FC0_BASE + 0xE30u)) + +#define FIFOSTAT_TXNOTFULL 0x00000020u +#define FIFOSTAT_RXNOTEMPTY 0x00000040u + +#define UART_TX_POLL_LIMIT 100000u + +void emu_uart_init(void) +{ +} + +void emu_uart_write(uint8_t c) +{ + uint32_t t = UART_TX_POLL_LIMIT; + + while (((FC0_FIFOSTAT & FIFOSTAT_TXNOTFULL) == 0u) && (t > 0u)) { + t--; + } + if (t != 0u) { + FC0_FIFOWR = (uint32_t)c; + } +} + +int emu_uart_read(uint8_t *c) +{ + if ((FC0_FIFOSTAT & FIFOSTAT_RXNOTEMPTY) == 0u) { + return 0; + } + *c = (uint8_t)(FC0_FIFORD & 0xFFu); + return 1; +} diff --git a/test-app/emu-test-apps/test.sh b/test-app/emu-test-apps/test.sh index 6322768299..afb85b010b 100755 --- a/test-app/emu-test-apps/test.sh +++ b/test-app/emu-test-apps/test.sh @@ -67,6 +67,7 @@ get_m33mu_target() { mcxw|mcxw71) echo "mcxw71c" ;; m2354) echo "m2354" ;; imx_rt7xx) echo "imxrt700" ;; + lpc55s69) echo "lpc55s69" ;; *) echo "" ;; esac } @@ -79,6 +80,7 @@ case "$TARGET" in mcxw|mcxw71) EMU_DIR=mcxw71; UART_BASE=40038000 ;; m2354) EMU_DIR=m2354; UART_BASE=40070000 ;; imx_rt7xx) EMU_DIR=imxrt700; UART_BASE=40110000 ;; + lpc55s69) EMU_DIR=lpc55s69; UART_BASE=40086000 ;; *) die "unsupported TARGET=$TARGET" ;; esac @@ -228,6 +230,7 @@ write_target_ld() { mcxw|mcxw71) base="ARM-mcxw" ;; m2354) base="ARM-m2354" ;; imx_rt7xx) base="ARM-imx_rt7xx" ;; + lpc55s69) base="ARM-lpc55s69" ;; *) die "unsupported TARGET for linker template: $TARGET" ;; esac @@ -583,12 +586,52 @@ if want_scenario "C"; then log "Scenario C: fallback ok (version=1)" fi -if want_scenario "S"; then - if [[ "$RAM_CODE" == "1" ]]; then - log "Scenario S: self-update enabled (RAM_CODE=1)" - log "Scenario S: not enabled in this script yet" - else +if [[ -n "$SCENARIOS_UP" ]] && want_scenario "S"; then + if [[ "$RAM_CODE" != "1" ]]; then log "Scenario S: RAM_CODE disabled, skipping" + elif [[ "${TZEN}" == "1" ]]; then + log "Scenario S: TZEN=1, skipping" + else + log "Scenario S: bootloader self-update from v1 to v2 (RAM_CODE=1)" + SELF_UPDATE_BIN="$EMU_PATH/wolfboot.bin" + SELF_UPDATE_SIGNED="$EMU_PATH/wolfboot_v2_signed.bin" + SELF_UPDATE_PART="$EMU_PATH/self_update_part.dd" + SIGN_ALG="$(cfg_get SIGN)" + HASH_ALG="$(cfg_get HASH)" + + log "Scenario S: build wolfBoot v2 and sign it as a wolfBoot update" + make -C "$WOLFBOOT_ROOT" "${WB_MAKE_ARGS[@]}" clean wolfboot.bin WOLFBOOT_VERSION=2 + cp "$WOLFBOOT_BIN" "$SELF_UPDATE_BIN" + IMAGE_HEADER_SIZE="$IMAGE_HEADER_SIZE" "$WOLFBOOT_ROOT/tools/keytools/sign" \ + --"$(echo "${SIGN_ALG:-ED25519}" | tr '[:upper:]' '[:lower:]')" \ + --"$(echo "${HASH_ALG:-SHA256}" | tr '[:upper:]' '[:lower:]')" \ + --wolfboot-update "$SELF_UPDATE_BIN" "$WOLFBOOT_ROOT/wolfboot_signing_private_key.der" 2 + + log "Scenario S: build wolfBoot v1 for the factory image" + make -C "$WOLFBOOT_ROOT" "${WB_MAKE_ARGS[@]}" clean wolfboot.bin WOLFBOOT_VERSION=1 + + log "Scenario S: assembling factory.bin from wolfBoot v1 + image_v8_signed.bin + pBOOT-triggered wolfBoot v2" + dd if=/dev/zero bs="$((PART_SIZE))" count=1 2>/dev/null | tr '\000' '\377' > "$SELF_UPDATE_PART" + dd if="$SELF_UPDATE_SIGNED" of="$SELF_UPDATE_PART" bs=1 conv=notrunc 2>/dev/null + printf "pBOOT" | dd of="$SELF_UPDATE_PART" bs=1 seek="$((PART_SIZE - 5))" conv=notrunc 2>/dev/null + "$BIN_ASSEMBLE" "$FACTORY_FLASH" \ + 0 "$WOLFBOOT_BIN" \ + "$BOOT_OFFSET_HEX" "$UPDATE_IMAGE_V8" \ + "$UPDATE_OFFSET_HEX" "$SELF_UPDATE_PART" >/dev/null + print_partition_flags "Scenario S: flags before self-update" + + run_log="$EMU_PATH/self_update.log" + log "Scenario S: boot wolfBoot v1, expect self-update, reset and BKPT 0x4E from image v8 (success)" + $STDBUF "$M33MU" --cpu "$EMU_CPU" --uart-stdout "${M33MU_TZ_ARGS[@]}" \ + --timeout "$UPDATE_TIMEOUT" --expect-bkpt=0x4e --persist --quit-on-faults \ + "$FACTORY_FLASH" >"$run_log" 2>&1 || die "self-update run: m33mu failed" + grep -q "\\[EXPECT BKPT\\] Success" "$run_log" || die "self-update run: expected BKPT 0x4E" + log "Scenario S: BKPT 0x4E hit" + print_partition_flags "Scenario S: flags after self-update" + + cmp -n "$(wc -c < "$SELF_UPDATE_BIN")" "$SELF_UPDATE_BIN" "$FACTORY_FLASH" >/dev/null || \ + die "self-update run: bootloader region does not match wolfBoot v2" + log "Scenario S: bootloader region matches wolfBoot v2" fi fi diff --git a/test-app/test_pkcs11.c b/test-app/test_pkcs11.c index b3bb295fd8..b5be949615 100644 --- a/test-app/test_pkcs11.c +++ b/test-app/test_pkcs11.c @@ -205,14 +205,17 @@ static int test_pkcs11_provision_token(void) CK_SESSION_HANDLE session = CK_INVALID_HANDLE; Pkcs11Token token; Pkcs11Dev dev; + unsigned char token_pin[sizeof(test_user_pin)]; printf("pkcs11: provisioning token\r\n"); dev.heap = NULL; dev.func = (CK_FUNCTION_LIST *)&wolfpkcs11nsFunctionList; + /* wc_Pkcs11Token_Final zeroes the PIN, which must not be the flash copy */ + XMEMCPY(token_pin, test_user_pin, sizeof(token_pin)); ret = wc_Pkcs11Token_Init(&token, &dev, (int)TEST_PKCS11_SLOT_ID, - test_token_name, test_user_pin, (int)(sizeof(test_user_pin) - 1)); + test_token_name, token_pin, (int)(sizeof(token_pin) - 1)); if (ret != 0) { printf("pkcs11: wc_Pkcs11Token_Init ret=%d\r\n", ret); return -1; diff --git a/tools/ci/fetch-mcuxsdk-headers.sh b/tools/ci/fetch-mcuxsdk-headers.sh index 32877b930f..ed7776d5ab 100755 --- a/tools/ci/fetch-mcuxsdk-headers.sh +++ b/tools/ci/fetch-mcuxsdk-headers.sh @@ -28,7 +28,9 @@ fi fetch "" mcuxsdk-core c6f4223f45fdab59509f25ee3ea63a71bd29d8aa \ /drivers/common/ /drivers/trng/ /drivers/mcx_spc/ /drivers/flexcomm/ \ - /drivers/port/ /drivers/lpuart/ /drivers/tstmr/ /drivers/elemu/ + /drivers/port/ /drivers/lpuart/ /drivers/tstmr/ /drivers/elemu/ \ + /drivers/iap1/ /drivers/rng_1/ /drivers/lpc_iocon/ /drivers/lpc_gpio/ \ + /drivers/puf/ fetch devices/Wireless mcux-devices-wireless \ 0d2c97770475558c72f5b6947f468bf97282df79 /RW/periph/ /RW/RW612/ fetch devices/MCX mcux-devices-mcx \ @@ -36,3 +38,7 @@ fetch devices/MCX mcux-devices-mcx \ /MCXW/periph2/ /MCXW/MCXW716C/ fetch middleware/secure-subsystem mcux-secure-subsystem \ 1cce4ff3093af3c1e79cf70891e96d13e3416b58 /inc/ /src/sscp/ +fetch devices/LPC mcux-devices-lpc \ + 3219d258961967797d435d874d1ca01b474aa390 /LPC5500/periph/ /LPC5500/LPC55S69/ +fetch examples mcuxsdk-examples 3151bc057b8924db54c7395d39b213c05ab94dc3 \ + /_boards/lpcxpresso55s69/project_template/ diff --git a/tools/scripts/eat_ueid.py b/tools/scripts/eat_ueid.py new file mode 100644 index 0000000000..2cca5299ac --- /dev/null +++ b/tools/scripts/eat_ueid.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# Print the UEID claim (EAT key 256, 33-byte bstr) from a test-app console log +# that dumps the attestation token as hex after "attest: token size". +import re +import sys + +UEID_CLAIM = bytes.fromhex("1901005821") +UEID_LEN = 33 + + +def main(): + if len(sys.argv) != 2: + sys.exit("usage: eat_ueid.py ") + data = bytearray() + seen = False + with open(sys.argv[1], errors="replace") as log: + for line in log: + if "attest: token size" in line: + seen = True + continue + if seen and line.startswith("\t"): + data += bytes.fromhex(" ".join( + re.findall(r"\b[0-9a-fA-F]{2}\b", line.split("|")[0]))) + i = data.find(UEID_CLAIM) + if i < 0: + sys.exit("UEID claim not found in " + sys.argv[1]) + start = i + len(UEID_CLAIM) + ueid = data[start:start + UEID_LEN] + if len(ueid) != UEID_LEN: + sys.exit("UEID claim truncated in " + sys.argv[1]) + print(ueid.hex()) + + +if __name__ == "__main__": + main() diff --git a/tools/scripts/lpc55s69/lpc55s69_debug.cmm b/tools/scripts/lpc55s69/lpc55s69_debug.cmm index 217fc53c25..f5238cc015 100644 --- a/tools/scripts/lpc55s69/lpc55s69_debug.cmm +++ b/tools/scripts/lpc55s69/lpc55s69_debug.cmm @@ -1,6 +1,7 @@ ;; lpc55s69_debug.cmm -&basedir="\\wsl.localhost\Ubuntu-22.04\home\tcook\wolf\wolfBoot" +; wolfBoot root: three levels above this script +&basedir=OS.PPD()+"\..\..\.." RESet SYStem.RESet @@ -22,8 +23,6 @@ SYStem.Up ;; STOP in wolfBoot... data.load "&basedir\wolfboot.elf" /nocode -symbol.sourcepath.TranslateSUBpath "\home\tcook\wolf\wolfBoot" "&basedir" -symbol.sourcepath.TranslateSUBpath "\home\tcook\wolf\NXP" "&basedir\..\NXP" Register.Set PC isr_reset Register.Set MSP END_STACK break.set main /onchip /temporary diff --git a/tools/scripts/lpc55s69/lpc55s69_flash_factory_bin.cmm b/tools/scripts/lpc55s69/lpc55s69_flash_factory_bin.cmm index e64a3550b1..9690273fd9 100644 --- a/tools/scripts/lpc55s69/lpc55s69_flash_factory_bin.cmm +++ b/tools/scripts/lpc55s69/lpc55s69_flash_factory_bin.cmm @@ -1,6 +1,7 @@ ;; lpc55s69_flash_factory_bin.cmm -&basedir="\\wsl.localhost\Ubuntu-22.04\home\tcook\wolf\wolfBoot" +; wolfBoot root: three levels above this script +&basedir=OS.PPD()+"\..\..\.." RESet SYStem.RESet diff --git a/tools/scripts/lpc55s69/lpc55s69_flash_update.cmm b/tools/scripts/lpc55s69/lpc55s69_flash_update.cmm index e358ac814d..8af02fe046 100644 --- a/tools/scripts/lpc55s69/lpc55s69_flash_update.cmm +++ b/tools/scripts/lpc55s69/lpc55s69_flash_update.cmm @@ -1,6 +1,14 @@ ;; lpc55s69_flash_update.cmm +;; usage: DO lpc55s69_flash_update.cmm [update partition address] -&basedir="\\wsl.localhost\Ubuntu-22.04\home\tcook\wolf\wolfBoot" +PARAMETERS &update_addr +IF "&update_addr"=="" +( + &update_addr=0x40000 +) + +; wolfBoot root: three levels above this script +&basedir=OS.PPD()+"\..\..\.." RESet SYStem.RESet @@ -25,7 +33,7 @@ flash.delete 1. flash.create 1. 0x00000000--0x00097fff 0x200 TARGET Long FLASH.ReProgram ALL -Data.LOAD.Binary "&basedir\test-app\image_v2_signed.bin" 0x18000 +Data.LOAD.Binary "&basedir\test-app\image_v2_signed.bin" &update_addr FLASH.ReProgram OFF ENDDO diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 934e5b8a7d..4aa5df8a91 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -223,6 +223,7 @@ MCUXSDK?= MCXA_SDK=$(MCUXSDK)/devices/MCX/MCXA/MCXA153 RW612_SDK=$(MCUXSDK)/devices/Wireless/RW/RW612 MCXW_SDK=$(MCUXSDK)/devices/MCX/MCXW/MCXW716C +LPC55S69_SDK=$(MCUXSDK)/devices/LPC/LPC5500/LPC55S69 ifneq ($(wildcard $(MCXA_SDK)/MCXA153.h),) TESTS+=unit-flash-write-mcxa else @@ -241,6 +242,14 @@ TESTS+=unit-mcxw-tz else $(info Skipping MCX W unit tests: set MCUXSDK (see tools/ci/fetch-mcuxsdk-headers.sh)) endif +ifneq ($(wildcard $(LPC55S69_SDK)/LPC55S69_cm33_core0.h),) +TESTS+=unit-lpc55s69-flash +TESTS+=unit-lpc55s69-tz +TESTS+=unit-lpc55s69-uart +TESTS+=unit-lpc55s69-puf +else +$(info Skipping LPC55S69 unit tests: set MCUXSDK (see tools/ci/fetch-mcuxsdk-headers.sh)) +endif TESTS+=unit-flash-write-nrf52 TESTS+=unit-nrf5340-uart-crlf TESTS+=unit-flash-write-samr21 @@ -2243,6 +2252,28 @@ unit-mcxw-flash: unit-mcxw-flash.c ../../hal/mcxw.c ../../hal/mcxw.h unit-mcxw-tz: unit-mcxw-tz.c unit-mmio.h ../../hal/mcxw.c ../../hal/mcxw.h gcc -o $@ unit-mcxw-tz.c $(MCXW_TEST_INC) -I../.. $(CFLAGS) $(LDFLAGS) +LPC55S69_TEST_INC=-DCPU_LPC55S69JBD100_cm33_core0 $(MCUXSDK_TEST_INC) \ + -isystem $(LPC55S69_SDK) -isystem $(LPC55S69_SDK)/../periph \ + -isystem $(LPC55S69_SDK)/drivers -isystem $(MCUXSDK)/drivers/iap1 \ + -isystem $(MCUXSDK)/drivers/rng_1 -isystem $(MCUXSDK)/drivers/lpc_iocon \ + -isystem $(MCUXSDK)/drivers/lpc_gpio -isystem $(MCUXSDK)/drivers/puf \ + -isystem $(MCUXSDK)/drivers/flexcomm \ + -isystem $(MCUXSDK)/drivers/flexcomm/usart \ + -isystem $(MCUXSDK)/examples/_boards/lpcxpresso55s69/project_template + +unit-lpc55s69-flash: unit-lpc55s69-flash.c ../../hal/lpc55s69.c ../../hal/lpc55s69.h + gcc -o $@ unit-lpc55s69-flash.c $(LPC55S69_TEST_INC) -I../.. $(CFLAGS) $(LDFLAGS) + +unit-lpc55s69-tz: unit-lpc55s69-tz.c ../../hal/lpc55s69.c ../../hal/lpc55s69.h + gcc -o $@ unit-lpc55s69-tz.c $(LPC55S69_TEST_INC) -I../.. $(CFLAGS) $(LDFLAGS) + +unit-lpc55s69-uart: unit-lpc55s69-uart.c ../../hal/lpc55s69.c ../../hal/lpc55s69.h + gcc -o $@ unit-lpc55s69-uart.c -DDEBUG_UART $(LPC55S69_TEST_INC) -I../.. \ + $(CFLAGS) $(LDFLAGS) + +unit-lpc55s69-puf: unit-lpc55s69-puf.c ../../hal/lpc55s69.c ../../hal/lpc55s69.h + gcc -o $@ unit-lpc55s69-puf.c $(LPC55S69_TEST_INC) -I../.. $(CFLAGS) $(LDFLAGS) + unit-flash-write-nrf52: unit-flash-write-nrf52.c ../../hal/nrf52.c gcc -o $@ unit-flash-write-nrf52.c -DTARGET_nrf52 -I../../hal $(CFLAGS) $(LDFLAGS) @@ -2327,7 +2358,8 @@ GENERATED_SRC:=aurix_erased_extract.h \ # lost gnu-efi/multilib/the MCUXpresso SDK keeps a stale binary forever. CONDITIONAL_TESTS:=unit-efi-x86-open-image unit-linux-loader-e820 \ unit-linux-loader-syssize unit-flash-write-mcxa unit-rw612-flash \ - unit-rw612-tz unit-rw612-uart unit-mcxw-flash unit-mcxw-tz + unit-rw612-tz unit-rw612-uart unit-mcxw-flash unit-mcxw-tz \ + unit-lpc55s69-flash unit-lpc55s69-tz unit-lpc55s69-uart unit-lpc55s69-puf clean: covclean rm -f $(TESTS) $(CONDITIONAL_TESTS) $(GENERATED_SRC) *.o *.gcno *.gcda \ diff --git a/tools/unit-tests/unit-lpc55s69-flash.c b/tools/unit-tests/unit-lpc55s69-flash.c new file mode 100644 index 0000000000..eee3a18c81 --- /dev/null +++ b/tools/unit-tests/unit-lpc55s69-flash.c @@ -0,0 +1,487 @@ +/* unit-lpc55s69-flash.c + * + * Unit tests for the LPC55S69 flash driver in hal/lpc55s69.c, against a model + * of the ROM flash API with the part's 512-byte ECC pages. + * + * Copyright (C) 2026 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 +#include + +/* lpc55s69.config layout, in place of target.h */ +#define H_TARGETS_TARGET_ +#define WOLFBOOT_FIXED_PARTITIONS +#define WOLFBOOT_SECTOR_SIZE 0x200 +#define WOLFBOOT_PARTITION_SIZE 0x30000 +#define WOLFBOOT_PARTITION_BOOT_ADDRESS 0x10000 +#define WOLFBOOT_PARTITION_UPDATE_ADDRESS 0x40000 +#define WOLFBOOT_PARTITION_SWAP_ADDRESS 0x70000 +#define ARCH_FLASH_OFFSET 0x0 +/* Bootloader side of the HAL: hal_init also fills the bootloader region */ +#define __WOLFBOOT +#define WOLFBOOT_HASH_SHA256 +#define WOLFBOOT_SIGN_ECC256 + +#include "fsl_common.h" + +const uint8_t *mock_flash_ptr(uint32_t address); +#define LPC55S69_FLASH_PTR(a) mock_flash_ptr(a) + +#include "../../hal/lpc55s69.c" + +#define PAGE LPC55S69_FLASH_PAGE_SIZE +#define FLASH_PAGES (LPC55S69_FLASH_SIZE / PAGE) +#define S_ALIAS LPC55S69_FLASH_SECURE_ALIAS +#define BOOT WOLFBOOT_PARTITION_BOOT_ADDRESS +#define MOCK_MAX_OPS 64 + +uint32_t mock_primask; +int mock_barriers; +NVIC_Type mock_nvic; +SysTick_Type mock_systick; + +static uint8_t mock_flash[LPC55S69_FLASH_SIZE]; +/* 0: erased and never programmed, so a read faults on the ECC check */ +static uint8_t programmed[FLASH_PAGES]; +static status_t init_ret; +static int init_calls; +static int erase_calls; +static int program_calls; +static int ptr_calls; +static int fail_erase_at; +static int fail_program_at; +static int irq_enabled_during_rom; +static char op_kind[MOCK_MAX_OPS]; +static uint32_t op_off[MOCK_MAX_OPS]; +static int n_ops; + +static void rom_call(char kind, uint32_t start, uint32_t len) +{ + ck_assert_uint_eq(start % PAGE, 0); + ck_assert_uint_eq(len, PAGE); + ck_assert_uint_lt(start, LPC55S69_FLASH_SIZE); + if (mock_primask == 0U) + irq_enabled_during_rom = 1; + if ((kind != 'V') && (n_ops < MOCK_MAX_OPS)) { + op_kind[n_ops] = kind; + op_off[n_ops] = start; + n_ops++; + } +} + +status_t FLASH_Init(flash_config_t *config) +{ + init_calls++; + config->PFlashTotalSize = LPC55S69_FLASH_SIZE; + config->PFlashPageSize = PAGE; + return init_ret; +} + +status_t FFR_Init(flash_config_t *config) +{ + (void)config; + return kStatus_Success; +} + +status_t FLASH_Erase(flash_config_t *config, uint32_t start, + uint32_t lengthInBytes, uint32_t key) +{ + ck_assert_ptr_eq(config, &pflash); + ck_assert_uint_eq(key, kFLASH_ApiEraseKey); + rom_call('E', start, lengthInBytes); + if (++erase_calls == fail_erase_at) + return kStatus_Fail; + memset(&mock_flash[start], 0xFF, PAGE); + programmed[start / PAGE] = 0; + return kStatus_FLASH_Success; +} + +status_t FLASH_Program(flash_config_t *config, uint32_t start, + const uint8_t *src, uint32_t lengthInBytes) +{ + ck_assert_ptr_eq(config, &pflash); + rom_call('P', start, lengthInBytes); + if (++program_calls == fail_program_at) + return kStatus_Fail; + ck_assert_msg(!programmed[start / PAGE], + "page 0x%x programmed twice without an erase", start); + memcpy(&mock_flash[start], src, PAGE); + programmed[start / PAGE] = 1; + return kStatus_FLASH_Success; +} + +status_t FLASH_VerifyErase(flash_config_t *config, uint32_t start, + uint32_t lengthInBytes) +{ + ck_assert_ptr_eq(config, &pflash); + rom_call('V', start, lengthInBytes); + return programmed[start / PAGE] ? kStatus_FLASH_CommandFailure : + kStatus_FLASH_Success; +} + +const uint8_t *mock_flash_ptr(uint32_t address) +{ + uint32_t off = (address >= S_ALIAS) ? address - S_ALIAS : address; + + ptr_calls++; + ck_assert_uint_lt(off, LPC55S69_FLASH_SIZE); + ck_assert_msg(programmed[off / PAGE], + "read of erased page 0x%x faults (ECC)", off); + return &mock_flash[off]; +} + +void BOARD_BootClockFROHF96M(void) +{ +} + +int wolfCrypt_Init(void) +{ + return 0; +} + +static void put_page(uint32_t off, uint8_t fill) +{ + memset(&mock_flash[off], fill, PAGE); + programmed[off / PAGE] = 1; +} + +static void wipe_page(uint32_t off) +{ + memset(&mock_flash[off], 0xFF, PAGE); + programmed[off / PAGE] = 0; +} + +static void reset_counters(void) +{ + erase_calls = program_calls = ptr_calls = 0; + fail_erase_at = fail_program_at = 0; + irq_enabled_during_rom = 0; + n_ops = 0; +} + +static void setup(void) +{ + memset(mock_flash, 0xFF, sizeof(mock_flash)); + memset(programmed, 0, sizeof(programmed)); + init_ret = kStatus_FLASH_Success; + init_calls = 0; + flash_ready = 0; + mock_primask = 0; + reset_counters(); + hal_init(); + ck_assert_int_eq(erase_calls, 0); + reset_counters(); +} + +START_TEST(test_init_fills_pages_wolfboot_reads) +{ + uint32_t off; + + ck_assert_int_eq(flash_ready, 1); + for (off = 0; off < BOOT; off += PAGE) + ck_assert_msg(programmed[off / PAGE], "bootloader page 0x%x", off); + for (off = BOOT; off < BOOT + WOLFBOOT_PARTITION_SIZE; off += PAGE) + ck_assert_msg(programmed[off / PAGE], "boot page 0x%x", off); + for (off = WOLFBOOT_PARTITION_UPDATE_ADDRESS; + off < WOLFBOOT_PARTITION_UPDATE_ADDRESS + WOLFBOOT_PARTITION_SIZE; + off += PAGE) + ck_assert_msg(programmed[off / PAGE], "update page 0x%x", off); + ck_assert(programmed[WOLFBOOT_PARTITION_SWAP_ADDRESS / PAGE]); + for (off = 0; off < WOLFBOOT_PARTITION_SWAP_ADDRESS + PAGE; off += PAGE) + ck_assert_uint_eq(mock_flash[off], 0xFF); + /* Nothing else, such as the HWPUF record, is touched */ + ck_assert(!programmed[(WOLFBOOT_PARTITION_SWAP_ADDRESS + PAGE) / PAGE]); + ck_assert(!programmed[0x80000 / PAGE]); +} +END_TEST + +START_TEST(test_init_keeps_programmed_pages) +{ + put_page(BOOT, 0x5A); + flash_ready = 0; + hal_init(); + ck_assert_int_eq(erase_calls, 0); + ck_assert_int_eq(program_calls, 0); + ck_assert_uint_eq(mock_flash[BOOT + 7], 0x5A); +} +END_TEST + +START_TEST(test_init_fill_stops_at_failure) +{ + memset(programmed, 0, sizeof(programmed)); + fail_program_at = 1; + flash_ready = 0; + hal_init(); + ck_assert_int_eq(program_calls, 1); +} +END_TEST + +START_TEST(test_init_failure_then_retry) +{ + uint8_t b = 0x11; + + init_ret = kStatus_Fail; + flash_ready = 0; + hal_init(); + ck_assert_int_eq(flash_ready, 0); + ck_assert_int_eq(hal_flash_write(BOOT, &b, 1), -1); + ck_assert_int_eq(hal_flash_erase(BOOT, PAGE), -1); + ck_assert_int_eq(n_ops, 0); + + init_ret = kStatus_FLASH_Success; + hal_init(); + ck_assert_int_eq(flash_ready, 1); + ck_assert_int_eq(hal_flash_write(S_ALIAS + BOOT, &b, 1), 0); +} +END_TEST + +START_TEST(test_partial_write_keeps_neighbours) +{ + static const uint8_t data[16] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; + uint32_t i; + + put_page(BOOT, 0xA5); + ck_assert_int_eq(hal_flash_write(S_ALIAS + BOOT + 100, data, + sizeof(data)), 0); + for (i = 0; i < PAGE; i++) { + if (i >= 100 && i < 116) + ck_assert_uint_eq(mock_flash[BOOT + i], data[i - 100]); + else + ck_assert_uint_eq(mock_flash[BOOT + i], 0xA5); + } + ck_assert_int_eq(n_ops, 2); + ck_assert_int_eq(op_kind[0], 'E'); + ck_assert_int_eq(op_kind[1], 'P'); +} +END_TEST + +START_TEST(test_write_to_blank_page_skips_erase) +{ + uint8_t data[8]; + uint32_t i; + + memset(data, 0x3C, sizeof(data)); + wipe_page(BOOT + PAGE); + ck_assert_int_eq(hal_flash_write(S_ALIAS + BOOT + PAGE + 8, data, + sizeof(data)), 0); + ck_assert_int_eq(erase_calls, 0); + ck_assert_int_eq(program_calls, 1); + ck_assert_int_eq(ptr_calls, 0); + for (i = 0; i < PAGE; i++) + ck_assert_uint_eq(mock_flash[BOOT + PAGE + i], + (i >= 8 && i < 16) ? 0x3C : 0xFF); +} +END_TEST + +START_TEST(test_write_spanning_pages) +{ + uint8_t data[600]; + uint32_t i; + + for (i = 0; i < sizeof(data); i++) + data[i] = (uint8_t)i; + put_page(BOOT, 0x00); + put_page(BOOT + PAGE, 0x00); + put_page(BOOT + 2 * PAGE, 0x77); + ck_assert_int_eq(hal_flash_write(S_ALIAS + BOOT + 400, data, + sizeof(data)), 0); + for (i = 0; i < 400; i++) + ck_assert_uint_eq(mock_flash[BOOT + i], 0x00); + for (i = 0; i < sizeof(data); i++) + ck_assert_uint_eq(mock_flash[BOOT + 400 + i], (uint8_t)i); + for (i = 400 + sizeof(data); i < 2 * PAGE; i++) + ck_assert_uint_eq(mock_flash[BOOT + i], 0x00); + ck_assert_uint_eq(mock_flash[BOOT + 2 * PAGE], 0x77); + ck_assert_int_eq(erase_calls, 2); + ck_assert_int_eq(program_calls, 2); +} +END_TEST + +START_TEST(test_full_page_write_reads_nothing) +{ + uint8_t data[PAGE]; + + memset(data, 0x42, sizeof(data)); + put_page(BOOT, 0x99); + /* The non-secure alias works too; a full page needs no merge read */ + ck_assert_int_eq(hal_flash_write(BOOT, data, sizeof(data)), 0); + ck_assert_int_eq(ptr_calls, 0); + ck_assert_uint_eq(mock_flash[BOOT], 0x42); + ck_assert_uint_eq(mock_flash[BOOT + PAGE - 1], 0x42); + ck_assert_int_eq(n_ops, 2); + ck_assert_uint_eq(op_off[0], BOOT); + ck_assert_uint_eq(op_off[1], BOOT); +} +END_TEST + +START_TEST(test_secure_and_nonsecure_alias_match) +{ + uint8_t b = 0xE1; + + ck_assert_int_eq(hal_flash_erase(S_ALIAS + BOOT, PAGE), 0); + ck_assert_int_eq(hal_flash_erase(BOOT, PAGE), 0); + ck_assert_uint_eq(op_off[0], BOOT); + ck_assert_uint_eq(op_off[2], BOOT); + ck_assert_int_eq(hal_flash_write(S_ALIAS + BOOT + 3, &b, 1), 0); + ck_assert_uint_eq(mock_flash[BOOT + 3], 0xE1); +} +END_TEST + +START_TEST(test_write_rejects_bad_arguments) +{ + uint8_t data[32]; + + memset(data, 0, sizeof(data)); + ck_assert_int_eq(hal_flash_write(BOOT, data, 0), 0); + ck_assert_int_eq(hal_flash_write(BOOT, data, -1), -1); + ck_assert_int_eq(hal_flash_write(BOOT, NULL, 4), -1); + ck_assert_int_eq(hal_flash_write(LPC55S69_FLASH_SIZE, data, 4), -1); + ck_assert_int_eq(hal_flash_write(LPC55S69_FLASH_SIZE - 16, data, 32), -1); + ck_assert_int_eq(hal_flash_write(S_ALIAS + LPC55S69_FLASH_SIZE, data, 4), + -1); + ck_assert_int_eq(hal_flash_write(0x20000000U, data, 4), -1); + ck_assert_int_eq(n_ops, 0); +} +END_TEST + +START_TEST(test_write_program_failure_stops) +{ + uint8_t data[2 * PAGE]; + + memset(data, 0x12, sizeof(data)); + wipe_page(BOOT); + wipe_page(BOOT + PAGE); + fail_program_at = 1; + ck_assert_int_eq(hal_flash_write(BOOT, data, sizeof(data)), -1); + ck_assert_int_eq(program_calls, 1); + ck_assert(!programmed[(BOOT + PAGE) / PAGE]); +} +END_TEST + +START_TEST(test_write_erase_failure_stops) +{ + uint8_t b = 0x34; + + put_page(BOOT, 0x00); + fail_erase_at = 1; + ck_assert_int_eq(hal_flash_write(S_ALIAS + BOOT + 1, &b, 1), -1); + ck_assert_int_eq(program_calls, 0); +} +END_TEST + +START_TEST(test_erase_leaves_readable_ff) +{ + uint32_t i; + + put_page(BOOT, 0x00); + put_page(BOOT + PAGE, 0x00); + ck_assert_int_eq(hal_flash_erase(BOOT, 2 * PAGE), 0); + ck_assert_int_eq(n_ops, 4); + ck_assert_int_eq(op_kind[0], 'E'); + ck_assert_int_eq(op_kind[1], 'P'); + ck_assert_int_eq(op_kind[2], 'E'); + ck_assert_int_eq(op_kind[3], 'P'); + for (i = 0; i < 2 * PAGE; i++) + ck_assert_uint_eq(mock_flash[BOOT + i], 0xFF); + ck_assert(programmed[BOOT / PAGE]); + ck_assert(programmed[(BOOT + PAGE) / PAGE]); +} +END_TEST + +START_TEST(test_erase_rejects_bad_arguments) +{ + ck_assert_int_eq(hal_flash_erase(BOOT, 0), 0); + ck_assert_int_eq(hal_flash_erase(BOOT + 1, PAGE), -1); + ck_assert_int_eq(hal_flash_erase(BOOT, PAGE + 1), -1); + ck_assert_int_eq(hal_flash_erase(BOOT, -(int)PAGE), -1); + ck_assert_int_eq(hal_flash_erase(0, -(int)PAGE), -1); + ck_assert_int_eq(hal_flash_erase(LPC55S69_FLASH_SIZE, PAGE), -1); + ck_assert_int_eq(hal_flash_erase(LPC55S69_FLASH_SIZE - PAGE, 2 * PAGE), + -1); + ck_assert_int_eq(n_ops, 0); +} +END_TEST + +START_TEST(test_erase_failure_stops) +{ + fail_erase_at = 2; + ck_assert_int_eq(hal_flash_erase(BOOT, 3 * PAGE), -1); + ck_assert_int_eq(erase_calls, 2); + ck_assert_int_eq(program_calls, 1); +} +END_TEST + +START_TEST(test_irqs_masked_and_primask_restored) +{ + uint8_t b = 0x56; + + mock_primask = 0; + ck_assert_int_eq(hal_flash_write(S_ALIAS + BOOT + 9, &b, 1), 0); + ck_assert_int_eq(hal_flash_erase(BOOT + PAGE, PAGE), 0); + ck_assert_uint_eq(mock_primask, 0); + ck_assert_int_eq(irq_enabled_during_rom, 0); + + mock_primask = 1; + ck_assert_int_eq(hal_flash_write(S_ALIAS + BOOT + 10, &b, 1), 0); + ck_assert_uint_eq(mock_primask, 1); + ck_assert_int_eq(irq_enabled_during_rom, 0); +} +END_TEST + +static Suite *lpc55s69_flash_suite(void) +{ + Suite *s = suite_create("lpc55s69-flash"); + TCase *tc = tcase_create("lpc55s69-flash"); + + tcase_add_checked_fixture(tc, setup, NULL); + tcase_add_test(tc, test_init_fills_pages_wolfboot_reads); + tcase_add_test(tc, test_init_keeps_programmed_pages); + tcase_add_test(tc, test_init_fill_stops_at_failure); + tcase_add_test(tc, test_init_failure_then_retry); + tcase_add_test(tc, test_partial_write_keeps_neighbours); + tcase_add_test(tc, test_write_to_blank_page_skips_erase); + tcase_add_test(tc, test_write_spanning_pages); + tcase_add_test(tc, test_full_page_write_reads_nothing); + tcase_add_test(tc, test_secure_and_nonsecure_alias_match); + tcase_add_test(tc, test_write_rejects_bad_arguments); + tcase_add_test(tc, test_write_program_failure_stops); + tcase_add_test(tc, test_write_erase_failure_stops); + tcase_add_test(tc, test_erase_leaves_readable_ff); + tcase_add_test(tc, test_erase_rejects_bad_arguments); + tcase_add_test(tc, test_erase_failure_stops); + tcase_add_test(tc, test_irqs_masked_and_primask_restored); + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s = lpc55s69_flash_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + + return fails; +} diff --git a/tools/unit-tests/unit-lpc55s69-puf.c b/tools/unit-tests/unit-lpc55s69-puf.c new file mode 100644 index 0000000000..7a47492706 --- /dev/null +++ b/tools/unit-tests/unit-lpc55s69-puf.c @@ -0,0 +1,394 @@ +/* unit-lpc55s69-puf.c + * + * Unit test for the LPC55S69 HW PUF provisioning record + * + * Copyright (C) 2026 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 +#include + +#define H_TARGETS_TARGET_ +#define WOLFBOOT_FIXED_PARTITIONS +#define WOLFBOOT_SECTOR_SIZE 0x200 +#define WOLFBOOT_PARTITION_SIZE 0x20000 +#define WOLFBOOT_PARTITION_BOOT_ADDRESS 0x40000 +#define WOLFBOOT_PARTITION_UPDATE_ADDRESS 0x60000 +#define WOLFBOOT_PARTITION_SWAP_ADDRESS 0x81000 +#define WOLFBOOT_KEYVAULT_ADDRESS 0x10082000 +#define WOLFBOOT_KEYVAULT_SIZE 0x16000 +#define ARCH_FLASH_OFFSET 0x0 + +#define __WOLFBOOT +#define WOLFBOOT_HASH_SHA256 +#define WOLFBOOT_SIGN_ECC256 +#define WOLFSSL_NXP_HWPUF +#define WOLFBOOT_HWPUF_PROVISION + +#include "fsl_common.h" + +const uint8_t *mock_flash_ptr(uint32_t address); +#define LPC55S69_FLASH_PTR(a) mock_flash_ptr(a) + +#include "../../hal/lpc55s69.c" + +#define PAGE LPC55S69_FLASH_PAGE_SIZE +#define FLASH_PAGES (LPC55S69_FLASH_SIZE / PAGE) +#define S_ALIAS LPC55S69_FLASH_SECURE_ALIAS +#define REC WOLFBOOT_HWPUF_PROV_ADDRESS +#define COMMIT HWPUF_PROV_COMMIT_ADDRESS +#define NO_FAIL 0xFFFFFFFFU + +uint32_t mock_primask; +int mock_barriers; +NVIC_Type mock_nvic; +SysTick_Type mock_systick; + +static uint8_t mock_flash[LPC55S69_FLASH_SIZE]; +/* 0: erased and never programmed, so a read faults on the ECC check */ +static uint8_t programmed[FLASH_PAGES]; +/* Fail the Nth program of one page: the first is the 0xFF fill after erase */ +static uint32_t fail_program_off; +static int fail_program_hit; +static int fail_program_hits; +static int enroll_calls; +static int genkey_calls; +static int puf_inits; +static int puf_deinits; +static uint8_t enrolled_ac[PUF_ACTIVATION_CODE_SIZE]; + +status_t FLASH_Init(flash_config_t *config) +{ + config->PFlashTotalSize = LPC55S69_FLASH_SIZE; + config->PFlashPageSize = PAGE; + return kStatus_FLASH_Success; +} + +status_t FFR_Init(flash_config_t *config) +{ + (void)config; + return kStatus_Success; +} + +status_t FLASH_Erase(flash_config_t *config, uint32_t start, + uint32_t lengthInBytes, uint32_t key) +{ + ck_assert_ptr_eq(config, &pflash); + ck_assert_uint_eq(key, kFLASH_ApiEraseKey); + ck_assert_uint_eq(lengthInBytes, PAGE); + ck_assert_uint_lt(start, LPC55S69_FLASH_SIZE); + memset(&mock_flash[start], 0xFF, PAGE); + programmed[start / PAGE] = 0; + return kStatus_FLASH_Success; +} + +status_t FLASH_Program(flash_config_t *config, uint32_t start, + const uint8_t *src, uint32_t lengthInBytes) +{ + ck_assert_ptr_eq(config, &pflash); + ck_assert_uint_eq(lengthInBytes, PAGE); + ck_assert_uint_lt(start, LPC55S69_FLASH_SIZE); + if ((start == fail_program_off) && (++fail_program_hits == fail_program_hit)) + return kStatus_Fail; + ck_assert_msg(!programmed[start / PAGE], + "page 0x%x programmed twice without an erase", start); + memcpy(&mock_flash[start], src, PAGE); + programmed[start / PAGE] = 1; + return kStatus_FLASH_Success; +} + +status_t FLASH_VerifyErase(flash_config_t *config, uint32_t start, + uint32_t lengthInBytes) +{ + ck_assert_ptr_eq(config, &pflash); + ck_assert_uint_eq(lengthInBytes, PAGE); + return programmed[start / PAGE] ? kStatus_FLASH_CommandFailure : + kStatus_FLASH_Success; +} + +/* An erased page fails the compare instead of faulting, like the ROM */ +status_t FLASH_VerifyProgram(flash_config_t *config, uint32_t start, + uint32_t lengthInBytes, const uint8_t *expectedData, + uint32_t *failedAddress, uint32_t *failedData) +{ + ck_assert_ptr_eq(config, &pflash); + ck_assert_uint_eq(lengthInBytes, PAGE); + *failedAddress = start; + *failedData = 0; + if (!programmed[start / PAGE]) + return kStatus_FLASH_EccError; + if (memcmp(&mock_flash[start], expectedData, PAGE) != 0) + return kStatus_FLASH_CommandFailure; + return kStatus_FLASH_Success; +} + +const uint8_t *mock_flash_ptr(uint32_t address) +{ + uint32_t off = (address >= S_ALIAS) ? address - S_ALIAS : address; + + ck_assert_uint_lt(off, LPC55S69_FLASH_SIZE); + ck_assert_msg(programmed[off / PAGE], + "read of erased page 0x%x faults (ECC)", off); + return &mock_flash[off]; +} + +void BOARD_BootClockFROHF96M(void) +{ +} + +int wolfCrypt_Init(void) +{ + return 0; +} + +int nxp_hwpuf_Init(void) +{ + puf_inits++; + return 0; +} + +int nxp_hwpuf_Deinit(void) +{ + puf_deinits++; + return 0; +} + +int nxp_hwpuf_Enroll(byte *actCode, word32 actCodeSz) +{ + word32 i; + + ck_assert_uint_eq(actCodeSz, sizeof(enrolled_ac)); + ck_assert_uint_eq(((uintptr_t)actCode) % 4U, 0); + enroll_calls++; + for (i = 0; i < actCodeSz; i++) + actCode[i] = (byte)(0xA5U + i + (word32)enroll_calls); + memcpy(enrolled_ac, actCode, actCodeSz); + return 0; +} + +int nxp_hwpuf_Start(byte *actCode, word32 actCodeSz) +{ + ck_assert_uint_eq(actCodeSz, sizeof(enrolled_ac)); + ck_assert_uint_eq(((uintptr_t)actCode) % 4U, 0); + return (memcmp(actCode, enrolled_ac, actCodeSz) == 0) ? 0 : -1; +} + +int nxp_hwpuf_GenerateKey(byte keyIdx, word32 keySz, byte *keyCode, + word32 keyCodeSz) +{ + word32 i; + + ck_assert_uint_eq(keyIdx, HWPUF_PROV_UDS_KEY_INDEX); + ck_assert_uint_eq(keySz, HWPUF_PROV_UDS_KEY_SIZE); + ck_assert_uint_eq(((uintptr_t)keyCode) % 4U, 0); + genkey_calls++; + for (i = 0; i < keyCodeSz; i++) + keyCode[i] = (byte)(0x5AU + i); + return 0; +} + +int nxp_hwpuf_GetKey(byte *keyCode, word32 keyCodeSz, byte *key, word32 keySz) +{ + (void)keyCode; + (void)keyCodeSz; + memset(key, 0x11, keySz); + return 0; +} + +int nxp_hwpuf_Zeroize(void) +{ + return 0; +} + +static void put_page(uint32_t off, uint8_t fill) +{ + memset(&mock_flash[off], fill, PAGE); + programmed[off / PAGE] = 1; +} + +static void put_word(uint32_t off, uint32_t word) +{ + put_page(off, 0xFF); + memcpy(&mock_flash[off], &word, sizeof(word)); +} + +static void put_record(void) +{ + uint32_t off; + + for (off = REC; off < REC + sizeof(hwpuf_prov); off += PAGE) + put_page(off, 0x33); +} + +static void setup(void) +{ + memset(mock_flash, 0xFF, sizeof(mock_flash)); + memset(programmed, 0, sizeof(programmed)); + memset(enrolled_ac, 0, sizeof(enrolled_ac)); + fail_program_off = NO_FAIL; + fail_program_hit = fail_program_hits = 0; + enroll_calls = genkey_calls = puf_inits = puf_deinits = 0; + flash_ready = 0; + mock_primask = 0; + flashInit(); + ck_assert_int_eq(flash_ready, 1); +} + +START_TEST(test_record_is_word_aligned) +{ + ck_assert_uint_eq(((uintptr_t)&prov) % 4U, 0); + ck_assert_uint_eq(((uintptr_t)prov.ac) % 4U, 0); + ck_assert_uint_eq(((uintptr_t)prov.uds_kc) % 4U, 0); + ck_assert_uint_eq(REC % PAGE, 0); + ck_assert_uint_eq(COMMIT % PAGE, 0); + ck_assert_uint_le(sizeof(hwpuf_prov), COMMIT - REC); +} +END_TEST + +START_TEST(test_erased_region_provisions_once) +{ + hwpuf_prov rec; + + ck_assert_int_eq(hwpuf_provision_get(&rec), HWPUF_PROV_BLANK); + ck_assert_int_eq(hwpuf_provision_set(), 0); + ck_assert_int_eq(enroll_calls, 1); + ck_assert_int_eq(genkey_calls, 1); + ck_assert_int_eq(puf_inits, puf_deinits); + ck_assert_uint_eq(hwpuf_commit_word(), HWPUF_PROV_MAGIC); + + ck_assert_int_eq(hwpuf_provision_get(&rec), 0); + ck_assert_int_eq(memcmp(rec.ac, enrolled_ac, sizeof(rec.ac)), 0); + ck_assert_uint_eq(rec.uds_kc[0], 0x5AU); + + /* A committed record is reused, never re-enrolled */ + ck_assert_int_eq(hwpuf_provision_set(), 0); + ck_assert_int_eq(enroll_calls, 1); + ck_assert_int_eq(hwpuf_provision_get(&rec), 0); + ck_assert_int_eq(memcmp(rec.ac, enrolled_ac, sizeof(rec.ac)), 0); +} +END_TEST + +START_TEST(test_commit_page_of_ff_is_blank) +{ + hwpuf_prov rec; + + put_record(); + put_page(COMMIT, 0xFF); + ck_assert_int_eq(hwpuf_provision_get(&rec), HWPUF_PROV_BLANK); + ck_assert_int_eq(hwpuf_provision_set(), 0); + ck_assert_int_eq(enroll_calls, 1); + ck_assert_uint_eq(hwpuf_commit_word(), HWPUF_PROV_MAGIC); +} +END_TEST + +START_TEST(test_other_commit_word_is_never_replaced) +{ + hwpuf_prov rec; + + put_record(); + put_word(COMMIT, 0x12345678U); + ck_assert_int_eq(hwpuf_provision_get(&rec), -1); + ck_assert_int_eq(hwpuf_provision_set(), -1); + ck_assert_int_eq(enroll_calls, 0); + ck_assert_uint_eq(hwpuf_commit_word(), 0x12345678U); + ck_assert_uint_eq(mock_flash[REC], 0x33U); +} +END_TEST + +START_TEST(test_committed_with_erased_payload_fails) +{ + hwpuf_prov rec; + + put_record(); + put_word(COMMIT, HWPUF_PROV_MAGIC); + programmed[(REC + PAGE) / PAGE] = 0; + ck_assert_int_eq(hwpuf_provision_get(&rec), -1); + ck_assert_int_eq(hwpuf_provision_set(), -1); + ck_assert_int_eq(enroll_calls, 0); +} +END_TEST + +START_TEST(test_interrupted_commit_is_redone) +{ + hwpuf_prov rec; + + fail_program_off = COMMIT; + fail_program_hit = 2; + ck_assert_int_eq(hwpuf_provision_set(), -1); + ck_assert_int_eq(enroll_calls, 1); + ck_assert_int_eq(puf_inits, puf_deinits); + ck_assert_uint_eq(mock_flash[REC], enrolled_ac[0]); + + /* Next boot: no commit word, so the record is written again */ + fail_program_off = NO_FAIL; + ck_assert_int_eq(hwpuf_provision_get(&rec), HWPUF_PROV_BLANK); + ck_assert_int_eq(hwpuf_provision_set(), 0); + ck_assert_int_eq(enroll_calls, 2); + ck_assert_int_eq(hwpuf_provision_get(&rec), 0); + ck_assert_int_eq(memcmp(rec.ac, enrolled_ac, sizeof(rec.ac)), 0); +} +END_TEST + +START_TEST(test_interrupted_payload_is_redone) +{ + hwpuf_prov rec; + + fail_program_off = REC + PAGE; + fail_program_hit = 2; + ck_assert_int_eq(hwpuf_provision_set(), -1); + ck_assert_uint_eq(mock_flash[REC], enrolled_ac[0]); + ck_assert_uint_eq(programmed[COMMIT / PAGE], 1); + ck_assert_uint_eq(hwpuf_commit_word(), 0xFFFFFFFFU); + + fail_program_off = NO_FAIL; + ck_assert_int_eq(hwpuf_provision_get(&rec), HWPUF_PROV_BLANK); + ck_assert_int_eq(hwpuf_provision_set(), 0); + ck_assert_int_eq(hwpuf_provision_get(&rec), 0); +} +END_TEST + +static Suite *lpc55s69_puf_suite(void) +{ + Suite *s = suite_create("lpc55s69-puf"); + TCase *tc = tcase_create("provisioning record"); + + tcase_add_checked_fixture(tc, setup, NULL); + tcase_add_test(tc, test_record_is_word_aligned); + tcase_add_test(tc, test_erased_region_provisions_once); + tcase_add_test(tc, test_commit_page_of_ff_is_blank); + tcase_add_test(tc, test_other_commit_word_is_never_replaced); + tcase_add_test(tc, test_committed_with_erased_payload_fails); + tcase_add_test(tc, test_interrupted_commit_is_redone); + tcase_add_test(tc, test_interrupted_payload_is_redone); + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s = lpc55s69_puf_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + return fails; +} diff --git a/tools/unit-tests/unit-lpc55s69-tz.c b/tools/unit-tests/unit-lpc55s69-tz.c new file mode 100644 index 0000000000..153a867129 --- /dev/null +++ b/tools/unit-tests/unit-lpc55s69-tz.c @@ -0,0 +1,624 @@ +/* unit-lpc55s69-tz.c + * + * Unit tests for the LPC55S69 secure-world HAL in hal/lpc55s69.c: the SAU and + * AHB secure controller attribution, the locks, the hand-over to the + * non-secure application, the boot timer and the RNG entropy source. + * + * Copyright (C) 2026 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 +#include +#include + +#define TZEN +#define WOLFCRYPT_SECURE_MODE +#define __WOLFBOOT +#define WOLFBOOT_HASH_SHA256 +#define WOLFBOOT_SIGN_ECC256 +#define BOOT_BENCHMARK +/* Replace hal/armv8m_tz.h with a recording SAU */ +#define TZ_INCLUDED + +#define LPC55S69_SAU_REGIONS 8 + +struct sau_region { + uint32_t start; + uint32_t end; + int nsc; + int set; +}; + +static struct sau_region sau[LPC55S69_SAU_REGIONS]; +static int sau_bad_region; +static uint32_t mock_sau_ctrl; +static uint32_t mock_shcsr; + +#define SAU_CTRL mock_sau_ctrl +#define SAU_INIT_CTRL_ENABLE (1U << 0) +#define SCB_SHCSR mock_shcsr +#define SCB_SHCSR_SECUREFAULT_EN (1U << 19) + +static void sau_init_region(uint32_t region, uint32_t start_addr, + uint32_t end_addr, int secure) +{ + if (region >= LPC55S69_SAU_REGIONS) { + sau_bad_region = 1; + return; + } + sau[region].start = start_addr; + sau[region].end = end_addr; + sau[region].nsc = secure; + sau[region].set = 1; +} + +/* lpc55s69-tz.config layout, in place of target.h */ +#define H_TARGETS_TARGET_ +#define WOLFBOOT_FIXED_PARTITIONS +#define WOLFBOOT_SECTOR_SIZE 0x200 +#define WOLFBOOT_PARTITION_SIZE 0x20000 +#define WOLFBOOT_PARTITION_BOOT_ADDRESS 0x00040000 +#define WOLFBOOT_PARTITION_UPDATE_ADDRESS 0x00060000 +#define WOLFBOOT_PARTITION_SWAP_ADDRESS 0x00081000 +#define WOLFBOOT_NSC_ADDRESS 0x1003E000 +#define WOLFBOOT_NSC_SIZE 0x2000 +#define ARCH_FLASH_OFFSET 0x0 +#define KEYVAULT_ADDRESS 0x10082000U +#define KEYVAULT_SIZE 0x16000U +#define LPC55S69_KEYVAULT_ADDR KEYVAULT_ADDRESS +#define LPC55S69_KEYVAULT_LEN KEYVAULT_SIZE + +#include "fsl_device_registers.h" + +static AHB_SECURE_CTRL_Type mock_ahbsc; +static SYSCON_Type mock_syscon; +static IOCON_Type mock_iocon; +RNG_Type mock_rng; +SYSCON_Type *mock_syscon_regs(void); + +/* Before fsl_common.h: the inline clock gates in fsl_clock.h expand these */ +#undef AHB_SECURE_CTRL +#undef SYSCON +#undef IOCON +#undef RNG +#define AHB_SECURE_CTRL (&mock_ahbsc) +#define SYSCON (mock_syscon_regs()) +#define IOCON (&mock_iocon) +#define RNG (&mock_rng) + +#include "fsl_common.h" +#include "fsl_iap.h" +#include "../../hal/lpc55s69.h" +#include "fsl_iap_ffr.h" +#include "fsl_rng.h" + +uint32_t mock_primask; +int mock_barriers; +NVIC_Type mock_nvic; +SysTick_Type mock_systick; + +/* Clock gates turned on through the write-only AHBCLKCTRLSET registers */ +static uint32_t clocks_on[SYSCON_AHBCLKCTRLSET_COUNT]; +static int attach_calls; +static clock_attach_id_t attach_id; +static int rng_init_calls; +static int rng_read_calls; +static int rng_resets; +static status_t rng_read_ret; + +SYSCON_Type *mock_syscon_regs(void) +{ + uint32_t i; + + for (i = 0; i < SYSCON_AHBCLKCTRLSET_COUNT; i++) { + clocks_on[i] |= mock_syscon.AHBCLKCTRLSET[i]; + mock_syscon.AHBCLKCTRLSET[i] = 0; + } + return &mock_syscon; +} + +static int clock_on(clock_ip_name_t clk) +{ + (void)mock_syscon_regs(); + return (clocks_on[CLK_GATE_ABSTRACT_REG_OFFSET(clk)] & + (1UL << CLK_GATE_ABSTRACT_BITS_SHIFT(clk))) != 0U; +} + +#define PAGE LPC55S69_FLASH_PAGE_SIZE +/* 0: erased and never programmed */ +static uint8_t programmed[LPC55S69_FLASH_SIZE / LPC55S69_FLASH_PAGE_SIZE]; +static int program_calls; + +status_t FLASH_Init(flash_config_t *config) +{ + config->PFlashTotalSize = LPC55S69_FLASH_SIZE; + return kStatus_FLASH_Success; +} + +status_t FFR_Init(flash_config_t *config) +{ + (void)config; + return kStatus_Success; +} + +status_t FLASH_VerifyErase(flash_config_t *config, uint32_t start, + uint32_t lengthInBytes) +{ + (void)config; + ck_assert_uint_eq(lengthInBytes, PAGE); + ck_assert_uint_lt(start, LPC55S69_FLASH_SIZE); + return programmed[start / PAGE] ? kStatus_FLASH_CommandFailure : + kStatus_FLASH_Success; +} + +status_t FLASH_Erase(flash_config_t *config, uint32_t start, + uint32_t lengthInBytes, uint32_t key) +{ + (void)config; + (void)start; + (void)lengthInBytes; + (void)key; + ck_abort_msg("unexpected flash erase"); + return kStatus_Fail; +} + +status_t FLASH_Program(flash_config_t *config, uint32_t start, + const uint8_t *src, uint32_t lengthInBytes) +{ + (void)config; + (void)src; + ck_assert_uint_eq(lengthInBytes, PAGE); + ck_assert_uint_lt(start, LPC55S69_FLASH_SIZE); + ck_assert_msg(!programmed[start / PAGE], "page 0x%x programmed twice", + start); + programmed[start / PAGE] = 1; + program_calls++; + return kStatus_FLASH_Success; +} + +void BOARD_BootClockFROHF96M(void) +{ +} + +int wolfCrypt_Init(void) +{ + return 0; +} + +void CLOCK_AttachClk(clock_attach_id_t connection) +{ + attach_calls++; + attach_id = connection; +} + +void RESET_PeripheralReset(reset_ip_name_t peripheral) +{ + if (peripheral == kRNG_RST_SHIFT_RSTn) + rng_resets++; +} + +void RNG_Init(RNG_Type *base) +{ + ck_assert_ptr_eq(base, &mock_rng); + rng_init_calls++; +} + +status_t RNG_GetRandomData(RNG_Type *base, void *data, size_t dataSize) +{ + ck_assert_ptr_eq(base, &mock_rng); + rng_read_calls++; + memset(data, 0xA5, dataSize); + return rng_read_ret; +} + +#include "../../hal/lpc55s69.c" + +#define AHBSC_S 0x33333333U +#define AHBSC_RULE(reg, n) (((reg) >> ((n) * 4U)) & 0x3U) + +static int sau_covers(uint32_t addr, int nsc) +{ + int i; + + for (i = 0; i < LPC55S69_SAU_REGIONS; i++) { + if (sau[i].set && (sau[i].nsc == nsc) && + (addr >= sau[i].start) && (addr <= sau[i].end)) + return 1; + } + return 0; +} + +static void setup(void) +{ + memset(sau, 0, sizeof(sau)); + memset(&mock_nvic, 0, sizeof(mock_nvic)); + memset(&mock_ahbsc, 0, sizeof(mock_ahbsc)); + memset(&mock_syscon, 0, sizeof(mock_syscon)); + memset(&mock_iocon, 0, sizeof(mock_iocon)); + memset(&mock_systick, 0, sizeof(mock_systick)); + memset(clocks_on, 0, sizeof(clocks_on)); + /* Reset values of the lock and control registers */ + mock_ahbsc.MISC_CTRL_REG = 0x0000AAAAU; + mock_ahbsc.MISC_CTRL_DP_REG = 0x0000AAAAU; + mock_ahbsc.CPU0_LOCK_REG = 0x800002AAU; + sau_bad_region = 0; + mock_sau_ctrl = 0; + mock_shcsr = 0; + memset(programmed, 0, sizeof(programmed)); + /* A keyvault page that already holds data */ + programmed[(KEYVAULT_ADDRESS - 0x10000000U + PAGE) / PAGE] = 1; + program_calls = 0; + flash_ready = 0; + hal_init(); + hal_trng_fini(); + attach_calls = 0; + rng_init_calls = rng_read_calls = rng_resets = 0; + rng_read_ret = kStatus_Success; +} + +START_TEST(test_init_fills_keyvault_pages) +{ + uint32_t off; + uint32_t kv = KEYVAULT_ADDRESS - 0x10000000U; + + ck_assert_int_eq(flash_ready, 1); + for (off = kv; off < kv + KEYVAULT_SIZE; off += PAGE) + ck_assert_msg(programmed[off / PAGE], "keyvault page 0x%x", off); + for (off = 0; off < WOLFBOOT_PARTITION_BOOT_ADDRESS; off += PAGE) + ck_assert_msg(programmed[off / PAGE], "bootloader page 0x%x", off); + /* The HWPUF record is left alone; the existing vault page kept */ + ck_assert(!programmed[0x80000U / PAGE]); + ck_assert_int_eq(program_calls, (int)((WOLFBOOT_PARTITION_BOOT_ADDRESS + + 2U * WOLFBOOT_PARTITION_SIZE + WOLFBOOT_SECTOR_SIZE + + KEYVAULT_SIZE) / PAGE) - 1); +} +END_TEST + +START_TEST(test_sau_enabled_and_well_formed) +{ + int i; + + ck_assert_int_eq(sau_bad_region, 0); + ck_assert_uint_ne(mock_sau_ctrl & SAU_INIT_CTRL_ENABLE, 0); + ck_assert_uint_ne(mock_shcsr & SCB_SHCSR_SECUREFAULT_EN, 0); + for (i = 0; i < LPC55S69_SAU_REGIONS; i++) { + if (!sau[i].set) + continue; + ck_assert_uint_le(sau[i].start, sau[i].end); + ck_assert_uint_eq(sau[i].start & 0x1FU, 0); + ck_assert_uint_eq(sau[i].end & 0x1FU, 0x1FU); + } +} +END_TEST + +START_TEST(test_nsc_region_is_veneers_only) +{ + int i; + int nsc_regions = 0; + + for (i = 0; i < LPC55S69_SAU_REGIONS; i++) { + if (sau[i].set && sau[i].nsc) { + nsc_regions++; + ck_assert_uint_eq(sau[i].start, WOLFBOOT_NSC_ADDRESS); + ck_assert_uint_eq(sau[i].end, + WOLFBOOT_NSC_ADDRESS + WOLFBOOT_NSC_SIZE - 1); + } + } + ck_assert_int_eq(nsc_regions, 1); +} +END_TEST + +START_TEST(test_application_resources_non_secure) +{ + static const uint32_t ns_addrs[] = { + WOLFBOOT_PARTITION_BOOT_ADDRESS, + WOLFBOOT_PARTITION_BOOT_ADDRESS + WOLFBOOT_PARTITION_SIZE - 1, + 0x20020000, 0x2002FFFF, /* SRAM2 */ + 0x40086000, 0x40086FFF, /* FLEXCOMM0 (console) */ + 0x4008C000, 0x4008FFFF /* GPIO */ + }; + unsigned int i; + + for (i = 0; i < sizeof(ns_addrs) / sizeof(ns_addrs[0]); i++) + ck_assert_msg(sau_covers(ns_addrs[i], 0), "0x%08x not non-secure", + ns_addrs[i]); +} +END_TEST + +START_TEST(test_secure_resources_not_non_secure) +{ + static const uint32_t s_addrs[] = { + 0x00000000, 0x10000000, /* wolfBoot */ + WOLFBOOT_NSC_ADDRESS, + WOLFBOOT_NSC_ADDRESS & 0x0FFFFFFFU, + WOLFBOOT_PARTITION_UPDATE_ADDRESS, + WOLFBOOT_PARTITION_UPDATE_ADDRESS + WOLFBOOT_PARTITION_SIZE - 1, + WOLFBOOT_PARTITION_SWAP_ADDRESS, + 0x00080000, /* HWPUF provisioning record */ + KEYVAULT_ADDRESS, KEYVAULT_ADDRESS & 0x0FFFFFFFU, + KEYVAULT_ADDRESS + KEYVAULT_SIZE - 1, + 0x20000000, 0x2001FFFF, /* SRAM0-1, wolfBoot */ + 0x20030000, 0x20040000, /* SRAM3, SRAM4 */ + 0x30000000, 0x30020000, /* secure SRAM alias */ + 0x40000000, 0x40001000, /* SYSCON, IOCON */ + 0x40020000, /* PMC */ + 0x40034000, 0x40035000, /* FLASH controller, PRINCE */ + 0x4003A000, 0x4003B000, /* RNG, PUF */ + 0x40082000, /* DMA0 */ + 0x40085FFF, 0x40087000, /* around FLEXCOMM0 */ + 0x400A4000, 0x400A5000, /* HASHCRYPT, CASPER */ + 0x400A7000, 0x400A8000, /* DMA1, SECGPIO */ + 0x400AC000, /* AHB_SECURE_CTRL */ + 0x40100000, /* USB SRAM */ + 0x50086000, 0x5008C000 /* secure aliases of the NS peripherals */ + }; + unsigned int i; + + for (i = 0; i < sizeof(s_addrs) / sizeof(s_addrs[0]); i++) + ck_assert_msg(!sau_covers(s_addrs[i], 0), "0x%08x is non-secure", + s_addrs[i]); +} +END_TEST + +START_TEST(test_only_console_irq_targets_non_secure) +{ + unsigned int i; + + ck_assert_uint_eq(mock_nvic.ITNS[0], 1UL << FLEXCOMM0_IRQn); + for (i = 1; i < sizeof(mock_nvic.ITNS) / sizeof(mock_nvic.ITNS[0]); i++) + ck_assert_uint_eq(mock_nvic.ITNS[i], 0); +} +END_TEST + +START_TEST(test_ahbsc_flash_rules_match_boot_partition) +{ + uint32_t region; + uint32_t start; + uint32_t rule; + int ns; + + for (region = 0; region < 24U; region++) { + start = region * LPC55S69_AHBSC_FLASH_REGION; + ns = (start >= WOLFBOOT_PARTITION_BOOT_ADDRESS) && + (start < WOLFBOOT_PARTITION_BOOT_ADDRESS + WOLFBOOT_PARTITION_SIZE); + rule = AHBSC_RULE(mock_ahbsc.SEC_CTRL_FLASH_ROM[0]. + SEC_CTRL_FLASH_MEM_RULE[region / 8U], region % 8U); + ck_assert_msg(rule == (ns ? 0U : 3U), "flash 0x%05x rule %u", start, + rule); + } + ck_assert_uint_eq(AHBSC_RULE(mock_ahbsc.SEC_CTRL_FLASH_ROM[0].SLAVE_RULE, + 0), 0); + ck_assert_uint_eq(AHBSC_RULE(mock_ahbsc.SEC_CTRL_FLASH_ROM[0].SLAVE_RULE, + 1), 3); + for (region = 0; region < 4U; region++) + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_FLASH_ROM[0]. + SEC_CTRL_ROM_MEM_RULE[region], AHBSC_S); +} +END_TEST + +START_TEST(test_ahbsc_sram_rules) +{ + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_RAM2[0].SLAVE_RULE, 0); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_RAM2[0].MEM_RULE[0], 0); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_RAM2[0].MEM_RULE[1], 0); + + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_RAMX[0].SLAVE_RULE, AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_RAMX[0].MEM_RULE[0], AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_RAM0[0].SLAVE_RULE, AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_RAM0[0].MEM_RULE[0], AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_RAM0[0].MEM_RULE[1], AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_RAM1[0].SLAVE_RULE, AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_RAM1[0].MEM_RULE[0], AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_RAM1[0].MEM_RULE[1], AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_RAM3[0].SLAVE_RULE, AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_RAM3[0].MEM_RULE[0], AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_RAM3[0].MEM_RULE[1], AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_RAM4[0].SLAVE_RULE, AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_RAM4[0].MEM_RULE[0], AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_USB_HS[0].SLAVE_RULE, AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_USB_HS[0].MEM_RULE[0], AHBSC_S); +} +END_TEST + +START_TEST(test_ahbsc_peripheral_rules) +{ + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_APB_BRIDGE[0].SLAVE_RULE, AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_APB_BRIDGE[0]. + SEC_CTRL_APB_BRIDGE0_MEM_CTRL0, AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_APB_BRIDGE[0]. + SEC_CTRL_APB_BRIDGE0_MEM_CTRL1, AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_APB_BRIDGE[0]. + SEC_CTRL_APB_BRIDGE0_MEM_CTRL2, AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_APB_BRIDGE[0]. + SEC_CTRL_APB_BRIDGE1_MEM_CTRL0, AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_APB_BRIDGE[0]. + SEC_CTRL_APB_BRIDGE1_MEM_CTRL1, AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_APB_BRIDGE[0]. + SEC_CTRL_APB_BRIDGE1_MEM_CTRL2, AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_APB_BRIDGE[0]. + SEC_CTRL_APB_BRIDGE1_MEM_CTRL3, AHBSC_S); + + /* Only FLEXCOMM0 and the GPIO block are open to the application */ + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_AHB_PORT8_SLAVE0_RULE, AHBSC_S & + ~AHB_SECURE_CTRL_SEC_CTRL_AHB_PORT8_SLAVE0_RULE_FLEXCOMM0_RULE_MASK); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_AHB_PORT8_SLAVE1_RULE, AHBSC_S & + ~AHB_SECURE_CTRL_SEC_CTRL_AHB_PORT8_SLAVE1_RULE_GPIO0_RULE_MASK); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_AHB_PORT9_SLAVE0_RULE, AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_AHB_PORT9_SLAVE1_RULE, AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_AHB_PORT10[0].SLAVE0_RULE, AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_AHB_PORT10[0].SLAVE1_RULE, AHBSC_S); + ck_assert_uint_eq(mock_ahbsc.SEC_CTRL_AHB_PORT10[0]. + SEC_CTRL_AHB_SEC_CTRL_MEM_RULE[0], AHBSC_S); +} +END_TEST + +START_TEST(test_ahbsc_checking_enabled_and_locked) +{ + const uint32_t checking = + AHB_SECURE_CTRL_MISC_CTRL_REG_ENABLE_SECURE_CHECKING_MASK; + const uint32_t lock = AHB_SECURE_CTRL_MISC_CTRL_REG_WRITE_LOCK_MASK; + + ck_assert_uint_eq(mock_ahbsc.MISC_CTRL_REG & checking, + AHB_SECURE_CTRL_MISC_CTRL_REG_ENABLE_SECURE_CHECKING(1U)); + ck_assert_uint_eq(mock_ahbsc.MISC_CTRL_DP_REG & checking, + AHB_SECURE_CTRL_MISC_CTRL_REG_ENABLE_SECURE_CHECKING(1U)); + ck_assert_uint_eq(mock_ahbsc.MISC_CTRL_REG & lock, + AHB_SECURE_CTRL_MISC_CTRL_REG_WRITE_LOCK(2U)); + ck_assert_uint_eq(mock_ahbsc.MISC_CTRL_DP_REG & lock, + AHB_SECURE_CTRL_MISC_CTRL_REG_WRITE_LOCK(1U)); + /* Every other control keeps its reset value */ + ck_assert_uint_eq(mock_ahbsc.MISC_CTRL_REG & ~(checking | lock), + 0x0000AAAAU & ~(checking | lock)); + ck_assert_uint_eq(mock_ahbsc.MISC_CTRL_DP_REG & ~(checking | lock), + 0x0000AAAAU & ~(checking | lock)); +} +END_TEST + +START_TEST(test_sau_write_locked) +{ + const uint32_t locks = AHB_SECURE_CTRL_CPU0_LOCK_REG_LOCK_SAU_MASK | + AHB_SECURE_CTRL_CPU0_LOCK_REG_CPU0_LOCK_REG_LOCK_MASK; + + ck_assert_uint_eq(mock_ahbsc.CPU0_LOCK_REG & locks, + AHB_SECURE_CTRL_CPU0_LOCK_REG_LOCK_SAU(1U) | + AHB_SECURE_CTRL_CPU0_LOCK_REG_CPU0_LOCK_REG_LOCK(1U)); + /* wolfBoot still sets the non-secure VTOR before the jump */ + ck_assert_uint_eq(mock_ahbsc.CPU0_LOCK_REG & ~locks, 0x800002AAU & ~locks); +} +END_TEST + +START_TEST(test_prepare_boot_hands_over_leds) +{ + const uint32_t mux = IOCON_FUNC0 | IOCON_MODE_PULLUP | IOCON_DIGITAL_EN; + + mock_systick.CTRL = SysTick_CTRL_ENABLE_Msk; + hal_prepare_boot(); + ck_assert_uint_eq(mock_iocon.PIO[LPC55S69_LED_PORT][LPC55S69_LED_BLUE_PIN], + mux); + ck_assert_uint_eq(mock_iocon.PIO[LPC55S69_LED_PORT][LPC55S69_LED_RED_PIN], + mux); + ck_assert_uint_eq( + mock_iocon.PIO[LPC55S69_LED_PORT][LPC55S69_LED_GREEN_PIN], mux); + ck_assert(clock_on(kCLOCK_Iocon)); + ck_assert(clock_on(kCLOCK_Gpio1)); + ck_assert_uint_eq(mock_systick.CTRL, 0); +} +END_TEST + +START_TEST(test_benchmark_timer_counts_fro1m_ticks) +{ + ck_assert_uint_eq(hal_get_timer_us(), 0); + ck_assert_uint_ne(mock_syscon.CLOCK_CTRL & + SYSCON_CLOCK_CTRL_FRO1MHZ_CLK_ENA_MASK, 0); + ck_assert_int_eq(attach_calls, 1); + ck_assert_int_eq(attach_id, kFRO1M_to_SYSTICK0); + ck_assert_uint_eq(mock_systick.LOAD, SysTick_LOAD_RELOAD_Msk); + ck_assert_uint_eq(mock_systick.CTRL, SysTick_CTRL_ENABLE_Msk); + + /* Down-counter wraps from 0 to the 24-bit reload value */ + mock_systick.VAL = SysTick_LOAD_RELOAD_Msk - 99U; + ck_assert_uint_eq(hal_get_timer_us(), 100); + mock_systick.VAL -= 400U; + ck_assert_uint_eq(hal_get_timer_us(), 500); + ck_assert_int_eq(attach_calls, 1); +} +END_TEST + +START_TEST(test_trng_needs_init) +{ + unsigned char buf[8]; + + ck_assert_int_eq(hal_trng_get_entropy(buf, sizeof(buf)), -1); + ck_assert_int_eq(rng_read_calls, 0); +} +END_TEST + +START_TEST(test_trng_init_and_read) +{ + unsigned char buf[8]; + unsigned int i; + + memset(buf, 0, sizeof(buf)); + hal_trng_init(); + ck_assert_int_eq(rng_init_calls, 1); + ck_assert_int_eq(rng_resets, 1); + ck_assert(clock_on(kCLOCK_Rng)); + ck_assert_int_eq(hal_trng_get_entropy(buf, sizeof(buf)), 0); + ck_assert_int_eq(rng_read_calls, 1); + for (i = 0; i < sizeof(buf); i++) + ck_assert_uint_eq(buf[i], 0xA5); + ck_assert_int_eq(hal_trng_get_entropy(NULL, 4), -1); + ck_assert_int_eq(rng_read_calls, 1); +} +END_TEST + +START_TEST(test_trng_read_failure_and_fini) +{ + unsigned char buf[8]; + + hal_trng_init(); + rng_read_ret = kStatus_Fail; + ck_assert_int_eq(hal_trng_get_entropy(buf, sizeof(buf)), -1); + + hal_trng_fini(); + rng_read_ret = kStatus_Success; + ck_assert_int_eq(hal_trng_get_entropy(buf, sizeof(buf)), -1); + ck_assert_int_eq(rng_read_calls, 1); +} +END_TEST + +static Suite *lpc55s69_tz_suite(void) +{ + Suite *s = suite_create("lpc55s69-tz"); + TCase *tc = tcase_create("lpc55s69-tz"); + + tcase_add_checked_fixture(tc, setup, NULL); + tcase_add_test(tc, test_init_fills_keyvault_pages); + tcase_add_test(tc, test_sau_enabled_and_well_formed); + tcase_add_test(tc, test_nsc_region_is_veneers_only); + tcase_add_test(tc, test_application_resources_non_secure); + tcase_add_test(tc, test_secure_resources_not_non_secure); + tcase_add_test(tc, test_only_console_irq_targets_non_secure); + tcase_add_test(tc, test_ahbsc_flash_rules_match_boot_partition); + tcase_add_test(tc, test_ahbsc_sram_rules); + tcase_add_test(tc, test_ahbsc_peripheral_rules); + tcase_add_test(tc, test_ahbsc_checking_enabled_and_locked); + tcase_add_test(tc, test_sau_write_locked); + tcase_add_test(tc, test_prepare_boot_hands_over_leds); + tcase_add_test(tc, test_benchmark_timer_counts_fro1m_ticks); + tcase_add_test(tc, test_trng_needs_init); + tcase_add_test(tc, test_trng_init_and_read); + tcase_add_test(tc, test_trng_read_failure_and_fini); + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s = lpc55s69_tz_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + + return fails; +} diff --git a/tools/unit-tests/unit-lpc55s69-uart.c b/tools/unit-tests/unit-lpc55s69-uart.c new file mode 100644 index 0000000000..7ee632cf5a --- /dev/null +++ b/tools/unit-tests/unit-lpc55s69-uart.c @@ -0,0 +1,263 @@ +/* unit-lpc55s69-uart.c + * + * Unit tests for the LPC55S69 debug console in hal/lpc55s69.c. + * + * Copyright (C) 2026 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 +#include +#include + +/* lpc55s69.config layout, in place of target.h */ +#define H_TARGETS_TARGET_ +#define WOLFBOOT_FIXED_PARTITIONS +#define WOLFBOOT_SECTOR_SIZE 0x200 +#define WOLFBOOT_PARTITION_SIZE 0x30000 +#define WOLFBOOT_PARTITION_BOOT_ADDRESS 0x10000 +#define WOLFBOOT_PARTITION_UPDATE_ADDRESS 0x40000 +#define WOLFBOOT_PARTITION_SWAP_ADDRESS 0x70000 +#define ARCH_FLASH_OFFSET 0x0 +#define WOLFBOOT_HASH_SHA256 +#define WOLFBOOT_SIGN_ECC256 + +#include "fsl_device_registers.h" + +static USART_Type mock_usart0; +static SYSCON_Type mock_syscon; +static IOCON_Type mock_iocon; +static RNG_Type mock_rng; + +/* Before fsl_common.h: the inline clock gates in fsl_clock.h expand these */ +#undef USART0 +#undef SYSCON +#undef IOCON +#undef RNG +#define USART0 (&mock_usart0) +#define SYSCON (&mock_syscon) +#define IOCON (&mock_iocon) +#define RNG (&mock_rng) + +#include "fsl_common.h" +#include "fsl_iap.h" +#include "fsl_iap_ffr.h" +#include "fsl_rng.h" +#include "fsl_usart.h" + +#define MOCK_TX_MAX 64 + +uint32_t mock_primask; +int mock_barriers; +NVIC_Type mock_nvic; +SysTick_Type mock_systick; + +static clock_attach_id_t attached_clk; +static int attach_calls; +static int fc0_unreset; +static usart_config_t init_cfg; +static uint32_t init_clk_hz; +static USART_Type *init_base; +static int init_calls; +static uint8_t tx[MOCK_TX_MAX]; +static unsigned int tx_len; +static int tx_bad_base; + +void CLOCK_AttachClk(clock_attach_id_t connection) +{ + attached_clk = connection; + attach_calls++; +} + +void RESET_ClearPeripheralReset(reset_ip_name_t peripheral) +{ + if (peripheral == kFC0_RST_SHIFT_RSTn) + fc0_unreset++; +} + +void USART_GetDefaultConfig(usart_config_t *config) +{ + memset(config, 0, sizeof(*config)); + config->baudRate_Bps = 9600U; +} + +status_t USART_Init(USART_Type *base, const usart_config_t *config, + uint32_t srcClock_Hz) +{ + init_base = base; + init_cfg = *config; + init_clk_hz = srcClock_Hz; + init_calls++; + return kStatus_Success; +} + +status_t USART_WriteBlocking(USART_Type *base, const uint8_t *data, + size_t length) +{ + if (base != &mock_usart0) + tx_bad_base = 1; + while ((length > 0U) && (tx_len < MOCK_TX_MAX)) { + tx[tx_len++] = *data++; + length--; + } + return kStatus_Success; +} + +/* hal_init (application side) links against these; the tests never call it */ +status_t FLASH_Init(flash_config_t *config) +{ + (void)config; + return kStatus_Fail; +} + +status_t FFR_Init(flash_config_t *config) +{ + (void)config; + return kStatus_Fail; +} + +status_t FLASH_VerifyErase(flash_config_t *config, uint32_t start, + uint32_t lengthInBytes) +{ + (void)config; + (void)start; + (void)lengthInBytes; + return kStatus_Fail; +} + +status_t FLASH_Erase(flash_config_t *config, uint32_t start, + uint32_t lengthInBytes, uint32_t key) +{ + (void)config; + (void)start; + (void)lengthInBytes; + (void)key; + return kStatus_Fail; +} + +status_t FLASH_Program(flash_config_t *config, uint32_t start, + const uint8_t *src, uint32_t lengthInBytes) +{ + (void)config; + (void)start; + (void)src; + (void)lengthInBytes; + return kStatus_Fail; +} + +void RESET_PeripheralReset(reset_ip_name_t peripheral) +{ + (void)peripheral; +} + +void RNG_Init(RNG_Type *base) +{ + (void)base; +} + +void uart_printf(const char *fmt, ...) +{ + (void)fmt; +} + +#include "../../hal/lpc55s69.c" + +static void setup(void) +{ + memset(&mock_syscon, 0, sizeof(mock_syscon)); + memset(&mock_iocon, 0, sizeof(mock_iocon)); + memset(&init_cfg, 0, sizeof(init_cfg)); + memset(tx, 0, sizeof(tx)); + tx_len = 0; + tx_bad_base = 0; + attach_calls = init_calls = fc0_unreset = 0; + init_base = NULL; +} + +START_TEST(test_uart_init_console_pins_and_baud) +{ + const uint32_t mux = IOCON_FUNC1 | IOCON_MODE_INACT | IOCON_DIGITAL_EN; + + uart_init(); + ck_assert_uint_eq(mock_iocon.PIO[0][29], mux); + ck_assert_uint_eq(mock_iocon.PIO[0][30], mux); + ck_assert_int_eq(attach_calls, 1); + ck_assert_int_eq(attached_clk, kFRO12M_to_FLEXCOMM0); + ck_assert_int_eq(fc0_unreset, 1); + ck_assert_int_eq(init_calls, 1); + ck_assert_ptr_eq(init_base, &mock_usart0); + ck_assert_uint_eq(init_cfg.baudRate_Bps, 115200U); + ck_assert(init_cfg.enableTx); + ck_assert(init_cfg.enableRx); + ck_assert_uint_eq(init_clk_hz, 12000000U); +} +END_TEST + +START_TEST(test_uart_write_adds_cr_before_lf) +{ + uart_write("a\nb\n", 4); + ck_assert_uint_eq(tx_len, 6); + ck_assert_mem_eq(tx, "a\r\nb\r\n", 6); + ck_assert_int_eq(tx_bad_base, 0); +} +END_TEST + +START_TEST(test_uart_write_plain_and_bounds) +{ + uart_write("hello", 5); + ck_assert_uint_eq(tx_len, 5); + ck_assert_mem_eq(tx, "hello", 5); + + tx_len = 0; + uart_write("\n\n", 2); + ck_assert_uint_eq(tx_len, 4); + ck_assert_mem_eq(tx, "\r\n\r\n", 4); + + tx_len = 0; + uart_write("xyz", 0); + uart_write(NULL, 3); + ck_assert_uint_eq(tx_len, 0); +} +END_TEST + +static Suite *lpc55s69_uart_suite(void) +{ + Suite *s = suite_create("lpc55s69-uart"); + TCase *tc = tcase_create("lpc55s69-uart"); + + tcase_add_checked_fixture(tc, setup, NULL); + tcase_add_test(tc, test_uart_init_console_pins_and_baud); + tcase_add_test(tc, test_uart_write_adds_cr_before_lf); + tcase_add_test(tc, test_uart_write_plain_and_bounds); + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s = lpc55s69_uart_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + + return fails; +}