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:
Dominik Ermel 2024-03-07 22:42:41 +00:00 committed by Henrik Brix Andersen
commit b8d073c572
6 changed files with 245 additions and 2 deletions

View file

@ -1,5 +1,7 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
zephyr_library_sources(flash_util.c)
zephyr_syscall_header_ifdef( zephyr_syscall_header_ifdef(
CONFIG_FLASH_SIMULATOR CONFIG_FLASH_SIMULATOR
${ZEPHYR_BASE}/include/zephyr/drivers/flash/flash_simulator.h ${ZEPHYR_BASE}/include/zephyr/drivers/flash/flash_simulator.h

View file

@ -127,6 +127,19 @@ config FLASH_SHELL_BUFFER_SIZE
endif # FLASH_SHELL 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 if FLASH_HAS_PAGE_LAYOUT

View file

@ -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> #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 #ifdef CONFIG_FLASH_PAGE_LAYOUT
static inline int z_vrfy_flash_get_page_info_by_offs(const struct device *dev, static inline int z_vrfy_flash_get_page_info_by_offs(const struct device *dev,
off_t offs, off_t offs,

View 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
}

View file

@ -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" * the driver, with the driver responsible for ensuring the "erase-protect"
* after the operation completes (successfully or not) matches the erase-protect * after the operation completes (successfully or not) matches the erase-protect
* state when the operation was started. * 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, typedef int (*flash_api_erase)(const struct device *dev, off_t offset,
size_t size); 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 * Any necessary erase protection management is performed by the driver
* erase implementation itself. * 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 dev : flash device
* @param offset : erase area starting offset * @param offset : erase area starting offset
* @param size : size of area to be erased * @param size : size of area to be erased
* *
* @return 0 on success, negative errno code on fail. * @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_offs()
* @see flash_get_page_info_by_idx() * @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, static inline int z_impl_flash_erase(const struct device *dev, off_t offset,
size_t size) size_t size)
{ {
int rc = -ENOSYS;
const struct flash_driver_api *api = const struct flash_driver_api *api =
(const struct flash_driver_api *)dev->api; (const struct flash_driver_api *)dev->api;
int rc;
if (api->erase != NULL) {
rc = api->erase(dev, offset, size); rc = api->erase(dev, offset, size);
}
return rc; 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 { struct flash_pages_info {
off_t start_offset; /* offset from the base of flash address */ off_t start_offset; /* offset from the base of flash address */
size_t size; size_t size;

View file

@ -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); ZTEST_SUITE(flash_driver, NULL, flash_driver_setup, NULL, NULL, NULL);