From 20db1ec39ba01cb77cde9db7784a2342e16cefe5 Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 18 Aug 2026 12:16:05 -0700 Subject: [PATCH 1/2] lib/wolfssl: bump to ti_c25 merge (e71b086fc) for CHAR_BIT!=8 wide-byte support --- arch.mk | 23 +++++++++++++++++++---- include/user_settings.h | 6 ++++++ lib/wolfssl | 2 +- tools/test.mk | 30 ++++++++++++++++++------------ 4 files changed, 44 insertions(+), 17 deletions(-) diff --git a/arch.mk b/arch.mk index 43f55caa79..abca961a9f 100644 --- a/arch.mk +++ b/arch.mk @@ -1111,10 +1111,25 @@ ifeq ($(ARCH),RISCV64) ifneq ($(NO_ASM),1) CFLAGS+=-DWOLFSSL_RISCV_ASM - WOLFCRYPT_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/port/riscv/riscv-64-sha256.o \ - $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/port/riscv/riscv-64-sha512.o \ - $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/port/riscv/riscv-64-sha3.o \ - $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/port/riscv/riscv-64-aes.o + # wolfSSL moved this port to wolfcrypt/src/port/riscv64/ and split each + # primitive into a generated -asm.S plus a -asm_c.c. Only one + # is live: the .S builds unless WOLFSSL_RISCV_ASM_INLINE is defined, which + # wolfBoot does not define, and the _asm_c.c compiles to an empty + # translation unit in that case. Pick whichever layout the pinned + # submodule actually has so this builds against wolfSSL before and after + # the move. + RISCV_ASM_DIR := $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/port/riscv64 + ifneq ($(wildcard $(RISCV_ASM_DIR)/riscv-64-sha256-asm.S),) + WOLFCRYPT_OBJS+=$(RISCV_ASM_DIR)/riscv-64-sha256-asm.o \ + $(RISCV_ASM_DIR)/riscv-64-sha512-asm.o \ + $(RISCV_ASM_DIR)/riscv-64-sha3-asm.o \ + $(RISCV_ASM_DIR)/riscv-64-aes-asm.o + else + WOLFCRYPT_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/port/riscv/riscv-64-sha256.o \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/port/riscv/riscv-64-sha512.o \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/port/riscv/riscv-64-sha3.o \ + $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/port/riscv/riscv-64-aes.o + endif endif endif diff --git a/include/user_settings.h b/include/user_settings.h index 28e98c07ce..5b329f5851 100644 --- a/include/user_settings.h +++ b/include/user_settings.h @@ -122,12 +122,18 @@ #define CTYPE_USER /* don't let wolfCrypt types.h include ctype.h */ #ifndef WOLFSSL_ARMASM +/* Not when preprocessing a .S: this header is pulled into wolfSSL's generated + * assembly (riscv64 and others) via libwolfssl_sources_asm.h, and a C + * declaration reaching the assembler is a syntax error. The macros below are + * harmless there; only the prototypes need hiding. */ +#ifndef __ASSEMBLER__ #ifndef toupper extern int toupper(int c); #endif #ifndef tolower extern int tolower(int c); #endif +#endif /* !__ASSEMBLER__ */ #define XTOUPPER(c) toupper((c)) #define XTOLOWER(c) tolower((c)) #endif diff --git a/lib/wolfssl b/lib/wolfssl index 5418d6cfdc..e71b086fc8 160000 --- a/lib/wolfssl +++ b/lib/wolfssl @@ -1 +1 @@ -Subproject commit 5418d6cfdc4acd126983331a498ba2a8c70839c7 +Subproject commit e71b086fc83758ca05bf80f7896fa6d770a1787a diff --git a/tools/test.mk b/tools/test.mk index 4080c3f4f4..cbc82c3d6c 100644 --- a/tools/test.mk +++ b/tools/test.mk @@ -1228,43 +1228,49 @@ test-all: clean test-size-all: + # Several limits below are raised relative to upstream. This branch bumps + # lib/wolfssl to the ti_c25 merge for CHAR_BIT!=8 wide-byte support, which + # costs 4-48 bytes depending on configuration (most of it in the no-ASM + # SP-math and ML-DSA paths). No wolfBoot code changed in these builds, and + # each new value is the measured size, matching upstream's convention. + # Measured with the CI container (ghcr.io/wolfssl/wolfboot-ci-arm). make test-size SIGN=NONE LIMIT=5116 NO_ARM_ASM=1 make keysclean make test-size SIGN=ED25519 LIMIT=12228 NO_ARM_ASM=1 make keysclean make test-size SIGN=ECC256 LIMIT=18924 NO_ARM_ASM=1 make clean - make test-size SIGN=ECC256 NO_ASM=1 LIMIT=13956 NO_ARM_ASM=1 + make test-size SIGN=ECC256 NO_ASM=1 LIMIT=13968 NO_ARM_ASM=1 make keysclean - make test-size SIGN=RSA2048 LIMIT=11808 NO_ARM_ASM=1 + make test-size SIGN=RSA2048 LIMIT=11816 NO_ARM_ASM=1 make clean make test-size SIGN=RSA2048 NO_ASM=1 LIMIT=12372 NO_ARM_ASM=1 make keysclean - make test-size SIGN=RSA4096 LIMIT=12108 NO_ARM_ASM=1 + make test-size SIGN=RSA4096 LIMIT=12116 NO_ARM_ASM=1 make clean - make test-size SIGN=RSA4096 NO_ASM=1 LIMIT=12652 NO_ARM_ASM=1 + make test-size SIGN=RSA4096 NO_ASM=1 LIMIT=12660 NO_ARM_ASM=1 make keysclean make test-size SIGN=ECC384 LIMIT=19608 NO_ARM_ASM=1 make clean - make test-size SIGN=ECC384 NO_ASM=1 LIMIT=15316 NO_ARM_ASM=1 + make test-size SIGN=ECC384 NO_ASM=1 LIMIT=15328 NO_ARM_ASM=1 make keysclean make test-size SIGN=ED448 LIMIT=14256 NO_ARM_ASM=1 make keysclean - make test-size SIGN=RSA3072 LIMIT=11948 NO_ARM_ASM=1 + make test-size SIGN=RSA3072 LIMIT=11956 NO_ARM_ASM=1 make clean make test-size SIGN=RSA3072 NO_ASM=1 LIMIT=12480 NO_ARM_ASM=1 make keysclean - make test-size SIGN=RSAPSS2048 LIMIT=13744 NO_ARM_ASM=1 + make test-size SIGN=RSAPSS2048 LIMIT=13748 NO_ARM_ASM=1 make clean make test-size SIGN=RSAPSS2048 NO_ASM=1 LIMIT=14304 NO_ARM_ASM=1 make keysclean - make test-size SIGN=RSAPSS3072 LIMIT=13912 NO_ARM_ASM=1 + make test-size SIGN=RSAPSS3072 LIMIT=13916 NO_ARM_ASM=1 make clean make test-size SIGN=RSAPSS3072 NO_ASM=1 LIMIT=14436 NO_ARM_ASM=1 make keysclean - make test-size SIGN=RSAPSS4096 LIMIT=14084 NO_ARM_ASM=1 + make test-size SIGN=RSAPSS4096 LIMIT=14088 NO_ARM_ASM=1 make clean - make test-size SIGN=RSAPSS4096 NO_ASM=1 LIMIT=14624 NO_ARM_ASM=1 + make test-size SIGN=RSAPSS4096 NO_ASM=1 LIMIT=14628 NO_ARM_ASM=1 make keysclean make test-size SIGN=LMS LMS_LEVELS=2 LMS_HEIGHT=5 LMS_WINTERNITZ=8 \ WOLFBOOT_SMALL_STACK=0 IMAGE_SIGNATURE_SIZE=2644 \ @@ -1272,8 +1278,8 @@ test-size-all: make keysclean make test-size SIGN=XMSS XMSS_PARAMS='XMSS-SHA2_10_256' \ IMAGE_SIGNATURE_SIZE=2500 IMAGE_HEADER_SIZE?=4096 \ - LIMIT=8768 NO_ARM_ASM=1 + LIMIT=8772 NO_ARM_ASM=1 make keysclean make clean - make test-size SIGN=ML_DSA ML_DSA_LEVEL=2 LIMIT=19582 \ + make test-size SIGN=ML_DSA ML_DSA_LEVEL=2 LIMIT=19630 \ IMAGE_SIGNATURE_SIZE=2420 IMAGE_HEADER_SIZE?=8192 From 745b58b65dfa8d8c036723859a4a55fd98d2d871 Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 18 Aug 2026 12:16:05 -0700 Subject: [PATCH 2/2] c2000: add wolfBoot TI C28x (TMS320F28P550SJ) secure-boot XIP port --- .gitignore | 3 + Makefile | 19 ++- arch.mk | 61 +++++++++ config/examples/f28p55x.config | 54 ++++++++ docs/Targets.md | 43 ++++++ hal/f28p55x.c | 234 ++++++++++++++++++++++++++++++++ hal/f28p55x.ld | 87 ++++++++++++ include/c2000_stdint.h | 44 ++++++ include/user_settings.h | 33 ++++- include/wolfboot/wolfboot.h | 54 +++++++- src/boot_c2000.c | 65 +++++++++ src/image.c | 65 +++++++-- src/libwolfboot.c | 20 ++- src/string.c | 9 ++ test-app/app_f28p55x.c | 88 ++++++++++++ test-app/f28p55x_app.cmd | 67 +++++++++ test-app/f28p55x_sign.sh | 92 +++++++++++++ test-app/gen_hdr_c.py | 36 +++++ tools/scripts/c2000_flashimg.py | 116 ++++++++++++++++ 19 files changed, 1161 insertions(+), 29 deletions(-) create mode 100644 config/examples/f28p55x.config create mode 100644 hal/f28p55x.c create mode 100644 hal/f28p55x.ld create mode 100644 include/c2000_stdint.h create mode 100644 src/boot_c2000.c create mode 100644 test-app/app_f28p55x.c create mode 100644 test-app/f28p55x_app.cmd create mode 100755 test-app/f28p55x_sign.sh create mode 100644 test-app/gen_hdr_c.py create mode 100644 tools/scripts/c2000_flashimg.py diff --git a/.gitignore b/.gitignore index f4b23154de..b2911318ae 100644 --- a/.gitignore +++ b/.gitignore @@ -523,3 +523,6 @@ tools/scripts/cm4/*.elf tools/scripts/cm4/*.bin tools/scripts/cm4/linux/ tools/scripts/cm4/rauc/ + +# TI cl2000 (ARCH=C2000) intermediate assembly listings left in the repo root +/*.asm diff --git a/Makefile b/Makefile index 7639cbc9dd..f8e46bc677 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,10 @@ ifneq ($(LIBERO_FPGA_CONFIG_DIR),) endif CFLAGS:=-D"__WOLFBOOT" +# gcc/clang warning flags; the TI cl2000 driver (ARCH=C2000) rejects them. +ifneq ($(ARCH),C2000) CFLAGS+=-Werror -Wextra -Wno-array-bounds +endif LSCRIPT:=config/target.ld LSCRIPT_FLAGS:= LDFLAGS:= @@ -238,9 +241,12 @@ $(WOLFHSM_OBJS): CFLAGS += -Wno-error=unused-parameter CFLAGS+= \ -I"." -I"include/" -I"$(WOLFBOOT_LIB_WOLFSSL)" \ - -Wno-array-bounds \ -D"WOLFSSL_USER_SETTINGS" \ -D"WOLFTPM_USER_SETTINGS" +# -Wno-array-bounds is a gcc/clang option; the TI cl2000 driver rejects it. +ifneq ($(ARCH),C2000) +CFLAGS+=-Wno-array-bounds +endif CFLAGS+=$(WOLFPSA_CFLAGS) # Setup default optimizations (for GCC) @@ -260,6 +266,10 @@ endif ifeq ($(TARGET),ti_hercules) LSCRIPT_FLAGS+=--run_linker $(LSCRIPT) endif +ifeq ($(ARCH),C2000) + # cl2000 enters link mode via -z (in LDFLAGS); the .cmd is a positional arg. + LSCRIPT_FLAGS+=$(LSCRIPT) +endif ifeq ($(ARCH),AURIX_TC3) ifneq ($(USE_GCC_HEADLESS),1) LSCRIPT_FLAGS+=-T $(LSCRIPT) @@ -387,6 +397,12 @@ ifeq ($(TARGET),imx95_m7) MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin endif +ifeq ($(TARGET),f28p55x) + # C28x flash is word-addressed; DSLite loads the cl2000 .out (ELF) directly. + # No objcopy / no flat .bin. + MAIN_TARGET:=wolfboot.elf +endif + ifeq ($(TARGET),sim) CFLAGS+=-fno-pie LDFLAGS+=-no-pie @@ -732,6 +748,7 @@ 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/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 $(Q)rm -f $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/port/Renesas/*.o $(Q)rm -f wolfboot.bin wolfboot.elf wolfboot.map test-update.rom wolfboot.hex wolfboot.srec factory.srec diff --git a/arch.mk b/arch.mk index abca961a9f..1d5970cea0 100644 --- a/arch.mk +++ b/arch.mk @@ -2108,6 +2108,67 @@ ifeq ($(ARCH),sim) endif endif +# TI C2000 C28x DSP (TMS320F28P550SJ / LAUNCHXL-F28P55X), cl2000 toolchain. +# Word-addressed, CHAR_BIT==16. Modeled on the ti_hercules (armcl) TI-CGT flow. +ifeq ($(ARCH),C2000) + # cl2000 is not gcc: turn off the gcc/headless CFLAGS+LDFLAGS blocks that + # follow the arch.mk include (Makefile ~line 247) before they are evaluated. + USE_GCC:=0 + USE_GCC_HEADLESS:=0 + + C2000WARE?=$(HOME)/ti/C2000Ware_26_01_00_00 + ifeq ($(CGT_ROOT),) + $(error Set CGT_ROOT to a TI C2000 codegen install (the dir with bin/cl2000)) + endif + C2000_DEV:=$(C2000WARE)/device_support/f28p55x + C2000_DRV:=$(C2000WARE)/driverlib/f28p55x/driverlib + C2000_FAPI:=$(C2000WARE)/libraries/flash_api/f28p55x + + CC=$(CGT_ROOT)/bin/cl2000 + LD=$(CGT_ROOT)/bin/cl2000 + AS=$(CGT_ROOT)/bin/cl2000 + AR=$(CGT_ROOT)/bin/ar2000 + OUTPUT_FLAG=--output_file + + # --float_support/--abi must match the prebuilt driverlib.lib + Fapi lib (EABI). + ARCH_FLAGS=-v28 --float_support=fpu32 --tmu_support=tmu1 --abi=eabi \ + --gen_func_subsections=on + # Set the level here so options.mk emits -O2 (matching cl2000) instead of its + # default gcc-only -Os, which would otherwise be appended after our flags. + OPTIMIZATION_LEVEL=2 + CFLAGS+=$(ARCH_FLAGS) -D_LAUNCHXL_F28P55X -D_FLASH \ + -I$(CGT_ROOT)/include -I$(C2000_DRV) \ + -I$(C2000_DEV)/common/include -I$(C2000_DEV)/headers/include \ + -I$(C2000_FAPI)/include -I$(C2000_FAPI)/include/FlashAPI + # The C28x has no 8-bit type, so ISO omits int8_t/uint8_t; supply + # them (as 16-bit) via a preinclude for every TU. #303 is the harmless + # "typedef already declared (same type)" clash with driverlib's hw_types.h. + # #169 is the expected uint8_t*(=uint16_t*) vs wolfSSL byte*(=unsigned char*) + # pointer mismatch; both are 16-bit cells holding one octet, so it is safe. + CFLAGS+=--preinclude=c2000_stdint.h --diag_suppress=303 --diag_suppress=169 + LDFLAGS+=$(ARCH_FLAGS) -z --reread_libs --warn_sections \ + -i$(CGT_ROOT)/lib -i$(C2000_DRV)/ccs/Release -i$(C2000_FAPI)/lib \ + -m wolfboot.map + LD_START_GROUP:= + LD_END_GROUP:=-l driverlib.lib -l FAPI_F28P55x_EABI_v4.00.00.lib -l libc.a + ARCH_FLASH_OFFSET=0x80000 + + # TI device startup: reset codestart -> _c_int00 (RTS) -> main. + OBJS+=$(C2000_DEV)/common/source/device.o + OBJS+=$(C2000_DEV)/common/source/f28p55x_codestartbranch.o + OBJS+=src/boot_c2000.o + + ifeq ($(SPMATH),1) + # SECP256R1 fast SP path (wide-byte hand-patched octet masks live here). + MATH_OBJS+=$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/sp_c32.o + endif + + # TI assembler sources use the .asm suffix. +%.o:%.asm + @echo "\t[AS-C2000] $@" + $(Q)$(CC) $(CFLAGS) -c $(OUTPUT_FLAG) $@ $^ +endif + # Infineon AURIX Tricore ifeq ($(ARCH), AURIX_TC3) # TC3xx specific diff --git a/config/examples/f28p55x.config b/config/examples/f28p55x.config new file mode 100644 index 0000000000..90a77d140b --- /dev/null +++ b/config/examples/f28p55x.config @@ -0,0 +1,54 @@ +# TI LAUNCHXL-F28P55X (TMS320F28P550SJ, C2000 C28x DSP) - secure-boot MVP +# +# The C28x is a word-addressed, CHAR_BIT==16 DSP built with the TI cl2000 +# compiler. wolfBoot runs from flash bank0 and XIP-boots a signed application +# resident in bank1. See docs/Targets.md and the port notes in hal/f28p55x.c. +# +# Build (point CGT_ROOT at a TI C2000 codegen install, C2000WARE at C2000Ware): +# cp config/examples/f28p55x.config .config +# make CGT_ROOT=$HOME/ti/ccs/ccs/tools/compiler/ti-cgt-c2000_ \ +# C2000WARE=$HOME/ti/C2000Ware_26_01_00_00 +# +# Flash offset (bank0 codestart) is fixed by the arch in arch.mk: +# ARCH_FLASH_OFFSET=0x80000 + +ARCH?=C2000 +TARGET?=f28p55x +SIGN?=ECC256 +HASH?=SHA256 +DEBUG?=0 +# Route wolfBoot_printf to SCIA (GPIO28/29 -> XDS110 virtual COM, 115200 8N1) +DEBUG_UART?=1 +VTOR?=0 +CORTEX_M0?=0 +NO_ASM?=1 +NO_MPU?=1 +EXT_FLASH?=0 +SPI_FLASH?=0 +ALLOW_DOWNGRADE?=0 +# Fapi programs whole flash words with ECC; single-byte trailer writes are not +# possible, so use the whole-sector read-modify-write cache path. +NVM_FLASH_WRITEONCE?=1 +WOLFBOOT_VERSION?=0 +V?=0 +# ECC P-256 verify via SP single-precision math (32-bit words, sp_c32.c). +SPMATH?=1 +# HAL flash program/erase (Fapi) must execute from RAM. +RAM_CODE?=1 +DUALBANK_SWAP?=0 + +# Flash bank map (word addresses; hardware banks are 0x80000, 0xA0000, 0xC0000, +# 0xE0000, 0x100000). wolfBoot owns bank0; BOOT is bank1, UPDATE bank2, SWAP +# bank3 (UPDATE/SWAP are declared for the phase-2 A/B update path and are not +# exercised by the MVP verify+jump). The signed app's codestart is linked at +# WOLFBOOT_PARTITION_BOOT_ADDRESS + IMAGE_HEADER_SIZE = 0xA0100. +# +# WOLFBOOT_SECTOR_SIZE must be >= IMAGE_HEADER_SIZE and match the Fapi erase +# granularity. Confirm the 2KB F28P55x sector in words against the Fapi sector +# table before relying on the phase-2 erase/trailer path; 0x800 is a safe +# conservative value for the MVP (no erase/write on the boot-critical path). +WOLFBOOT_SECTOR_SIZE?=0x800 +WOLFBOOT_PARTITION_SIZE?=0x20000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0xA0000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0xC0000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0xE0000 diff --git a/docs/Targets.md b/docs/Targets.md index afa2757c85..8f28417fa2 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -60,6 +60,7 @@ This README describes configuration of supported targets. * [STM32L5](#stm32l5) * [STM32U5](#stm32u5) * [STM32WB55](#stm32wb55) +* [TI C2000 C28x (LAUNCHXL-F28P55X)](#ti-c2000-c28x-launchxl-f28p55x) * [TI Hercules TMS570LC435](#ti-hercules-tms570lc435) * [Vorago VA416x0](#vorago-va416x0) * [Xilinx Zynq UltraScale](#xilinx-zynq-ultrascale) @@ -9202,3 +9203,45 @@ devmem 0x80F10014 # heartbeat, incrementing The difference between the two timestamps is the cost of everything wolfBoot does in between, which is dominated by signature verification. Note that these magics are spelled to read correctly as `devmem` 32-bit words, the opposite convention from the console magic, which is read from a hexdump of the ring. Both caches are enabled by `hal_init()`, which matters because verifying an image means hashing megabytes resident in DDR. The ARMv7-M default memory map marks `0x80000000-0x9FFFFFFF` as Normal write-through, so no MPU region is needed and M7 stores to the shared window still reach DDR; the HAL nevertheless cleans the affected lines explicitly so that behaviour is not left depending on an inherited attribute. + +## TI C2000 C28x (LAUNCHXL-F28P55X) + +wolfBoot runs on the Texas Instruments C2000 C28x DSP (TMS320F28P550SJ, 150 MHz) as a secure execute-in-place (XIP) bootloader. The C28x is word-addressed with `CHAR_BIT == 16` (no 8-bit type -- each octet occupies one 16-bit cell), built with the TI `cl2000` toolchain against wolfSSL's wide-byte (`CHAR_BIT != 8`) support. + +### Flash layout + +wolfBoot owns flash bank0 (`0x80000`); the signed application lives in the BOOT partition at bank1 (`0xA0000`). Because an octet-per-cell header and a native-word executable cannot share one representation, the BOOT partition uses a split layout: the 256-cell image header is stored one octet per 16-bit cell (so wolfBoot's generic octet parser and hash work byte-identically to the host), and the firmware follows at `0xA0100` as native 16-bit words that execute in place. + +| Region | Address | Contents | +|--------|---------|----------| +| wolfBoot (bank0) | `0x80000` | bootloader code + keystore | +| BOOT header | `0xA0000` | 256-cell signed header (one octet per cell) | +| BOOT firmware | `0xA0100` | native XIP application | + +### Build + +``` +cp config/examples/f28p55x.config .config +make CGT_ROOT=/path/to/ti-cgt-c2000 C2000WARE=/path/to/C2000Ware +``` + +This produces `wolfboot.elf` (the cl2000 `.out`); DSLite loads it directly, since C28x flash is word-addressed there is no objcopy / flat `.bin` step. + +### Sign and flash the application + +The test application (`test-app/app_f28p55x.c`) is linked to execute in place at `0xA0100`. `test-app/f28p55x_sign.sh` documents the flow: compile the XIP app, extract its firmware as the host octet stream, sign it (ECC P-256 + SHA-256), and emit the octet-per-cell header blob for `0xA0000`. Flash `wolfboot.elf` and the application image with DSLite over the onboard XDS110. + +### Boot mode + +The C28x boot ROM selects its boot source before any application code runs, so the device must be told to boot from flash. Under a debug session CCS/DSLite (via the device GEL) writes a volatile flash-boot override -- `EMU_BOOTPIN_KEY (0xD00) = 0x5AFFFFFF`, `EMU_BOOTPIN_CONFIG (0xD04) = 0x0003` (boot mode 3, flash entry `0x080000`) -- so wolfBoot boots from `0x80000` whenever a debugger has connected. That override is volatile and is not present on a bare power-on-reset. For **standalone** flash boot the device's persistent boot mode must be provisioned to flash (boot mode `0x03`) via the DCSM boot OTP, or set with the board's boot-mode straps where available; until then a bare power-up waits in the boot ROM. The SCIA console (GPIO28/29, 115200 8N1) is on the XDS110 virtual COM port; a live debug session garbles that backchannel, so read it with the probe detached. + +### Configuration options + +| Option | Description | +|--------|-------------| +| `SIGN=ECC256` / `HASH=SHA256` | Signature and hash for the secure-boot MVP. | +| `RAM_CODE` | **Required.** Flash program/erase (TI Fapi) runs from RAM. | +| `NVM_FLASH_WRITEONCE` | Flash is written once between erases. | +| `DEBUG` | Enables verbose boot progress and a JTAG-readable survive-log mirror (`g_log`) for bring-up. | + +A/B update / rollback is a follow-on: the partitions are declared, but the update path (flash erase/write, swap, trailer) is not yet wide-byte-hardened. diff --git a/hal/f28p55x.c b/hal/f28p55x.c new file mode 100644 index 0000000000..804b1a59aa --- /dev/null +++ b/hal/f28p55x.c @@ -0,0 +1,234 @@ +/* f28p55x.c + * + * HAL for the TI LAUNCHXL-F28P55X (TMS320F28P550SJ, C2000 C28x DSP). + * + * wolfBoot runs from flash bank0 (0x80000) in a word-addressed, CHAR_BIT==16 + * environment built with the TI cl2000 compiler. It verifies a signed image + * resident in bank1 (BOOT partition) and branches to it in place (XIP); see + * src/boot_c2000.c for the handoff and docs/Targets.md for the flash map. + * + * Clocks/flash-waitstates/GPIO come from the C2000Ware device support + * (Device_init); flash program/erase use the TI Flash API (Fapi) and must run + * from RAM (RAMFUNCTION -> .TI.ramfunc). The console is SCIA on GPIO28/29 + * (the XDS110 virtual COM, 115200 8N1), wired to wolfBoot_printf via DEBUG_UART. + * + * 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 "driverlib.h" +#include "device.h" +#include "FlashTech.h" + +/* Flash controller base the Fapi library operates on (matches Flash_initModule + * in Device_init). Confirm against the Fapi sector table for the phase-2 + * program/erase path. */ +#ifndef WOLFBOOT_C2000_FLASH_BASE +#define WOLFBOOT_C2000_FLASH_BASE FLASH0CTRL_BASE +#endif + +/* Fapi programs a 64-bit (8 x 16-bit word) main-array slice at a time. */ +#define C2000_FLASH_PGM_WORDS 8 + +/* --------------------------------------------------------------------- */ +/* SCIA console (XDS110 virtual COM) - wolfBoot_printf via DEBUG_UART */ +/* --------------------------------------------------------------------- */ +/* Debug-only mirror of all UART output into a RAM buffer, so boot progress can + * be read back over JTAG (symbols g_log / g_logpos) independent of SCI flow (an + * attached debugger garbles/stalls the XDS110 backchannel). Placed in a NOINIT + * section (RAMGS_SURV, 0xD000) that neither wolfBoot's nor the booted app's .bss + * clears, so the boot log survives the do_boot() handoff and can be read back + * after the app is running (g_logpos is reset in uart_init). Gated on DEBUG so + * production builds do not reserve the RAM or mirror every console byte. */ +#ifdef DEBUG +#define WOLF_LOG_SZ 2048 +#pragma DATA_SECTION(g_log, ".survivelog") +#pragma DATA_SECTION(g_logpos, ".survivelog") +volatile char g_log[WOLF_LOG_SZ]; +volatile unsigned long g_logpos; +#endif + +void uart_init(void) +{ +#ifdef DEBUG + g_logpos = 0; /* reset the survive-log at each boot */ +#endif + /* RX pin */ + GPIO_setPinConfig(DEVICE_GPIO_CFG_SCIRXDA); + GPIO_setDirectionMode(DEVICE_GPIO_PIN_SCIRXDA, GPIO_DIR_MODE_IN); + GPIO_setPadConfig(DEVICE_GPIO_PIN_SCIRXDA, GPIO_PIN_TYPE_STD); + GPIO_setQualificationMode(DEVICE_GPIO_PIN_SCIRXDA, GPIO_QUAL_ASYNC); + + /* TX pin */ + GPIO_setPinConfig(DEVICE_GPIO_CFG_SCITXDA); + GPIO_setDirectionMode(DEVICE_GPIO_PIN_SCITXDA, GPIO_DIR_MODE_OUT); + GPIO_setPadConfig(DEVICE_GPIO_PIN_SCITXDA, GPIO_PIN_TYPE_STD); + GPIO_setQualificationMode(DEVICE_GPIO_PIN_SCITXDA, GPIO_QUAL_ASYNC); + + SCI_performSoftwareReset(SCIA_BASE); + SCI_setConfig(SCIA_BASE, DEVICE_LSPCLK_FREQ, 115200, + (SCI_CONFIG_WLEN_8 | SCI_CONFIG_STOP_ONE | SCI_CONFIG_PAR_NONE)); + SCI_resetChannels(SCIA_BASE); + SCI_resetRxFIFO(SCIA_BASE); + SCI_resetTxFIFO(SCIA_BASE); + SCI_enableFIFO(SCIA_BASE); + SCI_enableModule(SCIA_BASE); + SCI_performSoftwareReset(SCIA_BASE); +} + +/* Bounded-spin SCI put: wait a limited time for FIFO space, then drop. This + * flushes cleanly when the JTAG probe is detached (FIFO drains) yet never + * stalls the CPU when a debug session is holding the XDS110 backchannel. */ +static void sci_putc(uint16_t c) +{ + uint32_t spin = 20000; + while ((SCI_getTxFIFOStatus(SCIA_BASE) == SCI_FIFO_TX16) && (spin-- > 0)) + ; + /* If the bounded wait expired with the FIFO still full, drop the byte + * rather than writing into a full FIFO (a stalled backchannel must not + * wedge the console). */ + if (SCI_getTxFIFOStatus(SCIA_BASE) == SCI_FIFO_TX16) + return; + SCI_writeCharNonBlocking(SCIA_BASE, c); +} + +void uart_write(const char *buf, unsigned int sz) +{ + unsigned int i; + for (i = 0; i < sz; i++) { +#ifdef DEBUG + if (g_logpos < (unsigned long)sizeof(g_log)) + g_log[g_logpos++] = buf[i]; +#endif + if (buf[i] == '\n') + sci_putc((uint16_t)'\r'); + sci_putc((uint16_t)(buf[i] & 0xFF)); + } +} + +/* --------------------------------------------------------------------- */ +/* Flash API (Fapi) helpers - execute from RAM */ +/* --------------------------------------------------------------------- */ +void RAMFUNCTION hal_flash_unlock(void) +{ + /* (Re)initialize the Flash API for the active system frequency and select + * bank0 as the FMC context. Fapi has no global write-enable; per-command + * sector protection is cleared in the write/erase paths. */ + (void)Fapi_initializeAPI((Fapi_FmcRegistersType *)WOLFBOOT_C2000_FLASH_BASE, + DEVICE_SYSCLK_FREQ / 1000000U); + (void)Fapi_setActiveFlashBank(Fapi_FlashBank0); +} + +void RAMFUNCTION hal_flash_lock(void) +{ + /* No persistent lock state to restore for Fapi. */ +} + +int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len) +{ + /* address is a C28x flash word address; data cells each hold one octet + * (the octet-per-cell header/trailer storage). Program 8 words per Fapi + * command; pad a short tail with 0xFFFF (leaves those cells erased). */ + const uint16_t *src = (const uint16_t *)data; + uint32_t addr = address; + int remaining = len; + Fapi_StatusType st; + uint16_t block[C2000_FLASH_PGM_WORDS]; + int i; + + while (remaining > 0) { + for (i = 0; i < C2000_FLASH_PGM_WORDS; i++) { + if (i < remaining) + block[i] = src[i]; + else + block[i] = 0xFFFFU; + } + st = Fapi_issueProgrammingCommand((uint32 *)addr, (uint16 *)block, + C2000_FLASH_PGM_WORDS, 0, 0, + Fapi_AutoEccGeneration); + if (st != Fapi_Status_Success) + return -1; + while (Fapi_checkFsmForReady() == Fapi_Status_FsmBusy) + ; + if (Fapi_getFsmStatus() != 3) + return -1; + addr += C2000_FLASH_PGM_WORDS; + src += C2000_FLASH_PGM_WORDS; + remaining -= C2000_FLASH_PGM_WORDS; + } + return 0; +} + +int RAMFUNCTION hal_flash_erase(uint32_t address, int len) +{ + /* Erase every flash sector overlapping [address, address+len). The Fapi + * sector granularity is WOLFBOOT_SECTOR_SIZE (confirm against the Fapi + * sector table). */ + uint32_t addr = address; + uint32_t end = address + (uint32_t)len; + Fapi_StatusType st; + + while (addr < end) { + st = Fapi_issueAsyncCommandWithAddress(Fapi_EraseSector, + (uint32 *)addr); + if (st != Fapi_Status_Success) + return -1; + while (Fapi_checkFsmForReady() != Fapi_Status_FsmReady) + ; + if (Fapi_getFsmStatus() != 3) + return -1; + addr += WOLFBOOT_SECTOR_SIZE; + } + return 0; +} + +/* --------------------------------------------------------------------- */ +/* wolfBoot HAL entry points */ +/* --------------------------------------------------------------------- */ +#ifdef __WOLFBOOT +void hal_init(void) +{ + /* Device_init: 150 MHz PLL, flash wait states, and the .TI.ramfunc + * copy-to-RAM (RamfuncsLoadStart -> RamfuncsRunStart). */ + Device_init(); + Device_initGPIO(); + uart_init(); + /* Prepare the Flash API for any later program/erase (phase-2 updates). */ + hal_flash_unlock(); +} + +void hal_prepare_boot(void) +{ + uint32_t spin = 200000; + /* Quiesce before the XIP handoff: drain the SCI TX FIFO, then wait for the + * transmit shift register to empty so the final byte is fully clocked out + * before the application runs (otherwise a byte in flight is corrupted if + * the app re-touches the clock). Both waits are bounded so a stalled FIFO + * (e.g. JTAG holding the backchannel) cannot hang the handoff. */ + while ((SCI_getTxFIFOStatus(SCIA_BASE) != SCI_FIFO_TX0) && (spin-- > 0)) + ; + spin = 200000; + while (SCI_isTransmitterBusy(SCIA_BASE) && (spin-- > 0)) + ; + DINT; +} +#endif /* __WOLFBOOT */ diff --git a/hal/f28p55x.ld b/hal/f28p55x.ld new file mode 100644 index 0000000000..2eb62ebff6 --- /dev/null +++ b/hal/f28p55x.ld @@ -0,0 +1,87 @@ +/* f28p55x.ld (TI cl2000 linker command file, tokenized by "make config") + * + * wolfBoot linker layout for the TI TMS320F28P550SJ (LAUNCHXL-F28P55X). + * wolfBoot owns flash bank0 only (@ARCH_FLASH_OFFSET@, @BOOTLOADER_PARTITION_SIZE@ + * bytes); banks 1-4 hold the BOOT/UPDATE/SWAP partitions and are NOT linked + * here (the resident application is linked separately at its BOOT address). + * + * The C28x stack pointer is 16-bit, so .stack must live below 0x10000. + * Flash program/erase code (RAMFUNCTION -> .TI.ramfunc) is loaded from flash + * and copied to RAM by Device_init's Ramfuncs memcpy. + * + * Copyright (C) 2026 wolfSSL Inc. GPLv3 - see project headers. + */ + +-stack 0x4000 /* 16 KW C28x stack (full RAMLS0-7, 0x8000..0xBFFF); below + * 0x10000. ECC P-256 verify keeps the ecc_key struct and the + * SP *_NO_MALLOC digit buffers on the stack (WOLFSSL_NO_MALLOC, + * no heap); 8 KW then 12 KW overflowed into the adjacent + * .TI.ramfunc region, corrupting saved frames (wild return -> + * ITRAP / hang). Matches the validated wolfCrypt example's + * 16 KW stack. */ +-heap 0x1000 /* 4 KW heap (unused: WOLFSSL_NO_MALLOC) */ + +MEMORY +{ + BEGIN : origin = @ARCH_FLASH_OFFSET@, length = 0x000002 + + BOOT_RSVD : origin = 0x000002, length = 0x000126 /* M0, boot-ROM stack */ + RAMM0 : origin = 0x000128, length = 0x0002D8 + RAMM1 : origin = 0x000400, length = 0x000400 + + /* Low RAM (< 0x10000). The 16-bit-SP stack fills RAMLS0-7 (16 KW). + * .TI.ramfunc RUNs from RAMGS0 (0xC000): on a cold flash-boot only RAMGS0 + * (and RAMLS0-5) are CPU instruction-fetchable early - RAMLS6-7 default to + * CLA ownership, so Flash_initModule() (called from Device_init before any + * MemCfg) ITRAPs if placed there. This matches the wolfCrypt example. */ + RAMLS_STACK : origin = 0x008000, length = 0x004000 /* RAMLS0-7, 16 KW */ + RAMGS_RAMCODE : origin = 0x00C000, length = 0x001000 /* RAMGS0 lo, .TI.ramfunc */ + RAMGS_SURV : origin = 0x00D000, length = 0x001000 /* survive-log (persists across do_boot) */ + RAMGS_HEAP : origin = 0x00E000, length = 0x002000 /* RAMGS1, heap (unused) */ + + /* High RAM (>= 0x10000): data only. */ + RAMGS_HI : origin = 0x010000, length = 0x004000 /* RAMGS2-3, 16 KW */ + RAMLS_HI : origin = 0x014000, length = 0x004000 /* RAMLS8-9, 16 KW */ + + /* wolfBoot code + const live in flash bank0 only. */ + FLASH_BANK0 : origin = 0x080002, length = 0x01FFFE + + RESET : origin = 0x3FFFC0, length = 0x000002 +} + +SECTIONS +{ + /* _start_text marks the base of the wolfBoot image (used by update_flash.c + * under RAM_CODE for the bootloader region / self-hash). */ + codestart : > BEGIN, START(_start_text) + + /* RAM-resident flash program/erase routines: loaded from flash, copied to + * RAM by Device_init. --gen_func_subsections=on makes each function its + * own input section; the ramfunc attribute places them in .TI.ramfunc. */ + .TI.ramfunc : LOAD = FLASH_BANK0, + RUN = RAMGS_RAMCODE, + LOAD_START(RamfuncsLoadStart), + LOAD_SIZE(RamfuncsLoadSize), + LOAD_END(RamfuncsLoadEnd), + RUN_START(RamfuncsRunStart), + RUN_SIZE(RamfuncsRunSize), + RUN_END(RamfuncsRunEnd), + ALIGN(8) + + .text : > FLASH_BANK0, ALIGN(8), END(_end_text) + .cinit : > FLASH_BANK0, ALIGN(8) + .switch : > FLASH_BANK0, ALIGN(8) + .init_array : > FLASH_BANK0, ALIGN(8) + .const : > FLASH_BANK0, ALIGN(8) + /* wolfBoot public-key keystore (src/keystore.c) */ + .keystore : > FLASH_BANK0, ALIGN(8) + .reset : > RESET, TYPE = DSECT /* unused; boot ROM owns it */ + + .stack : > RAMLS_STACK + + .bss : >> RAMGS_HI | RAMLS_HI + .bss:output : > RAMGS_HI + .data : >> RAMGS_HI | RAMLS_HI + .sysmem : > RAMGS_HEAP + .survivelog : > RAMGS_SURV, type = NOINIT +} diff --git a/include/c2000_stdint.h b/include/c2000_stdint.h new file mode 100644 index 0000000000..791bbe4663 --- /dev/null +++ b/include/c2000_stdint.h @@ -0,0 +1,44 @@ +/* c2000_stdint.h + * + * Force-included (--preinclude) for the TI C2000 C28x (ARCH=C2000) build. + * + * The C28x is word-addressed with CHAR_BIT==16 and has NO 8-bit integer type, + * so ISO correctly does not define int8_t/uint8_t on this target. + * wolfBoot (and TI's own driverlib hw_types.h) represents an octet in a 16-bit + * cell, so provide the exact-width 8-bit aliases as 16-bit types. When a + * translation unit also pulls in driverlib's hw_types.h, its identical + * typedefs produce diagnostic #303, suppressed for this arch in arch.mk. + * + * 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 WOLFBOOT_C2000_STDINT_H +#define WOLFBOOT_C2000_STDINT_H + +#include + +/* Guarded to the C28x: this header is --preinclude'd only for ARCH=C2000, but + * the guard keeps the 8-bit aliases from ever redefining a real int8_t/uint8_t + * should it be pulled into a normal-byte translation unit. */ +#if defined(__TMS320C28XX__) +typedef uint16_t uint8_t; +typedef int16_t int8_t; +#endif + +#endif /* WOLFBOOT_C2000_STDINT_H */ diff --git a/include/user_settings.h b/include/user_settings.h index 5b329f5851..1234d7f7f3 100644 --- a/include/user_settings.h +++ b/include/user_settings.h @@ -118,6 +118,33 @@ #endif #endif /* HAVE_FIPS */ +/* TI C2000 C28x: word-addressed, 16-bit int, CHAR_BIT==16. wolfCrypt's + * WOLFSSL_WIDE_BYTE support auto-enables on __TMS320C28XX__; configure the + * integer widths, pull in (for CHAR_BIT and sp_int size detection), + * and disable asm/inline paths that don't apply. */ +#if defined(WOLFBOOT_ARCH_C2000) || defined(__TMS320C28XX__) +# undef SIZEOF_LONG +# define SIZEOF_LONG 4 +# undef HAVE_LIMITS_H +# define HAVE_LIMITS_H +# undef WC_16BIT_CPU +# define WC_16BIT_CPU +# undef WOLFSSL_GENERAL_ALIGNMENT +# define WOLFSSL_GENERAL_ALIGNMENT 2 +# undef WOLFSSL_NO_ASM +# define WOLFSSL_NO_ASM +# undef WC_SHA3_NO_ASM +# define WC_SHA3_NO_ASM + /* cl2000 treats plain inline as C99 extern-inline, leaving misc.c helpers + * unresolved at link; make them ordinary extern functions (misc.o linked). */ +# undef NO_INLINE +# define NO_INLINE +# undef WOLFSSL_SP_ALLOW_16BIT_CPU +# define WOLFSSL_SP_ALLOW_16BIT_CPU +# undef WOLFSSL_SP_NO_MALLOC +# define WOLFSSL_SP_NO_MALLOC +#endif + /* Stdlib Types */ #define CTYPE_USER /* don't let wolfCrypt types.h include ctype.h */ @@ -559,8 +586,10 @@ extern int tolower(int c); # define SP_WORD_SIZE 32 # endif - /* SP Math needs to understand long long */ -# ifndef ULLONG_MAX + /* SP Math needs to understand long long. Skip this fallback when limits.h + * is available (HAVE_LIMITS_H), which defines ULLONG_MAX itself - otherwise + * the two definitions clash (e.g. TI cl2000 / CHAR_BIT!=8 builds). */ +# if !defined(ULLONG_MAX) && !defined(HAVE_LIMITS_H) # define ULLONG_MAX 18446744073709551615ULL # endif #endif diff --git a/include/wolfboot/wolfboot.h b/include/wolfboot/wolfboot.h index ca2f981a21..1b947395b4 100644 --- a/include/wolfboot/wolfboot.h +++ b/include/wolfboot/wolfboot.h @@ -30,6 +30,7 @@ extern "C" { #endif #include +#include /* CHAR_BIT */ #ifdef __WOLFBOOT /* Either hand-craft a device target.h file in [WOLFBOOT_ROOT]/include * or let build process auto-create one from .config file or cmake presets. @@ -59,6 +60,12 @@ extern "C" { # endif # elif defined(ARCH_PPC) # define RAMFUNCTION __attribute__((used,section(".ramcode"),longcall)) +# elif defined(__TMS320C28XX__) + /* TI C2000 cl2000: place in .TI.ramfunc; the linker LOAD/RUN pair + the + * device startup Ramfuncs memcpy relocate it to RAM (see hal/f28p55x.ld). + * Gated to the C28x specifically so the ti_hercules (armcl) .ramcode + * path above is not affected. */ +# define RAMFUNCTION __attribute__((ramfunc)) # else # define RAMFUNCTION __attribute__((used,section(".ramcode"))) # endif @@ -68,7 +75,7 @@ extern "C" { #endif #ifndef WEAKFUNCTION -# if defined(__GNUC__) || defined(__CC_ARM) +# if defined(__GNUC__) || defined(__CC_ARM) || defined(__TMS320C28XX__) # define WEAKFUNCTION __attribute__((weak)) # else # define WEAKFUNCTION @@ -101,7 +108,7 @@ extern "C" { /* Helpers for memory alignment */ #ifndef XALIGNED #if defined(__GNUC__) || defined(__llvm__) || \ - defined(__IAR_SYSTEMS_ICC__) + defined(__IAR_SYSTEMS_ICC__) || defined(__TMS320C28XX__) #define XALIGNED(x) __attribute__ ( (aligned (x))) #elif defined(__KEIL__) #define XALIGNED(x) __align(x) @@ -168,7 +175,48 @@ extern "C" { # endif #endif /* IMAGE_HEADER_SIZE */ -#define IMAGE_HEADER_OFFSET (2 * sizeof(uint32_t)) + +/* Image-header fixed-field access. + * + * The header's serialized 32-/16-bit fields (magic, size, version, type) are a + * little-endian octet stream. On normal targets one octet == one addressable + * byte; on the C28x (CHAR_BIT==16, header stored one octet per 16-bit cell) a + * u32 field spans 4 octets == 4 cells even though sizeof(uint32_t) is only 2 + * cells. So reconstruct each field from individually masked cells rather than + * a single (possibly unaligned) load: + * - on the C28x a cell may carry non-octet upper bits - the & 0xFF keeps the + * value octet-exact; + * - on every other target the & 0xFF is a no-op and the byte reconstruction + * avoids an unaligned 32/16-bit load (wolfBoot_find_header only guarantees + * 2-byte alignment) and is endian-neutral. + * The _SZ macros are the field's octet width (4 octets == 4 cells on the C28x), + * used for both pointer offsets and find_header() length checks. + * + * Only the wide-byte target needs the reconstruction. Where one octet is one + * byte the field is read exactly as it always was, for two reasons: the + * assembled form costs code size on every target (it pushed the SIGN=NONE + * footprint build over its limit), and callers wrap these in im2n()/im2ns(), + * which convert little-endian to native - so an already-native result would be + * byte-swapped a second time on a big-endian target. */ +#define WOLFBOOT_HDR_U32_SZ 4 +#define WOLFBOOT_HDR_U16_SZ 2 +#if CHAR_BIT != 8 +/* A cell may carry non-octet upper bits, so mask each one; the result is + * native order already, and these parts are little-endian so the caller's + * im2n()/im2ns() is a no-op. */ +#define WOLFBOOT_HDR_GET_U32(p) \ + (((uint32_t)(((const uint8_t*)(p))[0] & 0xFF)) | \ + ((uint32_t)(((const uint8_t*)(p))[1] & 0xFF) << 8) | \ + ((uint32_t)(((const uint8_t*)(p))[2] & 0xFF) << 16) | \ + ((uint32_t)(((const uint8_t*)(p))[3] & 0xFF) << 24)) +#define WOLFBOOT_HDR_GET_U16(p) \ + ((uint16_t)((((const uint8_t*)(p))[0] & 0xFF) | \ + ((((const uint8_t*)(p))[1] & 0xFF) << 8))) +#else +#define WOLFBOOT_HDR_GET_U32(p) (*(const uint32_t*)(const void*)(p)) +#define WOLFBOOT_HDR_GET_U16(p) (*(const uint16_t*)(const void*)(p)) +#endif +#define IMAGE_HEADER_OFFSET (2 * WOLFBOOT_HDR_U32_SZ) #ifndef FLASHBUFFER_SIZE # ifdef NVM_FLASH_WRITEONCE diff --git a/src/boot_c2000.c b/src/boot_c2000.c new file mode 100644 index 0000000000..29c5d81033 --- /dev/null +++ b/src/boot_c2000.c @@ -0,0 +1,65 @@ +/* boot_c2000.c + * + * Architecture boot handoff for the TI C2000 C28x DSP (TMS320F28P550SJ). + * + * The reset/startup path is provided by the C2000Ware codestart + * (f28p55x_codestartbranch.asm -> _c_int00 -> main); wolfBoot's main() + * (src/loader.c) then runs hal_init() and the verify state machine. This file + * provides the two arch hooks wolfBoot requires: do_boot(), which branches to + * the verified application resident in the BOOT partition (execute-in-place), + * and arch_reboot(). + * + * 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 "driverlib.h" +#include "device.h" + +/* + * Branch to the verified application. + * + * app_offset is the firmware base (BOOT partition address + IMAGE_HEADER_SIZE), + * i.e. the application's own codestart, linked to execute in place. There is + * no vector table to reload on the C28x: interrupts are masked here and the + * application's codestart re-establishes its stack pointer and re-runs the + * C-runtime init before calling its main(). This never returns. + */ +void do_boot(const uint32_t *app_offset) +{ + void (*app_entry)(void); + + DINT; /* mask maskable interrupts across the handoff */ + + app_entry = (void (*)(void))(uintptr_t)app_offset; + app_entry(); + + /* Not reached. */ + while (1) + ; +} + +void arch_reboot(void) +{ + SysCtl_resetDevice(); + while (1) + ; +} diff --git a/src/image.c b/src/image.c index b76648ff51..062bbcf7e0 100644 --- a/src/image.c +++ b/src/image.c @@ -282,7 +282,17 @@ static void wolfBoot_verify_signature_ecc(uint8_t key_slot, struct wolfBoot_image *img, uint8_t *sig) { int ret, verify_res = 0; +#if defined(__TMS320C28XX__) || defined(WOLFBOOT_ARCH_C2000) + /* C28x: the ecc_key struct is large relative to the 16-bit-SP low-RAM stack + * (WOLFSSL_NO_MALLOC keeps SP-256 verify temporaries on the stack too), so + * keep it in .bss to avoid overflowing the stack into adjacent RAM during + * verify. wolfBoot verifies images sequentially and wc_ecc_init_ex/ + * wc_ecc_free bracket each use, so a single shared instance is safe. The + * mp_ints r/s are small and stay on the stack, freshly mp_init'd per call. */ + static ecc_key ecc; +#else ecc_key ecc; +#endif mp_int r, s; #if !defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT) && \ !defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) @@ -1107,7 +1117,7 @@ static int header_sha256(wc_Sha256 *sha256_ctx, struct wolfBoot_image *img) stored_sha_len = get_header(img, HDR_SHA256, &stored_sha); if (stored_sha_len != WOLFBOOT_SHA_DIGEST_SIZE) return -1; - end_sha = stored_sha - (2 * sizeof(uint16_t)); /* Subtract 2 Type + 2 Len */ + end_sha = stored_sha - (2 * WOLFBOOT_HDR_U16_SZ); /* Subtract 2 Type + 2 Len */ #ifdef WOLFBOOT_IMG_HASH_ONESHOT if (end_sha <= p) { return -1; @@ -1144,7 +1154,39 @@ static int image_sha256(struct wolfBoot_image *img, uint8_t *hash) if (header_sha256(&sha256_ctx, img) != 0) return -1; -#ifdef WOLFBOOT_IMG_HASH_ONESHOT +#if defined(WOLFBOOT_ARCH_C2000) + /* C28x (CHAR_BIT==16): the firmware is stored as native, executable 16-bit + * program words, but the host signed an octet stream in which each program + * word was serialized low-octet-then-high-octet. Reproduce that ordering + * so the on-target digest matches the host's. img->fw_size is the octet + * count (2 octets per program word); each buf[] cell holds one octet, and + * the wide-byte wc_Sha256Update consumes one octet per cell. */ + { + const uint16_t *w = (const uint16_t *)img->fw_base; + uint32_t position = 0; + uint8_t buf[64]; /* even; each cell holds one octet */ + int n; + uint16_t val; + if (img->fw_base == NULL) { + wc_Sha256Free(&sha256_ctx); + return -1; + } + while (position < img->fw_size) { + n = 0; + while ((n <= (int)sizeof(buf) - 2) && (position < img->fw_size)) { + val = *w++; + buf[n++] = (uint8_t)(val & 0xFF); /* low octet */ + position++; + if (position < img->fw_size) { + buf[n++] = (uint8_t)((val >> 8) & 0xFF); /* high octet */ + position++; + } + } + wc_Sha256Update(&sha256_ctx, buf, n); + wolfBoot_watchdog_feed(); + } + } +#elif defined(WOLFBOOT_IMG_HASH_ONESHOT) if (img->fw_base == NULL) { wc_Sha256Free(&sha256_ctx); return -1; @@ -1216,7 +1258,7 @@ static int header_sha384(wc_Sha384 *sha384_ctx, struct wolfBoot_image *img) stored_sha_len = get_header(img, HDR_SHA384, &stored_sha); if (stored_sha_len != WOLFBOOT_SHA_DIGEST_SIZE) return -1; - end_sha = stored_sha - (2 * sizeof(uint16_t)); /* Subtract 2 Type + 2 Len */ + end_sha = stored_sha - (2 * WOLFBOOT_HDR_U16_SZ); /* Subtract 2 Type + 2 Len */ #ifdef WOLFBOOT_IMG_HASH_ONESHOT if (end_sha <= p) { return -1; @@ -1338,7 +1380,7 @@ static int header_sha3_384(wc_Sha3 *sha3_ctx, struct wolfBoot_image *img) stored_sha_len = get_header(img, HDR_SHA3_384, &stored_sha); if (stored_sha_len != WOLFBOOT_SHA_DIGEST_SIZE) return -1; - end_sha = stored_sha - (2 * sizeof(uint16_t)); /* Subtract 2 Type + 2 Len */ + end_sha = stored_sha - (2 * WOLFBOOT_HDR_U16_SZ); /* Subtract 2 Type + 2 Len */ #ifdef WOLFBOOT_IMG_HASH_ONESHOT if (end_sha <= p) { return -1; @@ -1466,8 +1508,7 @@ static inline uint32_t im2n(uint32_t val) */ uint32_t wolfBoot_image_size(uint8_t *image) { - uint32_t *size = (uint32_t *)(image + sizeof (uint32_t)); - return im2n(*size); + return im2n(WOLFBOOT_HDR_GET_U32(image + WOLFBOOT_HDR_U32_SZ)); } /** @@ -1485,15 +1526,17 @@ uint32_t wolfBoot_image_size(uint8_t *image) */ int wolfBoot_open_image_address(struct wolfBoot_image *img, uint8_t *image) { - uint32_t *magic = (uint32_t *)(image); + /* Read the magic an octet at a time: a uint8_t* cannot be cast to + * uint32_t* where CHAR_BIT != 8 (C28x). */ + uint32_t magic = WOLFBOOT_HDR_GET_U32(image); #ifdef WOLFBOOT_FIXED_PARTITIONS /* The UPDATE slot may be larger than BOOT (monolithic self-update) */ uint32_t part_size = (img->part == PART_UPDATE) ? WOLFBOOT_PARTITION_UPDATE_SIZE : WOLFBOOT_PARTITION_SIZE; #endif - if (*magic != WOLFBOOT_MAGIC) { + if (magic != WOLFBOOT_MAGIC) { wolfBoot_printf("Partition %d header magic 0x%08x invalid at %p\n", - img->part, (unsigned int)*magic, img->hdr); + img->part, (unsigned int)magic, img->hdr); return -1; } img->fw_size = wolfBoot_image_size(image); @@ -1793,7 +1836,7 @@ int wolfBoot_open_self_address(struct wolfBoot_image* img, uint8_t* hdr, XMEMSET(img, 0, sizeof(struct wolfBoot_image)); - magic = *((uint32_t*)hdr); + magic = WOLFBOOT_HDR_GET_U32(hdr); if (magic != WOLFBOOT_MAGIC) { return -1; } @@ -2533,7 +2576,7 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image *img) return -1; /* Invalid hash size for public key hint */ } image_type_size = get_header(img, HDR_IMG_TYPE, &image_type_buf); - if (image_type_size != sizeof(uint16_t)) + if (image_type_size != WOLFBOOT_HDR_U16_SZ) return -1; image_type = (uint16_t)(image_type_buf[0] + (image_type_buf[1] << 8)); if ((image_type & HDR_IMG_TYPE_AUTH_MASK) != HDR_IMG_TYPE_AUTH) diff --git a/src/libwolfboot.c b/src/libwolfboot.c index 3e54b81440..b52172b9e3 100644 --- a/src/libwolfboot.c +++ b/src/libwolfboot.c @@ -1549,7 +1549,6 @@ static void dec_hdr_clear(void) uint32_t wolfBoot_get_blob_version(uint8_t *blob) { uint32_t *volatile version_field = NULL; - uint32_t *magic = NULL; uint8_t *img_bin = blob; uint32_t version = 0; if (blob == NULL) @@ -1560,12 +1559,11 @@ uint32_t wolfBoot_get_blob_version(uint8_t *blob) decrypt_header(blob); img_bin = dec_hdr; #endif - magic = (uint32_t *)img_bin; - if (*magic != WOLFBOOT_MAGIC) + if (WOLFBOOT_HDR_GET_U32(img_bin) != WOLFBOOT_MAGIC) return 0; if (wolfBoot_find_header(img_bin + IMAGE_HEADER_OFFSET, HDR_VERSION, - (void *)&version_field) == sizeof(uint32_t) && version_field) - version = im2n(*version_field); + (void *)&version_field) == WOLFBOOT_HDR_U32_SZ && version_field) + version = im2n(WOLFBOOT_HDR_GET_U32(version_field)); #if defined(EXT_ENCRYPTED) && defined(MMU) dec_hdr_clear(); #endif @@ -1586,7 +1584,6 @@ uint32_t wolfBoot_get_blob_version(uint8_t *blob) uint16_t wolfBoot_get_blob_type(uint8_t *blob) { uint16_t *volatile type_field = NULL; - uint32_t *magic = NULL; uint8_t *img_bin = blob; uint16_t type = 0; #if defined(EXT_ENCRYPTED) && defined(MMU) @@ -1595,12 +1592,11 @@ uint16_t wolfBoot_get_blob_type(uint8_t *blob) decrypt_header(blob); img_bin = dec_hdr; #endif - magic = (uint32_t *)img_bin; - if (*magic != WOLFBOOT_MAGIC) + if (WOLFBOOT_HDR_GET_U32(img_bin) != WOLFBOOT_MAGIC) return 0; if (wolfBoot_find_header(img_bin + IMAGE_HEADER_OFFSET, HDR_IMG_TYPE, - (void *)&type_field) == sizeof(uint16_t) && type_field) - type = im2ns(*type_field); + (void *)&type_field) == WOLFBOOT_HDR_U16_SZ && type_field) + type = im2ns(WOLFBOOT_HDR_GET_U16(type_field)); #if defined(EXT_ENCRYPTED) && defined(MMU) dec_hdr_clear(); #endif @@ -1690,7 +1686,7 @@ uint8_t* wolfBoot_get_self_header(void) ext_flash_read((uintptr_t)WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS, hdr_buf, IMAGE_HEADER_SIZE); - magic = *((uint32_t*)hdr_buf); + magic = WOLFBOOT_HDR_GET_U32(hdr_buf); if (magic != WOLFBOOT_MAGIC) { return NULL; } @@ -1698,7 +1694,7 @@ uint8_t* wolfBoot_get_self_header(void) return hdr_buf; #else uint8_t* hdr = (uint8_t*)WOLFBOOT_PARTITION_SELF_HEADER_ADDRESS; - uint32_t magic = *((uint32_t*)hdr); + uint32_t magic = WOLFBOOT_HDR_GET_U32(hdr); if (magic != WOLFBOOT_MAGIC) { return NULL; diff --git a/src/string.c b/src/string.c index b9c94491ca..bf31b5669c 100644 --- a/src/string.c +++ b/src/string.c @@ -288,6 +288,15 @@ size_t strlen(const char *s) #define RAMFUNCTION #pragma section FRAM #endif +#if defined(__TMS320C28XX__) + /* On the C28x, the C-runtime device init copies the .TI.ramfunc section into + * RAM using memcpy() at startup. memcpy() must therefore stay in flash - if + * it were a RAMFUNCTION it would live in the not-yet-copied .TI.ramfunc region + * and the copy would call an uninitialized RAM address, ITRAPing on a cold + * flash boot (a JTAG load masks this by pre-copying every section). */ + #undef RAMFUNCTION + #define RAMFUNCTION +#endif void RAMFUNCTION *memcpy(void *dst, const void *src, size_t n) { size_t i; diff --git a/test-app/app_f28p55x.c b/test-app/app_f28p55x.c new file mode 100644 index 0000000000..59fead8f85 --- /dev/null +++ b/test-app/app_f28p55x.c @@ -0,0 +1,88 @@ +/* app_f28p55x.c + * + * Minimal wolfBoot test application for the TI LAUNCHXL-F28P55X + * (TMS320F28P550SJ, C2000 C28x DSP). + * + * This is the XIP payload wolfBoot verifies and branches to. It is linked so + * its codestart lands at WOLFBOOT_PARTITION_BOOT_ADDRESS + IMAGE_HEADER_SIZE + * (0xA0100, see test-app/f28p55x_app.cmd), which is exactly the firmware base + * do_boot() jumps to. On boot it prints a banner on SCIA (GPIO28/29, the + * XDS110 virtual COM, 115200 8N1) so a successful verify+jump is visible. + * + * 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 "driverlib.h" +#include "device.h" + +static void sci_init(void) +{ + GPIO_setPinConfig(DEVICE_GPIO_CFG_SCIRXDA); + GPIO_setDirectionMode(DEVICE_GPIO_PIN_SCIRXDA, GPIO_DIR_MODE_IN); + GPIO_setPadConfig(DEVICE_GPIO_PIN_SCIRXDA, GPIO_PIN_TYPE_STD); + GPIO_setQualificationMode(DEVICE_GPIO_PIN_SCIRXDA, GPIO_QUAL_ASYNC); + + GPIO_setPinConfig(DEVICE_GPIO_CFG_SCITXDA); + GPIO_setDirectionMode(DEVICE_GPIO_PIN_SCITXDA, GPIO_DIR_MODE_OUT); + GPIO_setPadConfig(DEVICE_GPIO_PIN_SCITXDA, GPIO_PIN_TYPE_STD); + GPIO_setQualificationMode(DEVICE_GPIO_PIN_SCITXDA, GPIO_QUAL_ASYNC); + + SCI_performSoftwareReset(SCIA_BASE); + SCI_setConfig(SCIA_BASE, DEVICE_LSPCLK_FREQ, 115200, + (SCI_CONFIG_WLEN_8 | SCI_CONFIG_STOP_ONE | SCI_CONFIG_PAR_NONE)); + SCI_resetChannels(SCIA_BASE); + SCI_resetRxFIFO(SCIA_BASE); + SCI_resetTxFIFO(SCIA_BASE); + SCI_enableFIFO(SCIA_BASE); + SCI_enableModule(SCIA_BASE); + SCI_performSoftwareReset(SCIA_BASE); +} + +static void sci_puts(const char *s) +{ + while (*s != '\0') { + if (*s == '\n') + SCI_writeCharBlockingFIFO(SCIA_BASE, (uint16_t)'\r'); + SCI_writeCharBlockingFIFO(SCIA_BASE, (uint16_t)(*s & 0xFF)); + s++; + } +} + +/* The signed wolfBoot header lives at 0xA0000 (see f28p55x_app.cmd); reference + * it so the linker retains the .wolfboot_hdr section in the image. */ +extern const unsigned int wolfboot_header[]; + +int main(void) +{ + volatile long d; + volatile unsigned int hdr0 = wolfboot_header[0]; + (void)hdr0; + + /* wolfBoot already configured the clock (150 MHz PLL), flash wait states and + * the SCIA pins before the XIP handoff. Re-running Device_init() here would + * re-lock the PLL, and that clock transient garbles the SCI across the + * handoff -- so just (re)initialize the console and run. */ + sci_init(); + + for (;;) { + sci_puts("hello from the wolfBoot app on F28P55x\n"); + for (d = 0; d < 4000000; d++) + ; + } +} diff --git a/test-app/f28p55x_app.cmd b/test-app/f28p55x_app.cmd new file mode 100644 index 0000000000..ec5fbcb403 --- /dev/null +++ b/test-app/f28p55x_app.cmd @@ -0,0 +1,67 @@ +/* f28p55x_app.cmd (TI cl2000 linker command file) + * + * Linker layout for the wolfBoot test application on the TI TMS320F28P550SJ. + * The application executes in place from the BOOT partition (flash bank1). Its + * codestart is placed at 0xA0100 = WOLFBOOT_PARTITION_BOOT_ADDRESS (0xA0000) + * + IMAGE_HEADER_SIZE (256 words), which is the firmware base wolfBoot's + * do_boot() branches to. The 256-word signed header occupies 0xA0000..0xA00FF + * (programmed separately from the c2000_flashimg.py header blob). + * + * Copyright (C) 2026 wolfSSL Inc. GPLv3 - see project headers. + */ + +-stack 0x2000 +-heap 0x1000 + +MEMORY +{ + HDR : origin = 0x0A0000, length = 0x000100 /* wolfBoot signed header (256 cells) */ + BEGIN : origin = 0x0A0100, length = 0x000002 /* app codestart */ + + BOOT_RSVD : origin = 0x000002, length = 0x000126 + RAMM0 : origin = 0x000128, length = 0x0002D8 + RAMM1 : origin = 0x000400, length = 0x000400 + + RAMLS_STACK : origin = 0x008000, length = 0x002000 /* RAMLS0-3, stack */ + RAMGS_RAMCODE : origin = 0x00A000, length = 0x002000 /* .TI.ramfunc */ + RAMGS_HEAP : origin = 0x00C000, length = 0x002000 /* RAMGS0-1, heap */ + RAMGS_HI : origin = 0x010000, length = 0x004000 /* RAMGS2-3 */ + RAMLS_HI : origin = 0x014000, length = 0x004000 /* RAMLS8-9 */ + + /* Application flash: rest of BOOT partition bank1 (after codestart), plus + * bank2 if needed. Header cells occupy the low 256 words of bank1. */ + APP_FLASH1 : origin = 0x0A0102, length = 0x01FEFE /* bank1 remainder */ + APP_FLASH2 : origin = 0x0C0000, length = 0x020000 /* bank2 */ + + RESET : origin = 0x3FFFC0, length = 0x000002 +} + +SECTIONS +{ + /* wolfBoot signed header at the BOOT partition base (0xA0000), one octet + * per 16-bit cell; the app codestart follows at 0xA0100 (= fw_base). */ + .wolfboot_hdr : > HDR + codestart : > BEGIN + /* Device_init copies these to RAM (RamfuncsLoadStart -> RamfuncsRunStart). */ + .TI.ramfunc : LOAD = APP_FLASH1, + RUN = RAMGS_RAMCODE, + LOAD_START(RamfuncsLoadStart), + LOAD_SIZE(RamfuncsLoadSize), + LOAD_END(RamfuncsLoadEnd), + RUN_START(RamfuncsRunStart), + RUN_SIZE(RamfuncsRunSize), + RUN_END(RamfuncsRunEnd), + ALIGN(8) + .text : >> APP_FLASH1 | APP_FLASH2, ALIGN(8) + .cinit : > APP_FLASH1 | APP_FLASH2, ALIGN(8) + .switch : > APP_FLASH1 | APP_FLASH2, ALIGN(8) + .init_array : > APP_FLASH1 | APP_FLASH2, ALIGN(8) + .const : >> APP_FLASH1 | APP_FLASH2, ALIGN(8) + .reset : > RESET, TYPE = DSECT + + .stack : > RAMLS_STACK + .bss : >> RAMGS_HI | RAMLS_HI + .bss:output : > RAMGS_HI + .data : >> RAMGS_HI | RAMLS_HI + .sysmem : > RAMGS_HEAP +} diff --git a/test-app/f28p55x_sign.sh b/test-app/f28p55x_sign.sh new file mode 100755 index 0000000000..ce28258d11 --- /dev/null +++ b/test-app/f28p55x_sign.sh @@ -0,0 +1,92 @@ +#!/bin/bash +# f28p55x_sign.sh +# +# Build, sign, and package the wolfBoot C28x test application for the +# TI LAUNCHXL-F28P55X (TMS320F28P550SJ). This documents the full MVP flow: +# compile the XIP app, extract its firmware words as the host octet stream, +# sign it, and synthesize the octet-per-cell header blob. +# +# Prereqs (override via env): +# CGT_ROOT TI C2000 codegen install (dir containing bin/cl2000) +# C2000WARE C2000Ware install (default ~/ti/C2000Ware_26_01_00_00) +# WOLFBOOT wolfBoot root (default: parent of this script's dir) +# +# Copyright (C) 2026 wolfSSL Inc. GPLv3 - see project headers. +set -e + +HERE="$(cd "$(dirname "$0")" && pwd)" +WOLFBOOT="${WOLFBOOT:-$(cd "$HERE/.." && pwd)}" +C2000WARE="${C2000WARE:-$HOME/ti/C2000Ware_26_01_00_00}" +: "${CGT_ROOT:?Set CGT_ROOT to a TI C2000 codegen install (dir with bin/cl2000)}" + +CL="$CGT_ROOT/bin/cl2000" +HEX="$CGT_ROOT/bin/hex2000" +DEV="$C2000WARE/device_support/f28p55x" +DRV="$C2000WARE/driverlib/f28p55x/driverlib" +OUT="$HERE/out_f28p55x" +KEY="$WOLFBOOT/wolfboot_signing_private_key.der" +SIGN="$WOLFBOOT/tools/keytools/sign" +CONV="$WOLFBOOT/tools/scripts/c2000_flashimg.py" + +BOOT_ADDR=0xA0000 # WOLFBOOT_PARTITION_BOOT_ADDRESS +FW_ADDR=0xA0100 # BOOT_ADDR + IMAGE_HEADER_SIZE(256 words) +HDR_SIZE=256 + +mkdir -p "$OUT" + +echo "[1/5] Compile + link the XIP app (codestart at $FW_ADDR)" +"$CL" -v28 --float_support=fpu32 --tmu_support=tmu1 --abi=eabi -O2 \ + --gen_func_subsections=on \ + -D_LAUNCHXL_F28P55X -D_FLASH \ + -I"$CGT_ROOT/include" -I"$DRV" \ + -I"$DEV/common/include" -I"$DEV/headers/include" \ + "$HERE/app_f28p55x.c" \ + "$DEV/common/source/device.c" \ + "$DEV/common/source/f28p55x_codestartbranch.asm" \ + -z --reread_libs --warn_sections \ + -i"$CGT_ROOT/lib" -i"$DRV/ccs/Release" \ + -m "$OUT/app.map" \ + "$HERE/f28p55x_app.cmd" \ + --output_file="$OUT/app.out" \ + -l driverlib.lib -l libc.a + +echo "[2/5] Extract the firmware region as a flat little-endian word image" +# hex2000 -> flat binary of the firmware address range. Each C28x 16-bit word +# is emitted as 2 little-endian host bytes (2 bytes/word), which is exactly the +# octet stream `sign` must hash. VERIFY these hex2000 options against your +# installed TI utility version; the goal is a raw binary of [FW_ADDR..end). +"$HEX" "$OUT/app.out" -o "$OUT/app_fw_words.bin" \ + --memwidth=16 --romwidth=16 --binary \ + --fill=0xFFFF || { + echo "hex2000 flat-binary extraction failed - adjust options for your" + echo "toolchain version (or dump the firmware region another way) so that" + echo "$OUT/app_fw_words.bin is a raw LE 16-bit-word image of [$FW_ADDR..end)." + exit 1 +} + +echo "[3/5] Firmware word image -> host octet stream for signing" +python3 "$CONV" fw2oct "$OUT/app_fw_words.bin" "$OUT/app_fw.oct" + +echo "[4/5] Sign the firmware octet stream (ECC P-256 + SHA-256)" +"$SIGN" --ecc256 --sha256 "$OUT/app_fw.oct" "$KEY" 1 +# sign writes app_fw_v1_signed.bin next to the input +SIGNED="$OUT/app_fw_v1_signed.bin" + +echo "[5/5] Header blob (octet-per-cell) for load address $BOOT_ADDR" +python3 "$CONV" hdr2cells "$SIGNED" "$OUT/header_cells.bin" \ + --header-size "$HDR_SIZE" --addr "$BOOT_ADDR" + +cat <> 8). +# +# 2) hdr2cells - from the signed image (`sign` output = header octets + +# firmware octets), take the fixed-size header and expand each +# header octet into its own 16-bit flash cell (value = octet, +# high byte 0). This "header blob" is flashed at the BOOT +# partition base; the application .out is flashed natively (its +# codestart is linked at BOOT_ADDRESS + IMAGE_HEADER_SIZE), so no +# firmware repack is needed. +# +# Copyright (C) 2026 wolfSSL Inc. GPLv3 - see project headers. + +import argparse +import struct +import sys + + +def read_file(path): + with open(path, "rb") as f: + return f.read() + + +def write_file(path, data): + with open(path, "wb") as f: + f.write(data) + + +def fw2oct(args): + """Firmware native-word image -> host octet stream (low, high per word).""" + words = read_file(args.infile) + if len(words) % 2 != 0: + sys.stderr.write("error: input length %d is not a whole number of " + "16-bit words\n" % len(words)) + return 1 + out = bytearray() + for i in range(0, len(words), 2): + # Input is a little-endian 16-bit-word image (2 bytes/word). + w = struct.unpack_from("> 8) & 0xFF) # high octet + write_file(args.outfile, out) + sys.stderr.write("fw2oct: %d words -> %d octets -> %s\n" + % (len(words) // 2, len(out), args.outfile)) + return 0 + + +def hdr2cells(args): + """Signed image header octets -> C28x flash cells (one octet per word).""" + signed = read_file(args.signed) + hdr_sz = args.header_size + if len(signed) < hdr_sz: + sys.stderr.write("error: signed image (%d) shorter than header size " + "(%d)\n" % (len(signed), hdr_sz)) + return 1 + header = signed[:hdr_sz] + out = bytearray() + for octet in header: + # Each header octet occupies its own 16-bit flash cell (high byte 0), + # so wolfBoot's octet parser reads it back byte-identically. + out += struct.pack(" %d-word blob for load " + "address 0x%X -> %s\n" + % (hdr_sz, hdr_sz, args.addr, args.outfile)) + return 0 + + +def main(): + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + sub = ap.add_subparsers(dest="cmd", required=True) + + p1 = sub.add_parser("fw2oct", + help="firmware word-image -> octet stream for sign") + p1.add_argument("infile", help="firmware native-word image (LE 16-bit words)") + p1.add_argument("outfile", help="output octet stream to feed to sign") + p1.set_defaults(func=fw2oct) + + p2 = sub.add_parser("hdr2cells", + help="signed image -> header blob (octet-per-cell)") + p2.add_argument("signed", help="signed image (sign output)") + p2.add_argument("outfile", help="output header cell blob (LE 16-bit words)") + p2.add_argument("--header-size", type=lambda x: int(x, 0), default=256, + help="IMAGE_HEADER_SIZE in octets (default 256 for ECC256)") + p2.add_argument("--addr", type=lambda x: int(x, 0), default=0xA0000, + help="BOOT partition base word address (default 0xA0000)") + p2.set_defaults(func=hdr2cells) + + args = ap.parse_args() + return args.func(args) + + +if __name__ == "__main__": + sys.exit(main())