esp-idf/components/espcoredump/src/core_dump_flash.c

572 wiersze
21 KiB
C

/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_partition.h"
#include "esp_log.h"
#include "esp_core_dump_types.h"
#include "core_dump_checksum.h"
#include "esp_flash_internal.h"
#include "esp_flash_encrypt.h"
#include "esp_rom_crc.h"
#include "esp_private/spi_flash_os.h"
#include "spi_flash_mmap.h"
#define BLANK_COREDUMP_SIZE 0xFFFFFFFF
const static char TAG[] __attribute__((unused)) = "esp_core_dump_flash";
#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
typedef struct _core_dump_partition_t {
/* Core dump partition start. */
uint32_t start;
/* Core dump partition size. */
uint32_t size;
/* Flag set to true if the partition is encrypted. */
bool encrypted;
#if CONFIG_ESP_COREDUMP_FLASH_NO_OVERWRITE
/* Flag set to true if the partition is empty. */
bool empty;
#endif
} core_dump_partition_t;
typedef struct _core_dump_flash_config_t {
/* Core dump partition config. */
core_dump_partition_t partition;
/* CRC of core dump partition config. */
core_dump_crc_t partition_config_crc;
} core_dump_flash_config_t;
/* Core dump flash data. */
static core_dump_flash_config_t s_core_flash_config;
void esp_core_dump_print_write_start(void) __attribute__((alias("esp_core_dump_flash_print_write_start")));
void esp_core_dump_print_write_end(void) __attribute__((alias("esp_core_dump_flash_print_write_end")));
esp_err_t esp_core_dump_write_init(void) __attribute__((alias("esp_core_dump_flash_hw_init")));
esp_err_t esp_core_dump_write_prepare(core_dump_write_data_t *wr_data, uint32_t *data_len) __attribute__((alias("esp_core_dump_flash_write_prepare")));
esp_err_t esp_core_dump_write_start(core_dump_write_data_t *wr_data) __attribute__((alias("esp_core_dump_flash_write_start")));
esp_err_t esp_core_dump_write_end(core_dump_write_data_t *wr_data) __attribute__((alias("esp_core_dump_flash_write_end")));
esp_err_t esp_core_dump_write_data(core_dump_write_data_t *wr_data, void *data, uint32_t data_len) __attribute__((alias("esp_core_dump_flash_write_data")));
#define ESP_COREDUMP_FLASH_WRITE(_off_, _data_, _len_) esp_flash_write(esp_flash_default_chip, _data_, _off_, _len_)
#define ESP_COREDUMP_FLASH_WRITE_ENCRYPTED(_off_, _data_, _len_) esp_flash_write_encrypted(esp_flash_default_chip, _off_, _data_, _len_)
#define ESP_COREDUMP_FLASH_ERASE(_off_, _len_) esp_flash_erase_region(esp_flash_default_chip, _off_, _len_)
esp_err_t esp_core_dump_image_check(void);
esp_err_t esp_core_dump_partition_and_size_get(const esp_partition_t **partition, uint32_t* size);
static void esp_core_dump_flash_print_write_start(void)
{
ESP_COREDUMP_LOGI("Save core dump to flash...");
}
static void esp_core_dump_flash_print_write_end(void)
{
ESP_COREDUMP_LOGI("Core dump has been saved to flash.");
}
static esp_err_t esp_core_dump_flash_custom_write(uint32_t address, const void *buffer, uint32_t length)
{
esp_err_t err = ESP_OK;
if (esp_flash_encryption_enabled() && s_core_flash_config.partition.encrypted) {
err = ESP_COREDUMP_FLASH_WRITE_ENCRYPTED(address, buffer, length);
} else {
err = ESP_COREDUMP_FLASH_WRITE(address, buffer, length);
}
return err;
}
static inline core_dump_crc_t esp_core_dump_calc_flash_config_crc(void)
{
return esp_rom_crc32_le(0, (uint8_t const *)&s_core_flash_config.partition, sizeof(s_core_flash_config.partition));
}
static esp_err_t esp_core_dump_flash_hw_init(void)
{
/* Check core dump partition configuration. */
core_dump_crc_t crc = esp_core_dump_calc_flash_config_crc();
if (s_core_flash_config.partition_config_crc != crc) {
ESP_COREDUMP_LOGE("Core dump flash config is corrupted! CRC=0x%x instead of 0x%x", crc, s_core_flash_config.partition_config_crc);
return ESP_FAIL;
}
/* Make sure that the partition can at least hold the data length. */
if (s_core_flash_config.partition.start == 0 || s_core_flash_config.partition.size < sizeof(uint32_t)) {
ESP_COREDUMP_LOGE("Invalid flash partition config!");
return ESP_FAIL;
}
#if CONFIG_ESP_COREDUMP_FLASH_NO_OVERWRITE
if (!s_core_flash_config.partition.empty) {
ESP_COREDUMP_LOGW("Core dump already exists in flash, will not overwrite it with a new core dump");
return ESP_FAIL;
}
#endif
/* Initialize non-OS flash access critical section. */
spi_flash_guard_set(&g_flash_guard_no_os_ops);
esp_flash_app_disable_protect(true);
return ESP_OK;
}
static void esp_core_dump_partition_init(void)
{
const esp_partition_t *core_part = NULL;
/* Look for the core dump partition on the flash. */
ESP_COREDUMP_LOGI("Init core dump to flash");
core_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_COREDUMP, NULL);
if (!core_part) {
ESP_COREDUMP_LOGE("No core dump partition found!");
return;
}
ESP_COREDUMP_LOGI("Found partition '%s' @ %x %d bytes", core_part->label, core_part->address, core_part->size);
s_core_flash_config.partition.start = core_part->address;
s_core_flash_config.partition.size = core_part->size;
s_core_flash_config.partition.encrypted = core_part->encrypted;
#if CONFIG_ESP_COREDUMP_FLASH_NO_OVERWRITE
uint32_t core_size = 0;
esp_err_t err = esp_partition_read(core_part, 0, &core_size, sizeof(core_size));
if (err == ESP_OK) {
s_core_flash_config.partition.empty = (core_size == BLANK_COREDUMP_SIZE);
} else {
ESP_COREDUMP_LOGE("Failed to read core dump data size (%d)!", err);
s_core_flash_config.partition.empty = false;
}
#endif
s_core_flash_config.partition_config_crc = esp_core_dump_calc_flash_config_crc();
if (esp_flash_encryption_enabled() && !core_part->encrypted) {
ESP_COREDUMP_LOGW("core dump partition is plain text, consider enabling `encrypted` flag");
}
}
static esp_err_t esp_core_dump_flash_write_data(core_dump_write_data_t* wr_data, uint8_t* data, uint32_t data_size)
{
esp_err_t err = ESP_OK;
uint32_t written = 0;
uint32_t wr_sz = 0;
/* Make sure that the partition is large enough to hold the data. */
ESP_COREDUMP_ASSERT((wr_data->off + data_size) < s_core_flash_config.partition.size);
if (wr_data->cached_bytes) {
/* Some bytes are in the cache, let's continue filling the cache
* with the data received as parameter. Let's calculate the maximum
* amount of bytes we can still fill the cache with. */
if ((COREDUMP_CACHE_SIZE - wr_data->cached_bytes) > data_size) {
wr_sz = data_size;
} else {
wr_sz = COREDUMP_CACHE_SIZE - wr_data->cached_bytes;
}
/* Append wr_sz bytes from data parameter to the cache. */
memcpy(&wr_data->cached_data[wr_data->cached_bytes], data, wr_sz);
wr_data->cached_bytes += wr_sz;
if (wr_data->cached_bytes == COREDUMP_CACHE_SIZE) {
/* The cache is full, we can flush it to the flash. */
err = esp_core_dump_flash_custom_write(s_core_flash_config.partition.start + wr_data->off,
wr_data->cached_data,
COREDUMP_CACHE_SIZE);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to write cached data to flash (%d)!", err);
return err;
}
/* The offset of the next data that will be written onto the flash
* can now be increased. */
wr_data->off += COREDUMP_CACHE_SIZE;
/* Update checksum with the newly written data on the flash. */
esp_core_dump_checksum_update(&wr_data->checksum_ctx, &wr_data->cached_data, COREDUMP_CACHE_SIZE);
/* Reset cache from the next use. */
wr_data->cached_bytes = 0;
memset(wr_data->cached_data, 0, COREDUMP_CACHE_SIZE);
}
written += wr_sz;
data_size -= wr_sz;
}
/* Figure out how many bytes we can write onto the flash directly, without
* using the cache. In our case the cache size is a multiple of the flash's
* minimum writing block size, so we will use it for our calculation.
* For example, if COREDUMP_CACHE_SIZE equals 32, here are interesting
* values:
* +---------+-----------------------+
* | | data_size |
* +---------+---+----+----+----+----+
* | | 0 | 31 | 32 | 40 | 64 |
* +---------+---+----+----+----+----+
* | (blocks | 0 | 0 | 1 | 1 | 2) |
* +---------+---+----+----+----+----+
* | wr_sz | 0 | 0 | 32 | 32 | 64 |
* +---------+---+----+----+----+----+
*/
wr_sz = (data_size / COREDUMP_CACHE_SIZE) * COREDUMP_CACHE_SIZE;
if (wr_sz) {
/* Write the contiguous amount of bytes to the flash,
* without using the cache */
err = esp_core_dump_flash_custom_write(s_core_flash_config.partition.start + wr_data->off, data + written, wr_sz);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to write data to flash (%d)!", err);
return err;
}
/* Update the checksum with the newly written bytes */
esp_core_dump_checksum_update(&wr_data->checksum_ctx, data + written, wr_sz);
wr_data->off += wr_sz;
written += wr_sz;
data_size -= wr_sz;
}
if (data_size > 0) {
/* There still some bytes from the data parameter that need to be sent,
* append it to cache in order to write them later. (i.e. when there
* will be enough bytes to fill the cache) */
memcpy(&wr_data->cached_data, data + written, data_size);
wr_data->cached_bytes = data_size;
}
return ESP_OK;
}
static esp_err_t esp_core_dump_flash_write_prepare(core_dump_write_data_t *wr_data, uint32_t *data_len)
{
esp_err_t err = ESP_OK;
uint32_t sec_num = 0;
uint32_t cs_len = 0;
/* Get the length, in bytes, of the checksum. */
cs_len = esp_core_dump_checksum_size();
/* At the end of the core dump file, a padding may be added, according to the
* cache size. We must take that padding into account. */
uint32_t padding = 0;
const uint32_t modulo = *data_len % COREDUMP_CACHE_SIZE;
if (modulo != 0) {
/* The data length is not a multiple of the cache size,
* so there will be a padding. */
padding = COREDUMP_CACHE_SIZE - modulo;
}
/* Now we can check whether we have enough space in our core dump partition
* or not. */
if ((*data_len + padding + cs_len) > s_core_flash_config.partition.size) {
ESP_COREDUMP_LOGE("Not enough space to save core dump!");
return ESP_ERR_NO_MEM;
}
/* We have enough space in the partition, add the padding and the checksum
* in the core dump file calculation. */
*data_len += padding + cs_len;
memset(wr_data, 0, sizeof(core_dump_write_data_t));
/* In order to erase the right amount of data in the flash, we have to
* calculate how many SPI flash sectors will be needed by the core dump
* file. */
sec_num = *data_len / SPI_FLASH_SEC_SIZE;
if (*data_len % SPI_FLASH_SEC_SIZE) {
sec_num++;
}
/* Erase the amount of sectors needed. */
ESP_COREDUMP_LOGI("Erase flash %d bytes @ 0x%x", sec_num * SPI_FLASH_SEC_SIZE, s_core_flash_config.partition.start + 0);
ESP_COREDUMP_ASSERT(sec_num * SPI_FLASH_SEC_SIZE <= s_core_flash_config.partition.size);
err = ESP_COREDUMP_FLASH_ERASE(s_core_flash_config.partition.start + 0, sec_num * SPI_FLASH_SEC_SIZE);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to erase flash (%d)!", err);
}
return err;
}
static esp_err_t esp_core_dump_flash_write_start(core_dump_write_data_t *wr_data)
{
esp_core_dump_checksum_init(&wr_data->checksum_ctx);
return ESP_OK;
}
static esp_err_t esp_core_dump_flash_write_end(core_dump_write_data_t *wr_data)
{
esp_err_t err = ESP_OK;
core_dump_checksum_bytes checksum = NULL;
uint32_t cs_len = 0;
/* Get the size, in bytes of the checksum. */
cs_len = esp_core_dump_checksum_size();
/* Flush cached bytes, including the zero padding at the end (if any). */
if (wr_data->cached_bytes) {
err = esp_core_dump_flash_custom_write(s_core_flash_config.partition.start + wr_data->off,
wr_data->cached_data,
COREDUMP_CACHE_SIZE);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to flush cached data to flash (%d)!", err);
return err;
}
/* Update the checksum with the data written, including the padding. */
esp_core_dump_checksum_update(&wr_data->checksum_ctx, wr_data->cached_data, COREDUMP_CACHE_SIZE);
wr_data->off += COREDUMP_CACHE_SIZE;
wr_data->cached_bytes = 0;
}
/* All data have been written to the flash, the cache is now empty, we can
* terminate the checksum calculation. */
esp_core_dump_checksum_finish(&wr_data->checksum_ctx, &checksum);
/* Use the cache to write the checksum if its size doesn't match the requirements.
* (e.g. its size is not a multiple of 32) */
if (cs_len < COREDUMP_CACHE_SIZE) {
/* Copy the checksum into the cache. */
memcpy(wr_data->cached_data, checksum, cs_len);
/* Fill the rest of the cache with zeros. */
memset(wr_data->cached_data + cs_len, 0, COREDUMP_CACHE_SIZE - cs_len);
/* Finally, write the checksum on the flash, using the cache. */
err = esp_core_dump_flash_custom_write(s_core_flash_config.partition.start + wr_data->off,
wr_data->cached_data,
COREDUMP_CACHE_SIZE);
} else {
/* In that case, the length of the checksum must be a multiple of 16. */
ESP_COREDUMP_ASSERT(cs_len % 16 == 0);
err = esp_core_dump_flash_custom_write(s_core_flash_config.partition.start + wr_data->off, checksum, cs_len);
}
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to flush cached data to flash (%d)!", err);
return err;
}
wr_data->off += cs_len;
ESP_COREDUMP_LOGI("Write end offset 0x%x, check sum length %d", wr_data->off, cs_len);
return err;
}
void esp_core_dump_init(void)
{
esp_core_dump_partition_init();
#if CONFIG_ESP_COREDUMP_CHECK_BOOT
const esp_partition_t *partition = 0;
uint32_t size = 0;
if (esp_core_dump_image_check() == ESP_OK
&& esp_core_dump_partition_and_size_get(&partition, &size) == ESP_OK) {
ESP_COREDUMP_LOGI("Found core dump %d bytes in flash @ 0x%x", size, partition->address);
}
#endif
}
esp_err_t esp_core_dump_image_check(void)
{
esp_err_t err = ESP_OK;
const esp_partition_t *core_part = NULL;
core_dump_write_data_t wr_data = { 0 };
uint32_t size = 0;
uint32_t total_size = 0;
uint32_t offset = 0;
const uint32_t checksum_size = esp_core_dump_checksum_size();
core_dump_checksum_bytes checksum_calc = NULL;
/* Initialize the checksum we have to read from the flash to the biggest
* size we can have for a checksum. */
uint8_t checksum_read[COREDUMP_CHECKSUM_MAX_LEN] = { 0 };
/* Assert that we won't have any problems with our checksum size. */
ESP_COREDUMP_DEBUG_ASSERT(checksum_size <= COREDUMP_CHECKSUM_MAX_LEN);
/* Retrieve the partition and size. */
err = esp_core_dump_partition_and_size_get(&core_part, &total_size);
if (err != ESP_OK) {
return err;
}
/* The final checksum, from the image, doesn't take part into the checksum
* calculation, so subtract it from the bytes we are going to read. */
size = total_size - checksum_size ;
/* Initiate the checksum calculation for the coredump in the flash. */
esp_core_dump_checksum_init(&wr_data.checksum_ctx);
while (size > 0) {
/* Use the cache in core_dump_write_data_t structure to read the
* partition. */
const uint32_t toread = (size < COREDUMP_CACHE_SIZE) ? size : COREDUMP_CACHE_SIZE;
/* Read the content of the flash. */
err = esp_partition_read(core_part, offset, wr_data.cached_data, toread);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to read data from core dump (%d)!", err);
return err;
}
/* Update the checksum according to what was just read. */
esp_core_dump_checksum_update(&wr_data.checksum_ctx, wr_data.cached_data, toread);
/* Move the offset forward and decrease the remaining size. */
offset += toread;
size -= toread;
}
/* The coredump has been totally read, finish the checksum calculation. */
esp_core_dump_checksum_finish(&wr_data.checksum_ctx, &checksum_calc);
/* Read the checksum from the flash and compare to the one just
* calculated. */
err = esp_partition_read(core_part, total_size - checksum_size, checksum_read, checksum_size);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to read checksum from core dump (%d)!", err);
return err;
}
/* Compare the checksum read from the flash and the one just calculated. */
if (memcmp(checksum_calc, checksum_read, checksum_size) != 0) {
ESP_COREDUMP_LOGE("Core dump data check failed:");
esp_core_dump_print_checksum("Calculated checksum", checksum_calc);
esp_core_dump_print_checksum("Image checksum", checksum_read);
return ESP_ERR_INVALID_CRC;
} else {
ESP_COREDUMP_LOGI("Core dump data checksum is correct");
}
return ESP_OK;
}
#endif
esp_err_t esp_core_dump_image_erase(void)
{
/* If flash is encrypted, we can only write blocks of 16 bytes, let's always
* write a 16-byte buffer. */
uint32_t helper[4] = { BLANK_COREDUMP_SIZE };
_Static_assert(sizeof(helper) % 16 == 0, "esp_partition_write() needs multiple of 16 bytes long buffers");
/* Find the partition that could potentially contain a (previous) core dump. */
const esp_partition_t *core_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_DATA_COREDUMP,
NULL);
if (!core_part) {
ESP_LOGE(TAG, "No core dump partition found!");
return ESP_ERR_NOT_FOUND;
}
if (core_part->size < sizeof(uint32_t)) {
ESP_LOGE(TAG, "Too small core dump partition!");
return ESP_ERR_INVALID_SIZE;
}
esp_err_t err = ESP_OK;
err = esp_partition_erase_range(core_part, 0, core_part->size);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to erase core dump partition (%d)!", err);
return err;
}
err = esp_partition_write(core_part, 0, helper, sizeof(helper));
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to write core dump partition size (%d)!", err);
}
#if CONFIG_ESP_COREDUMP_FLASH_NO_OVERWRITE
if (!s_core_flash_config.partition.empty) {
s_core_flash_config.partition.empty = true;
s_core_flash_config.partition_config_crc = esp_core_dump_calc_flash_config_crc();
}
#endif
return err;
}
esp_err_t esp_core_dump_partition_and_size_get(const esp_partition_t **partition, uint32_t* size)
{
uint32_t core_size = 0;
const esp_partition_t *core_part = NULL;
/* Check the arguments, at least one should be provided. */
if (partition == NULL && size == NULL) {
return ESP_ERR_INVALID_ARG;
}
/* Find the partition that could potentially contain a (previous) core dump. */
core_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_DATA_COREDUMP,
NULL);
if (core_part == NULL) {
ESP_COREDUMP_LOGE("No core dump partition found!");
return ESP_ERR_NOT_FOUND;
}
if (core_part->size < sizeof(uint32_t)) {
ESP_COREDUMP_LOGE("Too small core dump partition!");
return ESP_ERR_INVALID_SIZE;
}
/* The partition has been found, get its first uint32_t value, which
* describes the core dump file size. */
esp_err_t err = esp_partition_read(core_part, 0, &core_size, sizeof(uint32_t));
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to read core dump data size (%d)!", err);
return err;
}
/* Verify that the size read from the flash is not corrupted. */
if (core_size == 0xFFFFFFFF) {
ESP_COREDUMP_LOGD("Blank core dump partition!");
return ESP_ERR_INVALID_SIZE;
}
if ((core_size < sizeof(uint32_t)) || (core_size > core_part->size)) {
ESP_COREDUMP_LOGE("Incorrect size of core dump image: %d", core_size);
return ESP_ERR_INVALID_SIZE;
}
/* Return the values if needed. */
if (partition != NULL) {
*partition = core_part;
}
if (size != NULL) {
*size = core_size;
}
return ESP_OK;
}
esp_err_t esp_core_dump_image_get(size_t* out_addr, size_t *out_size)
{
esp_err_t err = ESP_OK;
uint32_t size = 0;
const esp_partition_t *core_part = NULL;
/* Check the validity of the parameters. */
if (out_addr == NULL || out_size == NULL) {
return ESP_ERR_INVALID_ARG;
}
/* Retrieve the partition and size. */
err = esp_core_dump_partition_and_size_get(&core_part, &size);
if (err != ESP_OK) {
return err;
}
/* Save the address. */
*out_addr = core_part->address;
/* Save the size read. */
*out_size = size;
return ESP_OK;
}