Skip to content

Commit 4a86648

Browse files
committed
Add compressed firmware updates installed into an execute-in-place slot
1 parent aa9685b commit 4a86648

32 files changed

Lines changed: 3385 additions & 39 deletions
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
name: Compressed images - test with simulator target
2+
3+
on:
4+
push:
5+
branches: [ 'master', 'main', 'release/**' ]
6+
pull_request:
7+
branches: [ '*' ]
8+
9+
jobs:
10+
compressed_update_simulator_tests:
11+
runs-on: ubuntu-latest
12+
container:
13+
image: ghcr.io/wolfssl/wolfboot-ci-sim:v1.0
14+
timeout-minutes: 20
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
# The sink decoder is a separate output path in src/gzip.c, and
19+
# GZIP_SMALL selects a different Huffman decoder behind it, so both
20+
# have to be exercised and not merely built. The window sizes the
21+
# decoder's buffer and bounds back-references, so the smallest and
22+
# the default are both run.
23+
sign: [ ED25519, ECC256 ]
24+
window: [ 4096, 16384 ]
25+
# The container on internal flash is read in place; on external
26+
# flash it is pulled in through the read callback, which is a
27+
# different path through the decoder's input.
28+
config: [ sim-compressed-update, sim-compressed-update-ext,
29+
sim-compressed-update-ext-enc ]
30+
31+
steps:
32+
- uses: actions/checkout@v4
33+
with:
34+
submodules: true
35+
36+
- name: Trust workspace
37+
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
38+
39+
- name: make clean
40+
run: |
41+
make distclean
42+
43+
- name: Install zlib headers
44+
run: |
45+
apt-get update && apt-get install -y zlib1g-dev
46+
47+
- name: Select config
48+
run: |
49+
cp config/examples/${{ matrix.config }}.config .config
50+
sed -i "s/^SIGN?=.*/SIGN?=${{ matrix.sign }}/" .config
51+
sed -i "s/^WOLFBOOT_COMPRESSED_WINDOW=.*//" .config
52+
echo "WOLFBOOT_COMPRESSED_WINDOW=${{ matrix.window }}" >> .config
53+
54+
- name: Build tools
55+
run: |
56+
make -C tools/keytools && make -C tools/bin-assemble
57+
58+
# Not a bare `make`: that also assembles internal_flash.dd with the
59+
# update partition inside it, which is wrong when the partition lives
60+
# on the external device. The test target below builds both images.
61+
- name: Build wolfboot.elf and test-app/image.elf
62+
run: |
63+
make clean && make wolfboot.elf test-app/image.elf
64+
65+
- name: Run the compressed update simulator test
66+
run: |
67+
./tools/scripts/sim-compressed-update.sh
68+
69+
compressed_update_cross_build:
70+
runs-on: ubuntu-latest
71+
container:
72+
image: ghcr.io/wolfssl/wolfboot-ci-arm:v1.0
73+
timeout-minutes: 20
74+
strategy:
75+
fail-fast: false
76+
matrix:
77+
config: [ stm32h7-compressed-update, stm32f439zi-compressed-update ]
78+
79+
steps:
80+
- uses: actions/checkout@v4
81+
with:
82+
submodules: true
83+
84+
- name: Trust workspace
85+
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
86+
87+
- name: Install zlib headers
88+
run: |
89+
apt-get update && apt-get install -y zlib1g-dev
90+
91+
- name: Select config
92+
run: |
93+
cp config/examples/${{ matrix.config }}.config .config
94+
95+
- name: Build
96+
run: |
97+
make clean && make keysclean && make
98+
99+
# The installer walks the boot partition a sector at a time and the
100+
# trailer lives in its last sector, so a partition that is not a whole
101+
# number of sectors would have its tail erased by the trailer write.
102+
- name: Check the partition geometry
103+
run: |
104+
BOOT=$(grep -E '^WOLFBOOT_PARTITION_SIZE\??=' .config | tail -1 | cut -d= -f2)
105+
UPD=$(grep -E '^WOLFBOOT_PARTITION_UPDATE_SIZE\??=' .config | tail -1 | cut -d= -f2)
106+
SEC=$(grep -E '^WOLFBOOT_SECTOR_SIZE\??=' .config | tail -1 | cut -d= -f2)
107+
echo "boot $BOOT, update $UPD, sector $SEC"
108+
[ $(($BOOT % $SEC)) -eq 0 ] || { echo "boot not sector aligned"; exit 1; }
109+
[ $(($UPD % $SEC)) -eq 0 ] || { echo "update not sector aligned"; exit 1; }
110+
# The point of the mode: the update partition is the smaller one.
111+
[ $(($UPD)) -lt $(($BOOT)) ] || { echo "update is not smaller than boot"; exit 1; }
112+
echo "ok: update is $(( 100 * $UPD / $BOOT ))% of boot"
113+
114+
# The window knob has to reach the decoder's buffer, which is the RAM
115+
# the mode actually costs.
116+
- name: Check the decoder window buffer was sized from the config
117+
run: |
118+
WIN=$(grep -E '^WOLFBOOT_COMPRESSED_WINDOW\??=' .config | tail -1 | cut -d= -f2)
119+
SZ=$(arm-none-eabi-nm -S wolfboot.elf | awk '$4=="compressed_window" {print $2}')
120+
[ -n "$SZ" ] || { echo "compressed_window not linked in"; exit 1; }
121+
WANT=$((2 * $WIN))
122+
[ $((0x$SZ)) -eq $WANT ] || \
123+
{ echo "compressed_window is $((0x$SZ)) bytes, expected $WANT"; exit 1; }
124+
echo "ok: compressed_window is $((0x$SZ)) bytes for a $WIN window"
125+
126+
# Signing has to refuse a container the update partition cannot hold,
127+
# so a release that compresses worse than the split fails here rather
128+
# than on the device.
129+
- name: Check a container is produced and fits
130+
run: |
131+
UPD=$(grep -E '^WOLFBOOT_PARTITION_UPDATE_SIZE\??=' .config | tail -1 | cut -d= -f2)
132+
SEC=$(grep -E '^WOLFBOOT_SECTOR_SIZE\??=' .config | tail -1 | cut -d= -f2)
133+
SIGN=$(grep -E '^SIGN\??=' .config | tail -1 | cut -d= -f2 | tr 'A-Z' 'a-z')
134+
HASH=$(grep -E '^HASH\??=' .config | tail -1 | cut -d= -f2 | tr 'A-Z' 'a-z')
135+
WIN=$(grep -E '^WOLFBOOT_COMPRESSED_WINDOW\??=' .config | tail -1 | cut -d= -f2)
136+
WOLFBOOT_PARTITION_SIZE=$(grep -E '^WOLFBOOT_PARTITION_SIZE\??=' .config | tail -1 | cut -d= -f2) \
137+
WOLFBOOT_PARTITION_UPDATE_SIZE=$UPD WOLFBOOT_SECTOR_SIZE=$SEC \
138+
WOLFBOOT_COMPRESSED_WINDOW=$WIN \
139+
./tools/keytools/sign --compress --$SIGN --$HASH \
140+
test-app/image.elf wolfboot_signing_private_key.der 2
141+
SZ=$(stat -c%s test-app/image_v2_signed_compressed.bin)
142+
LIMIT=$(( $(($UPD)) - $(($SEC)) ))
143+
echo "container $SZ bytes, limit $LIMIT"
144+
[ "$SZ" -le "$LIMIT" ] || { echo "container does not fit"; exit 1; }
145+
echo "ok: container fits the update partition"

