blob: 83b5dac88ecb223ad51e0482d0518efbb1f9f594 [file] [log] [blame]
/*
* 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 <drivers/flash.h>
#include <storage/flash_map.h>
#include <zephyr.h>
#include <init.h>
#include <sys/__assert.h>
#include <sys/byteorder.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_FLAG_SET 1
#define BOOT_FLAG_BAD 2
#define BOOT_FLAG_UNSET 3
#define BOOT_FLAG_ANY 4 /* NOTE: control only, not dependent on sector */
/*
* 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_MAGIC_ANY 4 /* NOTE: control only, not dependent on sector */
#define BOOT_MAGIC_NOTGOOD 5 /* NOTE: control only, not dependent on sector */
#define BOOT_FLAG_IMAGE_OK 0
#define BOOT_FLAG_COPY_DONE 1
#define FLASH_MIN_WRITE_SIZE DT_FLASH_WRITE_BLOCK_SIZE
/* DT_FLASH_AREA_IMAGE_XX_YY values used below are auto-generated by DT */
#ifdef CONFIG_TRUSTED_EXECUTION_NONSECURE
#define FLASH_AREA_IMAGE_PRIMARY DT_FLASH_AREA_IMAGE_0_NONSECURE_ID
#define FLASH_AREA_IMAGE_SECONDARY DT_FLASH_AREA_IMAGE_1_NONSECURE_ID
#define FLASH_AREA_IMAGE_SCRATCH DT_FLASH_AREA_IMAGE_SCRATCH_ID
#else
#define FLASH_AREA_IMAGE_PRIMARY DT_FLASH_AREA_IMAGE_0_ID
#define FLASH_AREA_IMAGE_SECONDARY DT_FLASH_AREA_IMAGE_1_ID
#define FLASH_AREA_IMAGE_SCRATCH DT_FLASH_AREA_IMAGE_SCRATCH_ID
#endif /* CONFIG_TRUSTED_EXECUTION_NONSECURE */
#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)
static const u32_t boot_img_magic[4] = {
0xf395c277,
0x7fefd260,
0x0f505235,
0x8079b62c,
};
#define BOOT_MAGIC_ARR_SZ ARRAY_SIZE(boot_img_magic)
struct boot_swap_table {
/** For each field, a value of 0 means "any". */
u8_t magic_primary_slot;
u8_t magic_secondary_slot;
u8_t image_ok_primary_slot;
u8_t image_ok_secondary_slot;
u8_t copy_done_primary_slot;
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 swap_type; /* One of the BOOT_SWAP_TYPE_[...] values. */
u8_t copy_done; /* One of the BOOT_FLAG_[...] values. */
u8_t image_ok; /* One of the BOOT_FLAG_[...] values. */
};
/**
* 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))
static int boot_magic_decode(const u32_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(u8_t flag)
{
if (flag != BOOT_FLAG_SET) {
return BOOT_FLAG_BAD;
}
return BOOT_FLAG_SET;
}
/* TODO: this function should be moved to flash_area api in future */
uint8_t flash_area_erased_val(const struct flash_area *fa)
{
#define ERASED_VAL 0xff
(void)fa;
return ERASED_VAL;
}
/* TODO: this function should be moved to flash_area api in future */
int flash_area_read_is_empty(const struct flash_area *fa, uint32_t off,
void *dst, uint32_t len)
{
const u8_t erase_val = flash_area_erased_val(fa);
u8_t *u8dst;
u8_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;
}
static int erased_flag_val(u8_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);
}
/**
* 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(u8_t tbl_val, u8_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;
}
}
static int boot_flag_offs(int flag, const struct flash_area *fa, u32_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, u32_t off,
u8_t val)
{
u8_t buf[BOOT_MAX_ALIGN];
u8_t align;
u8_t erased_val;
int rc;
align = flash_area_align(fa);
assert(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, u8_t bank_id)
{
const struct flash_area *fa;
u32_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, u8_t bank_id)
{
const struct flash_area *fa;
u32_t offs;
int rc;
u8_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(u8_t bank_id)
{
return boot_flag_read(BOOT_FLAG_IMAGE_OK, bank_id);
}
static int boot_image_ok_write(u8_t bank_id)
{
return boot_flag_write(BOOT_FLAG_IMAGE_OK, bank_id);
}
static int boot_magic_write(u8_t bank_id)
{
const struct flash_area *fa;
u32_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(u8_t bank_id, u8_t swap_type)
{
const struct flash_area *fa;
u32_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
static int boot_read_v1_header(u8_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_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(u8_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(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(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;
}
static int boot_read_swap_state(const struct flash_area *fa,
struct boot_swap_state *state)
{
u32_t magic[BOOT_MAGIC_ARR_SZ];
u32_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
if (fa->fa_id != FLASH_AREA_IMAGE_SCRATCH) {
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);
}
}
#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;
}
/**
* 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) {
case FLASH_AREA_IMAGE_SCRATCH:
case FLASH_AREA_IMAGE_PRIMARY:
case FLASH_AREA_IMAGE_SECONDARY:
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;
}
/* equivalent of boot_swap_type() in mcuboot bootutil_misc.c */
int mcuboot_swap_type(void)
{
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(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)
{
#ifdef CONFIG_MCUBOOT_TRAILER_SWAP_TYPE
u8_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;
}
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(u8_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;
}