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
|
# 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
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"
|
* 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;
|
|
||||||
|
|
||||||
rc = api->erase(dev, offset, size);
|
if (api->erase != NULL) {
|
||||||
|
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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue