diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 825ec1f000..ba8c9d2459 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -863,6 +863,43 @@ jobs: arch: arm config-file: ./config/examples/stm32c5.config + psoc_c3_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + config-file: ./config/examples/psoc_c3.config + + # 128 KB part: same target, smaller partitions. + psoc_c3_128k_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + config-file: ./config/examples/psoc_c3.config + make-args: WOLFBOOT_PARTITION_SIZE=0xB000 WOLFBOOT_PARTITION_BOOT_ADDRESS=0x12008000 WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x12013000 WOLFBOOT_PARTITION_SWAP_ADDRESS=0x1201E000 + + # A TPM over each of the two serial back-ends. + psoc_c3_tpm_spi_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + config-file: ./config/examples/psoc_c3.config + make-args: WOLFTPM=1 + + psoc_c3_tpm_i2c_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + config-file: ./config/examples/psoc_c3.config + make-args: WOLFTPM=1 WOLFBOOT_TPM_I2C=1 + + # Builds the TIS-over-I2C transport itself, with no target port involved. + sim_tpm_i2c_test: + uses: ./.github/workflows/test-build.yml + with: + arch: host + config-file: ./config/examples/sim-tpm.config + make-args: WOLFBOOT_TPM_I2C=1 + stm32c5_no_clock_restore_test: uses: ./.github/workflows/test-build.yml with: diff --git a/.gitignore b/.gitignore index 2cb4a18e3c..42266351b4 100644 --- a/.gitignore +++ b/.gitignore @@ -143,6 +143,7 @@ tools/unit-tests/unit-aes128 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-disk tools/unit-tests/unit-fs-probe tools/unit-tests/unit-fat32 diff --git a/Makefile b/Makefile index ce668f0e84..651bb2bad3 100644 --- a/Makefile +++ b/Makefile @@ -801,7 +801,7 @@ src/flash_otp_keystore.o: $(PRIVATE_KEY) src/flash_otp_keystore.c keys: $(PRIVATE_KEY) clean: - $(Q)rm -f src/*.o hal/*.o hal/spi/*.o hal/uart/*.o test-app/*.o src/x86/*.o + $(Q)rm -f src/*.o hal/*.o hal/spi/*.o hal/i2c/*.o hal/uart/*.o test-app/*.o src/x86/*.o $(Q)rm -f src/wolfboot_tz_nsc.o $(Q)rm -f *.asm # TI cl2000 (ARCH=C2000) intermediate listings in repo root $(Q)rm -f $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/*.o $(WOLFBOOT_LIB_WOLFTPM)/src/*.o $(WOLFBOOT_LIB_WOLFTPM)/src/fwtpm/*.o $(WOLFBOOT_LIB_WOLFTPM)/hal/*.o $(WOLFBOOT_LIB_WOLFTPM)/examples/pcr/*.o @@ -923,7 +923,7 @@ cppcheck: --suppress="uninitvar" \ --suppress="zerodiv" \ --check-level=exhaustive \ - --error-exitcode=89 --std=c89 src/*.c hal/*.c hal/spi/*.c hal/uart/*.c + --error-exitcode=89 --std=c89 src/*.c hal/*.c hal/spi/*.c hal/i2c/*.c hal/uart/*.c otp: tools/keytools/otp/otp-keystore-primer.bin FORCE diff --git a/arch.mk b/arch.mk index 88725b9d41..4270ac876e 100644 --- a/arch.mk +++ b/arch.mk @@ -21,6 +21,7 @@ ARCH_FLASH_OFFSET?=0x0 # Default SPI driver name SPI_TARGET=$(TARGET) +I2C_TARGET=$(TARGET) # Default UART driver name UART_TARGET=$(TARGET) @@ -588,6 +589,48 @@ ifeq ($(ARCH),ARM) LSCRIPT_IN=hal/$(TARGET).ld SPI_TARGET=stm32 endif + + ifeq ($(TARGET),psoc_c3) + CORTEX_M33=1 + CFLAGS+=-Ihal + # Code executes from the Secure CBUS alias; the BootROM flash API is + # reached through the Secure SBUS alias, which the HAL derives from it. + ARCH_FLASH_OFFSET=0x12000000 + WOLFBOOT_ORIGIN=0x12000000 + LSCRIPT_IN=hal/$(TARGET).ld + SPI_TARGET=psoc_c3 + # Board wiring. Defaults match the PSOC Control C3 evaluation kit; override + # for another variant. + PSOC_C3_UART_SCB?=3 + PSOC_C3_UART_PORT?=6 + PSOC_C3_UART_PCLK_GR?=4 + PSOC_C3_UART_PCLK_IDX?=6 + PSOC_C3_PCLK_HZ?=48000000 + PSOC_C3_SPI_SCB?=2 + PSOC_C3_SPI_PORT?=7 + PSOC_C3_SPI_HSIOM_SEL?=17 + CFLAGS+=-DPSOC_C3_UART_SCB=$(PSOC_C3_UART_SCB) + CFLAGS+=-DPSOC_C3_UART_PORT=$(PSOC_C3_UART_PORT) + CFLAGS+=-DPSOC_C3_UART_PCLK_GR=$(PSOC_C3_UART_PCLK_GR) + CFLAGS+=-DPSOC_C3_UART_PCLK_IDX=$(PSOC_C3_UART_PCLK_IDX) + CFLAGS+=-DPSOC_C3_PCLK_HZ=$(PSOC_C3_PCLK_HZ) + CFLAGS+=-DPSOC_C3_SPI_SCB=$(PSOC_C3_SPI_SCB) + CFLAGS+=-DPSOC_C3_SPI_PORT=$(PSOC_C3_SPI_PORT) + CFLAGS+=-DPSOC_C3_SPI_HSIOM_SEL=$(PSOC_C3_SPI_HSIOM_SEL) + # I2C: SCB0 on P9.0 (SCL) and P9.2 (SDA), HSIOM selector 19. + PSOC_C3_I2C_SCB?=0 + PSOC_C3_I2C_PORT?=9 + PSOC_C3_I2C_SCL_PIN?=0 + PSOC_C3_I2C_SDA_PIN?=2 + PSOC_C3_I2C_HSIOM_SEL?=19 + PSOC_C3_I2C_HZ?=100000 + CFLAGS+=-DPSOC_C3_I2C_SCB=$(PSOC_C3_I2C_SCB) + CFLAGS+=-DPSOC_C3_I2C_PORT=$(PSOC_C3_I2C_PORT) + CFLAGS+=-DPSOC_C3_I2C_SCL_PIN=$(PSOC_C3_I2C_SCL_PIN) + CFLAGS+=-DPSOC_C3_I2C_SDA_PIN=$(PSOC_C3_I2C_SDA_PIN) + CFLAGS+=-DPSOC_C3_I2C_HSIOM_SEL=$(PSOC_C3_I2C_HSIOM_SEL) + CFLAGS+=-DPSOC_C3_I2C_HZ=$(PSOC_C3_I2C_HZ) + endif ifeq ($(TARGET),m2354) CORTEX_M23=1 CFLAGS+=-Ihal diff --git a/config/examples/psoc_c3.config b/config/examples/psoc_c3.config new file mode 100644 index 0000000000..42629b746d --- /dev/null +++ b/config/examples/psoc_c3.config @@ -0,0 +1,51 @@ +ARCH?=ARM +TARGET?=psoc_c3 +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 +# Flags rewrite in place: write_row erases before programming and the HAL does +# a read-modify-write, so the two-sector write-once scheme is not needed. +NVM_FLASH_WRITEONCE?=0 +# This family erases flash to 0x00, not 0xFF. Without the inverted polarity +# the state written by wolfBoot_update_trigger() is indistinguishable from +# erased flash and no update is ever taken. +FLAGS_INVERT?=1 + +WOLFBOOT_VERSION?=1 +V?=0 +SPMATH?=1 +RAM_CODE?=1 +DUALBANK_SWAP?=0 +DEBUG_UART=1 + +# PSOC Control C3, 512 KB internal flash, 512-byte rows. Addresses are in the +# Secure CBUS alias, which is where code executes; the HAL translates to the +# Secure SBUS alias for the BootROM flash API. +# +# 0x12000000 - 0x1200FFFF wolfBoot (64 KB) +# 0x12010000 - 0x12026FFF BOOT (0x17000) +# 0x12027000 - 0x1203DFFF UPDATE (0x17000) +# 0x1203E000 - 0x1203E1FF SWAP (512 B) +# 0x12040000 - 0x1207FFFF free for the application +# +# The layout stays inside the low 256 KB because the debugger flash algorithms +# published for this family stop there, so an image staged above it cannot be +# provisioned. A 128 KB part uses 0xB000 partitions at 0x12008000 / 0x12013000 +# / 0x1201E000. +# +# Board wiring is set by make options defaulted in arch.mk for the evaluation +# kit: PSOC_C3_UART_*, PSOC_C3_SPI_*, PSOC_C3_I2C_* and PSOC_C3_PCLK_HZ. See +# docs/Targets.md for the full table. +WOLFBOOT_SECTOR_SIZE?=0x200 +WOLFBOOT_PARTITION_SIZE?=0x17000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x12010000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x12027000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x1203E000 diff --git a/docs/TPM.md b/docs/TPM.md index 512f0a8ec3..43f1550d16 100644 --- a/docs/TPM.md +++ b/docs/TPM.md @@ -7,6 +7,7 @@ In wolfBoot we support TPM based root of trust, sealing/unsealing, cryptographic | Config Option | Preprocessor Macro | Description | | ------------- | ------------------ | ----------------------------------- | | `WOLFTPM=1` | `WOLFBOOT_TPM` | Enables wolfTPM support | +| `WOLFBOOT_TPM_I2C=1` | `WOLFBOOT_TPM_I2C` | Use the TIS-over-I2C transport instead of SPI. Builds `hal/i2c/i2c_drv_$(I2C_TARGET).c` in place of the SPI driver and also defines `WOLFTPM_ADV_IO`, which the I2C transport requires. Only targets that provide an I2C back-end can use it. | | `WOLFBOOT_TPM_VERIFY=1` | `WOLFBOOT_TPM_VERIFY` | Enables cryptographic offloading for RSA2048 and ECC256/384 to the TPM. | | `WOLFBOOT_TPM_KEYSTORE=1` | `WOLFBOOT_TPM_KEYSTORE` | Enables TPM based root of trust. NV Index must store a hash of the trusted public key. | | `WOLFBOOT_TPM_KEYSTORE_NV_BASE=0x` | `WOLFBOOT_TPM_KEYSTORE_NV_BASE=0x` | NV index in platform range 0x1400000 - 0x17FFFFF. | diff --git a/docs/Targets.md b/docs/Targets.md index c91d0f4d71..ed60d3bb82 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -9,6 +9,7 @@ This README describes configuration of supported targets. * [Cortex-A53 / Raspberry PI 3](#cortex-a53--raspberry-pi-3-experimental) * [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 AURIX TC3xx](#infineon-aurix-tc3xx) * [Infineon AURIX TC4xx](#infineon-aurix-tc4xx) * [Intel x86-64 Intel FSP](#intel-x86_64-with-intel-fsp-support) @@ -8914,6 +8915,153 @@ Attempting boot from partition A ``` At this point, the kernel image in partition "A" is verified and staged and you should be seeing the log messages of your OS booting. +## Infineon PSOC Control C3 + +The [PSOC Control C3](https://www.infineon.com/products/microcontroller/32-bit-psoc-arm-cortex/32-bit-psoc-control-arm-cortex-m33-mcu/psoc-control-c3-main-line) family (PSC3, CAT1B) is an Arm Cortex-M33 motor-control MCU. wolfBoot runs as the first application the BootROM launches, verifies the signed firmware in the BOOT partition and jumps to it. + +The port is bare metal: it uses no Peripheral Driver Library, no BSP and no generated configuration, so it builds with nothing but an `arm-none-eabi` toolchain. It covers the PSOC Control C3 parts only. Other CAT1B devices such as the CYW20829 program their flash through the classic SROM/IPC mailbox instead of the BootROM table used here, and are not supported by this HAL. + +Verified on hardware on an evaluation kit fitted with a C3M6 (512 KB flash, 128 KB SRAM): console output, integrity and ECC256 signature verification, the handoff to the application, and a full A/B update through the swap sector with the new version confirming success. The 128 KB layout and the TPM transports are build-tested only. + +### Address aliases + +Every memory on this family is visible through four aliases, crossing Secure and Non-secure with the code bus (CBUS) and the system bus (SBUS): + +``` +Flash 0x02000000 NS-CBUS SRAM 0x04000000 NS-CBUS + 0x12000000 S-CBUS 0x14000000 S-CBUS + 0x22000000 NS-SBUS 0x24000000 NS-SBUS + 0x32000000 S-SBUS 0x34000000 S-SBUS +``` + +wolfBoot links and executes from the Secure CBUS alias at `0x12000000`, so every address in the `.config` is in that space. The BootROM flash API accepts SBUS addresses only, and the HAL derives one by setting bit 29 of the target address. + +### Flash + +Flash is organised as 512-byte rows, which are the erase and the program granularity alike, inside 128 KB sectors. `WOLFBOOT_SECTOR_SIZE` is therefore `0x200`. + +**Erased flash on this family reads as zero, not `0xFF`.** The configs therefore set `FLAGS_INVERT=1`, which selects the inverted partition and sector flag polarity and makes the image fill byte `0x00` so it matches the erased state. Without it the state written by `wolfBoot_update_trigger()` cannot be told apart from erased flash and no update is ever taken. + +They also set `NVM_FLASH_WRITEONCE=0`. The BootROM write call erases a row before programming it and the HAL does a read-modify-write around that, so a flag can be rewritten in place and the two-sector write-once scheme, whose freshness check assumes an `0xFF` erase, is neither needed nor correct here. + +Programming goes through a BootROM function table at a fixed address rather than a flash controller register block. The HAL calls its blocking erase and write entries directly and needs no vendor library. It range-checks the table before first use, so a device whose ROM lays the table out differently fails cleanly instead of branching into an arbitrary address. + +### Build options + +Board wiring is set by make options, defaulted in `arch.mk`: + +| Option | Default | Purpose | +| --- | --- | --- | +| `PSOC_C3_UART_SCB` | 3 | console SCB instance | +| `PSOC_C3_UART_PORT` | 6 | console pin group; RX is pin 2, TX is pin 3 | +| `PSOC_C3_UART_PCLK_GR` | 4 | peripheral clock group for the console | +| `PSOC_C3_UART_PCLK_IDX` | 6 | peripheral clock index for the console | +| `PSOC_C3_PCLK_HZ` | 48000000 | clock feeding the peripheral group | +| `PSOC_C3_SPI_SCB` | 2 | SPI instance | +| `PSOC_C3_SPI_PORT` | 7 | SPI pin group | +| `PSOC_C3_SPI_HSIOM_SEL` | 17 | HSIOM selector for the SPI function | + +SCB4 and SCB5 are absent on the smaller packages of this family. Only SCB0 to SCB3 are present on every part, so a variant in a small package needs `PSOC_C3_SPI_SCB` pointed at one of those. + +The peripheral clock index is a per-part value rather than a family constant, and the value that a given part uses is not always the one its headers document. Verify it on the board before relying on it. + +### Flash layout + +`config/examples/psoc_c3.config` targets a 512 KB part: + +``` +0x12000000 - 0x1200FFFF wolfBoot (64 KB) +0x12010000 - 0x12026FFF BOOT partition (0x17000, 92 KB) +0x12027000 - 0x1203DFFF UPDATE partition (0x17000, 92 KB) +0x1203E000 - 0x1203E1FF SWAP sector (512 B) +0x12040000 - 0x1207FFFF unused +``` + +Everything sits inside the low 256 KB deliberately. The silicon has 512 KB and wolfBoot erases and programs all of it at run time through the BootROM, but every J-Link flash algorithm published for this family stops at 256 KB, so an image staged above that cannot be provisioned by the debugger. Keeping the partitions low means the whole factory image can be written with the tools that exist; the upper 256 KB is left to the application. + +A 128 KB part uses the same layout scaled down, set on the command line rather than in a second config: + +```sh +make WOLFBOOT_PARTITION_SIZE=0xB000 \ + WOLFBOOT_PARTITION_BOOT_ADDRESS=0x12008000 \ + WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x12013000 \ + WOLFBOOT_PARTITION_SWAP_ADDRESS=0x1201E000 +``` + +Flash size is the only geometry that differs across the family, and it differs within every family name, so size the partitions to the part rather than to the marketing name. + +### Building + +```sh +cp config/examples/psoc_c3.config .config +make clean +make +``` + +The default signing scheme is ECC256 with SHA256. The build produces `wolfboot.bin`, `test-app/image_v1_signed.bin` and `factory.bin`. + +Editing a HAL header does not always trigger a rebuild of the objects that include it, so run `make clean` after changing `hal/psoc_c3.h`. + +### Flashing + +Program through the non-secure SBUS alias, which is the aperture the debugger's flash algorithm drives, even though the image executes from `0x12000000`: + +```sh +JLinkExe -SelectEmuBySN -device PSC3xxF -if SWD -speed 4000 +``` + +then, at the prompt: + +``` +loadbin factory.bin, 0x22000000 +r +g +``` + +Name a real PSC3 device. The part is an ADIv6 CoreSight SoC-600 design, and a generic `Cortex-M33` selection cannot find its access ports. + +The Infineon OpenOCD shipped with ModusToolbox is an alternative, and its `ENABLE_ACQUIRE` option recovers a part whose boot loop a bad image has broken: + +```sh +openocd -f interface/kitprog3.cfg \ + -c "set ENABLE_ACQUIRE 1" \ + -c "set SERIES psc3; set DEVICE a0; set BOARD generic" \ + -f target/infineon/cat1b/psc3.cfg \ + -c "init; reset init" \ + -c "flash write_image erase factory.bin 0x32000000 bin" \ + -c "reset run; shutdown" +``` + +### Testing an update + +```sh +./tools/keytools/sign --ecc256 --sha256 \ + test-app/image.bin wolfboot_signing_private_key.der 2 +``` + +Write the result to the UPDATE partition and let the test application trigger the update. wolfBoot swaps the partitions on the next reset, and the application confirms the new version so it is not rolled back. + +### Serial back-ends + +Two SCB back-ends ship with the target, both configured entirely through make options. + +`hal/spi/spi_drv_psoc_c3.c` is an SPI master. It drives chip select as a GPIO rather than from the SCB, so a TPM transport can hold it asserted across the wait-state poll the TIS protocol requires. The defaults target the evaluation kit's mikroBUS headers, which route SPI to SCB2 on port 7: P7.0 clock, P7.1 MOSI, P7.2 MISO, P7.3 chip select. The signal order across a port differs per SCB, so `PSOC_C3_SPI_SCK_PIN`, `_MOSI_PIN`, `_MISO_PIN` and `_CS_PIN` are set individually rather than assumed, as is `PSOC_C3_SPI_HSIOM_SEL` (13 on SCB0, 17 on SCB2 and SCB5, 18 on SCB1, SCB3 and SCB4). + +`hal/i2c/i2c_drv_psoc_c3.c` is an I2C master behind the generic interface in `include/i2c_drv.h`, which this target adds. Defaults are SCB0 on P9.0 (SCL) and P9.2 (SDA) at HSIOM selector 19, matching the kit's mikroBUS and Arduino headers; note the two pins are not adjacent. The pins are open drain with the input buffer enabled, which I2C needs in order to read SDA and to see a device stretching the clock. + +| Option | Default | Meaning | +| --- | --- | --- | +| `PSOC_C3_I2C_SCB` | 0 | SCB instance | +| `PSOC_C3_I2C_PORT` | 9 | pin group | +| `PSOC_C3_I2C_SCL_PIN` | 0 | SCL pin within the group | +| `PSOC_C3_I2C_SDA_PIN` | 2 | SDA pin within the group | +| `PSOC_C3_I2C_HSIOM_SEL` | 19 | HSIOM selector for the I2C function | +| `PSOC_C3_I2C_HZ` | 100000 | bus rate | + +A TPM is added with `make WOLFTPM=1`, which uses the SPI back-end. Adding `WOLFBOOT_TPM_I2C=1` selects the I2C back-end and the TIS-over-I2C transport instead. CI builds both. See [docs/TPM.md](TPM.md) for the option itself. + +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 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/hal/i2c/i2c_drv_psoc_c3.c b/hal/i2c/i2c_drv_psoc_c3.c new file mode 100644 index 0000000000..3a168becb1 --- /dev/null +++ b/hal/i2c/i2c_drv_psoc_c3.c @@ -0,0 +1,256 @@ +/* i2c_drv_psoc_c3.c + * + * Driver for the I2C back-end on Infineon PSOC Control C3. + * + * Pinout: see i2c_drv_psoc_c3.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +#include +#include "i2c_drv.h" + +#ifdef TARGET_psoc_c3 +#ifdef WOLFBOOT_TPM_I2C + +#ifndef PSOC_C3_PCLK_HZ +#define PSOC_C3_PCLK_HZ 48000000UL +#endif + +/* The SCB oversamples the I2C clock by the sum of the low and high phase + * counts. 8 + 8 is the vendor default for standard and fast mode. */ +#define PSOC_C3_I2C_OVS_LOW 8UL +#define PSOC_C3_I2C_OVS_HIGH 8UL +#define PSOC_C3_I2C_OVS (PSOC_C3_I2C_OVS_LOW + PSOC_C3_I2C_OVS_HIGH) + +/* Bound every wait: a missing or held-down device must let the caller report + * a failure rather than wedge the bootloader in a spin loop. */ +#ifndef PSOC_C3_I2C_TIMEOUT +#define PSOC_C3_I2C_TIMEOUT (1000000UL) +#endif + +static int initialized = 0; + +static void i2c_pins_setup(void) +{ + /* I2C is open drain and needs the input buffer on, both to read SDA and + * to see a slave stretching the clock. */ + psoc_c3_pin_setup(PSOC_C3_I2C_PORT, PSOC_C3_I2C_SCL_PIN, + PSOC_C3_I2C_HSIOM_SEL, GPIO_CFG_DM_OD_LOW); + psoc_c3_pin_setup(PSOC_C3_I2C_PORT, PSOC_C3_I2C_SDA_PIN, + PSOC_C3_I2C_HSIOM_SEL, GPIO_CFG_DM_OD_LOW); +} + +/* Route the SCB's clock destination to an integer divider. The 16.5 + * fractional divider the console uses does not exist in every group, and the + * group carrying most peripherals provides only integer ones. */ +static int i2c_clock_setup(void) +{ + return psoc_c3_pclk_setup(PSOC_C3_I2C_PCLK_GR, PSOC_C3_I2C_PCLK_IDX, + PSOC_C3_I2C_PCLK_DIV, PERI_PCLK_DIV_TYPE_16, PSOC_C3_PCLK_HZ, + PSOC_C3_I2C_HZ * PSOC_C3_I2C_OVS); +} + +/* Wait for any of the master interrupt causes in mask, then report which. The + * flags are write-one-to-clear and are cleared before each transfer step. */ +static int i2c_wait_m(uint32_t mask, uint32_t *got) +{ + uint32_t timeout = PSOC_C3_I2C_TIMEOUT; + uint32_t intr; + + do { + intr = SCB_INTR_M(PSOC_C3_I2C_SCB_BASE) & mask; + if (intr != 0) { + *got = intr; + SCB_INTR_M(PSOC_C3_I2C_SCB_BASE) = intr; + return I2C_OK; + } + timeout--; + } while (timeout > 0); + + *got = 0; + return I2C_ERR_TIMEOUT; +} + +/* Send a start (or repeated start) and the address byte, and report whether + * the device acknowledged it. */ +static int i2c_start(uint8_t addr, int read) +{ + uint32_t got; + int ret; + + SCB_INTR_M(PSOC_C3_I2C_SCB_BASE) = SCB_INTR_M_ALL; + SCB_TX_FIFO_WR(PSOC_C3_I2C_SCB_BASE) = + (uint32_t)((addr << 1) | (read ? 1U : 0U)); + /* Unconditional start, not START_ON_IDLE: the bus-busy detector reads + * busy until it has observed a stop condition, so a controller that has + * just been enabled waits for an idle that never arrives. */ + SCB_I2C_M_CMD(PSOC_C3_I2C_SCB_BASE) = SCB_I2C_M_CMD_M_START; + + ret = i2c_wait_m(SCB_INTR_M_I2C_ACK | SCB_INTR_M_I2C_NACK | + SCB_INTR_M_I2C_ARB_LOST | SCB_INTR_M_I2C_BUS_ERROR, &got); + if (ret != I2C_OK) + return ret; + if ((got & (SCB_INTR_M_I2C_ARB_LOST | SCB_INTR_M_I2C_BUS_ERROR)) != 0) + return I2C_ERR_BUS; + if ((got & SCB_INTR_M_I2C_NACK) != 0) + return I2C_ERR_NACK; + return I2C_OK; +} + +static int i2c_stop(void) +{ + uint32_t got; + int ret; + + SCB_I2C_M_CMD(PSOC_C3_I2C_SCB_BASE) = SCB_I2C_M_CMD_M_STOP; + ret = i2c_wait_m(SCB_INTR_M_I2C_STOP | SCB_INTR_M_I2C_BUS_ERROR, &got); + if (ret != I2C_OK) + return ret; + /* i2c_wait_m() reports only that one of the masked causes fired, so a bus + * error would otherwise be indistinguishable from a clean stop. */ + if ((got & SCB_INTR_M_I2C_BUS_ERROR) != 0) + return I2C_ERR_BUS; + return I2C_OK; +} + +void i2c_init(void) +{ + if (initialized) + return; + + psoc_c3_peri_init(); + i2c_pins_setup(); + /* Leave the block alone if its clock never started: an SCB without a + * running divider bus-faults on the first register access. Transfers then + * report a bus error rather than taking the fault. */ + if (i2c_clock_setup() != 0) + return; + initialized = 1; + + SCB_CTRL(PSOC_C3_I2C_SCB_BASE) = 0; + /* The clock phases live here, not in CTRL.OVS, which I2C mode ignores. */ + SCB_I2C_CTRL(PSOC_C3_I2C_SCB_BASE) = SCB_I2C_CTRL_MASTER_MODE | + ((PSOC_C3_I2C_OVS_HIGH - 1UL) << SCB_I2C_CTRL_HIGH_PHASE_Pos) | + ((PSOC_C3_I2C_OVS_LOW - 1UL) << SCB_I2C_CTRL_LOW_PHASE_Pos); + SCB_I2C_CFG(PSOC_C3_I2C_SCB_BASE) = SCB_I2C_CFG_DEFAULT; + SCB_TX_CTRL(PSOC_C3_I2C_SCB_BASE) = SCB_I2C_TX_CTRL_VAL; + SCB_RX_CTRL(PSOC_C3_I2C_SCB_BASE) = SCB_I2C_RX_CTRL_VAL; + SCB_TX_FIFO_CTRL(PSOC_C3_I2C_SCB_BASE) = 0; + SCB_RX_FIFO_CTRL(PSOC_C3_I2C_SCB_BASE) = 0; + SCB_INTR_M_MASK(PSOC_C3_I2C_SCB_BASE) = 0; /* polled, never interrupts */ + SCB_INTR_M(PSOC_C3_I2C_SCB_BASE) = SCB_INTR_M_ALL; + SCB_CTRL(PSOC_C3_I2C_SCB_BASE) = SCB_CTRL_ENABLED | SCB_CTRL_MODE_I2C; +} + +void i2c_release(void) +{ + uint32_t got; + + if (!initialized) + return; + /* Leave the bus idle rather than mid-transfer if a caller gave up. */ + if ((SCB_I2C_STATUS(PSOC_C3_I2C_SCB_BASE) & SCB_I2C_STATUS_BUS_BUSY) != 0) { + SCB_I2C_M_CMD(PSOC_C3_I2C_SCB_BASE) = SCB_I2C_M_CMD_M_STOP; + (void)i2c_wait_m(SCB_INTR_M_I2C_STOP | SCB_INTR_M_I2C_BUS_ERROR, &got); + } +} + +int i2c_write(uint8_t addr, const uint8_t *buf, uint32_t len, int stop) +{ + uint32_t got; + uint32_t i; + int ret; + + if ((buf == NULL) && (len > 0)) + return I2C_ERR_ARG; + if (!initialized) + return I2C_ERR_BUS; + + ret = i2c_start(addr, 0); + if (ret != I2C_OK) { + /* An unacknowledged address still leaves the bus owned. */ + (void)i2c_stop(); + return ret; + } + + for (i = 0; i < len; i++) { + SCB_TX_FIFO_WR(PSOC_C3_I2C_SCB_BASE) = (uint32_t)buf[i]; + ret = i2c_wait_m(SCB_INTR_M_I2C_ACK | SCB_INTR_M_I2C_NACK | + SCB_INTR_M_I2C_ARB_LOST | SCB_INTR_M_I2C_BUS_ERROR, &got); + if (ret != I2C_OK) { + (void)i2c_stop(); + return ret; + } + if ((got & (SCB_INTR_M_I2C_ARB_LOST | SCB_INTR_M_I2C_BUS_ERROR)) != 0) { + (void)i2c_stop(); + return I2C_ERR_BUS; + } + if ((got & SCB_INTR_M_I2C_NACK) != 0) { + (void)i2c_stop(); + return I2C_ERR_NACK; + } + } + + if (stop) + return i2c_stop(); + return I2C_OK; +} + +int i2c_read(uint8_t addr, uint8_t *buf, uint32_t len, int stop) +{ + uint32_t timeout; + uint32_t i; + int ret; + + if ((buf == NULL) || (len == 0)) + return I2C_ERR_ARG; + if (!initialized) + return I2C_ERR_BUS; + + ret = i2c_start(addr, 1); + if (ret != I2C_OK) { + (void)i2c_stop(); + return ret; + } + + for (i = 0; i < len; i++) { + /* The protocol requires the last byte to be NACKed so the device + * releases the bus. */ + SCB_I2C_M_CMD(PSOC_C3_I2C_SCB_BASE) = (i + 1U == len) ? + SCB_I2C_M_CMD_M_NACK : SCB_I2C_M_CMD_M_ACK; + + timeout = PSOC_C3_I2C_TIMEOUT; + while (((SCB_RX_FIFO_STATUS(PSOC_C3_I2C_SCB_BASE) & + SCB_RX_FIFO_USED_Msk) == 0) && (timeout > 0)) + timeout--; + if (timeout == 0) { + (void)i2c_stop(); + return I2C_ERR_TIMEOUT; + } + buf[i] = (uint8_t)SCB_RX_FIFO_RD(PSOC_C3_I2C_SCB_BASE); + } + + if (stop) + return i2c_stop(); + return I2C_OK; +} + + +#endif /* WOLFBOOT_TPM_I2C */ +#endif /* TARGET_psoc_c3 */ diff --git a/hal/i2c/i2c_drv_psoc_c3.h b/hal/i2c/i2c_drv_psoc_c3.h new file mode 100644 index 0000000000..ee873c5d03 --- /dev/null +++ b/hal/i2c/i2c_drv_psoc_c3.h @@ -0,0 +1,70 @@ +/* i2c_drv_psoc_c3.h + * + * Pinout and options for the I2C back-end on Infineon PSOC Control C3. + * + * 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 I2C_DRV_PSOC_C3_H_INCLUDED +#define I2C_DRV_PSOC_C3_H_INCLUDED + +#include "hal/psoc_c3.h" + +/* Defaults match the PSOC Control C3 evaluation kit: SCB0 on P9.0 (SCL) and + * P9.2 (SDA), which the kit routes to the mikroBUS and Arduino headers. The + * pins are not adjacent. Every value is a make option, so another package or + * board is a configuration change rather than an edit. + * + * This transport is not hardware-validated: the controller does not yet drive + * the bus. See docs/Targets.md. */ +#ifndef PSOC_C3_I2C_SCB +#define PSOC_C3_I2C_SCB 0 +#endif +#ifndef PSOC_C3_I2C_PORT +#define PSOC_C3_I2C_PORT 9 +#endif +#ifndef PSOC_C3_I2C_SCL_PIN +#define PSOC_C3_I2C_SCL_PIN 0 +#endif +#ifndef PSOC_C3_I2C_SDA_PIN +#define PSOC_C3_I2C_SDA_PIN 2 +#endif +/* Active functionality 7, which is I2C on every port of this family. */ +#ifndef PSOC_C3_I2C_HSIOM_SEL +#define PSOC_C3_I2C_HSIOM_SEL 19 +#endif +#ifndef PSOC_C3_I2C_HZ +#define PSOC_C3_I2C_HZ 100000UL +#endif +/* Peripheral clock feeding the SCB. Divider types are not uniform across + * groups, so a destination programmed with a type its group does not provide + * silently produces no clock. */ +#ifndef PSOC_C3_I2C_PCLK_GR +#define PSOC_C3_I2C_PCLK_GR 5 +#endif +#ifndef PSOC_C3_I2C_PCLK_IDX +#define PSOC_C3_I2C_PCLK_IDX 0 +#endif +#ifndef PSOC_C3_I2C_PCLK_DIV +#define PSOC_C3_I2C_PCLK_DIV 0 +#endif + +#define PSOC_C3_I2C_SCB_BASE PSOC_C3_SCB_BASE(PSOC_C3_I2C_SCB) + +#endif /* I2C_DRV_PSOC_C3_H_INCLUDED */ diff --git a/hal/i2c/i2c_drv_sim.c b/hal/i2c/i2c_drv_sim.c new file mode 100644 index 0000000000..5e66ed7aa7 --- /dev/null +++ b/hal/i2c/i2c_drv_sim.c @@ -0,0 +1,60 @@ +/* i2c_drv_sim.c + * + * I2C back-end for the simulator target. + * + * 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 "i2c_drv.h" + +#ifdef WOLFBOOT_TPM_I2C + +/* There is no bus under the simulator: the simulated TPM is reached over a + * socket instead. These exist so the I2C transport compiles and links on the + * host, which is what the build test covers. Every transfer reports that + * nothing acknowledged, so a caller that reaches them fails rather than + * believing an empty buffer. */ + +void i2c_init(void) +{ +} + +void i2c_release(void) +{ +} + +int i2c_write(uint8_t addr, const uint8_t *buf, uint32_t len, int stop) +{ + (void)addr; + (void)buf; + (void)len; + (void)stop; + return I2C_ERR_NACK; +} + +int i2c_read(uint8_t addr, uint8_t *buf, uint32_t len, int stop) +{ + (void)addr; + (void)buf; + (void)len; + (void)stop; + return I2C_ERR_NACK; +} + +#endif /* WOLFBOOT_TPM_I2C */ diff --git a/hal/psoc_c3.c b/hal/psoc_c3.c new file mode 100644 index 0000000000..132f70c5ea --- /dev/null +++ b/hal/psoc_c3.c @@ -0,0 +1,418 @@ +/* psoc_c3.c + * + * HAL for Infineon PSOC Control C3 (CAT1B, 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 + +#include "image.h" +#include "hal.h" +#include "printf.h" +#include "hal/psoc_c3.h" +#include + +/* Console SCB and its pin muxing. Defaults match the PSOC Control C3 + * evaluation kit, where the on-board debug probe's virtual COM port lands on + * SCB3 P6.2/P6.3. */ +#ifndef PSOC_C3_UART_SCB +#define PSOC_C3_UART_SCB 3 +#endif +#define PSOC_C3_UART_SCB_BASE PSOC_C3_SCB_BASE(PSOC_C3_UART_SCB) +#ifndef PSOC_C3_UART_PORT +#define PSOC_C3_UART_PORT 6 +#endif +#ifndef PSOC_C3_UART_RX_PIN +#define PSOC_C3_UART_RX_PIN 2 +#endif +#ifndef PSOC_C3_UART_TX_PIN +#define PSOC_C3_UART_TX_PIN 3 +#endif +#ifndef PSOC_C3_UART_HSIOM_SEL +#define PSOC_C3_UART_HSIOM_SEL 16 +#endif + +/* Peripheral clock routing for the console SCB. The index is NOT the one the + * PSC3M5 headers give for SCB3: on a C3M6 the console is driven through + * group 4 index 6, confirmed by perturbing the divider on a running part. + * Treat these as per-part values and override them for another variant. */ +#ifndef PSOC_C3_UART_PCLK_GR +#define PSOC_C3_UART_PCLK_GR 4 +#endif +#ifndef PSOC_C3_UART_PCLK_IDX +#define PSOC_C3_UART_PCLK_IDX 6 +#endif +#ifndef PSOC_C3_UART_PCLK_DIV +#define PSOC_C3_UART_PCLK_DIV 1 +#endif + +/* Frequency feeding the peripheral clock group. wolfBoot does not touch the + * clock tree, so this is the power-on IHO rate. */ +#ifndef PSOC_C3_PCLK_HZ +#define PSOC_C3_PCLK_HZ 48000000UL +#endif +#ifndef PSOC_C3_UART_BAUD +#define PSOC_C3_UART_BAUD 115200UL +#endif +#define PSOC_C3_UART_OVS 8UL + +/* The BootROM refresh bookkeeping is indexed by absolute 128 KB sector, so this + * must cover the whole device rather than just the partitions. Eight entries + * span 1 MB, comfortably more than any part in the family. */ +#ifndef PSOC_C3_FLASH_SECTORS +#define PSOC_C3_FLASH_SECTORS 8 +#endif + +/* BootROM flash API: a table of function pointers at a fixed address. Only the + * three blocking row operations are used; the 29 preceding entries are skipped + * by the reserved field. The pointers are range-checked before first use, so a + * table layout change fails loudly rather than branching into hyperspace. */ +#define PSOC_C3_ROM_BASE (0x10800000UL) +#define PSOC_C3_ROM_SIZE (0x10000UL) +#define PSOC_C3_ROM_FUNC_ADDR (0x1080FF6CUL) +#define PSOC_C3_ROM_SKIP 29 + +#define CYBOOT_FLASH_SUCCESS (0x0D50B002UL) +/* flags == 0 selects a blocking operation and lets the ROM compute the row's + * column-33 metadata itself. */ +#define CYBOOT_FLAGS_BLOCKING (0UL) + +typedef struct { + uint32_t min_count; + uint32_t max_count; + uint32_t min_page_addr; + uint32_t scratch_row_idx; +} cyboot_flash_refresh_t; + +typedef struct { + uint32_t flags; + uint32_t hv_params_addr; + cyboot_flash_refresh_t *refresh; + void (*callback_pre_irq)(void *ctx); + void (*callback_post_irq)(void *ctx); + void (*callback_complete)(void *ctx); + uint32_t callback_param; + uint32_t state; + uint32_t flash_addr; + uint32_t data_addr; + uint32_t reserved[2]; +} cyboot_flash_context_t; + +typedef uint32_t (*cyboot_flash_erase_row_t)(uint32_t addr, + cyboot_flash_context_t *ctx); +typedef uint32_t (*cyboot_flash_row_op_t)(uint32_t addr, uint32_t *data, + cyboot_flash_context_t *ctx); + +typedef struct { + uint32_t reserved[PSOC_C3_ROM_SKIP]; + cyboot_flash_erase_row_t erase_row; + cyboot_flash_row_op_t program_row; + cyboot_flash_row_op_t write_row; +} psoc_c3_rom_flash_t; + +#define PSOC_C3_ROM ((const psoc_c3_rom_flash_t *)PSOC_C3_ROM_FUNC_ADDR) + +/* The ROM write path updates the refresh bookkeeping through ctx->refresh even + * when the refresh feature is unused, and it writes one entry per sector. A + * NULL pointer or an undersized array here corrupts whatever follows it. */ +static cyboot_flash_refresh_t flash_refresh[PSOC_C3_FLASH_SECTORS]; +static cyboot_flash_context_t flash_ctx; +/* Row staging buffer: too large for the stack under -Wstack-usage. */ +static uint32_t flash_row[PSOC_C3_FLASH_ROW_WORDS]; +static int flash_rom_checked; +static int flash_rom_ok; +static int peri_init_done; + +/* Group 0 is enabled out of reset; the rest are not, and a slave in a gated + * group bus-faults on first access. Release the slave reset before enabling, + * the order the vendor startup uses. Slaves that do not exist read back as + * zero, so writing all ones stays correct whatever a variant populates. */ +void psoc_c3_peri_init(void) +{ + int g; + + if (peri_init_done) + return; + peri_init_done = 1; + + for (g = 1; g < PERI_GR_COUNT; g++) { + PERI_GR_SL_CTL2(g) = 0; + PERI_GR_SL_CTL(g) = 0xFFFFFFFFUL; + } + DSB(); +} + +/* A pin is unusable until its bit is cleared here: while it is marked + * non-secure the port's HSIOM and GPIO registers discard secure writes and + * read back zero. */ +void psoc_c3_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; +} + +static int RAMFUNCTION rom_ptr_valid(void *fn) +{ + uint32_t a = (uint32_t)fn; + return ((a >= PSOC_C3_ROM_BASE) && + (a < (PSOC_C3_ROM_BASE + PSOC_C3_ROM_SIZE)) && + ((a & 1U) != 0U)); +} + +/* Validated on first use, not from hal_init(): the test application links this + * HAL without __WOLFBOOT and never calls hal_init(), so gating flash on + * hal_init() state made every application write fail silently -- which is the + * path wolfBoot_update_trigger() takes. */ +static int RAMFUNCTION flash_rom_ready(void) +{ + if (flash_rom_checked == 0) { + flash_rom_checked = 1; + flash_rom_ok = (rom_ptr_valid((void *)PSOC_C3_ROM->erase_row) && + rom_ptr_valid((void *)PSOC_C3_ROM->program_row) && + rom_ptr_valid((void *)PSOC_C3_ROM->write_row)); + } + return flash_rom_ok; +} + +void RAMFUNCTION hal_flash_unlock(void) +{ +} + +void RAMFUNCTION hal_flash_lock(void) +{ +} + +/* A len that is not a whole number of rows erases the row the final byte falls + * in, rounding up. wolfBoot only ever passes sector multiples. */ +int RAMFUNCTION hal_flash_erase(uint32_t address, int len) +{ + uint32_t status; + + if ((len <= 0) || (flash_rom_ready() == 0)) + return -1; + if ((address % PSOC_C3_FLASH_ROW_SIZE) != 0) + return -1; + + while (len > 0) { + flash_ctx.flags = CYBOOT_FLAGS_BLOCKING; + flash_ctx.refresh = flash_refresh; + status = PSOC_C3_ROM->erase_row(PSOC_C3_SBUS_ALIAS(address), + &flash_ctx); + if (status != CYBOOT_FLASH_SUCCESS) + return -1; + address += PSOC_C3_FLASH_ROW_SIZE; + len -= (int)PSOC_C3_FLASH_ROW_SIZE; + } + return 0; +} + +int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len) +{ + uint32_t row_start; + uint32_t offset; + uint32_t chunk; + uint32_t status; + + if ((data == NULL) || (len <= 0) || (flash_rom_ready() == 0)) + return -1; + + while (len > 0) { + row_start = address & ~(PSOC_C3_FLASH_ROW_SIZE - 1U); + offset = address - row_start; + chunk = PSOC_C3_FLASH_ROW_SIZE - offset; + if (chunk > (uint32_t)len) + chunk = (uint32_t)len; + + /* write_row erases before programming, so carry over the rest of the + * row. */ + memcpy(flash_row, (const void *)row_start, PSOC_C3_FLASH_ROW_SIZE); + memcpy((uint8_t *)flash_row + offset, data, chunk); + /* The ROM fills the column-33 trailer itself for a blocking write; + * clear it so no bytes of the previous row are carried into this one. */ + memset((uint8_t *)flash_row + PSOC_C3_FLASH_ROW_SIZE, 0, + PSOC_C3_FLASH_COL33_SZ); + + flash_ctx.flags = CYBOOT_FLAGS_BLOCKING; + flash_ctx.refresh = flash_refresh; + status = PSOC_C3_ROM->write_row(PSOC_C3_SBUS_ALIAS(row_start), + flash_row, &flash_ctx); + if (status != CYBOOT_FLASH_SUCCESS) + return -1; + + address += chunk; + data += chunk; + len -= (int)chunk; + } + return 0; +} + +/* Program a peripheral clock destination and start its divider. + * + * The divider's EN bit is status, not control: only DIV_CMD starts it. Touching + * an SCB before its divider runs bus-faults, so callers must not proceed on a + * failure here. Divider types are not uniform across groups, and a destination + * programmed with a type its group does not provide silently produces no clock. + */ +int psoc_c3_pclk_setup(uint32_t gr, uint32_t idx, uint32_t div, uint32_t type, + uint32_t pclk_hz, uint32_t target_hz) +{ + uint32_t timeout = PSOC_C3_PERI_TIMEOUT; + uint32_t int_div; + uint32_t frac_div; + + if (target_hz == 0U) + return -1; + + if (type == PERI_PCLK_DIV_TYPE_16_5) { + psoc_c3_div16_5(pclk_hz, target_hz, &int_div, &frac_div); + PERI_PCLK_DIV_16_5_CTL(gr, div) = + (int_div << PERI_PCLK_DIV_16_5_INT_Pos) | + (frac_div << PERI_PCLK_DIV_16_5_FRAC_Pos); + } + else { + /* Round to nearest; an integer divider cannot hit every rate. */ + int_div = (pclk_hz + (target_hz / 2U)) / target_hz; + if (int_div == 0U) + int_div = 1U; + if (int_div > 0x10000U) /* INT16_DIV holds divisor - 1 */ + int_div = 0x10000U; + PERI_PCLK_DIV_16_CTL(gr, div) = + ((int_div - 1U) << PERI_PCLK_DIV_INT16_Pos); + } + + PERI_PCLK_CLOCK_CTL(gr, idx) = (type << PERI_PCLK_CLOCK_CTL_TYPE_Pos) | div; + PERI_PCLK_DIV_CMD(gr) = PERI_PCLK_DIV_CMD_ENABLE | + PERI_PCLK_DIV_CMD_PA_NONE | + (type << PERI_PCLK_DIV_CMD_TYPE_Pos) | div; + + while (timeout > 0) { + if (type == PERI_PCLK_DIV_TYPE_16_5) { + if ((PERI_PCLK_DIV_16_5_CTL(gr, div) & PERI_PCLK_DIV_16_5_EN) != 0) + return 0; + } + else if ((PERI_PCLK_DIV_16_CTL(gr, div) & PERI_PCLK_DIV_EN) != 0) { + return 0; + } + timeout--; + } + return -1; +} + +#if defined(DEBUG_UART) || !defined(__WOLFBOOT) + +static void uart_pins_setup(void) +{ + psoc_c3_pin_setup(PSOC_C3_UART_PORT, PSOC_C3_UART_RX_PIN, + PSOC_C3_UART_HSIOM_SEL, GPIO_CFG_DM_HIGHZ); + psoc_c3_pin_setup(PSOC_C3_UART_PORT, PSOC_C3_UART_TX_PIN, + PSOC_C3_UART_HSIOM_SEL, GPIO_CFG_DM_STRONG); +} + +static int uart_clock_setup(void) +{ + return psoc_c3_pclk_setup(PSOC_C3_UART_PCLK_GR, PSOC_C3_UART_PCLK_IDX, + PSOC_C3_UART_PCLK_DIV, PERI_PCLK_DIV_TYPE_16_5, PSOC_C3_PCLK_HZ, + PSOC_C3_UART_BAUD * PSOC_C3_UART_OVS); +} + +void uart_init(void) +{ + psoc_c3_peri_init(); + uart_pins_setup(); + /* An SCB without a running divider bus-faults on first access, so a + * failed clock leaves the console silent rather than taking the fault. */ + if (uart_clock_setup() != 0) + return; + + SCB_CTRL(PSOC_C3_UART_SCB_BASE) = 0; + SCB_UART_CTRL(PSOC_C3_UART_SCB_BASE) = 0; /* standard UART */ + SCB_UART_TX_CTRL(PSOC_C3_UART_SCB_BASE) = 1; /* one stop bit */ + SCB_UART_RX_CTRL(PSOC_C3_UART_SCB_BASE) = 1; + SCB_TX_CTRL(PSOC_C3_UART_SCB_BASE) = SCB_DATA_WIDTH(8); + SCB_RX_CTRL(PSOC_C3_UART_SCB_BASE) = SCB_DATA_WIDTH(8); + SCB_TX_FIFO_CTRL(PSOC_C3_UART_SCB_BASE) = 0x3F; + SCB_RX_FIFO_CTRL(PSOC_C3_UART_SCB_BASE) = 0x3F; + SCB_CTRL(PSOC_C3_UART_SCB_BASE) = SCB_CTRL_ENABLED | SCB_CTRL_MODE_UART | + ((PSOC_C3_UART_OVS - 1UL) & SCB_CTRL_OVS_MASK); +} + +void uart_write(const char *buf, unsigned int sz) +{ + unsigned int i; + uint32_t timeout; + + for (i = 0; i < sz; i++) { + /* USED is the low field; bits 16 and up are the FIFO pointers. */ + timeout = PSOC_C3_PERI_TIMEOUT; + while (((SCB_TX_FIFO_STATUS(PSOC_C3_UART_SCB_BASE) & + SCB_TX_FIFO_USED_Msk) >= SCB_TX_FIFO_SAFE_DEPTH) && + (timeout > 0)) + timeout--; + if (timeout == 0) + return; /* console is stuck; drop the rest rather than hang */ + SCB_TX_FIFO_WR(PSOC_C3_UART_SCB_BASE) = (uint32_t)(uint8_t)buf[i]; + } +} + +#endif /* DEBUG_UART || !__WOLFBOOT */ + +#ifdef __WOLFBOOT + +void hal_init(void) +{ + psoc_c3_peri_init(); +#ifdef DEBUG_UART + uart_init(); + uart_write("wolfBoot HAL Init\n", sizeof("wolfBoot HAL Init\n") - 1); + if (flash_rom_ready() == 0) { + uart_write("BootROM flash table invalid\n", + sizeof("BootROM flash table invalid\n") - 1); + } +#endif +} + + +void hal_prepare_boot(void) +{ +#ifdef DEBUG_UART + uint32_t timeout = PSOC_C3_PERI_TIMEOUT; + + /* Drain the shift register before the application touches the SCB. A + * stuck console must not hold up the handoff. */ + while (((SCB_TX_FIFO_STATUS(PSOC_C3_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_c3.h b/hal/psoc_c3.h new file mode 100644 index 0000000000..3a511f588d --- /dev/null +++ b/hal/psoc_c3.h @@ -0,0 +1,259 @@ +/* psoc_c3.h + * + * Register definitions for Infineon PSOC Control C3 (CAT1B, 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_C3_H_ +#define _PSOC_C3_H_ + +#include +#include + +/* The memory clobber matters as much as the instruction: without it the + * compiler may reorder accesses across the barrier. */ +#define DMB() __asm__ volatile ("dmb" ::: "memory") +#define ISB() __asm__ volatile ("isb" ::: "memory") +#define DSB() __asm__ volatile ("dsb" ::: "memory") + +/* Every memory is visible through four aliases: Secure/Non-secure crossed with + * CBUS (code) and SBUS (system). Code executes from the CBUS view; the BootROM + * flash API only accepts SBUS addresses, hence PSOC_C3_SBUS_ALIAS below. */ +#define PSOC_C3_SBUS_OFFSET (0x20000000UL) +#define PSOC_C3_SBUS_ALIAS(a) (((uint32_t)(a)) | PSOC_C3_SBUS_OFFSET) + +#define PSOC_C3_FLASH_ROW_SIZE (512U) +/* Each row carries 16 bytes of "column 33" metadata alongside its data. */ +#define PSOC_C3_FLASH_COL33_SZ (16U) +#define PSOC_C3_FLASH_ROW_WORDS ((PSOC_C3_FLASH_ROW_SIZE + \ + PSOC_C3_FLASH_COL33_SZ) / sizeof(uint32_t)) + +/* Peripheral blocks. The non-secure view is listed; wolfBoot runs in the + * Secure state, so PSOC_C3_PERI_OFFSET shifts every access to the secure + * alias. Set PSOC_C3_PERI_SECURE=0 for a non-secure build. */ +#ifndef PSOC_C3_PERI_SECURE +#define PSOC_C3_PERI_SECURE 1 +#endif +#if PSOC_C3_PERI_SECURE +#define PSOC_C3_PERI_OFFSET (0x10000000UL) +#else +#define PSOC_C3_PERI_OFFSET (0UL) +#endif + +#define PERI_BASE (0x42000000UL + PSOC_C3_PERI_OFFSET) +#define PERI_PCLK_BASE (0x42040000UL + PSOC_C3_PERI_OFFSET) +#define HSIOM_BASE (0x42400000UL + PSOC_C3_PERI_OFFSET) +#define GPIO_BASE (0x42410000UL + PSOC_C3_PERI_OFFSET) + +/* PERI groups gate their slave peripherals. Out of reset most groups are + * disabled, and touching a peripheral in a disabled group bus-faults. */ +#define PERI_GR_COUNT 6 +#define PERI_GR_SL_CTL(g) \ + (*(volatile uint32_t *)(PERI_BASE + 0x4000UL + \ + ((uint32_t)(g) * 0x40UL) + 0x10UL)) +/* SL_CTL2 holds each slave in reset; it must be cleared before SL_CTL + * enables the slave, and the group's clock root must already be running or + * the slave never completes its clock handshake. */ +#define PERI_GR_SL_CTL2(g) \ + (*(volatile uint32_t *)(PERI_BASE + 0x4000UL + \ + ((uint32_t)(g) * 0x40UL) + 0x14UL)) + + +/* PERI_PCLK: 16 groups of 0x2000. Within a group the divider control blocks + * are indexed by divider number and CLOCK_CTL by peripheral number. */ +#define PERI_PCLK_GR(g) (PERI_PCLK_BASE + ((uint32_t)(g) * 0x2000UL)) +#define PERI_PCLK_DIV_CMD(g) (*(volatile uint32_t *)(PERI_PCLK_GR(g) + 0x000UL)) +#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_CTL(g,d) \ + (*(volatile uint32_t *)(PERI_PCLK_GR(g) + 0x1400UL + ((uint32_t)(d) * 4UL))) +#define PERI_PCLK_DIV_INT16_Pos (8UL) +#define PERI_PCLK_DIV_EN (1UL << 0) + +/* The EN bit in a divider's control register is status only. A divider is + * turned on through DIV_CMD, which also handles phase alignment. */ +#define PERI_PCLK_DIV_CMD_ENABLE (1UL << 31) +#define PERI_PCLK_DIV_CMD_PA_TYPE_Pos (24U) +#define PERI_PCLK_DIV_CMD_PA_DIV_Pos (16U) +#define PERI_PCLK_DIV_CMD_TYPE_Pos (8U) +/* PA_DIV_SEL 0xFF with PA_TYPE_SEL 3 means "no phase alignment". */ +#define PERI_PCLK_DIV_CMD_PA_NONE \ + ((3UL << PERI_PCLK_DIV_CMD_PA_TYPE_Pos) | \ + (0xFFUL << PERI_PCLK_DIV_CMD_PA_DIV_Pos)) +#define PERI_PCLK_DIV_TYPE_16 (1UL) +#define PERI_PCLK_DIV_TYPE_16_5 (2UL) +#define PERI_PCLK_DIV_16_5_EN (1UL << 0) +/* Bound on the divider-enable handshake, so a misrouted clock index reports + * rather than spinning forever. */ +#ifndef PSOC_C3_PERI_TIMEOUT +#define PSOC_C3_PERI_TIMEOUT (1000000UL) +#endif +#define PERI_PCLK_DIV_16_5_FRAC_Pos (3U) +#define PERI_PCLK_DIV_16_5_INT_Pos (8U) +/* CLOCK_CTL selects a divider: type 2 is the 16.5 (fractional) divider. */ +#define PERI_PCLK_CLOCK_CTL_TYPE_Pos (8U) + +/* HSIOM: one 0x10 block per port, PORT_SEL0 covers pins 0-3, SEL1 pins 4-7, + * one byte of mux select per pin. */ +/* Pins 0-3 live in PORT_SEL0 and pins 4-7 in PORT_SEL1, one byte each. */ +#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) + +/* Per-port security attribution for the pins, one 0x10 block per port with a + * bit per pin: 1 marks the pin non-secure. The BootROM leaves the pins it used + * marked non-secure, and while a pin is non-secure the port's HSIOM and GPIO + * registers ignore secure writes and read back as zero. Clear the bit for + * every pin before configuring it. */ +#define HSIOM_SECURE_PRT_NSMASK(p) \ + (*(volatile uint32_t *)(HSIOM_BASE + 0x1000UL + ((uint32_t)(p) * 0x10UL))) + +/* GPIO: one 0x80 block per port. CFG holds 4 bits of drive mode per pin. */ +#define GPIO_PRT_OUT(p) \ + (*(volatile uint32_t *)(GPIO_BASE + ((uint32_t)(p) * 0x80UL) + 0x00UL)) +#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_OD_LOW (0xCUL) /* open drain drives low, input on */ +#define GPIO_CFG_DM_MASK (0xFUL) + +/* SCB instances. SCB4 and SCB5 are absent on the smaller packages. */ +#define SCB0_BASE (0x42820000UL + PSOC_C3_PERI_OFFSET) +#define SCB1_BASE (0x42840000UL + PSOC_C3_PERI_OFFSET) +#define SCB2_BASE (0x42850000UL + PSOC_C3_PERI_OFFSET) +#define SCB3_BASE (0x42860000UL + PSOC_C3_PERI_OFFSET) +#define SCB4_BASE (0x42870000UL + PSOC_C3_PERI_OFFSET) +#define SCB5_BASE (0x42C00000UL + PSOC_C3_PERI_OFFSET) + +/* Select an SCB by instance number so the console and SPI peripherals are a + * build option rather than an edit. Folds to a constant for a literal n. */ +#define PSOC_C3_SCB_BASE(n) \ + ((n) == 0 ? SCB0_BASE : (n) == 1 ? SCB1_BASE : \ + (n) == 2 ? SCB2_BASE : (n) == 3 ? SCB3_BASE : \ + (n) == 4 ? SCB4_BASE : SCB5_BASE) + +#define SCB_CTRL(b) (*(volatile uint32_t *)((b) + 0x000UL)) +#define SCB_SPI_CTRL(b) (*(volatile uint32_t *)((b) + 0x020UL)) +#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_RX_FIFO_STATUS(b) (*(volatile uint32_t *)((b) + 0x308UL)) +#define SCB_RX_FIFO_RD(b) (*(volatile uint32_t *)((b) + 0x340UL)) + +#define SCB_CTRL_ENABLED (1UL << 31) +#define SCB_CTRL_MODE_I2C (0UL << 24) +#define SCB_CTRL_MODE_SPI (1UL << 24) +#define SCB_CTRL_MODE_UART (2UL << 24) +#define SCB_CTRL_OVS_MASK (0xFUL) +/* In I2C mode the low and high clock phases are counted separately. */ +#define SCB_CTRL_OVS_HIGH_Pos (4UL) +/* Data width fields are encoded as (bits - 1). */ +#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_RX_FIFO_USED_Msk (0x1FFUL) + +/* I2C master. INTR_M flags are write-one-to-clear. */ +#define SCB_I2C_CTRL(b) (*(volatile uint32_t *)((b) + 0x60UL)) +#define SCB_I2C_STATUS(b) (*(volatile uint32_t *)((b) + 0x64UL)) +#define SCB_I2C_M_CMD(b) (*(volatile uint32_t *)((b) + 0x68UL)) +#define SCB_INTR_M(b) (*(volatile uint32_t *)((b) + 0xF00UL)) +#define SCB_INTR_M_MASK(b) (*(volatile uint32_t *)((b) + 0xF08UL)) +#define SCB_I2C_CFG(b) (*(volatile uint32_t *)((b) + 0x70UL)) +#define SCB_I2C_CTRL_MASTER_MODE (1UL << 31) +#define SCB_I2C_CTRL_HIGH_PHASE_Pos (0UL) +#define SCB_I2C_CTRL_LOW_PHASE_Pos (4UL) +/* Filter trims the vendor driver programs. Bit 1 of SDA_IN_FILT_TRIM is the + * SCB clock enable (Cypress ID 282226), so leaving this register at reset + * leaves the block unclocked and no transfer ever starts. */ +#define SCB_I2C_CFG_DEFAULT (0x002A1013UL) +/* I2C is fixed at 8 data bits, MSB first, and the transmitter must be open + * drain on both lines. */ +/* Bit order. SPI NOR and the TCG PTP SPI TPM are both MSB first, and I2C is + * MSB first by definition, so every mode this HAL supports sets it. */ +#define SCB_CTRL_MSB_FIRST (1UL << 8) +#define SCB_I2C_RX_CTRL_VAL (SCB_DATA_WIDTH(8) | (1UL << 8)) +#define SCB_I2C_TX_CTRL_VAL (SCB_DATA_WIDTH(8) | (1UL << 8) | \ + (1UL << 16) | (1UL << 17)) +#define SCB_I2C_STATUS_BUS_BUSY (1UL << 0) +#define SCB_I2C_M_CMD_M_START (1UL << 0) +#define SCB_I2C_M_CMD_M_ACK (1UL << 2) +#define SCB_I2C_M_CMD_M_NACK (1UL << 3) +#define SCB_I2C_M_CMD_M_STOP (1UL << 4) +#define SCB_INTR_M_I2C_ARB_LOST (1UL << 0) +#define SCB_INTR_M_I2C_NACK (1UL << 1) +#define SCB_INTR_M_I2C_ACK (1UL << 2) +#define SCB_INTR_M_I2C_STOP (1UL << 4) +#define SCB_INTR_M_I2C_BUS_ERROR (1UL << 8) +#define SCB_INTR_M_ALL (0x0000011FUL) +/* Stay well inside the smallest FIFO any SCB instance provides. */ +#define SCB_TX_FIFO_SAFE_DEPTH (8UL) + +#define SCB_SPI_CTRL_MASTER (1UL << 31) +#define SCB_SPI_CTRL_CPHA (1UL << 2) +#define SCB_SPI_CTRL_CPOL (1UL << 3) + +/* Compute the 16.5 fractional divider fields for a target frequency. The + * divider produces pclk / (int_div + 1 + frac_div/32), so the whole divisor is + * computed in 32nds to keep the fractional part. Kept in the header so it can + * be exercised by a host unit test. */ +static inline void psoc_c3_div16_5(uint32_t pclk_hz, uint32_t target_hz, + uint32_t *int_div, uint32_t *frac_div) +{ + uint32_t scaled; + + if ((target_hz == 0U) || (int_div == NULL) || (frac_div == NULL)) + return; + + scaled = (uint32_t)(((uint64_t)pclk_hz * 32U) / target_hz); + if (scaled < 32U) + scaled = 32U; /* divisor of 1: cannot go faster */ + if (scaled > (0x10000UL * 32U)) + scaled = 0x10000UL * 32U; /* INT is 16 bits */ + *int_div = (scaled / 32U) - 1U; + *frac_div = scaled % 32U; +} + +/* Ungate the peripheral groups. Idempotent, and required before any SCB is + * touched: a slave in a gated group bus-faults on first access. */ +/* Program a peripheral clock destination and start its divider. Returns 0 on + * success; a caller must not touch the peripheral if this fails, because an + * SCB without a running divider bus-faults on first access. */ +int psoc_c3_pclk_setup(uint32_t gr, uint32_t idx, uint32_t div, uint32_t type, + uint32_t pclk_hz, uint32_t target_hz); + +void psoc_c3_peri_init(void); + +/* Claim a pin as secure, route it to an HSIOM function and set its drive + * mode. Pins must be claimed before HSIOM or GPIO will accept a write. */ +void psoc_c3_pin_setup(int port, int pin, uint32_t sel, uint32_t drive); + +#endif /* _PSOC_C3_H_ */ diff --git a/hal/psoc_c3.ld b/hal/psoc_c3.ld new file mode 100644 index 0000000000..222eb4da44 --- /dev/null +++ b/hal/psoc_c3.ld @@ -0,0 +1,58 @@ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x12000000, LENGTH = @BOOTLOADER_PARTITION_SIZE@ + RAM (rwx) : ORIGIN = 0x34000000, LENGTH = 0x00020000 /* 128 KB SRAM */ +} + +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/spi/spi_drv_psoc_c3.c b/hal/spi/spi_drv_psoc_c3.c new file mode 100644 index 0000000000..d8105c478e --- /dev/null +++ b/hal/spi/spi_drv_psoc_c3.c @@ -0,0 +1,179 @@ +/* spi_drv_psoc_c3.c + * + * Driver for the SPI back-end on Infineon PSOC Control C3. + * + * Pinout: see spi_drv_psoc_c3.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ +#include +#include "spi_drv.h" + +#ifdef TARGET_psoc_c3 + +#if defined(SPI_FLASH) || defined(WOLFBOOT_TPM) + +#include "hal/spi/spi_drv_psoc_c3.h" + +#ifndef PSOC_C3_PCLK_HZ +#define PSOC_C3_PCLK_HZ 48000000UL +#endif +/* The SCB oversamples the SPI clock by this factor in master mode. */ +#define PSOC_C3_SPI_OVS 4UL + +/* Bound every FIFO wait: an absent or misrouted TPM would otherwise wedge the + * bootloader in a spin loop instead of letting the TPM layer report it. */ +#ifndef PSOC_C3_SPI_TIMEOUT +#define PSOC_C3_SPI_TIMEOUT (1000000UL) +#endif + +static void spi_pins_setup(void) +{ + psoc_c3_pin_setup(PSOC_C3_SPI_PORT, PSOC_C3_SPI_SCK_PIN, + PSOC_C3_SPI_HSIOM_SEL, GPIO_CFG_DM_STRONG); + psoc_c3_pin_setup(PSOC_C3_SPI_PORT, PSOC_C3_SPI_MOSI_PIN, + PSOC_C3_SPI_HSIOM_SEL, GPIO_CFG_DM_STRONG); + psoc_c3_pin_setup(PSOC_C3_SPI_PORT, PSOC_C3_SPI_MISO_PIN, + PSOC_C3_SPI_HSIOM_SEL, GPIO_CFG_DM_HIGHZ); + /* Chip select stays a GPIO (mux 0) so it can be held across a transfer. */ + psoc_c3_pin_setup(PSOC_C3_SPI_PORT, PSOC_C3_SPI_CS_PIN, + 0, GPIO_CFG_DM_STRONG); +} + +static int spi_clock_setup(void) +{ + return psoc_c3_pclk_setup(PSOC_C3_SPI_PCLK_GR, PSOC_C3_SPI_PCLK_IDX, + PSOC_C3_SPI_PCLK_DIV, PERI_PCLK_DIV_TYPE_16_5, PSOC_C3_PCLK_HZ, + PSOC_C3_SPI_HZ * PSOC_C3_SPI_OVS); +} + +/* The shared spi_drv.h prototype names this "base"; on this part chip select + * is a GPIO, so it carries a port index rather than a peripheral address. */ +void RAMFUNCTION spi_cs_off(uint32_t base, int pin) +{ + GPIO_PRT_OUT(base) |= (1UL << pin); +} + +void RAMFUNCTION spi_cs_on(uint32_t base, int pin) +{ + GPIO_PRT_OUT(base) &= ~(1UL << pin); +} + +/* spi_write() and spi_read() cannot report a stalled FIFO through the shared + * prototypes, so they record it here for spi_xfer() to return. */ +static int spi_fault; +/* File scope so the transfer path can refuse to run when spi_init() bailed + * out; an SCB whose divider never started bus-faults on first access. */ +static int initialized = 0; + +void RAMFUNCTION spi_write(const char byte) +{ + uint32_t timeout = PSOC_C3_SPI_TIMEOUT; + + while (((SCB_TX_FIFO_STATUS(PSOC_C3_SPI_SCB_BASE) & + SCB_TX_FIFO_USED_Msk) >= SCB_TX_FIFO_SAFE_DEPTH) && + (timeout > 0)) + timeout--; + if (timeout == 0) { + /* FIFO never drained; dropping beats a silent overflow. */ + spi_fault = 1; + return; + } + SCB_TX_FIFO_WR(PSOC_C3_SPI_SCB_BASE) = (uint32_t)(uint8_t)byte; +} + +uint8_t RAMFUNCTION spi_read(void) +{ + uint32_t timeout = PSOC_C3_SPI_TIMEOUT; + + /* Master mode clocks a byte in for every byte out. */ + while (((SCB_RX_FIFO_STATUS(PSOC_C3_SPI_SCB_BASE) & + SCB_RX_FIFO_USED_Msk) == 0) && (timeout > 0)) + timeout--; + if (timeout == 0) { + spi_fault = 1; + return 0xFF; + } + return (uint8_t)SCB_RX_FIFO_RD(PSOC_C3_SPI_SCB_BASE); +} + +void spi_init(int polarity, int phase) +{ + if (!initialized) { + psoc_c3_peri_init(); + spi_pins_setup(); + /* An SCB without a running divider bus-faults on first access. Leave + * the flag clear so a later call can retry rather than run against an + * unconfigured block. */ + if (spi_clock_setup() != 0) + return; + initialized++; + spi_cs_off(PSOC_C3_SPI_PORT, PSOC_C3_SPI_CS_PIN); + + SCB_CTRL(PSOC_C3_SPI_SCB_BASE) = 0; + SCB_SPI_CTRL(PSOC_C3_SPI_SCB_BASE) = SCB_SPI_CTRL_MASTER | + (polarity ? SCB_SPI_CTRL_CPOL : 0) | + (phase ? SCB_SPI_CTRL_CPHA : 0); + SCB_TX_CTRL(PSOC_C3_SPI_SCB_BASE) = + SCB_DATA_WIDTH(8) | SCB_CTRL_MSB_FIRST; + SCB_RX_CTRL(PSOC_C3_SPI_SCB_BASE) = + SCB_DATA_WIDTH(8) | SCB_CTRL_MSB_FIRST; + SCB_TX_FIFO_CTRL(PSOC_C3_SPI_SCB_BASE) = 0x3F; + SCB_RX_FIFO_CTRL(PSOC_C3_SPI_SCB_BASE) = 0x3F; + SCB_CTRL(PSOC_C3_SPI_SCB_BASE) = SCB_CTRL_ENABLED | + SCB_CTRL_MODE_SPI | + ((PSOC_C3_SPI_OVS - 1UL) & SCB_CTRL_OVS_MASK); + } +} + +void spi_release(void) +{ + spi_cs_off(PSOC_C3_SPI_PORT, PSOC_C3_SPI_CS_PIN); +} + +#ifdef WOLFBOOT_TPM +int spi_xfer(int cs, const uint8_t* tx, uint8_t* rx, uint32_t sz, int flags) +{ + uint32_t i; + uint8_t byte; + + /* Only one chip select is wired, and spi_init() configured that pin, so + * use it rather than the caller's number. */ + (void)cs; + if (!initialized) + return -1; + spi_fault = 0; + spi_cs_on(PSOC_C3_SPI_PORT, PSOC_C3_SPI_CS_PIN); + for (i = 0; i < sz; i++) { + spi_write((tx != NULL) ? (char)tx[i] : 0); + byte = spi_read(); + if (rx != NULL) + rx[i] = byte; + } + if (!(flags & SPI_XFER_FLAG_CONTINUE)) { + spi_cs_off(PSOC_C3_SPI_PORT, PSOC_C3_SPI_CS_PIN); + } + /* A stalled bus must not read as a successful transfer, or the caller + * treats the 0xFF fill as real data. */ + return (spi_fault != 0) ? -1 : 0; +} +#endif /* WOLFBOOT_TPM */ + +#endif /* SPI_FLASH || WOLFBOOT_TPM */ +#endif /* TARGET_psoc_c3 */ diff --git a/hal/spi/spi_drv_psoc_c3.h b/hal/spi/spi_drv_psoc_c3.h new file mode 100644 index 0000000000..08d3372683 --- /dev/null +++ b/hal/spi/spi_drv_psoc_c3.h @@ -0,0 +1,90 @@ +/* spi_drv_psoc_c3.h + * + * Pinout and configuration for the SPI back-end on Infineon PSOC Control C3. + * + * 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 SPI_DRV_PSOC_C3_H_INCLUDED +#define SPI_DRV_PSOC_C3_H_INCLUDED + +#include +#include "hal/psoc_c3.h" + +/* SCB instance carrying SPI. The C3 evaluation kit routes its mikroBUS + * headers (J12 and J16) to SCB2 on port 7. SCB4 and SCB5 are absent on the + * smaller packages; use SCB0 to SCB3 there. */ +#ifndef PSOC_C3_SPI_SCB +#define PSOC_C3_SPI_SCB 2 +#endif +#define PSOC_C3_SPI_SCB_BASE PSOC_C3_SCB_BASE(PSOC_C3_SPI_SCB) + +/* Pin group, and which pin of it carries which signal. The order is NOT the + * same for every SCB: SCB3 and SCB4 lay their signals out as MOSI, MISO, CLK, + * SEL0 while SCB2 uses CLK, MOSI, MISO, SEL0. Getting these the wrong way + * round drives MISO against the peripheral and reads back nothing, so they + * are spelled out per pin rather than assumed. */ +#ifndef PSOC_C3_SPI_PORT +#define PSOC_C3_SPI_PORT 7 +#endif +#ifndef PSOC_C3_SPI_SCK_PIN +#define PSOC_C3_SPI_SCK_PIN 0 +#endif +#ifndef PSOC_C3_SPI_MOSI_PIN +#define PSOC_C3_SPI_MOSI_PIN 1 +#endif +#ifndef PSOC_C3_SPI_MISO_PIN +#define PSOC_C3_SPI_MISO_PIN 2 +#endif +#ifndef PSOC_C3_SPI_CS_PIN +#define PSOC_C3_SPI_CS_PIN 3 +#endif +/* The HSIOM selector for the SPI function differs per SCB: 13 on SCB0, 17 on + * SCB2 and SCB5, 18 on SCB1, SCB3 and SCB4. */ +#ifndef PSOC_C3_SPI_HSIOM_SEL +#define PSOC_C3_SPI_HSIOM_SEL 17 +#endif + +/* Peripheral clock routing for the SPI SCB. + * + * Measured on silicon: within group 4 each SCB's clock sits at index 2*n, so + * SCB0 is 0, SCB3 (the console) is 6 and SCB4 is 8. That is not what the + * PSC3M5 headers say, which place SCB0 to SCB4 at indices 0 to 4. SCB5 lives + * in group 6. Check the index on any new variant rather than assuming it. */ +#ifndef PSOC_C3_SPI_PCLK_GR +#define PSOC_C3_SPI_PCLK_GR 4 +#endif +#ifndef PSOC_C3_SPI_PCLK_IDX +#define PSOC_C3_SPI_PCLK_IDX (2 * PSOC_C3_SPI_SCB) +#endif +#ifndef PSOC_C3_SPI_PCLK_DIV +#define PSOC_C3_SPI_PCLK_DIV 2 +#endif + +/* Deliberately slow for bring-up: a wrong clock looks exactly like a wiring + * fault. Raise it once the device answers. */ +#ifndef PSOC_C3_SPI_HZ +#define PSOC_C3_SPI_HZ 1000000UL +#endif + +/* Chip select is a plain GPIO, not the SCB's, because the TIS protocol needs + * it held asserted across the wait-state poll. */ +#define SPI_CS_TPM PSOC_C3_SPI_CS_PIN +#define SPI_CS_TPM_PIO_BASE PSOC_C3_SPI_PORT + +#endif /* SPI_DRV_PSOC_C3_H_INCLUDED */ diff --git a/include/i2c_drv.h b/include/i2c_drv.h new file mode 100644 index 0000000000..675270a7e8 --- /dev/null +++ b/include/i2c_drv.h @@ -0,0 +1,60 @@ +/* i2c_drv.h + * + * Generic I2C master interface for wolfBoot back-end drivers. + * + * 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 I2C_DRV_H_INCLUDED +#define I2C_DRV_H_INCLUDED + +#include + +/* Return codes. A device that is awake but busy answers a transfer with + * I2C_ERR_NACK, which callers use to decide whether to retry. */ +#define I2C_OK (0) +#define I2C_ERR_NACK (-1) +#define I2C_ERR_TIMEOUT (-2) +#define I2C_ERR_BUS (-3) +#define I2C_ERR_ARG (-4) + +#if defined(WOLFBOOT_TPM_I2C) + +#if defined(TARGET_psoc_c3) +#include "hal/i2c/i2c_drv_psoc_c3.h" +#endif + +/* Bring the controller up. Safe to call more than once. */ +void i2c_init(void); + +/* Release the bus and leave the pins in a safe state. */ +void i2c_release(void); + +/* Address a 7-bit slave and write len bytes. A zero stop leaves the bus held + * so the next call issues a repeated start; a non-zero stop releases it. + * Which of the two a device needs between selecting a register and reading it + * is device specific. */ +int i2c_write(uint8_t addr, const uint8_t *buf, uint32_t len, int stop); + +/* Address a 7-bit slave and read len bytes. The final byte is NACKed, as the + * protocol requires, before the optional stop. */ +int i2c_read(uint8_t addr, uint8_t *buf, uint32_t len, int stop); + +#endif /* WOLFBOOT_TPM_I2C */ +#endif /* I2C_DRV_H_INCLUDED */ diff --git a/include/spi_drv.h b/include/spi_drv.h index d32f750869..a9baec1b61 100644 --- a/include/spi_drv.h +++ b/include/spi_drv.h @@ -48,6 +48,10 @@ #include "hal/spi/spi_drv_stm32.h" #endif +#if defined(TARGET_psoc_c3) +#include "hal/spi/spi_drv_psoc_c3.h" +#endif + #if defined(TARGET_zynq) #include "hal/spi/spi_drv_zynq.h" #endif @@ -76,7 +80,7 @@ void spi_init(int polarity, int phase); void spi_release(void); -#ifdef SPI_FLASH +#if defined(SPI_FLASH) || defined(WOLFBOOT_TPM) void spi_cs_on(uint32_t base, int pin); void spi_cs_off(uint32_t base, int pin); void spi_write(const char byte); diff --git a/options.mk b/options.mk index 469bc5214b..d0ae41c0df 100644 --- a/options.mk +++ b/options.mk @@ -1392,9 +1392,13 @@ ifeq ($(WOLFTPM),1) CFLAGS+=-D"WOLFBOOT_TPM" CFLAGS+=-D"WOLFTPM_SMALL_STACK" ifneq ($(SPI_FLASH),1) - # don't use spi if we're using simulator + # don't use spi if we're using simulator, unless an explicit transport was + # asked for: WOLFBOOT_TPM_I2C=1 on the simulator builds the I2C transport + # against the stub back-end, which is how that code path is build-tested. ifeq ($(TARGET),sim) - SIM_TPM=1 + ifneq ($(WOLFBOOT_TPM_I2C),1) + SIM_TPM=1 + endif endif ifeq ($(SIM_TPM),1) CFLAGS+=-DWOLFTPM_SWTPM -DTPM_TIMEOUT_TRIES=0 -DHAVE_NETDB_H -DHAVE_UNISTD_H @@ -1404,6 +1408,12 @@ ifeq ($(WOLFTPM),1) ifeq ($(ARCH),x86_64) CFLAGS+=-DWOLFTPM_MMIO -DWOLFTPM_EXAMPLE_HAL -DWOLFTPM_INCLUDE_IO_FILE OBJS+=$(WOLFBOOT_LIB_WOLFTPM)/hal/tpm_io_mmio.o + # A TPM reached over I2C needs the I2C back-end instead of SPI. + # WOLFTPM_I2C is what selects the TCG PTP I2C register map inside + # wolfTPM; without it the transport would use the SPI offsets. + else ifeq ($(WOLFBOOT_TPM_I2C),1) + CFLAGS+=-DWOLFBOOT_TPM_I2C -DWOLFTPM_I2C -DWOLFTPM_ADV_IO + WOLFCRYPT_OBJS+=hal/i2c/i2c_drv_$(I2C_TARGET).o # By default, on other architectures, provide SPI driver else WOLFCRYPT_OBJS+=hal/spi/spi_drv_$(SPI_TARGET).o diff --git a/src/tpm.c b/src/tpm.c index 9a24b97b8d..a381e4b1d1 100644 --- a/src/tpm.c +++ b/src/tpm.c @@ -112,10 +112,126 @@ void wolfBoot_print_bin(const uint8_t* buffer, uint32_t length) } #endif /* WOLFTPM_DEBUG_IO || WOLFBOOT_DEBUG_TPM */ -#if !defined(ARCH_SIM) && !defined(WOLFTPM_MMIO) +#if defined(WOLFBOOT_TPM_I2C) + +/* TPM 2.0 over I2C. The TIS register address is carried as the low byte of a + * transfer rather than in a SPI header, so this needs the advanced callback + * form, which reports the register and direction separately. */ +#include "i2c_drv.h" + +#ifndef TPM2_I2C_ADDR +#define TPM2_I2C_ADDR 0x2E /* 7-bit TCG TIS address */ +#endif +#ifndef TPM_I2C_TRIES +#define TPM_I2C_TRIES 10 +#endif +/* The part answers with a NAK for roughly 80us after it is addressed while it + * wakes, and the specification asks for a guard time between transfers. This + * is a bounded spin rather than a calibrated delay: it only has to be long + * enough, and the retry loop covers the rest. */ +#ifndef TPM_I2C_GUARD_LOOPS +#define TPM_I2C_GUARD_LOOPS 20000 +#endif + +static void tpm_i2c_guard(void) +{ + volatile uint32_t i; + + for (i = 0; i < (uint32_t)TPM_I2C_GUARD_LOOPS; i++) + ; +} + +static int tpm_i2c_read(uint32_t reg, uint8_t* data, int len) +{ + uint8_t regbuf = (uint8_t)(reg & 0xFF); + int tries = TPM_I2C_TRIES; + int ret; + + /* Address the register and release the bus, rather than holding it for a + * repeated start: this part answers NAK while it wakes and needs the guard + * time between the two transfers, which a repeated start cannot provide. + * A read always needs that guard, whether this transfer woke the part or + * was the real thing. */ + do { + ret = i2c_write(TPM2_I2C_ADDR, ®buf, 1, 1); + tpm_i2c_guard(); + } while ((ret != I2C_OK) && (--tries > 0)); + + if (ret != I2C_OK) + return TPM_RC_FAILURE; + + tries = TPM_I2C_TRIES; + do { + ret = i2c_read(TPM2_I2C_ADDR, data, (uint32_t)len, 1); + if (ret != I2C_OK) + tpm_i2c_guard(); + } while ((ret != I2C_OK) && (--tries > 0)); + + return (ret == I2C_OK) ? TPM_RC_SUCCESS : TPM_RC_FAILURE; +} + +static int tpm_i2c_write(uint32_t reg, const uint8_t* data, int len) +{ + uint8_t buf[MAX_SPI_FRAMESIZE + 1]; + int tries = TPM_I2C_TRIES; + int ret; + + if ((len < 0) || (len > (int)MAX_SPI_FRAMESIZE)) + return BAD_FUNC_ARG; + + /* One transfer: the register byte followed by the payload. */ + buf[0] = (uint8_t)(reg & 0xFF); + memcpy(&buf[1], data, (size_t)len); + + do { + ret = i2c_write(TPM2_I2C_ADDR, buf, (uint32_t)len + 1, 1); + if (ret != I2C_OK) + tpm_i2c_guard(); + } while ((ret != I2C_OK) && (--tries > 0)); + + /* The command payload can carry an authValue; do not leave it on the + * stack. */ + TPM2_ForceZero(buf, sizeof(buf)); + return (ret == I2C_OK) ? TPM_RC_SUCCESS : TPM_RC_FAILURE; +} + +/* Types match TPM2HalIoCb exactly: INT32 and int are distinct for pointer + * compatibility even where both are 32-bit. */ +static int TPM2_IoCb(TPM2_CTX* ctx, INT32 isRead, UINT32 addr, BYTE* buf, + UINT16 size, void* userCtx) +{ + int ret; + + if ((buf == NULL) || (size == 0)) + return BAD_FUNC_ARG; + +#ifdef WOLFTPM_DEBUG_IO + wolfBoot_printf("TPM2_IoCb (I2C): Read %d, Addr %x, Size %d\n", + isRead ? 1 : 0, addr, size); +#endif + + if (isRead) + ret = tpm_i2c_read(addr, buf, (int)size); + else + ret = tpm_i2c_write(addr, buf, (int)size); + +#ifdef WOLFTPM_DEBUG_IO + if (isRead && ret == TPM_RC_SUCCESS) + wolfBoot_print_bin(buf, size); +#endif + + (void)ctx; + (void)userCtx; + return ret; +} + +#elif !defined(ARCH_SIM) && !defined(WOLFTPM_MMIO) #ifdef WOLFTPM_ADV_IO -static int TPM2_IoCb(TPM2_CTX* ctx, int isRead, uint32_t addr, uint8_t* buf, - word16 size, void* userCtx) +/* Types match TPM2HalIoCb exactly. int and INT32 are distinct types for + * pointer compatibility even where both are 32-bit, so spelling these as + * plain int makes the callback unassignable to wolfTPM2_Init(). */ +static int TPM2_IoCb(TPM2_CTX* ctx, INT32 isRead, UINT32 addr, BYTE* buf, + UINT16 size, void* userCtx) #else /** @@ -234,7 +350,7 @@ static int TPM2_IoCb(TPM2_CTX* ctx, const uint8_t* txBuf, uint8_t* rxBuf, return ret; } -#endif /* !ARCH_SIM && !WOLFTPM_MMIO */ +#endif /* WOLFBOOT_TPM_I2C */ #ifdef WOLFBOOT_MEASURED_BOOT @@ -1559,7 +1675,9 @@ int wolfBoot_tpm2_init(void) uint8_t digest[WOLFBOOT_SHA_DIGEST_SIZE]; #endif -#if !defined(ARCH_SIM) && !defined(WOLFTPM_MMIO) +#if defined(WOLFBOOT_TPM_I2C) + i2c_init(); +#elif !defined(ARCH_SIM) && !defined(WOLFTPM_MMIO) spi_init(0,0); #endif @@ -1570,7 +1688,9 @@ int wolfBoot_tpm2_init(void) /* Init the TPM2 device */ /* simulator should use the network connection, not spi */ -#if defined(ARCH_SIM) || defined(WOLFTPM_MMIO) +#if defined(WOLFBOOT_TPM_I2C) + rc = wolfTPM2_Init(&wolftpm_dev, TPM2_IoCb, NULL); +#elif defined(ARCH_SIM) || defined(WOLFTPM_MMIO) rc = wolfTPM2_Init(&wolftpm_dev, NULL, NULL); #else rc = wolfTPM2_Init(&wolftpm_dev, TPM2_IoCb, NULL); diff --git a/test-app/ARM-psoc_c3.ld b/test-app/ARM-psoc_c3.ld new file mode 100644 index 0000000000..d5a2ff9a86 --- /dev/null +++ b/test-app/ARM-psoc_c3.ld @@ -0,0 +1,53 @@ +MEMORY +{ + FLASH (rx) : ORIGIN = @WOLFBOOT_TEST_APP_ADDRESS@, LENGTH = @WOLFBOOT_TEST_APP_SIZE@ + RAM (rwx) : ORIGIN = 0x34000000, LENGTH = 0x00020000 /* 128 KB SRAM */ +} + +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_stack = ORIGIN(RAM) + LENGTH(RAM)); diff --git a/test-app/Makefile b/test-app/Makefile index 79f29e9f19..e3fcb8e4c9 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -745,6 +745,14 @@ ifeq ($(TARGET),stm32c5) CFLAGS+=-I.. endif +ifeq ($(TARGET),psoc_c3) + LSCRIPT_TEMPLATE=ARM-psoc_c3.ld + 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 diff --git a/test-app/app_psoc_c3.c b/test-app/app_psoc_c3.c new file mode 100644 index 0000000000..86b1aab5ba --- /dev/null +++ b/test-app/app_psoc_c3.c @@ -0,0 +1,117 @@ +/* app_psoc_c3.c + * + * Test bare-metal application for Infineon PSOC Control C3. + * + * 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 "hal/psoc_c3.h" +#include "wolfboot/wolfboot.h" +#include "target.h" + +/* Application Interrupt and Reset Control. Several test applications carry + * their own copy of these; they are duplicated rather than shared because the + * apps that already define them do not all include a common header. */ +#define AIRCR (*(volatile uint32_t *)(0xE000ED0C)) +#define AIRCR_VKEY (0x05FA << 16) +#define AIRCR_SYSRESETREQ (1 << 2) + +extern void uart_init(void); +extern void uart_write(const char *buf, unsigned int len); + +static void uart_print(const char *s) +{ + unsigned int n = 0; + while (s[n] != 0) + n++; + uart_write(s, n); +} + +static void print_dec(uint32_t v) +{ + char tmp[12]; + char num[12]; + int t = 0; + int n = 0; + + if (v == 0) { + num[n++] = '0'; + } + else { + while (v > 0) { + tmp[t++] = (char)('0' + (v % 10)); + v /= 10; + } + while (t > 0) + num[n++] = tmp[--t]; + } + uart_write(num, (unsigned int)n); +} + +/* Request a system reset, preserving the configured priority grouping. */ +static void system_reset(void) +{ + uint32_t prigroup = AIRCR & 0x0700U; + + AIRCR = AIRCR_VKEY | prigroup | AIRCR_SYSRESETREQ; + while (1) + ; +} + +static void busy_delay(volatile uint32_t ticks) +{ + while (ticks > 0) + ticks--; +} + +void main(void) +{ + uint32_t version; + int i; + + uart_init(); + uart_print("TEST APP\r\n"); + + version = wolfBoot_current_firmware_version(); + uart_print("App version: "); + print_dec(version); + uart_print("\r\n"); + + /* 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) { + wolfBoot_success(); + uart_print("update OK -- success confirmed\r\n"); + while (1) + busy_delay(1000000); + } + + /* Pause before asking for the update. Without a window here the reset + * that follows comes round fast enough to interrupt a debugger trying to + * program the update partition. */ + for (i = 0; i < 5; i++) { + busy_delay(4000000); + uart_print("."); + } + uart_print("\r\ntriggering update -> reset\r\n"); + wolfBoot_update_trigger(); + system_reset(); +} diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index e3ba1b043f..6f6e64bc1e 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -86,6 +86,7 @@ TESTS+=unit-pkcs11-nsc-zeroize TESTS+=unit-pkcs11-pin-zeroize TESTS+=unit-ubootenv TESTS+=unit-diagnostics +TESTS+=unit-psoc-c3-divider TESTS+=unit-diagnostics-256 TESTS+=unit-fit-gzip unit-fit-nogzip TESTS+=unit-fit-fpga @@ -545,6 +546,9 @@ unit-zynq-dma-range: unit-zynq-dma-range.c unit-ns16550: unit-ns16550.c ../../hal/uart/ns16550.c gcc -o $@ unit-ns16550.c $(CFLAGS) $(LDFLAGS) +unit-psoc-c3-divider: unit-psoc-c3-divider.c ../../hal/psoc_c3.h + gcc -o $@ unit-psoc-c3-divider.c $(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-c3-divider.c b/tools/unit-tests/unit-psoc-c3-divider.c new file mode 100644 index 0000000000..3312cbf953 --- /dev/null +++ b/tools/unit-tests/unit-psoc-c3-divider.c @@ -0,0 +1,125 @@ +/* unit-psoc-c3-divider.c + * + * Exercises the PSOC Control C3 16.5 fractional peripheral divider maths. + * The HAL cannot be run on the host, but this computation is pure and decides + * the console baud rate and the SPI clock, so it is worth covering here. + */ + +#include +#include +#include + +#include "../../hal/psoc_c3.h" + +static int failures; + +/* Reconstruct the frequency the divider fields actually produce. */ +static uint32_t produced(uint32_t pclk, uint32_t i, uint32_t f) +{ + return (uint32_t)(((uint64_t)pclk * 32U) / (((uint64_t)i + 1U) * 32U + f)); +} + +static void check(uint32_t pclk, uint32_t target, uint32_t exp_i, + uint32_t exp_f, const char *what) +{ + uint32_t i = 0xFFFFFFFFUL, f = 0xFFFFFFFFUL; + + psoc_c3_div16_5(pclk, target, &i, &f); + if ((i != exp_i) || (f != exp_f)) { + printf("FAIL %s: pclk=%u target=%u -> int=%u frac=%u, expected %u/%u\n", + what, pclk, target, i, f, exp_i, exp_f); + failures++; + return; + } + printf("ok %s: int=%u frac=%u -> %u Hz\n", what, i, f, + produced(pclk, i, f)); +} + +/* Mirrors the integer-divider branch of psoc_c3_pclk_setup(). */ +static uint32_t int_divider(uint32_t pclk_hz, uint32_t target_hz) +{ + uint32_t d = (pclk_hz + (target_hz / 2U)) / target_hz; + + if (d == 0U) + d = 1U; + return d; +} + +int main(void) +{ + uint32_t i, f, hz; + + /* Console: 115200 baud at oversample 8 from the power-on IHO rate. */ + check(48000000UL, 115200UL * 8UL, 51, 2, "115200x8 from 48MHz"); + + /* SPI bring-up rate: 1 MHz at oversample 4 divides exactly. */ + check(48000000UL, 1000000UL * 4UL, 11, 0, "1MHz x4 from 48MHz"); + + /* A target at or above the source clamps to a divisor of one. */ + check(48000000UL, 48000000UL, 0, 0, "target == pclk"); + check(48000000UL, 96000000UL, 0, 0, "target above pclk"); + + /* A target low enough to overflow the 16-bit integer field clamps. */ + psoc_c3_div16_5(48000000UL, 1UL, &i, &f); + if (i > 0xFFFFUL) { + printf("FAIL int field overflowed: %u\n", i); + failures++; + } + else { + printf("ok very low target clamps: int=%u frac=%u\n", i, f); + } + + /* Zero target must not divide by zero, and must leave outputs alone. */ + i = 0xAAAAUL; f = 0x55UL; + psoc_c3_div16_5(48000000UL, 0UL, &i, &f); + if ((i != 0xAAAAUL) || (f != 0x55UL)) { + printf("FAIL zero target modified outputs: %u/%u\n", i, f); + failures++; + } + else { + printf("ok zero target is rejected\n"); + } + + /* The console rate must land within 2%% or the UART will not frame. */ + psoc_c3_div16_5(48000000UL, 115200UL * 8UL, &i, &f); + hz = produced(48000000UL, i, f) / 8U; + if ((hz < (115200UL - 2304UL)) || (hz > (115200UL + 2304UL))) { + printf("FAIL console baud %u Hz is out of tolerance\n", hz); + failures++; + } + else { + printf("ok console baud %u Hz within 2%% of 115200\n", hz); + } + + /* The integer divider the I2C clock uses rounds to nearest, so check the + * rate it produces rather than the register value. The same expression is + * in psoc_c3_pclk_setup(); a bus that is too fast is out of spec, so the + * tolerance is one-sided in practice but checked both ways here. */ + i = int_divider(48000000UL, 100000UL * 16UL); + hz = 48000000UL / i / 16U; + if ((hz < 90000UL) || (hz > 100000UL)) { + printf("FAIL i2c rate %u Hz out of tolerance (divider %u)\n", hz, i); + failures++; + } + else { + printf("ok i2c rate %u Hz within tolerance of 100000\n", hz); + } + + /* A target faster than the source must still divide by at least one + * rather than by zero. */ + i = int_divider(48000000UL, 96000000UL); + if (i == 0UL) { + printf("FAIL integer divider returned zero\n"); + failures++; + } + else { + printf("ok over-fast target clamps to divider %u\n", i); + } + + if (failures != 0) { + printf("%d failure(s)\n", failures); + return 1; + } + printf("All PSOC C3 divider tests passed\n"); + return 0; +}