Skip to content

Commit 91c3ae7

Browse files
committed
Add compressed firmware images, executed from RAM or installed in place
1 parent 3bd9770 commit 91c3ae7

47 files changed

Lines changed: 4642 additions & 38 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
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_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+
# GZIP_SMALL selects a different Huffman decoder in src/gzip.c, so
19+
# both have to be exercised and not merely built.
20+
sign: [ ED25519, ECC256 ]
21+
gzip_small: [ 0, 1 ]
22+
23+
steps:
24+
- uses: actions/checkout@v4
25+
with:
26+
submodules: true
27+
28+
- name: Trust workspace
29+
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
30+
31+
- name: make clean
32+
run: |
33+
make distclean
34+
35+
- name: Select config
36+
run: |
37+
cp config/examples/sim-compressed-ram.config .config
38+
sed -i "s/^SIGN?=.*/SIGN?=${{ matrix.sign }}/" .config
39+
sed -i "s/^GZIP_SMALL=.*//" .config
40+
echo "GZIP_SMALL=${{ matrix.gzip_small }}" >> .config
41+
42+
- name: Build tools
43+
run: |
44+
make -C tools/keytools && make -C tools/bin-assemble
45+
46+
- name: Build wolfboot.elf and test-app/image.elf
47+
run: |
48+
make clean && make
49+
50+
- name: Assemble the compressed A/B slots
51+
run: |
52+
make test-sim-compressed-ram-with-update
53+
54+
- name: Run the compressed image simulator test
55+
run: |
56+
./tools/scripts/sim-compressed-ram.sh
57+
58+
compressed_update_simulator_tests:
59+
runs-on: ubuntu-latest
60+
container:
61+
image: ghcr.io/wolfssl/wolfboot-ci-sim:v1.0
62+
timeout-minutes: 20
63+
strategy:
64+
fail-fast: false
65+
matrix:
66+
# The sink decoder is a separate output path in src/gzip.c, and
67+
# GZIP_SMALL selects a different Huffman decoder behind it, so both
68+
# have to be exercised and not merely built. The window sizes the
69+
# decoder's buffer and bounds back-references, so the smallest and
70+
# the default are both run.
71+
sign: [ ED25519, ECC256 ]
72+
window: [ 4096, 16384 ]
73+
74+
steps:
75+
- uses: actions/checkout@v4
76+
with:
77+
submodules: true
78+
79+
- name: Trust workspace
80+
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
81+
82+
- name: make clean
83+
run: |
84+
make distclean
85+
86+
- name: Select config
87+
run: |
88+
cp config/examples/sim-compressed-update.config .config
89+
sed -i "s/^SIGN?=.*/SIGN?=${{ matrix.sign }}/" .config
90+
sed -i "s/^WOLFBOOT_COMPRESSED_WINDOW=.*//" .config
91+
echo "WOLFBOOT_COMPRESSED_WINDOW=${{ matrix.window }}" >> .config
92+
93+
- name: Build tools
94+
run: |
95+
make -C tools/keytools && make -C tools/bin-assemble
96+
97+
- name: Build wolfboot.elf and test-app/image.elf
98+
run: |
99+
make clean && make
100+
101+
- name: Run the compressed update simulator test
102+
run: |
103+
./tools/scripts/sim-compressed-update.sh
104+
105+
compressed_encrypted_simulator_tests:
106+
runs-on: ubuntu-latest
107+
container:
108+
image: ghcr.io/wolfssl/wolfboot-ci-sim:v1.0
109+
timeout-minutes: 20
110+
strategy:
111+
fail-fast: false
112+
matrix:
113+
# Each cipher has its own block size, which the per-slot keystream
114+
# stride is derived from, so all three have to run.
115+
cipher: [ chacha, aes128, aes256 ]
116+
117+
steps:
118+
- uses: actions/checkout@v4
119+
with:
120+
submodules: true
121+
122+
- name: Trust workspace
123+
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
124+
125+
- name: make clean
126+
run: |
127+
make distclean
128+
129+
- name: Select config
130+
run: |
131+
cp config/examples/sim-compressed-ram-enc.config .config
132+
case "${{ matrix.cipher }}" in
133+
aes128) echo "ENCRYPT_WITH_AES128=1" >> .config ;;
134+
aes256) echo "ENCRYPT_WITH_AES256=1" >> .config ;;
135+
esac
136+
137+
- name: Build tools
138+
run: |
139+
make -C tools/keytools && make -C tools/bin-assemble
140+
141+
- name: Build wolfboot.elf and test-app/image.elf
142+
run: |
143+
make clean && make
144+
145+
- name: Assemble the encrypted compressed A/B slots
146+
run: |
147+
make test-sim-compressed-ram-enc-with-update
148+
149+
- name: Run the encrypted compressed image simulator test
150+
run: |
151+
./tools/scripts/sim-compressed-ram-enc.sh
152+
153+
compressed_cross_build:
154+
runs-on: ubuntu-latest
155+
container:
156+
image: ghcr.io/wolfssl/wolfboot-ci-arm:v1.0
157+
timeout-minutes: 20
158+
strategy:
159+
fail-fast: false
160+
matrix:
161+
config: [ stm32h7-compressed-ram, stm32f407-compressed-ram ]
162+
163+
steps:
164+
- uses: actions/checkout@v4
165+
with:
166+
submodules: true
167+
168+
- name: Trust workspace
169+
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
170+
171+
- name: Select config
172+
run: |
173+
cp config/examples/${{ matrix.config }}.config .config
174+
175+
- name: Build
176+
run: |
177+
make clean && make keysclean && make
178+
179+
# A link that succeeds but places the vector table somewhere other
180+
# than the load address boots into garbage, and nothing catches that
181+
# without hardware. Assert the layout from the ELF instead.
182+
- name: Check the application is linked for the load address
183+
run: |
184+
LOAD=$(grep -E '^WOLFBOOT_LOAD_ADDRESS\??=' .config | tail -1 | cut -d= -f2)
185+
SIZE=$(grep -E '^WOLFBOOT_RAMBOOT_MAX_SIZE\??=' .config | tail -1 | cut -d= -f2)
186+
echo "load address $LOAD, region size $SIZE"
187+
# .text must start exactly at the load address, with LMA == VMA
188+
arm-none-eabi-objdump -h test-app/image.elf | \
189+
awk -v want=$(printf "%08x" $((LOAD))) '
190+
$2==".text" { if ($4!=want || $5!=want) {
191+
printf "`.text` at vma %s lma %s, expected %s\n",$4,$5,want; exit 1 }
192+
print "ok: .text at " $4 " (lma " $5 ")" }'
193+
# word 0 of the image is the initial stack pointer and must be the
194+
# top of the load region
195+
SP=$(od -An -t x4 -N 4 test-app/image.bin | tr -d ' ')
196+
WANT_SP=$(printf "%08x" $((LOAD + SIZE)))
197+
if [ "$SP" != "$WANT_SP" ]; then
198+
echo "initial SP is 0x$SP, expected 0x$WANT_SP"; exit 1
199+
fi
200+
echo "ok: initial SP 0x$SP"
201+
202+
- name: Check wolfBoot does not overlap the load region
203+
run: |
204+
LOAD=$(grep -E '^WOLFBOOT_LOAD_ADDRESS\??=' .config | tail -1 | cut -d= -f2)
205+
END=$(arm-none-eabi-nm wolfboot.elf | awk '$3=="_end" {print $1}')
206+
# Only meaningful when wolfBoot's RAM and the load region share an
207+
# address space (STM32F4); on the H7 they are different SRAMs.
208+
if [ $((0x$END)) -ge $((LOAD)) ] && \
209+
[ $((0x$END)) -lt $((LOAD + 0x100000)) ]; then
210+
echo "wolfBoot _end 0x$END is inside the load region at $LOAD"; exit 1
211+
fi
212+
echo "ok: wolfBoot _end 0x$END clear of the load region at $LOAD"
213+
214+
# The execute-in-place mode has no load address and no RAM-linked app, so
215+
# it gets its own geometry assertions rather than the ones above.
216+
compressed_update_cross_build:
217+
runs-on: ubuntu-latest
218+
container:
219+
image: ghcr.io/wolfssl/wolfboot-ci-arm:v1.0
220+
timeout-minutes: 20
221+
strategy:
222+
fail-fast: false
223+
matrix:
224+
config: [ stm32h7-compressed-update, stm32f439zi-compressed-update ]
225+
226+
steps:
227+
- uses: actions/checkout@v4
228+
with:
229+
submodules: true
230+
231+
- name: Trust workspace
232+
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
233+
234+
- name: Select config
235+
run: |
236+
cp config/examples/${{ matrix.config }}.config .config
237+
238+
- name: Build
239+
run: |
240+
make clean && make keysclean && make
241+
242+
# The installer walks the boot partition a sector at a time and the
243+
# trailer lives in its last sector, so a partition that is not a whole
244+
# number of sectors would have its tail erased by the trailer write.
245+
- name: Check the partition geometry
246+
run: |
247+
BOOT=$(grep -E '^WOLFBOOT_PARTITION_SIZE\??=' .config | tail -1 | cut -d= -f2)
248+
UPD=$(grep -E '^WOLFBOOT_PARTITION_UPDATE_SIZE\??=' .config | tail -1 | cut -d= -f2)
249+
SEC=$(grep -E '^WOLFBOOT_SECTOR_SIZE\??=' .config | tail -1 | cut -d= -f2)
250+
echo "boot $BOOT, update $UPD, sector $SEC"
251+
[ $(($BOOT % $SEC)) -eq 0 ] || { echo "boot not sector aligned"; exit 1; }
252+
[ $(($UPD % $SEC)) -eq 0 ] || { echo "update not sector aligned"; exit 1; }
253+
# The point of the mode: the update partition is the smaller one.
254+
[ $(($UPD)) -lt $(($BOOT)) ] || { echo "update is not smaller than boot"; exit 1; }
255+
echo "ok: update is $(( 100 * $UPD / $BOOT ))% of boot"
256+
257+
# The window knob has to reach the decoder's buffer, which is the RAM
258+
# the mode actually costs.
259+
- name: Check the decoder window buffer was sized from the config
260+
run: |
261+
WIN=$(grep -E '^WOLFBOOT_COMPRESSED_WINDOW\??=' .config | tail -1 | cut -d= -f2)
262+
SZ=$(arm-none-eabi-nm -S wolfboot.elf | awk '$4=="compressed_window" {print $2}')
263+
[ -n "$SZ" ] || { echo "compressed_window not linked in"; exit 1; }
264+
WANT=$((2 * $WIN))
265+
[ $((0x$SZ)) -eq $WANT ] || \
266+
{ echo "compressed_window is $((0x$SZ)) bytes, expected $WANT"; exit 1; }
267+
echo "ok: compressed_window is $((0x$SZ)) bytes for a $WIN window"
268+
269+
# Signing has to refuse a container the update partition cannot hold,
270+
# so a release that compresses worse than the split fails here rather
271+
# than on the device.
272+
- name: Check a container is produced and fits
273+
run: |
274+
UPD=$(grep -E '^WOLFBOOT_PARTITION_UPDATE_SIZE\??=' .config | tail -1 | cut -d= -f2)
275+
SEC=$(grep -E '^WOLFBOOT_SECTOR_SIZE\??=' .config | tail -1 | cut -d= -f2)
276+
SIGN=$(grep -E '^SIGN\??=' .config | tail -1 | cut -d= -f2 | tr 'A-Z' 'a-z')
277+
HASH=$(grep -E '^HASH\??=' .config | tail -1 | cut -d= -f2 | tr 'A-Z' 'a-z')
278+
WOLFBOOT_PARTITION_SIZE=$(grep -E '^WOLFBOOT_PARTITION_SIZE\??=' .config | tail -1 | cut -d= -f2) \
279+
WOLFBOOT_PARTITION_UPDATE_SIZE=$UPD WOLFBOOT_SECTOR_SIZE=$SEC \
280+
./tools/keytools/sign --compress --$SIGN --$HASH \
281+
test-app/image.elf wolfboot_signing_private_key.der 2
282+
SZ=$(stat -c%s test-app/image_v2_signed_compressed.bin)
283+
LIMIT=$(( $(($UPD)) - $(($SEC)) ))
284+
echo "container $SZ bytes, limit $LIMIT"
285+
[ "$SZ" -le "$LIMIT" ] || { echo "container does not fit"; exit 1; }
286+
echo "ok: container fits the update partition"

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ bootmode1/
453453

