364 lines
7.9 KiB
C
364 lines
7.9 KiB
C
/*
|
|
* Copyright (c) 2017, 2020 Nordic Semiconductor ASA
|
|
* Copyright (c) 2017 Linaro Limited
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define LOG_MODULE_NAME STREAM_FLASH
|
|
#define LOG_LEVEL CONFIG_STREAM_FLASH_LOG_LEVEL
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_STREAM_FLASH_LOG_LEVEL);
|
|
|
|
#include <zephyr/types.h>
|
|
#include <string.h>
|
|
#include <zephyr/drivers/flash.h>
|
|
|
|
#include <zephyr/storage/stream_flash.h>
|
|
|
|
#ifdef CONFIG_STREAM_FLASH_PROGRESS
|
|
#include <zephyr/settings/settings.h>
|
|
|
|
static int settings_direct_loader(const char *key, size_t len,
|
|
settings_read_cb read_cb, void *cb_arg,
|
|
void *param)
|
|
{
|
|
struct stream_flash_ctx *ctx = (struct stream_flash_ctx *) param;
|
|
|
|
/* Handle the subtree if it is an exact key match. */
|
|
if (settings_name_next(key, NULL) == 0) {
|
|
size_t bytes_written = 0;
|
|
ssize_t len = read_cb(cb_arg, &bytes_written,
|
|
sizeof(bytes_written));
|
|
|
|
if (len != sizeof(ctx->bytes_written)) {
|
|
LOG_ERR("Unable to read bytes_written from storage");
|
|
return len;
|
|
}
|
|
|
|
/* Check that loaded progress is not outdated. */
|
|
if (bytes_written >= ctx->bytes_written) {
|
|
ctx->bytes_written = bytes_written;
|
|
} else {
|
|
LOG_WRN("Loaded outdated bytes_written %zu < %zu",
|
|
bytes_written, ctx->bytes_written);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_STREAM_FLASH_ERASE
|
|
int rc;
|
|
struct flash_pages_info page;
|
|
off_t offset = (off_t) (ctx->offset + ctx->bytes_written) - 1;
|
|
|
|
/* Update the last erased page to avoid deleting already
|
|
* written data.
|
|
*/
|
|
if (ctx->bytes_written > 0) {
|
|
rc = flash_get_page_info_by_offs(ctx->fdev, offset,
|
|
&page);
|
|
if (rc != 0) {
|
|
LOG_ERR("Error %d while getting page info", rc);
|
|
return rc;
|
|
}
|
|
ctx->last_erased_page_start_offset = page.start_offset;
|
|
} else {
|
|
ctx->last_erased_page_start_offset = -1;
|
|
}
|
|
#endif /* CONFIG_STREAM_FLASH_ERASE */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* CONFIG_STREAM_FLASH_PROGRESS */
|
|
|
|
#ifdef CONFIG_STREAM_FLASH_ERASE
|
|
|
|
int stream_flash_erase_page(struct stream_flash_ctx *ctx, off_t off)
|
|
{
|
|
int rc;
|
|
struct flash_pages_info page;
|
|
|
|
rc = flash_get_page_info_by_offs(ctx->fdev, off, &page);
|
|
if (rc != 0) {
|
|
LOG_ERR("Error %d while getting page info", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (ctx->last_erased_page_start_offset == page.start_offset) {
|
|
return 0;
|
|
}
|
|
|
|
LOG_DBG("Erasing page at offset 0x%08lx", (long)page.start_offset);
|
|
|
|
rc = flash_erase(ctx->fdev, page.start_offset, page.size);
|
|
|
|
if (rc != 0) {
|
|
LOG_ERR("Error %d while erasing page", rc);
|
|
} else {
|
|
ctx->last_erased_page_start_offset = page.start_offset;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#endif /* CONFIG_STREAM_FLASH_ERASE */
|
|
|
|
static int flash_sync(struct stream_flash_ctx *ctx)
|
|
{
|
|
int rc = 0;
|
|
size_t write_addr = ctx->offset + ctx->bytes_written;
|
|
size_t buf_bytes_aligned;
|
|
size_t fill_length;
|
|
uint8_t filler;
|
|
|
|
|
|
if (ctx->buf_bytes == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_STREAM_FLASH_ERASE)) {
|
|
|
|
rc = stream_flash_erase_page(ctx,
|
|
write_addr + ctx->buf_bytes - 1);
|
|
if (rc < 0) {
|
|
LOG_ERR("stream_flash_erase_page err %d offset=0x%08zx",
|
|
rc, write_addr);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
fill_length = flash_get_write_block_size(ctx->fdev);
|
|
if (ctx->buf_bytes % fill_length) {
|
|
fill_length -= ctx->buf_bytes % fill_length;
|
|
filler = flash_get_parameters(ctx->fdev)->erase_value;
|
|
|
|
memset(ctx->buf + ctx->buf_bytes, filler, fill_length);
|
|
} else {
|
|
fill_length = 0;
|
|
}
|
|
|
|
buf_bytes_aligned = ctx->buf_bytes + fill_length;
|
|
rc = flash_write(ctx->fdev, write_addr, ctx->buf, buf_bytes_aligned);
|
|
|
|
if (rc != 0) {
|
|
LOG_ERR("flash_write error %d offset=0x%08zx", rc,
|
|
write_addr);
|
|
return rc;
|
|
}
|
|
|
|
if (ctx->callback) {
|
|
/* Invert to ensure that caller is able to discover a faulty
|
|
* flash_read() even if no error code is returned.
|
|
*/
|
|
for (int i = 0; i < ctx->buf_bytes; i++) {
|
|
ctx->buf[i] = ~ctx->buf[i];
|
|
}
|
|
|
|
rc = flash_read(ctx->fdev, write_addr, ctx->buf,
|
|
ctx->buf_bytes);
|
|
if (rc != 0) {
|
|
LOG_ERR("flash read failed: %d", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = ctx->callback(ctx->buf, ctx->buf_bytes, write_addr);
|
|
if (rc != 0) {
|
|
LOG_ERR("callback failed: %d", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
ctx->bytes_written += ctx->buf_bytes;
|
|
ctx->buf_bytes = 0U;
|
|
|
|
return rc;
|
|
}
|
|
|
|
int stream_flash_buffered_write(struct stream_flash_ctx *ctx, const uint8_t *data,
|
|
size_t len, bool flush)
|
|
{
|
|
int processed = 0;
|
|
int rc = 0;
|
|
int buf_empty_bytes;
|
|
|
|
if (!ctx) {
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (ctx->bytes_written + ctx->buf_bytes + len > ctx->available) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
while ((len - processed) >=
|
|
(buf_empty_bytes = ctx->buf_len - ctx->buf_bytes)) {
|
|
memcpy(ctx->buf + ctx->buf_bytes, data + processed,
|
|
buf_empty_bytes);
|
|
|
|
ctx->buf_bytes = ctx->buf_len;
|
|
rc = flash_sync(ctx);
|
|
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
|
|
processed += buf_empty_bytes;
|
|
}
|
|
|
|
/* place rest of the data into ctx->buf */
|
|
if (processed < len) {
|
|
memcpy(ctx->buf + ctx->buf_bytes,
|
|
data + processed, len - processed);
|
|
ctx->buf_bytes += len - processed;
|
|
}
|
|
|
|
if (flush && ctx->buf_bytes > 0) {
|
|
rc = flash_sync(ctx);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
size_t stream_flash_bytes_written(struct stream_flash_ctx *ctx)
|
|
{
|
|
return ctx->bytes_written;
|
|
}
|
|
|
|
struct _inspect_flash {
|
|
size_t buf_len;
|
|
size_t total_size;
|
|
};
|
|
|
|
static bool find_flash_total_size(const struct flash_pages_info *info,
|
|
void *data)
|
|
{
|
|
struct _inspect_flash *ctx = (struct _inspect_flash *) data;
|
|
|
|
if (ctx->buf_len > info->size) {
|
|
LOG_ERR("Buffer size is bigger than page");
|
|
ctx->total_size = 0;
|
|
return false;
|
|
}
|
|
|
|
ctx->total_size += info->size;
|
|
|
|
return true;
|
|
}
|
|
|
|
int stream_flash_init(struct stream_flash_ctx *ctx, const struct device *fdev,
|
|
uint8_t *buf, size_t buf_len, size_t offset, size_t size,
|
|
stream_flash_callback_t cb)
|
|
{
|
|
if (!ctx || !fdev || !buf) {
|
|
return -EFAULT;
|
|
}
|
|
|
|
#ifdef CONFIG_STREAM_FLASH_PROGRESS
|
|
int rc = settings_subsys_init();
|
|
|
|
if (rc != 0) {
|
|
LOG_ERR("Error %d initializing settings subsystem", rc);
|
|
return rc;
|
|
}
|
|
#endif
|
|
|
|
struct _inspect_flash inspect_flash_ctx = {
|
|
.buf_len = buf_len,
|
|
.total_size = 0
|
|
};
|
|
|
|
if (buf_len % flash_get_write_block_size(fdev)) {
|
|
LOG_ERR("Buffer size is not aligned to minimal write-block-size");
|
|
return -EFAULT;
|
|
}
|
|
|
|
/* Calculate the total size of the flash device */
|
|
flash_page_foreach(fdev, find_flash_total_size, &inspect_flash_ctx);
|
|
|
|
/* The flash size counted should never be equal zero */
|
|
if (inspect_flash_ctx.total_size == 0) {
|
|
return -EFAULT;
|
|
}
|
|
|
|
if ((offset + size) > inspect_flash_ctx.total_size ||
|
|
offset % flash_get_write_block_size(fdev)) {
|
|
LOG_ERR("Incorrect parameter");
|
|
return -EFAULT;
|
|
}
|
|
|
|
ctx->fdev = fdev;
|
|
ctx->buf = buf;
|
|
ctx->buf_len = buf_len;
|
|
ctx->bytes_written = 0;
|
|
ctx->buf_bytes = 0U;
|
|
ctx->offset = offset;
|
|
ctx->available = (size == 0 ? inspect_flash_ctx.total_size - offset :
|
|
size);
|
|
ctx->callback = cb;
|
|
|
|
#ifdef CONFIG_STREAM_FLASH_ERASE
|
|
ctx->last_erased_page_start_offset = -1;
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_STREAM_FLASH_PROGRESS
|
|
|
|
int stream_flash_progress_load(struct stream_flash_ctx *ctx,
|
|
const char *settings_key)
|
|
{
|
|
if (!ctx || !settings_key) {
|
|
return -EFAULT;
|
|
}
|
|
|
|
int rc = settings_load_subtree_direct(settings_key,
|
|
settings_direct_loader,
|
|
(void *) ctx);
|
|
|
|
if (rc != 0) {
|
|
LOG_ERR("Error %d while loading progress for \"%s\"",
|
|
rc, settings_key);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int stream_flash_progress_save(struct stream_flash_ctx *ctx,
|
|
const char *settings_key)
|
|
{
|
|
if (!ctx || !settings_key) {
|
|
return -EFAULT;
|
|
}
|
|
|
|
int rc = settings_save_one(settings_key,
|
|
&ctx->bytes_written,
|
|
sizeof(ctx->bytes_written));
|
|
|
|
if (rc != 0) {
|
|
LOG_ERR("Error %d while storing progress for \"%s\"",
|
|
rc, settings_key);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int stream_flash_progress_clear(struct stream_flash_ctx *ctx,
|
|
const char *settings_key)
|
|
{
|
|
if (!ctx || !settings_key) {
|
|
return -EFAULT;
|
|
}
|
|
|
|
int rc = settings_delete(settings_key);
|
|
|
|
if (rc != 0) {
|
|
LOG_ERR("Error %d while deleting progress for \"%s\"",
|
|
rc, settings_key);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#endif /* CONFIG_STREAM_FLASH_PROGRESS */
|