Skip to content

Commit 8fa90d5

Browse files
committed
ci: add swap-move OTA resilience canary
1 parent 7448115 commit 8fa90d5

1 file changed

Lines changed: 185 additions & 0 deletions

File tree

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
name: OTA resilience canary
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- .github/workflows/ota-resilience-canary.yml
9+
- boot/**
10+
- scripts/**
11+
- zephyr/**
12+
- root-rsa-2048.pem
13+
schedule:
14+
- cron: '0 8 * * 1'
15+
workflow_dispatch:
16+
inputs:
17+
zephyr_ref:
18+
description: 'Which Zephyr release tag should be used?'
19+
required: true
20+
default: 'v3.7.0'
21+
22+
permissions:
23+
contents: read
24+
25+
concurrency:
26+
group: ${{ github.workflow }}-${{ github.ref }}
27+
cancel-in-progress: true
28+
29+
env:
30+
ZEPHYR_VERSION: v3.7.0
31+
32+
jobs:
33+
swap-move-canary:
34+
runs-on: ubuntu-24.04
35+
container:
36+
image: zephyrprojectrtos/ci-base:v0.28.4
37+
options: '--entrypoint /bin/bash'
38+
timeout-minutes: 60
39+
defaults:
40+
run:
41+
shell: bash
42+
43+
steps:
44+
- name: Set Zephyr version for manual runs
45+
if: github.event_name == 'workflow_dispatch'
46+
run: |
47+
echo "ZEPHYR_VERSION=${{ github.event.inputs.zephyr_ref }}" >> $GITHUB_ENV
48+
49+
- name: Checkout pinned Zephyr release
50+
uses: actions/checkout@v4
51+
with:
52+
repository: zephyrproject-rtos/zephyr
53+
ref: ${{ env.ZEPHYR_VERSION }}
54+
path: repos/zephyr
55+
56+
- name: Install Zephyr Python requirements
57+
working-directory: repos/zephyr
58+
run: pip install -r scripts/requirements-actions.txt --require-hashes
59+
60+
- name: Setup Zephyr SDK and workspace
61+
uses: zephyrproject-rtos/action-zephyr-setup@c125c5ebeeadbd727fa740b407f862734af1e52a
62+
with:
63+
base-path: repos/zephyr
64+
toolchains: arm-zephyr-eabi
65+
sdk-version: 0.17.4
66+
west-project-filter: -.*,+cmsis,+cmsis_6,+hal_nordic,+mbedtls,+mcuboot,+tinycrypt,+zcbor
67+
ccache-max-size: 256MB
68+
69+
- name: Checkout MCUboot
70+
uses: actions/checkout@v4
71+
with:
72+
fetch-depth: 0
73+
path: repos/bootloader/mcuboot
74+
75+
- name: Build MCUboot bootloader (swap-move, RSA-2048)
76+
working-directory: repos
77+
run: |
78+
export ZEPHYR_BASE=$(pwd)/zephyr
79+
export ZEPHYR_TOOLCHAIN_VARIANT=zephyr
80+
west build -b nrf52840dk_nrf52840 bootloader/mcuboot/boot/zephyr \
81+
-d build_boot \
82+
-p always \
83+
-- \
84+
-DCONFIG_BOOT_SWAP_USING_MOVE=y \
85+
-DCONFIG_BOOT_SIGNATURE_TYPE_RSA=y \
86+
-DCONFIG_BOOT_SIGNATURE_TYPE_RSA_LEN=2048 \
87+
-DCONFIG_BOOT_VALIDATE_SLOT0=n
88+
89+
- name: Build test application images
90+
working-directory: repos
91+
run: |
92+
set -euo pipefail
93+
export ZEPHYR_BASE=$(pwd)/zephyr
94+
export ZEPHYR_TOOLCHAIN_VARIANT=zephyr
95+
pip3 install --quiet click intelhex cbor2 cryptography
96+
97+
# Build hello_world as the test application.
98+
west build -b nrf52840dk_nrf52840 zephyr/samples/hello_world \
99+
-d build_app \
100+
-p always \
101+
-- \
102+
-DCONFIG_BOOTLOADER_MCUBOOT=y
103+
104+
IMGTOOL="python3 bootloader/mcuboot/scripts/imgtool.py"
105+
KEY="bootloader/mcuboot/root-rsa-2048.pem"
106+
107+
# Sign as v1 (exec slot) — confirmed so MCUboot boots it directly.
108+
${IMGTOOL} sign --key ${KEY} \
109+
--header-size 0x200 --align 4 --slot-size 0x76000 \
110+
--version 1.0.0 --pad --confirm \
111+
build_app/zephyr/zephyr.bin slot0.bin
112+
113+
# Sign as v2 (staging slot) — pending upgrade, not confirmed.
114+
${IMGTOOL} sign --key ${KEY} \
115+
--header-size 0x200 --align 4 --slot-size 0x76000 \
116+
--version 2.0.0 --pad \
117+
build_app/zephyr/zephyr.bin slot1.bin
118+
119+
ls -la slot0.bin slot1.bin
120+
121+
- name: Write canary profile
122+
working-directory: repos
123+
run: |
124+
BOOT_ELF="$(realpath build_boot/zephyr/zephyr.elf)"
125+
SLOT0="$(realpath slot0.bin)"
126+
SLOT1="$(realpath slot1.bin)"
127+
128+
cat > ota_resilience_profile.yaml << YAML
129+
schema_version: 1
130+
name: mcuboot_swap_move_canary
131+
description: >
132+
Post-merge OTA resilience canary for MCUboot swap-using-move on
133+
nrf52840dk. Upgrade scenario: slot0 has confirmed v1, slot1 has
134+
pending v2. After swap, exec slot should contain the v2 image.
135+
136+
platform: platforms/cortex_m4_flash_fast.repl
137+
bootloader:
138+
elf: ${BOOT_ELF}
139+
entry: 0x00000000
140+
memory:
141+
sram: { start: 0x20000000, end: 0x20040000 }
142+
write_granularity: 4
143+
slots:
144+
exec: { base: 0x0000C000, size: 0x76000 }
145+
staging: { base: 0x00082000, size: 0x76000 }
146+
images:
147+
exec: ${SLOT0}
148+
staging: ${SLOT1}
149+
success_criteria:
150+
vtor_in_slot: exec
151+
image_hash: true
152+
expected_image: staging
153+
fault_sweep:
154+
mode: runtime
155+
evaluation_mode: execute
156+
max_writes: auto
157+
max_writes_cap: 200000
158+
run_duration: "5.0"
159+
max_step_limit: 50000000
160+
hash_bypass_symbols: ["bootutil_img_validate"]
161+
expect:
162+
should_find_issues: false
163+
YAML
164+
165+
# Strip leading whitespace from heredoc indentation.
166+
sed -i 's/^ //' ota_resilience_profile.yaml
167+
cat ota_resilience_profile.yaml
168+
169+
- name: Run OTA resilience sweep
170+
id: ota_resilience
171+
uses: neilberkman/tardigrade@02bc0b1fe66d41a15cf10592a0563f627da3fb00
172+
with:
173+
profile: repos/ota_resilience_profile.yaml
174+
quick: "false"
175+
workers: "2"
176+
177+
- name: Upload artifacts
178+
if: always()
179+
uses: actions/upload-artifact@v4
180+
with:
181+
name: ota-resilience-swap-move
182+
if-no-files-found: ignore
183+
path: |
184+
repos/ota_resilience_profile.yaml
185+
${{ steps.ota_resilience.outputs.report-path }}

0 commit comments

Comments
 (0)