Skip to content

Commit 10a51de

Browse files
committed
stm32h5: TZEN=1 NS app under wolfBoot
1 parent 3635d50 commit 10a51de

8 files changed

Lines changed: 365 additions & 138 deletions

File tree

.github/workflows/stm32h563-m33mu-ssh-tzen.yml

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ on:
99
jobs:
1010
stm32h563_m33mu_ssh_tzen:
1111
runs-on: ubuntu-latest
12-
timeout-minutes: 25
12+
timeout-minutes: 45
1313
container:
14-
image: ghcr.io/wolfssl/wolfboot-ci-m33mu:v1.2
14+
image: ghcr.io/wolfssl/wolfboot-ci-m33mu:latest
1515
options: --privileged
1616

1717
steps:
@@ -24,7 +24,7 @@ jobs:
2424
apt-get install -y sudo dnsmasq iproute2 netcat-openbsd git \
2525
openssh-client sshpass
2626
27-
- name: Fetch wolfSSL/wolfSSH
27+
- name: Fetch wolfSSL/wolfSSH/wolfBoot
2828
run: |
2929
set -euo pipefail
3030
if [ ! -d ../wolfssl ]; then
@@ -33,18 +33,30 @@ jobs:
3333
if [ ! -d ../wolfssh ]; then
3434
git clone --depth 1 --branch master https://github.com/wolfSSL/wolfssh.git ../wolfssh
3535
fi
36+
if [ ! -d ../wolfboot ]; then
37+
git clone --depth 1 --branch stm32tz https://github.com/dgarske/wolfboot.git ../wolfboot
38+
git -C ../wolfboot submodule update --init --single-branch
39+
fi
40+
41+
- name: Build wolfBoot (stm32h5-tz)
42+
run: |
43+
set -euo pipefail
44+
cd ../wolfboot
45+
cp config/examples/stm32h5-tz.config .config
46+
make wolfboot.bin
3647
37-
- name: Build STM32H563 SSH (TZEN on)
48+
- name: Build STM32H563 SSH NS app (TZEN on) and sign
3849
run: |
3950
set -euo pipefail
4051
make -C src/port/stm32h563 clean TZEN=1 ENABLE_SSH=1
41-
make -C src/port/stm32h563 TZEN=1 ENABLE_SSH=1 \
52+
make -C src/port/stm32h563 TZEN=1 ENABLE_SSH=1 signed \
53+
WOLFBOOT_ROOT="$GITHUB_WORKSPACE/../wolfboot" \
4254
CC=arm-none-eabi-gcc OBJCOPY=arm-none-eabi-objcopy
4355
strings src/port/stm32h563/app.bin > /tmp/wolfip-app.strings
4456
grep -Fq "Initializing SSH server" /tmp/wolfip-app.strings
4557
4658
- name: Run m33mu + DHCP + SSH test
47-
timeout-minutes: 15
59+
timeout-minutes: 30
4860
run: |
4961
set -euo pipefail
5062
@@ -74,8 +86,14 @@ jobs:
7486
CONF
7587
sudo dnsmasq --conf-file=/tmp/dnsmasq.conf --pid-file=/tmp/dnsmasq.pid
7688
77-
sudo m33mu src/port/stm32h563/app.bin \
78-
--cpu stm32h563 --tap:tap0 --uart-stdout --timeout 180 --quit-on-faults \
89+
# wolfBoot does a full software ECC256 verify of the 300 KB
90+
# signed NS image, which takes ~10 minutes inside m33mu before
91+
# it BLXNSes into wolfIP. Allow up to 25 minutes total runtime
92+
# and ~20 minutes for the DHCP lease to appear.
93+
sudo m33mu \
94+
../wolfboot/wolfboot.bin \
95+
src/port/stm32h563/app_v1_signed.bin:0x60000 \
96+
--cpu stm32h563 --tap:tap0 --uart-stdout --timeout 1500 --quit-on-faults \
7997
2>&1 | tee /tmp/m33mu.log &
8098
sleep 1
8199
m33mu_pid="$(pgrep -n -x m33mu || true)"
@@ -84,7 +102,7 @@ jobs:
84102
fi
85103
86104
ip=""
87-
for _ in $(seq 1 90); do
105+
for _ in $(seq 1 1200); do
88106
if [ -s /tmp/dnsmasq.leases ]; then
89107
ip="$(tail -n1 /tmp/dnsmasq.leases | cut -d' ' -f3)"
90108
fi
@@ -102,7 +120,7 @@ jobs:
102120
echo "Leased IP: ${ip}"
103121
104122
ok=0
105-
for _ in $(seq 1 60); do
123+
for _ in $(seq 1 240); do
106124
if ! pgrep -x m33mu >/dev/null 2>&1; then
107125
echo "m33mu exited before SSH check."
108126
tail -n 200 /tmp/m33mu.log || true

