| /* |
| * Copyright (c) 2017 Nordic Semiconductor ASA |
| * Copyright (c) 2016-2017 Linaro Limited |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <assert.h> |
| #include <stddef.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <flash.h> |
| #include <zephyr.h> |
| #include <init.h> |
| |
| #include <misc/__assert.h> |
| #include <misc/byteorder.h> |
| #include <board.h> |
| #include <dfu/mcuboot.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 |
| |
| /* Trailer: */ |
| #define BOOT_MAX_ALIGN 8 |
| #define BOOT_MAGIC_SZ 16 |
| #define BOOT_FLAG_SET 0x01 |
| #define BOOT_FLAG_UNSET 0xff |
| |
| /* |
| * Raw (on-flash) representation of the v1 image header. |
| */ |
| struct mcuboot_v1_raw_header { |
| u32_t header_magic; |
| u32_t image_load_address; |
| u16_t header_size; |
| u16_t pad; |
| u32_t image_size; |
| u32_t image_flags; |
| struct { |
| u8_t major; |
| u8_t minor; |
| u16_t revision; |
| u32_t build_num; |
| } version; |
| u32_t pad2; |
| } __packed; |
| |
| /* |
| * End of strict defines |
| */ |
| |
| #define BOOT_MAGIC_GOOD 1 |
| #define BOOT_MAGIC_BAD 2 |
| #define BOOT_MAGIC_UNSET 3 |
| |
| #define BOOT_FLAG_IMAGE_OK 0 |
| #define BOOT_FLAG_COPY_DONE 1 |
| |
| #define FLASH_MIN_WRITE_SIZE FLASH_WRITE_BLOCK_SIZE |
| #define FLASH_BANK0_OFFSET FLASH_AREA_IMAGE_0_OFFSET |
| |
| /* FLASH_AREA_IMAGE_XX_YY values used below are auto-generated thanks to DT */ |
| #define FLASH_BANK_SIZE FLASH_AREA_IMAGE_0_SIZE |
| #define FLASH_BANK1_OFFSET FLASH_AREA_IMAGE_1_OFFSET |
| #define FLASH_STATE_OFFSET (FLASH_AREA_IMAGE_SCRATCH_OFFSET +\ |
| FLASH_AREA_IMAGE_SCRATCH_SIZE) |
| |
| #define COPY_DONE_OFFS(bank_offs) (bank_offs + FLASH_BANK_SIZE -\ |
| BOOT_MAGIC_SZ - BOOT_MAX_ALIGN * 2) |
| |
| #define IMAGE_OK_OFFS(bank_offs) (bank_offs + FLASH_BANK_SIZE - BOOT_MAGIC_SZ -\ |
| BOOT_MAX_ALIGN) |
| #define MAGIC_OFFS(bank_offs) (bank_offs + FLASH_BANK_SIZE - BOOT_MAGIC_SZ) |
| |
| static const u32_t boot_img_magic[4] = { |
| 0xf395c277, |
| 0x7fefd260, |
| 0x0f505235, |
| 0x8079b62c, |
| }; |
| |
| struct boot_swap_table { |
| /** For each field, a value of 0 means "any". */ |
| u8_t magic_slot0; |
| u8_t magic_slot1; |
| u8_t image_ok_slot0; |
| u8_t image_ok_slot1; |
| u8_t copy_done_slot0; |
| |
| u8_t swap_type; |
| }; |
| |
| /** Represents the management state of a single image slot. */ |
| struct boot_swap_state { |
| u8_t magic; /* One of the BOOT_MAGIC_[...] values. */ |
| u8_t copy_done; |
| u8_t image_ok; |
| }; |
| |
| /** |
| * This set of tables maps image trailer contents to swap operation type. |
| * When searching for a match, these tables must be iterated sequentially. |
| */ |
| static const struct boot_swap_table boot_swap_tables[] = { |
| { |
| /* | slot-0 | slot-1 | |
| *----------+------------+------------| |
| * magic | Any | Good | |
| * image-ok | Any | Unset | |
| * ---------+------------+------------+ |
| * swap: test | |
| * -----------------------------------' |
| */ |
| .magic_slot0 = 0, |
| .magic_slot1 = BOOT_MAGIC_GOOD, |
| .image_ok_slot0 = 0, |
| .image_ok_slot1 = 0xff, |
| .copy_done_slot0 = 0, |
| .swap_type = BOOT_SWAP_TYPE_TEST, |
| }, |
| { |
| /* | slot-0 | slot-1 | |
| *----------+------------+------------| |
| * magic | Any | Good | |
| * image-ok | Any | 0x01 | |
| * ---------+------------+------------+ |
| * swap: permanent | |
| * -----------------------------------' |
| */ |
| .magic_slot0 = 0, |
| .magic_slot1 = BOOT_MAGIC_GOOD, |
| .image_ok_slot0 = 0, |
| .image_ok_slot1 = 0x01, |
| .copy_done_slot0 = 0, |
| .swap_type = BOOT_SWAP_TYPE_PERM, |
| }, |
| { |
| /* | slot-0 | slot-1 | |
| *----------+------------+------------| |
| * magic | Good | Unset | |
| * image-ok | Unset | Any | |
| * ---------+------------+------------+ |
| * swap: revert (test image running) | |
| * -----------------------------------' |
| */ |
| .magic_slot0 = BOOT_MAGIC_GOOD, |
| .magic_slot1 = BOOT_MAGIC_UNSET, |
| .image_ok_slot0 = 0xff, |
| .image_ok_slot1 = 0, |
| .copy_done_slot0 = 0x01, |
| .swap_type = BOOT_SWAP_TYPE_REVERT, |
| }, |
| }; |
| #define BOOT_SWAP_TABLES_COUNT (ARRAY_SIZE(boot_swap_tables)) |
| |
| static struct device *flash_dev; |
| |
| static int boot_flag_offs(int flag, u32_t bank_offs, u32_t *offs) |
| { |
| switch (flag) { |
| case BOOT_FLAG_COPY_DONE: |
| *offs = COPY_DONE_OFFS(bank_offs); |
| return 0; |
| case BOOT_FLAG_IMAGE_OK: |
| *offs = IMAGE_OK_OFFS(bank_offs); |
| return 0; |
| default: |
| return -ENOTSUP; |
| } |
| } |
| |
| static int boot_flash_write(off_t offs, const void *data, size_t len) |
| { |
| int rc; |
| |
| rc = flash_write_protection_set(flash_dev, false); |
| if (rc) { |
| return rc; |
| } |
| |
| rc = flash_write(flash_dev, offs, data, len); |
| if (rc) { |
| return rc; |
| } |
| |
| rc = flash_write_protection_set(flash_dev, true); |
| |
| return rc; |
| } |
| |
| static int boot_flag_write(int flag, u32_t bank_offs) |
| { |
| u8_t buf[FLASH_MIN_WRITE_SIZE]; |
| u32_t offs; |
| int rc; |
| |
| rc = boot_flag_offs(flag, bank_offs, &offs); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| memset(buf, BOOT_FLAG_UNSET, sizeof(buf)); |
| buf[0] = BOOT_FLAG_SET; |
| |
| rc = boot_flash_write(offs, buf, sizeof(buf)); |
| |
| return rc; |
| } |
| |
| static int boot_flag_read(int flag, u32_t bank_offs) |
| { |
| u32_t offs; |
| int rc; |
| u8_t flag_val; |
| |
| rc = boot_flag_offs(flag, bank_offs, &offs); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| rc = flash_read(flash_dev, offs, &flag_val, sizeof(flag_val)); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| return flag_val; |
| } |
| |
| static int boot_image_ok_read(u32_t bank_offs) |
| { |
| return boot_flag_read(BOOT_FLAG_IMAGE_OK, bank_offs); |
| } |
| |
| static int boot_image_ok_write(u32_t bank_offs) |
| { |
| return boot_flag_write(BOOT_FLAG_IMAGE_OK, bank_offs); |
| } |
| |
| static int boot_copy_done_read(u32_t bank_offs) |
| { |
| return boot_flag_read(BOOT_FLAG_COPY_DONE, bank_offs); |
| } |
| |
| static int boot_magic_write(u32_t bank_offs) |
| { |
| u32_t offs; |
| int rc; |
| |
| offs = MAGIC_OFFS(bank_offs); |
| |
| rc = boot_flash_write(offs, boot_img_magic, BOOT_MAGIC_SZ); |
| |
| return rc; |
| } |
| |
| static int boot_read_v1_header(u32_t bank_offset, |
| struct mcuboot_v1_raw_header *v1_raw) |
| { |
| int rc; |
| |
| /* |
| * Read and sanity-check the raw header. |
| */ |
| rc = flash_read(flash_dev, bank_offset, v1_raw, sizeof(*v1_raw)); |
| 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_TEXT_SECTION_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(u32_t bank_offset, |
| 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(u32_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(bank_offset, &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 = 1; |
| 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; |
| } |
| |
| static int boot_magic_code_check(const u32_t *magic) |
| { |
| int i; |
| |
| if (memcmp(magic, boot_img_magic, sizeof(boot_img_magic)) == 0) { |
| return BOOT_MAGIC_GOOD; |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(boot_img_magic); i++) { |
| if (magic[i] != 0xffffffff) { |
| return BOOT_MAGIC_BAD; |
| } |
| } |
| |
| return BOOT_MAGIC_UNSET; |
| } |
| |
| static int boot_magic_state_read(u32_t bank_offs) |
| { |
| u32_t magic[4]; |
| u32_t offs; |
| int rc; |
| |
| offs = MAGIC_OFFS(bank_offs); |
| rc = flash_read(flash_dev, offs, magic, sizeof(magic)); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| return boot_magic_code_check(magic); |
| } |
| |
| static int boot_read_swap_state(u32_t bank_offs, struct boot_swap_state *state) |
| { |
| int rc; |
| |
| rc = boot_magic_state_read(bank_offs); |
| if (rc < 0) { |
| return rc; |
| } |
| state->magic = rc; |
| |
| if (bank_offs != FLASH_AREA_IMAGE_SCRATCH_OFFSET) { |
| rc = boot_copy_done_read(bank_offs); |
| if (rc < 0) { |
| return rc; |
| } |
| state->copy_done = rc; |
| } |
| |
| rc = boot_image_ok_read(bank_offs); |
| if (rc < 0) { |
| return rc; |
| } |
| state->image_ok = rc; |
| |
| return 0; |
| } |
| |
| int boot_swap_type(void) |
| { |
| const struct boot_swap_table *table; |
| struct boot_swap_state state_slot0; |
| struct boot_swap_state state_slot1; |
| int rc; |
| int i; |
| |
| rc = boot_read_swap_state(FLASH_BANK0_OFFSET, &state_slot0); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| rc = boot_read_swap_state(FLASH_BANK1_OFFSET, &state_slot1); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| for (i = 0; i < BOOT_SWAP_TABLES_COUNT; i++) { |
| table = boot_swap_tables + i; |
| |
| if ((table->magic_slot0 == 0 || |
| table->magic_slot0 == state_slot0.magic) && |
| (table->magic_slot1 == 0 || |
| table->magic_slot1 == state_slot1.magic) && |
| (table->image_ok_slot0 == 0 || |
| table->image_ok_slot0 == state_slot0.image_ok) && |
| (table->image_ok_slot1 == 0 || |
| table->image_ok_slot1 == state_slot1.image_ok) && |
| (table->copy_done_slot0 == 0 || |
| table->copy_done_slot0 == state_slot0.copy_done)) { |
| |
| assert(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; |
| } |
| } |
| |
| return BOOT_SWAP_TYPE_NONE; |
| } |
| |
| int boot_request_upgrade(int permanent) |
| { |
| int rc; |
| |
| rc = boot_magic_write(FLASH_BANK1_OFFSET); |
| if (rc == 0 && permanent) { |
| rc = boot_image_ok_write(FLASH_BANK1_OFFSET); |
| } |
| |
| return rc; |
| } |
| |
| bool boot_is_img_confirmed(void) |
| { |
| return boot_image_ok_read(FLASH_BANK0_OFFSET) == BOOT_FLAG_SET; |
| } |
| |
| int boot_write_img_confirmed(void) |
| { |
| int rc; |
| |
| if (boot_image_ok_read(FLASH_BANK0_OFFSET) != BOOT_FLAG_UNSET) { |
| /* Already confirmed. */ |
| return 0; |
| } |
| |
| rc = boot_image_ok_write(FLASH_BANK0_OFFSET); |
| |
| return rc; |
| } |
| |
| int boot_erase_img_bank(u32_t bank_offset) |
| { |
| int rc; |
| |
| rc = flash_write_protection_set(flash_dev, false); |
| if (rc) { |
| return rc; |
| } |
| |
| rc = flash_erase(flash_dev, bank_offset, FLASH_BANK_SIZE); |
| if (rc) { |
| return rc; |
| } |
| |
| rc = flash_write_protection_set(flash_dev, true); |
| |
| return rc; |
| } |
| |
| static int boot_init(struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| flash_dev = device_get_binding(FLASH_DEV_NAME); |
| if (!flash_dev) { |
| return -ENODEV; |
| } |
| return 0; |
| } |
| |
| SYS_INIT(boot_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); |