Morse Micro IoT SDK  2.9.7
bootloader.c
Go to the documentation of this file.
1/*
2 * Copyright 2023 Morse Micro
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
57#include <stdio.h>
58#include <stdint.h>
59#include <stdbool.h>
60#include <string.h>
61#include <endian.h>
62#include <sys/unistd.h>
63#include <sys/fcntl.h>
64#include <sys/errno.h>
65#include "sha256.h"
66#include "mmhal_flash.h"
67#include "mmconfig.h"
68#include "mmhal.h"
69#include "mmosal.h"
70#include "puff.h"
71#include "mbin.h"
72
73
75#define MAX_UPDATE_ATTEMPTS 10
76
78#define MAX_SEGMENT_SIZE 32768
79
82{
83 BOOTLOADER_OK = 0,
84 BOOTLOADER_ERR_FILE_NOT_FOUND,
85 BOOTLOADER_ERR_SIGNATURE_NOT_FOUND,
86 BOOTLOADER_ERR_FILE_VERIFICATION_FAILED,
87 BOOTLOADER_ERR_INVALID_FILE,
88 BOOTLOADER_ERR_FILE_CORRUPT,
89 BOOTLOADER_ERR_ERASE_FAILED,
90 BOOTLOADER_ERR_PROGRAM_FAILED,
91 BOOTLOADER_ERR_TOO_MANY_ATTEMPTS,
92 BOOTLOADER_ERR_FILE_DECOMPRESSION,
93};
94
96extern uint8_t application_start;
97
99extern uint8_t application_end;
100
103
106
107
113static void delay_ms(uint32_t approx_delay_ms)
114{
115 uint32_t i;
116 for (i = 0; i < approx_delay_ms; i++)
117 {
118 uint32_t tick = mmosal_get_time_ticks();
119 while (tick == mmosal_get_time_ticks())
120 {
121 }
122 }
123}
124
130static void blink_error_code(int code)
131{
132 int ii;
133 /* Flash error LED code times */
134
135 for (ii = 0; ii < code; ii++)
136 {
138 delay_ms(100);
139 mmhal_set_error_led(false);
140 delay_ms(100);
141 }
142}
143
164static void update_failed(int code)
165{
166 /* Write error code to config store */
167 mmconfig_write_int("BOOTLOADER_ERROR", code);
168 while (true)
169 {
170 /* We repeatedly blink the error code here */
171 blink_error_code(code);
172 delay_ms(1000);
173 }
174}
175
184static void erase_application_area(void)
185{
186 uint32_t block_address = (uint32_t) &application_start;
187 uint32_t end_address = (uint32_t) &application_end;
188
189 while (block_address < end_address)
190 {
191 /* Erase block */
192 if (mmhal_flash_erase(block_address) != 0)
193 {
194 /* We had a failure erasing flash, try again */
195 if (mmhal_flash_erase(block_address) != 0)
196 {
197 /* We had a second consecutive failure, give up as the flash is likely worn out */
198 update_failed(BOOTLOADER_ERR_ERASE_FAILED);
199 }
200 }
201
202 /*
203 * No need to sanity check this as everything between @c application_start
204 * and @c application_end should be in valid flash or the linker script is wrong.
205 */
206 block_address += mmhal_flash_getblocksize(block_address);
207 }
208}
209
210
218static int read_segment_header(int fd, struct mbin_tlv_hdr *seg_hdr)
219{
220 struct mbin_tlv_hdr hdr_overlay;
221
222 int ret = read(fd, &hdr_overlay, sizeof(hdr_overlay));
223 if (ret != sizeof(hdr_overlay))
224 {
225 return BOOTLOADER_ERR_FILE_CORRUPT;
226 }
227
228 seg_hdr->type = le16toh(hdr_overlay.type);
229 seg_hdr->len = le16toh(hdr_overlay.len);
230
231 return BOOTLOADER_OK;
232}
233
241static int read_uint32(int fd, uint32_t *val)
242{
243 uint32_t tmp;
244
245 int ret = read(fd, &tmp, sizeof(tmp));
246 if (ret != sizeof(tmp))
247 {
248 return BOOTLOADER_ERR_FILE_CORRUPT;
249 }
250
251 *val = le32toh(tmp);
252
253 return BOOTLOADER_OK;
254}
255
263static int load_and_flash_mbin(const char *fname, bool make_changes)
264{
265 struct mbin_tlv_hdr seg_hdr;
266 uint32_t mbin_magic = 0;
267 uint32_t load_address, load_size;
268 unsigned long compressed_size, puffed_size; // NOLINT(runtime/int) match puff API
269 int ret;
270 bool eof = false;
271 bool modified = false;
272
273 /* Open file */
274 int fd = open(fname, O_RDONLY);
275 if (fd < 0)
276 {
277 /* Could not open file */
278 return BOOTLOADER_ERR_FILE_NOT_FOUND;
279 }
280
281 /* Verify MBIN magic */
282 ret = read_segment_header(fd, &seg_hdr);
283 if (ret != BOOTLOADER_OK)
284 {
285 ret = BOOTLOADER_ERR_FILE_CORRUPT;
286 goto bailout;
287 }
288 if (seg_hdr.type != FIELD_TYPE_MAGIC)
289 {
290 ret = BOOTLOADER_ERR_INVALID_FILE;
291 goto bailout;
292 }
293 if (seg_hdr.len != sizeof(mbin_magic))
294 {
295 ret = BOOTLOADER_ERR_FILE_CORRUPT;
296 goto bailout;
297 }
298 ret = read_uint32(fd, &mbin_magic);
299 if (MBIN_SW_MAGIC_NUMBER != mbin_magic)
300 {
301 ret = BOOTLOADER_ERR_INVALID_FILE;
302 goto bailout;
303 }
304
305 /* Iterate through TLV headers */
306 while (!eof)
307 {
308 ret = read_segment_header(fd, &seg_hdr);
309 if (ret != BOOTLOADER_OK)
310 {
311 break;
312 }
313
314 switch (seg_hdr.type)
315 {
316 case FIELD_TYPE_SW_SEGMENT:
317 ret = read_uint32(fd, &load_address);
318 if (ret != BOOTLOADER_OK)
319 {
320 /* We had a failure reading MBIN, give up */
321 goto bailout;
322 }
323
324 load_size = seg_hdr.len - sizeof(load_address);
325 if (load_size > MAX_SEGMENT_SIZE)
326 {
327 /* We had a failure reading MBIN, give up */
328 ret = BOOTLOADER_ERR_FILE_CORRUPT;
329 goto bailout;
330 }
331
332 ret = read(fd, segment_buffer, load_size);
333 if (ret != (int)load_size)
334 {
335 /* We had a failure reading MBIN, give up */
336 ret = BOOTLOADER_ERR_FILE_CORRUPT;
337 goto bailout;
338 }
339
340 /* Does the load address fall within the application area? */
341 if ((load_address >= (uint32_t) &application_start) &&
342 (load_address + load_size <= (uint32_t) &application_end))
343 {
344 modified = true;
345 if (make_changes)
346 {
347 ret = mmhal_flash_write(load_address, segment_buffer, load_size);
348 if (ret != 0)
349 {
350 /* We had a failure while flashing, give up as flash state is unknown */
351 ret = BOOTLOADER_ERR_PROGRAM_FAILED;
352 goto bailout;
353 }
354 }
355 }
356 else
357 {
358 /* We attempted to write outside the valid area */
359 ret = BOOTLOADER_ERR_INVALID_FILE;
360 goto bailout;
361 }
362 break;
363
364 case FIELD_TYPE_SW_SEGMENT_DEFLATED:
365 ret = read_uint32(fd, &load_address);
366 if (ret != BOOTLOADER_OK)
367 {
368 /* We had a failure reading MBIN, give up */
369 goto bailout;
370 }
371
372 ret = read_uint32(fd, &load_size);
373 if (ret != BOOTLOADER_OK)
374 {
375 /* We had a failure reading MBIN, give up */
376 goto bailout;
377 }
378
379 if (load_size > MAX_SEGMENT_SIZE)
380 {
381 /* We had a failure reading MBIN, give up */
382 ret = BOOTLOADER_ERR_FILE_CORRUPT;
383 goto bailout;
384 }
385
386 compressed_size = seg_hdr.len - sizeof(load_address) - sizeof(load_size);
387 if (compressed_size > MAX_SEGMENT_SIZE)
388 {
389 /* We had a failure reading MBIN, give up */
390 ret = BOOTLOADER_ERR_FILE_CORRUPT;
391 goto bailout;
392 }
393
394 ret = read(fd, deflate_buffer, compressed_size);
395 if (ret != (int)compressed_size)
396 {
397 /* We had a failure reading MBIN, give up */
398 ret = BOOTLOADER_ERR_FILE_CORRUPT;
399 goto bailout;
400 }
401
402 /* Decompress the segment */
403 puffed_size = load_size;
404 puff(segment_buffer, &puffed_size, deflate_buffer, &compressed_size);
405 if (puffed_size != load_size)
406 {
407 /* We had a failure decompressing, give up */
408 ret = BOOTLOADER_ERR_FILE_DECOMPRESSION;
409 goto bailout;
410 }
411
412 /* Does the load address fall within the application area? */
413 if ((load_address >= (uint32_t) &application_start) &&
414 (load_address + load_size <= (uint32_t) &application_end))
415 {
416 modified = true;
417 if (make_changes)
418 {
419 ret = mmhal_flash_write(load_address, segment_buffer, load_size);
420 if (ret!= 0)
421 {
422 /* We had a failure while flashing, give up as flash state is unknown */
423 ret = BOOTLOADER_ERR_PROGRAM_FAILED;
424 goto bailout;
425 }
426 }
427 }
428 else
429 {
430 /* We attempted to write outside the valid area */
431 ret = BOOTLOADER_ERR_INVALID_FILE;
432 goto bailout;
433 }
434 break;
435
436 case FIELD_TYPE_EOF_WITH_SIGNATURE:
437 /* We validate this signature in the application and generate the
438 * simplified IMAGE_SIGNATURE hash in config store for the bootloader.
439 * So the bootloader just treats this as an EOF. */
440 case FIELD_TYPE_EOF:
441 eof = true;
442 break;
443
444 /* Skip these fields since they are not used for the software update process */
445 case FIELD_TYPE_FW_SEGMENT:
446 case FIELD_TYPE_FW_SEGMENT_DEFLATED:
447 case FIELD_TYPE_FW_TLV_BCF_ADDR:
448 case FIELD_TYPE_BCF_BOARD_CONFIG:
449 case FIELD_TYPE_BCF_REGDOM:
450 if (seg_hdr.len > MAX_SEGMENT_SIZE)
451 {
452 /* We had a failure reading MBIN, give up */
453 ret = BOOTLOADER_ERR_FILE_CORRUPT;
454 goto bailout;
455 }
456 /* Skip over */
457 ret = read(fd, segment_buffer, seg_hdr.len);
458 if (ret != seg_hdr.len)
459 {
460 /* We had an error reading the file */
461 ret = BOOTLOADER_ERR_FILE_CORRUPT;
462 goto bailout;
463 }
464 break;
465
466 case FIELD_TYPE_MAGIC:
467 default:
468 /* These are unexpected */
469 ret = BOOTLOADER_ERR_FILE_CORRUPT;
470 goto bailout;
471 }
472 }
473
474 if (!eof)
475 {
476 /* File ended unexpectedly */
477 ret = BOOTLOADER_ERR_FILE_CORRUPT;
478 goto bailout;
479 }
480
481 if (!modified)
482 {
483 /* No attempts were made to write to the application region of flash. */
484 /* Maybe this file was meant for another device? */
485 ret = BOOTLOADER_ERR_INVALID_FILE;
486 goto bailout;
487 }
488
489 ret = BOOTLOADER_OK;
490bailout:
491 close(fd);
492 return ret;
493}
494
501static int verify_signature(const char *fname)
502{
503 /* Stub to compute memory usage of sha256 */
504 BYTE data[64];
505 BYTE hash[32];
506 SHA256_CTX ctx;
507 sha256_init(&ctx);
508
509 BYTE compare[32];
510 if (mmconfig_read_bytes("IMAGE_SIGNATURE", compare, sizeof(compare), 0) != sizeof(compare))
511 {
512 /* Error reading hash, bail */
513 return BOOTLOADER_ERR_SIGNATURE_NOT_FOUND;
514 }
515
516 /* Open file */
517 int fd = open(fname, O_RDONLY);
518 if (fd < 0)
519 {
520 /* Could not open file */
521 return BOOTLOADER_ERR_FILE_NOT_FOUND;
522 }
523
524 int bytesread;
525 do
526 {
527 bytesread = read(fd, data, sizeof(data));
528 if (bytesread <= 0)
529 {
530 /* break out on error */
531 break;
532 }
533 sha256_update(&ctx, data, bytesread);
534 } while (bytesread == sizeof(data));
535
536 sha256_final(&ctx, hash);
537 close(fd);
538
539 if (memcmp(hash, compare, sizeof(hash)) != 0)
540 {
541 /* Verification failed */
542 return BOOTLOADER_ERR_FILE_VERIFICATION_FAILED;
543 }
544 return BOOTLOADER_OK;
545}
546
550static void check_update()
551{
552 static char update_path[128];
553
554 /* Check if UPDATE_IMAGE is set in config store */
555 int ret = mmconfig_read_string("UPDATE_IMAGE", update_path, sizeof(update_path));
556 if (ret <= 0)
557 {
558 /* Could not find UPDATE_IMAGE, bail to application, this is the normal use case */
559 return;
560 }
561
562 ret = verify_signature(update_path);
563 if (ret != 0)
564 {
565 /* Signature check failed, bail to application */
566 mmconfig_write_int("BOOTLOADER_ERROR", ret);
567 blink_error_code(ret);
568 return;
569 }
570
571 /* Do a dry run to ensure MBIN file is ok */
572 ret = load_and_flash_mbin(update_path, false);
573 if (ret != BOOTLOADER_OK)
574 {
575 /* Update failed early, write error code and bail to application */
576 mmconfig_write_int("BOOTLOADER_ERROR", ret);
577 blink_error_code(ret);
578 return;
579 }
580
581 /* MBIN looks good, increment update attempts */
582 int update_attempts = 0;
583 mmconfig_read_int("UPDATE_ATTEMPTS", &update_attempts);
584 if (update_attempts >= MAX_UPDATE_ATTEMPTS)
585 {
586 /* We tried too many times, give up */
587 update_failed(BOOTLOADER_ERR_TOO_MANY_ATTEMPTS);
588 }
589 mmconfig_write_int("UPDATE_ATTEMPTS", ++update_attempts);
590
591 /* No turning back, any failures past this point are critical */
593
594 /* Start the flashing process for real */
595 ret = load_and_flash_mbin(update_path, true);
596 if (ret != BOOTLOADER_OK)
597 {
598 /* We failed once, so erase and try again */
600 ret = load_and_flash_mbin(update_path, true);
601 if (ret != BOOTLOADER_OK)
602 {
603 /* We failed a second time, situation is unrecoverable, give up */
604 update_failed(ret);
605 }
606 }
607
608 /* We successfully did an update, so cleanup and reboot now */
609 mmconfig_delete_key("UPDATE_IMAGE");
610 mmconfig_delete_key("UPDATE_ATTEMPTS");
611 mmconfig_delete_key("IMAGE_SIGNATURE");
612 mmconfig_delete_key("BOOTLOADER_ERROR");
613 mmhal_reset();
614}
615
620int main(void)
621{
622 /* This writes the bootloader version into config store for applicatiopn to read.
623 * Note: This function returns without writing anything if the key is already present
624 * and has the same value - so no need to worry about flash wear and tear. */
625 mmconfig_write_string("BOOTLOADER_VERSION", BOOTLOADER_VERSION);
626
627 /* Check for updates */
628 check_update();
629
630 /* Jump to application. The address to jump to is the second entry
631 * in the vector table located at the start of application area.
632 * Note: we ignore the first entry (Stack pointer) as this is usually the same
633 * for the bootloader and set to end of RAM - requires messy assembly to change. */
634 uint32_t go_address = (*((volatile uint32_t*) (((uint32_t)&application_start) + 4)));
635 void (*jump_to_app)(void) = (void (*)(void)) go_address;
636
637 /* No updates found, jump to application */
638 jump_to_app();
639
640 /* We should not get here */
641 MMOSAL_ASSERT(false);
642}
uint8_t application_start
Start of Application region in flash.
static void check_update()
Check for an update, if an update is found, perform it.
Definition: bootloader.c:550
static int read_segment_header(int fd, struct mbin_tlv_hdr *seg_hdr)
Loads the segment header from file.
Definition: bootloader.c:218
static void erase_application_area(void)
This function erases the entire application flash region.
Definition: bootloader.c:184
static void blink_error_code(int code)
Blinks the hardware error LED to indicate the error code.
Definition: bootloader.c:130
#define MAX_UPDATE_ATTEMPTS
Maximum number of times we attempt to update before giving up.
Definition: bootloader.c:75
#define MAX_SEGMENT_SIZE
Maximum size of a segment, set to 32768.
Definition: bootloader.c:78
static int verify_signature(const char *fname)
Verifies the signature of the file.
Definition: bootloader.c:501
static uint8_t segment_buffer[MAX_SEGMENT_SIZE]
Temporary buffer for loading segments.
Definition: bootloader.c:102
int main(void)
The bootloader entry point.
Definition: bootloader.c:620
bootloader_return_codes
Bootloader return codes.
Definition: bootloader.c:82
static int load_and_flash_mbin(const char *fname, bool make_changes)
Loads and flashes the image.
Definition: bootloader.c:263
uint8_t application_end
End of Application region in flash.
static uint8_t deflate_buffer[MAX_SEGMENT_SIZE]
Temporary buffer for deflating segments.
Definition: bootloader.c:105
static void delay_ms(uint32_t approx_delay_ms)
Implements a delay of approximately the duration specified.
Definition: bootloader.c:113
static int read_uint32(int fd, uint32_t *val)
Loads a 32 bit integer from file and converts to host endian.
Definition: bootloader.c:241
static void update_failed(int code)
Called when multiple attempts to update failed, must not return.
Definition: bootloader.c:164
#define MBIN_SW_MAGIC_NUMBER
Expected value of the magic field for a SW image MMSW.
Definition: mbin.h:76
int mmconfig_write_int(const char *key, int value)
Converts the given integer to a string and writes to persistent store.
static int mmconfig_delete_key(const char *key)
Deletes the specified key(s) from persistent store.
Definition: mmconfig.h:299
int mmconfig_read_string(const char *key, char *buffer, int bufsize)
Returns the persistent store string value identified by the key.
int mmconfig_read_int(const char *key, int *value)
Returns the integer stored in persistent store identified by the key.
int mmconfig_read_bytes(const char *key, void *buffer, uint32_t buffsize, uint32_t offset)
Returns the persistent store data identified by the key.
int mmconfig_write_string(const char *key, const char *value)
Writes the null terminated string to persistent store location identified by key.
int mmhal_flash_erase(uint32_t block_address)
Erases a block of Flash pointed to by the block_address.
uint32_t mmhal_flash_getblocksize(uint32_t block_address)
Returns the size of the Flash block at the specified address.
int mmhal_flash_write(uint32_t write_address, const uint8_t *data, size_t size)
Write a block of data to the specified Flash address.
void mmhal_set_error_led(bool state)
Set the error LED to the requested state.
void mmhal_reset(void)
Reset the microcontroller.
#define MMOSAL_ASSERT(expr)
Assert that the given expression evaluates to true and abort execution if not.
Definition: mmosal.h:927
uint32_t mmosal_get_time_ticks(void)
Get the system time in ticks.
TLV header data structure.
Definition: mbin.h:41
uint16_t len
Length of payload (excludes header).
Definition: mbin.h:45
uint16_t type
Type (see mbin_tlv_types).
Definition: mbin.h:43