src/port/stm32/stm32_eth.c

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616
#endif
1717

1818
#if defined(STM32H5)
19-
#if TZEN_ENABLED
20-
#define ETH_BASE 0x50028000UL /* Secure alias */
21-
#else
22-
#define ETH_BASE 0x40028000UL /* Non-secure alias */
23-
#endif
19+
/* Always use the NS alias of ETH on H5. Both wolfIP build modes run in
20+
* the Non-Secure world: TZEN=0 has no security distinction, and TZEN=1
21+
* runs as a NS application launched by wolfBoot (plain LDR/STR carry
22+
* HNONSEC=1 and would fault on a 0x5xxxxxxx access). */
23+
#define ETH_BASE 0x40028000UL
2424
#define STM32_ETH_NEEDS_MDIO_DELAY 0
2525
#elif defined(STM32H7)
2626
#define ETH_BASE 0x40028000UL
@@ -207,14 +207,16 @@ struct eth_desc {
207207
#if defined(STM32H7) || defined(STM32N6)
208208
#define ETH_SECTION __attribute__((section(".eth_buffers")))
209209
#elif defined(STM32H5)
210-
#if TZEN_ENABLED
211-
#define ETH_SECTION __attribute__((section(".eth_buffers")))
212-
#else
210+
/* H5 places ETH descriptors and buffers in the regular .bss region.
211+
* Both TZEN=0 (TrustZone disabled) and TZEN=1 (NS app under wolfBoot)
212+
* link RAM into a single NS-accessible region; no separate ETHMEM
213+
* alias is needed since the CPU and ETH DMA both run in NS world and
214+
* share the same NS view of physical SRAM. */
213215
#define ETH_SECTION
214216
#endif
215-
#endif
216217

217-
/* DMA buffer address — use secure alias (0x34), matching RISAF SEC=1. */
218+
/* DMA descriptor / buffer addresses are taken as-is. NS pointers map
219+
* directly to NS DMA accesses on H5; H7 / N6 use identity mapping. */
218220
#define ETH_DMA_ADDR(ptr) ((uint32_t)(ptr))
219221

220222
static struct eth_desc rx_ring[RX_DESC_COUNT] __attribute__((aligned(32))) ETH_SECTION;
@@ -235,6 +237,9 @@ static int32_t phy_addr = -1;
235237

236238
static uint32_t rx_poll_count = 0;
237239
static uint32_t rx_pkt_count = 0;
240+
#ifdef DEBUG_H5_ETH
241+
static uint32_t tx_send_count = 0;
242+
#endif
238243

239244
static uint16_t eth_mdio_read(uint32_t phy, uint32_t reg);
240245
static void eth_mdio_write(uint32_t phy, uint32_t reg, uint16_t value);
@@ -256,7 +261,7 @@ static int eth_hw_reset(void)
256261
* (HAL uses 1 second). Do NOT manually clear SWR — it corrupts DMA. */
257262
timeout = 50000000U; /* ~80ms at 600MHz */
258263
#else
259-
timeout = 1000000U;
264+
timeout = 5000000U;
260265
#endif
261266

262267
ETH_DMAMR |= ETH_DMAMR_SWR;
@@ -664,9 +669,10 @@ static void eth_phy_init(void)
664669
}
665670
}
666671

