From b8d073c572057354e3865f9aeafdad76eb6b3fb4 Mon Sep 17 00:00:00 2001 From: Dominik Ermel Date: Thu, 7 Mar 2024 22:42:41 +0000 Subject: [PATCH] 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 --- drivers/flash/CMakeLists.txt | 2 + drivers/flash/Kconfig | 13 +++++ drivers/flash/flash_handlers.c | 16 ++++++ drivers/flash/flash_util.c | 82 +++++++++++++++++++++++++++ include/zephyr/drivers/flash.h | 60 +++++++++++++++++++- tests/drivers/flash/common/src/main.c | 74 ++++++++++++++++++++++++ 6 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 drivers/flash/flash_util.c diff --git a/drivers/flash/CMakeLists.txt b/drivers/flash/CMakeLists.txt index 54b9178c091..744a6a5862c 100644 --- a/drivers/flash/CMakeLists.txt +++ b/drivers/flash/CMakeLists.txt @@ -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 diff --git a/drivers/flash/Kconfig b/drivers/flash/Kconfig index 2762cd913f2..4816db0b580 100644 --- a/drivers/flash/Kconfig +++ b/drivers/flash/Kconfig @@ -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 diff --git a/drivers/flash/flash_handlers.c b/drivers/flash/flash_handlers.c index 6e47b10dbd3..338c6c8402d 100644 --- a/drivers/flash/flash_handlers.c +++ b/drivers/flash/flash_handlers.c @@ -50,6 +50,22 @@ static inline const struct flash_parameters *z_vrfy_flash_get_parameters(const s } #include +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 + +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 + #ifdef CONFIG_FLASH_PAGE_LAYOUT static inline int z_vrfy_flash_get_page_info_by_offs(const struct device *dev, off_t offs, diff --git a/drivers/flash/flash_util.c b/drivers/flash/flash_util.c new file mode 100644 index 00000000000..ee3b55798a5 --- /dev/null +++ b/drivers/flash/flash_util.c @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include + +#include +#include +#include + +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 +} diff --git a/include/zephyr/drivers/flash.h b/include/zephyr/drivers/flash.h index ecc0f4d0e29..7ceea4a5b25 100644 --- a/include/zephyr/drivers/flash.h +++ b/include/zephyr/drivers/flash.h @@ -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; diff --git a/tests/drivers/flash/common/src/main.c b/tests/drivers/flash/common/src/main.c index 8b6bb80ecab..4eeb4920993 100644 --- a/tests/drivers/flash/common/src/main.c +++ b/tests/drivers/flash/common/src/main.c @@ -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);