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