@@ -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+
3034struct sketch_header_v1 {
3135 uint8_t ver ; // @ 0x07
3236 uint32_t len ; // @ 0x08
@@ -116,6 +120,147 @@ struct backup_store {
116120};
117121volatile __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+ struct fs_dirent entry ;
142+ int rc ;
143+
144+ /* Check for pending OTA update */
145+ if (fs_stat (OTA_SENTINEL_PATH , & entry ) != 0 ) {
146+ printk ("OTA: no update pending\n" );
147+ return 0 ;
148+ }
149+
150+ printk ("OTA: update pending, validating...\n" );
151+
152+ /* Open UPDATE.BIN */
153+ struct fs_file_t file ;
154+ fs_file_t_init (& file );
155+ rc = fs_open (& file , OTA_UPDATE_PATH , FS_O_READ );
156+ if (rc < 0 ) {
157+ printk ("OTA: failed to open %s, rc %d\n" , OTA_UPDATE_PATH , rc );
158+ fs_unlink (OTA_SENTINEL_PATH );
159+ return -1 ;
160+ }
161+
162+ /* Get file size */
163+ fs_seek (& file , 0 , FS_SEEK_END );
164+ off_t file_size = fs_tell (& file );
165+ fs_seek (& file , 0 , FS_SEEK_SET );
166+
167+ if (file_size < HEADER_LEN ) {
168+ printk ("OTA: file too small (%ld bytes)\n" , (long )file_size );
169+ fs_close (& file );
170+ fs_unlink (OTA_SENTINEL_PATH );
171+ fs_unlink (OTA_UPDATE_PATH );
172+ return -1 ;
173+ }
174+
175+ /* Read and validate sketch header */
176+ char header [HEADER_LEN ];
177+ rc = fs_read (& file , header , HEADER_LEN );
178+ if (rc != HEADER_LEN ) {
179+ printk ("OTA: failed to read header\n" );
180+ fs_close (& file );
181+ fs_unlink (OTA_SENTINEL_PATH );
182+ fs_unlink (OTA_UPDATE_PATH );
183+ return -1 ;
184+ }
185+
186+ struct sketch_header_v1 * hdr = (struct sketch_header_v1 * )(header + 7 );
187+ if (hdr -> ver != 0x1 || hdr -> magic != 0x2341 ) {
188+ printk ("OTA: invalid sketch header (ver=0x%x magic=0x%x)\n" , hdr -> ver , hdr -> magic );
189+ fs_close (& file );
190+ fs_unlink (OTA_SENTINEL_PATH );
191+ fs_unlink (OTA_UPDATE_PATH );
192+ return -1 ;
193+ }
194+
195+ size_t sketch_len = hdr -> len ;
196+ printk ("OTA: sketch length = %u bytes\n" , (unsigned )sketch_len );
197+
198+ /* Bounds-check header before the destructive erase: refuse to brick
199+ * the device on a malformed/oversized header or a truncated file. */
200+ if (sketch_len > fa -> fa_size ) {
201+ printk ("OTA: sketch too large for partition (%u > %u)\n" , (unsigned )sketch_len ,
202+ (unsigned )fa -> fa_size );
203+ fs_close (& file );
204+ fs_unlink (OTA_SENTINEL_PATH );
205+ fs_unlink (OTA_UPDATE_PATH );
206+ return -1 ;
207+ }
208+ if ((off_t )sketch_len > file_size ) {
209+ printk ("OTA: header len exceeds file size (%u > %ld)\n" , (unsigned )sketch_len ,
210+ (long )file_size );
211+ fs_close (& file );
212+ fs_unlink (OTA_SENTINEL_PATH );
213+ fs_unlink (OTA_UPDATE_PATH );
214+ return -1 ;
215+ }
216+
217+ /* From here on the partition is about to become inconsistent.
218+ * On failure we leave the sentinel + UPDATE.BIN in place so the
219+ * next boot can retry. */
220+ printk ("OTA: erasing flash partition (%u bytes)...\n" , (unsigned )fa -> fa_size );
221+ rc = flash_area_erase (fa , 0 , fa -> fa_size );
222+ if (rc ) {
223+ printk ("OTA: flash erase failed, rc %d — retry on next boot\n" , rc );
224+ fs_close (& file );
225+ return -1 ;
226+ }
227+
228+ /* Write sketch data from file to flash in chunks */
229+ fs_seek (& file , 0 , FS_SEEK_SET );
230+ uint8_t chunk [4096 ];
231+ off_t offset = 0 ;
232+ size_t remaining = sketch_len ;
233+ ssize_t n ;
234+ while (remaining > 0 &&
235+ (n = fs_read (& file , chunk , remaining < sizeof (chunk ) ? remaining : sizeof (chunk ))) > 0 ) {
236+ rc = flash_area_write (fa , offset , chunk , n );
237+ if (rc ) {
238+ printk ("OTA: flash write failed at offset %ld, rc %d — retry on next boot\n" ,
239+ (long )offset , rc );
240+ fs_close (& file );
241+ return -1 ;
242+ }
243+ offset += n ;
244+ remaining -= n ;
245+ }
246+ fs_close (& file );
247+
248+ if (remaining > 0 ) {
249+ printk ("OTA: short read, %u bytes missing — retry on next boot\n" , (unsigned )remaining );
250+ return -1 ;
251+ }
252+
253+ printk ("OTA: wrote %ld bytes to flash\n" , (long )offset );
254+
255+ /* Success — clear sentinel and update file so the next boot is normal. */
256+ fs_unlink (OTA_SENTINEL_PATH );
257+ fs_unlink (OTA_UPDATE_PATH );
258+
259+ printk ("OTA: update complete\n" );
260+ return 0 ;
261+ }
262+ #endif /* CONFIG_FILE_SYSTEM */
263+
119264static int loader (const struct shell * sh ) {
120265 const struct flash_area * fa ;
121266 int rc ;
@@ -127,6 +272,10 @@ static int loader(const struct shell *sh) {
127272 return rc ;
128273 }
129274
275+ #if defined(CONFIG_FILE_SYSTEM )
276+ try_ota_update (fa );
277+ #endif
278+
130279 uintptr_t base_addr =
131280 DT_REG_ADDR (DT_GPARENT (DT_NODELABEL (user_sketch ))) + DT_REG_ADDR (DT_NODELABEL (user_sketch ));
132281
0 commit comments