454454
# Emulator test: generated linker scripts
455455
test-app/emu-test-apps/*/target.ld
456+
test-app/*.gz
456457

457458
# AMD/Xilinx Versal test artifacts
458459
uart_log*.txt

‎arch.mk‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3207,9 +3207,22 @@ else
32073207
endif
32083208
endif
32093209
else
3210-
ifeq ($(DUALBANK_SWAP),1)
3210+
ifeq ($(COMPRESSED_RAM_SLOTS),1)
3211+
# Compressed A/B slots, execute from RAM. Both slots hold a signed,
3212+
# gzip-compressed image; the RAM loader picks the highest valid version,
3213+
# verifies the signature over the compressed bytes and inflates to
3214+
# WOLFBOOT_LOAD_ADDRESS. There is no swap or install step, so the slot
3215+
# roles are decided per boot by version, as for the hardware bank swap.
3216+
ifeq ($(DUALBANK_SWAP),1)
3217+
$(error COMPRESSED_RAM_SLOTS=1 is incompatible with DUALBANK_SWAP=1)
3218+
endif
32113219
CFLAGS+=-DWOLFBOOT_DUALBOOT
3212-
UPDATE_OBJS:=src/update_flash_hwswap.o
3220+
UPDATE_OBJS:=src/update_ram.o
3221+
else
3222+
ifeq ($(DUALBANK_SWAP),1)
3223+
CFLAGS+=-DWOLFBOOT_DUALBOOT
3224+
UPDATE_OBJS:=src/update_flash_hwswap.o
3225+
endif
32133226
endif
32143227
endif
32153228

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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 A/B slots, executed from RAM, encrypted at rest.
10+
COMPRESSED_RAM_SLOTS=1
11+
COMPRESSED_ENCRYPT=1
12+
GZIP=1
13+
14+
# sizes should be multiple of system page size
15+
WOLFBOOT_PARTITION_SIZE=0x40000
16+
WOLFBOOT_SECTOR_SIZE=0x1000
17+
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80000
18+
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x100000
19+
WOLFBOOT_PARTITION_SWAP_ADDRESS=0x180000
20+
21+
# See sim-compressed-ram.config for why the load region sits above wolfboot.elf.
22+
WOLFBOOT_LOAD_ADDRESS=0x1000000
23+
WOLFBOOT_RAMBOOT_MAX_SIZE=0x40000
24+
25+
# An encrypted slot cannot be verified in place, so it is decrypted into a
26+
# staging region first. Must be disjoint from the load region, or the
27+
# decompressor would overwrite its own input.
28+
WOLFBOOT_COMPRESSED_STAGE_ADDRESS=0x1100000
29+
30+
# required for keytools
31+
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+
DEBUG=1
8+
9+
# Compressed A/B slots, executed from RAM. Both slots hold a signed gzip
10+
# stream; wolfBoot verifies the compressed bytes in place, inflates to
11+
# WOLFBOOT_LOAD_ADDRESS and boots there.
12+
COMPRESSED_RAM_SLOTS=1
13+
GZIP=1
14+
15+
# sizes should be multiple of system page size
16+
WOLFBOOT_PARTITION_SIZE=0x40000
17+
WOLFBOOT_SECTOR_SIZE=0x1000
18+
WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80000
19+
# if on external flash, it should be multiple of system page size
20+
WOLFBOOT_PARTITION_UPDATE_ADDRESS=0x100000
21+
WOLFBOOT_PARTITION_SWAP_ADDRESS=0x180000
22+
23+
# The simulator's do_boot() writes a whole partition from the load address,
24+
# so the load region must be at least WOLFBOOT_PARTITION_SIZE.
25+
#
26+
# WOLFBOOT_ORIGIN is 0 here (simulator flash lives at the runtime
27+
# sim_ram_base), so the loader's bootloader-overlap guard degenerates to
28+
# "below _end is suspect". wolfboot.elf is a non-PIE host binary loaded at
29+
# 0x400000, so keep the load region above it.
30+
WOLFBOOT_LOAD_ADDRESS=0x1000000
31+
WOLFBOOT_RAMBOOT_MAX_SIZE=0x40000
32+
33+
# required for keytools
34+
WOLFBOOT_FIXED_PARTITIONS=1

0 commit comments

Comments
 (0)