diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 78005a1fad..72d9eea3e8 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -1054,6 +1054,39 @@ jobs: arch: arm config-file: ./config/examples/stm32c5.config + psoc_e8_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + config-file: ./config/examples/psoc_e8_ai.config + + psoc_e8_wolfcrypt_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + config-file: ./config/examples/psoc_e8_ai.config + make-args: WOLFCRYPT_TEST=1 WOLFCRYPT_BENCHMARK=1 + + psoc_e8_tz_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + config-file: ./config/examples/psoc_e8_ai-tz.config + + psoc_e8_tz_wolfhsm_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + config-file: ./config/examples/psoc_e8_ai-tz-wolfhsm.config + + # wolfBoot linked into RRAM, partitions still in QSPI. + psoc_e8_rram_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + config-file: ./config/examples/psoc_e8_ai.config + make-args: PSOC_E8_BOOT_RRAM=1 + psoc_c3_test: uses: ./.github/workflows/test-build.yml with: diff --git a/.gitignore b/.gitignore index 3a07d3512c..21b230e806 100644 --- a/.gitignore +++ b/.gitignore @@ -144,6 +144,8 @@ tools/unit-tests/unit-aes256 tools/unit-tests/unit-chacha20 tools/unit-tests/unit-delta tools/unit-tests/unit-psoc-c3-divider +tools/unit-tests/unit-psoc-e8-tz +tools/unit-tests/unit-psoc-e8-flash tools/unit-tests/unit-disk tools/unit-tests/unit-fs-probe tools/unit-tests/unit-fat32 diff --git a/Makefile b/Makefile index e15961d8a3..3d7c05f0e3 100644 --- a/Makefile +++ b/Makefile @@ -408,6 +408,11 @@ ifeq ($(TARGET),mcxn) endif endif # TZEN=1 +ifeq ($(TARGET),psoc_e8) + # wolfBoot is programmed with its boot header, not as a contiguous image + MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin +endif + ifeq ($(TARGET),pic32cz) MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin endif diff --git a/arch.mk b/arch.mk index 15c5aebb36..d51ab14d89 100644 --- a/arch.mk +++ b/arch.mk @@ -758,6 +758,68 @@ ifeq ($(ARCH),ARM) CFLAGS+=-DPSOC_C3_I2C_HSIOM_SEL=$(PSOC_C3_I2C_HSIOM_SEL) CFLAGS+=-DPSOC_C3_I2C_HZ=$(PSOC_C3_I2C_HZ) endif + ifeq ($(TARGET),psoc_e8) + CORTEX_M33=1 + CFLAGS+=-Ihal + # Extended boot runs the first CM33 image in place, after a 0x400 boot + # header: from QSPI by default, or from RRAM with PSOC_E8_BOOT_RRAM=1 + # (needs the boot policy reprovisioned). ARCH_FLASH_OFFSET is the slot, + # which also holds the header's 0x28-byte SHA-256 trailer after wolfBoot. + PSOC_E8_BOOT_RRAM?=0 + ifeq ($(PSOC_E8_BOOT_RRAM),1) + PSOC_E8_RRAM_SLOT_SIZE?=0x20000 + ARCH_FLASH_OFFSET=0x12011000 + WOLFBOOT_ORIGIN=0x12011400 + BOOTLOADER_PARTITION_SIZE=$$(( $(PSOC_E8_RRAM_SLOT_SIZE) - 0x400 - 0x40)) + else + ARCH_FLASH_OFFSET=0x18100000 + WOLFBOOT_ORIGIN=0x18100400 + # The boot partition is in the Non-secure alias with TZEN=1. + BOOTLOADER_PARTITION_SIZE=$$(( ($(WOLFBOOT_PARTITION_BOOT_ADDRESS) | 0x10000000) - $(WOLFBOOT_ORIGIN) - 0x40)) + endif + ifeq ($(TZEN),1) + LSCRIPT_IN=hal/$(TARGET)-tz.ld + OBJS+=hal/psoc_e8_tz.o + # wolfHSM NVM in the RRAM user area, Secure. + WOLFBOOT_KEYVAULT_ADDRESS?=0x32040000 + WOLFBOOT_KEYVAULT_SIZE?=0x10000 + else + LSCRIPT_IN=hal/$(TARGET).ld + endif + # Board wiring, defaulted for KIT_PSE84_AI. + PSOC_E8_UART_SCB?=2 + PSOC_E8_UART_PORT?=6 + PSOC_E8_UART_RX_PIN?=5 + PSOC_E8_UART_TX_PIN?=7 + PSOC_E8_UART_HSIOM_SEL?=20 + PSOC_E8_UART_PCLK_GR?=1 + PSOC_E8_UART_PCLK_IDX?=1 + PSOC_E8_PCLK_HZ?=100000000 + PSOC_E8_QSPI_SECTOR_SIZE?=0x40000 + PSOC_E8_QSPI_SIZE?=0x4000000 + PSOC_E8_CPU_HZ?=200000000 + CFLAGS+=-DPSOC_E8_UART_SCB=$(PSOC_E8_UART_SCB) + CFLAGS+=-DPSOC_E8_UART_PORT=$(PSOC_E8_UART_PORT) + CFLAGS+=-DPSOC_E8_UART_RX_PIN=$(PSOC_E8_UART_RX_PIN) + CFLAGS+=-DPSOC_E8_UART_TX_PIN=$(PSOC_E8_UART_TX_PIN) + CFLAGS+=-DPSOC_E8_UART_HSIOM_SEL=$(PSOC_E8_UART_HSIOM_SEL) + CFLAGS+=-DPSOC_E8_UART_PCLK_GR=$(PSOC_E8_UART_PCLK_GR) + CFLAGS+=-DPSOC_E8_UART_PCLK_IDX=$(PSOC_E8_UART_PCLK_IDX) + CFLAGS+=-DPSOC_E8_PCLK_HZ=$(PSOC_E8_PCLK_HZ) + CFLAGS+=-DPSOC_E8_QSPI_SECTOR_SIZE=$(PSOC_E8_QSPI_SECTOR_SIZE) + # QSPI through src/qspi_flash.c, single-line with 4-byte opcodes. The + # driver runs from RAM because wolfBoot executes from the same device; it + # is used directly, not as EXT_FLASH, so the partitions stay in place. + OBJS+=src/qspi_flash.o + CFLAGS+=-DQSPI_FLASH -DQSPI_FLASH_RAM_CODE -DQSPI_ADDR_SZ=4 + CFLAGS+=-DQSPI_NO_4BYTE_MODE + CFLAGS+=-DQSPI_DATA_MODE=QSPI_DATA_MODE_SPI + CFLAGS+=-DPAGE_PROG_CMD=0x12 -DSEC_ERASE_CMD=0xDC + CFLAGS+=-DFLASH_DEVICE_SIZE=$(PSOC_E8_QSPI_SIZE) + CFLAGS+=-DSPI_FLASH_SECTOR_SIZE=$(PSOC_E8_QSPI_SECTOR_SIZE) + CFLAGS+=-DQSPI_FLASH_READY_TRIES=4000000 + CFLAGS+=-DPSOC_E8_CPU_HZ=$(PSOC_E8_CPU_HZ) + endif ifeq ($(TARGET),m2354) CORTEX_M23=1 CFLAGS+=-Ihal diff --git a/config/examples/psoc_e8_ai-tz-wolfhsm.config b/config/examples/psoc_e8_ai-tz-wolfhsm.config new file mode 100644 index 0000000000..bfb0f0fec2 --- /dev/null +++ b/config/examples/psoc_e8_ai-tz-wolfhsm.config @@ -0,0 +1,50 @@ +ARCH?=ARM +TARGET?=psoc_e8 +SIGN?=ECC256 +HASH?=SHA256 +DEBUG?=0 +VTOR?=1 +CORTEX_M33?=1 +TZEN?=1 +NO_ASM?=0 +NO_MPU=1 +EXT_FLASH?=0 +SPI_FLASH?=0 +ALLOW_DOWNGRADE?=0 +# The QSPI NOR rejects a second program into a 16-byte ECC unit. +NVM_FLASH_WRITEONCE?=1 +WOLFBOOT_VERSION?=1 +V?=0 +SPMATH?=1 +RAM_CODE?=1 +DUALBANK_SWAP?=0 +DEBUG_UART=1 +# Keeps the application vector table 1 KB aligned. +IMAGE_HEADER_SIZE?=1024 + +# KIT_PSE84_AI with TrustZone and a wolfHSM server in the Secure world. The +# Non-secure application is the wolfHSM client, over the NSC transport, and +# uses SRAM1 (0x24080000). 64 MB QSPI NOR, 256 KB sectors; the boot partition +# is in the Non-secure code-bus alias, the rest in the Secure one. Erase the +# key store once with psoc_e8_flash.sh --erase-keyvault. +# +# 0x32040000 - 0x3204FFFF RRAM wolfHSM NVM (WOLFBOOT_KEYVAULT_*) +# 0x34058000 - 0x3407FFFF SRAM0 top, Secure wolfHSM heap +# 0x18100000 - 0x1813BFFF wolfBoot slot: 0x400 boot header + wolfBoot +# 0x1813C000 - 0x1813FFFF NSC gateway veneers +# 0x08140000 - 0x0823FFFF BOOT (1 MB, Non-secure) +# 0x18240000 - 0x1833FFFF UPDATE (1 MB) +# 0x18340000 - 0x1837FFFF SWAP (one sector) +# +# Board wiring: PSOC_E8_* make options in arch.mk, see docs/Targets.md. +WOLFBOOT_SECTOR_SIZE?=0x40000 +WOLFBOOT_PARTITION_SIZE?=0x100000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x08140000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x18240000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x18340000 +WOLFBOOT_NSC_ADDRESS?=0x1813C000 +WOLFBOOT_NSC_SIZE?=0x4000 +WOLFCRYPT_TZ=1 +WOLFCRYPT_TZ_WOLFHSM=1 +WOLFBOOT_KEYVAULT_ADDRESS?=0x32040000 +WOLFBOOT_KEYVAULT_SIZE?=0x10000 diff --git a/config/examples/psoc_e8_ai-tz.config b/config/examples/psoc_e8_ai-tz.config new file mode 100644 index 0000000000..ecab874e9a --- /dev/null +++ b/config/examples/psoc_e8_ai-tz.config @@ -0,0 +1,43 @@ +ARCH?=ARM +TARGET?=psoc_e8 +SIGN?=ECC256 +HASH?=SHA256 +DEBUG?=0 +VTOR?=1 +CORTEX_M33?=1 +TZEN?=1 +NO_ASM?=0 +NO_MPU=1 +EXT_FLASH?=0 +SPI_FLASH?=0 +ALLOW_DOWNGRADE?=0 +# The QSPI NOR rejects a second program into a 16-byte ECC unit. +NVM_FLASH_WRITEONCE?=1 +WOLFBOOT_VERSION?=1 +V?=0 +SPMATH?=1 +RAM_CODE?=1 +DUALBANK_SWAP?=0 +DEBUG_UART=1 +# Keeps the application vector table 1 KB aligned. +IMAGE_HEADER_SIZE?=1024 + +# KIT_PSE84_AI with TrustZone: wolfBoot Secure, the application Non-secure. +# 64 MB QSPI NOR, 256 KB sectors. The boot partition is in the Non-secure +# code-bus alias, the rest in the Secure one. The application uses SRAM1 +# (0x24080000) and prints through a Secure console gateway. +# +# 0x18100000 - 0x1813BFFF wolfBoot slot: 0x400 boot header + wolfBoot +# 0x1813C000 - 0x1813FFFF NSC gateway veneers +# 0x08140000 - 0x0823FFFF BOOT (1 MB, Non-secure) +# 0x18240000 - 0x1833FFFF UPDATE (1 MB) +# 0x18340000 - 0x1837FFFF SWAP (one sector) +# +# Board wiring: PSOC_E8_* make options in arch.mk, see docs/Targets.md. +WOLFBOOT_SECTOR_SIZE?=0x40000 +WOLFBOOT_PARTITION_SIZE?=0x100000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x08140000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x18240000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x18340000 +WOLFBOOT_NSC_ADDRESS?=0x1813C000 +WOLFBOOT_NSC_SIZE?=0x4000 diff --git a/config/examples/psoc_e8_ai.config b/config/examples/psoc_e8_ai.config new file mode 100644 index 0000000000..6ba4ce5aec --- /dev/null +++ b/config/examples/psoc_e8_ai.config @@ -0,0 +1,37 @@ +ARCH?=ARM +TARGET?=psoc_e8 +SIGN?=ECC256 +HASH?=SHA256 +DEBUG?=0 +VTOR?=1 +CORTEX_M33?=1 +TZEN?=0 +NO_ASM?=0 +NO_MPU=1 +EXT_FLASH?=0 +SPI_FLASH?=0 +ALLOW_DOWNGRADE?=0 +# The QSPI NOR rejects a second program into a 16-byte ECC unit. +NVM_FLASH_WRITEONCE?=1 +WOLFBOOT_VERSION?=1 +V?=0 +SPMATH?=1 +RAM_CODE?=1 +DUALBANK_SWAP?=0 +DEBUG_UART=1 +# Keeps the application vector table 1 KB aligned. +IMAGE_HEADER_SIZE?=1024 + +# KIT_PSE84_AI: 64 MB QSPI NOR, 256 KB sectors, Secure code-bus alias. +# +# 0x18100000 - 0x1813FFFF wolfBoot slot: 0x400 boot header + wolfBoot +# 0x18140000 - 0x1823FFFF BOOT (1 MB) +# 0x18240000 - 0x1833FFFF UPDATE (1 MB) +# 0x18340000 - 0x1837FFFF SWAP (one sector) +# +# Board wiring: PSOC_E8_* make options in arch.mk, see docs/Targets.md. +WOLFBOOT_SECTOR_SIZE?=0x40000 +WOLFBOOT_PARTITION_SIZE?=0x100000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x18140000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x18240000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x18340000 diff --git a/docs/Targets.md b/docs/Targets.md index 6d328ad7ff..7739aa726d 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -11,6 +11,7 @@ This README describes configuration of supported targets. * [Cortex-A72 / Raspberry Pi Compute Module 4](#cortex-a72--raspberry-pi-compute-module-4-bcm2711) * [Cypress PSoC-6](#cypress-psoc-6) * [Infineon PSOC Control C3](#infineon-psoc-control-c3) +* [Infineon PSOC Edge E8](#infineon-psoc-edge-e8) * [Infineon AURIX TC3xx](#infineon-aurix-tc3xx) * [Infineon AURIX TC4xx](#infineon-aurix-tc4xx) * [Intel x86-64 Intel FSP](#intel-x86_64-with-intel-fsp-support) @@ -10026,6 +10027,99 @@ A TPM that offers both buses latches its choice from a strap while its own reset Every FIFO and bus wait in both drivers is bounded, so a device that is absent or unpowered makes `wolfBoot_tpm2_init()` report a failure rather than wedging the bootloader in a spin loop. +## Infineon PSOC Edge E8 + +The Infineon PSOC Edge E8 family (PSE8x, CAT1D) pairs a Cortex-M33 with TrustZone and a Cortex-M55 behind a Secure Enclave, which runs the boot ROM and Infineon's extended boot. wolfBoot runs on the CM33 in the Secure state as the first image extended boot launches. The port is bare metal and needs no Peripheral Driver Library or BSP. Board differences are make options, and `config/examples/psoc_e8_ai.config` describes KIT_PSE84_AI. + +Verified on KIT_PSE84_AI (PSE846GPS2DBZC4A): boot from QSPI under the factory extended boot, ECC256 verification and handoff, A/B update with confirmation, resumption of an update interrupted mid-swap, RRAM write and erase, and the wolfCrypt test and benchmark. With TrustZone, the Non-secure application booting from the Secure wolfBoot, the A/B update through the gateway calls, Secure-memory isolation and the wolfHSM client test suite against the Secure server were verified on the same board. Booting from RRAM (`PSOC_E8_BOOT_RRAM=1`) is build-tested only. The CM55 is not started. + +### Boot chain and memory + +Extended boot runs the first CM33 image in place from QSPI at `0x60100000`. It requires a 0x400-byte header and a trailing SHA-256 of header and image (an unsigned, MCUboot-compatible format), which `tools/scripts/psoc_e8/psoc_e8_boot_header.py` adds to `wolfboot.bin`. + +Each memory has four aliases, Secure/Non-secure by code/system bus. wolfBoot links at `0x18100400`; the layout below is the non-TrustZone configuration and uses the Secure code-bus QSPI alias: + +``` +RRAM 0x02000000 NS code 0x12000000 S code 0x22000000 NS sys 0x32000000 S sys +QSPI 0x08000000 NS code 0x18000000 S code 0x60000000 NS sys 0x70000000 S sys + +0x18100000 - 0x1813FFFF wolfBoot slot (boot header + wolfBoot) +0x18140000 - 0x1823FFFF BOOT partition (1 MB) +0x18240000 - 0x1833FFFF UPDATE partition (1 MB) +0x18340000 - 0x1837FFFF SWAP sector (256 KB) +``` + +`IMAGE_HEADER_SIZE=1024` keeps the application vector table on the 1 KB boundary the CM33 requires. + +### QSPI flash + +QSPI is programmed through the generic `src/qspi_flash.c` driver, without `EXT_FLASH`, so the partitions stay in place for execute-in-place. The HAL supplies its `qspi_transfer()` over the SMIF MMIO command interface, using the device extended boot configured, and the driver is built with `QSPI_FLASH_RAM_CODE`, dedicated 4-byte opcodes and `QSPI_NO_4BYTE_MODE` so the part stays in the address mode extended boot expects. Writes run from RAM with interrupts masked, stage each page in RAM (the source is often QSPI itself), and invalidate the SMIF and CM33 caches afterwards. The test application runs from the QSPI it programs, so its flash path is also placed in RAM. + +The NOR keeps ECC over 16-byte units and rejects a second program into a unit, so the config sets `NVM_FLASH_WRITEONCE=1`. With 256 KB sectors this costs a sector-sized RAM buffer. A rejected program leaves the part busy across a warm reset, where extended boot can no longer read it, so the HAL clears the failure flags when it sees them. + +### Build options + +wolfBoot does not change the clocks extended boot sets. The console is SCB2 on P6.5/P6.7, the KitProg3 COM port, at 115200 baud. + +| Option | Default | Purpose | +| --- | --- | --- | +| `PSOC_E8_UART_SCB` | 2 | console SCB instance | +| `PSOC_E8_UART_PORT` / `_RX_PIN` / `_TX_PIN` | 6 / 5 / 7 | console pins | +| `PSOC_E8_UART_HSIOM_SEL` | 20 | HSIOM selector for the UART | +| `PSOC_E8_UART_PCLK_GR` / `_IDX` | 1 / 1 | console peripheral clock group and index | +| `PSOC_E8_PCLK_HZ` | 100000000 | CLK_HF10, feeding that group | +| `PSOC_E8_CPU_HZ` | 200000000 | CLK_HF0, test-app benchmark time base | +| `PSOC_E8_QSPI_SECTOR_SIZE` | 0x40000 | QSPI erase sector | +| `PSOC_E8_QSPI_SIZE` | 0x4000000 | QSPI device size | +| `PSOC_E8_BOOT_RRAM` | 0 | link wolfBoot into RRAM instead of QSPI | +| `PSOC_E8_RRAM_SLOT_SIZE` | 0x20000 | RRAM slot with `PSOC_E8_BOOT_RRAM=1` | + +### Building and flashing + +```sh +cp config/examples/psoc_e8_ai.config .config +make clean && make +tools/scripts/psoc_e8/psoc_e8_flash.sh +``` + +The script adds the boot header to `wolfboot.bin` and programs it and `test-app/image_v1_signed.bin` over the KitProg3, with the ModusToolbox Programming Tools OpenOCD (`MTB_PROGTOOLS`, default `/opt/Tools/ModusToolboxProgtools-1.6`). Set `KITPROG_SERIAL` if several probes are attached. This replaces the factory CM33 image, so read QSPI back first to keep the stock demo. + +To test an update, sign v2 and program it into UPDATE. The v1 application triggers the update, and v2 confirms itself: + +```sh +IMAGE_HEADER_SIZE=1024 ./tools/keytools/sign --ecc256 --sha256 \ + test-app/image.bin wolfboot_signing_private_key.der 2 +tools/scripts/psoc_e8/psoc_e8_flash.sh --update test-app/image_v2_signed.bin +``` + +`make WOLFCRYPT_TEST=1 WOLFCRYPT_BENCHMARK=1` adds the software wolfCrypt test and benchmark to the test application. Its RNG seed is deterministic and only for testing. + +### TrustZone and wolfHSM + +`config/examples/psoc_e8_ai-tz.config` runs wolfBoot in the Secure state and the application Non-secure. `config/examples/psoc_e8_ai-tz-wolfhsm.config` adds a wolfHSM server in the Secure world (`WOLFCRYPT_TZ_WOLFHSM=1`), which the Non-secure application reaches as a wolfHSM client through one gateway call, as on STM32H5 (see [wolfHSM.md](wolfHSM.md)). + +``` +0x18100000 - 0x1813BFFF wolfBoot slot (Secure) +0x1813C000 - 0x1813FFFF NSC gateway veneers +0x08140000 - 0x0823FFFF BOOT partition, Non-secure alias +0x18240000 - 0x1837FFFF UPDATE and SWAP, Secure +0x24080000 - 0x240FFFFF SRAM1, Non-secure application RAM +0x34058000 - 0x3407FFFF SRAM0 top, Secure wolfHSM heap +0x32040000 - 0x3204FFFF RRAM, wolfHSM NVM (WOLFBOOT_KEYVAULT_*) +``` + +The CM33 runs both worlds in protection context 2, so `hal_init()` grants that context Non-secure access to the boot partition in the SMIF memory protection controller and to SRAM1 in its controller, and adds SAU regions for those and the gateway. wolfBoot then reads and verifies the boot partition through its Non-secure alias. Peripherals stay Secure: the application updates through the `wolfBoot_nsc_*` calls and prints through a console gateway. The crypto block's TRNG, with both health monitors running, seeds the Secure DRBG. + +Fresh RRAM reads as zero, which the wolfHSM NVM does not accept as empty, so program a board once with `--erase-keyvault`, which fills the key store with 0xFF and destroys anything stored there: + +```sh +tools/scripts/psoc_e8/psoc_e8_flash.sh --erase-keyvault +``` + +### Booting from RRAM + +`PSOC_E8_BOOT_RRAM=1` links wolfBoot at `0x12011400` in the RRAM user area, keeping the partitions in QSPI. Extended boot only launches it once the boot policy is reprovisioned with Infineon's Edge Protect Tools, which this port does not automate. RRAM is written in 16-byte blocks through the RRAMC indirect-write action, under the IPC and PC_LOCK locks. It has no erase, so `hal_flash_erase()` fills the range with 0xFF to read like erased NOR. + ## Infineon AURIX TC3xx wolfBoot supports the Infineon AURIX TC3xx family and includes a demo application for the TC375 AURIX LiteKit-V2. It can be configured to run on either the TriCore application cores or the HSM core. diff --git a/docs/wolfHSM.md b/docs/wolfHSM.md index dd3b052a93..9d0183cb99 100644 --- a/docs/wolfHSM.md +++ b/docs/wolfHSM.md @@ -23,6 +23,7 @@ wolfBoot supports using wolfHSM on the following platforms: - AURIX TC3xx (shared memory transport) - Microchip PIC32CZ CA9x (shared memory transport) - STM32H5 TrustZone (the secure-side wolfBoot hosts a wolfHSM server and exposes it to the non-secure application through a single NSC veneer; see [STM32H5 TrustZone Engine](#stm32h5-trustzone-engine) below) +- Infineon PSOC Edge E8 TrustZone (the same engine, with the NVM in RRAM; see the PSOC Edge E8 section of [Targets.md](Targets.md)) Details on configuring wolfBoot to use wolfHSM on each of these platforms can be found in the wolfBoot (and wolfHSM) documentation specific to that target, with the exception of the simulator, which is documented here. The remainder of this document focuses on the generic wolfHSM-related configuration options. @@ -315,4 +316,6 @@ The default build prints a final `WOLFHSM_TZ_TEST_PASS` UART line on success. Th ### Notes +The same engine runs on Infineon PSOC Edge E8 (`config/examples/psoc_e8_ai-tz-wolfhsm.config`), with the NVM in RRAM through the same flash adapter. + The wolfHSM NVM lives in the existing `FLASH_KEYVAULT` region (112 KiB at `0x0C040000`) shared with the other STM32H5 TrustZone engines. The flash adapter (`src/wolfhsm_flash_hal.c`) caches the affected sector, modifies it, and rewrites the whole 8 KiB sector in one erase + program cycle, mirroring `psa_store.c` / `pkcs11_store.c`. This satisfies the H5 quad-word ECC rule that each 16-byte unit may be programmed exactly once between erases, which wolfHSM's 8-byte-unit writes would otherwise violate. diff --git a/hal/psoc_e8-tz.ld b/hal/psoc_e8-tz.ld new file mode 100644 index 0000000000..92dc5cf03e --- /dev/null +++ b/hal/psoc_e8-tz.ld @@ -0,0 +1,73 @@ +MEMORY +{ + FLASH (rx) : ORIGIN = @WOLFBOOT_ORIGIN@, LENGTH = @WOLFBOOT_NSC_ADDRESS@ - @WOLFBOOT_ORIGIN@ + FLASH_NSC (rx) : ORIGIN = @WOLFBOOT_NSC_ADDRESS@, LENGTH = @WOLFBOOT_ORIGIN@ + @BOOTLOADER_PARTITION_SIZE@ - @WOLFBOOT_NSC_ADDRESS@ + RAM (rwx) : ORIGIN = 0x34002000, LENGTH = 0x00056000 + RAM_HEAP (rw) : ORIGIN = 0x34058000, LENGTH = 0x00028000 + FLASH_KEYVAULT (rw) : ORIGIN = @WOLFBOOT_KEYVAULT_ADDRESS@, LENGTH = @WOLFBOOT_KEYVAULT_SIZE@ +} + +SECTIONS +{ + .text : + { + _start_text = .; + KEEP(*(.isr_vector)) + *(.text*) + *(.rodata*) + . = ALIGN(4); + _end_text = .; + } > FLASH + + .edidx : + { + . = ALIGN(4); + *(.ARM.exidx*) + } > FLASH + + .gnu.sgstubs ORIGIN(FLASH_NSC) : + { + . = ALIGN(32); + *(.gnu.sgstubs*) + . = ALIGN(4); + } > FLASH_NSC + + .keystore : + { + . = ALIGN(4); + KEEP(*(.keystore*)) + } > FLASH + + _stored_data = .; + + .data : AT (_stored_data) + { + _start_data = .; + KEEP(*(.data*)) + . = ALIGN(4); + KEEP(*(.ramcode)) + . = ALIGN(4); + _end_data = .; + } > RAM + + .bss (NOLOAD) : + { + _start_bss = .; + __bss_start__ = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _end_bss = .; + __bss_end__ = .; + _end = .; + } > RAM + + . = ALIGN(4); +} + +END_STACK = ORIGIN(RAM) + LENGTH(RAM); + +_flash_keyvault = ORIGIN(FLASH_KEYVAULT); +_flash_keyvault_size = LENGTH(FLASH_KEYVAULT); +_start_heap = ORIGIN(RAM_HEAP); +_heap_size = LENGTH(RAM_HEAP); diff --git a/hal/psoc_e8.c b/hal/psoc_e8.c new file mode 100644 index 0000000000..56aa6b2f9a --- /dev/null +++ b/hal/psoc_e8.c @@ -0,0 +1,652 @@ +/* psoc_e8.c + * + * HAL for Infineon PSOC Edge E8 (CAT1D, Cortex-M33). + * + * 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 "image.h" +#include "hal.h" +#include "printf.h" +#include "hal/psoc_e8.h" +#include "spi_drv.h" +#include "spi_flash.h" +#include + +#define PSOC_E8_UART_SCB_BASE PSOC_E8_SCB_BASE(PSOC_E8_UART_SCB) + +#ifndef NONSECURE_APP + +static int peri_init_done; + +/* Ungate PERI0 group 1, which holds the SCBs. Ungating groups 3-5 as well + * takes the core off the debug port. */ +void psoc_e8_peri_init(void) +{ + if (peri_init_done) + return; + peri_init_done = 1; + + PERI_GR_SL_CTL2(PSOC_E8_PERI_GR_SCB) = 0; + PERI_GR_SL_CTL(PSOC_E8_PERI_GR_SCB) = 0xFFFFFFFFUL; + DSB(); +} + +void psoc_e8_pin_setup(int port, int pin, uint32_t sel, uint32_t drive) +{ + uint32_t v; + + HSIOM_SECURE_PRT_NSMASK(port) &= ~(1UL << pin); + + v = HSIOM_PORT_SEL(port, pin); + v &= ~(0xFFUL << HSIOM_SEL_SHIFT(pin)); + v |= (sel << HSIOM_SEL_SHIFT(pin)); + HSIOM_PORT_SEL(port, pin) = v; + + v = GPIO_PRT_CFG(port); + v &= ~(GPIO_CFG_DM_MASK << ((uint32_t)pin * 4U)); + v |= (drive << ((uint32_t)pin * 4U)); + GPIO_PRT_CFG(port) = v; +} + +/* Route a 16.5 fractional divider at target_hz to a clock destination. An + * unclocked peripheral bus-faults, so a failure here must stop the caller. */ +int psoc_e8_pclk_setup(uint32_t gr, uint32_t idx, uint32_t div, + uint32_t pclk_hz, uint32_t target_hz) +{ + uint32_t timeout = PSOC_E8_PERI_TIMEOUT; + uint32_t scaled; + + if (target_hz == 0U) + return -1; + + /* The divider produces pclk / (int + 1 + frac / 32). */ + scaled = (uint32_t)(((uint64_t)pclk_hz * 32U) / target_hz); + if (scaled < 32U) + scaled = 32U; + if (scaled > (0x10000UL * 32U)) + scaled = 0x10000UL * 32U; + + PERI_PCLK_DIV_16_5_CTL(gr, div) = + (((scaled / 32U) - 1U) << PERI_PCLK_DIV_16_5_INT_Pos) | + ((scaled % 32U) << PERI_PCLK_DIV_16_5_FRAC_Pos); + PERI_PCLK_CLOCK_CTL(gr, idx) = + (PERI_PCLK_DIV_TYPE_16_5 << PERI_PCLK_CLOCK_CTL_TYPE_Pos) | div; + PERI_PCLK_DIV_CMD(gr) = PERI_PCLK_DIV_CMD_ENABLE | + PERI_PCLK_DIV_CMD_PA_NONE | + (PERI_PCLK_DIV_TYPE_16_5 << PERI_PCLK_DIV_CMD_TYPE_Pos) | div; + + while (timeout > 0) { + if ((PERI_PCLK_DIV_16_5_CTL(gr, div) & PERI_PCLK_DIV_16_5_EN) != 0) + return 0; + timeout--; + } + return -1; +} + +static int RAMFUNCTION smif_wait_fifo(volatile uint32_t *status) +{ + uint32_t timeout = PSOC_E8_PERI_TIMEOUT; + + while (((*status & SMIF_FIFO_USED_Msk) >= SMIF_FIFO_DEPTH) && + (timeout > 0)) + timeout--; + return (timeout == 0) ? -1 : 0; +} + +static int RAMFUNCTION smif_push_cmd(uint32_t entry) +{ + if (smif_wait_fifo(&SMIF_TX_CMD_STATUS) != 0) + return -1; + SMIF_TX_CMD_WR = entry; + return 0; +} + +/* Single-line opcode plus addr_len address bytes, MSB first. last releases + * chip select after the final byte; otherwise a data phase follows. */ +static int RAMFUNCTION smif_send(int dev, uint8_t op, uint32_t addr, + uint32_t addr_len, int last) +{ + uint32_t ss = SMIF_CMD_SS(dev) | SMIF_CMD_MODE_TX; + uint32_t i; + + if (smif_push_cmd(ss | ((last && (addr_len == 0U)) ? SMIF_CMD_LAST : 0) | + op) != 0) + return -1; + for (i = addr_len; i > 0U; i--) { + if (smif_push_cmd(ss | ((last && (i == 1U)) ? SMIF_CMD_LAST : 0) | + ((addr >> ((i - 1U) * 8U)) & 0xFFU)) != 0) + return -1; + } + return 0; +} + +/* The device extended boot configured for XIP. */ +static int RAMFUNCTION smif_device(void) +{ + int n; + + for (n = 0; n < SMIF_DEVICES; n++) { + if ((SMIF_DEVICE_CTL(n) & SMIF_DEVICE_CTL_ENABLED) != 0) + return n; + } + return -1; +} + +/* Drop stale XIP lines from the SMIF cache and the CM33 I-cache. */ +static void RAMFUNCTION smif_invalidate_caches(void) +{ + uint32_t timeout = PSOC_E8_PERI_TIMEOUT; + + SMIF_CACHE_MAINT_ALL = SMIF_CACHE_INVALIDATE_ALL; + while (((SMIF_CACHE_MAINT_STATUS & SMIF_CACHE_MAINT_ONGOING) != 0) && + (timeout > 0)) + timeout--; + SMIF_SLOW_CA_CMD = SMIF_CA_CMD_INV; + SMIF_FAST_CA_CMD = SMIF_CA_CMD_INV; + ICACHE0_CMD = ICACHE_CMD_INV; + DSB(); + ISB(); +} + +static int RAMFUNCTION smif_wait_idle(void) +{ + uint32_t timeout = PSOC_E8_PERI_TIMEOUT; + + while (((SMIF_STATUS & SMIF_STATUS_BUSY) != 0) && (timeout > 0)) + timeout--; + return (timeout == 0) ? -1 : 0; +} + +/* src/qspi_flash.c bus hooks. Extended boot has already configured SMIF0 and + * the device for XIP, so there is nothing to set up or release. */ +void spi_init(int polarity, int phase) +{ + (void)polarity; + (void)phase; +} + +void spi_release(void) +{ +} + +/* Set when a status read shows a failed program or erase. The flags are + * cleared on the spot, so the driver's next poll reads ready; the HAL checks + * this to report the failure. */ +static int qspi_failed; + +/* Program data is staged here first: the source is often in XIP flash + * itself, and an XIP read while a page program waits for its data + * deadlocks. */ +static uint8_t qspi_page[SPI_FLASH_PAGE_SIZE]; + +/* qspi_transfer() for src/qspi_flash.c over the SMIF MMIO command interface, + * single-line only. Extended boot leaves XIP enabled; MMIO commands run + * alongside it while nothing executes from the device. */ +int RAMFUNCTION qspi_transfer(uint8_t fmode, const uint8_t cmd, + uint32_t addr, uint32_t addrSz, uint32_t addrMode, + uint32_t alt, uint32_t altSz, uint32_t altMode, + uint32_t dummySz, + uint8_t *data, uint32_t dataSz, uint32_t dataMode) +{ + int dev = smif_device(); + uint32_t timeout; + uint32_t i; + + (void)addrMode; + (void)alt; + (void)altMode; + (void)dataMode; + if ((dev < 0) || (altSz != 0U) || (dummySz != 0U) || + ((fmode == QSPI_MODE_WRITE) && (dataSz > sizeof(qspi_page))) || + ((dataSz != 0U) && (data == NULL))) + return -1; + if (fmode == QSPI_MODE_WRITE) { + for (i = 0; i < dataSz; i++) + qspi_page[i] = data[i]; + } + if ((smif_wait_idle() != 0) || + (smif_send(dev, cmd, addr, addrSz, (dataSz == 0U)) != 0)) + return -1; + if (dataSz == 0U) + return 0; + + if (fmode == QSPI_MODE_WRITE) { + if (smif_push_cmd(SMIF_CMD_MODE_TX_COUNT | SMIF_CMD_LAST | + (dataSz - 1U)) != 0) + return -1; + for (i = 0; i < dataSz; i++) { + if (smif_wait_fifo(&SMIF_TX_DATA_STATUS) != 0) + return -1; + SMIF_TX_DATA_WR1 = qspi_page[i]; + } + return 0; + } + + if (smif_push_cmd(SMIF_CMD_MODE_RX_COUNT | SMIF_CMD_LAST | + (dataSz - 1U)) != 0) + return -1; + /* The receiver needs 1 us to enter receive mode before its FIFO status + * is valid. */ + psoc_e8_delay_us(1); + for (i = 0; i < dataSz; i++) { + timeout = PSOC_E8_PERI_TIMEOUT; + while (((SMIF_RX_DATA_STATUS & SMIF_FIFO_USED_Msk) == 0U) && + (timeout > 0U)) + timeout--; + if (timeout == 0U) + return -1; + data[i] = (uint8_t)SMIF_RX_DATA_RD1; + } + /* A failed program or erase (e.g. a second program into a 16-byte ECC + * unit) leaves the part busy until its failure flags are cleared, even + * across a warm reset, where extended boot can then no longer read it. */ + if ((cmd == NOR_CMD_RDSR) && + ((data[0] & (NOR_SR_E_ERR | NOR_SR_P_ERR)) != 0U)) { + (void)smif_send(dev, NOR_CMD_CLPEF, 0, 0, 1); + qspi_failed = 1; + return -1; + } + return 0; +} + +static uint32_t RAMFUNCTION irq_save(void) +{ + uint32_t primask = 0; + +#if defined(__arm__) || defined(__ARM_ARCH) + __asm__ volatile ("mrs %0, primask" : "=r" (primask)); + __asm__ volatile ("cpsid i" ::: "memory"); +#endif + return primask; +} + +static void RAMFUNCTION irq_restore(uint32_t primask) +{ +#if defined(__arm__) || defined(__ARM_ARCH) + __asm__ volatile ("msr primask, %0" :: "r" (primask) : "memory"); +#else + (void)primask; +#endif +} + +static uint32_t rram_blk[RRAM_BLOCK_SIZE / sizeof(uint32_t)]; + +static int RAMFUNCTION rram_wait_idle(void) +{ + uint32_t timeout = PSOC_E8_RRAM_TIMEOUT; + + while (((RRAMC_NVM_STATUS & RRAMC_NVM_STATUS_BUSY) != 0) && + (timeout > 0)) + timeout--; + return (timeout == 0) ? -1 : 0; +} + +static int RAMFUNCTION rram_lock(void) +{ + if ((IPC0_RRAM_ACQUIRE & IPC_ACQUIRE_SUCCESS) == 0) + return -1; + /* MS_CTL faults from here, so unlike the vendor driver the active + * protection context is not cross-checked. */ + RRAMC_PC_MASK |= RRAMC_PC_MASK_PC_LOCK_Msk; + if ((RRAMC_PC_MASK & RRAMC_PC_MASK_PC_LOCK_Msk) != + RRAMC_PC_MASK_PC_LOCK_Msk) { + RRAMC_PC_MASK &= ~RRAMC_PC_MASK_PC_LOCK_Msk; + IPC0_RRAM_RELEASE = 0; + return -1; + } + return 0; +} + +static void RAMFUNCTION rram_unlock(void) +{ + RRAMC_PC_MASK &= ~RRAMC_PC_MASK_PC_LOCK_Msk; + IPC0_RRAM_RELEASE = 0; +} + +/* Write within one 16-byte block; off / 16 is the physical block index. + * data == NULL fills with 0xFF, so an erased range reads like erased NOR. */ +static int RAMFUNCTION rram_write_block(uint32_t off, const uint8_t *data, + uint32_t len) +{ + volatile uint32_t *blk = (volatile uint32_t *)(uintptr_t) + ((PSOC_E8_RRAM_SBUS_S + off) & ~(RRAM_BLOCK_SIZE - 1U)); + uint32_t pos = off & (RRAM_BLOCK_SIZE - 1U); + uint32_t phys = off / RRAM_BLOCK_SIZE; + uint8_t *b = (uint8_t *)rram_blk; + uint32_t verr; + uint32_t i; + uint32_t t; + + RRAMC_NVM_ADDR = phys; + if (rram_wait_idle() != 0) + return -1; + /* A block failing ECC is rebuilt from zeros, like the vendor driver. */ + for (i = 0; i < RRAM_BLOCK_SIZE / sizeof(uint32_t); i++) { + rram_blk[i] = ((RRAMC_NVM_STATUS & RRAMC_NVM_STATUS_ECC_MULTI) != 0) ? + 0U : blk[i]; + } + for (i = 0; i < len; i++) + b[pos + i] = (data != NULL) ? data[i] : 0xFFU; + if (RRAMC_NVM_ADDR != phys) + return -1; + + for (t = 0; t < RRAM_WRITE_TRIALS; t++) { + for (i = 0; i < RRAM_BLOCK_SIZE / sizeof(uint32_t); i++) + blk[i] = rram_blk[i]; + RRAMC_NVM_PROG |= RRAMC_NVM_PROG_STARTOP; + if (rram_wait_idle() != 0) + return -1; + verr = (RRAMC_NVM_STATUS & RRAMC_NVM_STATUS_VERR_Msk) >> + RRAMC_NVM_STATUS_VERR_Pos; + /* 0-1 ok, 2 ok on the last try, 5-7 fatal, else retry. */ + if ((verr <= 1U) || ((verr == 2U) && (t == RRAM_WRITE_TRIALS - 1U))) + return 0; + if (verr >= 5U) + return -1; + } + return -1; +} + +static int RAMFUNCTION rram_write(uint32_t off, const uint8_t *data, int len) +{ + uint32_t chunk; + int ret = 0; + + if ((off + (uint32_t)len) > PSOC_E8_RRAM_SIZE) + return -1; + if (rram_lock() != 0) + return -1; + RRAMC_NVM_PROG = (RRAMC_NVM_PROG & ~RRAMC_NVM_PROG_ACTION_Msk) | + RRAMC_NVM_PROG_IDLE; + RRAMC_NVM_PROG = (RRAMC_NVM_PROG & ~RRAMC_NVM_PROG_ACTION_Msk) | + RRAMC_NVM_PROG_IND_WRITE; + while ((len > 0) && (ret == 0)) { + chunk = RRAM_BLOCK_SIZE - (off & (RRAM_BLOCK_SIZE - 1U)); + if (chunk > (uint32_t)len) + chunk = (uint32_t)len; + ret = rram_write_block(off, data, chunk); + off += chunk; + if (data != NULL) + data += chunk; + len -= (int)chunk; + } + RRAMC_NVM_PROG = (RRAMC_NVM_PROG & ~RRAMC_NVM_PROG_ACTION_Msk) | + RRAMC_NVM_PROG_IDLE; + rram_unlock(); + ICACHE0_CMD = ICACHE_CMD_INV; + DSB(); + ISB(); + return ret; +} + +/* Any RRAM alias (0x02/0x12/0x22/0x32000000) selects the RRAM path. */ +static int RAMFUNCTION is_rram(uint32_t address, uint32_t *off) +{ + uint32_t a = (address & 0x0FFFFFFFUL) - + (PSOC_E8_RRAM_SBUS_S & 0x0FFFFFFFUL); + + if (((address >> 28) > 3U) || (a >= PSOC_E8_RRAM_SIZE)) + return 0; + *off = a; + return 1; +} + +void RAMFUNCTION hal_flash_unlock(void) +{ +} + +void RAMFUNCTION hal_flash_lock(void) +{ +} + +/* RRAM aliases go to the RRAM controller, anything else is an SMIF0 alias + * programmed through src/qspi_flash.c. Nothing may run from QSPI while it is + * busy, so interrupts stay masked. */ +int RAMFUNCTION hal_flash_erase(uint32_t address, int len) +{ + uint32_t off = address & (PSOC_E8_SMIF_WINDOW - 1U); + uint32_t primask; + int ret = 0; + + if (len <= 0) + return -1; + if (is_rram(address, &off)) { + primask = irq_save(); + ret = rram_write(off, NULL, len); + irq_restore(primask); + return ret; + } + if (((off % PSOC_E8_QSPI_SECTOR_SIZE) != 0) || + (((uint32_t)len % PSOC_E8_QSPI_SECTOR_SIZE) != 0)) + return -1; + + primask = irq_save(); + qspi_failed = 0; + while ((len > 0) && (ret == 0)) { + ret = spi_flash_sector_erase(off); + if (qspi_failed) + ret = -1; + off += PSOC_E8_QSPI_SECTOR_SIZE; + len -= (int)PSOC_E8_QSPI_SECTOR_SIZE; + } + smif_invalidate_caches(); + irq_restore(primask); + return ret; +} + +int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len) +{ + uint32_t off = address & (PSOC_E8_SMIF_WINDOW - 1U); + uint32_t primask; + int ret; + + if ((data == NULL) || (len <= 0)) + return -1; + primask = irq_save(); + if (is_rram(address, &off)) { + ret = rram_write(off, data, len); + irq_restore(primask); + return ret; + } + qspi_failed = 0; + ret = spi_flash_write(off, data, len); + if (qspi_failed) + ret = -1; + smif_invalidate_caches(); + irq_restore(primask); + return ret; +} + +#endif /* !NONSECURE_APP */ + +/* Extend the cycle counter to 64 bits. Each read recovers at most one wrap, + * about 21 s at 200 MHz. */ +uint64_t hal_get_timer_us(void) +{ + static uint64_t hi; + static uint32_t last; + uint32_t now; + + DEMCR |= DEMCR_TRCENA; + DWT_CTRL |= DWT_CTRL_CYCCNTENA; + now = DWT_CYCCNT; + if (now < last) + hi += 0x100000000ULL; + last = now; + return (hi + now) / (PSOC_E8_CPU_HZ / 1000000UL); +} + +void RAMFUNCTION psoc_e8_delay_us(uint32_t us) +{ + uint32_t start; + uint32_t chunk; + + DEMCR |= DEMCR_TRCENA; + DWT_CTRL |= DWT_CTRL_CYCCNTENA; + /* Wait in 1 s steps so the cycle count stays within 32 bits. */ + while (us > 0U) { + chunk = (us > 1000000UL) ? 1000000UL : us; + start = DWT_CYCCNT; + while ((DWT_CYCCNT - start) < (chunk * (PSOC_E8_CPU_HZ / 1000000UL))) + ; + us -= chunk; + } +} + +#ifndef __WOLFBOOT +/* wolfBoot's own arch_reboot() lives in src/boot_arm.c. */ +void arch_reboot(void) +{ + AIRCR = AIRCR_VKEY | AIRCR_SYSRESETREQ; + DSB(); + while (1) + ; +} +#endif + +#if defined(NONSECURE_APP) + +/* The flash controller belongs to the Secure world; the application updates + * through the wolfBoot_nsc_* gateway calls instead. */ +void hal_flash_unlock(void) +{ +} + +void hal_flash_lock(void) +{ +} + +int hal_flash_erase(uint32_t address, int len) +{ + (void)address; + (void)len; + return -1; +} + +int hal_flash_write(uint32_t address, const uint8_t *data, int len) +{ + (void)address; + (void)data; + (void)len; + return -1; +} + +/* The console belongs to the Secure world; print through its gateway. */ +void uart_init(void) +{ +} + +void uart_write(const char *buf, unsigned int sz) +{ + unsigned int n; + + while (sz > 0U) { + n = (sz > PSOC_E8_NSC_UART_MAX) ? PSOC_E8_NSC_UART_MAX : sz; + psoc_e8_nsc_uart_write(buf, n); + buf += n; + sz -= n; + } +} + +#elif defined(DEBUG_UART) || !defined(__WOLFBOOT) + +/* Set once the console SCB is clocked; gates every register access. */ +static int uart_ready; + +void uart_init(void) +{ + psoc_e8_peri_init(); + psoc_e8_pin_setup(PSOC_E8_UART_PORT, PSOC_E8_UART_RX_PIN, + PSOC_E8_UART_HSIOM_SEL, GPIO_CFG_DM_HIGHZ); + psoc_e8_pin_setup(PSOC_E8_UART_PORT, PSOC_E8_UART_TX_PIN, + PSOC_E8_UART_HSIOM_SEL, GPIO_CFG_DM_STRONG); + + uart_ready = 0; + if (psoc_e8_pclk_setup(PSOC_E8_UART_PCLK_GR, PSOC_E8_UART_PCLK_IDX, + PSOC_E8_UART_PCLK_DIV, PSOC_E8_PCLK_HZ, + PSOC_E8_UART_BAUD * PSOC_E8_UART_OVS) != 0) + return; + + SCB_CTRL(PSOC_E8_UART_SCB_BASE) = 0; + SCB_UART_CTRL(PSOC_E8_UART_SCB_BASE) = 0; /* standard UART */ + SCB_UART_TX_CTRL(PSOC_E8_UART_SCB_BASE) = 1; /* one stop bit */ + SCB_UART_RX_CTRL(PSOC_E8_UART_SCB_BASE) = 1; + SCB_TX_CTRL(PSOC_E8_UART_SCB_BASE) = SCB_DATA_WIDTH(8); + SCB_RX_CTRL(PSOC_E8_UART_SCB_BASE) = SCB_DATA_WIDTH(8); + SCB_TX_FIFO_CTRL(PSOC_E8_UART_SCB_BASE) = 0x3F; + SCB_RX_FIFO_CTRL(PSOC_E8_UART_SCB_BASE) = 0x3F; + SCB_CTRL(PSOC_E8_UART_SCB_BASE) = SCB_CTRL_ENABLED | SCB_CTRL_MODE_UART | + ((PSOC_E8_UART_OVS - 1UL) & SCB_CTRL_OVS_MASK); + uart_ready = 1; +} + +void uart_write(const char *buf, unsigned int sz) +{ + unsigned int i; + uint32_t timeout; + + if (!uart_ready) + return; + for (i = 0; i < sz; i++) { + timeout = PSOC_E8_PERI_TIMEOUT; + while (((SCB_TX_FIFO_STATUS(PSOC_E8_UART_SCB_BASE) & + SCB_TX_FIFO_USED_Msk) >= SCB_TX_FIFO_SAFE_DEPTH) && + (timeout > 0)) + timeout--; + if (timeout == 0) + return; + SCB_TX_FIFO_WR(PSOC_E8_UART_SCB_BASE) = (uint32_t)(uint8_t)buf[i]; + } +} + +#endif /* DEBUG_UART || !__WOLFBOOT */ + +#ifdef __WOLFBOOT + +void hal_init(void) +{ + psoc_e8_peri_init(); +#ifdef TZEN + hal_gtzc_init(); + hal_tz_sau_init(); +#endif +#ifdef DEBUG_UART + uart_init(); + uart_write("wolfBoot HAL Init\n", sizeof("wolfBoot HAL Init\n") - 1); +#endif +} + +void hal_prepare_boot(void) +{ +#ifdef DEBUG_UART + uint32_t timeout = PSOC_E8_PERI_TIMEOUT; + + if (!uart_ready) + return; + while (((SCB_TX_FIFO_STATUS(PSOC_E8_UART_SCB_BASE) & + (SCB_TX_FIFO_USED_Msk | SCB_TX_FIFO_SR_VALID_Msk)) != 0U) && + (timeout > 0)) + timeout--; +#endif +} + +#endif /* __WOLFBOOT */ diff --git a/hal/psoc_e8.h b/hal/psoc_e8.h new file mode 100644 index 0000000000..4c9583f212 --- /dev/null +++ b/hal/psoc_e8.h @@ -0,0 +1,331 @@ +/* psoc_e8.h + * + * Register definitions for Infineon PSOC Edge E8 (CAT1D, Cortex-M33). + * + * 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 PSOC_E8_H_INCLUDED +#define PSOC_E8_H_INCLUDED + +#include + +/* A host-built unit test cannot assemble the barriers; the compiler barrier + * alone stands in there. */ +#if defined(__arm__) || defined(__ARM_ARCH) +#define ISB() __asm__ volatile ("isb" ::: "memory") +#define DSB() __asm__ volatile ("dsb" ::: "memory") +#else +#define ISB() __asm__ volatile ("" ::: "memory") +#define DSB() __asm__ volatile ("" ::: "memory") +#endif + +/* Each memory has four aliases (S/NS x code/system bus). Within an SMIF + * window the low 26 bits are the device offset. */ +#define PSOC_E8_SMIF_WINDOW (0x04000000UL) +#define PSOC_E8_RRAM_SBUS_S (0x32000000UL) +#define PSOC_E8_RRAM_SIZE (0x00080000UL) + +/* Build options, defaulted for KIT_PSE84_AI. The console is SCB2 on P6.5 (RX) + * / P6.7 (TX), the KitProg3 COM port; its clock destination is + * PCLK_SCB2_CLOCK_SCB_EN (group 1, index 1). wolfBoot leaves the clocks as + * extended boot sets them: CLK_HF0 (CM33) 200 MHz, CLK_HF10 100 MHz. */ +#ifndef PSOC_E8_UART_SCB +#define PSOC_E8_UART_SCB 2 +#endif +#ifndef PSOC_E8_UART_PORT +#define PSOC_E8_UART_PORT 6 +#endif +#ifndef PSOC_E8_UART_RX_PIN +#define PSOC_E8_UART_RX_PIN 5 +#endif +#ifndef PSOC_E8_UART_TX_PIN +#define PSOC_E8_UART_TX_PIN 7 +#endif +#ifndef PSOC_E8_UART_HSIOM_SEL +#define PSOC_E8_UART_HSIOM_SEL 20 +#endif +#ifndef PSOC_E8_UART_PCLK_GR +#define PSOC_E8_UART_PCLK_GR 1 +#endif +#ifndef PSOC_E8_UART_PCLK_IDX +#define PSOC_E8_UART_PCLK_IDX 1 +#endif +#ifndef PSOC_E8_UART_PCLK_DIV +#define PSOC_E8_UART_PCLK_DIV 0 +#endif +#ifndef PSOC_E8_UART_BAUD +#define PSOC_E8_UART_BAUD 115200UL +#endif +#define PSOC_E8_UART_OVS 8UL +#ifndef PSOC_E8_PCLK_HZ +#define PSOC_E8_PCLK_HZ 100000000UL +#endif +#ifndef PSOC_E8_CPU_HZ +#define PSOC_E8_CPU_HZ 200000000UL +#endif +/* QSPI NOR erase sector. */ +#ifndef PSOC_E8_QSPI_SECTOR_SIZE +#define PSOC_E8_QSPI_SECTOR_SIZE (0x40000UL) +#endif +#ifndef PSOC_E8_RRAM_TIMEOUT +#define PSOC_E8_RRAM_TIMEOUT (1000000UL) +#endif + +/* Peripherals, Secure alias. */ +#define PERI0_BASE (0x52000000UL) +#define PERI_PCLK0_BASE (0x52040000UL) +#define HSIOM_BASE (0x52800000UL) +#define GPIO_BASE (0x52810000UL) + +/* A slave in a gated PERI group bus-faults on access. */ +#define PSOC_E8_PERI_GR_SCB 1 +#define PERI_GR_SL_CTL(g) \ + (*(volatile uint32_t *)(PERI0_BASE + 0x4000UL + \ + ((uint32_t)(g) * 0x40UL) + 0x10UL)) +#define PERI_GR_SL_CTL2(g) \ + (*(volatile uint32_t *)(PERI0_BASE + 0x4000UL + \ + ((uint32_t)(g) * 0x40UL) + 0x14UL)) + +/* PERI_PCLK: 0x2000 per group; dividers by number, CLOCK_CTL by destination. */ +#define PERI_PCLK_GR(g) (PERI_PCLK0_BASE + ((uint32_t)(g) * 0x2000UL)) +#define PERI_PCLK_DIV_CMD(g) (*(volatile uint32_t *)(PERI_PCLK_GR(g))) +#define PERI_PCLK_CLOCK_CTL(g,i) \ + (*(volatile uint32_t *)(PERI_PCLK_GR(g) + 0xC00UL + ((uint32_t)(i) * 4UL))) +#define PERI_PCLK_DIV_16_5_CTL(g,d) \ + (*(volatile uint32_t *)(PERI_PCLK_GR(g) + 0x1800UL + ((uint32_t)(d) * 4UL))) +#define PERI_PCLK_DIV_16_5_EN (1UL << 0) +#define PERI_PCLK_DIV_16_5_FRAC_Pos (3U) +#define PERI_PCLK_DIV_16_5_INT_Pos (8U) +#define PERI_PCLK_DIV_TYPE_16_5 (2UL) +#define PERI_PCLK_CLOCK_CTL_TYPE_Pos (8U) +/* A divider's EN bit is status only; DIV_CMD starts it. PA_TYPE 3 with + * PA_DIV 0xFF selects no phase alignment. */ +#define PERI_PCLK_DIV_CMD_ENABLE (1UL << 31) +#define PERI_PCLK_DIV_CMD_PA_NONE ((3UL << 24) | (0xFFUL << 16)) +#define PERI_PCLK_DIV_CMD_TYPE_Pos (8U) + +#ifndef PSOC_E8_PERI_TIMEOUT +#define PSOC_E8_PERI_TIMEOUT (1000000UL) +#endif + +/* HSIOM: 0x10 per port, one mux byte per pin (pins 4-7 in PORT_SEL1). */ +#define HSIOM_PORT_SEL(p, pin) \ + (*(volatile uint32_t *)(HSIOM_BASE + ((uint32_t)(p) * 0x10UL) + \ + (((uint32_t)(pin) < 4U) ? 0x00UL : 0x04UL))) +#define HSIOM_SEL_SHIFT(pin) ((((uint32_t)(pin)) & 3U) * 8U) +/* 1 marks a pin non-secure, and secure writes to its HSIOM/GPIO are then + * ignored, so it is cleared first. */ +#define HSIOM_SECURE_PRT_NSMASK(p) \ + (*(volatile uint32_t *)(HSIOM_BASE + 0x1000UL + ((uint32_t)(p) * 0x10UL))) + +/* GPIO: 0x80 per port, 4 drive-mode bits per pin in CFG. */ +#define GPIO_PRT_CFG(p) \ + (*(volatile uint32_t *)(GPIO_BASE + ((uint32_t)(p) * 0x80UL) + 0x44UL)) +#define GPIO_CFG_DM_HIGHZ (0x8UL) /* input buffer on, no drive */ +#define GPIO_CFG_DM_STRONG (0x6UL) /* strong drive, input buffer off */ +#define GPIO_CFG_DM_MASK (0xFUL) + +/* SCB2-SCB11 are contiguous; SCB0 and SCB1 are not. */ +#define PSOC_E8_SCB_BASE(n) \ + ((n) == 0 ? 0x52990000UL : (n) == 1 ? 0x52D00000UL : \ + (0x52980000UL + ((uint32_t)(n) * 0x10000UL))) + +#define SCB_CTRL(b) (*(volatile uint32_t *)((b) + 0x000UL)) +#define SCB_UART_CTRL(b) (*(volatile uint32_t *)((b) + 0x040UL)) +#define SCB_UART_TX_CTRL(b) (*(volatile uint32_t *)((b) + 0x044UL)) +#define SCB_UART_RX_CTRL(b) (*(volatile uint32_t *)((b) + 0x048UL)) +#define SCB_TX_CTRL(b) (*(volatile uint32_t *)((b) + 0x200UL)) +#define SCB_TX_FIFO_CTRL(b) (*(volatile uint32_t *)((b) + 0x204UL)) +#define SCB_TX_FIFO_STATUS(b) (*(volatile uint32_t *)((b) + 0x208UL)) +#define SCB_TX_FIFO_WR(b) (*(volatile uint32_t *)((b) + 0x240UL)) +#define SCB_RX_CTRL(b) (*(volatile uint32_t *)((b) + 0x300UL)) +#define SCB_RX_FIFO_CTRL(b) (*(volatile uint32_t *)((b) + 0x304UL)) + +#define SCB_CTRL_ENABLED (1UL << 31) +#define SCB_CTRL_MODE_UART (2UL << 24) +#define SCB_CTRL_OVS_MASK (0xFUL) +#define SCB_DATA_WIDTH(n) ((uint32_t)((n) - 1)) +#define SCB_TX_FIFO_USED_Msk (0x1FFUL) +#define SCB_TX_FIFO_SR_VALID_Msk (1UL << 15) +#define SCB_TX_FIFO_SAFE_DEPTH (8UL) + +/* SMIF0: MMIO command FIFOs and per-device XIP setup in the core block, the + * XIP line cache in its own block. */ +#define SMIF0_CACHE_BASE (0x54450000UL) +#define SMIF0_CORE_BASE (0x54460000UL) + +#define SMIF_STATUS (*(volatile uint32_t *)(SMIF0_CORE_BASE + 0x004UL)) +#define SMIF_TX_CMD_STATUS (*(volatile uint32_t *)(SMIF0_CORE_BASE + 0x048UL)) +#define SMIF_TX_CMD_WR (*(volatile uint32_t *)(SMIF0_CORE_BASE + 0x050UL)) +#define SMIF_TX_DATA_STATUS (*(volatile uint32_t *)(SMIF0_CORE_BASE + 0x088UL)) +#define SMIF_TX_DATA_WR1 (*(volatile uint32_t *)(SMIF0_CORE_BASE + 0x090UL)) +#define SMIF_RX_DATA_STATUS (*(volatile uint32_t *)(SMIF0_CORE_BASE + 0x0C4UL)) +#define SMIF_RX_DATA_RD1 (*(volatile uint32_t *)(SMIF0_CORE_BASE + 0x0D0UL)) +#define SMIF_SLOW_CA_CMD (*(volatile uint32_t *)(SMIF0_CORE_BASE + 0x108UL)) +#define SMIF_FAST_CA_CMD (*(volatile uint32_t *)(SMIF0_CORE_BASE + 0x188UL)) +#define SMIF_DEVICE_CTL(n) \ + (*(volatile uint32_t *)(SMIF0_CORE_BASE + 0x800UL + ((n) * 0x80UL))) +#define SMIF_DEVICE_ADDR_CTL(n) \ + (*(volatile uint32_t *)(SMIF0_CORE_BASE + 0x820UL + ((n) * 0x80UL))) +#define SMIF_DEVICES 4 +#define SMIF_DEVICE_CTL_ENABLED (1UL << 31) +#define SMIF_STATUS_BUSY (1UL << 31) +#define SMIF_CA_CMD_INV (1UL << 0) +#define SMIF_FIFO_USED_Msk (0xFUL) +#define SMIF_FIFO_DEPTH (8UL) + +/* TX_CMD_MMIO_FIFO_WR: MODE[26:24], one-hot SS[23:20], LAST_BYTE[19] (release + * select), WIDTH[17:16], byte[7:0]; count entries hold count - 1 in [15:0]. */ +#define SMIF_CMD_MODE_TX (0UL << 24) +#define SMIF_CMD_MODE_TX_COUNT (1UL << 24) +#define SMIF_CMD_MODE_RX_COUNT (2UL << 24) +#define SMIF_CMD_SS(dev) ((1UL << (dev)) << 20) +#define SMIF_CMD_LAST (1UL << 19) + +#define SMIF_CACHE_MAINT_ALL \ + (*(volatile uint32_t *)(SMIF0_CACHE_BASE + 0x20UL)) +#define SMIF_CACHE_MAINT_STATUS \ + (*(volatile uint32_t *)(SMIF0_CACHE_BASE + 0x28UL)) +#define SMIF_CACHE_INVALIDATE_ALL (1UL << 1) +#define SMIF_CACHE_MAINT_ONGOING (1UL << 2) + +/* CM33 instruction cache. */ +#define ICACHE0_CMD (*(volatile uint32_t *)(0x52223008UL)) +#define ICACHE_CMD_INV (1UL << 0) + +/* RRAM indirect write: select the 16-byte block in NVM_ADDR, write its four + * words at the mapped address, then STARTOP. */ +#define RRAMC_PC_MASK (*(volatile uint32_t *)(0x52210020UL)) +#define RRAMC_NVM_STATUS (*(volatile uint32_t *)(0x52213000UL)) +#define RRAMC_NVM_PROG (*(volatile uint32_t *)(0x52213004UL)) +#define RRAMC_NVM_ADDR (*(volatile uint32_t *)(0x52213008UL)) +#define RRAMC_PC_MASK_PC_LOCK_Msk (0xFUL << 28) +#define RRAMC_NVM_STATUS_BUSY (1UL << 0) +#define RRAMC_NVM_STATUS_VERR_Pos (2U) +#define RRAMC_NVM_STATUS_VERR_Msk (0x7UL << 2) +#define RRAMC_NVM_STATUS_ECC_MULTI (0xF00UL) /* ECC2READ..ECCAREAD */ +#define RRAMC_NVM_PROG_ACTION_Msk (0xFUL) +#define RRAMC_NVM_PROG_IDLE (0UL) +#define RRAMC_NVM_PROG_IND_WRITE (1UL) +#define RRAMC_NVM_PROG_STARTOP (0x3UL << 14) +#define RRAM_BLOCK_SIZE (16U) +#define RRAM_WRITE_TRIALS (3U) + +/* IPC channel 2 serialises RRAM writers (vendor erratum workaround); reading + * ACQUIRE attempts the lock. */ +#define IPC0_RRAM_ACQUIRE (*(volatile uint32_t *)(0x522A0040UL)) +#define IPC0_RRAM_RELEASE (*(volatile uint32_t *)(0x522A0044UL)) +#define IPC_ACQUIRE_SUCCESS (1UL << 31) + +#define AIRCR (*(volatile uint32_t *)(0xE000ED0CUL)) +#define AIRCR_VKEY (0x05FAUL << 16) +#define AIRCR_SYSRESETREQ (1UL << 2) + +/* CM33 cycle counter, the time base for hal_get_timer_us(). */ +#define DEMCR (*(volatile uint32_t *)(0xE000EDFCUL)) +#define DEMCR_TRCENA (1UL << 24) +#define DWT_CTRL (*(volatile uint32_t *)(0xE0001000UL)) +#define DWT_CTRL_CYCCNTENA (1UL << 0) +#define DWT_CYCCNT (*(volatile uint32_t *)(0xE0001004UL)) + +/* SPI NOR commands. */ +#define NOR_CMD_RDSR (0x05U) +#define NOR_CMD_CLPEF (0x82U) /* clear program/erase failure */ +#define NOR_SR_E_ERR (1U << 5) +#define NOR_SR_P_ERR (1U << 6) + +/* Memory protection controllers, ROT view. Each LUT word holds eight 4-bit + * entries per block: bit 0 Non-secure, bit 1 read, bit 2 write. */ +#define RAMC1_MPC_BASE (0x52244000UL) +#define SMIF0_AHB_MPC_BASE (0x54454000UL) +#define MPC_ROT_CTRL(b) (*(volatile uint32_t *)((b) + 0x200UL)) +#define MPC_ROT_BLK_MAX(b) (*(volatile uint32_t *)((b) + 0x208UL)) +#define MPC_ROT_BLK_CFG(b) (*(volatile uint32_t *)((b) + 0x20CUL)) +#define MPC_ROT_BLK_IDX(b) (*(volatile uint32_t *)((b) + 0x210UL)) +#define MPC_ROT_BLK_PC(b) (*(volatile uint32_t *)((b) + 0x214UL)) +#define MPC_ROT_BLK_LUT(b) (*(volatile uint32_t *)((b) + 0x218UL)) +#define MPC_ROT_CTRL_AUTO_INC (1UL << 8) +#define MPC_ROT_BLK_CFG_SIZE_Msk (0xFUL) +#define MPC_ROT_BLK_CFG_INIT_BUSY (1UL << 31) +#define MPC_ROT_BLKS_PER_IDX (8U) +#define MPC_ROT_NS_RW (0x7UL) +/* Protection context of the CM33, Secure and Non-secure alike. */ +#define PSOC_E8_PC_CM33 (2U) + +/* SAU */ +#define SAU_CTRL (*(volatile uint32_t *)(0xE000EDD0UL)) +#define SAU_RNR (*(volatile uint32_t *)(0xE000EDD8UL)) +#define SAU_RBAR (*(volatile uint32_t *)(0xE000EDDCUL)) +#define SAU_RLAR (*(volatile uint32_t *)(0xE000EDE0UL)) +#define SAU_CTRL_ENABLE (1UL << 0) +#define SAU_RLAR_ENABLE (1UL << 0) +#define SAU_RLAR_NSC (1UL << 1) + +/* Crypto block TRNG, used to seed the Secure world's DRBG. */ +#define CRYPTO_BASE (0x52440000UL) +#define CRYPTO_CTL (*(volatile uint32_t *)(CRYPTO_BASE + 0x000UL)) +#define CRYPTO_INTR (*(volatile uint32_t *)(CRYPTO_BASE + 0x100UL)) +/* INTR flags are write-one-to-clear. */ +#define CRYPTO_INTR_CLR(m) (CRYPTO_INTR = (m)) +#define CRYPTO_TR_CTL0 (*(volatile uint32_t *)(CRYPTO_BASE + 0x280UL)) +#define CRYPTO_TR_CTL1 (*(volatile uint32_t *)(CRYPTO_BASE + 0x284UL)) +#define CRYPTO_TR_CTL2 (*(volatile uint32_t *)(CRYPTO_BASE + 0x288UL)) +#define CRYPTO_TR_CMD (*(volatile uint32_t *)(CRYPTO_BASE + 0x290UL)) +#define CRYPTO_TR_RESULT (*(volatile uint32_t *)(CRYPTO_BASE + 0x298UL)) +#define CRYPTO_TR_GARO_CTL (*(volatile uint32_t *)(CRYPTO_BASE + 0x2A0UL)) +#define CRYPTO_TR_FIRO_CTL (*(volatile uint32_t *)(CRYPTO_BASE + 0x2A4UL)) +#define CRYPTO_TR_MON_CTL (*(volatile uint32_t *)(CRYPTO_BASE + 0x2C0UL)) +#define CRYPTO_TR_MON_CMD (*(volatile uint32_t *)(CRYPTO_BASE + 0x2C8UL)) +#define CRYPTO_TR_MON_RC_CTL (*(volatile uint32_t *)(CRYPTO_BASE + 0x2D0UL)) +#define CRYPTO_TR_MON_AP_CTL (*(volatile uint32_t *)(CRYPTO_BASE + 0x2E0UL)) +#define CRYPTO_STATUS (*(volatile uint32_t *)(CRYPTO_BASE + 0x1004UL)) +#define CRYPTO_CTL_ENABLED (1UL << 31) +#define CRYPTO_INTR_TR_INITIALIZED (1UL << 2) +#define CRYPTO_INTR_TR_DATA_AVAILABLE (1UL << 3) +#define CRYPTO_INTR_TR_AP_ERROR (1UL << 19) +#define CRYPTO_INTR_TR_RC_ERROR (1UL << 20) +#define CRYPTO_INTR_TR_ALL (CRYPTO_INTR_TR_INITIALIZED | \ + CRYPTO_INTR_TR_DATA_AVAILABLE | \ + CRYPTO_INTR_TR_AP_ERROR | \ + CRYPTO_INTR_TR_RC_ERROR) +/* TR_CTL0: init delay 3, von Neumann correction, stop on either health test + * failure. TR_CTL1: all six ring oscillators. */ +#define CRYPTO_TR_CTL0_DEFAULT ((3UL << 16) | (1UL << 24) | (1UL << 28) | \ + (1UL << 29)) +#define CRYPTO_TR_CTL1_ALL_RO (0x3FUL) +#define CRYPTO_TR_POLY (0x04C11DB7UL) +#define CRYPTO_TR_MON_BS_TR (2UL) /* monitor the TRNG bit stream */ +#define CRYPTO_TR_MON_START_AP_RC (0x3UL) +#define CRYPTO_TR_RC_CUTOFF (255UL) +#define CRYPTO_TR_AP_CTL ((65535UL << 16) | 65535UL) + +/* HAL services also used by the application. */ +void psoc_e8_peri_init(void); +void psoc_e8_pin_setup(int port, int pin, uint32_t sel, uint32_t drive); +int psoc_e8_pclk_setup(uint32_t gr, uint32_t idx, uint32_t div, + uint32_t pclk_hz, uint32_t target_hz); +void psoc_e8_delay_us(uint32_t us); +uint64_t hal_get_timer_us(void); + +/* Console gateway for the Non-secure application. */ +#define PSOC_E8_NSC_UART_MAX (256U) +void psoc_e8_nsc_uart_write(const char *buf, uint32_t len); + +#endif /* PSOC_E8_H_INCLUDED */ diff --git a/hal/psoc_e8.ld b/hal/psoc_e8.ld new file mode 100644 index 0000000000..40c10914a1 --- /dev/null +++ b/hal/psoc_e8.ld @@ -0,0 +1,58 @@ +MEMORY +{ + FLASH (rx) : ORIGIN = @WOLFBOOT_ORIGIN@, LENGTH = @BOOTLOADER_PARTITION_SIZE@ + RAM (rwx) : ORIGIN = 0x34002000, LENGTH = 0x00056000 +} + +SECTIONS +{ + .text : + { + _start_text = .; + KEEP(*(.isr_vector)) + *(.text*) + *(.rodata*) + . = ALIGN(4); + _end_text = .; + } > FLASH + + .edidx : + { + . = ALIGN(4); + *(.ARM.exidx*) + } > FLASH + + .keystore : + { + . = ALIGN(4); + KEEP(*(.keystore*)) + } > FLASH + + _stored_data = .; + + .data : AT (_stored_data) + { + _start_data = .; + KEEP(*(.data*)) + . = ALIGN(4); + KEEP(*(.ramcode)) + . = ALIGN(4); + _end_data = .; + } > RAM + + .bss (NOLOAD) : + { + _start_bss = .; + __bss_start__ = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _end_bss = .; + __bss_end__ = .; + _end = .; + } > RAM + + . = ALIGN(4); +} + +END_STACK = ORIGIN(RAM) + LENGTH(RAM); diff --git a/hal/psoc_e8_tz.c b/hal/psoc_e8_tz.c new file mode 100644 index 0000000000..f505758643 --- /dev/null +++ b/hal/psoc_e8_tz.c @@ -0,0 +1,231 @@ +/* psoc_e8_tz.c + * + * TrustZone setup for Infineon PSOC Edge E8 (CAT1D, Cortex-M33). + * + * 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 + */ + +/* The SAU decides how the core sees an address and the memory protection + * controllers decide what the bus allows per block; both are programmed. The + * CM33 runs Secure and Non-secure code in the same protection context (PC2), + * so the controllers are configured for PC2 and the TrustZone NS attribute + * alone separates the two worlds. Peripherals stay Secure; the Non-secure + * application reaches the console through a gateway. */ + +#include +#include "image.h" +#include "hal.h" +#include "wolfboot/wc_secure.h" +#include "printf.h" +#include "hal/psoc_e8.h" +#include + +#if defined(TZEN) && !defined(NONSECURE_APP) + +#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U) +#include +#endif + +/* Extended boot uses SAU regions 0 and 1 (Non-secure peripheral and SFLASH + * windows) and leaves the SAU enabled. */ +#define SAU_REGION_BOOT_NS 2 +#define SAU_REGION_SRAM_NS 3 +#define SAU_REGION_NSC 4 + +/* The application runs from the boot partition and uses SRAM1. */ +#define NS_SRAM_BASE (0x24080000UL) +#define NS_SRAM_SIZE (0x00080000UL) +#define NS_BOOT_OFFSET (WOLFBOOT_PARTITION_BOOT_ADDRESS & \ + (PSOC_E8_SMIF_WINDOW - 1U)) + +static void sau_region(uint32_t region, uint32_t start, uint32_t end, + uint32_t att) +{ + SAU_RNR = region; + SAU_RBAR = start & ~0x1FUL; + SAU_RLAR = (end & ~0x1FUL) | att | SAU_RLAR_ENABLE; +} + +/* Grant PC2 non-secure read/write over [offset, offset + len) of one MPC, + * through its ROT view. A range that is not block aligned is not opened at + * all, rather than rounded out over Secure neighbours. Each LUT word holds + * eight 4-bit block entries, and AUTO_INC would move BLK_IDX between the read + * and the write of one update, so it is held off meanwhile. */ +static void mpc_set_ns(uint32_t base, uint32_t offset, uint32_t len) +{ + uint32_t blk = 1UL << ((MPC_ROT_BLK_CFG(base) & + MPC_ROT_BLK_CFG_SIZE_Msk) + 5U); + uint32_t nblk = (MPC_ROT_BLK_MAX(base) + 1UL) * MPC_ROT_BLKS_PER_IDX; + uint32_t timeout = PSOC_E8_PERI_TIMEOUT; + uint32_t first = offset / blk; + uint32_t last = (offset + len - 1U) / blk; + uint32_t ctrl; + uint32_t idx; + uint32_t lut; + uint32_t b; + + if ((len == 0U) || ((offset % blk) != 0U) || ((len % blk) != 0U) || + (last >= nblk)) + return; + while (((MPC_ROT_BLK_CFG(base) & MPC_ROT_BLK_CFG_INIT_BUSY) != 0U) && + (timeout > 0U)) + timeout--; + if (timeout == 0U) + return; + + ctrl = MPC_ROT_CTRL(base); + MPC_ROT_CTRL(base) = ctrl & ~MPC_ROT_CTRL_AUTO_INC; + MPC_ROT_BLK_PC(base) = PSOC_E8_PC_CM33; + for (idx = first / MPC_ROT_BLKS_PER_IDX; + idx <= last / MPC_ROT_BLKS_PER_IDX; idx++) { + MPC_ROT_BLK_IDX(base) = idx; + lut = MPC_ROT_BLK_LUT(base); + for (b = 0; b < MPC_ROT_BLKS_PER_IDX; b++) { + uint32_t n = (idx * MPC_ROT_BLKS_PER_IDX) + b; + if ((n >= first) && (n <= last)) { + lut &= ~(0xFUL << (b * 4U)); + lut |= (MPC_ROT_NS_RW << (b * 4U)); + } + } + MPC_ROT_BLK_LUT(base) = lut; + } + MPC_ROT_CTRL(base) = ctrl; + DSB(); +} + +/* Memory attribution. Named for the generic wolfBoot hook. */ +void hal_gtzc_init(void) +{ + /* Only the boot partition is handed over: the application executes from + * it. wolfBoot reads it through the same Non-secure alias, so this runs + * before the image is verified. Update and swap stay Secure. */ + mpc_set_ns(SMIF0_AHB_MPC_BASE, NS_BOOT_OFFSET, WOLFBOOT_PARTITION_SIZE); + mpc_set_ns(RAMC1_MPC_BASE, 0, NS_SRAM_SIZE); +} + +void hal_tz_sau_init(void) +{ + uint32_t boot = WOLFBOOT_PARTITION_BOOT_ADDRESS; + + sau_region(SAU_REGION_BOOT_NS, boot, boot + WOLFBOOT_PARTITION_SIZE - 1U, + 0); + sau_region(SAU_REGION_SRAM_NS, NS_SRAM_BASE, + NS_SRAM_BASE + NS_SRAM_SIZE - 1U, 0); + /* Without NSC an SG instruction is not a valid entry point and the first + * call takes a SecureFault (SFSR.INVEP). */ + sau_region(SAU_REGION_NSC, WOLFBOOT_NSC_ADDRESS, + WOLFBOOT_NSC_ADDRESS + WOLFBOOT_NSC_SIZE - 1U, SAU_RLAR_NSC); + SAU_CTRL |= SAU_CTRL_ENABLE; + DSB(); + ISB(); +} + +void hal_tz_sau_ns_region(void) +{ + hal_tz_sau_init(); +} + +/* The crypto block's TRNG seeds the Secure DRBG. Both health monitors run and + * a failure in either stops the generator, which is reported as an error. */ +void hal_trng_init(void) +{ + CRYPTO_CTL |= CRYPTO_CTL_ENABLED; + CRYPTO_TR_MON_CMD = 0; + CRYPTO_TR_CTL1 = 0; + CRYPTO_INTR_CLR(CRYPTO_INTR_TR_ALL); + CRYPTO_TR_GARO_CTL = CRYPTO_TR_POLY; + CRYPTO_TR_FIRO_CTL = CRYPTO_TR_POLY; + CRYPTO_TR_CTL0 = CRYPTO_TR_CTL0_DEFAULT; + CRYPTO_TR_CTL1 = CRYPTO_TR_CTL1_ALL_RO; + CRYPTO_TR_MON_CTL = CRYPTO_TR_MON_BS_TR; + CRYPTO_TR_MON_RC_CTL = CRYPTO_TR_RC_CUTOFF; + CRYPTO_TR_MON_AP_CTL = CRYPTO_TR_AP_CTL; + CRYPTO_TR_MON_CMD = CRYPTO_TR_MON_START_AP_RC; +} + +void hal_trng_fini(void) +{ + CRYPTO_TR_MON_CMD = 0; + CRYPTO_TR_CTL1 = 0; +} + +static int trng_word(volatile uint32_t *word) +{ + uint32_t timeout = PSOC_E8_PERI_TIMEOUT; + + while (((CRYPTO_STATUS != 0U) || + ((CRYPTO_INTR & CRYPTO_INTR_TR_INITIALIZED) == 0U)) && + (timeout > 0U)) + timeout--; + if (timeout == 0U) + return -1; + CRYPTO_INTR_CLR(CRYPTO_INTR_TR_DATA_AVAILABLE); + CRYPTO_TR_CTL2 = 32U; + CRYPTO_TR_CMD = 1U; + timeout = PSOC_E8_PERI_TIMEOUT; + while (((CRYPTO_INTR & (CRYPTO_INTR_TR_DATA_AVAILABLE | + CRYPTO_INTR_TR_AP_ERROR | CRYPTO_INTR_TR_RC_ERROR)) == 0U) && + (timeout > 0U)) + timeout--; + if ((timeout == 0U) || + ((CRYPTO_INTR & (CRYPTO_INTR_TR_AP_ERROR | CRYPTO_INTR_TR_RC_ERROR)) + != 0U)) + return -1; + *word = CRYPTO_TR_RESULT; + return 0; +} + +int hal_trng_get_entropy(unsigned char *out, unsigned len) +{ + volatile uint32_t word = 0; + unsigned i; + + for (i = 0; i < len; i++) { + if ((i & 3U) == 0U) { + if (trng_word(&word) != 0) { + word = 0; + return -1; + } + } + out[i] = (unsigned char)(word >> ((i & 3U) * 8U)); + } + /* Raw entropy seeds the DRBG: do not leave it on the stack. */ + word = 0; + return 0; +} + +/* The console stays Secure; the Non-secure application prints through this + * gateway. The buffer must lie in Non-secure memory, or a caller could have + * the Secure world print Secure data back. */ +CSME_NSE_API +void psoc_e8_nsc_uart_write(const char *buf, uint32_t len) +{ + if ((buf == NULL) || (len == 0U) || (len > PSOC_E8_NSC_UART_MAX)) + return; +#if defined(__ARM_FEATURE_CMSE) && (__ARM_FEATURE_CMSE == 3U) + if (cmse_check_address_range((void *)buf, (size_t)len, CMSE_NONSECURE) + == NULL) + return; +#endif +#ifdef DEBUG_UART + uart_write(buf, (unsigned int)len); +#endif +} + +#endif /* TZEN && !NONSECURE_APP */ diff --git a/src/qspi_flash.c b/src/qspi_flash.c index 7193d03ce6..cebb29f46e 100644 --- a/src/qspi_flash.c +++ b/src/qspi_flash.c @@ -53,6 +53,17 @@ #define QSPI_FLASH_READY_TRIES 10000 #endif +/* QSPI_NO_4BYTE_MODE leaves the part in its default address mode, for a part + * addressed through dedicated 4-byte opcodes (PAGE_PROG_CMD, SEC_ERASE_CMD). + * + * A target that executes in place from the flash it programs keeps the + * program and erase path in RAM. */ +#ifdef QSPI_FLASH_RAM_CODE +#define QSPI_RAMFUNCTION RAMFUNCTION +#else +#define QSPI_RAMFUNCTION +#endif + /* Flash Commands */ #define WRITE_ENABLE_CMD 0x06U @@ -76,7 +87,9 @@ #define DUAL_READ_4B_CMD 0x3CU #define QUAD_READ_4B_CMD 0x6CU +#ifndef PAGE_PROG_CMD #define PAGE_PROG_CMD 0x02U +#endif #define DUAL_PROG_CMD 0xA2U #define QUAD_PROG_CMD 0x32U @@ -84,7 +97,9 @@ #define DUAL_PROG_4B_CMD 0x12U #define QUAD_PROG_4B_CMD 0x34U +#ifndef SEC_ERASE_CMD #define SEC_ERASE_CMD 0x20U /* 4KB */ +#endif #define BLOCK_ERASE_CMD 0xD8U /* 64KB */ #define RESET_ENABLE_CMD 0x66U #define RESET_MEMORY_CMD 0x99U @@ -123,13 +138,14 @@ /* forward declarations */ -static int qspi_wait_ready(void); -static int qspi_status(uint8_t* status); +static int QSPI_RAMFUNCTION qspi_wait_ready(void); +static int QSPI_RAMFUNCTION qspi_status(uint8_t* status); #ifdef TEST_EXT_FLASH static int test_ext_flash(void); #endif -static inline int qspi_command_simple(uint8_t fmode, uint8_t cmd, +static inline int QSPI_RAMFUNCTION qspi_command_simple(uint8_t fmode, + uint8_t cmd, uint8_t* data, uint32_t dataSz) { uint32_t dmode = QSPI_DATA_MODE_NONE; @@ -167,7 +183,7 @@ static int qspi_flash_read_id(uint8_t* id, uint32_t idSz) return ret; } -static int qspi_write_enable(void) +static int QSPI_RAMFUNCTION qspi_write_enable(void) { int ret; uint32_t timeout; @@ -212,15 +228,14 @@ static int qspi_write_disable(void) return ret; } -static int qspi_status(uint8_t* status) +static int QSPI_RAMFUNCTION qspi_status(uint8_t* status) { int ret; - uint8_t data[4]; /* size multiple of uint32_t */ + uint8_t data[4] = {0, 0, 0, 0}; /* size multiple of uint32_t */ #if defined(DEBUG_QSPI) && DEBUG_QSPI > 1 static uint8_t last_status = 0; #endif - memset(data, 0, sizeof(data)); ret = qspi_command_simple(QSPI_MODE_READ, READ_SR_CMD, data, 1); #if defined(DEBUG_QSPI) && DEBUG_QSPI > 1 if (status == NULL || last_status != data[0]) { @@ -235,7 +250,7 @@ static int qspi_status(uint8_t* status) return ret; } -static int qspi_wait_ready(void) +static int QSPI_RAMFUNCTION qspi_wait_ready(void) { int ret; uint32_t timeout; @@ -295,7 +310,7 @@ static int qspi_quad_enable(void) } #endif -#if QSPI_ADDR_SZ == 4 +#if QSPI_ADDR_SZ == 4 && !defined(QSPI_NO_4BYTE_MODE) static int qspi_enter_4byte_addr(void) { int ret = qspi_write_enable(); @@ -337,7 +352,7 @@ uint16_t spi_flash_probe(void) #if QSPI_DATA_MODE == QSPI_DATA_MODE_QSPI qspi_quad_enable(); #endif -#if QSPI_ADDR_SZ == 4 +#if QSPI_ADDR_SZ == 4 && !defined(QSPI_NO_4BYTE_MODE) qspi_enter_4byte_addr(); #endif @@ -351,7 +366,7 @@ uint16_t spi_flash_probe(void) /* Called for each sector from hal.h inline ext_flash_erase function * Use SPI_FLASH_SECTOR_SIZE to adjust for QSPI sector size */ -int spi_flash_sector_erase(uint32_t address) +int QSPI_RAMFUNCTION spi_flash_sector_erase(uint32_t address) { int ret; @@ -414,7 +429,8 @@ int spi_flash_read(uint32_t address, void *data, int len) return (ret == 0) ? len : ret; } -int spi_flash_write(uint32_t address, const void *data, int len) +int QSPI_RAMFUNCTION spi_flash_write(uint32_t address, const void *data, + int len) { int ret = 0; int remaining = len; @@ -480,7 +496,7 @@ int spi_flash_write(uint32_t address, const void *data, int len) void spi_flash_release(void) { -#if QSPI_ADDR_SZ == 4 +#if QSPI_ADDR_SZ == 4 && !defined(QSPI_NO_4BYTE_MODE) qspi_exit_4byte_addr(); #endif diff --git a/test-app/ARM-psoc_e8-ns.ld b/test-app/ARM-psoc_e8-ns.ld new file mode 100644 index 0000000000..c065f23e10 --- /dev/null +++ b/test-app/ARM-psoc_e8-ns.ld @@ -0,0 +1,55 @@ +MEMORY +{ + FLASH (rx) : ORIGIN = @WOLFBOOT_TEST_APP_ADDRESS@, LENGTH = @WOLFBOOT_TEST_APP_SIZE@ + RAM (rwx) : ORIGIN = 0x24080000, LENGTH = 0x00080000 /* SRAM1, Non-secure */ +} + +SECTIONS +{ + .text : + { + _start_text = .; + KEEP(*(.isr_vector)) + *(.text*) + *(.rodata*) + . = ALIGN(4); + _end_text = .; + } > FLASH + + .edidx : + { + . = ALIGN(4); + *(.ARM.exidx*) + } > FLASH + + _stored_data = .; + + .data : AT (_stored_data) + { + _start_data = .; + KEEP(*(.data*)) + . = ALIGN(4); + KEEP(*(.ramcode)) + . = ALIGN(4); + _end_data = .; + } > RAM + + .bss (NOLOAD) : + { + _start_bss = .; + __bss_start__ = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _end_bss = .; + __bss_end__ = .; + _end = .; + } > RAM + + . = ALIGN(4); +} + +PROVIDE(_start_heap = _end); +PROVIDE(end = _end); +PROVIDE(_Min_Heap_Size = 0x1000); +PROVIDE(_end_stack = ORIGIN(RAM) + LENGTH(RAM)); diff --git a/test-app/ARM-psoc_e8.ld b/test-app/ARM-psoc_e8.ld new file mode 100644 index 0000000000..263be5de7f --- /dev/null +++ b/test-app/ARM-psoc_e8.ld @@ -0,0 +1,54 @@ +MEMORY +{ + FLASH (rx) : ORIGIN = @WOLFBOOT_TEST_APP_ADDRESS@, LENGTH = @WOLFBOOT_TEST_APP_SIZE@ + RAM (rwx) : ORIGIN = 0x34002000, LENGTH = 0x00056000 +} + +SECTIONS +{ + .text : + { + _start_text = .; + KEEP(*(.isr_vector)) + *(.text*) + *(.rodata*) + . = ALIGN(4); + _end_text = .; + } > FLASH + + .edidx : + { + . = ALIGN(4); + *(.ARM.exidx*) + } > FLASH + + _stored_data = .; + + .data : AT (_stored_data) + { + _start_data = .; + KEEP(*(.data*)) + . = ALIGN(4); + KEEP(*(.ramcode)) + . = ALIGN(4); + _end_data = .; + } > RAM + + .bss (NOLOAD) : + { + _start_bss = .; + __bss_start__ = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _end_bss = .; + __bss_end__ = .; + _end = .; + } > RAM + + . = ALIGN(4); +} + +PROVIDE(_start_heap = _end); +PROVIDE(_Min_Heap_Size = 0x1000); +PROVIDE(_end_stack = ORIGIN(RAM) + LENGTH(RAM)); diff --git a/test-app/Makefile b/test-app/Makefile index 38fb4fc7f2..7ec4cc3d10 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -600,15 +600,9 @@ else ifeq ($(WOLFHAL),1) APP_OBJS+=../hal/wolfhal.o else - # Keep the Agilex test HAL separate from the bootloader HAL. The - # test-app rule below is forced and uses different feature defines; - # sharing ../hal/agilex5.o can silently replace the bootloader object - # before a later incremental relink. - ifeq ($(TARGET),agilex5) - APP_OBJS+=hal_agilex5.o - else - APP_OBJS+=../hal/$(TARGET).o - endif + # A private object: the app builds the HAL with its own defines, and + # sharing ../hal/$(TARGET).o would get it relinked into wolfboot.elf. + APP_OBJS+=hal_$(TARGET).o # The test-app links the same HAL object, so HALs using the # generic NS16550 console need it here too. ifneq (,$(filter $(TARGET),agilex5 nxp_ls1028a nxp_t2080 nxp_t1024 nxp_t1040 nxp_p1021)) @@ -834,6 +828,21 @@ ifeq ($(TARGET),psoc_c3) CFLAGS+=-I.. endif +ifeq ($(TARGET),psoc_e8) + ifeq ($(TZEN),1) + LSCRIPT_TEMPLATE=ARM-psoc_e8-ns.ld + else + LSCRIPT_TEMPLATE=ARM-psoc_e8.ld + APP_OBJS+=../src/qspi_flash.o + endif + # The app runs from the QSPI it programs, so its flash path runs from RAM. + CFLAGS+=-DRAMFUNCTION='__attribute__((used,section(".ramcode"),long_call))' + CFLAGS+=-mcpu=cortex-m33 -ffunction-sections -fdata-sections -fno-common + LDFLAGS+=-mcpu=cortex-m33 + LDFLAGS+=-Wl,-gc-sections -Wl,-Map=image.map + CFLAGS+=-I.. +endif + ifeq ($(TARGET),nrf5340) ifeq ($(TZEN),1) LSCRIPT_TEMPLATE=ARM-nrf5340-ns.ld @@ -1113,7 +1122,7 @@ ifeq ($(TARGET),mcxw) else LSCRIPT_TEMPLATE=ARM-mcxw.ld # Flash writes run from the same flash the app executes from - ../hal/$(TARGET).o: CFLAGS+=-DRAM_CODE -D__WOLFBOOT + hal_$(TARGET).o: CFLAGS+=-DRAM_CODE -D__WOLFBOOT ../test-app/libwolfboot.o: CFLAGS+=-DRAM_CODE -D__WOLFBOOT APP_OBJS+=$(MCXW_SDK_SRCS:.c=.o) APP_OBJS+=$(MCUXPRESSO_DRIVERS)/drivers/fsl_clock.o @@ -1161,7 +1170,7 @@ ifeq ($(TARGET),rw612) else LSCRIPT_TEMPLATE=ARM-rw612.ld # Flash writes run from the same NOR the app executes from - ../hal/$(TARGET).o: CFLAGS+=-DRAM_CODE -D__WOLFBOOT + hal_$(TARGET).o: CFLAGS+=-DRAM_CODE -D__WOLFBOOT ../test-app/libwolfboot.o: CFLAGS+=-DRAM_CODE -D__WOLFBOOT APP_OBJS+=$(RW612_SDK_SRCS:.c=.o) endif @@ -1237,7 +1246,7 @@ ifeq ($(TARGET),imx_rt7xx) LDFLAGS+=-Wl,--no-warn-rwx-segments # The flash path runs from the same NOR it programs, so RAMFUNCTION must be # active in the HAL and libwolfboot objects the app links. - ../hal/$(TARGET).o: CFLAGS+=-DRAM_CODE -D__WOLFBOOT + hal_$(TARGET).o: CFLAGS+=-DRAM_CODE -D__WOLFBOOT ../test-app/libwolfboot.o: CFLAGS+=-DRAM_CODE -D__WOLFBOOT APP_OBJS+=\ $(MCUXPRESSO_DRIVERS)/drivers/fsl_clock.o \ @@ -1527,7 +1536,7 @@ ifneq (,$(filter aurix_tc4xx aurix_tc4xx_csrm,$(TARGET))) CFLAGS:=$(filter-out -fdata-sections,$(CFLAGS)) # Flash helpers run from PSPR. # Limit __WOLFBOOT to objects that define RAMFUNCTION helpers. - ../hal/$(TARGET).o: CFLAGS+=-DRAM_CODE -D__WOLFBOOT + hal_$(TARGET).o: CFLAGS+=-DRAM_CODE -D__WOLFBOOT ../test-app/libwolfboot.o: CFLAGS+=-DRAM_CODE -D__WOLFBOOT ifeq ($(TARGET),aurix_tc4xx_csrm) # CSRM app uses the shared startup and driver objects. @@ -1716,6 +1725,12 @@ delta-extra-data: image.bin hal_agilex5.o: ../hal/agilex5.c FORCE $(Q)$(CC) $(CFLAGS) -c -o $(@) ../hal/agilex5.c +ifneq ($(TARGET),agilex5) +hal_$(TARGET).o: ../hal/$(TARGET).c FORCE + @echo "\t[CC-$(ARCH)] $@" + $(Q)$(CC) $(CFLAGS) -c $(OUTPUT_FLAG) $@ $< +endif + ../hal/$(TARGET)_ns.o: ../hal/$(TARGET).c FORCE $(Q)$(CC) $(CFLAGS) -c -o $(@) ../hal/$(TARGET).c -DNONSECURE_APP diff --git a/test-app/app_psoc_e8.c b/test-app/app_psoc_e8.c new file mode 100644 index 0000000000..c262d44230 --- /dev/null +++ b/test-app/app_psoc_e8.c @@ -0,0 +1,111 @@ +/* app_psoc_e8.c + * + * Test bare-metal application for Infineon PSOC Edge E8. + * + * 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 "hal.h" +#include "printf.h" +#include "hal/psoc_e8.h" +#include "wolfboot/wolfboot.h" +#include "target.h" + +#ifdef WOLFCRYPT_TZ_WOLFHSM +#include "wcs/wolfhsm_test.h" +#endif +#ifdef WOLFCRYPT_TEST +#include +#include +int wolfcrypt_test(void *args); +#endif +#ifdef WOLFCRYPT_BENCHMARK +#include +#include +int benchmark_test(void *args); +#endif + +/* With TrustZone the application is Non-secure and the update API is a set + * of Secure gateway calls. */ +#ifdef TZEN +#define APP_FW_VERSION() wolfBoot_nsc_current_firmware_version() +#define APP_SUCCESS() wolfBoot_nsc_success() +#define APP_UPDATE_TRIGGER() wolfBoot_nsc_update_trigger() +#else +#define APP_FW_VERSION() wolfBoot_current_firmware_version() +#define APP_SUCCESS() wolfBoot_success() +#define APP_UPDATE_TRIGGER() wolfBoot_update_trigger() +#endif + +#ifdef WOLFCRYPT_TZ_WOLFHSM +/* newlib stdout for the wolfHSM client tests. */ +int _write(int file, char *ptr, int len) +{ + (void)file; + uart_write(ptr, (unsigned int)len); + return len; +} +#endif + +void main(void) +{ + uint32_t version; + int i; + + uart_init(); + version = APP_FW_VERSION(); + wolfBoot_printf("TEST APP\r\nApp version: %u\r\n", (unsigned)version); + +#ifdef WOLFCRYPT_TZ_WOLFHSM + wolfBoot_printf("Running wolfHSM client tests...\r\n"); + (void)cmd_wolfhsm_test(NULL); +#endif +#ifdef WOLFCRYPT_TEST + wolfBoot_printf("Running wolfCrypt tests...\r\n"); + wolfCrypt_Init(); + wolfcrypt_test(NULL); + wolfCrypt_Cleanup(); +#endif +#ifdef WOLFCRYPT_BENCHMARK + wolfBoot_printf("Running wolfCrypt benchmarks...\r\n"); + wolfCrypt_Init(); + benchmark_test(NULL); + wolfCrypt_Cleanup(); +#endif + + /* v1 asks for an update and resets so wolfBoot performs the swap. + * v2 and later confirm the update so it is not rolled back. */ + if (version >= 2) { + APP_SUCCESS(); + wolfBoot_printf("update OK -- success confirmed\r\n"); + while (1) + ; + } + + /* Pause before asking for the update, so a debugger can program the + * update partition before the reset comes round. */ + for (i = 0; i < 5; i++) { + psoc_e8_delay_us(400000); + wolfBoot_printf("."); + } + wolfBoot_printf("\r\ntriggering update -> reset\r\n"); + APP_UPDATE_TRIGGER(); + arch_reboot(); +} diff --git a/test-app/wolfcrypt_support.c b/test-app/wolfcrypt_support.c index 7dd949a78d..1cd2e6b614 100644 --- a/test-app/wolfcrypt_support.c +++ b/test-app/wolfcrypt_support.c @@ -105,6 +105,10 @@ { (void)m7_get_ticks(); } +#elif defined(TARGET_psoc_e8) + /* The HAL's cycle-counter time base. */ + #include "hal/psoc_e8.h" + static uint64_t e8_start_us = 0; #elif defined(TARGET_nxp_t2080) || defined(TARGET_nxp_t1024) || \ defined(TARGET_nxp_t1040) /* PPC time base register for accurate timing (e6500/e5500). */ @@ -217,6 +221,10 @@ unsigned long my_time(unsigned long* timer) if (timer) *timer = t; return t; } +#elif defined(TARGET_psoc_e8) + unsigned long t = (unsigned long)(hal_get_timer_us() / 1000000ULL); + if (timer) *timer = t; + return t; #elif defined(TARGET_nxp_t2080) || defined(TARGET_nxp_t1024) || \ defined(TARGET_nxp_t1040) if (ppc_tb_hz == 0) @@ -269,6 +277,10 @@ double current_time(int reset) if (reset) m7_start_ticks = m7_get_ticks(); return (double)(m7_get_ticks() - m7_start_ticks) / (double)IMX95_M7_HZ; +#elif defined(TARGET_psoc_e8) + if (reset) + e8_start_us = hal_get_timer_us(); + return (double)(hal_get_timer_us() - e8_start_us) / 1000000.0; #elif defined(TARGET_nxp_t2080) || defined(TARGET_nxp_t1024) || \ defined(TARGET_nxp_t1040) if (ppc_tb_hz == 0) diff --git a/tools/scripts/psoc_e8/psoc_e8_boot_header.py b/tools/scripts/psoc_e8/psoc_e8_boot_header.py new file mode 100755 index 0000000000..bb8320f40a --- /dev/null +++ b/tools/scripts/psoc_e8/psoc_e8_boot_header.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +"""Add the extended-boot image header to wolfboot.bin for PSOC Edge E8. + +The boot ROM only launches a CM33 image that carries a 0x400-byte header and a +trailing SHA-256 of header and image (an MCUboot-compatible, unsigned format). + +Usage: psoc_e8_boot_header.py wolfboot.bin wolfboot_boot.bin [slot_size] + +With slot_size, fails if header, image and trailer do not fit the boot slot. +""" + +import hashlib +import struct +import sys + +IMAGE_MAGIC = 0x96F3B83D +TLV_INFO_MAGIC = 0x6907 +TLV_SHA256 = 0x10 +HDR_SIZE = 0x400 +# TLV info header, SHA-256 TLV header and digest. +TRAILER_SIZE = 4 + 4 + 32 + + +def add_header(img, slot_size=None): + # magic, load_addr, hdr_size, protect_tlv_size, img_size, flags, + # version (major, minor, revision, build), pad + hdr = struct.pack(' slot_size: + raise ValueError('boot image is 0x%x bytes, slot is 0x%x' + % (len(out), slot_size)) + return out + + +def main(): + if len(sys.argv) not in (3, 4): + sys.stderr.write(__doc__) + return 1 + slot_size = int(sys.argv[3], 0) if len(sys.argv) == 4 else None + with open(sys.argv[1], 'rb') as f: + img = f.read() + try: + out = add_header(img, slot_size) + except ValueError as e: + sys.stderr.write('psoc_e8_boot_header: %s\n' % e) + return 1 + with open(sys.argv[2], 'wb') as f: + f.write(out) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/scripts/psoc_e8/psoc_e8_flash.sh b/tools/scripts/psoc_e8/psoc_e8_flash.sh new file mode 100755 index 0000000000..8a4ea7cbe2 --- /dev/null +++ b/tools/scripts/psoc_e8/psoc_e8_flash.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# Program wolfBoot and signed images into the KIT_PSE84_AI QSPI over KitProg3, +# using the ModusToolbox Programming Tools OpenOCD. Run from the wolfBoot root +# after `make`; partition addresses are taken from .config. +# +# Usage: psoc_e8_flash.sh [--boot image_v1_signed.bin] [--update image_v2_signed.bin] +# [--erase-keyvault] +# +# --erase-keyvault fills the wolfHSM NVM (WOLFBOOT_KEYVAULT_*) with 0xFF. Fresh +# RRAM reads as zero, which the NVM does not accept as empty, so a board needs +# this once before the first wolfHSM boot. It destroys any stored keys. +# +# Environment: MTB_PROGTOOLS (default /opt/Tools/ModusToolboxProgtools-1.6), +# KITPROG_SERIAL to pick the probe when several are attached. + +set -e + +MTB_PROGTOOLS=${MTB_PROGTOOLS:-/opt/Tools/ModusToolboxProgtools-1.6} +OCD=$MTB_PROGTOOLS/openocd +SCRIPTS=$MTB_PROGTOOLS/mtb-programmer/scripts +HERE=$(cd "$(dirname "$0")" && pwd) + +config_addr() { + local v + v=$(sed -n "s/^$1?*=[[:space:]]*\(0x[0-9a-fA-F]*\).*/\1/p" .config | tail -1) + [ -n "$v" ] || { echo "$1 not found in .config" >&2; exit 1; } + printf "0x%08x" $(( (v & 0x03FFFFFF) | 0x60000000 )) +} +BOOT_ADDR=$(config_addr WOLFBOOT_PARTITION_BOOT_ADDRESS) +UPDATE_ADDR=$(config_addr WOLFBOOT_PARTITION_UPDATE_ADDRESS) + +BOOT_IMG=test-app/image_v1_signed.bin +UPDATE_IMG= +ERASE_KEYVAULT=0 +while [ $# -gt 0 ]; do + case "$1" in + --boot) BOOT_IMG=$2; shift 2 ;; + --update) UPDATE_IMG=$2; shift 2 ;; + --erase-keyvault) ERASE_KEYVAULT=1; shift ;; + *) echo "unknown argument: $1" >&2; exit 1 ;; + esac +done + +BOOTIMG=$(mktemp --suffix=.bin) +BLANK=$(mktemp --suffix=.bin) +trap 'rm -f "$BOOTIMG" "$BLANK"' EXIT +python3 "$HERE/psoc_e8_boot_header.py" wolfboot.bin "$BOOTIMG" \ + $(( BOOT_ADDR - 0x60100000 )) + +CMDS="init; reset init" +CMDS="$CMDS; flash write_image erase $BOOTIMG 0x60100000 bin" +CMDS="$CMDS; verify_image $BOOTIMG 0x60100000 bin" +CMDS="$CMDS; flash write_image erase $BOOT_IMG $BOOT_ADDR bin" +CMDS="$CMDS; verify_image $BOOT_IMG $BOOT_ADDR bin" +if [ -n "$UPDATE_IMG" ]; then + CMDS="$CMDS; flash write_image erase $UPDATE_IMG $UPDATE_ADDR bin" + CMDS="$CMDS; verify_image $UPDATE_IMG $UPDATE_ADDR bin" +fi +if [ "$ERASE_KEYVAULT" = 1 ]; then + # The RRAM loader programs through the Non-secure system-bus alias. + KV=$(sed -n 's/^WOLFBOOT_KEYVAULT_ADDRESS?*=[[:space:]]*\(0x[0-9a-fA-F]*\).*/\1/p' .config | tail -1) + KV_SIZE=$(sed -n 's/^WOLFBOOT_KEYVAULT_SIZE?*=[[:space:]]*\(0x[0-9a-fA-F]*\).*/\1/p' .config | tail -1) + [ -n "$KV" ] && [ -n "$KV_SIZE" ] || { echo "no WOLFBOOT_KEYVAULT_* in .config" >&2; exit 1; } + head -c $((KV_SIZE)) /dev/zero | tr '\0' '\377' > "$BLANK" + KV_NS=$(printf "0x%08x" $(( (KV & 0x0FFFFFFF) | 0x20000000 ))) + CMDS="$CMDS; flash write_image erase $BLANK $KV_NS bin" +fi +# "reset run" would leave the part in KitProg3 acquire mode. +CMDS="$CMDS; reset halt; resume; shutdown" + +SERIAL_ARGS=() +if [ -n "$KITPROG_SERIAL" ]; then + SERIAL_ARGS=(-c "adapter serial $KITPROG_SERIAL") +fi + +"$OCD/bin/openocd" -s "$OCD/scripts" -s "$SCRIPTS" \ + -c "set SCRIPTS_DIR $SCRIPTS" \ + -f interface/kitprog3.cfg "${SERIAL_ARGS[@]}" -c "transport select swd" \ + -f CAT1D/kit_PSE84_AI_external.cfg \ + -c "$CMDS" diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index f06f10ece0..6a3d16fb01 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -90,6 +90,8 @@ TESTS+=unit-pkcs11-pin-zeroize TESTS+=unit-ubootenv TESTS+=unit-diagnostics TESTS+=unit-psoc-c3-divider +TESTS+=unit-psoc-e8-tz +TESTS+=unit-psoc-e8-flash TESTS+=unit-diagnostics-256 TESTS+=unit-fit-gzip unit-fit-nogzip TESTS+=unit-gzip-small unit-gzip-bits32 unit-gzip-root7 unit-fit-gzip-small @@ -311,6 +313,7 @@ run: $(TESTS) python3 unit-sign-custom-tlv-large.py || exit 1 python3 unit-sign-custom-tlv-pubkey-der.py || exit 1 python3 unit-sign-dts.py || exit 1 + python3 unit-psoc-e8-boot-header.py || exit 1 WOLFCRYPT_SRC:=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha.c \ $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sha256.c \ @@ -647,6 +650,13 @@ unit-ns16550: unit-ns16550.c ../../hal/uart/ns16550.c unit-psoc-c3-divider: unit-psoc-c3-divider.c ../../hal/psoc_c3.h gcc -o $@ unit-psoc-c3-divider.c $(CFLAGS) $(LDFLAGS) +unit-psoc-e8-tz: unit-psoc-e8-tz.c ../../hal/psoc_e8_tz.c ../../hal/psoc_e8.h + gcc -o $@ unit-psoc-e8-tz.c -I../.. $(CFLAGS) $(LDFLAGS) + +unit-psoc-e8-flash: unit-psoc-e8-flash.c unit-mmio.h ../../hal/psoc_e8.c \ + ../../hal/psoc_e8.h ../../src/qspi_flash.c + gcc -o $@ unit-psoc-e8-flash.c -I../.. $(CFLAGS) $(LDFLAGS) + unit-fdt-memrsv-wrap:CFLAGS+=-DWOLFBOOT_FDT unit-fdt-memrsv-wrap: unit-fdt-memrsv-wrap.c ../../src/fdt.c gcc -o $@ unit-fdt-memrsv-wrap.c ../../src/fdt.c -I../../include \ diff --git a/tools/unit-tests/unit-psoc-e8-boot-header.py b/tools/unit-tests/unit-psoc-e8-boot-header.py new file mode 100644 index 0000000000..a848c5af2f --- /dev/null +++ b/tools/unit-tests/unit-psoc-e8-boot-header.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# unit-psoc-e8-boot-header.py +# +# Parses images produced by tools/scripts/psoc_e8/psoc_e8_boot_header.py and +# checks every header and TLV field, the padding, the digest and the boot-slot +# size limit. +# +# 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 + +import hashlib +import os +import struct +import subprocess +import sys +import tempfile +import unittest + +HERE = os.path.dirname(os.path.abspath(__file__)) +TOOL = os.path.join(HERE, '..', 'scripts', 'psoc_e8', 'psoc_e8_boot_header.py') +sys.dont_write_bytecode = True +sys.path.insert(0, os.path.dirname(TOOL)) +import psoc_e8_boot_header as bh # noqa: E402 + +HDR_SIZE = 0x400 +TRAILER_SIZE = 40 + + +def parse(out): + (magic, load_addr, hdr_size, protect_tlv, img_size, flags, v_major, + v_minor, v_rev, v_build, pad) = struct.unpack_from(' +#include +#include +#include + +#include "unit-mmio.h" + +/* KIT_PSE84_AI layout (config/examples/psoc_e8_ai.config) */ +#define H_TARGETS_TARGET_ +#define WOLFBOOT_FIXED_PARTITIONS +#define WOLFBOOT_SECTOR_SIZE 0x40000 +#define WOLFBOOT_PARTITION_SIZE 0x100000 +#define WOLFBOOT_PARTITION_BOOT_ADDRESS 0x18140000 +#define WOLFBOOT_PARTITION_UPDATE_ADDRESS 0x18240000 +#define WOLFBOOT_PARTITION_SWAP_ADDRESS 0x18340000 + +#define __WOLFBOOT +#define WOLFBOOT_HASH_SHA256 +#define WOLFBOOT_SIGN_ECC256 +#define PSOC_E8_PERI_TIMEOUT 64U +#define PSOC_E8_RRAM_TIMEOUT 64U + +/* src/qspi_flash.c as arch.mk builds it for this target */ +#define QSPI_FLASH +#define QSPI_FLASH_RAM_CODE +#define QSPI_ADDR_SZ 4 +#define QSPI_NO_4BYTE_MODE +#define QSPI_DATA_MODE QSPI_DATA_MODE_SPI +#define PAGE_PROG_CMD 0x12 +#define SEC_ERASE_CMD 0xDC +#define FLASH_DEVICE_SIZE 0x4000000 +#define SPI_FLASH_SECTOR_SIZE 0x40000 +#define QSPI_FLASH_READY_TRIES 100 + +#include "hal/psoc_e8.h" + +/* ---- SMIF model: a log of command entries and data bytes, and scripted + * status register reads. ---------------------------------------------- */ + +#define MOCK_LOG 256 +static uint32_t cmd_log[MOCK_LOG]; +static unsigned int cmd_n; +static uint8_t data_log[MOCK_LOG]; +static unsigned int data_n; +static uint32_t cmd_sink; +static uint32_t data_sink; +static uint8_t sr_script[16]; +static unsigned int sr_n; +static unsigned int sr_pos; +static uint32_t mock_dev_ctl[SMIF_DEVICES]; +static uint32_t mock_fifo_status; +static uint32_t mock_rx_status = 1; +static uint32_t mock_smif_status; +static uint32_t mock_cache_maint; +static uint32_t mock_cache_status; +static uint32_t mock_slow_ca; +static uint32_t mock_fast_ca; +static uint32_t mock_icache; + +static uint32_t *cmd_slot(void) +{ + if (cmd_n < MOCK_LOG) + return &cmd_log[cmd_n++]; + return &cmd_sink; +} + +static uint32_t *data_slot(void) +{ + data_sink = 0; + return &data_sink; +} + +static uint32_t rx_read(void) +{ + if (sr_pos < sr_n) + return sr_script[sr_pos++]; + return 0; +} + +#undef SMIF_TX_CMD_STATUS +#undef SMIF_TX_CMD_WR +#undef SMIF_TX_DATA_STATUS +#undef SMIF_TX_DATA_WR1 +#undef SMIF_RX_DATA_STATUS +#undef SMIF_RX_DATA_RD1 +#undef SMIF_STATUS +#undef SMIF_DEVICE_CTL +#undef SMIF_CACHE_MAINT_ALL +#undef SMIF_CACHE_MAINT_STATUS +#undef SMIF_SLOW_CA_CMD +#undef SMIF_FAST_CA_CMD +#undef ICACHE0_CMD +#define SMIF_TX_CMD_STATUS mock_fifo_status +#define SMIF_TX_CMD_WR (*cmd_slot()) +#define SMIF_TX_DATA_STATUS mock_fifo_status +#define SMIF_TX_DATA_WR1 (*data_slot()) +#define SMIF_RX_DATA_STATUS mock_rx_status +#define SMIF_RX_DATA_RD1 rx_read() +#define SMIF_STATUS mock_smif_status +#define SMIF_DEVICE_CTL(n) mock_dev_ctl[(n)] +#define SMIF_CACHE_MAINT_ALL mock_cache_maint +#define SMIF_CACHE_MAINT_STATUS mock_cache_status +#define SMIF_SLOW_CA_CMD mock_slow_ca +#define SMIF_FAST_CA_CMD mock_fast_ca +#define ICACHE0_CMD mock_icache + +/* ---- RRAM model: memory is mapped at the Secure system-bus alias, and each + * STARTOP consumes the next scripted VERR value. ----------------------- */ + +static uint32_t mock_nvm_prog; +static uint32_t mock_nvm_addr; +static uint32_t mock_pc_mask; +static uint32_t mock_ipc_acquire; +static uint32_t mock_ipc_release; +static uint32_t verr_script[8]; +static unsigned int verr_n; +static unsigned int verr_pos; +static uint32_t last_verr; +static unsigned int startops; + +static uint32_t nvm_status(void) +{ + if ((mock_nvm_prog & RRAMC_NVM_PROG_STARTOP) != 0U) { + mock_nvm_prog &= ~RRAMC_NVM_PROG_STARTOP; + startops++; + last_verr = (verr_pos < verr_n) ? verr_script[verr_pos++] : 0U; + } + return last_verr << RRAMC_NVM_STATUS_VERR_Pos; +} + +#undef RRAMC_NVM_STATUS +#undef RRAMC_NVM_PROG +#undef RRAMC_NVM_ADDR +#undef RRAMC_PC_MASK +#undef IPC0_RRAM_ACQUIRE +#undef IPC0_RRAM_RELEASE +#define RRAMC_NVM_STATUS nvm_status() +#define RRAMC_NVM_PROG mock_nvm_prog +#define RRAMC_NVM_ADDR mock_nvm_addr +#define RRAMC_PC_MASK mock_pc_mask +#define IPC0_RRAM_ACQUIRE mock_ipc_acquire +#define IPC0_RRAM_RELEASE mock_ipc_release + +/* ---- Cycle counter and the rest ---------------------------------------- */ + +static uint32_t mock_demcr; +static uint32_t mock_dwt_ctrl; +static uint32_t cyc_script[4]; +static unsigned int cyc_n; +static unsigned int cyc_pos; +static uint32_t cyc_free; +static uint64_t cyc_total; + +/* Scripted values first, then a free-running counter. */ +static uint32_t cyccnt(void) +{ + if (cyc_pos < cyc_n) + return cyc_script[cyc_pos++]; + cyc_free += 1000U; + cyc_total += 1000U; + return cyc_free; +} + +static uint32_t mock_sl_ctl[8]; +static uint32_t mock_sl_ctl2[8]; + +/* One divider and clock destination; the divider reports enabled from the + * first read after it was written. */ +static uint32_t mock_div_ctl; +static uint32_t mock_clock_ctl; +static uint32_t mock_div_cmd; +static unsigned int div_ctl_accesses; +static int div_never_enables; + +static uint32_t *div_ctl(void) +{ + if ((div_ctl_accesses++ > 0U) && !div_never_enables) + mock_div_ctl |= PERI_PCLK_DIV_16_5_EN; + return &mock_div_ctl; +} + +#undef DEMCR +#undef DWT_CTRL +#undef DWT_CYCCNT +#undef PERI_GR_SL_CTL +#undef PERI_GR_SL_CTL2 +#define DEMCR mock_demcr +#define DWT_CTRL mock_dwt_ctrl +#define DWT_CYCCNT cyccnt() +#define PERI_GR_SL_CTL(g) mock_sl_ctl[(g)] +#define PERI_GR_SL_CTL2(g) mock_sl_ctl2[(g)] +#undef PERI_PCLK_DIV_16_5_CTL +#undef PERI_PCLK_CLOCK_CTL +#undef PERI_PCLK_DIV_CMD +#define PERI_PCLK_DIV_16_5_CTL(g, d) (*div_ctl()) +#define PERI_PCLK_CLOCK_CTL(g, i) mock_clock_ctl +#define PERI_PCLK_DIV_CMD(g) mock_div_cmd + +#include "../../src/qspi_flash.c" +#include "../../hal/psoc_e8.c" + +static uint8_t *rram; + +#define SS1 SMIF_CMD_SS(1) + +static void reset_mocks(void) +{ + memset(cmd_log, 0, sizeof(cmd_log)); + memset(data_log, 0, sizeof(data_log)); + cmd_n = data_n = 0; + sr_n = sr_pos = 0; + memset(mock_dev_ctl, 0, sizeof(mock_dev_ctl)); + mock_dev_ctl[1] = SMIF_DEVICE_CTL_ENABLED; + verr_n = verr_pos = 0; + last_verr = 0; + startops = 0; + mock_nvm_prog = 0; + mock_pc_mask = 0; + mock_ipc_acquire = IPC_ACQUIRE_SUCCESS; + cyc_n = cyc_pos = 0; + qspi_failed = 0; +} + +static void script_sr(const uint8_t *sr, unsigned int n) +{ + memcpy(sr_script, sr, n); + sr_n = n; + sr_pos = 0; +} + +/* Index of the first command entry carrying opcode op as its first byte. */ +static int find_cmd(uint8_t op) +{ + unsigned int i; + + for (i = 0; i < cmd_n; i++) { + if ((cmd_log[i] & (0x7UL << 24)) == SMIF_CMD_MODE_TX && + (cmd_log[i] & 0xFFU) == op && + (i == 0 || (cmd_log[i - 1] & SMIF_CMD_LAST) != 0 || + (cmd_log[i - 1] & (0x7UL << 24)) != SMIF_CMD_MODE_TX)) + return (int)i; + } + return -1; +} + +START_TEST(test_is_rram_aliases) +{ + uint32_t off; + + ck_assert_int_eq(is_rram(0x02040010U, &off), 1); + ck_assert_uint_eq(off, 0x40010); + ck_assert_int_eq(is_rram(0x12040010U, &off), 1); + ck_assert_int_eq(is_rram(0x22040010U, &off), 1); + ck_assert_int_eq(is_rram(0x3207FFFFU, &off), 1); + ck_assert_uint_eq(off, 0x7FFFF); + ck_assert_int_eq(is_rram(0x32080000U, &off), 0); + ck_assert_int_eq(is_rram(0x18140000U, &off), 0); + ck_assert_int_eq(is_rram(0x08140000U, &off), 0); + ck_assert_int_eq(is_rram(0x60140000U, &off), 0); + ck_assert_int_eq(is_rram(0x70140000U, &off), 0); +} +END_TEST + +START_TEST(test_rram_write_spans_blocks) +{ + uint8_t buf[40]; + unsigned int i; + + reset_mocks(); + memset(rram + 0x40000, 0xAA, 0x40); + for (i = 0; i < sizeof(buf); i++) + buf[i] = (uint8_t)i; + ck_assert_int_eq(hal_flash_write(0x32040005U, buf, sizeof(buf)), 0); + ck_assert_int_eq(memcmp(rram + 0x40005, buf, sizeof(buf)), 0); + /* The rest of the touched blocks is preserved. */ + ck_assert_uint_eq(rram[0x40004], 0xAA); + ck_assert_uint_eq(rram[0x40005 + sizeof(buf)], 0xAA); + /* 0x40005..0x4002C spans three 16-byte blocks. */ + ck_assert_uint_eq(startops, 3); + ck_assert_uint_eq(mock_ipc_release, 0); + ck_assert_uint_eq(mock_pc_mask & RRAMC_PC_MASK_PC_LOCK_Msk, 0); +} +END_TEST + +START_TEST(test_rram_erase_fills_ff) +{ + unsigned int i; + + reset_mocks(); + memset(rram + 0x41000, 0x00, 0x40); + ck_assert_int_eq(hal_flash_erase(0x32041000U, 0x20), 0); + for (i = 0; i < 0x20; i++) + ck_assert_uint_eq(rram[0x41000 + i], 0xFF); + ck_assert_uint_eq(rram[0x41020], 0x00); +} +END_TEST + +START_TEST(test_rram_verify_retry) +{ + uint8_t b = 0x5A; + static const uint32_t retry_ok[] = { 3, 0 }; + static const uint32_t last_try_2[] = { 3, 3, 2 }; + static const uint32_t never[] = { 3, 3, 3 }; + static const uint32_t fatal[] = { 5 }; + + reset_mocks(); + memcpy(verr_script, retry_ok, sizeof(retry_ok)); + verr_n = 2; + ck_assert_int_eq(hal_flash_write(0x32042000U, &b, 1), 0); + ck_assert_uint_eq(startops, 2); + + reset_mocks(); + memcpy(verr_script, last_try_2, sizeof(last_try_2)); + verr_n = 3; + ck_assert_int_eq(hal_flash_write(0x32042000U, &b, 1), 0); + ck_assert_uint_eq(startops, 3); + + reset_mocks(); + memcpy(verr_script, never, sizeof(never)); + verr_n = 3; + ck_assert_int_eq(hal_flash_write(0x32042000U, &b, 1), -1); + + reset_mocks(); + memcpy(verr_script, fatal, sizeof(fatal)); + verr_n = 1; + ck_assert_int_eq(hal_flash_write(0x32042000U, &b, 1), -1); + ck_assert_uint_eq(startops, 1); +} +END_TEST + +START_TEST(test_rram_lock_busy) +{ + uint8_t b = 0x5A; + + reset_mocks(); + mock_ipc_acquire = 0; + ck_assert_int_eq(hal_flash_write(0x32042000U, &b, 1), -1); + ck_assert_uint_eq(startops, 0); +} +END_TEST + +START_TEST(test_rram_bounds) +{ + uint8_t b[2] = { 1, 2 }; + + reset_mocks(); + ck_assert_int_eq(hal_flash_write(0x3207FFFFU, b, 2), -1); +} +END_TEST + +START_TEST(test_qspi_program) +{ + static const uint8_t sr[] = { 0x02, 0x00 }; /* WEL, then ready */ + uint8_t buf[4] = { 0xDE, 0xAD, 0xBE, 0xEF }; + int i; + + reset_mocks(); + script_sr(sr, sizeof(sr)); + ck_assert_int_eq(hal_flash_write(0x18140010U, buf, sizeof(buf)), 0); + ck_assert_int_ge(find_cmd(0x06), 0); + i = find_cmd(PAGE_PROG_CMD); + ck_assert_int_ge(i, 0); + /* 4-byte offset, MSB first, then a 4-byte data phase. */ + ck_assert_uint_eq(cmd_log[i], SS1 | SMIF_CMD_MODE_TX | 0x12U); + ck_assert_uint_eq(cmd_log[i + 1], SS1 | SMIF_CMD_MODE_TX | 0x00U); + ck_assert_uint_eq(cmd_log[i + 2], SS1 | SMIF_CMD_MODE_TX | 0x14U); + ck_assert_uint_eq(cmd_log[i + 3], SS1 | SMIF_CMD_MODE_TX | 0x00U); + ck_assert_uint_eq(cmd_log[i + 4], SS1 | SMIF_CMD_MODE_TX | 0x10U); + ck_assert_uint_eq(cmd_log[i + 5], + SMIF_CMD_MODE_TX_COUNT | SMIF_CMD_LAST | 3U); + /* The data went out of the staging buffer. */ + ck_assert_int_eq(memcmp(qspi_page, buf, sizeof(buf)), 0); +} +END_TEST + +START_TEST(test_qspi_erase) +{ + static const uint8_t sr[] = { 0x02, 0x01, 0x01, 0x00 }; + int i; + + reset_mocks(); + script_sr(sr, sizeof(sr)); + ck_assert_int_eq(hal_flash_erase(0x18240000U, 0x40000), 0); + i = find_cmd(SEC_ERASE_CMD); + ck_assert_int_ge(i, 0); + ck_assert_uint_eq(cmd_log[i + 2], SS1 | SMIF_CMD_MODE_TX | 0x24U); + ck_assert_uint_eq(cmd_log[i + 4], + SS1 | SMIF_CMD_MODE_TX | SMIF_CMD_LAST | 0x00U); + /* Unaligned erases are refused. */ + ck_assert_int_eq(hal_flash_erase(0x18241000U, 0x40000), -1); + ck_assert_int_eq(hal_flash_erase(0x18240000U, 0x1000), -1); +} +END_TEST + +START_TEST(test_qspi_failure_reported) +{ + /* WEL, then a program failure, then ready: the HAL must still fail. */ + static const uint8_t sr[] = { 0x02, NOR_SR_P_ERR, 0x00 }; + uint8_t b = 0x5A; + + reset_mocks(); + script_sr(sr, sizeof(sr)); + ck_assert_int_eq(hal_flash_write(0x18140000U, &b, 1), -1); + ck_assert_int_ge(find_cmd(NOR_CMD_CLPEF), 0); +} +END_TEST + +START_TEST(test_qspi_transfer_args) +{ + uint8_t buf[4]; + + reset_mocks(); + ck_assert_int_eq(qspi_transfer(QSPI_MODE_READ, 0x05, 0, 0, 0, 0xF0, 1, 1, + 0, buf, 1, 1), -1); + ck_assert_int_eq(qspi_transfer(QSPI_MODE_READ, 0x0B, 0, 4, 1, 0, 0, 0, + 8, buf, 1, 1), -1); + ck_assert_int_eq(qspi_transfer(QSPI_MODE_WRITE, 0x12, 0, 4, 1, 0, 0, 0, + 0, buf, SPI_FLASH_PAGE_SIZE + 1U, 1), -1); + ck_assert_int_eq(qspi_transfer(QSPI_MODE_READ, 0x05, 0, 0, 0, 0, 0, 0, + 0, NULL, 1, 1), -1); + ck_assert_uint_eq(cmd_n, 0); + /* No XIP device configured. */ + memset(mock_dev_ctl, 0, sizeof(mock_dev_ctl)); + ck_assert_int_eq(qspi_transfer(QSPI_MODE_WRITE, 0x06, 0, 0, 0, 0, 0, 0, + 0, NULL, 0, 0), -1); +} +END_TEST + +static int pclk(uint32_t pclk_hz, uint32_t target_hz, uint32_t *int_part, + uint32_t *frac) +{ + int ret; + + mock_div_ctl = 0; + mock_clock_ctl = 0; + mock_div_cmd = 0; + div_ctl_accesses = 0; + ret = psoc_e8_pclk_setup(1, 1, 3, pclk_hz, target_hz); + *int_part = (mock_div_ctl >> PERI_PCLK_DIV_16_5_INT_Pos) & 0xFFFFU; + *frac = (mock_div_ctl >> PERI_PCLK_DIV_16_5_FRAC_Pos) & 0x1FU; + return ret; +} + +START_TEST(test_pclk_divider) +{ + uint32_t i; + uint32_t f; + + div_never_enables = 0; + /* 100 MHz to 115200 x 16: 54.25 -> INT 53, FRAC 8 */ + ck_assert_int_eq(pclk(100000000U, 1843200U, &i, &f), 0); + ck_assert_uint_eq(i, 53); + ck_assert_uint_eq(f, 8); + ck_assert_uint_eq(mock_clock_ctl, + (PERI_PCLK_DIV_TYPE_16_5 << PERI_PCLK_CLOCK_CTL_TYPE_Pos) | 3U); + ck_assert_uint_eq(mock_div_cmd, PERI_PCLK_DIV_CMD_ENABLE | + PERI_PCLK_DIV_CMD_PA_NONE | + (PERI_PCLK_DIV_TYPE_16_5 << PERI_PCLK_DIV_CMD_TYPE_Pos) | 3U); + /* A fraction above 16/32. */ + ck_assert_int_eq(pclk(100000000U, 1826484U, &i, &f), 0); + ck_assert_uint_eq(i, 53); + ck_assert_uint_eq(f, 24); + /* Exact integer division. */ + ck_assert_int_eq(pclk(100000000U, 25000000U, &i, &f), 0); + ck_assert_uint_eq(i, 3); + ck_assert_uint_eq(f, 0); + /* Faster than the source clamps to divide by one. */ + ck_assert_int_eq(pclk(100000000U, 200000000U, &i, &f), 0); + ck_assert_uint_eq(i, 0); + ck_assert_uint_eq(f, 0); + /* Just inside and beyond the 16-bit integer range. */ + ck_assert_int_eq(pclk(0x10000U, 1U, &i, &f), 0); + ck_assert_uint_eq(i, 0xFFFF); + ck_assert_uint_eq(f, 0); + ck_assert_int_eq(pclk(100000000U, 1U, &i, &f), 0); + ck_assert_uint_eq(i, 0xFFFF); + ck_assert_uint_eq(f, 0); + ck_assert_int_eq(pclk(0x20001U, 2U, &i, &f), 0); + ck_assert_uint_eq(i, 0xFFFF); + ck_assert_uint_eq(f, 0); + /* No target, and a divider that never starts. */ + mock_div_ctl = 0x1234; + ck_assert_int_eq(psoc_e8_pclk_setup(1, 1, 3, 100000000U, 0), -1); + ck_assert_uint_eq(mock_div_ctl, 0x1234); + div_never_enables = 1; + ck_assert_int_eq(pclk(100000000U, 1843200U, &i, &f), -1); + div_never_enables = 0; +} +END_TEST + +START_TEST(test_rram_write_skips_smif_cache) +{ + uint8_t b = 0x5A; + + reset_mocks(); + mock_cache_maint = 0; + mock_slow_ca = 0; + mock_fast_ca = 0; + ck_assert_int_eq(hal_flash_write(0x32042000U, &b, 1), 0); + ck_assert_uint_eq(mock_cache_maint | mock_slow_ca | mock_fast_ca, 0); +} +END_TEST + +START_TEST(test_delay_long) +{ + reset_mocks(); + cyc_total = 0; + /* 30 s is more cycles than one 32-bit count holds. */ + psoc_e8_delay_us(30000000U); + ck_assert(cyc_total >= 30000000ULL * (PSOC_E8_CPU_HZ / 1000000UL)); +} +END_TEST + +START_TEST(test_timer_wrap) +{ + uint64_t a; + uint64_t b; + + reset_mocks(); + cyc_script[0] = 0xFFFFF000U; + cyc_script[1] = 0x00001000U; + cyc_n = 2; + a = hal_get_timer_us(); + b = hal_get_timer_us(); + ck_assert(b > a); + ck_assert_uint_eq(b - a, (0x2000U) / (PSOC_E8_CPU_HZ / 1000000UL)); +} +END_TEST + +static Suite *psoc_e8_flash_suite(void) +{ + Suite *s = suite_create("psoc_e8_flash"); + TCase *tc = tcase_create("flash"); + + tcase_add_test(tc, test_is_rram_aliases); + tcase_add_test(tc, test_rram_write_spans_blocks); + tcase_add_test(tc, test_rram_erase_fills_ff); + tcase_add_test(tc, test_rram_verify_retry); + tcase_add_test(tc, test_rram_lock_busy); + tcase_add_test(tc, test_rram_bounds); + tcase_add_test(tc, test_qspi_program); + tcase_add_test(tc, test_qspi_erase); + tcase_add_test(tc, test_qspi_failure_reported); + tcase_add_test(tc, test_qspi_transfer_args); + tcase_add_test(tc, test_timer_wrap); + tcase_add_test(tc, test_pclk_divider); + tcase_add_test(tc, test_rram_write_skips_smif_cache); + tcase_add_test(tc, test_delay_long); + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s; + SRunner *sr; + + rram = unit_mmio_map(PSOC_E8_RRAM_SBUS_S, PSOC_E8_RRAM_SIZE); + if (rram == NULL) { + fprintf(stderr, "cannot map RRAM model at 0x%08lx\n", + (unsigned long)PSOC_E8_RRAM_SBUS_S); + return 1; + } + s = psoc_e8_flash_suite(); + sr = srunner_create(s); + srunner_set_fork_status(sr, CK_NOFORK); + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + return fails; +} diff --git a/tools/unit-tests/unit-psoc-e8-tz.c b/tools/unit-tests/unit-psoc-e8-tz.c new file mode 100644 index 0000000000..9b050f6a40 --- /dev/null +++ b/tools/unit-tests/unit-psoc-e8-tz.c @@ -0,0 +1,448 @@ +/* unit-psoc-e8-tz.c + * + * Unit tests for hal/psoc_e8_tz.c: the memory protection controller block + * map, the SAU regions, the TRNG entropy source and the console gateway, with + * the register blocks replaced by host models. + * + * 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 + +/* KIT_PSE84_AI TrustZone layout (config/examples/psoc_e8_ai-tz.config) */ +#define H_TARGETS_TARGET_ +#define WOLFBOOT_FIXED_PARTITIONS +#define WOLFBOOT_SECTOR_SIZE 0x40000 +#define WOLFBOOT_PARTITION_SIZE 0x100000 +#define WOLFBOOT_PARTITION_BOOT_ADDRESS 0x08140000 +#define WOLFBOOT_PARTITION_UPDATE_ADDRESS 0x18240000 +#define WOLFBOOT_PARTITION_SWAP_ADDRESS 0x18340000 +#define WOLFBOOT_NSC_ADDRESS 0x1813C000 +#define WOLFBOOT_NSC_SIZE 0x4000 + +#define TZEN +#define __WOLFBOOT +#define DEBUG_UART +#define WOLFBOOT_HASH_SHA256 +#define WOLFBOOT_SIGN_ECC256 +#define PSOC_E8_PERI_TIMEOUT 64U + +#include "hal/psoc_e8.h" + +/* ---- Memory protection controller model ------------------------------- */ + +#define MOCK_MPC_PCS 8 +#define MOCK_MPC_IDX 64 + +struct mock_mpc { + uint32_t base; + uint32_t ctrl; + uint32_t blk_max; + uint32_t blk_cfg; + uint32_t idx; + uint32_t pc; + uint32_t lut[MOCK_MPC_PCS][MOCK_MPC_IDX]; + int ctrl_writes_with_auto_inc; +}; + +/* SMIF AHB: 128 KB blocks over 64 MB; RAMC1: 4 KB blocks over 512 KB. */ +static struct mock_mpc mpc_smif = { SMIF0_AHB_MPC_BASE, MPC_ROT_CTRL_AUTO_INC, + 0x3F, 12 }; +static struct mock_mpc mpc_ramc1 = { RAMC1_MPC_BASE, MPC_ROT_CTRL_AUTO_INC, + 0x0F, 7 }; + +static struct mock_mpc *mpc_of(uint32_t base) +{ + if (base == SMIF0_AHB_MPC_BASE) + return &mpc_smif; + ck_assert_uint_eq(base, RAMC1_MPC_BASE); + return &mpc_ramc1; +} + +static uint32_t *mpc_lut(uint32_t base) +{ + struct mock_mpc *m = mpc_of(base); + + ck_assert_uint_lt(m->pc, MOCK_MPC_PCS); + ck_assert_uint_lt(m->idx, MOCK_MPC_IDX); + /* The read-modify-write of one LUT word must not run with AUTO_INC. */ + ck_assert_uint_eq(m->ctrl & MPC_ROT_CTRL_AUTO_INC, 0); + return &m->lut[m->pc][m->idx]; +} + +#undef MPC_ROT_CTRL +#undef MPC_ROT_BLK_MAX +#undef MPC_ROT_BLK_CFG +#undef MPC_ROT_BLK_IDX +#undef MPC_ROT_BLK_PC +#undef MPC_ROT_BLK_LUT +#define MPC_ROT_CTRL(b) (mpc_of(b)->ctrl) +#define MPC_ROT_BLK_MAX(b) (mpc_of(b)->blk_max) +#define MPC_ROT_BLK_CFG(b) (mpc_of(b)->blk_cfg) +#define MPC_ROT_BLK_IDX(b) (mpc_of(b)->idx) +#define MPC_ROT_BLK_PC(b) (mpc_of(b)->pc) +#define MPC_ROT_BLK_LUT(b) (*mpc_lut(b)) + +/* ---- SAU model -------------------------------------------------------- */ + +#define MOCK_SAU_REGIONS 8 +static uint32_t mock_sau_ctrl; +static uint32_t mock_sau_rnr; +static uint32_t mock_sau_rbar[MOCK_SAU_REGIONS]; +static uint32_t mock_sau_rlar[MOCK_SAU_REGIONS]; + +static uint32_t *sau_reg(uint32_t *bank) +{ + ck_assert_uint_lt(mock_sau_rnr, MOCK_SAU_REGIONS); + return &bank[mock_sau_rnr]; +} + +#undef SAU_CTRL +#undef SAU_RNR +#undef SAU_RBAR +#undef SAU_RLAR +#define SAU_CTRL mock_sau_ctrl +#define SAU_RNR mock_sau_rnr +#define SAU_RBAR (*sau_reg(mock_sau_rbar)) +#define SAU_RLAR (*sau_reg(mock_sau_rlar)) + +/* ---- TRNG model ------------------------------------------------------- */ + +static uint32_t mock_crypto_ctl; +static uint32_t mock_tr_ctl0; +static uint32_t mock_tr_ctl1; +static uint32_t mock_tr_ctl2; +static uint32_t mock_tr_cmd; +static uint32_t mock_tr_garo; +static uint32_t mock_tr_firo; +static uint32_t mock_tr_mon_ctl; +static uint32_t mock_tr_mon_cmd; +static uint32_t mock_tr_mon_rc; +static uint32_t mock_tr_mon_ap; +static uint32_t mock_crypto_status; +static uint32_t mock_intr; +static int mock_initialized; /* TR_INITIALIZED is reported */ +static int mock_health_error_at; /* word index that reports a failure */ +static uint32_t mock_words; /* words produced so far */ + +/* A started generation reports DATA_AVAILABLE (or a health failure). */ +static uint32_t intr_read(void) +{ + uint32_t v = mock_intr; + + if (mock_initialized) + v |= CRYPTO_INTR_TR_INITIALIZED; + if (mock_tr_cmd != 0U) { + if ((int)mock_words == mock_health_error_at) + v |= CRYPTO_INTR_TR_RC_ERROR; + else + v |= CRYPTO_INTR_TR_DATA_AVAILABLE; + } + return v; +} + +static uint32_t result_read(void) +{ + ck_assert_uint_eq(mock_tr_ctl2, 32U); + mock_tr_cmd = 0; + return 0xA0B0C0D0U + mock_words++; +} + +#undef CRYPTO_CTL +#undef CRYPTO_INTR +#undef CRYPTO_INTR_CLR +#undef CRYPTO_TR_CTL0 +#undef CRYPTO_TR_CTL1 +#undef CRYPTO_TR_CTL2 +#undef CRYPTO_TR_CMD +#undef CRYPTO_TR_RESULT +#undef CRYPTO_TR_GARO_CTL +#undef CRYPTO_TR_FIRO_CTL +#undef CRYPTO_TR_MON_CTL +#undef CRYPTO_TR_MON_CMD +#undef CRYPTO_TR_MON_RC_CTL +#undef CRYPTO_TR_MON_AP_CTL +#undef CRYPTO_STATUS +#define CRYPTO_CTL mock_crypto_ctl +#define CRYPTO_INTR intr_read() +#define CRYPTO_INTR_CLR(m) (mock_intr &= ~(m)) +#define CRYPTO_TR_CTL0 mock_tr_ctl0 +#define CRYPTO_TR_CTL1 mock_tr_ctl1 +#define CRYPTO_TR_CTL2 mock_tr_ctl2 +#define CRYPTO_TR_CMD mock_tr_cmd +#define CRYPTO_TR_RESULT result_read() +#define CRYPTO_TR_GARO_CTL mock_tr_garo +#define CRYPTO_TR_FIRO_CTL mock_tr_firo +#define CRYPTO_TR_MON_CTL mock_tr_mon_ctl +#define CRYPTO_TR_MON_CMD mock_tr_mon_cmd +#define CRYPTO_TR_MON_RC_CTL mock_tr_mon_rc +#define CRYPTO_TR_MON_AP_CTL mock_tr_mon_ap +#define CRYPTO_STATUS mock_crypto_status + +/* ---- Console ---------------------------------------------------------- */ + +static char uart_buf[512]; +static unsigned int uart_len; + +void uart_write(const char *buf, unsigned int sz) +{ + ck_assert_uint_le(uart_len + sz, sizeof(uart_buf)); + memcpy(uart_buf + uart_len, buf, sz); + uart_len += sz; +} + +#include "../../hal/psoc_e8_tz.c" + +static uint32_t lut_nibble(const struct mock_mpc *m, uint32_t pc, uint32_t blk) +{ + return (m->lut[pc][blk / 8U] >> ((blk % 8U) * 4U)) & 0xFU; +} + +static void reset_mocks(void) +{ + memset(mpc_smif.lut, 0, sizeof(mpc_smif.lut)); + memset(mpc_ramc1.lut, 0, sizeof(mpc_ramc1.lut)); + mpc_smif.ctrl = mpc_ramc1.ctrl = MPC_ROT_CTRL_AUTO_INC; + mpc_smif.idx = mpc_ramc1.idx = 0; + mpc_smif.pc = mpc_ramc1.pc = 0; + memset(mock_sau_rbar, 0, sizeof(mock_sau_rbar)); + memset(mock_sau_rlar, 0, sizeof(mock_sau_rlar)); + mock_sau_ctrl = 0; + mock_sau_rnr = 0; + mock_intr = 0; + mock_initialized = 1; + mock_health_error_at = -1; + mock_words = 0; + mock_tr_cmd = 0; + mock_tr_ctl2 = 0; + mock_crypto_status = 0; + uart_len = 0; +} + +START_TEST(test_gtzc_boot_partition_and_sram1) +{ + uint32_t b; + + reset_mocks(); + hal_gtzc_init(); + + /* Boot partition: QSPI offset 0x140000, 1 MB = SMIF blocks 10..17. */ + for (b = 0; b < 64U; b++) { + uint32_t want = ((b >= 10U) && (b <= 17U)) ? MPC_ROT_NS_RW : 0U; + ck_assert_uint_eq(lut_nibble(&mpc_smif, PSOC_E8_PC_CM33, b), want); + } + /* All of SRAM1 (128 blocks of 4 KB). */ + for (b = 0; b < 128U; b++) + ck_assert_uint_eq(lut_nibble(&mpc_ramc1, PSOC_E8_PC_CM33, b), + MPC_ROT_NS_RW); + /* Only the CM33 context is touched. */ + for (b = 0; b < MOCK_MPC_IDX; b++) { + ck_assert_uint_eq(mpc_smif.lut[0][b], 0); + ck_assert_uint_eq(mpc_smif.lut[7][b], 0); + } + /* AUTO_INC is restored afterwards. */ + ck_assert_uint_eq(mpc_smif.ctrl, MPC_ROT_CTRL_AUTO_INC); + ck_assert_uint_eq(mpc_ramc1.ctrl, MPC_ROT_CTRL_AUTO_INC); +} +END_TEST + +START_TEST(test_mpc_preserves_neighbours) +{ + reset_mocks(); + /* Secure entries already in the word must survive the update. */ + mpc_smif.lut[PSOC_E8_PC_CM33][1] = 0x66666666U; + mpc_set_ns(SMIF0_AHB_MPC_BASE, 0x140000U, 0x20000U); /* block 10 */ + ck_assert_uint_eq(mpc_smif.lut[PSOC_E8_PC_CM33][1], 0x66666766U); +} +END_TEST + +START_TEST(test_mpc_rejects_unaligned) +{ + uint32_t i; + + reset_mocks(); + mpc_set_ns(SMIF0_AHB_MPC_BASE, 0x140000U + 0x1000U, 0x20000U); + mpc_set_ns(SMIF0_AHB_MPC_BASE, 0x140000U, 0x20000U + 0x10U); + mpc_set_ns(SMIF0_AHB_MPC_BASE, 0x140000U, 0U); + for (i = 0; i < MOCK_MPC_IDX; i++) + ck_assert_uint_eq(mpc_smif.lut[PSOC_E8_PC_CM33][i], 0); +} +END_TEST + +START_TEST(test_mpc_rejects_out_of_range) +{ + uint32_t i; + + reset_mocks(); + /* RAMC1 covers 512 KB; one block past the end is refused outright. */ + mpc_set_ns(RAMC1_MPC_BASE, 0x7F000U, 0x2000U); + for (i = 0; i < MOCK_MPC_IDX; i++) + ck_assert_uint_eq(mpc_ramc1.lut[PSOC_E8_PC_CM33][i], 0); +} +END_TEST + +START_TEST(test_mpc_init_busy) +{ + uint32_t i; + + reset_mocks(); + /* A controller that never finishes initialising is left untouched. */ + mpc_smif.blk_cfg |= MPC_ROT_BLK_CFG_INIT_BUSY; + mpc_set_ns(SMIF0_AHB_MPC_BASE, 0x140000U, 0x20000U); + mpc_smif.blk_cfg &= ~MPC_ROT_BLK_CFG_INIT_BUSY; + for (i = 0; i < MOCK_MPC_IDX; i++) + ck_assert_uint_eq(mpc_smif.lut[PSOC_E8_PC_CM33][i], 0); +} +END_TEST + +START_TEST(test_sau_regions) +{ + reset_mocks(); + /* Regions extended boot already programmed must be left alone. */ + mock_sau_rbar[0] = 0x42000000U; + mock_sau_rlar[0] = 0x43FFFFE1U; + mock_sau_ctrl = SAU_CTRL_ENABLE; + hal_tz_sau_init(); + + ck_assert_uint_eq(mock_sau_rbar[0], 0x42000000U); + ck_assert_uint_eq(mock_sau_rlar[0], 0x43FFFFE1U); + ck_assert_uint_eq(mock_sau_rbar[2], 0x08140000U); + ck_assert_uint_eq(mock_sau_rlar[2], 0x0823FFE0U | SAU_RLAR_ENABLE); + ck_assert_uint_eq(mock_sau_rbar[3], 0x24080000U); + ck_assert_uint_eq(mock_sau_rlar[3], 0x240FFFE0U | SAU_RLAR_ENABLE); + ck_assert_uint_eq(mock_sau_rbar[4], 0x1813C000U); + ck_assert_uint_eq(mock_sau_rlar[4], + 0x1813FFE0U | SAU_RLAR_NSC | SAU_RLAR_ENABLE); + ck_assert_uint_ne(mock_sau_ctrl & SAU_CTRL_ENABLE, 0); +} +END_TEST + +START_TEST(test_trng_init_config) +{ + reset_mocks(); + hal_trng_init(); + ck_assert_uint_ne(mock_crypto_ctl & CRYPTO_CTL_ENABLED, 0); + ck_assert_uint_eq(mock_tr_ctl0, CRYPTO_TR_CTL0_DEFAULT); + ck_assert_uint_eq(mock_tr_ctl1, CRYPTO_TR_CTL1_ALL_RO); + ck_assert_uint_eq(mock_tr_garo, CRYPTO_TR_POLY); + ck_assert_uint_eq(mock_tr_firo, CRYPTO_TR_POLY); + ck_assert_uint_eq(mock_tr_mon_cmd, CRYPTO_TR_MON_START_AP_RC); + hal_trng_fini(); + ck_assert_uint_eq(mock_tr_mon_cmd, 0); + ck_assert_uint_eq(mock_tr_ctl1, 0); +} +END_TEST + +START_TEST(test_trng_entropy_bytes) +{ + unsigned char out[7]; + + reset_mocks(); + memset(out, 0, sizeof(out)); + ck_assert_int_eq(hal_trng_get_entropy(out, sizeof(out)), 0); + /* Two words, little-endian, the second only partly used. */ + ck_assert_uint_eq(mock_words, 2); + ck_assert_uint_eq(out[0], 0xD0); + ck_assert_uint_eq(out[3], 0xA0); + ck_assert_uint_eq(out[4], 0xD1); + ck_assert_uint_eq(out[6], 0xB0); +} +END_TEST + +START_TEST(test_trng_health_failure) +{ + unsigned char out[16]; + + reset_mocks(); + mock_health_error_at = 1; + ck_assert_int_eq(hal_trng_get_entropy(out, sizeof(out)), -1); +} +END_TEST + +START_TEST(test_trng_not_initialized) +{ + unsigned char out[4]; + + reset_mocks(); + mock_initialized = 0; + ck_assert_int_eq(hal_trng_get_entropy(out, sizeof(out)), -1); + ck_assert_uint_eq(mock_words, 0); +} +END_TEST + +START_TEST(test_trng_busy) +{ + unsigned char out[4]; + + reset_mocks(); + mock_crypto_status = 1; + ck_assert_int_eq(hal_trng_get_entropy(out, sizeof(out)), -1); +} +END_TEST + +START_TEST(test_nsc_uart_bounds) +{ + static char big[PSOC_E8_NSC_UART_MAX + 1]; + + reset_mocks(); + psoc_e8_nsc_uart_write(NULL, 4); + psoc_e8_nsc_uart_write("abc", 0); + psoc_e8_nsc_uart_write(big, sizeof(big)); + ck_assert_uint_eq(uart_len, 0); + psoc_e8_nsc_uart_write("hello", 5); + ck_assert_uint_eq(uart_len, 5); + ck_assert_int_eq(memcmp(uart_buf, "hello", 5), 0); +} +END_TEST + +static Suite *psoc_e8_tz_suite(void) +{ + Suite *s = suite_create("psoc_e8_tz"); + TCase *tc = tcase_create("tz"); + + tcase_add_test(tc, test_gtzc_boot_partition_and_sram1); + tcase_add_test(tc, test_mpc_preserves_neighbours); + tcase_add_test(tc, test_mpc_rejects_unaligned); + tcase_add_test(tc, test_mpc_rejects_out_of_range); + tcase_add_test(tc, test_mpc_init_busy); + tcase_add_test(tc, test_sau_regions); + tcase_add_test(tc, test_trng_init_config); + tcase_add_test(tc, test_trng_entropy_bytes); + tcase_add_test(tc, test_trng_health_failure); + tcase_add_test(tc, test_trng_not_initialized); + tcase_add_test(tc, test_trng_busy); + tcase_add_test(tc, test_nsc_uart_bounds); + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s = psoc_e8_tz_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + return fails; +}