@@ -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,149 @@ 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+ {
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+
119266static 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
0 commit comments