‎Makefile‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ endif
362362

363363
# Environment variables for sign tool
364364
SIGN_ENV=IMAGE_HEADER_SIZE=$(IMAGE_HEADER_SIZE) \
365+
WOLFBOOT_COMPRESSED_WINDOW=$(WOLFBOOT_COMPRESSED_WINDOW) \
365366
WOLFBOOT_PARTITION_SIZE=$(WOLFBOOT_PARTITION_SIZE) \
366367
WOLFBOOT_PARTITION_UPDATE_SIZE=$(WOLFBOOT_PARTITION_UPDATE_SIZE) \
367368
WOLFBOOT_SECTOR_SIZE=$(WOLFBOOT_SECTOR_SIZE) \
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
ARCH=sim
2+
TARGET=sim
3+
SIGN?=ED25519
4+
HASH?=SHA256
5+
WOLFBOOT_SMALL_STACK?=0
6+
SPI_FLASH=0
7+
EXT_FLASH=1
8+
ENCRYPT=1
9+
ENCRYPT_WITH_AES128=1
10+
DEBUG=1
11+
12+
# Compressed update partition on external flash, encrypted at rest, boot
13+
# partition executed in place from internal flash. ENCRYPT=1 hooks the
14+
# external flash read, which is the same seam the installer and the image
15+
# hasher pull the container through, so it is decrypted on its way in.
16+
COMPRESSED_UPDATE=1
17+
GZIP=1
18+
19+
# LZ77 window the image was compressed with. The decoder needs twice this in
20+
# RAM and refuses an image compressed with a larger one.
21+
WOLFBOOT_COMPRESSED_WINDOW=16384
22+
23+
# The update partition holds only the container, so it is sized for that.
24+
# sizes should be multiple of system page size
25+
WOLFBOOT_PARTITION_SIZE=0x50000
26+
WOLFBOOT_PARTITION_UPDATE_SIZE=0x2C000
27+
WOLFBOOT_SECTOR_SIZE=0x1000
28+
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80000
29+
# on external flash, so it starts at the base of that device
30+
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x00000
31+
# Unused in this mode: there is no swap step.
32+
WOLFBOOT_PARTITION_SWAP_ADDRESS=0x180000
33+
34+
# required for keytools
35+
WOLFBOOT_FIXED_PARTITIONS=1
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
ARCH=sim
2+
TARGET=sim
3+
SIGN?=ED25519
4+
HASH?=SHA256
5+
WOLFBOOT_SMALL_STACK?=0
6+
SPI_FLASH=0
7+
EXT_FLASH=1
8+
DEBUG=1
9+
10+
# Compressed update partition on external flash, boot partition executed in
11+
# place from internal flash. This is the common microcontroller layout: a
12+
# small internal flash for the application and a cheap serial flash for the
13+
# incoming update. The container is not addressable, so wolfBoot pulls it in
14+
# through the external flash reader as it inflates.
15+
COMPRESSED_UPDATE=1
16+
GZIP=1
17+
18+
# LZ77 window the image was compressed with. The decoder needs twice this in
19+
# RAM and refuses an image compressed with a larger one.
20+
WOLFBOOT_COMPRESSED_WINDOW=16384
21+
22+
# The update partition holds only the container, so it is sized for that.
23+
# sizes should be multiple of system page size
24+
WOLFBOOT_PARTITION_SIZE=0x50000
25+
WOLFBOOT_PARTITION_UPDATE_SIZE=0x2C000
26+
WOLFBOOT_SECTOR_SIZE=0x1000
27+
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80000
28+
# on external flash, so it starts at the base of that device
29+
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x00000
30+
# Unused in this mode: there is no swap step.
31+
WOLFBOOT_PARTITION_SWAP_ADDRESS=0x180000
32+
33+
# required for keytools
34+
WOLFBOOT_FIXED_PARTITIONS=1
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
ARCH=sim
2+
TARGET=sim
3+
SIGN?=ED25519
4+
HASH?=SHA256
5+
WOLFBOOT_SMALL_STACK?=0
6+
SPI_FLASH=0
7+
DEBUG=1
8+
9+
# Compressed update partition, execute in place. UPDATE holds a signed gzip
10+
# stream whose payload is a complete signed image; wolfBoot verifies the
11+
# compressed bytes, inflates them into BOOT a chunk at a time, and BOOT is
12+
# then an ordinary partition that the normal boot path verifies.
13+
COMPRESSED_UPDATE=1
14+
GZIP=1
15+
16+
# LZ77 window the image was compressed with. The decoder needs twice this in
17+
# RAM and refuses an image compressed with a larger one.
18+
WOLFBOOT_COMPRESSED_WINDOW=16384
19+
20+
# UPDATE only ever holds the compressed image, so it is sized for that and
21+
# the flash a second full copy would have cost goes to BOOT instead. The
22+
# signing tool refuses to emit an artifact that does not fit.
23+
# sizes should be multiple of system page size
24+
# Sized for a realistic ratio: the test fixture compresses to about 63%,
25+
# so the container fills roughly 90% of the update partition and the fit
26+
# check is exercised near its real margin rather than with slack.
27+
WOLFBOOT_PARTITION_SIZE=0x50000
28+
WOLFBOOT_PARTITION_UPDATE_SIZE=0x2C000
29+
WOLFBOOT_SECTOR_SIZE=0x1000
30+
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80000
31+
# if on external flash, it should be multiple of system page size
32+
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x100000
33+
# Unused in this mode: there is no swap step. Still required by the keytools
34+
# and the partition trailer layout, so it stays reserved.
35+
WOLFBOOT_PARTITION_SWAP_ADDRESS=0x180000
36+
37+
# required for keytools
38+
WOLFBOOT_FIXED_PARTITIONS=1
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# NUCLEO-F439ZI with a compressed update partition, executing in place.
2+
#
3+
# The update partition only ever holds the compressed container, so it is
4+
# sized for that and the flash a second full copy would have cost goes to
5+
# the application: 512KB of executable instead of the 448KB an equal split
6+
# of the same space would allow.
7+
#
8+
# Everything stays inside the first 1MB because hal/stm32f4.c describes only
9+
# that much flash. The F439ZI has 2MB, but the second bank's sector numbers
10+
# are not contiguous with the first's, so this HAL cannot erase there; the
11+
# HAL now refuses such a layout at build time rather than failing silently.
12+
#
13+
# The split has to cover the worst ratio the product will ever ship. 512/384
14+
# leaves room for a container up to 75% of the image; measured embedded
15+
# images land near 65-70%. The signing tool refuses an artifact that does
16+
# not fit, so a release that compresses worse than the split fails at build
17+
# time, not on the device.
18+
#
19+
# Console is USART3 on PD8/PD9, the ST-LINK virtual COM port on the
20+
# NUCLEO-144 boards, via hal/uart/uart_drv_stm32f4.c.
21+
ARCH?=ARM
22+
TARGET?=stm32f4
23+
SIGN?=ED25519
24+
HASH?=SHA256
25+
DEBUG?=1
26+
DEBUG_UART?=1
27+
CFLAGS_EXTRA+=-DUSE_UART3
28+
VTOR?=1
29+
NVM_FLASH_WRITEONCE?=0
30+
SPMATH?=1
31+
32+
# 168MHz from an 8MHz HSE, the NUCLEO-144 default.
33+
CLOCK_SPEED?=168000000
34+
STM32_PLLM?=8
35+
STM32_PLLN?=336
36+
STM32_PLLP?=2
37+
STM32_PLLQ?=7
38+
39+
# Compressed update partition, inflated into BOOT, which keeps executing in
40+
# place. Implies DISABLE_BACKUP: there is no swap and no backup of the
41+
# outgoing image. An interrupted install restarts from an intact source,
42+
# because the update partition is only ever read.
43+
COMPRESSED_UPDATE?=1
44+
GZIP?=1
45+
GZIP_SMALL?=0
46+
47+
# LZ77 window the image was compressed with. The decoder needs twice this
48+
# in RAM, so 16KB costs 32KB of wolfBoot bss on a part with 192KB of SRAM.
49+
# Sign with 'sign --compress=14' to match.
50+
WOLFBOOT_COMPRESSED_WINDOW?=16384
51+
52+
WOLFBOOT_SECTOR_SIZE?=0x20000
53+
WOLFBOOT_PARTITION_SIZE?=0x80000
54+
WOLFBOOT_PARTITION_UPDATE_SIZE?=0x60000
55+
WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x08020000
56+
WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x080A0000
57+
# No swap step in this mode, so no swap partition is reserved.
58+
WOLFBOOT_PARTITION_SWAP_ADDRESS?=0
59+
60+
# required for keytools
61+
WOLFBOOT_FIXED_PARTITIONS=1
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# STM32H7 with a compressed update partition, executing in place.
2+
#
3+
# The H743 has 2MB of flash in 128KB sectors. The update partition only ever
4+
# holds the compressed container, so it is sized for that and the flash a
5+
# second full copy would have cost goes to the application: 1MB of
6+
# executable against the 896KB an equal split of the same 15 sectors would
7+
# allow, and against the 832KB the stock stm32h7.config configures.
8+
#
9+
# The split has to cover the worst ratio the product will ever ship. 8/7
10+
# leaves room for a container up to 87.5% of the image, which no real
11+
# firmware approaches; measured embedded images land near 65-70%. The
12+
# signing tool refuses an artifact that does not fit, so a release that
13+
# compresses worse than the split fails at build time, not on the device.
14+
ARCH?=ARM
15+
TARGET?=stm32h7
16+
SIGN?=ECC256
17+
HASH?=SHA256
18+
DEBUG?=0
19+
DEBUG_UART?=0
20+
VTOR?=1
21+
NO_ASM?=0
22+
EXT_FLASH?=0
23+
SPI_FLASH?=0
24+
ALLOW_DOWNGRADE?=0
25+
NVM_FLASH_WRITEONCE?=0
26+
WOLFBOOT_VERSION?=1
27+
V?=0
28+
SPMATH?=1
29+
RAM_CODE?=0
30+
DUALBANK_SWAP?=0
31+
32+
# Compressed update partition, inflated into BOOT, which keeps executing in
33+
# place. Implies DISABLE_BACKUP: there is no swap and no backup of the
34+
# outgoing image. An interrupted install restarts from an intact source,
35+
# because the update partition is only ever read.
36+
COMPRESSED_UPDATE?=1
37+
GZIP?=1
38+
GZIP_SMALL?=0
39+
40+
# LZ77 window the image was compressed with. The decoder needs twice this
41+
# in RAM; wolfBoot runs from the 128KB DTCM here, so 32KB of buffer plus the
42+
# decoder's tables fits comfortably. Sign with 'sign --compress=14'.
43+
WOLFBOOT_COMPRESSED_WINDOW?=16384
44+
45+
WOLFBOOT_SECTOR_SIZE?=0x20000
46+
WOLFBOOT_PARTITION_SIZE?=0x100000
47+
WOLFBOOT_PARTITION_UPDATE_SIZE?=0xE0000
48+
WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x8020000
49+
WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x8120000
50+
# No swap step in this mode, so no swap partition is reserved.
51+
WOLFBOOT_PARTITION_SWAP_ADDRESS?=0
52+
53+
# required for keytools
54+
WOLFBOOT_FIXED_PARTITIONS=1

0 commit comments

Comments
 (0)