diff --git a/src/port/stm32h563/Makefile b/src/port/stm32h563/Makefile index 8bcadf2e..ca4ea2f2 100644 --- a/src/port/stm32h563/Makefile +++ b/src/port/stm32h563/Makefile @@ -30,6 +30,11 @@ ENABLE_MQTT ?= 0 # MQTT Broker: set ENABLE_MQTT_BROKER=1 to include wolfMQTT broker (requires TLS) ENABLE_MQTT_BROKER ?= 0 +# TFTP client demo: set ENABLE_TFTP=1 to include the wolfIP TFTP client +# that downloads a firmware image at boot and stages it into the +# wolfBoot update partition. TZEN=0 only. +ENABLE_TFTP ?= 0 + # FreeRTOS integration: set FREERTOS=1 to run the HTTPS server from a # FreeRTOS task using the blocking BSD socket wrapper layer. FREERTOS ?= 0 @@ -328,6 +333,33 @@ endif endif # ENABLE_MQTT_BROKER +# ----------------------------------------------------------------------------- +# TFTP Client Demo (wolfIP TFTP) - one-shot RRQ GET into wolfBoot update partition +# ----------------------------------------------------------------------------- +ifeq ($(ENABLE_TFTP),1) + +ifeq ($(TZEN),1) + $(error ENABLE_TFTP=1 currently only supports TZEN=0) +endif + +CFLAGS += -DENABLE_TFTP -DWOLFIP_ENABLE_TFTP=1 +CFLAGS += -I$(ROOT)/src/tftp + +# wolfBoot partition layout. Defaults match +# ../wolfboot/config/examples/stm32h5-no-tz.config. Override on the +# command line to match a different wolfBoot config. +WOLFBOOT_PARTITION_UPDATE_ADDRESS ?= 0x08100000 +WOLFBOOT_PARTITION_SIZE ?= 0xA0000 +WOLFBOOT_SECTOR_SIZE ?= 0x4000 +CFLAGS += -DWOLFBOOT_PARTITION_UPDATE_ADDRESS=$(WOLFBOOT_PARTITION_UPDATE_ADDRESS)UL +CFLAGS += -DWOLFBOOT_PARTITION_SIZE=$(WOLFBOOT_PARTITION_SIZE)UL +CFLAGS += -DWOLFBOOT_SECTOR_SIZE=$(WOLFBOOT_SECTOR_SIZE)UL + +SRCS += tftp_client_demo.c +SRCS += $(ROOT)/src/tftp/wolftftp.c + +endif # ENABLE_TFTP + # ----------------------------------------------------------------------------- # Build rules # ----------------------------------------------------------------------------- @@ -432,6 +464,7 @@ help: @echo " ENABLE_SSH=1 Enable SSH server (requires TLS + wolfSSH)" @echo " ENABLE_MQTT=1 Enable MQTT client (requires TLS + wolfMQTT)" @echo " ENABLE_MQTT_BROKER=1 Enable MQTT broker (requires TLS + wolfMQTT)" + @echo " ENABLE_TFTP=1 Enable TFTP client demo (RRQ GET into wolfBoot update partition; TZEN=0 only)" @echo " FREERTOS=1 Run HTTPS in a FreeRTOS task via BSD socket wrappers" @echo " FREERTOS_PATH= Path to FreeRTOS-Kernel (default: $(ROOT)/../FreeRTOS_Kernel)" @echo " WOLFSSL_ROOT= Path to wolfSSL (default: ../wolfssl)" @@ -448,6 +481,7 @@ help: @echo " make ENABLE_TLS=1 ENABLE_TLS_CLIENT=1 # TLS client (Google test)" @echo " make ENABLE_TLS=1 ENABLE_MQTT=1 # TLS + MQTT client" @echo " make ENABLE_TLS=1 ENABLE_MQTT_BROKER=1 # TLS + MQTT broker" + @echo " make ENABLE_TFTP=1 # TFTP client demo (one-shot RRQ at boot)" @echo " make FREERTOS=1 ENABLE_HTTPS=1 # FreeRTOS HTTPS via BSD sockets" @echo " make ENABLE_TLS=1 ENABLE_HTTPS=1 ENABLE_SSH=1 ENABLE_MQTT=1 ENABLE_MQTT_BROKER=1 # Full featured" @echo "" diff --git a/src/port/stm32h563/README.md b/src/port/stm32h563/README.md index a9bff301..dec9d8eb 100644 --- a/src/port/stm32h563/README.md +++ b/src/port/stm32h563/README.md @@ -893,6 +893,183 @@ mqttclient -h -p 8883 -t -A /tmp/wolfip_cert.pem \ | `../certs.h` | Embedded ECC P-256 cert/key (shared with TLS/HTTPS) | | `user_settings.h` | wolfMQTT broker compile-time config | +## TFTP Client + +When built with `ENABLE_TFTP=1`, the device runs a one-shot wolfIP TFTP +RRQ (GET) client at boot. After the network comes up, the client fetches +a single file from a host-side TFTP server and stages the bytes directly +into the wolfBoot update partition. On success the wolfBoot update flag +is written to the trailer of the update partition; the next reset hands +control to wolfBoot, which can verify the staged image and swap it in. + +`ENABLE_TFTP=1` is **TZEN=0 only** in this release. The build errors out +if `TZEN=1` is also set. + +### Building TFTP Mode + +```bash +cd src/port/stm32h563 +make clean +make ENABLE_TFTP=1 +``` + +The Makefile pulls in `../../tftp/wolftftp.c` from wolfIP's TFTP module +and `tftp_client_demo.c` from this port, and sets +`-DWOLFIP_ENABLE_TFTP=1`. No external dependencies (no wolfSSL, +wolfSSH, wolfMQTT). The demo can be combined with the other services - +e.g. `make ENABLE_TFTP=1 ENABLE_HTTPS=1 ENABLE_SSH=1`. + +### Configuration + +The defaults in `config.h` and the Makefile target a host on the +`192.168.12.0/24` static-fallback subnet. Override them on the command +line via `EXTRA_CFLAGS`: + +```bash +make ENABLE_TFTP=1 \ + EXTRA_CFLAGS='-DTFTP_SERVER_IP=\"10.0.4.24\" -DTFTP_FETCH_FILENAME=\"app_v2_signed.bin\"' +``` + +| Setting | Default | Where | +|---------|---------|-------| +| `TFTP_SERVER_IP` | `"192.168.12.10"` | `config.h` | +| `TFTP_FETCH_FILENAME` | `"app_v2_signed.bin"` | `config.h` | +| `WOLFBOOT_PARTITION_UPDATE_ADDRESS` | `0x08100000` | `Makefile` (matches `stm32h5-no-tz.config`) | +| `WOLFBOOT_PARTITION_SIZE` | `0xA0000` (640 KB) | `Makefile` | +| `WOLFBOOT_SECTOR_SIZE` | `0x4000` (16 KB) | `Makefile` | +| TFTP client local UDP port | `20100` | `tftp_client_demo.c` | +| TFTP block size | `1428` | `tftp_client_demo.c` | +| TFTP window size | `8` | `tftp_client_demo.c` | + +Override the partition layout on the command line if your wolfBoot is +configured differently: + +```bash +make ENABLE_TFTP=1 WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x08080000 \ + WOLFBOOT_PARTITION_SIZE=0x70000 +``` + +### Host Setup (`tftpd-hpa`) + +```bash +sudo apt-get install tftpd-hpa +echo "TFTP test fixture" | sudo tee /srv/tftp/app_v2_signed.bin +sudo systemctl restart tftpd-hpa + +# Sanity check from another host on the same LAN: +tftp -c get app_v2_signed.bin +``` + +`/etc/default/tftpd-hpa` (default Ubuntu/Debian install) listens on +`:69` and serves `/srv/tftp`. The TFTP daemon accepts files mode `0666` +by default; restart it after dropping a new fixture. + +### Expected Serial Output (TFTP Mode) + +``` +DHCP configuration received: + IP: 10.0.4.116 + Mask: 255.255.255.0 + GW: 10.0.4.1 +Creating TCP socket on port 7... +Initializing TFTP client demo... + TFTP server: 10.0.4.24 + TFTP file: test.txt +TFTP: RRQ sent +Entering main loop. Ready for connections! + TCP Echo: port 7 +TFTP: open update partition (erase on demand) +TFTP: programmed bytes=44 +TFTP: update flag set, reset to apply +TFTP: close status=0 +``` + +`programmed bytes=N` matches the file's size on the host. `close +status=0` means success. Negative values map to `WOLFTFTP_ERR_*` codes +in `../../tftp/wolftftp.h` (`-1000` IO, `-1001` STATE, `-1002` PACKET, +`-1003` TIMEOUT, `-1004` SIZE, `-1005` VERIFY, `-1006` UNSUPPORTED, +`-1007` TID). + +### Verifying the Staged Image + +Halt the target and dump the update partition to confirm the bytes +match the host file: + +```bash +$OPENOCD -s $OPENOCD_SCRIPTS -f interface/stlink-dap.cfg \ + -c "adapter serial $H5_SN" \ + -f target/stm32h5x.cfg \ + -c "init; halt" \ + -c "dump_image /tmp/h5_update_head.bin 0x08100000 64" \ + -c "dump_image /tmp/h5_update_tail.bin 0x0819FF00 256" \ + -c "resume; shutdown" + +# Compare: +xxd /srv/tftp/app_v2_signed.bin | head +xxd /tmp/h5_update_head.bin + +# The last byte of the partition (0x0819FFFF) is the wolfBoot update +# trigger - it should read 0x70 (IMG_STATE_UPDATING): +tail -c 1 /tmp/h5_update_tail.bin | xxd +``` + +### How It Works + +The demo uses the wolfIP UDP socket API directly (no BSD wrapper) and +plugs the TFTP state machine's `transport` callback into +`wolfIP_sock_sendto()`. The main loop drains the UDP socket with +`wolfIP_sock_recvfrom()`, hands incoming datagrams to +`wolftftp_client_receive()`, and lets `wolftftp_client_poll()` drive +retransmits and timeouts. + +The `io.write` callback in `tftp_client_demo.c` buffers each incoming +DATA block into a 16-byte staging qword. When the staging buffer fills +up, the demo: + +1. Lazily erases the 8 KB page that holds the destination address (if + it has not already been erased on this transfer). +2. Programs the 16-byte quad-word at the destination address. STM32H5 + flash programs in 128-bit (16-byte) quanta with per-qword ECC; each + qword can only be programmed once between erases. + +On successful transfer completion the demo writes +`IMG_STATE_UPDATING` (`0x70`) to the very last byte of the update +partition (`WOLFBOOT_PARTITION_UPDATE_ADDRESS + WOLFBOOT_PARTITION_SIZE +- 1`). That single byte is the wolfBoot update trigger; on the next +reset wolfBoot reads the trailer and swaps in the staged image. + +### Limitations / Out of Scope + +- **wolfBoot is not bundled in the demo's flash image.** The current + `target.ld` places the application at `0x08000000`, so when the + unmodified demo boots there is no wolfBoot bootloader to consume the + update flag. The TFTP staging side is correct and hardware-verified; + the round-trip "fetch -> reset -> v2 boots" requires shifting the + app linker script to `0x08060000`, installing a wolfBoot built from + `config/examples/stm32h5-no-tz.config`, and re-packing `factory.bin` + as `wolfboot_padded.bin + signed app`. That packaging is out of scope + for the `ENABLE_TFTP=1` flag. +- **TZEN=1 is not supported.** The flash HAL uses the non-secure + register view (`FLASH_NS_*` at `0x40022000`). A TrustZone-aware + version would need the secure aliases plus + `hal_tz_claim_nonsecure_area()` bracketing each program/erase. +- **Client only, RRQ only.** No WRQ (PUT), and no on-target TFTP + server. +- **No authentication or integrity check on the wire.** TFTP is + unauthenticated by design. The staged image is verified by wolfBoot + on the next boot via its signature check; that catches a tampered + or corrupted image but does not protect the partition until the next + reset window. + +### TFTP Files + +| File | Description | +|------|-------------| +| `tftp_client_demo.c` | UDP glue + STM32H5 flash HAL + wolfBoot trigger | +| `tftp_client_demo.h` | `_start`/`_poll`/`_status` API | +| `../../tftp/wolftftp.c` | Library: TFTP state machine (RFC 1350 + RFC 2347 options) | +| `../../tftp/wolftftp.h` | Library: public API (`wolftftp_client_*`, `wolftftp_server_*`) | + ## Files | File | Description | @@ -915,6 +1092,8 @@ mqttclient -h -p 8883 -t -A /tmp/wolfip_cert.pem \ | `ssh_server.c/h` | SSH shell server (SSH builds only) | | `ssh_keys.h` | Embedded SSH host key (SSH builds only) | | `mqtt_client.c/h` | MQTT client state machine (MQTT builds only) | +| `tftp_client_demo.c/h` | TFTP client demo + inline H5 flash HAL (TFTP builds only) | +| `../../tftp/wolftftp.c` | wolfIP TFTP library (TFTP builds only) | | `../wolfssl_io.c` | wolfSSL I/O callbacks for wolfIP (TLS builds only) | | `../wolfssh_io.c` | wolfSSH I/O callbacks for wolfIP (SSH builds only) | | `../wolfmqtt_io.c` | wolfMQTT I/O callbacks for wolfIP (MQTT builds only) | diff --git a/src/port/stm32h563/config.h b/src/port/stm32h563/config.h index 099670c4..2b2e294c 100644 --- a/src/port/stm32h563/config.h +++ b/src/port/stm32h563/config.h @@ -65,4 +65,14 @@ #define DHCP_REQUEST_RETRIES 1 #endif +/* TFTP client demo (ENABLE_TFTP=1). On boot, after DHCP, the demo fetches + * TFTP_FETCH_FILENAME from a tftpd-hpa running on TFTP_SERVER_IP and + * stages it into the wolfBoot update partition. */ +#ifndef TFTP_SERVER_IP +#define TFTP_SERVER_IP "192.168.12.10" +#endif +#ifndef TFTP_FETCH_FILENAME +#define TFTP_FETCH_FILENAME "app_v2_signed.bin" +#endif + #endif /* WOLF_CONFIG_H */ diff --git a/src/port/stm32h563/main.c b/src/port/stm32h563/main.c index ff4f0d4e..79534278 100644 --- a/src/port/stm32h563/main.c +++ b/src/port/stm32h563/main.c @@ -65,6 +65,10 @@ extern volatile unsigned long broker_uptime_sec; #endif +#ifdef ENABLE_TFTP +#include "tftp_client_demo.h" +#endif + #ifdef ENABLE_TLS_CLIENT /* Google IP for TLS client test (run: dig +short google.com) */ @@ -1074,6 +1078,22 @@ int main(void) } #endif +#ifdef ENABLE_TFTP + uart_puts("Initializing TFTP client demo...\n"); + { + ip4 srv = atoip4(TFTP_SERVER_IP); + uart_puts(" TFTP server: "); + uart_putip4(srv); + uart_puts("\n TFTP file: "); + uart_puts(TFTP_FETCH_FILENAME); + uart_puts("\n"); + if (tftp_client_demo_start(IPStack, srv, TFTP_FETCH_FILENAME, + uart_puts) < 0) { + uart_puts("ERROR: TFTP client init failed\n"); + } + } +#endif + uart_puts("Entering main loop. Ready for connections!\n"); uart_puts(" TCP Echo: port 7\n"); #ifdef ENABLE_TLS_CLIENT @@ -1103,6 +1123,10 @@ int main(void) ssh_server_poll(); #endif +#ifdef ENABLE_TFTP + tftp_client_demo_poll(tick); +#endif + #ifdef ENABLE_MQTT /* Poll MQTT client */ mqtt_client_poll(); diff --git a/src/port/stm32h563/tftp_client_demo.c b/src/port/stm32h563/tftp_client_demo.c new file mode 100644 index 00000000..0632abe4 --- /dev/null +++ b/src/port/stm32h563/tftp_client_demo.c @@ -0,0 +1,593 @@ +/* tftp_client_demo.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP 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. + * + * wolfIP 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 + * + * Wires the wolfIP TFTP client into the STM32H5 demo. Performs a + * one-shot RRQ GET of a host-served firmware image and stages the + * bytes into the wolfBoot update partition at + * WOLFBOOT_PARTITION_UPDATE_ADDRESS. On successful verify the demo + * sets the wolfBoot update flag in the swap trailer so wolfBoot picks + * the staged image up on the next reset. + * + * TZEN=0 only: writes flash directly via the non-secure FLASH register + * view. The TZEN=1 path is intentionally out of scope here. + */ + +#include "wolfip.h" +#include "wolftftp.h" +#include "tftp_client_demo.h" + +#include +#include +#include + +/* ----- wolfBoot partition layout (override via -D in Makefile) ------ */ +#ifndef WOLFBOOT_PARTITION_UPDATE_ADDRESS +#define WOLFBOOT_PARTITION_UPDATE_ADDRESS 0x08100000UL +#endif +#ifndef WOLFBOOT_PARTITION_SIZE +#define WOLFBOOT_PARTITION_SIZE 0xA0000UL +#endif +#ifndef WOLFBOOT_SECTOR_SIZE +#define WOLFBOOT_SECTOR_SIZE 0x4000UL +#endif + +/* IMG_STATE_UPDATING from wolfBoot. Written to the very last byte of + * the update partition trailer to ask wolfBoot to swap on next boot. + * Using the non-inverted-flags convention (matches stm32h5-no-tz). */ +#ifndef WOLFBOOT_IMG_STATE_UPDATING +#define WOLFBOOT_IMG_STATE_UPDATING 0x70U +#endif + +/* ----- STM32H5 FLASH controller (TZEN=0 / non-secure view) ---------- */ +/* Offsets match wolfBoot/hal/stm32h5.h FLASH_NS_* but redeclared here + * to keep this demo independent of the wolfBoot tree. */ +#define H5_FLASH_BASE 0x40022000UL +#define H5_FLASH_KEYR (*(volatile uint32_t *)(H5_FLASH_BASE + 0x04)) +#define H5_FLASH_SR (*(volatile uint32_t *)(H5_FLASH_BASE + 0x20)) +#define H5_FLASH_CR (*(volatile uint32_t *)(H5_FLASH_BASE + 0x28)) +#define H5_FLASH_CCR (*(volatile uint32_t *)(H5_FLASH_BASE + 0x30)) +#define H5_FLASH_OPTSR_CUR (*(volatile uint32_t *)(H5_FLASH_BASE + 0x50)) + +#define H5_FLASH_KEY1 0x45670123UL +#define H5_FLASH_KEY2 0xCDEF89ABUL + +#define H5_FLASH_CR_LOCK (1U << 0) +#define H5_FLASH_CR_PG (1U << 1) +#define H5_FLASH_CR_SER (1U << 2) +#define H5_FLASH_CR_BER (1U << 3) +#define H5_FLASH_CR_STRT (1U << 5) +#define H5_FLASH_CR_PNB_SHIFT 6 +#define H5_FLASH_CR_PNB_MASK 0x7FU +#define H5_FLASH_CR_MER (1U << 15) +#define H5_FLASH_CR_BKSEL (1U << 31) + +#define H5_FLASH_SR_BSY (1U << 0) +#define H5_FLASH_SR_EOP (1U << 16) + +/* FLASH_CCR is write-1-clear for all the status/error bits we care + * about: clear EOP, all error flags, and the BSY clear bits. */ +#define H5_FLASH_CCR_CLR_ALL (0x7FE0001FU) + +/* Any SR error flag: bits 17-26 (WRPERR, PGSERR, STRBERR, INCERR, + * OBKERR, OBKWERR, OPTCHANGEERR, etc. per H5 RM). */ +#define H5_FLASH_SR_ERR_MASK (0x07FE0000U) + +#define H5_FLASH_OPTSR_SWAP_BANK (1U << 31) + +#define H5_FLASH_PAGE_SIZE 0x2000UL /* 8KB erase granule */ +#define H5_FLASH_BANK2_BASE 0x08100000UL +#define H5_FLASH_TOP 0x081FFFFFUL +#define H5_FLASH_BASE_ADDR 0x08000000UL + +/* ----- TFTP tunables ---------------------------------------------- */ +#define TFTP_CLIENT_LOCAL_PORT 20100U +#define TFTP_DEMO_BLKSIZE 1428U +#define TFTP_DEMO_WINDOWSIZE 8U +#define TFTP_DEMO_TIMEOUT_S 1U +#define TFTP_DEMO_MAX_RETRIES 5U +#define TFTP_DEMO_RX_BUF 1500U + +/* ----- Module state (single-shot, single transfer) ----------------- */ +struct tftp_demo_handle { + uint32_t bytes_written; + uint32_t erased_through; /* address >=erased_through is fresh-erased */ + uint32_t qword_addr; /* flash address of the qword being filled */ + uint8_t qword[16]; + uint8_t qword_have; + uint8_t flash_unlocked; +}; + +static struct wolfIP *g_stack; +static struct wolftftp_client g_client; +static int g_sock = -1; +static int g_started; +static int g_last_status = INT_MIN; +static uint32_t g_server_ip; +static struct tftp_demo_handle g_handle; +static tftp_client_demo_debug_cb g_dbg; +static uint8_t g_rx_buf[TFTP_DEMO_RX_BUF]; + +/* ----- Logging helpers --------------------------------------------- */ +static void dbg(const char *s) +{ + if (g_dbg != NULL) + g_dbg(s); +} + +static void dbg_u32(const char *prefix, uint32_t v) +{ + char buf[12]; + int i; + int n = 0; + if (v == 0) { + buf[n++] = '0'; + } else { + char tmp[12]; + int t = 0; + while (v != 0) { + tmp[t++] = (char)('0' + (v % 10)); + v /= 10; + } + for (i = t - 1; i >= 0; i--) + buf[n++] = tmp[i]; + } + buf[n] = '\0'; + if (prefix != NULL) + dbg(prefix); + dbg(buf); +} + +/* Signed-decimal counterpart to dbg_u32(); used for status codes that + * are negative WOLFTFTP_ERR_* values. */ +static void dbg_i32(const char *prefix, int32_t v) +{ + uint32_t u; + if (v < 0) { + if (prefix != NULL) + dbg(prefix); + dbg("-"); + u = (uint32_t)(-(v + 1)) + 1U; + dbg_u32(NULL, u); + } else { + dbg_u32(prefix, (uint32_t)v); + } +} + +/* ----- Inline H5 flash HAL (TZEN=0) -------------------------------- */ +static void h5_flash_wait(void) +{ + while ((H5_FLASH_SR & H5_FLASH_SR_BSY) != 0) + ; +} + +static void h5_flash_clear_errors(void) +{ + H5_FLASH_CCR = H5_FLASH_CCR_CLR_ALL; +} + +static void h5_flash_unlock(void) +{ + h5_flash_wait(); + if ((H5_FLASH_CR & H5_FLASH_CR_LOCK) != 0) { + H5_FLASH_KEYR = H5_FLASH_KEY1; + H5_FLASH_KEYR = H5_FLASH_KEY2; + while ((H5_FLASH_CR & H5_FLASH_CR_LOCK) != 0) + ; + } +} + +static void h5_flash_lock(void) +{ + h5_flash_wait(); + if ((H5_FLASH_CR & H5_FLASH_CR_LOCK) == 0) + H5_FLASH_CR |= H5_FLASH_CR_LOCK; +} + +/* Erase the 8KB page that covers `addr`. addr must be page-aligned. */ +static int h5_flash_erase_page(uint32_t addr) +{ + uint32_t reg; + uint32_t bnksel = 0; + uint32_t base = H5_FLASH_BASE_ADDR; + + if (addr < H5_FLASH_BASE_ADDR || addr > H5_FLASH_TOP) + return -1; + if (addr >= H5_FLASH_BANK2_BASE) { + base = H5_FLASH_BANK2_BASE; + bnksel = 1; + } + if (((H5_FLASH_OPTSR_CUR & H5_FLASH_OPTSR_SWAP_BANK) >> 31) != 0) + bnksel = bnksel ? 0 : 1; + + h5_flash_clear_errors(); + reg = H5_FLASH_CR & ~((H5_FLASH_CR_PNB_MASK << H5_FLASH_CR_PNB_SHIFT) | + H5_FLASH_CR_SER | H5_FLASH_CR_BER | H5_FLASH_CR_PG | H5_FLASH_CR_MER | + H5_FLASH_CR_BKSEL); + reg |= (((addr - base) >> 13) << H5_FLASH_CR_PNB_SHIFT) | H5_FLASH_CR_SER | + (bnksel ? H5_FLASH_CR_BKSEL : 0U); + H5_FLASH_CR = reg; + __asm volatile ("isb"); + H5_FLASH_CR |= H5_FLASH_CR_STRT; + h5_flash_wait(); + H5_FLASH_CR &= ~H5_FLASH_CR_SER; + if ((H5_FLASH_SR & H5_FLASH_SR_ERR_MASK) != 0) { + dbg_u32("TFTP: erase SR err=", H5_FLASH_SR & H5_FLASH_SR_ERR_MASK); + dbg("\n"); + H5_FLASH_CCR = H5_FLASH_CCR_CLR_ALL; + return -1; + } + return 0; +} + +/* Program one 128-bit (16-byte) quad-word at addr. addr must be 16-byte + * aligned. */ +static int h5_flash_program_qword(uint32_t addr, const uint8_t *qword) +{ + volatile uint32_t *dst; + const uint32_t *src; + int i; + uint32_t buf[4]; + + if ((addr & 0xFU) != 0) + return -1; + h5_flash_clear_errors(); + /* Copy into a local aligned buffer; the caller's `qword` may be + * arbitrarily aligned (it comes out of a TFTP DATA byte stream). */ + for (i = 0; i < 16; i++) + ((uint8_t *)buf)[i] = qword[i]; + dst = (volatile uint32_t *)addr; + src = buf; + H5_FLASH_CR |= H5_FLASH_CR_PG; + for (i = 0; i < 4; i++) { + dst[i] = src[i]; + __asm volatile ("isb"); + } + h5_flash_wait(); + { + uint32_t sr = H5_FLASH_SR; + /* Clear EOP / error flags via CCR (FLASH_SR bits are read-only + * on H5; CCR is write-1-to-clear). */ + H5_FLASH_CCR = H5_FLASH_CCR_CLR_ALL; + H5_FLASH_CR &= ~H5_FLASH_CR_PG; + if ((sr & H5_FLASH_SR_ERR_MASK) != 0) { + dbg_u32("TFTP: program SR err=", sr & H5_FLASH_SR_ERR_MASK); + dbg("\n"); + return -1; + } + } + return 0; +} + +/* ----- TFTP io_ops mapped to the wolfBoot update partition --------- */ +static int demo_open(void *arg, const char *name, int is_write, + uint32_t *size_hint, void **handle) +{ + struct tftp_demo_handle *h = &g_handle; + + (void)arg; + (void)name; + (void)size_hint; + if (!is_write) + return WOLFTFTP_ERR_UNSUPPORTED; + h->bytes_written = 0; + h->erased_through = WOLFBOOT_PARTITION_UPDATE_ADDRESS; + h->qword_addr = WOLFBOOT_PARTITION_UPDATE_ADDRESS; + h->qword_have = 0; + h->flash_unlocked = 0; + memset(h->qword, 0xFF, sizeof(h->qword)); + + h5_flash_unlock(); + h->flash_unlocked = 1; + *handle = h; + dbg("TFTP: open update partition (erase on demand)\n"); + return 0; +} + +/* Make sure flash from [erased_through .. addr_end) is erased. */ +static int demo_ensure_erased(struct tftp_demo_handle *h, uint32_t addr_end) +{ + while (h->erased_through < addr_end) { + int rc = h5_flash_erase_page(h->erased_through); + if (rc != 0) + return rc; + h->erased_through += H5_FLASH_PAGE_SIZE; + } + return 0; +} + +/* Flush g_handle.qword to flash if it is full (qword_have == 16). The + * flash address comes from h->qword_addr, captured when the first byte + * of this qword was buffered - bytes_written has since advanced past + * the qword and can't be used here. */ +static int demo_flush_qword(struct tftp_demo_handle *h) +{ + int rc; + + if (h->qword_have < 16) + return 0; + rc = demo_ensure_erased(h, h->qword_addr + 16U); + if (rc != 0) + return rc; + rc = h5_flash_program_qword(h->qword_addr, h->qword); + if (rc != 0) + return rc; + h->qword_addr += 16U; + h->qword_have = 0; + memset(h->qword, 0xFF, sizeof(h->qword)); + return 0; +} + +static int demo_write(void *arg, void *handle, uint32_t offset, + const uint8_t *buf, uint16_t len) +{ + struct tftp_demo_handle *h = (struct tftp_demo_handle *)handle; + uint16_t i; + + (void)arg; + (void)offset; + if (h == NULL || buf == NULL) + return WOLFTFTP_ERR_IO; + if ((uint32_t)h->bytes_written + (uint32_t)len > WOLFBOOT_PARTITION_SIZE) + return WOLFTFTP_ERR_SIZE; + for (i = 0; i < len; i++) { + h->qword[h->qword_have++] = buf[i]; + h->bytes_written++; + if (h->qword_have == 16) { + int rc = demo_flush_qword(h); + if (rc != 0) { + dbg("TFTP: flash program failed\n"); + return WOLFTFTP_ERR_IO; + } + } + } + return 0; +} + +static int demo_hash_update(void *arg, void *handle, + const uint8_t *buf, uint16_t len) +{ + (void)arg; + (void)handle; + (void)buf; + (void)len; + /* wolfBoot re-hashes on next boot via its own signature check. */ + return 0; +} + +/* Write IMG_STATE_UPDATING to the last byte of the update partition. + * This is the wolfBoot "trigger" marker used by libwolfboot.c for + * non-inverted-flags builds (stm32h5-no-tz config). The page that + * contains the trailer is the last page of the partition; it has + * already been erased by demo_open() / demo_ensure_erased() once we + * pass through it on the way to the end, so a single byte write + * (programmed as a qword with 0xFF padding) is sufficient. */ +static int demo_trigger_update(void) +{ + uint32_t trailer_addr; + uint32_t qword_addr; + uint32_t off; + uint8_t qword[16]; + int rc; + + trailer_addr = WOLFBOOT_PARTITION_UPDATE_ADDRESS + + WOLFBOOT_PARTITION_SIZE - 1U; + qword_addr = trailer_addr & ~0xFU; + off = trailer_addr - qword_addr; + /* Erase the page that holds the trailer (in case bytes_written + * stopped earlier in the partition and we never erased it). */ + rc = demo_ensure_erased(&g_handle, + (qword_addr & ~(H5_FLASH_PAGE_SIZE - 1U)) + H5_FLASH_PAGE_SIZE); + if (rc != 0) + return rc; + memset(qword, 0xFF, sizeof(qword)); + qword[off] = WOLFBOOT_IMG_STATE_UPDATING; + return h5_flash_program_qword(qword_addr, qword); +} + +static int demo_verify(void *arg, void *handle, uint32_t total_size) +{ + struct tftp_demo_handle *h = (struct tftp_demo_handle *)handle; + int rc; + + (void)arg; + if (h == NULL) + return WOLFTFTP_ERR_IO; + /* Pad any trailing partial qword with 0xFF and flush. */ + if (h->qword_have > 0 && h->qword_have < 16) { + while (h->qword_have < 16) + h->qword[h->qword_have++] = 0xFFU; + rc = demo_flush_qword(h); + if (rc != 0) + return WOLFTFTP_ERR_IO; + } + dbg_u32("TFTP: programmed bytes=", h->bytes_written); + dbg("\n"); + if (total_size != 0 && total_size != h->bytes_written) { + dbg("TFTP: byte count mismatch vs tsize\n"); + return WOLFTFTP_ERR_VERIFY; + } + rc = demo_trigger_update(); + if (rc != 0) { + dbg("TFTP: failed to set update flag\n"); + return WOLFTFTP_ERR_IO; + } + dbg("TFTP: update flag set, reset to apply\n"); + return 0; +} + +static void demo_close(void *arg, void *handle, int status) +{ + struct tftp_demo_handle *h = (struct tftp_demo_handle *)handle; + + (void)arg; + if (h != NULL && h->flash_unlocked) { + h5_flash_lock(); + h->flash_unlocked = 0; + } + g_last_status = status; + dbg_i32("TFTP: close status=", status); + dbg("\n"); +} + +/* ----- Transport: send via wolfIP UDP socket ----------------------- */ +static int demo_udp_send(void *arg, uint16_t local_port, + const struct wolftftp_endpoint *remote, const uint8_t *buf, uint16_t len) +{ + struct wolfIP_sockaddr_in dst; + int ret; + + (void)arg; + (void)local_port; + memset(&dst, 0, sizeof(dst)); + dst.sin_family = AF_INET; + dst.sin_port = ee16(remote->port); + dst.sin_addr.s_addr = ee32(remote->ip); + ret = wolfIP_sock_sendto(g_stack, g_sock, buf, len, 0, + (struct wolfIP_sockaddr *)&dst, sizeof(dst)); + if (ret == (int)len) + return 0; + return ret < 0 ? ret : -1; +} + +/* ----- Public API -------------------------------------------------- */ +int tftp_client_demo_start(struct wolfIP *stack, uint32_t server_ip, + const char *filename, tftp_client_demo_debug_cb debug_cb) +{ + struct wolfIP_sockaddr_in bind_addr; + struct wolftftp_endpoint server_ep; + struct wolftftp_transport_ops tx; + struct wolftftp_io_ops io; + struct wolftftp_transfer_cfg cfg; + int ret; + + if (stack == NULL || filename == NULL) + return -1; + if (g_started) + return -1; + + g_stack = stack; + g_dbg = debug_cb; + g_server_ip = server_ip; + + g_sock = wolfIP_sock_socket(stack, AF_INET, IPSTACK_SOCK_DGRAM, 0); + if (g_sock < 0) { + dbg("TFTP: socket() failed\n"); + return -1; + } + memset(&bind_addr, 0, sizeof(bind_addr)); + bind_addr.sin_family = AF_INET; + bind_addr.sin_port = ee16(TFTP_CLIENT_LOCAL_PORT); + bind_addr.sin_addr.s_addr = 0; + ret = wolfIP_sock_bind(stack, g_sock, + (struct wolfIP_sockaddr *)&bind_addr, sizeof(bind_addr)); + if (ret < 0) { + dbg("TFTP: bind() failed\n"); + wolfIP_sock_close(stack, g_sock); + g_sock = -1; + return -1; + } + + memset(&tx, 0, sizeof(tx)); + tx.send = demo_udp_send; + tx.arg = NULL; + + memset(&io, 0, sizeof(io)); + io.open = demo_open; + io.write = demo_write; + io.hash_update = demo_hash_update; + io.verify = demo_verify; + io.close = demo_close; + io.arg = NULL; + + memset(&cfg, 0, sizeof(cfg)); + cfg.local_port = TFTP_CLIENT_LOCAL_PORT; + cfg.blksize = TFTP_DEMO_BLKSIZE; + cfg.timeout_s = TFTP_DEMO_TIMEOUT_S; + cfg.windowsize = TFTP_DEMO_WINDOWSIZE; + cfg.max_retries = TFTP_DEMO_MAX_RETRIES; + cfg.max_image_size = WOLFBOOT_PARTITION_SIZE; + + wolftftp_client_init(&g_client, &tx, &io, &cfg); + + server_ep.ip = server_ip; + server_ep.port = WOLFTFTP_PORT; + ret = wolftftp_client_start_rrq(&g_client, &server_ep, filename); + if (ret != 0) { + dbg_i32("TFTP: start_rrq failed rc=", ret); + dbg("\n"); + wolfIP_sock_close(stack, g_sock); + g_sock = -1; + return ret; + } + g_started = 1; + g_last_status = 1; /* in progress */ + dbg("TFTP: RRQ sent\n"); + return 0; +} + +void tftp_client_demo_poll(uint32_t now_ms) +{ + struct wolfIP_sockaddr_in remote; + socklen_t rlen; + int n; + + if (!g_started || g_sock < 0) + return; + + for (;;) { + rlen = sizeof(remote); + n = wolfIP_sock_recvfrom(g_stack, g_sock, g_rx_buf, + sizeof(g_rx_buf), 0, (struct wolfIP_sockaddr *)&remote, &rlen); + if (n <= 0) + break; + { + struct wolftftp_endpoint rep; + rep.ip = ee32(remote.sin_addr.s_addr); + rep.port = ee16(remote.sin_port); + (void)wolftftp_client_receive(&g_client, + TFTP_CLIENT_LOCAL_PORT, &rep, g_rx_buf, (uint16_t)n); + } + } + (void)wolftftp_client_poll(&g_client, now_ms); + + /* Status semantics exposed via tftp_client_demo_status(): + * INT_MIN = never started + * 1 = transfer in progress (set in start()) + * 0 = transfer complete (success) + * < 0 = transfer failed with WOLFTFTP_ERR_* (or -1 fallback) + * Latch the first time the wolftftp state machine reports a terminal + * state so the user-facing status() stays stable after completion. */ + if (g_last_status == 1) { + if (g_client.state == WOLFTFTP_CLIENT_COMPLETE) { + g_last_status = 0; + dbg("TFTP: transfer complete\n"); + } else if (g_client.state == WOLFTFTP_CLIENT_ERROR) { + int s = wolftftp_client_status(&g_client); + g_last_status = s != 0 ? s : -1; + dbg_i32("TFTP: transfer failed rc=", g_last_status); + dbg("\n"); + } + } +} + +int tftp_client_demo_status(void) +{ + return g_last_status; +} diff --git a/src/port/stm32h563/tftp_client_demo.h b/src/port/stm32h563/tftp_client_demo.h new file mode 100644 index 00000000..68200aeb --- /dev/null +++ b/src/port/stm32h563/tftp_client_demo.h @@ -0,0 +1,54 @@ +/* tftp_client_demo.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP 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. + * + * wolfIP 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 TFTP_CLIENT_DEMO_H +#define TFTP_CLIENT_DEMO_H + +#include + +struct wolfIP; + +typedef void (*tftp_client_demo_debug_cb)(const char *msg); + +/* Start a one-shot TFTP RRQ GET of `filename` from `server_ip` (network + * byte order, big-endian uint32). The received bytes are programmed + * into the wolfBoot update partition at WOLFBOOT_PARTITION_UPDATE_ADDRESS; + * on successful completion the update flag is set so wolfBoot picks the + * staged image up on the next reset. + * + * Returns 0 on successful kickoff, negative on error. The actual + * transfer runs asynchronously - call tftp_client_demo_poll() from the + * main loop. */ +int tftp_client_demo_start(struct wolfIP *stack, uint32_t server_ip, + const char *filename, tftp_client_demo_debug_cb debug_cb); + +/* Drive the TFTP client state machine. `now_ms` is monotonic + * milliseconds (same source the main loop already passes to + * wolfIP_poll). Safe to call when no transfer is active. */ +void tftp_client_demo_poll(uint32_t now_ms); + +/* Returns the last status of the transfer: + * 1 - in progress + * 0 - complete success (update flag set) + * <0 - WOLFTFTP_ERR_* or other failure + * INT32_MIN until the first transfer is started. */ +int tftp_client_demo_status(void); + +#endif /* TFTP_CLIENT_DEMO_H */