Skip to content

Commit dc5ff70

Browse files
committed
Add STM32H5 TFTP client demo
1 parent 1164e65 commit dc5ff70

6 files changed

Lines changed: 852 additions & 0 deletions

File tree

src/port/stm32h563/Makefile

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ ENABLE_MQTT ?= 0
3030
# MQTT Broker: set ENABLE_MQTT_BROKER=1 to include wolfMQTT broker (requires TLS)
3131
ENABLE_MQTT_BROKER ?= 0
3232

33+
# TFTP client demo: set ENABLE_TFTP=1 to include the wolfIP TFTP client
34+
# that downloads a firmware image at boot and stages it into the
35+
# wolfBoot update partition. TZEN=0 only.
36+
ENABLE_TFTP ?= 0
37+
3338
# FreeRTOS integration: set FREERTOS=1 to run the HTTPS server from a
3439
# FreeRTOS task using the blocking BSD socket wrapper layer.
3540
FREERTOS ?= 0
@@ -328,6 +333,33 @@ endif
328333

329334
endif # ENABLE_MQTT_BROKER
330335

336+
# -----------------------------------------------------------------------------
337+
# TFTP Client Demo (wolfIP TFTP) - one-shot RRQ GET into wolfBoot update partition
338+
# -----------------------------------------------------------------------------
339+
ifeq ($(ENABLE_TFTP),1)
340+
341+
ifeq ($(TZEN),1)
342+
$(error ENABLE_TFTP=1 currently only supports TZEN=0)
343+
endif
344+
345+
CFLAGS += -DENABLE_TFTP -DWOLFIP_ENABLE_TFTP=1
346+
CFLAGS += -I$(ROOT)/src/tftp
347+
348+
# wolfBoot partition layout. Defaults match
349+
# ../wolfboot/config/examples/stm32h5-no-tz.config. Override on the
350+
# command line to match a different wolfBoot config.
351+
WOLFBOOT_PARTITION_UPDATE_ADDRESS ?= 0x08100000
352+
WOLFBOOT_PARTITION_SIZE ?= 0xA0000
353+
WOLFBOOT_SECTOR_SIZE ?= 0x4000
354+
CFLAGS += -DWOLFBOOT_PARTITION_UPDATE_ADDRESS=$(WOLFBOOT_PARTITION_UPDATE_ADDRESS)UL
355+
CFLAGS += -DWOLFBOOT_PARTITION_SIZE=$(WOLFBOOT_PARTITION_SIZE)UL
356+
CFLAGS += -DWOLFBOOT_SECTOR_SIZE=$(WOLFBOOT_SECTOR_SIZE)UL
357+
358+
SRCS += tftp_client_demo.c
359+
SRCS += $(ROOT)/src/tftp/wolftftp.c
360+
361+
endif # ENABLE_TFTP
362+
331363
# -----------------------------------------------------------------------------
332364
# Build rules
333365
# -----------------------------------------------------------------------------
@@ -432,6 +464,7 @@ help:
432464
@echo " ENABLE_SSH=1 Enable SSH server (requires TLS + wolfSSH)"
433465
@echo " ENABLE_MQTT=1 Enable MQTT client (requires TLS + wolfMQTT)"
434466
@echo " ENABLE_MQTT_BROKER=1 Enable MQTT broker (requires TLS + wolfMQTT)"
467+
@echo " ENABLE_TFTP=1 Enable TFTP client demo (RRQ GET into wolfBoot update partition; TZEN=0 only)"
435468
@echo " FREERTOS=1 Run HTTPS in a FreeRTOS task via BSD socket wrappers"
436469
@echo " FREERTOS_PATH= Path to FreeRTOS-Kernel (default: $(ROOT)/../FreeRTOS_Kernel)"
437470
@echo " WOLFSSL_ROOT= Path to wolfSSL (default: ../wolfssl)"
@@ -448,6 +481,7 @@ help:
448481
@echo " make ENABLE_TLS=1 ENABLE_TLS_CLIENT=1 # TLS client (Google test)"
449482
@echo " make ENABLE_TLS=1 ENABLE_MQTT=1 # TLS + MQTT client"
450483
@echo " make ENABLE_TLS=1 ENABLE_MQTT_BROKER=1 # TLS + MQTT broker"
484+
@echo " make ENABLE_TFTP=1 # TFTP client demo (one-shot RRQ at boot)"
451485
@echo " make FREERTOS=1 ENABLE_HTTPS=1 # FreeRTOS HTTPS via BSD sockets"
452486
@echo " make ENABLE_TLS=1 ENABLE_HTTPS=1 ENABLE_SSH=1 ENABLE_MQTT=1 ENABLE_MQTT_BROKER=1 # Full featured"
453487
@echo ""

