Skip to content

Commit 81461c0

Browse files
committed
feat: loader: support ota
Signed-off-by: Gilberto Conti <g.conti@arduino.cc>
1 parent 48be02b commit 81461c0

5 files changed

Lines changed: 177 additions & 0 deletions

File tree

boards.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ giga.debug.server.openocd.scripts.2=target/stm32h7x_dual_bank.cfg
7070
giga.debug.cortex-debug.custom.request=attach
7171
giga.debug.svd_file={runtime.platform.path}/svd/STM32H747_CM7.svd
7272

73+
giga.build.ota.magic=0x23410266
74+
giga.build.ota.sketch_offset=0xA0000
75+
giga.build.ota.pack_command_legacy="{runtime.tools.ota-pack.path}/ota-pack" -loader "{runtime.platform.path}/firmwares/zephyr-{build.variant}.bin" -sketch "{build.path}/{build.project_name}.{upload.extension}" -offset {build.ota.sketch_offset} -magic {build.ota.magic} -output "{build.path}/{build.project_name}.legacy.ota"
76+
giga.build.ota.pack_command="{runtime.tools.ota-pack.path}/ota-pack" --sketch-only -sketch "{build.path}/{build.project_name}.{upload.extension}" -magic {build.ota.magic} -output "{build.path}/{build.project_name}.ota"
77+
7378
##########################################################################################
7479

7580
nano33ble.name=Arduino Nano 33 BLE
@@ -310,6 +315,11 @@ portentah7.bootloader.interface=0
310315
portentah7.bootloader.file=zephyr-{build.variant}.bin
311316
portentah7.bootloader.address=0x08040000
312317

318+
portentah7.build.ota.magic=0x2341025B
319+
portentah7.build.ota.sketch_offset=0xA0000
320+
portentah7.build.ota.pack_command_legacy="{runtime.tools.ota-pack.path}/ota-pack" -loader "{runtime.platform.path}/firmwares/zephyr-{build.variant}.bin" -sketch "{build.path}/{build.project_name}.{upload.extension}" -offset {build.ota.sketch_offset} -magic {build.ota.magic} -output "{build.path}/{build.project_name}.legacy.ota"
321+
portentah7.build.ota.pack_command="{runtime.tools.ota-pack.path}/ota-pack" --sketch-only -sketch "{build.path}/{build.project_name}.{upload.extension}" -magic {build.ota.magic} -output "{build.path}/{build.project_name}.ota"
322+
313323
portentah7.debug.tool=gdb
314324
portentah7.debug.server.openocd.scripts.0=interface/{programmer.protocol}.cfg
315325
portentah7.debug.server.openocd.scripts.1={programmer.transport_script}
@@ -560,6 +570,9 @@ portentac33.bootloader.interface=0
560570
portentac33.bootloader.address=0x10000
561571
portentac33.bootloader.dfuse=-Q
562572

573+
portentac33.build.ota.magic=0x23410068
574+
portentac33.build.ota.pack_command="{runtime.tools.ota-pack.path}/ota-pack" --sketch-only -sketch "{build.path}/{build.project_name}.{upload.extension}" -magic {build.ota.magic} -output "{build.path}/{build.project_name}.ota"
575+
563576
##########################################################################################
564577

565578
opta.name=Arduino Opta
@@ -628,6 +641,11 @@ opta.debug.server.openocd.scripts.2=target/stm32h7x_dual_bank.cfg
628641
opta.debug.cortex-debug.custom.request=attach
629642
opta.debug.svd_file={runtime.platform.path}/svd/STM32H747_CM7.svd
630643

644+
opta.build.ota.magic=0x23410064
645+
opta.build.ota.sketch_offset=0xA0000
646+
opta.build.ota.pack_command_legacy="{runtime.tools.ota-pack.path}/ota-pack" -loader "{runtime.platform.path}/firmwares/zephyr-{build.variant}.bin" -sketch "{build.path}/{build.project_name}.{upload.extension}" -offset {build.ota.sketch_offset} -magic {build.ota.magic} -output "{build.path}/{build.project_name}.legacy.ota"
647+
opta.build.ota.pack_command="{runtime.tools.ota-pack.path}/ota-pack" --sketch-only -sketch "{build.path}/{build.project_name}.{upload.extension}" -magic {build.ota.magic} -output "{build.path}/{build.project_name}.ota"
648+
631649
##########################################################################################
632650

633651
nano_matter.name=Arduino Nano Matter

loader/llext_exports.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <strings.h>
99
#include <zephyr/llext/symbol.h>
1010
#include <zephyr/usb/usb_device.h>
11+
#include <zephyr/sys/reboot.h>
1112
#include <ctype.h>
1213
#include <stdio.h>
1314
#include <stdlib.h>
@@ -289,6 +290,7 @@ EXPORT_SYMBOL(k_work_submit_to_queue);
289290

290291
EXPORT_SYMBOL(time);
291292
EXPORT_SYMBOL(sys_clock_settime);
293+
EXPORT_SYMBOL(sys_reboot);
292294
EXPORT_SYMBOL(mktime);
293295

294296
EXPORT_SYMBOL(printf);

