storage: add stream flash library
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 <haakon.amundsen@nordicsemi.no>
This commit is contained in:
parent
61aff640f9
commit
a9676831cb
|
@ -244,6 +244,11 @@ current :ref:`stability level <api_lifecycle>`.
|
|||
- 1.11
|
||||
- 2.1
|
||||
|
||||
* - :ref:`stream_flash`
|
||||
- Experimental
|
||||
- 2.3
|
||||
- 2.3
|
||||
|
||||
* - :ref:`flash_map_api`
|
||||
- Stable
|
||||
- 1.11
|
||||
|
|
|
@ -11,3 +11,4 @@ Storage
|
|||
disk/sdhc.rst
|
||||
flash_map/flash_map.rst
|
||||
fcb/fcb.rst
|
||||
stream/stream_flash.rst
|
||||
|
|
24
doc/reference/storage/stream/stream_flash.rst
Normal file
24
doc/reference/storage/stream/stream_flash.rst
Normal file
|
@ -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
|
135
include/storage/stream_flash.h
Normal file
135
include/storage/stream_flash.h
Normal file
|
@ -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 <stdbool.h>
|
||||
#include <drivers/flash.h>
|
||||
|
||||
#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_ */
|
|
@ -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)
|
||||
|
|
|
@ -6,5 +6,6 @@
|
|||
menu "Storage"
|
||||
|
||||
source "subsys/storage/flash_map/Kconfig"
|
||||
source "subsys/storage/stream/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
|
7
subsys/storage/stream/CMakeLists.txt
Normal file
7
subsys/storage/stream/CMakeLists.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
zephyr_sources(stream_flash.c)
|
24
subsys/storage/stream/Kconfig
Normal file
24
subsys/storage/stream/Kconfig
Normal file
|
@ -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
|
206
subsys/storage/stream/stream_flash.c
Normal file
206
subsys/storage/stream/stream_flash.c
Normal file
|
@ -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 <logging/log.h>
|
||||
LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_STREAM_FLASH_LOG_LEVEL);
|
||||
|
||||
#include <zephyr/types.h>
|
||||
#include <string.h>
|
||||
#include <drivers/flash.h>
|
||||
|
||||
#include <storage/stream_flash.h>
|
||||
|
||||
#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;
|
||||
}
|
12
tests/subsys/storage/stream/stream_flash/CMakeLists.txt
Normal file
12
tests/subsys/storage/stream/stream_flash/CMakeLists.txt
Normal file
|
@ -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})
|
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
CONFIG_MPU_ALLOW_FLASH_WRITE=y
|
|
@ -0,0 +1,7 @@
|
|||
#
|
||||
# Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
CONFIG_STREAM_FLASH_ERASE=n
|
13
tests/subsys/storage/stream/stream_flash/prj.conf
Normal file
13
tests/subsys/storage/stream/stream_flash/prj.conf
Normal file
|
@ -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
|
351
tests/subsys/storage/stream/stream_flash/src/main.c
Normal file
351
tests/subsys/storage/stream/stream_flash/src/main.c
Normal file
|
@ -0,0 +1,351 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <zephyr/types.h>
|
||||
#include <stdbool.h>
|
||||
#include <ztest.h>
|
||||
#include <drivers/flash.h>
|
||||
|
||||
#include <storage/stream_flash.h>
|
||||
|
||||
#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);
|
||||
}
|
12
tests/subsys/storage/stream/stream_flash/testcase.yaml
Normal file
12
tests/subsys/storage/stream/stream_flash/testcase.yaml
Normal file
|
@ -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
|
Loading…
Reference in a new issue