/* * Copyright (c) 2019-2021, Arm Limited. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause * */ #include #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; /* * 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; uint32_t aligned_off; uint8_t temp_buffer[sizeof(uint32_t)]; uint8_t align_unit, i = 0; int ret = 0; /* Valid entries for data item width */ uint32_t data_width_byte[] = { sizeof(uint8_t), sizeof(uint16_t), sizeof(uint32_t), }; 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(); align_unit = data_width_byte[DriverCapabilities.data_width]; aligned_off = FLOOR_ALIGN(off, align_unit); /* Read the first align_unit long data if `off` is not aligned. */ if (aligned_off != off) { ret = DRV_FLASH_AREA(area)->ReadData(area->fa_off + aligned_off, temp_buffer, align_unit); if (ret < 0) { return ret; } /* Copy the read data from off. */ for (i = 0; i + off - aligned_off < align_unit; i++) { ((uint8_t *)dst)[i] = temp_buffer[i + off - aligned_off]; } remaining_len -= align_unit - (off - aligned_off); } /* CMSIS ARM_FLASH_ReadData does not require the alignment of `cnt`.*/ if (remaining_len) { ret = DRV_FLASH_AREA(area)->ReadData(area->fa_off + off + i, (uint8_t *)dst + i, remaining_len); } /* 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 /* 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 = 0; /* * 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; } /* 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)) { 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)) { 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)) { 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; }