Skip to content

Commit 9cd5e75

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

47 files changed

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