@@ -24,9 +24,12 @@ 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_UPDATE_PATH "/ota:/UPDATE.BIN"
32+
3033struct sketch_header_v1 {
3134 uint8_t ver ; // @ 0x07
3235 uint32_t len ; // @ 0x08
@@ -116,6 +119,153 @@ struct backup_store {
116119};
117120volatile __stm32_backup_sram_section struct backup_store backup ;
118121
122+ #if defined(CONFIG_FILE_SYSTEM )
123+ /*
124+ * Install a pending OTA update if one is present on /ota:.
125+ *
126+ * Trigger: /ota:/UPDATE.BIN exists. The sketch is responsible for
127+ * validating the OTA payload (magic, CRC32, optional decompression)
128+ * before writing UPDATE.BIN and rebooting — the loader trusts the
129+ * file and only re-checks the inner sketch_header_v1 for bounds.
130+ *
131+ * Recovery policy on failure:
132+ * - Pre-erase errors (bad header, bad bounds, file too small):
133+ * UPDATE.BIN is removed and the loader proceeds to boot the
134+ * existing sketch.
135+ * - Post-erase errors (flash write fault, truncated read mid-stream):
136+ * the partition is already partially written, so UPDATE.BIN is
137+ * KEPT IN PLACE. The next boot will retry from the same UPDATE.BIN,
138+ * which is the only way back from an in-progress flash without DFU.
139+ * If the failure is persistent the user must recover externally.
140+ */
141+ static int try_ota_update (const struct flash_area * fa ) {
142+ struct fs_dirent entry ;
143+ int rc ;
144+
145+ /* Check for pending OTA update */
146+ if (fs_stat (OTA_UPDATE_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_UPDATE_PATH );
160+ return -1 ;
161+ }
162+
163+ /* Get file size */
164+ off_t file_size ;
165+ if (fs_seek (& file , 0 , FS_SEEK_END ) < 0 ||
166+ (file_size = fs_tell (& file )) < 0 ||
167+ fs_seek (& file , 0 , FS_SEEK_SET ) < 0 ) {
168+ printk ("OTA: failed to determine file size\n" );
169+ fs_close (& file );
170+ fs_unlink (OTA_UPDATE_PATH );
171+ return -1 ;
172+ }
173+
174+ if (file_size < HEADER_LEN ) {
175+ printk ("OTA: file too small (%ld bytes)\n" , (long )file_size );
176+ fs_close (& file );
177+ fs_unlink (OTA_UPDATE_PATH );
178+ return -1 ;
179+ }
180+
181+ /* Read and validate sketch header */
182+ char header [HEADER_LEN ];
183+ rc = fs_read (& file , header , HEADER_LEN );
184+ if (rc != HEADER_LEN ) {
185+ printk ("OTA: failed to read header\n" );
186+ fs_close (& file );
187+ fs_unlink (OTA_UPDATE_PATH );
188+ return -1 ;
189+ }
190+
191+ struct sketch_header_v1 * hdr = (struct sketch_header_v1 * )(header + 7 );
192+ if (hdr -> ver != 0x1 || hdr -> magic != 0x2341 ) {
193+ printk ("OTA: invalid sketch header (ver=0x%x magic=0x%x)\n" , hdr -> ver , hdr -> magic );
194+ fs_close (& file );
195+ fs_unlink (OTA_UPDATE_PATH );
196+ return -1 ;
197+ }
198+
199+ size_t sketch_len = hdr -> len ;
200+ printk ("OTA: sketch length = %u bytes\n" , (unsigned )sketch_len );
201+
202+ /* Bounds-check header before the destructive erase: refuse to brick
203+ * the device on a malformed/oversized header or a truncated file. */
204+ if (sketch_len > fa -> fa_size ) {
205+ printk ("OTA: sketch too large for partition (%u > %u)\n" , (unsigned )sketch_len ,
206+ (unsigned )fa -> fa_size );
207+ fs_close (& file );
208+ fs_unlink (OTA_UPDATE_PATH );
209+ return -1 ;
210+ }
211+ if ((off_t )sketch_len > file_size ) {
212+ printk ("OTA: header len exceeds file size (%u > %ld)\n" , (unsigned )sketch_len ,
213+ (long )file_size );
214+ fs_close (& file );
215+ fs_unlink (OTA_UPDATE_PATH );
216+ return -1 ;
217+ }
218+
219+ /* From here on the partition is about to become inconsistent.
220+ * On failure we leave UPDATE.BIN in place so the next boot can
221+ * retry. */
222+ printk ("OTA: erasing flash partition (%u bytes)...\n" , (unsigned )fa -> fa_size );
223+ rc = flash_area_erase (fa , 0 , fa -> fa_size );
224+ if (rc ) {
225+ printk ("OTA: flash erase failed, rc %d — retry on next boot\n" , rc );
226+ fs_close (& file );
227+ return -1 ;
228+ }
229+
230+ /* Write sketch data from file to flash in chunks */
231+ if (fs_seek (& file , 0 , FS_SEEK_SET ) < 0 ) {
232+ printk ("OTA: failed to seek to start of file — retry on next boot\n" );
233+ fs_close (& file );
234+ return -1 ;
235+ }
236+ uint8_t chunk [4096 ];
237+ off_t offset = 0 ;
238+ size_t remaining = sketch_len ;
239+ ssize_t n ;
240+ while (remaining > 0 &&
241+ (n = fs_read (& file , chunk , remaining < sizeof (chunk ) ? remaining : sizeof (chunk ))) > 0 ) {
242+ rc = flash_area_write (fa , offset , chunk , n );
243+ if (rc ) {
244+ printk ("OTA: flash write failed at offset %ld, rc %d — retry on next boot\n" ,
245+ (long )offset , rc );
246+ fs_close (& file );
247+ return -1 ;
248+ }
249+ offset += n ;
250+ remaining -= n ;
251+ }
252+ fs_close (& file );
253+
254+ if (remaining > 0 ) {
255+ printk ("OTA: short read, %u bytes missing — retry on next boot\n" , (unsigned )remaining );
256+ return -1 ;
257+ }
258+
259+ printk ("OTA: wrote %ld bytes to flash\n" , (long )offset );
260+
261+ /* Success — clear update file so the next boot is normal. */
262+ fs_unlink (OTA_UPDATE_PATH );
263+
264+ printk ("OTA: update complete\n" );
265+ return 0 ;
266+ }
267+ #endif /* CONFIG_FILE_SYSTEM */
268+
119269static int loader (const struct shell * sh ) {
120270 const struct flash_area * fa ;
121271 int rc ;
@@ -127,6 +277,10 @@ static int loader(const struct shell *sh) {
127277 return rc ;
128278 }
129279
280+ #if defined(CONFIG_FILE_SYSTEM )
281+ try_ota_update (fa );
282+ #endif
283+
130284 uintptr_t base_addr =
131285 DT_REG_ADDR (DT_GPARENT (DT_NODELABEL (user_sketch ))) + DT_REG_ADDR (DT_NODELABEL (user_sketch ));
132286
0 commit comments