From a9676831cb85f7382ae025f057efb8e2dc49d829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20=C3=98ye=20Amundsen?= Date: Thu, 5 Mar 2020 12:45:53 +0000 Subject: [PATCH] storage: add stream flash library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This library supports stream writes to flash with optinal progressive erase. This module is a direct copy of the functionality found in subsys/dfu/img_util/flash_img.c Signed-off-by: Håkon Øye Amundsen --- doc/reference/overview.rst | 5 + doc/reference/storage/index.rst | 1 + doc/reference/storage/stream/stream_flash.rst | 24 ++ include/storage/stream_flash.h | 135 +++++++ subsys/storage/CMakeLists.txt | 1 + subsys/storage/Kconfig | 1 + subsys/storage/stream/CMakeLists.txt | 7 + subsys/storage/stream/Kconfig | 24 ++ subsys/storage/stream/stream_flash.c | 206 ++++++++++ .../stream/stream_flash/CMakeLists.txt | 12 + .../mpu_allow_flash_write.overlay | 7 + .../stream/stream_flash/no_erase.overlay | 7 + .../storage/stream/stream_flash/prj.conf | 13 + .../storage/stream/stream_flash/src/main.c | 351 ++++++++++++++++++ .../storage/stream/stream_flash/testcase.yaml | 12 + 15 files changed, 806 insertions(+) create mode 100644 doc/reference/storage/stream/stream_flash.rst create mode 100644 include/storage/stream_flash.h create mode 100644 subsys/storage/stream/CMakeLists.txt create mode 100644 subsys/storage/stream/Kconfig create mode 100644 subsys/storage/stream/stream_flash.c create mode 100644 tests/subsys/storage/stream/stream_flash/CMakeLists.txt create mode 100644 tests/subsys/storage/stream/stream_flash/mpu_allow_flash_write.overlay create mode 100644 tests/subsys/storage/stream/stream_flash/no_erase.overlay create mode 100644 tests/subsys/storage/stream/stream_flash/prj.conf create mode 100644 tests/subsys/storage/stream/stream_flash/src/main.c create mode 100644 tests/subsys/storage/stream/stream_flash/testcase.yaml diff --git a/doc/reference/overview.rst b/doc/reference/overview.rst index 41df8a55077..b8655ebb601 100644 --- a/doc/reference/overview.rst +++ b/doc/reference/overview.rst @@ -244,6 +244,11 @@ current :ref:`stability level `. - 1.11 - 2.1 + * - :ref:`stream_flash` + - Experimental + - 2.3 + - 2.3 + * - :ref:`flash_map_api` - Stable - 1.11 diff --git a/doc/reference/storage/index.rst b/doc/reference/storage/index.rst index c7d92f4729d..b0298b58b70 100644 --- a/doc/reference/storage/index.rst +++ b/doc/reference/storage/index.rst @@ -11,3 +11,4 @@ Storage disk/sdhc.rst flash_map/flash_map.rst fcb/fcb.rst + stream/stream_flash.rst diff --git a/doc/reference/storage/stream/stream_flash.rst b/doc/reference/storage/stream/stream_flash.rst new file mode 100644 index 00000000000..808767bfabe --- /dev/null +++ b/doc/reference/storage/stream/stream_flash.rst @@ -0,0 +1,24 @@ +.. _stream_flash: + +Stream Flash +############ +The Stream Flash module takes contiguous fragments of a stream of data (e.g. +from radio packets), aggregates them into a user-provided buffer, then when the +buffer fills (or stream ends) writes it to a raw flash partition. It supports +providing the read-back buffer to the client to use in validating the persisted +stream content. + +One typical use of a stream write operation is when receiving a new firmware +image to be used in a DFU operation. + +There are several reasons why one might want to use buffered writes instead of +writing the data directly as it is made available. Some devices have hardware +limitations which does not allow flash writes to be performed in parallell with +other operations, such as radio RX and TX. Also, fewer write operations result +in faster response times seen from the application. + +API Reference +************* + +.. doxygengroup:: stream_flash + :project: Zephyr diff --git a/include/storage/stream_flash.h b/include/storage/stream_flash.h new file mode 100644 index 00000000000..484718fde39 --- /dev/null +++ b/include/storage/stream_flash.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2017, 2020 Nordic Semiconductor ASA + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Public API for stream writes to flash + */ + +#ifndef ZEPHYR_INCLUDE_STORAGE_STREAM_FLASH_H_ +#define ZEPHYR_INCLUDE_STORAGE_STREAM_FLASH_H_ + +/** + * @brief Abstraction over stream writes to flash + * + * @defgroup stream_flash Stream to flash interface + * @{ + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @typedef stream_flash_callback_t + * + * @brief Signature for callback invoked after flash write completes. + * + * @details Functions of this type are invoked with a buffer containing + * data read back from the flash after a flash write has completed. + * This enables verifying that the data has been correctly stored (for + * instance by using a SHA function). The write buffer 'buf' provided in + * stream_flash_init is used as a read buffer for this purpose. + * + * @param buf Pointer to the data read. + * @param len The length of the data read. + * @param offset The offset the data was read from. + */ +typedef int (*stream_flash_callback_t)(u8_t *buf, size_t len, size_t offset); + +/** + * @brief Structure for stream flash context + * + * Users should treat these structures as opaque values and only interact + * with them through the below API. + */ +struct stream_flash_ctx { + u8_t *buf; /* Write buffer */ + size_t buf_len; /* Length of write buffer */ + size_t buf_bytes; /* Number of bytes currently stored in write buf */ + struct device *fdev; /* Flash device */ + size_t bytes_written; /* Number of bytes written to flash */ + size_t offset; /* Offset from base of flash device to write area */ + size_t available; /* Available bytes in write area */ + stream_flash_callback_t callback; /* Callback invoked after write op */ +#ifdef CONFIG_STREAM_FLASH_ERASE + off_t last_erased_page_start_offset; /* Last erased offset */ +#endif +}; + +/** + * @brief Initialize context needed for stream writes to flash. + * + * @param ctx context to be initialized + * @param fdev Flash device to operate on + * @param buf Write buffer + * @param buf_len Length of write buffer. Can not be larger than the page size + * @param offset Offset within flash device to start writing to + * @param size Number of bytes available for performing buffered write. + * If this is '0', the size will be set to the total size + * of the flash device minus the offset. + * @param cb Callback to be invoked on completed flash write operations. + * + * @return non-negative on success, negative errno code on fail + */ +int stream_flash_init(struct stream_flash_ctx *ctx, struct device *fdev, + u8_t *buf, size_t buf_len, size_t offset, size_t size, + stream_flash_callback_t cb); +/** + * @brief Read number of bytes written to the flash. + * + * @note api-tags: pre-kernel-ok isr-ok + * + * @param ctx context + * + * @return Number of bytes written to flash. + */ +size_t stream_flash_bytes_written(struct stream_flash_ctx *ctx); + +/** + * @brief Process input buffers to be written to flash device in single blocks. + * Will store remainder between calls. + * + * A final call to this function with flush set to true + * will write out the remaining block buffer to flash. + * + * @param ctx context + * @param data data to write + * @param len Number of bytes to write + * @param flush when true this forces any buffered data to be written to flash + * + * @return non-negative on success, negative errno code on fail + */ +int stream_flash_buffered_write(struct stream_flash_ctx *ctx, const u8_t *data, + size_t len, bool flush); + +/** + * @brief Erase the flash page to which a given offset belongs. + * + * This function erases a flash page to which an offset belongs if this page + * is not the page previously erased by the provided ctx + * (ctx->last_erased_page_start_offset). + * + * @param ctx context + * @param off offset from the base address of the flash device + * + * @return non-negative on success, negative errno code on fail + */ +int stream_flash_erase_page(struct stream_flash_ctx *ctx, off_t off); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_STORAGE_STREAM_FLASH_H_ */ diff --git a/subsys/storage/CMakeLists.txt b/subsys/storage/CMakeLists.txt index 99fd0f27928..ed94d1920ba 100644 --- a/subsys/storage/CMakeLists.txt +++ b/subsys/storage/CMakeLists.txt @@ -1,3 +1,4 @@ # SPDX-License-Identifier: Apache-2.0 add_subdirectory_ifdef(CONFIG_FLASH_MAP flash_map) +add_subdirectory_ifdef(CONFIG_STREAM_FLASH stream) diff --git a/subsys/storage/Kconfig b/subsys/storage/Kconfig index 53cbf6c2779..6d78778449c 100644 --- a/subsys/storage/Kconfig +++ b/subsys/storage/Kconfig @@ -6,5 +6,6 @@ menu "Storage" source "subsys/storage/flash_map/Kconfig" +source "subsys/storage/stream/Kconfig" endmenu diff --git a/subsys/storage/stream/CMakeLists.txt b/subsys/storage/stream/CMakeLists.txt new file mode 100644 index 00000000000..c5216466630 --- /dev/null +++ b/subsys/storage/stream/CMakeLists.txt @@ -0,0 +1,7 @@ +# +# Copyright (c) 2020 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +zephyr_sources(stream_flash.c) diff --git a/subsys/storage/stream/Kconfig b/subsys/storage/stream/Kconfig new file mode 100644 index 00000000000..63a2420b129 --- /dev/null +++ b/subsys/storage/stream/Kconfig @@ -0,0 +1,24 @@ +# +# Copyright (c) 2020 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +menuconfig STREAM_FLASH + bool "Stream to flash" + select FLASH_PAGE_LAYOUT + help + Enable support of stream to flash API + +if STREAM_FLASH +config STREAM_FLASH_ERASE + bool "Perform erase operations" + help + If disabled an external actor must erase the flash area being written + to. + +module = STREAM_FLASH +module-str = stream flash +source "subsys/logging/Kconfig.template.log_config" + +endif # STREAM_FLASH diff --git a/subsys/storage/stream/stream_flash.c b/subsys/storage/stream/stream_flash.c new file mode 100644 index 00000000000..86e12288226 --- /dev/null +++ b/subsys/storage/stream/stream_flash.c @@ -0,0 +1,206 @@ +/* + * 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 +LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_STREAM_FLASH_LOG_LEVEL); + +#include +#include +#include + +#include + +#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; + } + + ctx->last_erased_page_start_offset = page.start_offset; + LOG_INF("Erasing page at offset 0x%08lx", (long)page.start_offset); + + flash_write_protection_set(ctx->fdev, false); + rc = flash_erase(ctx->fdev, page.start_offset, page.size); + flash_write_protection_set(ctx->fdev, true); + + if (rc != 0) { + LOG_ERR("Error %d while erasing page", rc); + } + + 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; + + + if (IS_ENABLED(CONFIG_STREAM_FLASH_ERASE)) { + if (ctx->buf_bytes == 0) { + return 0; + } + + 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; + } + } + + flash_write_protection_set(ctx->fdev, false); + rc = flash_write(ctx->fdev, write_addr, ctx->buf, ctx->buf_bytes); + flash_write_protection_set(ctx->fdev, true); + + 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); + } + } + + ctx->bytes_written += ctx->buf_bytes; + ctx->buf_bytes = 0U; + + return rc; +} + +int stream_flash_buffered_write(struct stream_flash_ctx *ctx, const u8_t *data, + size_t len, bool flush) +{ + int processed = 0; + int rc = 0; + int buf_empty_bytes; + + if (!ctx || !data) { + 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; +} + +int stream_flash_init(struct stream_flash_ctx *ctx, struct device *fdev, + u8_t *buf, size_t buf_len, size_t offset, size_t size, + stream_flash_callback_t cb) +{ + if (!ctx || !fdev || !buf) { + return -EFAULT; + } + + size_t layout_size = 0; + size_t total_size = 0; + const struct flash_pages_layout *layout; + const struct flash_driver_api *api = fdev->driver_api; + + /* Calculate the total size of the flash device */ + api->page_layout(fdev, &layout, &layout_size); + for (int i = 0; i < layout_size; i++) { + + total_size += layout->pages_count * layout->pages_size; + + if (buf_len > layout->pages_size) { + LOG_ERR("Buffer size is bigger than page"); + return -EFAULT; + } + + layout++; + + } + + if ((offset + size) > total_size || + offset % api->write_block_size) { + 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 ? total_size - offset : size); + ctx->callback = cb; + +#ifdef CONFIG_STREAM_FLASH_ERASE + ctx->last_erased_page_start_offset = -1; +#endif + + return 0; +} diff --git a/tests/subsys/storage/stream/stream_flash/CMakeLists.txt b/tests/subsys/storage/stream/stream_flash/CMakeLists.txt new file mode 100644 index 00000000000..ea4657aac6b --- /dev/null +++ b/tests/subsys/storage/stream/stream_flash/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# Copyright (c) 2020 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +cmake_minimum_required(VERSION 3.13.1) +include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +project(stream_flash) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/subsys/storage/stream/stream_flash/mpu_allow_flash_write.overlay b/tests/subsys/storage/stream/stream_flash/mpu_allow_flash_write.overlay new file mode 100644 index 00000000000..7671079522e --- /dev/null +++ b/tests/subsys/storage/stream/stream_flash/mpu_allow_flash_write.overlay @@ -0,0 +1,7 @@ +# +# Copyright (c) 2020 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +CONFIG_MPU_ALLOW_FLASH_WRITE=y diff --git a/tests/subsys/storage/stream/stream_flash/no_erase.overlay b/tests/subsys/storage/stream/stream_flash/no_erase.overlay new file mode 100644 index 00000000000..4db43d50ab1 --- /dev/null +++ b/tests/subsys/storage/stream/stream_flash/no_erase.overlay @@ -0,0 +1,7 @@ +# +# Copyright (c) 2020 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +CONFIG_STREAM_FLASH_ERASE=n diff --git a/tests/subsys/storage/stream/stream_flash/prj.conf b/tests/subsys/storage/stream/stream_flash/prj.conf new file mode 100644 index 00000000000..2b16be9247b --- /dev/null +++ b/tests/subsys/storage/stream/stream_flash/prj.conf @@ -0,0 +1,13 @@ +# +# Copyright (c) 2020 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +CONFIG_ZTEST=y +CONFIG_FLASH=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_DEBUG_OPTIMIZATIONS=y + +CONFIG_STREAM_FLASH=y +CONFIG_STREAM_FLASH_ERASE=y diff --git a/tests/subsys/storage/stream/stream_flash/src/main.c b/tests/subsys/storage/stream/stream_flash/src/main.c new file mode 100644 index 00000000000..bd12ed5b07f --- /dev/null +++ b/tests/subsys/storage/stream/stream_flash/src/main.c @@ -0,0 +1,351 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include + +#define BUF_LEN 512 +#define MAX_PAGE_SIZE 0x1000 /* Max supported page size to run test on */ +#define MAX_NUM_PAGES 4 /* Max number of pages used in these tests */ +#define TESTBUF_SIZE (MAX_PAGE_SIZE * MAX_NUM_PAGES) +#define FLASH_SIZE DT_SOC_NV_FLASH_0_SIZE +#define FLASH_NAME DT_CHOSEN_ZEPHYR_FLASH_CONTROLLER_LABEL + +/* so that we don't overwrite the application when running on hw */ +#define FLASH_BASE (64*1024) +#define FLASH_AVAILABLE (FLASH_SIZE-FLASH_BASE) + +static struct device *fdev; +static const struct flash_driver_api *api; +static const struct flash_pages_layout *layout; +static size_t layout_size; +static struct stream_flash_ctx ctx; +static int page_size; +static u8_t *cb_buf; +static size_t cb_len; +static size_t cb_offset; +static int cb_ret; + +static u8_t buf[BUF_LEN]; +static u8_t read_buf[TESTBUF_SIZE]; +const static u8_t write_buf[TESTBUF_SIZE] = {[0 ... TESTBUF_SIZE - 1] = 0xaa}; +static u8_t written_pattern[TESTBUF_SIZE] = {[0 ... TESTBUF_SIZE - 1] = 0xaa}; +static u8_t erased_pattern[TESTBUF_SIZE] = {[0 ... TESTBUF_SIZE - 1] = 0xff}; + +#define VERIFY_BUF(start, size, buf) \ +do { \ + rc = flash_read(fdev, FLASH_BASE + start, read_buf, size); \ + zassert_equal(rc, 0, "should succeed"); \ + zassert_mem_equal(read_buf, buf, size, "should equal %s", #buf);\ +} while (0) + +#define VERIFY_WRITTEN(start, size) VERIFY_BUF(start, size, written_pattern) +#define VERIFY_ERASED(start, size) VERIFY_BUF(start, size, erased_pattern) + +int stream_flash_callback(u8_t *buf, size_t len, size_t offset) +{ + if (cb_buf) { + zassert_equal(cb_buf, buf, "incorrect buf"); + zassert_equal(cb_len, len, "incorrect length"); + zassert_equal(cb_offset, offset, "incorrect offset"); + } + + return cb_ret; +} + +static void erase_flash(void) +{ + int rc; + + rc = flash_write_protection_set(fdev, false); + zassert_equal(rc, 0, "should succeed"); + + for (int i = 0; i < MAX_NUM_PAGES; i++) { + rc = flash_erase(fdev, + FLASH_BASE + (i * layout->pages_size), + layout->pages_size); + zassert_equal(rc, 0, "should succeed"); + } + + rc = flash_write_protection_set(fdev, true); + zassert_equal(rc, 0, "should succeed"); +} + + +static void init_target(void) +{ + int rc; + + /* Ensure that target is clean */ + memset(&ctx, 0, sizeof(ctx)); + memset(buf, 0, BUF_LEN); + + /* Disable callback tests */ + cb_len = 0; + cb_offset = 0; + cb_buf = NULL; + cb_ret = 0; + + erase_flash(); + + rc = stream_flash_init(&ctx, fdev, buf, BUF_LEN, FLASH_BASE, 0, + stream_flash_callback); + zassert_equal(rc, 0, "expected success"); +} + +static void test_stream_flash_init(void) +{ + int rc; + + init_target(); + + /* End address out of range */ + rc = stream_flash_init(&ctx, fdev, buf, BUF_LEN, FLASH_BASE, + FLASH_AVAILABLE + 4, NULL); + zassert_true(rc < 0, "should fail as size is more than available"); + + rc = stream_flash_init(NULL, fdev, buf, BUF_LEN, FLASH_BASE, 0, NULL); + zassert_true(rc < 0, "should fail as ctx is NULL"); + + rc = stream_flash_init(&ctx, NULL, buf, BUF_LEN, FLASH_BASE, 0, NULL); + zassert_true(rc < 0, "should fail as fdev is NULL"); + + rc = stream_flash_init(&ctx, fdev, NULL, BUF_LEN, FLASH_BASE, 0, NULL); + zassert_true(rc < 0, "should fail as buffer is NULL"); + + /* Entering '0' as flash size uses rest of flash. */ + rc = stream_flash_init(&ctx, fdev, buf, BUF_LEN, FLASH_BASE, 0, NULL); + zassert_equal(rc, 0, "should succeed"); + zassert_equal(FLASH_AVAILABLE, ctx.available, "Wrong size"); +} + +static void test_stream_flash_buffered_write(void) +{ + int rc; + + init_target(); + + /* Don't fill up the buffer */ + rc = stream_flash_buffered_write(&ctx, write_buf, BUF_LEN - 1, false); + zassert_equal(rc, 0, "expected success"); + + /* Verify that no data has been written */ + VERIFY_ERASED(0, BUF_LEN); + + /* Now, write the missing byte, which should trigger a dump to flash */ + rc = stream_flash_buffered_write(&ctx, write_buf, 1, false); + zassert_equal(rc, 0, "expected success"); + + VERIFY_WRITTEN(0, BUF_LEN); +} + +static void test_stream_flash_buffered_write_cross_buf_border(void) +{ + int rc; + + init_target(); + + /* Test when write crosses border of the buffer */ + rc = stream_flash_buffered_write(&ctx, write_buf, BUF_LEN + 128, false); + zassert_equal(rc, 0, "expected success"); + + /* 1xBuffer should be dumped to flash */ + VERIFY_WRITTEN(0, BUF_LEN); + + /* Fill rest of the buffer */ + rc = stream_flash_buffered_write(&ctx, write_buf, BUF_LEN - 128, false); + zassert_equal(rc, 0, "expected success"); + VERIFY_WRITTEN(BUF_LEN, BUF_LEN); + + /* Fill half of the buffer */ + rc = stream_flash_buffered_write(&ctx, write_buf, BUF_LEN/2, false); + zassert_equal(rc, 0, "expected success"); + + /* Flush the buffer */ + rc = stream_flash_buffered_write(&ctx, write_buf, 0, true); + zassert_equal(rc, 0, "expected success"); + + /* Two and a half buffers should be written */ + VERIFY_WRITTEN(0, BUF_LEN * 2 + BUF_LEN / 2); +} + +static void test_stream_flash_buffered_write_multi_page(void) +{ + int rc; + int num_pages = MAX_NUM_PAGES - 1; + + init_target(); + + /* Test when write spans multiple pages crosses border of page */ + rc = stream_flash_buffered_write(&ctx, write_buf, + (page_size * num_pages) + 128, false); + zassert_equal(rc, 0, "expected success"); + + /* First three pages should be written */ + VERIFY_WRITTEN(0, page_size * num_pages); + + /* Fill rest of the page */ + rc = stream_flash_buffered_write(&ctx, write_buf, + page_size - 128, false); + zassert_equal(rc, 0, "expected success"); + + /* First four pages should be written */ + VERIFY_WRITTEN(0, BUF_LEN * (num_pages + 1)); +} + +static void test_stream_flash_bytes_written(void) +{ + int rc; + size_t offset; + + init_target(); + + /* Verify that the offset is retained across failed downolads */ + rc = stream_flash_buffered_write(&ctx, write_buf, BUF_LEN + 128, false); + zassert_equal(rc, 0, "expected success"); + + /* First page should be written */ + VERIFY_WRITTEN(0, BUF_LEN); + + /* Fill rest of the page */ + offset = stream_flash_bytes_written(&ctx); + zassert_equal(offset, BUF_LEN, "offset should match buf size"); + + /* Fill up the buffer MINUS 128 to verify that write_buf_pos is kept */ + rc = stream_flash_buffered_write(&ctx, write_buf, BUF_LEN - 128, false); + zassert_equal(rc, 0, "expected success"); + + /* Second page should be written */ + VERIFY_WRITTEN(BUF_LEN, BUF_LEN); +} + +static void test_stream_flash_buf_size_greater_than_page_size(void) +{ + int rc; + + /* To illustrate that other params does not trigger error */ + rc = stream_flash_init(&ctx, fdev, buf, 0x10, 0, 0, NULL); + zassert_equal(rc, 0, "expected success"); + + /* Only change buf_len param */ + rc = stream_flash_init(&ctx, fdev, buf, 0x10000, 0, 0, NULL); + zassert_true(rc < 0, "expected failure"); +} + +static void test_stream_flash_buffered_write_callback(void) +{ + int rc; + + init_target(); + + /* Trigger verification in callback */ + cb_buf = buf; + cb_len = BUF_LEN; + cb_offset = FLASH_BASE; + + rc = stream_flash_buffered_write(&ctx, write_buf, BUF_LEN + 128, false); + zassert_equal(rc, 0, "expected success"); + + cb_len = BUF_LEN; + cb_offset = FLASH_BASE + BUF_LEN; + + /* Fill rest of the buffer */ + rc = stream_flash_buffered_write(&ctx, write_buf, BUF_LEN - 128, false); + zassert_equal(rc, 0, "expected success"); + VERIFY_WRITTEN(BUF_LEN, BUF_LEN); + + /* Fill half of the buffer and flush it to flash */ + cb_len = BUF_LEN/2; + cb_offset = FLASH_BASE + (2 * BUF_LEN); + + rc = stream_flash_buffered_write(&ctx, write_buf, BUF_LEN/2, true); + zassert_equal(rc, 0, "expected success"); + + /* Ensure that failing callback trickles up to caller */ + cb_ret = -EFAULT; + cb_buf = NULL; /* Don't verify other parameters of the callback */ + rc = stream_flash_buffered_write(&ctx, write_buf, BUF_LEN, true); + zassert_equal(rc, -EFAULT, "expected failure from callback"); +} + +#ifdef CONFIG_STREAM_FLASH_ERASE +static void test_stream_flash_buffered_write_whole_page(void) +{ + int rc; + + init_target(); + + /* Write all bytes of a page, verify that next page is not erased */ + + /* First fill two pages with data */ + rc = stream_flash_buffered_write(&ctx, write_buf, page_size * 2, true); + zassert_equal(rc, 0, "expected success"); + + VERIFY_WRITTEN(0, page_size); + VERIFY_WRITTEN(page_size, page_size); + + /* Reset stream_flash context */ + memset(&ctx, 0, sizeof(ctx)); + memset(buf, 0, BUF_LEN); + rc = stream_flash_init(&ctx, fdev, buf, BUF_LEN, FLASH_BASE, 0, + stream_flash_callback); + zassert_equal(rc, 0, "expected success"); + + /* Write all bytes of a page, verify that next page is not erased */ + rc = stream_flash_buffered_write(&ctx, write_buf, page_size, true); + zassert_equal(rc, 0, "expected success"); + + /* Second page should not be erased */ + VERIFY_WRITTEN(page_size, page_size); +} + +static void test_stream_flash_erase_page(void) +{ + int rc; + + init_target(); + + /* Write out one buf */ + rc = stream_flash_buffered_write(&ctx, write_buf, BUF_LEN, false); + zassert_equal(rc, 0, "expected success"); + + rc = stream_flash_erase_page(&ctx, FLASH_BASE); + zassert_equal(rc, 0, "expected success"); + + VERIFY_ERASED(FLASH_BASE, page_size); +} +#endif + +void test_main(void) +{ + fdev = device_get_binding(FLASH_NAME); + api = fdev->driver_api; + api->page_layout(fdev, &layout, &layout_size); + + page_size = layout->pages_size; + __ASSERT_NO_MSG(page_size > BUF_LEN); + + ztest_test_suite(lib_stream_flash_test, + ztest_unit_test(test_stream_flash_init), + ztest_unit_test(test_stream_flash_buffered_write), + ztest_unit_test(test_stream_flash_buffered_write_cross_buf_border), + ztest_unit_test(test_stream_flash_buffered_write_multi_page), + ztest_unit_test(test_stream_flash_buf_size_greater_than_page_size), + ztest_unit_test(test_stream_flash_buffered_write_callback), +#ifdef CONFIG_STREAM_FLASH_ERASE + ztest_unit_test(test_stream_flash_buffered_write_whole_page), + ztest_unit_test(test_stream_flash_erase_page), +#endif + ztest_unit_test(test_stream_flash_bytes_written) + ); + + ztest_run_test_suite(lib_stream_flash_test); +} diff --git a/tests/subsys/storage/stream/stream_flash/testcase.yaml b/tests/subsys/storage/stream/stream_flash/testcase.yaml new file mode 100644 index 00000000000..523b6356902 --- /dev/null +++ b/tests/subsys/storage/stream/stream_flash/testcase.yaml @@ -0,0 +1,12 @@ +tests: + storage.stream_flash: + platform_whitelist: native_posix native_posix_64 + tags: stream_flash + storage.stream_flash.no_erase: + extra_args: OVERLAY_CONFIG=no_erase.overlay + platform_whitelist: native_posix native_posix_64 + tags: stream_flash + storage.stream_flash.mpu_allow_flash_write: + extra_args: OVERLAY_CONFIG=mpu_allow_flash_write.overlay + platform_whitelist: nrf52840_pca10056 + tags: stream_flash