drivers/flash: Add flash_fill() and flash_flatten()
The commit adds two new API calls: - flash_fill - that allows to fill selected part of device with specified value; - flash_flatten - that allows to erase or fill device with erase_value, depending on whether driver for the device provides erase callback. Signed-off-by: Dominik Ermel <dominik.ermel@nordicsemi.no>
This commit is contained in:
parent
010b8d19e9
commit
b8d073c572
6 changed files with 245 additions and 2 deletions
|
@ -1,5 +1,7 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
zephyr_library_sources(flash_util.c)
|
||||
|
||||
zephyr_syscall_header_ifdef(
|
||||
CONFIG_FLASH_SIMULATOR
|
||||
${ZEPHYR_BASE}/include/zephyr/drivers/flash/flash_simulator.h
|
||||
|
|
|
@ -127,6 +127,19 @@ config FLASH_SHELL_BUFFER_SIZE
|
|||
|
||||
endif # FLASH_SHELL
|
||||
|
||||
config FLASH_FILL_BUFFER_SIZE
|
||||
int "Buffer size of flash_fill funciton"
|
||||
default 32
|
||||
help
|
||||
Size of a buffer used by flash_fill function to fill a device with
|
||||
specific value; this buffer is allocated on stack.
|
||||
The buffer is needed as most devices have write-block alignment
|
||||
requirements that which imposes minimal size of data, which can
|
||||
be written to a device, and alignment of write offset.
|
||||
Even if device does not have such requirement, filling device by
|
||||
single bytes is not efficient.
|
||||
Value selected here should be multiply of the largest write-block-size
|
||||
among all the memory devices used in system.
|
||||
|
||||
if FLASH_HAS_PAGE_LAYOUT
|
||||
|
||||
|
|
|
@ -50,6 +50,22 @@ static inline const struct flash_parameters *z_vrfy_flash_get_parameters(const s
|
|||
}
|
||||
#include <zephyr/syscalls/flash_get_parameters_mrsh.c>
|
||||
|
||||
int z_vrfy_flash_fill(const struct device *dev, uint8_t val, off_t offset,
|
||||
size_t size)
|
||||
{
|
||||
K_OOPS(K_SYSCALL_OBJ(dev, K_OBJ_DRIVER_FLASH));
|
||||
return z_impl_flash_fill(dev, val, offset, size);
|
||||
}
|
||||
#include <syscalls/flash_fill_mrsh.c>
|
||||
|
||||
int z_vrfy_flash_flatten(const struct device *dev, off_t offset, size_t size)
|
||||
{
|
||||
K_OOPS(K_SYSCALL_OBJ(dev, K_OBJ_DRIVER_FLASH));
|
||||
return z_impl_flash_flatten(dev, offset, size);
|
||||
}
|
||||
|
||||
#include <syscalls/flash_flatten_mrsh.c>
|
||||
|
||||
#ifdef CONFIG_FLASH_PAGE_LAYOUT
|
||||
static inline int z_vrfy_flash_get_page_info_by_offs(const struct device *dev,
|
||||
off_t offs,
|
||||
|
|
82
drivers/flash/flash_util.c
Normal file
82
drivers/flash/flash_util.c
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (c) 2024 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <zephyr/drivers/flash.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <string.h>
|
||||
|
||||
LOG_MODULE_REGISTER(flash, CONFIG_FLASH_LOG_LEVEL);
|
||||
|
||||
int z_impl_flash_fill(const struct device *dev, uint8_t val, off_t offset,
|
||||
size_t size)
|
||||
{
|
||||
uint8_t filler[CONFIG_FLASH_FILL_BUFFER_SIZE];
|
||||
const struct flash_driver_api *api =
|
||||
(const struct flash_driver_api *)dev->api;
|
||||
const struct flash_parameters *fparams = api->get_parameters(dev);
|
||||
int rc = 0;
|
||||
size_t stored = 0;
|
||||
|
||||
if (sizeof(filler) < fparams->write_block_size) {
|
||||
LOG_ERR("Size of CONFIG_FLASH_FILL_BUFFER_SIZE");
|
||||
return -EINVAL;
|
||||
}
|
||||
/* The flash_write will, probably, check write alignment but this
|
||||
* is too late, as we write datain chunks; data alignment may be
|
||||
* broken by the size of the last chunk, that is why the check
|
||||
* happens here too.
|
||||
* Note that we have no way to check whether offset and size are
|
||||
* are correct, as such info is only available at the level of
|
||||
* a driver, so only basic check on offset.
|
||||
*/
|
||||
if (offset < 0) {
|
||||
LOG_ERR("Negative offset not allowed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if ((size | (size_t)offset) & (fparams->write_block_size - 1)) {
|
||||
LOG_ERR("Incorrect size or offset alignment, expected %zx\n",
|
||||
fparams->write_block_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
memset(filler, val, sizeof(filler));
|
||||
|
||||
while (stored < size) {
|
||||
size_t chunk = MIN(sizeof(filler), size);
|
||||
|
||||
rc = api->write(dev, offset + stored, filler, chunk);
|
||||
if (rc < 0) {
|
||||
LOG_DBG("Fill to dev %p failed at offset 0x%zx\n",
|
||||
dev, (size_t)offset + stored);
|
||||
break;
|
||||
}
|
||||
stored += chunk;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int z_impl_flash_flatten(const struct device *dev, off_t offset, size_t size)
|
||||
{
|
||||
const struct flash_driver_api *api =
|
||||
(const struct flash_driver_api *)dev->api;
|
||||
const struct flash_parameters *params = api->get_parameters(dev);
|
||||
|
||||
#if IS_ENABLED(CONFIG_FLASH_HAS_EXPLICIT_ERASE)
|
||||
if ((flash_params_get_erase_cap(params) & FLASH_ERASE_C_EXPLICIT) &&
|
||||
api->erase != NULL) {
|
||||
return api->erase(dev, offset, size);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_FLASH_HAS_NO_EXPLICIT_ERASE)
|
||||
return flash_fill(dev, params->erase_value, offset, size);
|
||||
#else
|
||||
return -ENOSYS;
|
||||
#endif
|
||||
}
|
|
@ -141,6 +141,11 @@ typedef int (*flash_api_write)(const struct device *dev, off_t offset,
|
|||
* the driver, with the driver responsible for ensuring the "erase-protect"
|
||||
* after the operation completes (successfully or not) matches the erase-protect
|
||||
* state when the operation was started.
|
||||
*
|
||||
* The callback is optional for RAM non-volatile devices, which do not
|
||||
* require erase by design, but may be provided if it allows device to
|
||||
* work more effectively, or if device has a support for internal fill
|
||||
* operation the erase in driver uses.
|
||||
*/
|
||||
typedef int (*flash_api_erase)(const struct device *dev, off_t offset,
|
||||
size_t size);
|
||||
|
@ -278,12 +283,19 @@ static inline int z_impl_flash_write(const struct device *dev, off_t offset,
|
|||
* Any necessary erase protection management is performed by the driver
|
||||
* erase implementation itself.
|
||||
*
|
||||
* The function should be used only for devices that are really
|
||||
* explicit erase devices; in case when code relies on erasing
|
||||
* device, i.e. setting it to erase-value, prior to some operations,
|
||||
* but should work with explicit erase and RAM non-volatile devices,
|
||||
* then flash_flatten should rather be used.
|
||||
*
|
||||
* @param dev : flash device
|
||||
* @param offset : erase area starting offset
|
||||
* @param size : size of area to be erased
|
||||
*
|
||||
* @return 0 on success, negative errno code on fail.
|
||||
*
|
||||
* @see flash_flatten()
|
||||
* @see flash_get_page_info_by_offs()
|
||||
* @see flash_get_page_info_by_idx()
|
||||
*/
|
||||
|
@ -292,15 +304,59 @@ __syscall int flash_erase(const struct device *dev, off_t offset, size_t size);
|
|||
static inline int z_impl_flash_erase(const struct device *dev, off_t offset,
|
||||
size_t size)
|
||||
{
|
||||
int rc = -ENOSYS;
|
||||
|
||||
const struct flash_driver_api *api =
|
||||
(const struct flash_driver_api *)dev->api;
|
||||
int rc;
|
||||
|
||||
rc = api->erase(dev, offset, size);
|
||||
if (api->erase != NULL) {
|
||||
rc = api->erase(dev, offset, size);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
__syscall int flash_fill(const struct device *dev, uint8_t val, off_t offset, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Erase part or all of a flash memory or level it
|
||||
*
|
||||
* If device is explicit erase type device or device driver provides erase
|
||||
* callback, the callback of the device is called, in which it behaves
|
||||
* the same way as flash_erase.
|
||||
* If a device is does not require explicit erase, either because
|
||||
* it has no erase at all or has auto-erase/erase-on-write,
|
||||
* and does not provide erase callback then erase is emulated by
|
||||
* leveling selected device memory area with erase_value assigned to
|
||||
* device.
|
||||
*
|
||||
* Erase page offset and size are constrains of paged, explicit erase devices,
|
||||
* but can be relaxed with devices without such requirement, which means that
|
||||
* it is up to user code to make sure they are correct as the function
|
||||
* will return on, if these constrains are not met, -EINVAL for
|
||||
* paged device, but may succeed on non-explicit erase devices.
|
||||
* For RAM non-volatile devices the erase pages are emulated,
|
||||
* at this point, to allow smooth transition for code relying on
|
||||
* device being paged to function properly; but this is completely
|
||||
* software constrain.
|
||||
*
|
||||
* Generally: if your code previously required device to be erase
|
||||
* prior to some actions to work, replace flash_erase calls with this
|
||||
* function; but if your code can work with non-volatile RAM type devices,
|
||||
* without emulating erase, you should rather have different path
|
||||
* of execution for page-erase, i.e. Flash, devices and call
|
||||
* flash_erase for them.
|
||||
*
|
||||
* @param dev : flash device
|
||||
* @param offset : erase area starting offset
|
||||
* @param size : size of area to be erased
|
||||
*
|
||||
* @return 0 on success, negative errno code on fail.
|
||||
*
|
||||
* @see flash_erase()
|
||||
*/
|
||||
__syscall int flash_flatten(const struct device *dev, off_t offset, size_t size);
|
||||
|
||||
struct flash_pages_info {
|
||||
off_t start_offset; /* offset from the base of flash address */
|
||||
size_t size;
|
||||
|
|
|
@ -180,4 +180,78 @@ ZTEST(flash_driver, test_read_unaligned_address)
|
|||
}
|
||||
}
|
||||
|
||||
ZTEST(flash_driver, test_flash_fill)
|
||||
{
|
||||
uint8_t buf[EXPECTED_SIZE];
|
||||
int rc;
|
||||
off_t i;
|
||||
|
||||
if (IS_ENABLED(CONFIG_FLASH_HAS_EXPLICIT_ERASE) && ebw_required) {
|
||||
/* Erase a nb of pages aligned to the EXPECTED_SIZE */
|
||||
rc = flash_erase(flash_dev, page_info.start_offset,
|
||||
(page_info.size *
|
||||
((EXPECTED_SIZE + page_info.size - 1)
|
||||
/ page_info.size)));
|
||||
|
||||
zassert_equal(rc, 0, "Flash memory not properly erased");
|
||||
} else {
|
||||
rc = flash_fill(flash_dev, 0x55, page_info.start_offset,
|
||||
(page_info.size *
|
||||
((EXPECTED_SIZE + page_info.size - 1)
|
||||
/ page_info.size)));
|
||||
zassert_equal(rc, 0, "Leveling memory with fill failed\n");
|
||||
}
|
||||
|
||||
/* Fill the device with 0xaa */
|
||||
rc = flash_fill(flash_dev, 0xaa, page_info.start_offset,
|
||||
(page_info.size *
|
||||
((EXPECTED_SIZE + page_info.size - 1)
|
||||
/ page_info.size)));
|
||||
zassert_equal(rc, 0, "Fill failed\n");
|
||||
|
||||
rc = flash_read(flash_dev, TEST_AREA_OFFSET,
|
||||
buf, EXPECTED_SIZE);
|
||||
zassert_equal(rc, 0, "Cannot read flash");
|
||||
|
||||
for (i = 0; i < EXPECTED_SIZE; i++) {
|
||||
if (buf[i] != 0xaa) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
zassert_equal(i, EXPECTED_SIZE, "Expected device to be filled wth 0xaa");
|
||||
}
|
||||
|
||||
ZTEST(flash_driver, test_flash_flatten)
|
||||
{
|
||||
uint8_t buf[EXPECTED_SIZE];
|
||||
int rc;
|
||||
off_t i;
|
||||
|
||||
rc = flash_flatten(flash_dev, page_info.start_offset,
|
||||
(page_info.size *
|
||||
((EXPECTED_SIZE + page_info.size - 1)
|
||||
/ page_info.size)));
|
||||
|
||||
zassert_equal(rc, 0, "Flash not leveled not properly erased");
|
||||
|
||||
/* Fill the device with 0xaa */
|
||||
rc = flash_fill(flash_dev, 0xaa, page_info.start_offset,
|
||||
(page_info.size *
|
||||
((EXPECTED_SIZE + page_info.size - 1)
|
||||
/ page_info.size)));
|
||||
zassert_equal(rc, 0, "Fill failed\n");
|
||||
|
||||
rc = flash_read(flash_dev, TEST_AREA_OFFSET,
|
||||
buf, EXPECTED_SIZE);
|
||||
zassert_equal(rc, 0, "Cannot read flash");
|
||||
|
||||
for (i = 0; i < EXPECTED_SIZE; i++) {
|
||||
if (buf[i] != 0xaa) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
zassert_equal(i, EXPECTED_SIZE, "Expected device to be filled wth 0xaa");
|
||||
}
|
||||
|
||||
|
||||
ZTEST_SUITE(flash_driver, NULL, flash_driver_setup, NULL, NULL, NULL);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue