508 lines
11 KiB
C
508 lines
11 KiB
C
/*
|
|
* 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 <flash_map.h>
|
|
#include <zephyr.h>
|
|
#include <init.h>
|
|
|
|
#include <misc/__assert.h>
|
|
#include <misc/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_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 DT_FLASH_WRITE_BLOCK_SIZE
|
|
|
|
/* DT_FLASH_AREA_IMAGE_XX_YY values used below are auto-generated by DT */
|
|
#define FLASH_BANK0_ID DT_FLASH_AREA_IMAGE_0_ID
|
|
#define FLASH_BANK1_ID DT_FLASH_AREA_IMAGE_1_ID
|
|
|
|
#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,
|
|
};
|
|
|
|
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 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_flag_write(int flag, u8_t bank_id)
|
|
{
|
|
const struct flash_area *fa;
|
|
u8_t buf[FLASH_MIN_WRITE_SIZE];
|
|
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;
|
|
}
|
|
|
|
(void)memset(buf, BOOT_FLAG_UNSET, sizeof(buf));
|
|
buf[0] = BOOT_FLAG_SET;
|
|
|
|
rc = flash_area_write(fa, offs, buf, sizeof(buf));
|
|
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_copy_done_read(u8_t bank_id)
|
|
{
|
|
return boot_flag_read(BOOT_FLAG_COPY_DONE, 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;
|
|
}
|
|
|
|
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 = 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(u8_t bank_id)
|
|
{
|
|
const struct flash_area *fa;
|
|
u32_t magic[(sizeof(u32_t) - 1 + BOOT_MAGIC_SZ) / sizeof(u32_t)];
|
|
u32_t offs;
|
|
int rc;
|
|
|
|
rc = flash_area_open(bank_id, &fa);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
offs = MAGIC_OFFS(fa);
|
|
rc = flash_area_read(fa, offs, magic, BOOT_MAGIC_SZ);
|
|
flash_area_close(fa);
|
|
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
return boot_magic_code_check(magic);
|
|
}
|
|
|
|
static int boot_read_swap_state(u8_t bank_id, struct boot_swap_state *state)
|
|
{
|
|
int rc;
|
|
|
|
rc = boot_magic_state_read(bank_id);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
state->magic = rc;
|
|
|
|
if (bank_id != DT_FLASH_AREA_IMAGE_SCRATCH_ID) {
|
|
rc = boot_copy_done_read(bank_id);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
state->copy_done = rc;
|
|
}
|
|
|
|
rc = boot_image_ok_read(bank_id);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
state->image_ok = rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mcuboot_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_ID, &state_slot0);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
rc = boot_read_swap_state(FLASH_BANK1_ID, &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_ID);
|
|
if (rc == 0 && permanent) {
|
|
rc = boot_image_ok_write(FLASH_BANK1_ID);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool boot_is_img_confirmed(void)
|
|
{
|
|
return boot_image_ok_read(FLASH_BANK0_ID) == BOOT_FLAG_SET;
|
|
}
|
|
|
|
int boot_write_img_confirmed(void)
|
|
{
|
|
int rc;
|
|
|
|
if (boot_image_ok_read(FLASH_BANK0_ID) != BOOT_FLAG_UNSET) {
|
|
/* Already confirmed. */
|
|
return 0;
|
|
}
|
|
|
|
rc = boot_image_ok_write(FLASH_BANK0_ID);
|
|
|
|
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;
|
|
}
|