From 1372e3e8ed968b2b707d133992f8468aaa2e4b53 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 | 12 ++++++++++-- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/arch.mk b/arch.mk index 867e9ac4f6..8e7e74ec1b 100644 --- a/arch.mk +++ b/arch.mk @@ -1083,10 +1083,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 96605cec0a..0856ea98b9 100644 --- a/include/user_settings.h +++ b/include/user_settings.h @@ -47,12 +47,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 ff05573205..97999185a8 100644 --- a/tools/test.mk +++ b/tools/test.mk @@ -1246,7 +1246,12 @@ test-size-all: make keysclean make test-size SIGN=ECC384 LIMIT=19604 NO_ARM_ASM=1 make clean - make test-size SIGN=ECC384 NO_ASM=1 LIMIT=15300 NO_ARM_ASM=1 + # 15300 was exactly the measured size, i.e. zero headroom - the only + # entry here without any. The lib/wolfssl bump this branch needs for + # CHAR_BIT!=8 support grows the no-ASM SP-math ECC384 path ~12 bytes + # (ECC384 with ASM is unchanged), so give it the same ~12 byte margin + # the other entries carry. + make test-size SIGN=ECC384 NO_ASM=1 LIMIT=15324 NO_ARM_ASM=1 make keysclean make test-size SIGN=ED448 LIMIT=14252 NO_ARM_ASM=1 make keysclean @@ -1275,5 +1280,8 @@ test-size-all: LIMIT=8768 NO_ARM_ASM=1 make keysclean make clean - make test-size SIGN=ML_DSA ML_DSA_LEVEL=2 LIMIT=19578 \ + # Same reason as the ECC384 NO_ASM entry above: the lib/wolfssl bump this + # branch needs rewrites wc_mldsa.c substantially (~1400 net lines), which + # costs ~48 bytes here. No wolfBoot ML-DSA code changed. + make test-size SIGN=ML_DSA ML_DSA_LEVEL=2 LIMIT=19626 \ IMAGE_SIGNATURE_SIZE=2420 IMAGE_HEADER_SIZE?=8192 From 31f574d6816e7ba74ab1120be289ff4b062ee8d7 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 | 2 + 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, 1160 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 e85780869f..5a063ed252 100644 --- a/.gitignore +++ b/.gitignore @@ -494,3 +494,5 @@ tools/unit-tests/zynq_write_extract.h tools/unit-tests/unit-sign-header-failure tools/unit-tests/unit-sign-hybrid-keyload tools/unit-tests/unit-update-ram-uboot +# TI cl2000 (ARCH=C2000) intermediate assembly listings left in the repo root +/*.asm diff --git a/Makefile b/Makefile index e69fec60c7..5ee4abbd0b 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) @@ -377,6 +387,12 @@ ifeq ($(TARGET),tegra234) 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 @@ -722,6 +738,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 8e7e74ec1b..b41f141045 100644 --- a/arch.mk +++ b/arch.mk @@ -2080,6 +2080,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 dcc5892848..d317396be8 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -58,6 +58,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) @@ -8977,3 +8978,45 @@ Boot success marked. Version: 1 | `FLAGS_HOME` | Keep boot flags in internal flash (required when `EXT_FLASH=1`). | | `MAX3266X_TPU` | Enable TPU hardware SHA256 acceleration (requires `MSDK_DIR`). | | `MAX3266X_OLD` | Build TPU acceleration against the older, deprecated Maxim SDK tree instead of the modern MSDK. | + +## 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 0856ea98b9..4eb5610744 100644 --- a/include/user_settings.h +++ b/include/user_settings.h @@ -43,6 +43,33 @@ #define HAVE_EMPTY_AGGREGATES 0 #define HAVE_ANONYMOUS_INLINE_AGGREGATES 0 +/* 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 */ @@ -482,8 +509,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 f9bf6bfe84..e98f795d55 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) @@ -1103,7 +1113,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; @@ -1140,7 +1150,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; @@ -1212,7 +1254,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; @@ -1334,7 +1376,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; @@ -1462,8 +1504,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)); } /** @@ -1481,15 +1522,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); @@ -1789,7 +1832,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; } @@ -2507,7 +2550,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 6274550109..10281b7927 100644 --- a/src/libwolfboot.c +++ b/src/libwolfboot.c @@ -1537,7 +1537,6 @@ static int decrypt_header(uint8_t *src) uint32_t wolfBoot_get_blob_version(uint8_t *blob) { uint32_t *volatile version_field = NULL; - uint32_t *magic = NULL; uint8_t *img_bin = blob; if (blob == NULL) return 0; @@ -1547,14 +1546,13 @@ 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)) + (void *)&version_field) != WOLFBOOT_HDR_U32_SZ) return 0; if (version_field) - return im2n(*version_field); + return im2n(WOLFBOOT_HDR_GET_U32(version_field)); return 0; } @@ -1572,7 +1570,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; #if defined(EXT_ENCRYPTED) && defined(MMU) if (wolfBoot_initialize_encryption() < 0) @@ -1580,14 +1577,13 @@ 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)) + (void *)&type_field) != WOLFBOOT_HDR_U16_SZ) return 0; if (type_field) - return im2ns(*type_field); + return im2ns(WOLFBOOT_HDR_GET_U16(type_field)); return 0; } @@ -1673,7 +1669,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; } @@ -1681,7 +1677,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())