/* * Copyright (c) 2017 Nordic Semiconductor ASA * Copyright (c) 2016-2017 Linaro Limited * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include "mcuboot_priv.h" /* * Helpers for image headers and trailers, as defined by mcuboot. */ /* * Strict defines: the definitions in the following block contain * values which are MCUboot implementation requirements. */ /* Header: */ #define BOOT_HEADER_MAGIC_V1 0x96f3b83d #define BOOT_HEADER_SIZE_V1 32 /* * Raw (on-flash) representation of the v1 image header. */ struct mcuboot_v1_raw_header { uint32_t header_magic; uint32_t image_load_address; uint16_t header_size; uint16_t pad; uint32_t image_size; uint32_t image_flags; struct { uint8_t major; uint8_t minor; uint16_t revision; uint32_t build_num; } version; uint32_t pad2; } __packed; /* * End of strict defines */ #define BOOT_FLAG_IMAGE_OK 0 #define BOOT_FLAG_COPY_DONE 1 #define FLASH_MIN_WRITE_SIZE \ DT_PROP(DT_CHOSEN(zephyr_flash), write_block_size) #ifdef CONFIG_MCUBOOT_TRAILER_SWAP_TYPE #define SWAP_TYPE_OFFS(bank_area) ((bank_area)->fa_size -\ BOOT_MAGIC_SZ - BOOT_MAX_ALIGN * 3) #endif #define COPY_DONE_OFFS(bank_area) ((bank_area)->fa_size -\ BOOT_MAGIC_SZ - BOOT_MAX_ALIGN * 2) #define IMAGE_OK_OFFS(bank_area) ((bank_area)->fa_size - BOOT_MAGIC_SZ -\ BOOT_MAX_ALIGN) #define MAGIC_OFFS(bank_area) ((bank_area)->fa_size - BOOT_MAGIC_SZ) #if defined(CONFIG_BOOTLOADER_MCUBOOT) || defined(CONFIG_ZTEST) static const uint32_t boot_img_magic[4] = { 0xf395c277, 0x7fefd260, 0x0f505235, 0x8079b62c, }; #define BOOT_MAGIC_ARR_SZ ARRAY_SIZE(boot_img_magic) #endif struct boot_swap_table { /** For each field, a value of 0 means "any". */ uint8_t magic_primary_slot; uint8_t magic_secondary_slot; uint8_t image_ok_primary_slot; uint8_t image_ok_secondary_slot; uint8_t copy_done_primary_slot; uint8_t swap_type; }; #ifdef FLASH_AREA_IMAGE_SECONDARY /** * This set of tables maps image trailer contents to swap operation type. * When searching for a match, these tables must be iterated sequentially. * * NOTE: the table order is very important. The settings in the secondary * slot always are priority to the primary slot and should be located * earlier in the table. * * The table lists only states where there is action needs to be taken by * the bootloader, as in starting/finishing a swap operation. */ static const struct boot_swap_table boot_swap_tables[] = { { /* | slot-0 | slot-1 | *----------+------------+------------| * magic | Any | Good | * image-ok | Any | Unset | * ---------+------------+------------+ * swap: test | * -----------------------------------' */ .magic_primary_slot = BOOT_MAGIC_ANY, .magic_secondary_slot = BOOT_MAGIC_GOOD, .image_ok_primary_slot = BOOT_FLAG_ANY, .image_ok_secondary_slot = BOOT_FLAG_UNSET, .copy_done_primary_slot = BOOT_FLAG_ANY, .swap_type = BOOT_SWAP_TYPE_TEST, }, { /* | slot-0 | slot-1 | *----------+------------+------------| * magic | Any | Good | * image-ok | Any | 0x01 | * ---------+------------+------------+ * swap: permanent | * -----------------------------------' */ .magic_primary_slot = BOOT_MAGIC_ANY, .magic_secondary_slot = BOOT_MAGIC_GOOD, .image_ok_primary_slot = BOOT_FLAG_ANY, .image_ok_secondary_slot = BOOT_FLAG_SET, .copy_done_primary_slot = BOOT_FLAG_ANY, .swap_type = BOOT_SWAP_TYPE_PERM, }, { /* | slot-0 | slot-1 | *----------+------------+------------| * magic | Good | Unset | * image-ok | Unset | Any | * ---------+------------+------------+ * swap: revert (test image running) | * -----------------------------------' */ .magic_primary_slot = BOOT_MAGIC_GOOD, .magic_secondary_slot = BOOT_MAGIC_UNSET, .image_ok_primary_slot = BOOT_FLAG_UNSET, .image_ok_secondary_slot = BOOT_FLAG_ANY, .copy_done_primary_slot = BOOT_FLAG_SET, .swap_type = BOOT_SWAP_TYPE_REVERT, }, }; #define BOOT_SWAP_TABLES_COUNT (ARRAY_SIZE(boot_swap_tables)) #endif #if defined(CONFIG_BOOTLOADER_MCUBOOT) || defined(CONFIG_ZTEST) static int boot_magic_decode(const uint32_t *magic) { if (memcmp(magic, boot_img_magic, BOOT_MAGIC_SZ) == 0) { return BOOT_MAGIC_GOOD; } return BOOT_MAGIC_BAD; } static int boot_flag_decode(uint8_t flag) { if (flag != BOOT_FLAG_SET) { return BOOT_FLAG_BAD; } return BOOT_FLAG_SET; } #endif #if defined(CONFIG_BOOTLOADER_MCUBOOT) || defined(CONFIG_ZTEST) int flash_area_read_is_empty(const struct flash_area *fa, uint32_t off, void *dst, uint32_t len) { const uint8_t erase_val = flash_area_erased_val(fa); uint8_t *u8dst; uint8_t i; int rc; rc = flash_area_read(fa, off, dst, len); if (rc) { return rc; } for (i = 0, u8dst = (uint8_t *)dst; i < len; i++) { if (u8dst[i] != erase_val) { return 0; } } return 1; } #endif static int erased_flag_val(uint8_t bank_id) { const struct flash_area *fa; int rc; rc = flash_area_open(bank_id, &fa); if (rc) { return -EINVAL; } return flash_area_erased_val(fa); } #if !defined(CONFIG_BOOTLOADER_MCUBOOT) && !defined(CONFIG_ZTEST) /* Provided by MCUBoot */ int boot_magic_compatible_check(uint8_t tbl_val, uint8_t val); #else /** * Determines if a status source table is satisfied by the specified magic * code. * * @param tbl_val A magic field from a status source table. * @param val The magic value in a trailer, encoded as a * BOOT_MAGIC_[...]. * * @return 1 if the two values are compatible; * 0 otherwise. */ int boot_magic_compatible_check(uint8_t tbl_val, uint8_t val) { switch (tbl_val) { case BOOT_MAGIC_ANY: return 1; case BOOT_MAGIC_NOTGOOD: return val != BOOT_MAGIC_GOOD; default: return tbl_val == val; } } #endif static int boot_flag_offs(int flag, const struct flash_area *fa, uint32_t *offs) { switch (flag) { case BOOT_FLAG_COPY_DONE: *offs = COPY_DONE_OFFS(fa); return 0; case BOOT_FLAG_IMAGE_OK: *offs = IMAGE_OK_OFFS(fa); return 0; default: return -ENOTSUP; } } static int boot_write_trailer_byte(const struct flash_area *fa, uint32_t off, uint8_t val) { uint8_t buf[BOOT_MAX_ALIGN]; uint8_t align; uint8_t erased_val; int rc; align = flash_area_align(fa); __ASSERT_NO_MSG(align <= BOOT_MAX_ALIGN); erased_val = flash_area_erased_val(fa); memset(buf, erased_val, BOOT_MAX_ALIGN); buf[0] = val; rc = flash_area_write(fa, off, buf, align); if (rc != 0) { return -EIO; } return 0; } static int boot_flag_write(int flag, uint8_t bank_id) { const struct flash_area *fa; uint32_t offs; int rc; rc = flash_area_open(bank_id, &fa); if (rc) { return rc; } rc = boot_flag_offs(flag, fa, &offs); if (rc != 0) { flash_area_close(fa); return rc; } rc = boot_write_trailer_byte(fa, offs, BOOT_FLAG_SET); flash_area_close(fa); return rc; } static int boot_flag_read(int flag, uint8_t bank_id) { const struct flash_area *fa; uint32_t offs; int rc; uint8_t flag_val; rc = flash_area_open(bank_id, &fa); if (rc) { return rc; } rc = boot_flag_offs(flag, fa, &offs); if (rc != 0) { flash_area_close(fa); return rc; } rc = flash_area_read(fa, offs, &flag_val, sizeof(flag_val)); if (rc != 0) { return rc; } return flag_val; } static int boot_image_ok_read(uint8_t bank_id) { return boot_flag_read(BOOT_FLAG_IMAGE_OK, bank_id); } static int boot_image_ok_write(uint8_t bank_id) { return boot_flag_write(BOOT_FLAG_IMAGE_OK, bank_id); } #ifdef FLASH_AREA_IMAGE_SECONDARY static int boot_magic_write(uint8_t bank_id) { const struct flash_area *fa; uint32_t offs; int rc; rc = flash_area_open(bank_id, &fa); if (rc) { return rc; } offs = MAGIC_OFFS(fa); rc = flash_area_write(fa, offs, boot_img_magic, BOOT_MAGIC_SZ); flash_area_close(fa); return rc; } #ifdef CONFIG_MCUBOOT_TRAILER_SWAP_TYPE static int boot_swap_type_write(uint8_t bank_id, uint8_t swap_type) { const struct flash_area *fa; uint32_t offs; int rc; rc = flash_area_open(bank_id, &fa); if (rc) { return rc; } offs = SWAP_TYPE_OFFS(fa); rc = boot_write_trailer_byte(fa, offs, swap_type); flash_area_close(fa); return rc; } #endif /* CONFIG_MCUBOOT_TRAILER_SWAP_TYPE */ #endif /* FLASH_AREA_IMAGE_SECONDARY */ static int boot_read_v1_header(uint8_t area_id, struct mcuboot_v1_raw_header *v1_raw) { const struct flash_area *fa; int rc; rc = flash_area_open(area_id, &fa); if (rc) { return rc; } /* * Read and sanity-check the raw header. */ rc = flash_area_read(fa, 0, v1_raw, sizeof(*v1_raw)); flash_area_close(fa); if (rc) { return rc; } v1_raw->header_magic = sys_le32_to_cpu(v1_raw->header_magic); v1_raw->image_load_address = sys_le32_to_cpu(v1_raw->image_load_address); v1_raw->header_size = sys_le16_to_cpu(v1_raw->header_size); v1_raw->image_size = sys_le32_to_cpu(v1_raw->image_size); v1_raw->image_flags = sys_le32_to_cpu(v1_raw->image_flags); v1_raw->version.revision = sys_le16_to_cpu(v1_raw->version.revision); v1_raw->version.build_num = sys_le32_to_cpu(v1_raw->version.build_num); /* * Sanity checks. * * Larger values in header_size than BOOT_HEADER_SIZE_V1 are * possible, e.g. if Zephyr was linked with * CONFIG_ROM_START_OFFSET > BOOT_HEADER_SIZE_V1. */ if ((v1_raw->header_magic != BOOT_HEADER_MAGIC_V1) || (v1_raw->header_size < BOOT_HEADER_SIZE_V1)) { return -EIO; } return 0; } int boot_read_bank_header(uint8_t area_id, struct mcuboot_img_header *header, size_t header_size) { int rc; struct mcuboot_v1_raw_header v1_raw; struct mcuboot_img_sem_ver *sem_ver; size_t v1_min_size = (sizeof(uint32_t) + sizeof(struct mcuboot_img_header_v1)); /* * Only version 1 image headers are supported. */ if (header_size < v1_min_size) { return -ENOMEM; } rc = boot_read_v1_header(area_id, &v1_raw); if (rc) { return rc; } /* * Copy just the fields we care about into the return parameter. * * - header_magic: skip (only used to check format) * - image_load_address: skip (only matters for PIC code) * - header_size: skip (only used to check format) * - image_size: include * - image_flags: skip (all unsupported or not relevant) * - version: include */ header->mcuboot_version = 1U; header->h.v1.image_size = v1_raw.image_size; sem_ver = &header->h.v1.sem_ver; sem_ver->major = v1_raw.version.major; sem_ver->minor = v1_raw.version.minor; sem_ver->revision = v1_raw.version.revision; sem_ver->build_num = v1_raw.version.build_num; return 0; } #if defined(CONFIG_BOOTLOADER_MCUBOOT) || defined(CONFIG_ZTEST) static int boot_read_swap_state(const struct flash_area *fa, struct boot_swap_state *state) { uint32_t magic[BOOT_MAGIC_ARR_SZ]; uint32_t off; int rc; off = MAGIC_OFFS(fa); rc = flash_area_read_is_empty(fa, off, magic, BOOT_MAGIC_SZ); if (rc < 0) { return -EIO; } if (rc == 1) { state->magic = BOOT_MAGIC_UNSET; } else { state->magic = boot_magic_decode(magic); } #ifdef CONFIG_MCUBOOT_TRAILER_SWAP_TYPE off = SWAP_TYPE_OFFS(fa); rc = flash_area_read_is_empty(fa, off, &state->swap_type, sizeof(state->swap_type)); if (rc < 0) { return -EIO; } if (rc == 1 || state->swap_type > BOOT_SWAP_TYPE_REVERT) { state->swap_type = BOOT_SWAP_TYPE_NONE; } off = COPY_DONE_OFFS(fa); rc = flash_area_read_is_empty(fa, off, &state->copy_done, sizeof(state->copy_done)); if (rc < 0) { return -EIO; } if (rc == 1) { state->copy_done = BOOT_FLAG_UNSET; } else { state->copy_done = boot_flag_decode(state->copy_done); } #else #ifdef FLASH_AREA_IMAGE_SCRATCH if (fa->fa_id != FLASH_AREA_IMAGE_SCRATCH) { #endif off = COPY_DONE_OFFS(fa); rc = flash_area_read_is_empty(fa, off, &state->copy_done, sizeof(state->copy_done)); if (rc < 0) { return -EIO; } if (rc == 1) { state->copy_done = BOOT_FLAG_UNSET; } else { state->copy_done = boot_flag_decode(state->copy_done); } #ifdef FLASH_AREA_IMAGE_SCRATCH } #endif #endif off = IMAGE_OK_OFFS(fa); rc = flash_area_read_is_empty(fa, off, &state->image_ok, sizeof(state->image_ok)); if (rc < 0) { return -EIO; } if (rc == 1) { state->image_ok = BOOT_FLAG_UNSET; } else { state->image_ok = boot_flag_decode(state->image_ok); } return 0; } #endif #if defined(CONFIG_BOOTLOADER_MCUBOOT) || defined(CONFIG_ZTEST) /** * Reads the image trailer from the scratch area. */ int boot_read_swap_state_by_id(int flash_area_id, struct boot_swap_state *state) { const struct flash_area *fap; int rc; switch (flash_area_id) { #ifdef FLASH_AREA_IMAGE_SCRATCH case FLASH_AREA_IMAGE_SCRATCH: #endif case FLASH_AREA_IMAGE_PRIMARY: #ifdef FLASH_AREA_IMAGE_SECONDARY case FLASH_AREA_IMAGE_SECONDARY: #endif rc = flash_area_open(flash_area_id, &fap); if (rc != 0) { return -EIO; } break; default: return -EINVAL; } rc = boot_read_swap_state(fap, state); flash_area_close(fap); return rc; } #endif /* equivalent of boot_swap_type() in mcuboot bootutil_misc.c */ int mcuboot_swap_type(void) { #ifdef FLASH_AREA_IMAGE_SECONDARY const struct boot_swap_table *table; struct boot_swap_state primary_slot; struct boot_swap_state secondary_slot; int rc; size_t i; rc = boot_read_swap_state_by_id(FLASH_AREA_IMAGE_PRIMARY, &primary_slot); if (rc) { return rc; } rc = boot_read_swap_state_by_id(FLASH_AREA_IMAGE_SECONDARY, &secondary_slot); if (rc) { return rc; } for (i = 0; i < BOOT_SWAP_TABLES_COUNT; i++) { table = boot_swap_tables + i; if (boot_magic_compatible_check(table->magic_primary_slot, primary_slot.magic) && boot_magic_compatible_check(table->magic_secondary_slot, secondary_slot.magic) && (table->image_ok_primary_slot == BOOT_FLAG_ANY || table->image_ok_primary_slot == primary_slot.image_ok) && (table->image_ok_secondary_slot == BOOT_FLAG_ANY || table->image_ok_secondary_slot == secondary_slot.image_ok) && (table->copy_done_primary_slot == BOOT_FLAG_ANY || table->copy_done_primary_slot == primary_slot.copy_done)) { __ASSERT_NO_MSG(table->swap_type == BOOT_SWAP_TYPE_TEST || table->swap_type == BOOT_SWAP_TYPE_PERM || table->swap_type == BOOT_SWAP_TYPE_REVERT); return table->swap_type; } } #endif return BOOT_SWAP_TYPE_NONE; } int boot_request_upgrade(int permanent) { #ifdef FLASH_AREA_IMAGE_SECONDARY #ifdef CONFIG_MCUBOOT_TRAILER_SWAP_TYPE uint8_t swap_type; #endif int rc; rc = boot_magic_write(FLASH_AREA_IMAGE_SECONDARY); if (rc) { goto op_end; } if (permanent) { rc = boot_image_ok_write(FLASH_AREA_IMAGE_SECONDARY); #ifdef CONFIG_MCUBOOT_TRAILER_SWAP_TYPE if (rc) { goto op_end; } swap_type = BOOT_SWAP_TYPE_PERM; } else { swap_type = BOOT_SWAP_TYPE_TEST; } rc = boot_swap_type_write(FLASH_AREA_IMAGE_SECONDARY, swap_type); #else } #endif op_end: return rc; #else return 0; #endif /* FLASH_AREA_IMAGE_SECONDARY */ } bool boot_is_img_confirmed(void) { return boot_image_ok_read(FLASH_AREA_IMAGE_PRIMARY) == BOOT_FLAG_SET; } int boot_write_img_confirmed(void) { int rc; if (boot_image_ok_read(FLASH_AREA_IMAGE_PRIMARY) != erased_flag_val(FLASH_AREA_IMAGE_PRIMARY)) { /* Already confirmed. */ return 0; } rc = boot_image_ok_write(FLASH_AREA_IMAGE_PRIMARY); return rc; } int boot_erase_img_bank(uint8_t area_id) { const struct flash_area *fa; int rc; rc = flash_area_open(area_id, &fa); if (rc) { return rc; } rc = flash_area_erase(fa, 0, fa->fa_size); flash_area_close(fa); return rc; }