src/port/stm32h563/README.md

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,183 @@ mqttclient -h <device-ip> -p 8883 -t -A /tmp/wolfip_cert.pem \
893893
| `../certs.h` | Embedded ECC P-256 cert/key (shared with TLS/HTTPS) |
894894
| `user_settings.h` | wolfMQTT broker compile-time config |
895895

896+
## TFTP Client
897+
898+
When built with `ENABLE_TFTP=1`, the device runs a one-shot wolfIP TFTP
899+
RRQ (GET) client at boot. After the network comes up, the client fetches
900+
a single file from a host-side TFTP server and stages the bytes directly
901+
into the wolfBoot update partition. On success the wolfBoot update flag
902+
is written to the trailer of the update partition; the next reset hands
903+
control to wolfBoot, which can verify the staged image and swap it in.
904+
905+
`ENABLE_TFTP=1` is **TZEN=0 only** in this release. The build errors out
906+
if `TZEN=1` is also set.
907+
908+
### Building TFTP Mode
909+
910+
```bash
911+
cd src/port/stm32h563
912+
make clean
913+
make ENABLE_TFTP=1
914+
```
915+
916+
The Makefile pulls in `../../tftp/wolftftp.c` from wolfIP's TFTP module
917+
and `tftp_client_demo.c` from this port, and sets
918+
`-DWOLFIP_ENABLE_TFTP=1`. No external dependencies (no wolfSSL,
919+
wolfSSH, wolfMQTT). The demo can be combined with the other services -
920+
e.g. `make ENABLE_TFTP=1 ENABLE_HTTPS=1 ENABLE_SSH=1`.
921+
922+
### Configuration
923+
924+
The defaults in `config.h` and the Makefile target a host on the
925+
`192.168.12.0/24` static-fallback subnet. Override them on the command
926+
line via `EXTRA_CFLAGS`:
927+
928+
```bash
929+
make ENABLE_TFTP=1 \
930+
EXTRA_CFLAGS='-DTFTP_SERVER_IP=\"10.0.4.24\" -DTFTP_FETCH_FILENAME=\"app_v2_signed.bin\"'
931+
```
932+
933+
| Setting | Default | Where |
934+
|---------|---------|-------|
935+
| `TFTP_SERVER_IP` | `"192.168.12.10"` | `config.h` |
936+
| `TFTP_FETCH_FILENAME` | `"app_v2_signed.bin"` | `config.h` |
937+
| `WOLFBOOT_PARTITION_UPDATE_ADDRESS` | `0x08100000` | `Makefile` (matches `stm32h5-no-tz.config`) |
938+
| `WOLFBOOT_PARTITION_SIZE` | `0xA0000` (640 KB) | `Makefile` |
939+
| `WOLFBOOT_SECTOR_SIZE` | `0x4000` (16 KB) | `Makefile` |
940+
| TFTP client local UDP port | `20100` | `tftp_client_demo.c` |
941+
| TFTP block size | `1428` | `tftp_client_demo.c` |
942+
| TFTP window size | `8` | `tftp_client_demo.c` |
943+
944+
Override the partition layout on the command line if your wolfBoot is
945+
configured differently:
946+
947+
```bash
948+
make ENABLE_TFTP=1 WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x08080000 \
949+
WOLFBOOT_PARTITION_SIZE=0x70000
950+
```
951+
952+
### Host Setup (`tftpd-hpa`)
953+
954+
```bash
955+
sudo apt-get install tftpd-hpa
956+
echo "TFTP test fixture" | sudo tee /srv/tftp/app_v2_signed.bin
957+
sudo systemctl restart tftpd-hpa
958+
959+
# Sanity check from another host on the same LAN:
960+
tftp <host-ip> -c get app_v2_signed.bin
961+
```
962+
963+
`/etc/default/tftpd-hpa` (default Ubuntu/Debian install) listens on
964+
`:69` and serves `/srv/tftp`. The TFTP daemon accepts files mode `0666`
965+
by default; restart it after dropping a new fixture.
966+
967+
### Expected Serial Output (TFTP Mode)
968+
969+
```
970+
DHCP configuration received:
971+
IP: 10.0.4.116
972+
Mask: 255.255.255.0
973+
GW: 10.0.4.1
974+
Creating TCP socket on port 7...
975+
Initializing TFTP client demo...
976+
TFTP server: 10.0.4.24
977+
TFTP file: test.txt
978+
TFTP: RRQ sent
979+
Entering main loop. Ready for connections!
980+
TCP Echo: port 7
981+
TFTP: open update partition (erase on demand)
982+
TFTP: programmed bytes=44
983+
TFTP: update flag set, reset to apply
984+
TFTP: close status=0
985+
```
986+
987+
`programmed bytes=N` matches the file's size on the host. `close
988+
status=0` means success. Negative values map to `WOLFTFTP_ERR_*` codes
989+
in `../../tftp/wolftftp.h` (`-1000` IO, `-1001` STATE, `-1002` PACKET,
990+
`-1003` TIMEOUT, `-1004` SIZE, `-1005` VERIFY, `-1006` UNSUPPORTED,
991+
`-1007` TID).
992+
993+
### Verifying the Staged Image
994+
995+
Halt the target and dump the update partition to confirm the bytes
996+
match the host file:
997+
998+
```bash
999+
$OPENOCD -s $OPENOCD_SCRIPTS -f interface/stlink-dap.cfg \
1000+
-c "adapter serial $H5_SN" \
1001+
-f target/stm32h5x.cfg \
1002+
-c "init; halt" \
1003+
-c "dump_image /tmp/h5_update_head.bin 0x08100000 64" \
1004+
-c "dump_image /tmp/h5_update_tail.bin 0x0819FF00 256" \
1005+
-c "resume; shutdown"
1006+
1007+
# Compare:
1008+
xxd /srv/tftp/app_v2_signed.bin | head
1009+
xxd /tmp/h5_update_head.bin
1010+
1011+
# The last byte of the partition (0x0819FFFF) is the wolfBoot update
1012+
# trigger - it should read 0x70 (IMG_STATE_UPDATING):
1013+
tail -c 1 /tmp/h5_update_tail.bin | xxd
1014+
```
1015+
1016+
### How It Works
1017+
1018+
The demo uses the wolfIP UDP socket API directly (no BSD wrapper) and
1019+
plugs the TFTP state machine's `transport` callback into
1020+
`wolfIP_sock_sendto()`. The main loop drains the UDP socket with
1021+
`wolfIP_sock_recvfrom()`, hands incoming datagrams to
1022+
`wolftftp_client_receive()`, and lets `wolftftp_client_poll()` drive
1023+
retransmits and timeouts.
1024+
1025+
The `io.write` callback in `tftp_client_demo.c` buffers each incoming
1026+
DATA block into a 16-byte staging qword. When the staging buffer fills
1027+
up, the demo:
1028+
1029+
1. Lazily erases the 8 KB page that holds the destination address (if
1030+
it has not already been erased on this transfer).
1031+
2. Programs the 16-byte quad-word at the destination address. STM32H5
1032+
flash programs in 128-bit (16-byte) quanta with per-qword ECC; each
1033+
qword can only be programmed once between erases.
1034+
1035+
On successful transfer completion the demo writes
1036+
`IMG_STATE_UPDATING` (`0x70`) to the very last byte of the update
1037+
partition (`WOLFBOOT_PARTITION_UPDATE_ADDRESS + WOLFBOOT_PARTITION_SIZE
1038+
- 1`). That single byte is the wolfBoot update trigger; on the next
1039+
reset wolfBoot reads the trailer and swaps in the staged image.
1040+
1041+
### Limitations / Out of Scope
1042+
1043+
- **wolfBoot is not bundled in the demo's flash image.** The current
1044+
`target.ld` places the application at `0x08000000`, so when the
1045+
unmodified demo boots there is no wolfBoot bootloader to consume the
1046+
update flag. The TFTP staging side is correct and hardware-verified;
1047+
the round-trip "fetch -> reset -> v2 boots" requires shifting the
1048+
app linker script to `0x08060000`, installing a wolfBoot built from
1049+
`config/examples/stm32h5-no-tz.config`, and re-packing `factory.bin`
1050+
as `wolfboot_padded.bin + signed app`. That packaging is out of scope
1051+
for the `ENABLE_TFTP=1` flag.
1052+
- **TZEN=1 is not supported.** The flash HAL uses the non-secure
1053+
register view (`FLASH_NS_*` at `0x40022000`). A TrustZone-aware
1054+
version would need the secure aliases plus
1055+
`hal_tz_claim_nonsecure_area()` bracketing each program/erase.
1056+
- **Client only, RRQ only.** No WRQ (PUT), and no on-target TFTP
1057+
server.
1058+
- **No authentication or integrity check on the wire.** TFTP is
1059+
unauthenticated by design. The staged image is verified by wolfBoot
1060+
on the next boot via its signature check; that catches a tampered
1061+
or corrupted image but does not protect the partition until the next
1062+
reset window.
1063+
1064+
### TFTP Files
1065+
1066+
| File | Description |
1067+
|------|-------------|
1068+
| `tftp_client_demo.c` | UDP glue + STM32H5 flash HAL + wolfBoot trigger |
1069+
| `tftp_client_demo.h` | `_start`/`_poll`/`_status` API |
1070+
| `../../tftp/wolftftp.c` | Library: TFTP state machine (RFC 1350 + RFC 2347 options) |
1071+
| `../../tftp/wolftftp.h` | Library: public API (`wolftftp_client_*`, `wolftftp_server_*`) |
1072+
8961073
## Files
8971074

8981075
| File | Description |
@@ -915,6 +1092,8 @@ mqttclient -h <device-ip> -p 8883 -t -A /tmp/wolfip_cert.pem \
9151092
| `ssh_server.c/h` | SSH shell server (SSH builds only) |
9161093
| `ssh_keys.h` | Embedded SSH host key (SSH builds only) |
9171094
| `mqtt_client.c/h` | MQTT client state machine (MQTT builds only) |
1095+
| `tftp_client_demo.c/h` | TFTP client demo + inline H5 flash HAL (TFTP builds only) |
1096+
| `../../tftp/wolftftp.c` | wolfIP TFTP library (TFTP builds only) |
9181097
| `../wolfssl_io.c` | wolfSSL I/O callbacks for wolfIP (TLS builds only) |
9191098
| `../wolfssh_io.c` | wolfSSH I/O callbacks for wolfIP (SSH builds only) |
9201099
| `../wolfmqtt_io.c` | wolfMQTT I/O callbacks for wolfIP (MQTT builds only) |

src/port/stm32h563/config.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,14 @@
6565
#define DHCP_REQUEST_RETRIES 1
6666
#endif
6767

68+
/* TFTP client demo (ENABLE_TFTP=1). On boot, after DHCP, the demo fetches
69+
* TFTP_FETCH_FILENAME from a tftpd-hpa running on TFTP_SERVER_IP and
70+
* stages it into the wolfBoot update partition. */
71+
#ifndef TFTP_SERVER_IP
72+
#define TFTP_SERVER_IP "192.168.12.10"
73+
#endif
74+
#ifndef TFTP_FETCH_FILENAME
75+
#define TFTP_FETCH_FILENAME "app_v2_signed.bin"
76+
#endif
77+
6878
#endif /* WOLF_CONFIG_H */

src/port/stm32h563/main.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@
6565
extern volatile unsigned long broker_uptime_sec;
6666
#endif
6767

68+
#ifdef ENABLE_TFTP
69+
#include "tftp_client_demo.h"
70+
#endif
71+
6872
#ifdef ENABLE_TLS_CLIENT
6973

7074
/* Google IP for TLS client test (run: dig +short google.com) */
@@ -1074,6 +1078,22 @@ int main(void)
10741078
}
10751079
#endif
10761080

1081+
#ifdef ENABLE_TFTP
1082+
uart_puts("Initializing TFTP client demo...\n");
1083+
{
1084+
ip4 srv = atoip4(TFTP_SERVER_IP);
1085+
uart_puts(" TFTP server: ");
1086+
uart_putip4(srv);
1087+
uart_puts("\n TFTP file: ");
1088+
uart_puts(TFTP_FETCH_FILENAME);
1089+
uart_puts("\n");
1090+
if (tftp_client_demo_start(IPStack, srv, TFTP_FETCH_FILENAME,
1091+
uart_puts) < 0) {
1092+
uart_puts("ERROR: TFTP client init failed\n");
1093+
}
1094+
}
1095+
#endif
1096+
10771097
uart_puts("Entering main loop. Ready for connections!\n");
10781098
uart_puts(" TCP Echo: port 7\n");
10791099
#ifdef ENABLE_TLS_CLIENT
@@ -1103,6 +1123,10 @@ int main(void)
11031123
ssh_server_poll();
11041124
#endif
11051125

1126+
#ifdef ENABLE_TFTP
1127+
tftp_client_demo_poll(tick);
1128+
#endif
1129+
11061130
#ifdef ENABLE_MQTT
11071131
/* Poll MQTT client */
11081132
mqtt_client_poll();

0 commit comments

Comments
 (0)