blob: 1197af56f57863210258225e0b82872c55ce8225 [file] [log] [blame]
/*
* Copyright (c) 2019-2022, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include <stdbool.h>
#include "target.h"
#include "flash_map/flash_map.h"
#include "flash_map_backend/flash_map_backend.h"
#include "bootutil_priv.h"
#include "bootutil/bootutil_log.h"
#include "Driver_Flash.h"
#define FLASH_PROGRAM_UNIT TFM_HAL_FLASH_PROGRAM_UNIT
/**
* Return the greatest value not greater than `value` that is aligned to
* `alignment`.
*/
#define FLOOR_ALIGN(value, alignment) ((value) & ~((alignment) - 1))
/**
* Return the least value not less than `value` that is aligned to `alignment`.
*/
#define CEILING_ALIGN(value, alignment) \
(((value) + ((alignment) - 1)) & ~((alignment) - 1))
extern const struct flash_area flash_map[];
extern const int flash_map_entry_num;
/* Valid entries for data item width */
static const uint32_t data_width_byte[] = {
sizeof(uint8_t),
sizeof(uint16_t),
sizeof(uint32_t),
};
/*
* Check the target address in the flash_area_xxx operation.
*/
static bool is_range_valid(const struct flash_area *area,
uint32_t off,
uint32_t len)
{
uint32_t size;
if (!area) {
return false;
}
if (!boot_u32_safe_add(&size, off, len)) {
return false;
}
if (area->fa_size < size) {
return false;
}
return true;
}
/*
* `open` a flash area. The `area` in this case is not the individual
* sectors, but describes the particular flash area in question.
*/
int flash_area_open(uint8_t id, const struct flash_area **area)
{
int i;
BOOT_LOG_DBG("area %d", id);
for (i = 0; i < flash_map_entry_num; i++) {
if (id == flash_map[i].fa_id) {
break;
}
}
if (i == flash_map_entry_num) {
return -1;
}
*area = &flash_map[i];
return 0;
}
void flash_area_close(const struct flash_area *area)
{
/* Nothing to do. */
}
/*
* Read/write/erase. Offset is relative from beginning of flash area.
* `off` and `len` can be any alignment.
* Return 0 on success, other value on failure.
*/
int flash_area_read(const struct flash_area *area, uint32_t off, void *dst,
uint32_t len)
{
uint32_t remaining_len, read_length;
uint32_t aligned_off;
uint32_t item_number;
/* The maximum value of data_width is 4 bytes. */
uint8_t temp_buffer[sizeof(uint32_t)];
uint8_t data_width, i = 0, j;
int ret = 0;
ARM_FLASH_CAPABILITIES DriverCapabilities;
BOOT_LOG_DBG("read area=%d, off=%#x, len=%#x", area->fa_id, off, len);
if (!is_range_valid(area, off, len)) {
return -1;
}
remaining_len = len;
/* CMSIS ARM_FLASH_ReadData API requires the `addr` data type size aligned.
* Data type size is specified by the data_width in ARM_FLASH_CAPABILITIES.
*/
DriverCapabilities = DRV_FLASH_AREA(area)->GetCapabilities();
data_width = data_width_byte[DriverCapabilities.data_width];
aligned_off = FLOOR_ALIGN(off, data_width);
/* Read the first data_width long data if `off` is not aligned. */
if (aligned_off != off) {
ret = DRV_FLASH_AREA(area)->ReadData(area->fa_off + aligned_off,
temp_buffer,
1);
if (ret < 0) {
return ret;
}
/* Record how many target data have been read. */
read_length = off - aligned_off + len >= data_width ?
data_width - (off - aligned_off) : len;
/* Copy the read data from off. */
for (i = 0; i < read_length; i++) {
((uint8_t *)dst)[i] = temp_buffer[i + off - aligned_off];
}
remaining_len -= read_length;
}
/* The `cnt` parameter in CMSIS ARM_FLASH_ReadData indicates number of data
* items to read.
*/
if (remaining_len) {
item_number = remaining_len / data_width;
if (item_number) {
ret = DRV_FLASH_AREA(area)->ReadData(area->fa_off + off + i,
(uint8_t *)dst + i,
item_number);
if (ret < 0) {
return ret;
}
remaining_len -= item_number * data_width;
}
}
if (remaining_len) {
ret = DRV_FLASH_AREA(area)->ReadData(
area->fa_off + off + i + item_number * data_width,
temp_buffer,
1);
if (ret < 0) {
return ret;
}
for (j = 0; j < remaining_len; j++) {
((uint8_t *)dst)[i + item_number * data_width + j] = temp_buffer[j];
}
}
/* CMSIS ARM_FLASH_ReadData can return the number of data items read or
* Status Error Codes which are negative for failures.
*/
if (ret < 0) {
return ret;
} else {
return 0;
}
}
/* Writes `len` bytes of flash memory at `off` from the buffer at `src`.
* `off` and `len` can be any alignment.
*/
int flash_area_write(const struct flash_area *area, uint32_t off,
const void *src, uint32_t len)
{
uint8_t add_padding[FLASH_PROGRAM_UNIT];
#if (FLASH_PROGRAM_UNIT == 1)
uint8_t len_padding[FLASH_PROGRAM_UNIT]; /* zero sized arrayas are illegal C */
#else
uint8_t len_padding[FLASH_PROGRAM_UNIT - 1];
#endif
ARM_FLASH_CAPABILITIES DriverCapabilities;
uint8_t data_width;
/* The PROGRAM_UNIT aligned value of `off` */
uint32_t aligned_off;
/* The total write length. */
uint32_t aligned_len;
uint32_t i, k;
/* The index in src[] that has been programmed. */
uint32_t src_written_idx = 0;
uint32_t add_padding_size, len_padding_size;
uint32_t write_size;
uint32_t last_unit_start_off;
/*
* aligned_off off last_unit_start_off
* | | |
* | add_padding_size | | | len_padding_size |
* |+++++++++++++++++++**|******************|***@@@@@@@@@@@@@@@@@@@@|
* | | | |
* ---->--|---- PROGRAM UNIT ---|-- PROGRAM UNIT --|---- PROGRAM UNIT -----|
* | | | |
* |+++++++++++++++++++**|******************|***@@@@@@@@@@@@@@@@@@@@|
* |<-------- len --------->|
*/
BOOT_LOG_DBG("write area=%d, off=%#x, len=%#x", area->fa_id, off, len);
/* Align the target address. The area->fa_off should already be aligned. */
aligned_off = FLOOR_ALIGN(off, FLASH_PROGRAM_UNIT);
add_padding_size = off - aligned_off;
if (!is_range_valid(area, off, len)) {
return -1;
}
DriverCapabilities = DRV_FLASH_AREA(area)->GetCapabilities();
data_width = data_width_byte[DriverCapabilities.data_width];
if (FLASH_PROGRAM_UNIT)
/* Read the bytes from aligned_off to off. */
if (flash_area_read(area, aligned_off, add_padding, add_padding_size)) {
return -1;
}
/* Align the write size */
aligned_len = CEILING_ALIGN(len + add_padding_size, FLASH_PROGRAM_UNIT);
len_padding_size = aligned_len - len - add_padding_size;
if (!is_range_valid(area, aligned_off, aligned_len)) {
return -1;
}
/* Read the bytes from (off + len) to (off + aligned_len). */
if (flash_area_read(area, off + len, len_padding,
len_padding_size)) {
return -1;
}
/* Program the first FLASH_PROGRAM_UNIT. */
if (add_padding_size) {
/* Fill the first program unit bytes with data from src. */
for (i = add_padding_size, src_written_idx = 0;
i < FLASH_PROGRAM_UNIT && src_written_idx < len;
i++, src_written_idx++) {
add_padding[i] = ((uint8_t *)src)[src_written_idx];
}
if (src_written_idx == len) {
/* aligned_len equals to FLASH_PROGRAM_UNIT in this case.
* Fill the len_padding_size datas into add_padding.
*/
for (k = 0; i < FLASH_PROGRAM_UNIT && k < len_padding_size;
i++, k++) {
add_padding[i] = len_padding[k];
}
if (k != len_padding_size) {
return -1;
}
}
/* Check the first program unit bytes are all filled. */
if (i != FLASH_PROGRAM_UNIT) {
return -1;
}
if (DRV_FLASH_AREA(area)->ProgramData(area->fa_off + aligned_off,
add_padding,
FLASH_PROGRAM_UNIT / data_width) < 0) {
return -1;
}
}
/* 'src_written_idx' indicates the number of the src data which has already
* been programed into flash. 'src_written_idx' equals to 'len' means that
* all the data in src has been programmed and aligned_len equals to
* FLASH_PROGRAM_UNIT. This case has been handled above.
* 'src_written_idx' less than 'len' means that not all the data in src has
* been programmed.
*/
if (src_written_idx < len) {
/* Program from the first aligned bytes(src_written_idx) to the last
* aligned bytes in src.
*/
write_size = FLOOR_ALIGN(len - src_written_idx, FLASH_PROGRAM_UNIT);
if (write_size > 0) {
if (DRV_FLASH_AREA(area)->ProgramData(
area->fa_off + off + src_written_idx,
src,
write_size / data_width) < 0) {
return -1;
}
src_written_idx += write_size;
}
last_unit_start_off = src_written_idx;
/* Program the last program unit data into flash. */
if (len_padding_size) {
/* Copy the last unaligned bytes in src to add_padding. */
for (i = 0; i < FLASH_PROGRAM_UNIT && src_written_idx < len;
i++, src_written_idx++) {
add_padding[i] = ((uint8_t *)src)[src_written_idx];
}
if (src_written_idx != len) {
return -1;
}
/* Copy the len_padding_size bytes in len_padding to add_padding. */
for (k = 0; i < FLASH_PROGRAM_UNIT && k < len_padding_size;
i++, k++) {
add_padding[i] = len_padding[k];
}
write_size = add_padding_size + last_unit_start_off +
FLASH_PROGRAM_UNIT;
if (i != FLASH_PROGRAM_UNIT || k != len_padding_size ||
aligned_len != write_size) {
return -1;
}
if (DRV_FLASH_AREA(area)->ProgramData(
area->fa_off + off + last_unit_start_off,
add_padding,
FLASH_PROGRAM_UNIT / data_width) < 0) {
return -1;
}
}
}
return 0;
}
int flash_area_erase(const struct flash_area *area, uint32_t off, uint32_t len)
{
ARM_FLASH_INFO *flash_info;
uint32_t deleted_len = 0;
int32_t rc = 0;
BOOT_LOG_DBG("erase area=%d, off=%#x, len=%#x", area->fa_id, off, len);
if (!is_range_valid(area, off, len)) {
return -1;
}
flash_info = DRV_FLASH_AREA(area)->GetInfo();
if (flash_info->sector_info == NULL) {
/* Uniform sector layout */
while (deleted_len < len) {
rc = DRV_FLASH_AREA(area)->EraseSector(area->fa_off + off);
if (rc != 0) {
break;
}
deleted_len += flash_info->sector_size;
off += flash_info->sector_size;
}
} else {
/* Inhomogeneous sector layout, explicitly defined
* Currently not supported.
*/
}
return rc;
}
uint32_t flash_area_align(const struct flash_area *area)
{
ARM_FLASH_INFO *flash_info;
flash_info = DRV_FLASH_AREA(area)->GetInfo();
return flash_info->program_unit;
}