blob: 9fb74203540c9804706b4eb2bb8b3c4526de2f3a [file] [log] [blame]
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright (c) 2017-2019 Linaro LTD
* Copyright (c) 2016-2019 JUUL Labs
* Copyright (c) 2019-2020 Arm Limited
* Copyright (c) 2020 Cypress Semiconductors
*
* Original license:
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "crc32c.h"
#include <string.h>
#include <stdlib.h>
#include "swap_status.h"
#include "sysflash/sysflash.h"
#ifdef MCUBOOT_SWAP_USING_STATUS
static uint8_t record_buff[BOOT_SWAP_STATUS_ROW_SZ];
static uint8_t status_buff[BOOT_SWAP_STATUS_PAYLD_SZ];
static const struct flash_area *last_fap_stat;
static uint32_t last_fin_offset;
static void status_buff_cache_inv(void)
{
last_fap_stat = NULL;
}
static void status_buff_cache_upd(const struct flash_area* fap, uint32_t offset)
{
last_fap_stat = fap;
last_fin_offset = offset;
}
static bool status_buff_cache_valid(const struct flash_area* fap, uint32_t offset)
{
return (last_fap_stat == fap) && (last_fin_offset == offset);
}
const uint32_t stat_part_magic[] = {
BOOT_SWAP_STATUS_MAGIC
};
static inline uint32_t calc_rec_idx(uint32_t value)
{
return value / BOOT_SWAP_STATUS_PAYLD_SZ;
}
static inline uint32_t calc_record_offs(uint32_t offs)
{
return BOOT_SWAP_STATUS_ROW_SZ * calc_rec_idx(offs);
}
static inline uint32_t calc_record_crc(const uint8_t *data, uint32_t length)
{
return crc32c_checksum(data, length);
}
static inline uint32_t pack_bytes_u32(const uint8_t *data)
{
uint32_t result = 0U;
result = ((uint32_t)data[3U] << 24U) | ((uint32_t)data[2U] << 16U) |
((uint32_t)data[1U] << 8U) | (uint32_t)data[0U];
return result;
}
int32_t swap_status_init_offset(uint8_t area_id)
{
int32_t result = -1;
int32_t offset = 0;
uint32_t i;
#ifdef MCUBOOT_SWAP_USING_SCRATCH
#define ADD_ARRAY_MEMBER_FOR_SCRATCH (1U)
#else
#define ADD_ARRAY_MEMBER_FOR_SCRATCH (0U)
#endif /* MCUBOOT_SWAP_USING_SCRATCH */
/* we always have at least 2 images in BOOT and UPGRADE slots */
#define ARR_SIZE (SLOTS_FOR_IMAGE + ADD_ARRAY_MEMBER_FOR_SCRATCH + ((uint8_t)BOOT_IMAGE_NUMBER - 1U) * SLOTS_FOR_IMAGE)
uint8_t slots_ids[ARR_SIZE];
slots_ids[0] = FLASH_AREA_IMAGE_PRIMARY(0U);
slots_ids[1] = FLASH_AREA_IMAGE_SECONDARY(0U);
#ifdef MCUBOOT_SWAP_USING_SCRATCH
/* The third position of SCRATCH is saved as it was before */
slots_ids[2] = FLASH_AREA_IMAGE_SCRATCH;
#endif /* MCUBOOT_SWAP_USING_SCRATCH */
#if (BOOT_IMAGE_NUMBER > 1)
uint8_t primary_slots_per_id = SLOTS_FOR_IMAGE + ADD_ARRAY_MEMBER_FOR_SCRATCH;
uint8_t secondary_slots_per_id = primary_slots_per_id + 1U;
for (i = 1U; i < (uint32_t) BOOT_IMAGE_NUMBER; ++i)
{
slots_ids[primary_slots_per_id] = FLASH_AREA_IMAGE_PRIMARY( i );
slots_ids[secondary_slots_per_id] = FLASH_AREA_IMAGE_SECONDARY( i );
primary_slots_per_id += 2U;
secondary_slots_per_id += 2U;
}
#endif /* BOOT_IMAGE_NUMBER > 1 */
for (i = 0U; i < ARR_SIZE; i++) {
if (slots_ids[i] == area_id) {
result = offset;
break;
}
offset += BOOT_SWAP_STATUS_SIZE;
}
return result;
}
static int swap_status_read_record(uint32_t rec_offset, uint8_t *data, uint32_t *copy_counter, uint32_t *max_idx)
{ /* returns BOOT_SWAP_STATUS_PAYLD_SZ of data */
int rc = -1;
uint32_t fin_offset;
uint32_t data_offset = 0;
uint32_t counter, crc, magic;
uint32_t crc_fail = 0;
uint32_t magic_fail = 0;
uint32_t max_cnt = 0;
const struct flash_area *fap_stat = NULL;
rc = flash_area_open(FLASH_AREA_IMAGE_SWAP_STATUS, &fap_stat);
if (rc != 0) {
return -1;
}
else {
/* loop over copies/duplicates */
for (uint32_t i = 0; i < BOOT_SWAP_STATUS_MULT; i++) {
/* calculate final duplicate offset */
fin_offset = rec_offset + i * BOOT_SWAP_STATUS_D_SIZE;
rc = flash_area_read(fap_stat, fin_offset, record_buff, sizeof(record_buff));
if (rc != 0) {
return -1;
}
else {
/* read magic value to know if area was pre-erased */
magic = pack_bytes_u32(&record_buff[BOOT_SWAP_STATUS_PAYLD_SZ]);
if (magic == BOOT_SWAP_STATUS_MAGIC) { /* read CRC */
crc = pack_bytes_u32(&record_buff[BOOT_SWAP_STATUS_ROW_SZ -
BOOT_SWAP_STATUS_CRC_SZ]);
/* check record data integrity first */
if (crc == calc_record_crc(record_buff, BOOT_SWAP_STATUS_ROW_SZ-BOOT_SWAP_STATUS_CRC_SZ)) {
/* look for counter */
counter = pack_bytes_u32(&record_buff[BOOT_SWAP_STATUS_ROW_SZ -
BOOT_SWAP_STATUS_CNT_SZ -
BOOT_SWAP_STATUS_CRC_SZ]);
/* find out counter max */
if (counter >= max_cnt) {
max_cnt = counter;
*max_idx = i;
data_offset = fin_offset;
}
}
/* if crc != calculated() */
else {
crc_fail++;
}
}
else {
magic_fail++;
}
}
}
}
/* no magic found - status area is pre-erased, start from scratch */
if (magic_fail == BOOT_SWAP_STATUS_MULT) {
/* emulate last index was received, so next will start from beginning */
*max_idx = BOOT_SWAP_STATUS_MULT - 1U;
*copy_counter = 0;
/* return all erased values */
if (status_buff_cache_valid(fap_stat, fin_offset) == false) {
(void)memset(data, (int32_t)flash_area_erased_val(fap_stat), BOOT_SWAP_STATUS_PAYLD_SZ);
status_buff_cache_upd(fap_stat, fin_offset);
}
}
else {
/* no valid CRC found - status pre-read failure */
if (crc_fail == BOOT_SWAP_STATUS_MULT) {
rc = -1;
}
else {
*copy_counter = max_cnt;
/* read payload data */
rc = flash_area_read(fap_stat, data_offset, data, BOOT_SWAP_STATUS_PAYLD_SZ);
status_buff_cache_inv();
if (rc != 0) {
rc = -1;
}
}
}
flash_area_close(fap_stat);
/* return back duplicate index */
return rc;
}
static int swap_status_write_record(uint32_t rec_offset, uint32_t copy_num, uint32_t copy_counter, const uint8_t *data)
{ /* it receives explicitly BOOT_SWAP_STATUS_PAYLD_SZ of data */
int rc = -1;
uint32_t fin_offset;
/* increment counter field */
uint32_t next_counter = copy_counter + 1U;
uint32_t next_crc;
const struct flash_area *fap_stat = NULL;
rc = flash_area_open(FLASH_AREA_IMAGE_SWAP_STATUS, &fap_stat);
if (rc != 0) {
return -1;
}
/* copy data into buffer */
(void)memcpy(record_buff, data, BOOT_SWAP_STATUS_PAYLD_SZ);
/* append next counter to whole record row */
(void)memcpy(&record_buff[BOOT_SWAP_STATUS_ROW_SZ-BOOT_SWAP_STATUS_CNT_SZ-BOOT_SWAP_STATUS_CRC_SZ],
(uint8_t *)&next_counter,
BOOT_SWAP_STATUS_CNT_SZ);
/* append record magic */
(void)memcpy(&record_buff[BOOT_SWAP_STATUS_PAYLD_SZ], (const uint8_t *)stat_part_magic, BOOT_SWAP_STATUS_MGCREC_SZ);
/* calculate CRC field*/
next_crc = calc_record_crc(record_buff, BOOT_SWAP_STATUS_ROW_SZ-BOOT_SWAP_STATUS_CRC_SZ);
/* append new CRC to whole record row */
(void)memcpy(&record_buff[BOOT_SWAP_STATUS_ROW_SZ-BOOT_SWAP_STATUS_CRC_SZ],
(uint8_t *)&next_crc,
BOOT_SWAP_STATUS_CRC_SZ);
/* we already know what copy number was last and correct */
/* increment duplicate index */
/* calculate final duplicate offset */
if (copy_num == (BOOT_SWAP_STATUS_MULT - 1U)) {
copy_num = 0;
}
else {
copy_num++;
}
fin_offset = rec_offset + copy_num*BOOT_SWAP_STATUS_D_SIZE;
/* erase obsolete status record before write */
rc = flash_area_erase(fap_stat, fin_offset, sizeof(record_buff));
if (rc != 0) {
return -1;
}
/* write prepared record into flash */
rc = flash_area_write(fap_stat, fin_offset, record_buff, sizeof(record_buff));
flash_area_close(fap_stat);
return rc;
}
static int boot_magic_decode(uint8_t *magic)
{
if (memcmp((const void *)magic, (const void *)&boot_img_magic.val, BOOT_MAGIC_SZ) == 0) {
return BOOT_MAGIC_GOOD;
}
return BOOT_MAGIC_BAD;
}
/**
* Updates len bytes of status partition with values from *data-pointer.
*
* @param target_area_id Target area id for which status is being written.
* Not a status-partition area id.
* @param offset Status byte offset inside status table. Should not include CRC and CNT.
* @param data Pointer to data status table to needs to be updated with.
* @param len Number of bytes to be written
*
* @return 0 on success; -1 on failure.
*/
int swap_status_update(uint8_t target_area_id, uint32_t offs, const void *data, uint32_t len)
{
int rc = -1;
int32_t init_offs;
uint32_t copy_num = 0;
uint32_t rec_offs;
uint32_t copy_sz;
uint32_t copy_counter = 0;
uint32_t data_idx = 0;
uint32_t buff_idx = offs % BOOT_SWAP_STATUS_PAYLD_SZ;
if ((UINT32_MAX - offs) < len) {
return -1;
}
/* check if end of data is still inside writable area */
if ((offs + len) > BOOT_SWAP_STATUS_D_SIZE_RAW) {
return -1;
}
/* pre-calculate sub-area offset */
init_offs = swap_status_init_offset(target_area_id);
if (init_offs < 0) {
return -1;
}
/* will start from it
* this will be write-aligned */
rec_offs = (uint32_t)init_offs + calc_record_offs(offs);
/* go over all records to be updated */
while (len != 0U) {
/* preserve record */
rc = swap_status_read_record(rec_offs, status_buff, &copy_counter, &copy_num);
/* it returns copy number */
if (rc < 0)
{ /* something went wrong while read, exit */
rc = -1;
break;
}
/* update record data */
if (len > BOOT_SWAP_STATUS_PAYLD_SZ) {
copy_sz = BOOT_SWAP_STATUS_PAYLD_SZ - buff_idx;
}
else {
copy_sz = len;
}
(void)memcpy(status_buff + buff_idx, (const uint8_t *)data + data_idx, copy_sz);
status_buff_cache_inv();
buff_idx = 0;
/* write record back */
rc = swap_status_write_record(rec_offs, copy_num, copy_counter, status_buff);
if (rc != 0) {
break;
}
/* proceed to next record */
if (len < BOOT_SWAP_STATUS_PAYLD_SZ) {
len = 0;
}
else {
len -= BOOT_SWAP_STATUS_PAYLD_SZ;
rec_offs += BOOT_SWAP_STATUS_ROW_SZ;
data_idx += BOOT_SWAP_STATUS_PAYLD_SZ;
}
}
return rc;
}
/**
* Reads len bytes of status partition with values from *data-pointer.
*
* @param target_area_id Target area id for which status is being read.
* Not a status-partition area id.
* @param offset Status byte offset inside status table. Should not include CRC and CNT.
* @param data Pointer to data where status table values will be written.
* @param len Number of bytes to be read from status table.
*
* @return 0 on success; -1 on failure.
*/
int swap_status_retrieve(uint8_t target_area_id, uint32_t offs, void *data, uint32_t len)
{
int rc = 0;
int32_t init_offs;
uint32_t copy_num = 0;
uint32_t rec_offs;
uint32_t copy_sz;
uint32_t copy_counter = 0;
uint32_t data_idx = 0;
uint32_t buff_idx = offs % BOOT_SWAP_STATUS_PAYLD_SZ;
if ((UINT32_MAX - offs) < len) {
return -1;
}
/* check if end of data is still inside writable area */
if ((offs + len) > BOOT_SWAP_STATUS_D_SIZE_RAW) {
return -1;
}
/* pre-calculate sub-area offset */
init_offs = swap_status_init_offset(target_area_id);
if (init_offs < 0) {
return -1;
}
/* will start from it
* this will be write-aligned */
rec_offs = (uint32_t)init_offs + calc_record_offs(offs);
/* go over all records to be updated */
while (len != 0U) {
/* preserve record */
rc = swap_status_read_record(rec_offs, status_buff, &copy_counter, &copy_num);
/* it returns copy number */
if (rc < 0) {
/* something went wrong while read, exit */
rc = -1;
break;
}
/* update record data */
if (len > BOOT_SWAP_STATUS_PAYLD_SZ) {
copy_sz = BOOT_SWAP_STATUS_PAYLD_SZ - buff_idx;
}
else {
copy_sz = len;
}
(void)memcpy((uint8_t *)data + data_idx, status_buff + buff_idx, copy_sz);
buff_idx = 0;
/* proceed to next record */
if (len < BOOT_SWAP_STATUS_PAYLD_SZ) {
len = 0;
}
else {
len -= BOOT_SWAP_STATUS_PAYLD_SZ;
rec_offs += BOOT_SWAP_STATUS_ROW_SZ;
data_idx += BOOT_SWAP_STATUS_PAYLD_SZ;
}
}
return rc;
}
/**
* Copy trailer from status partition to primary image and set copy_done flag.
* This function calls only once, before set copy_done flag in status trailer
*
* @param fap Target area id for which status is being read.
*
* @return 0 on success; -1 on failure.
*/
int swap_status_to_image_trailer(const struct flash_area *fap)
{
uint8_t erased_val;
uint8_t stat_erased_val;
const uint8_t *copy_src;
uint32_t cur_trailer_pos;
uint32_t primary_trailer_sz;
uint32_t primary_trailer_buf_sz;
uint32_t align = MEMORY_ALIGN;
int rc = 0;
const struct flash_area *fap_stat = NULL;
uint8_t primary_trailer_buf[MAX_TRAILER_BUF_SIZE];
/* get the erased flash byte for status partition */
rc = flash_area_open(FLASH_AREA_IMAGE_SWAP_STATUS, &fap_stat);
if (rc != 0) {
return rc;
}
stat_erased_val = flash_area_erased_val(fap_stat);
flash_area_close(fap_stat);
/* get status partition trailer size and copy it to buffer */
const uint32_t status_trailer_buf_sz = BOOT_SWAP_STATUS_SWAPSZ_SZ + BOOT_SWAP_STATUS_SWAPINF_SZ +
BOOT_SWAP_STATUS_COPY_DONE_SZ + BOOT_SWAP_STATUS_IMG_OK_SZ + BOOT_SWAP_STATUS_MAGIC_SZ;
uint8_t status_trailer_buf[status_trailer_buf_sz];
(void)memset(&status_trailer_buf, 0, status_trailer_buf_sz);
rc = swap_status_retrieve(fap->fa_id, boot_swap_size_off(fap), (uint8_t *)status_trailer_buf, status_trailer_buf_sz);
if (rc != 0) {
return rc;
}
/* check trailer magic in status partition */
if (boot_magic_decode(&status_trailer_buf[status_trailer_buf_sz - BOOT_SWAP_STATUS_MAGIC_SZ]) != BOOT_MAGIC_GOOD) {
return -1;
}
/* get primary slot trailer size without status data fields */
primary_trailer_sz = boot_trailer_sz(0);
/* align image trailer buffer size to minimal write size */
#if !defined(__BOOTSIM__)
align = flash_area_align(fap);
/* TODO: this code needs to be refined. It is introduced to overcome
* situation when MCUBootApp lives in internal memory, but user app
* is executed from different type memory - external in XIP mode in
* this case. This situation now arise on PSOC6 when XIP execution is
* used, bay may be applicable to other devices, where solution is
* distributed between memories with different write/erase sizes.
*/
#ifdef CY_BOOT_USE_EXTERNAL_FLASH
if (align > MEMORY_ALIGN) {
align = MEMORY_ALIGN;
}
#endif
#else
align = MEMORY_ALIGN;
#endif
if ((align > MAX_TRAILER_BUF_SIZE) || (align == 0U)) {
return -1;
}
primary_trailer_buf_sz = align;
while (primary_trailer_buf_sz < primary_trailer_sz) {
primary_trailer_buf_sz += align;
}
if (primary_trailer_buf_sz > MAX_TRAILER_BUF_SIZE) {
return -1;
}
/* erase primary slot trailer */
rc= flash_area_erase(fap, fap->fa_size - primary_trailer_buf_sz, primary_trailer_buf_sz);
if (rc != 0) {
return rc;
}
/* erase trailer area */
erased_val = flash_area_erased_val(fap); /* stat_erased_val and erased_val may differ! */
(void)memset(&primary_trailer_buf[primary_trailer_buf_sz-primary_trailer_sz], (int) erased_val, primary_trailer_sz);
/* copy and align flags and data from status_trailer_buf to primary_trailer_buf
Status part trailer --> Pimary image trailer */
/* copy trailer magic */
cur_trailer_pos = primary_trailer_buf_sz - BOOT_SWAP_STATUS_MAGIC_SZ;
copy_src = &status_trailer_buf[status_trailer_buf_sz - BOOT_SWAP_STATUS_MAGIC_SZ];
if (stat_erased_val != erased_val &&
bootutil_buffer_is_filled(copy_src, stat_erased_val, BOOT_SWAP_STATUS_MAGIC_SZ)) {
(void)memset(&primary_trailer_buf[cur_trailer_pos], (int)erased_val, BOOT_SWAP_STATUS_MAGIC_SZ);
}
else {
(void)memcpy(&primary_trailer_buf[cur_trailer_pos], copy_src, BOOT_SWAP_STATUS_MAGIC_SZ);
}
/* copy image_ok flag */
cur_trailer_pos -= BOOT_MAX_ALIGN;
copy_src = &status_trailer_buf[BOOT_SWAP_STATUS_SWAPSZ_SZ + BOOT_SWAP_STATUS_SWAPINF_SZ + BOOT_SWAP_STATUS_COPY_DONE_SZ];
if (stat_erased_val != erased_val && stat_erased_val == *copy_src) {
copy_src = &erased_val;
}
primary_trailer_buf[cur_trailer_pos] = *copy_src;
/* set copy_done flag */
cur_trailer_pos -= BOOT_MAX_ALIGN;
primary_trailer_buf[cur_trailer_pos] = BOOT_FLAG_SET;
/* copy swap_type flag */
cur_trailer_pos -= BOOT_MAX_ALIGN;
copy_src = &status_trailer_buf[BOOT_SWAP_STATUS_SWAPSZ_SZ];
if (stat_erased_val != erased_val && stat_erased_val == *copy_src) {
copy_src = &erased_val;
}
primary_trailer_buf[cur_trailer_pos] = *copy_src;
/* copy swap_size field */
cur_trailer_pos -= BOOT_MAX_ALIGN;
if (stat_erased_val != erased_val &&
bootutil_buffer_is_filled(status_trailer_buf, stat_erased_val, BOOT_SWAP_STATUS_SWAPSZ_SZ)) {
(void)memset(&primary_trailer_buf[cur_trailer_pos], (int)erased_val, BOOT_SWAP_STATUS_SWAPSZ_SZ);
}
else {
(void)memcpy(&primary_trailer_buf[cur_trailer_pos], status_trailer_buf, BOOT_SWAP_STATUS_SWAPSZ_SZ);
}
/* write primary image trailer with copy_done flag set */
rc = flash_area_write(fap, fap->fa_size - primary_trailer_buf_sz, primary_trailer_buf, primary_trailer_buf_sz);
return rc;
}
#endif /* MCUBOOT_SWAP_USING_STATUS */