667-
/* Reset PHY. */
672+
/* Reset PHY. PHY reset typically completes in <1 ms; allow ~1 s
673+
* worst case at the H5's 32 MHz HSI / CR=4 MDC speed. */
668674
eth_mdio_write((uint32_t)phy_addr, PHY_REG_BCR, PHY_BCR_RESET);
669-
timeout = 100000U;
675+
timeout = 5000U;
670676
do {
671677
ctrl = eth_mdio_read((uint32_t)phy_addr, PHY_REG_BCR);
672678
} while ((ctrl & PHY_BCR_RESET) != 0U && --timeout != 0U);
@@ -677,15 +683,19 @@ static void eth_phy_init(void)
677683
ctrl |= PHY_BCR_AUTONEG_ENABLE | PHY_BCR_RESTART_AUTONEG;
678684
eth_mdio_write((uint32_t)phy_addr, PHY_REG_BCR, ctrl);
679685

680-
/* Wait for auto-negotiation complete. */
681-
timeout = 100000U;
686+
/* Wait for auto-negotiation complete. With a cable plugged in
687+
* aneg takes ~3 s; without a cable it never completes. Cap the
688+
* wait so boot proceeds (with link DOWN) in ~5 s instead of ~40 s. */
689+
timeout = 20000U;
682690
do {
683691
bsr = eth_mdio_read((uint32_t)phy_addr, PHY_REG_BSR);
684692
bsr |= eth_mdio_read((uint32_t)phy_addr, PHY_REG_BSR);
685693
} while ((bsr & PHY_BSR_AUTONEG_COMPLETE) == 0U && --timeout != 0U);
686694

687-
/* Wait for link up. */
688-
timeout = 100000U;
695+
/* Wait for link up. Link comes up promptly once aneg finishes;
696+
* a short timeout is enough -- if there is no cable the previous
697+
* loop has already burned its budget. */
698+
timeout = 5000U;
689699
do {
690700
bsr = eth_mdio_read((uint32_t)phy_addr, PHY_REG_BSR);
691701
bsr |= eth_mdio_read((uint32_t)phy_addr, PHY_REG_BSR);
@@ -779,6 +789,9 @@ static int eth_send(struct wolfIP_ll_dev *dev, void *frame, uint32_t len)
779789
next_idx = (tx_idx + 1U) % TX_DESC_COUNT;
780790
ETH_DMACTXDTPR = ETH_DMA_ADDR(&tx_ring[next_idx]);
781791
tx_idx = next_idx;
792+
#ifdef DEBUG_H5_ETH
793+
tx_send_count++;
794+
#endif
782795

783796
return (int)len;
784797
}
@@ -827,6 +840,47 @@ uint32_t stm32_eth_get_dmacsr(void)
827840
return val;
828841
}
829842