loader/main.c

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,13 @@ LOG_MODULE_REGISTER(sketch);
2424
#include <zephyr/usb/usb_device.h>
2525

2626
#include <zephyr/devicetree/fixed-partitions.h>
27+
#include <zephyr/fs/fs.h>
2728

2829
#define HEADER_LEN 16
2930

31+
#define OTA_SENTINEL_PATH "/ota:/OTA_UPDATE_PENDING"
32+
#define OTA_UPDATE_PATH "/ota:/UPDATE.BIN"
33+
3034
struct sketch_header_v1 {
3135
uint8_t ver; // @ 0x07
3236
uint32_t len; // @ 0x08
@@ -116,6 +120,149 @@ struct backup_store {
116120
};
117121
volatile __stm32_backup_sram_section struct backup_store backup;
118122

123+
#if defined(CONFIG_FILE_SYSTEM)
124+
/*
125+
* Install a pending OTA update if one is present on /ota:.
126+
*
127+
* Trigger: /ota:/OTA_UPDATE_PENDING is a zero-byte sentinel dropped by
128+
* the sketch (Arduino_OTA_Loader.cpp) immediately before sys_reboot.
129+
*
130+
* Recovery policy on failure:
131+
* - Pre-erase errors (bad header, bad bounds, file too small): the
132+
* source file is unrecoverable, so the sentinel and UPDATE.BIN are
133+
* both removed and the loader proceeds to boot the existing sketch.
134+
* - Post-erase errors (flash write fault, truncated read mid-stream):
135+
* the partition is already partially written, so the SENTINEL is
136+
* KEPT IN PLACE. The next boot will retry from the same UPDATE.BIN,
137+
* which is the only way back from an in-progress flash without DFU.
138+
* If the failure is persistent the user must recover externally.
139+
*/
140+
static int try_ota_update(const struct flash_area *fa)
141+
{
142+
struct fs_dirent entry;
143+
int rc;
144+
145+
/* Check for pending OTA update */
146+
if (fs_stat(OTA_SENTINEL_PATH, &entry) != 0) {
147+
printk("OTA: no update pending\n");
148+
return 0;
149+
}
150+
151+
printk("OTA: update pending, validating...\n");
152+
153+
/* Open UPDATE.BIN */
154+
struct fs_file_t file;
155+
fs_file_t_init(&file);
156+
rc = fs_open(&file, OTA_UPDATE_PATH, FS_O_READ);
157+
if (rc < 0) {
158+
printk("OTA: failed to open %s, rc %d\n", OTA_UPDATE_PATH, rc);
159+
fs_unlink(OTA_SENTINEL_PATH);
160+
return -1;
161+
}
162+
163+
/* Get file size */
164+
fs_seek(&file, 0, FS_SEEK_END);
165+
off_t file_size = fs_tell(&file);
166+
fs_seek(&file, 0, FS_SEEK_SET);
167+
168+
if (file_size < HEADER_LEN) {
169+
printk("OTA: file too small (%ld bytes)\n", (long)file_size);
170+
fs_close(&file);
171+
fs_unlink(OTA_SENTINEL_PATH);
172+
fs_unlink(OTA_UPDATE_PATH);
173+
return -1;
174+
}
175+
176+
/* Read and validate sketch header */
177+
char header[HEADER_LEN];
178+
rc = fs_read(&file, header, HEADER_LEN);
179+
if (rc != HEADER_LEN) {
180+
printk("OTA: failed to read header\n");
181+
fs_close(&file);
182+
fs_unlink(OTA_SENTINEL_PATH);
183+
fs_unlink(OTA_UPDATE_PATH);
184+
return -1;
185+
}
186+
187+
struct sketch_header_v1 *hdr = (struct sketch_header_v1 *)(header + 7);
188+
if (hdr->ver != 0x1 || hdr->magic != 0x2341) {
189+
printk("OTA: invalid sketch header (ver=0x%x magic=0x%x)\n", hdr->ver, hdr->magic);
190+
fs_close(&file);
191+
fs_unlink(OTA_SENTINEL_PATH);
192+
fs_unlink(OTA_UPDATE_PATH);
193+
return -1;
194+
}
195+
196+
size_t sketch_len = hdr->len;
197+
printk("OTA: sketch length = %u bytes\n", (unsigned)sketch_len);
198+
199+
/* Bounds-check header before the destructive erase: refuse to brick
200+
* the device on a malformed/oversized header or a truncated file. */
201+
if (sketch_len > fa->fa_size) {
202+
printk("OTA: sketch too large for partition (%u > %u)\n",
203+
(unsigned)sketch_len, (unsigned)fa->fa_size);
204+
fs_close(&file);
205+
fs_unlink(OTA_SENTINEL_PATH);
206+
fs_unlink(OTA_UPDATE_PATH);
207+
return -1;
208+
}
209+
if ((off_t)sketch_len > file_size) {
210+
printk("OTA: header len exceeds file size (%u > %ld)\n",
211+
(unsigned)sketch_len, (long)file_size);
212+
fs_close(&file);
213+
fs_unlink(OTA_SENTINEL_PATH);
214+
fs_unlink(OTA_UPDATE_PATH);
215+
return -1;
216+
}
217+
218+
/* From here on the partition is about to become inconsistent.
219+
* On failure we leave the sentinel + UPDATE.BIN in place so the
220+
* next boot can retry. */
221+
printk("OTA: erasing flash partition (%u bytes)...\n", (unsigned)fa->fa_size);
222+
rc = flash_area_erase(fa, 0, fa->fa_size);
223+
if (rc) {
224+
printk("OTA: flash erase failed, rc %d — retry on next boot\n", rc);
225+
fs_close(&file);
226+
return -1;
227+
}
228+
229+
/* Write sketch data from file to flash in chunks */
230+
fs_seek(&file, 0, FS_SEEK_SET);
231+
uint8_t chunk[4096];
232+
off_t offset = 0;
233+
size_t remaining = sketch_len;
234+
ssize_t n;
235+
while (remaining > 0 && (n = fs_read(&file, chunk,
236+
remaining < sizeof(chunk) ? remaining : sizeof(chunk))) > 0) {
237+
rc = flash_area_write(fa, offset, chunk, n);
238+
if (rc) {
239+
printk("OTA: flash write failed at offset %ld, rc %d — retry on next boot\n",
240+
(long)offset, rc);
241+
fs_close(&file);
242+
return -1;
243+
}
244+
offset += n;
245+
remaining -= n;
246+
}
247+
fs_close(&file);
248+
249+
if (remaining > 0) {
250+
printk("OTA: short read, %u bytes missing — retry on next boot\n",
251+
(unsigned)remaining);
252+
return -1;
253+
}
254+
255+
printk("OTA: wrote %ld bytes to flash\n", (long)offset);
256+
257+
/* Success — clear sentinel and update file so the next boot is normal. */
258+
fs_unlink(OTA_SENTINEL_PATH);
259+
fs_unlink(OTA_UPDATE_PATH);
260+
261+
printk("OTA: update complete\n");
262+
return 0;
263+
}
264+
#endif /* CONFIG_FILE_SYSTEM */
265+
119266
static int loader(const struct shell *sh) {
120267
const struct flash_area *fa;
121268
int rc;
@@ -127,6 +274,10 @@ static int loader(const struct shell *sh) {
127274
return rc;
128275
}
129276

277+
#if defined(CONFIG_FILE_SYSTEM)
278+
try_ota_update(fa);
279+
#endif
280+
130281
uintptr_t base_addr =
131282
DT_REG_ADDR(DT_GPARENT(DT_NODELABEL(user_sketch))) + DT_REG_ADDR(DT_NODELABEL(user_sketch));
132283

loader/prj.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,5 @@ CONFIG_RTC=y
4949
CONFIG_RTC_ALARM=y
5050
CONFIG_RTC_UPDATE=y
5151
CONFIG_RTC_CALIBRATION=y
52+
53+
CONFIG_REBOOT=y

platform.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ build.link_mode=dynamic
5353
build.boot_mode=wait
5454
upload.extension=elf-zsk.bin
5555

56+
build.ota.pack_command=
57+
build.ota.pack_command_legacy=
5658
build.ldscript.path={runtime.platform.path}/variants/_ldscripts
5759
build.link_command="{compiler.path}{compiler.c.elf.cmd}" "-L{build.path}" "-L{build.variant.path}" {compiler.c.elf.flags} {compiler.c.elf.extra_flags} {build.extra_flags} {build.extra_ldflags} {compiler.zephyr.common_ldflags} --specs=nano.specs --specs=nosys.specs {compiler.ldflags} {object_files} -Wl,--start-group "{build.path}/{archive_file}" {compiler.zephyr.extra_ldflags} {compiler.libraries.ldflags} -Wl,--end-group {build.link_args.{build.link_mode}}
5860

@@ -142,6 +144,8 @@ recipe.objcopy.hex.pattern="{compiler.path}{compiler.elf2hex.cmd}" {compiler.elf
142144
## Mangle the file
143145
recipe.hooks.objcopy.postobjcopy.1.pattern="{runtime.tools.zephyr-sketch-tool.path}/zephyr-sketch-tool" {build.zsk_args.debug} {build.zsk_args.mode-{build.link_mode}} {build.zsk_args.startup-mode-{build.boot_mode}} "{build.path}/{build.project_name}.elf"
144146
recipe.hooks.objcopy.postobjcopy.2.pattern="{runtime.tools.zephyr-sketch-tool.path}/zephyr-sketch-tool" {build.zsk_args.debug} {build.zsk_args.mode-{build.link_mode}} {build.zsk_args.startup-mode-{build.boot_mode}} "{build.path}/{build.project_name}.bin"
147+
recipe.hooks.objcopy.postobjcopy.3.pattern={build.ota.pack_command}
148+
recipe.hooks.objcopy.postobjcopy.4.pattern={build.ota.pack_command_legacy}
145149

146150
## Compute size
147151
recipe.size.pattern="{compiler.path}{compiler.size.cmd}" -A "{build.path}/{build.project_name}.elf"

0 commit comments

Comments
 (0)