843+
#ifdef DEBUG_H5_ETH
844+
/* H5 MAC frame counters (MMC) - read-only, free-running.
845+
* ETH base 0x40028000, MMC offsets:
846+
* 0x0114 MMC Tx Single Collision Good Frames -> not us
847+
* 0x014C TX FRAMES OK BY CNT (TXSNGLCOLG_GBPF.. actual)
848+
* Simpler: use generic MAC_TX_PACKET_COUNT_GOOD = 0x40028068
849+
* and MAC_RX_PACKET_COUNT_GOOD = 0x4002809C
850+
* But H5 uses MMC: TX 0x0768 RX 0x0794 (good_packet counters).
851+
* Reference: RM0481 Rev2 Sec 60.8.4.
852+
*/
853+
#define ETH_MMC_TX_PACKET_GOOD ETH_REG(0x0768U)
854+
#define ETH_MMC_RX_PACKET_GOOD ETH_REG(0x0794U)
855+
#define ETH_MMC_RX_CRC_ERR ETH_REG(0x0794U + 0x10U) /* 0x07A4 */
856+
857+
uint32_t stm32_eth_get_tx_count(void)
858+
{
859+
return tx_send_count;
860+
}
861+
862+
uint32_t stm32_eth_get_tx_des3(uint32_t i)
863+
{
864+
if (i >= TX_DESC_COUNT) return 0xFFFFFFFFU;
865+
return tx_ring[i].des3;
866+
}
867+
868+
uint32_t stm32_eth_get_mac_rx_frames(void)
869+
{
870+
return ETH_MMC_RX_PACKET_GOOD;
871+
}
872+
873+
uint32_t stm32_eth_get_mac_tx_frames(void)
874+
{
875+
return ETH_MMC_TX_PACKET_GOOD;
876+
}
877+
878+
uint32_t stm32_eth_get_mac_rx_errors(void)
879+
{
880+
return ETH_MMC_RX_CRC_ERR;
881+
}
882+
#endif
883+
830884
int stm32_eth_init(struct wolfIP_ll_dev *ll, const uint8_t *mac)
831885
{
832886
uint8_t local_mac[6];

src/port/stm32/stm32_eth.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,12 @@ int stm32_eth_init(struct wolfIP_ll_dev *ll, const uint8_t *mac);
1616
void stm32_eth_get_stats(uint32_t *polls, uint32_t *pkts);
1717
uint32_t stm32_eth_get_rx_des3(void);
1818
uint32_t stm32_eth_get_dmacsr(void);
19+
#ifdef DEBUG_H5_ETH
20+
uint32_t stm32_eth_get_tx_count(void);
21+
uint32_t stm32_eth_get_tx_des3(uint32_t i);
22+
uint32_t stm32_eth_get_mac_rx_frames(void);
23+
uint32_t stm32_eth_get_mac_tx_frames(void);
24+
uint32_t stm32_eth_get_mac_rx_errors(void);
25+
#endif
1926

2027
#endif /* WOLFIP_STM32_ETH_H */

src/port/stm32h563/Makefile

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,25 @@ WOLFSSL_SP_NO_ASM ?= 0
5757
WOLFSSL_ROOT ?= $(ROOT)/../wolfssl
5858
WOLFSSH_ROOT ?= $(ROOT)/../wolfssh
5959
WOLFMQTT_ROOT ?= $(ROOT)/../wolfmqtt
60+
# wolfBoot tree, used for TZEN=1 to sign the NS image and to provide
61+
# the secure stub binary that gets concatenated into factory.bin.
62+
# Override on the command line for local clones, e.g.:
63+
# make TZEN=1 WOLFBOOT_ROOT=$(HOME)/GitHub/wolfboot-alt3 ...
64+
WOLFBOOT_ROOT ?= $(ROOT)/../wolfboot
6065
FREERTOS_PORT_DIR := $(FREERTOS_PATH)/portable/GCC/ARM_CM33_NTZ/non_secure
6166

62-
# Base compiler flags
63-
CFLAGS := -mcpu=cortex-m33 -mthumb -mcmse -Os -ffreestanding -fdata-sections -ffunction-sections
67+
# wolfBoot boot-partition layout (must match wolfBoot's stm32h5-tz.config).
68+
# WOLFBOOT_BOOT_OFFSET is the offset (from 0x08000000) of the boot
69+
# partition; we pad wolfboot.bin up to this offset before appending the
70+
# signed NS image to produce factory.bin.
71+
WOLFBOOT_BOOT_OFFSET ?= 0x60000
72+
WOLFBOOT_SIGN_KEY ?= $(WOLFBOOT_ROOT)/wolfboot_signing_private_key.der
73+
74+
# Base compiler flags. -mcmse is intentionally NOT set: the NS app does
75+
# not declare or call any NSC entry stubs (no cmse_nonsecure_entry
76+
# functions). Adding -mcmse without those would be benign but
77+
# unnecessary and pulls in the secure-mode support runtime.
78+
CFLAGS := -mcpu=cortex-m33 -mthumb -Os -ffreestanding -fdata-sections -ffunction-sections
6479
CFLAGS += -g -ggdb -Wall -Wextra -Werror
6580
CFLAGS += -I. -I$(ROOT) -I$(ROOT)/src -I$(ROOT)/src/port -I$(ROOT)/src/port/stm32
6681
CFLAGS += -DSTM32H5
@@ -354,6 +369,40 @@ app.elf: $(OBJS) $(LDSCRIPT)
354369
app.bin: app.elf
355370
$(OBJCOPY) -O binary $< $@
356371

372+
# -----------------------------------------------------------------------------
373+
# TZEN=1: sign the NS image with wolfBoot keytools and combine with
374+
# wolfboot.bin to produce factory.bin (single artifact for OpenOCD).
375+
# -----------------------------------------------------------------------------
376+
377+
# Image header size MUST match the IMAGE_HEADER_SIZE that wolfBoot was
378+
# built with (config/examples/stm32h5-tz.config sets 1024). Sign reads
379+
# it from the env; the default for ECC256 is only 256 bytes, which
380+
# would put the vector table at 0x08060100 instead of 0x08060400.
381+
WOLFBOOT_IMAGE_HEADER_SIZE ?= 1024
382+
383+
# Signed NS image: wolfBoot's sign tool prepends a 1024-byte image
384+
# header; the resulting app_v1_signed.bin loads at 0x08060000 and the
385+
# vector table lands at 0x08060400 (which target_tzen.ld assumes).
386+
app_v1_signed.bin: app.bin $(WOLFBOOT_SIGN_KEY)
387+
IMAGE_HEADER_SIZE=$(WOLFBOOT_IMAGE_HEADER_SIZE) \
388+
$(WOLFBOOT_ROOT)/tools/keytools/sign --ecc256 --sha256 \
389+
app.bin $(WOLFBOOT_SIGN_KEY) 1
390+
391+
signed: app_v1_signed.bin
392+
393+
# Combined factory image: wolfBoot stub padded out to the boot
394+
# partition offset, followed by the signed NS image. Flash with
395+
# `program factory.bin 0x08000000` in one step.
396+
factory.bin: app_v1_signed.bin $(WOLFBOOT_ROOT)/wolfboot.bin
397+
$(OBJCOPY) -I binary -O binary --pad-to=$(WOLFBOOT_BOOT_OFFSET) \
398+
--gap-fill=0xFF $(WOLFBOOT_ROOT)/wolfboot.bin wolfboot_padded.bin
399+
cat wolfboot_padded.bin app_v1_signed.bin > factory.bin
400+
@echo "factory.bin: $$(stat -c%s factory.bin) bytes"
401+
@echo " wolfBoot: $$(stat -c%s $(WOLFBOOT_ROOT)/wolfboot.bin) bytes (padded to $(WOLFBOOT_BOOT_OFFSET))"
402+
@echo " signed app: $$(stat -c%s app_v1_signed.bin) bytes"
403+
404+
factory: factory.bin
405+
357406
%.o: %.c
358407
$(CC) $(CFLAGS) -c $< -o $@
359408

@@ -363,6 +412,7 @@ $(WOLFSSL_ROOT)/%.o: $(WOLFSSL_ROOT)/%.c
363412

364413
clean:
365414
rm -f *.o app.elf app.bin
415+
rm -f app_v1_signed.bin factory.bin wolfboot_padded.bin
366416
rm -f $(ROOT)/src/*.o
367417
rm -f $(ROOT)/src/port/*.o
368418
rm -f $(ROOT)/src/port/stm32/*.o

src/port/stm32h563/config.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,24 @@
2828
#define ETHERNET
2929
#define LINK_MTU 1536
3030

31+
#if TZEN_ENABLED
32+
/* TZEN=1: NS app under wolfBoot has only 384 KB NS RAM (SRAM2+SRAM3,
33+
* since wolfBoot keeps SRAM1 secure for its own .bss/heap). Scale the
34+
* preallocated socket pool down to fit baseline TCP echo + TLS in that
35+
* window; the MQTT broker config (5 client slots, 4xMTU buffers) does
36+
* not fit and is not enabled in this mode yet. */
37+
#define MAX_TCPSOCKETS 8
38+
#define MAX_UDPSOCKETS 2
39+
#define MAX_ICMPSOCKETS 1
40+
#define RXBUF_SIZE (LINK_MTU * 2)
41+
#define TXBUF_SIZE (LINK_MTU * 2)
42+
#else
3143
#define MAX_TCPSOCKETS 17 /* 12 base + 5 for MQTT broker (listen + 4 clients) */
3244
#define MAX_UDPSOCKETS 2
3345
#define MAX_ICMPSOCKETS 1 /* Reduced from 2 */
3446
#define RXBUF_SIZE (LINK_MTU * 4) /* Reduced for RAM fit with broker */
3547
#define TXBUF_SIZE (LINK_MTU * 4) /* Reduced for RAM fit with broker */
48+
#endif
3649

3750
#define MAX_NEIGHBORS 16
3851

0 commit comments

Comments
 (0)