diff --git a/drivers/flash/Kconfig.simulator b/drivers/flash/Kconfig.simulator index fd75479b31682..e86f7a9517742 100644 --- a/drivers/flash/Kconfig.simulator +++ b/drivers/flash/Kconfig.simulator @@ -14,6 +14,14 @@ menuconfig FLASH_SIMULATOR if FLASH_SIMULATOR +config FLASH_SIMULATOR_CALLBACKS + bool "Provide callback mechanism" + help + If selected the flash_simulator_set_callbacks is implemented allowing + to change write and erase functions behavior. + This option is meant to be used with tests when checking module reaction + to different write/erase errors in the memory. + config FLASH_SIMULATOR_UNALIGNED_READ bool "Allow read access to be unaligned" default y diff --git a/drivers/flash/flash_simulator.c b/drivers/flash/flash_simulator.c index e9795d68440e1..31441bd004c84 100644 --- a/drivers/flash/flash_simulator.c +++ b/drivers/flash/flash_simulator.c @@ -17,6 +17,8 @@ #include #include +#include + #ifdef CONFIG_ARCH_POSIX #include "flash_simulator_native.h" @@ -170,6 +172,26 @@ static const struct flash_parameters flash_sim_parameters = { }, }; + +#ifdef CONFIG_FLASH_SIMULATOR_CALLBACKS +#ifdef CONFIG_ARCH_POSIX +static struct flash_simulator_params flash_sim_params = { +#else +static const struct flash_simulator_params flash_sim_params = { + .memory = mock_flash, +#endif + .memory_size = FLASH_SIMULATOR_FLASH_SIZE, + .base_offset = FLASH_SIMULATOR_BASE_OFFSET, + .erase_unit = FLASH_SIMULATOR_ERASE_UNIT, + .prog_unit = FLASH_SIMULATOR_PROG_UNIT, + .explicit_erase = IS_ENABLED(CONFIG_FLASH_SIMULATOR_EXPLICIT_ERASE), + .erase_value = FLASH_SIMULATOR_ERASE_VALUE, +}; + +static const struct flash_simulator_cb *flash_simulator_cbs; +#endif /* CONFIG_FLASH_SIMULATOR_CALLBACKS */ + + static int flash_range_is_valid(const struct device *dev, off_t offset, size_t len) { @@ -231,7 +253,7 @@ static int flash_sim_write(const struct device *dev, const off_t offset, FLASH_SIM_STATS_INC(flash_sim_stats, flash_write_calls); -#if defined(CONFIG_FLASH_SIMULATOR_EXPLICIT_ERASE) +#ifdef CONFIG_FLASH_SIMULATOR_EXPLICIT_ERASE /* check if any unit has been already programmed */ memset(buf, FLASH_SIMULATOR_ERASE_VALUE, sizeof(buf)); #else @@ -264,6 +286,15 @@ static int flash_sim_write(const struct device *dev, const off_t offset, } #endif +#ifdef CONFIG_FLASH_SIMULATOR_CALLBACKS + flash_simulator_write_byte_cb_t write_cb = NULL; + const struct flash_simulator_cb *cb = flash_simulator_cbs; + + if (cb != NULL) { + write_cb = cb->write_byte; + } +#endif /* CONFIG_FLASH_SIMULATOR_CALLBACKS */ + for (uint32_t i = 0; i < len; i++) { #ifdef CONFIG_FLASH_SIMULATOR_STATS if (data_part_ignored) { @@ -273,16 +304,28 @@ static int flash_sim_write(const struct device *dev, const off_t offset, } #endif /* CONFIG_FLASH_SIMULATOR_STATS */ - /* only pull bits to zero */ -#if defined(CONFIG_FLASH_SIMULATOR_EXPLICIT_ERASE) + uint8_t data_val = *((const uint8_t *)data + i); + +#ifdef CONFIG_FLASH_SIMULATOR_EXPLICIT_ERASE #if FLASH_SIMULATOR_ERASE_VALUE == 0xFF - *(MOCK_FLASH(offset + i)) &= *((uint8_t *)data + i); + /* only pull bits to zero */ + data_val &= *(MOCK_FLASH(offset + i)); #else - *(MOCK_FLASH(offset + i)) |= *((uint8_t *)data + i); + /* only pull bits to one */ + data_val |= *(MOCK_FLASH(offset + i)); #endif -#else - *(MOCK_FLASH(offset + i)) = *((uint8_t *)data + i); #endif +#ifdef CONFIG_FLASH_SIMULATOR_CALLBACKS + if (write_cb != NULL) { + int ret = write_cb(dev, &flash_sim_params, + offset + i, data_val); + if (ret < 0) { + return ret; + } + data_val = (uint8_t)ret; + } +#endif /* CONFIG_FLASH_SIMULATOR_CALLBACKS */ + *(MOCK_FLASH(offset + i)) = data_val; } FLASH_SIM_STATS_INCN(flash_sim_stats, bytes_written, len); @@ -297,20 +340,31 @@ static int flash_sim_write(const struct device *dev, const off_t offset, return 0; } -static void unit_erase(const uint32_t unit) +static int unit_erase(const struct device *dev, const uint32_t unit) { const off_t unit_addr = unit * FLASH_SIMULATOR_ERASE_UNIT; +#ifdef CONFIG_FLASH_SIMULATOR_CALLBACKS + flash_simulator_erase_unit_cb_t erase_cb = NULL; + const struct flash_simulator_cb *cb = flash_simulator_cbs; + + if (cb != NULL) { + erase_cb = cb->erase_unit; + } + if (erase_cb != NULL) { + return erase_cb(dev, &flash_sim_params, unit_addr); + } +#endif /* CONFIG_FLASH_SIMULATOR_CALLBACKS */ + /* erase the memory unit by setting it to erase value */ memset(MOCK_FLASH(unit_addr), FLASH_SIMULATOR_ERASE_VALUE, FLASH_SIMULATOR_ERASE_UNIT); + return 0; } static int flash_sim_erase(const struct device *dev, const off_t offset, const size_t len) { - ARG_UNUSED(dev); - if (!flash_range_is_valid(dev, offset, len)) { return -EINVAL; } @@ -335,8 +389,13 @@ static int flash_sim_erase(const struct device *dev, const off_t offset, /* erase as many units as necessary and increase their erase counter */ for (uint32_t i = 0; i < len / FLASH_SIMULATOR_ERASE_UNIT; i++) { + int ret; + ERASE_CYCLES_INC(unit_start + i); - unit_erase(unit_start + i); + ret = unit_erase(dev, unit_start + i); + if (ret < 0) { + return ret; + } } #ifdef CONFIG_FLASH_SIMULATOR_SIMULATE_TIMING @@ -405,6 +464,9 @@ static int flash_mock_init(const struct device *dev) rc = flash_mock_init_native(flash_in_ram, &mock_flash, FLASH_SIMULATOR_FLASH_SIZE, &flash_fd, flash_file_path, FLASH_SIMULATOR_ERASE_VALUE, flash_erase_at_start); +#ifdef CONFIG_FLASH_SIMULATOR_CALLBACKS + flash_sim_params.memory = mock_flash; +#endif if (rc < 0) { return -EIO; @@ -498,6 +560,15 @@ void *z_impl_flash_simulator_get_memory(const struct device *dev, return mock_flash; } +#ifdef CONFIG_FLASH_SIMULATOR_CALLBACKS +void z_impl_flash_simulator_set_callbacks(const struct device *dev, + const struct flash_simulator_cb *cb) +{ + ARG_UNUSED(dev); + flash_simulator_cbs = cb; +} +#endif /* CONFIG_FLASH_SIMULATOR_CALLBACKS */ + #ifdef CONFIG_USERSPACE #include @@ -510,6 +581,14 @@ void *z_vrfy_flash_simulator_get_memory(const struct device *dev, return z_impl_flash_simulator_get_memory(dev, mock_size); } +void z_vrfy_flash_simulator_set_callbacks(const struct device *dev, + const struct flash_simulator_cb *cb) +{ + K_OOPS(K_SYSCALL_SPECIFIC_DRIVER(dev, K_OBJ_DRIVER_FLASH, &flash_sim_api)); + + z_impl_flash_simulator_set_callbacks(dev, cb); +} + #include #endif /* CONFIG_USERSPACE */ diff --git a/include/zephyr/drivers/flash/flash_simulator.h b/include/zephyr/drivers/flash/flash_simulator.h index e1a201b62b3a4..18e3cddb5d81c 100644 --- a/include/zephyr/drivers/flash/flash_simulator.h +++ b/include/zephyr/drivers/flash/flash_simulator.h @@ -10,6 +10,10 @@ extern "C" { #endif +#include +#include +#include + /** * @file * @brief Flash simulator specific API. @@ -17,6 +21,99 @@ extern "C" { * Extension for flash simulator. */ +/** + * @brief Flash simulator parameters structure + * + * Auxiliary structure containing flash simulator parameters to be used by callbacks + * that can modify the behavior of the flash simulator driver. + */ +struct flash_simulator_params { + uint8_t *memory; /**< @brief The memory pointer */ + size_t memory_size; /**< @brief The total size of the memory */ + size_t base_offset; /**< @brief The base offset of the flash simulator */ + size_t erase_unit; /**< @brief The erase unit size */ + size_t prog_unit; /**< @brief The program unit size */ + bool explicit_erase; /**< @brief Whether explicit erase is required */ + uint8_t erase_value; /**< @brief The value used to represent erased memory */ +}; + +/** + * @brief Flash simulator write byte callback type + * + * Callback used to overwrite single byte write. The callback gets the requested data + * and offset as the parameter. The offset is the same offset passed to the flash write. + * The callback must return a value to be written at the specified offset or negative value + * in case of error. + * + * @param dev Flash simulator device pointer. + * @param params Flash simulator parameters. + * @param offset Offset within the flash simulator memory. + * @param data Data byte to be written. + * @return Value to be written at the specified offset or negative value in case of error. + */ +typedef int (*flash_simulator_write_byte_cb_t)(const struct device *dev, + const struct flash_simulator_params *params, + off_t offset, + const uint8_t data); + +/** + * @brief Flash simulator erase unit callback type + * + * Callback used to overwrite unit erase operation. The callback gets the unit offset + * as the parameter. The offset is the same offset passed to the flash erase. The callback + * must return 0 in case of success or negative value in case of error. + * + * @note If this callback is set, the flash simulator driver will not perform any erase operation + * by itself. + * + * @param dev Flash simulator device pointer. + * @param params Flash simulator parameters. + * @param unit_offset Offset within the flash simulator memory. + * @return 0 in case of success or negative value in case of error. + */ +typedef int (*flash_simulator_erase_unit_cb_t)(const struct device *dev, + const struct flash_simulator_params *params, + off_t unit_offset); + +/** + * @brief Flash simulator callbacks structure + * + * Structure containing flash simulator operation callbacks. + */ +struct flash_simulator_cb { + flash_simulator_write_byte_cb_t write_byte; /**< @brief Byte write callback */ + flash_simulator_erase_unit_cb_t erase_unit; /**< @brief Unit erase callback */ +}; + +/** + * @brief Get pointer to the flash simulator mock flash memory + * + * This function allows the caller to get the address of the RAM buffer + * in which the flash simulator emulates its flash memory content. + * + * @param[in] params flash simulator parameters. + * @param[in] offset offset within the flash simulator memory. + * + * @retval pointer to the ram buffer at specified offset + */ +static inline uint8_t *flash_simulator_mock_flash(const struct flash_simulator_params *params, + off_t offset) +{ + return params->memory + offset; +} + +/** + * @brief Set flash simulator callbacks + * + * This function allows the caller to set custom callbacks for byte write and + * unit erase operations performed by the flash simulator. + * + * @param[in] dev flash simulator device pointer. + * @param[in] cb pointer to the structure containing the callbacks. + */ +__syscall void flash_simulator_set_callbacks(const struct device *dev, + const struct flash_simulator_cb *cb); + /** * @brief Obtain a pointer to the RAM buffer used but by the simulator * diff --git a/tests/drivers/flash_simulator/flash_sim_impl/prj.conf b/tests/drivers/flash_simulator/flash_sim_impl/prj.conf index c9fb30e0402fb..d8a60c274c0c5 100644 --- a/tests/drivers/flash_simulator/flash_sim_impl/prj.conf +++ b/tests/drivers/flash_simulator/flash_sim_impl/prj.conf @@ -1,5 +1,6 @@ CONFIG_ZTEST=y CONFIG_FLASH=y CONFIG_FLASH_SIMULATOR=y +CONFIG_FLASH_SIMULATOR_CALLBACKS=y CONFIG_FLASH_SIMULATOR_DOUBLE_WRITES=n CONFIG_FLASH_SIMULATOR_UNALIGNED_READ=n diff --git a/tests/drivers/flash_simulator/flash_sim_impl/src/callbacks.c b/tests/drivers/flash_simulator/flash_sim_impl/src/callbacks.c new file mode 100644 index 0000000000000..b272bd63193b1 --- /dev/null +++ b/tests/drivers/flash_simulator/flash_sim_impl/src/callbacks.c @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2025 Koppel Electronic + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include + +/* Test the flash simulator callbacks to modify the behaviour of the + * flash simulator on the fly. + */ + +/* configuration derived from DT */ +#ifdef CONFIG_ARCH_POSIX +#define SOC_NV_FLASH_NODE DT_CHILD(DT_INST(0, zephyr_sim_flash), flash_0) +#else +#define SOC_NV_FLASH_NODE DT_CHILD(DT_INST(0, zephyr_sim_flash), flash_sim_0) +#endif /* CONFIG_ARCH_POSIX */ +#define FLASH_SIMULATOR_BASE_OFFSET DT_REG_ADDR(SOC_NV_FLASH_NODE) +#define FLASH_SIMULATOR_ERASE_UNIT DT_PROP(SOC_NV_FLASH_NODE, erase_block_size) +#define FLASH_SIMULATOR_PROG_UNIT DT_PROP(SOC_NV_FLASH_NODE, write_block_size) +#define FLASH_SIMULATOR_FLASH_SIZE DT_REG_SIZE(SOC_NV_FLASH_NODE) + +#define FLASH_SIMULATOR_ERASE_VALUE \ + DT_PROP(DT_PARENT(SOC_NV_FLASH_NODE), erase_value) + +#if (defined(CONFIG_ARCH_POSIX) || defined(CONFIG_BOARD_QEMU_X86)) +static const struct device *const flash_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_flash_controller)); +#else +static const struct device *const flash_dev = DEVICE_DT_GET(DT_NODELABEL(sim_flash_controller)); +#endif + + +/* We are simulating the broken FLASH memory. + * Simulate different types of errors depending on the erase page. + * Page 0: behaves normal + * Page 1: erase works as expected, all writes fails (no write, -EIO returned) + * Page 2: erase works as expected, all writes silently corrupt data (bits 0, 1, 20 in sticks to 1) + * Page 3: erase fails (no erase, -EIO returned), all writes fails (-EIO returned) + * Page 4: erase fails silently (erases writes 0x55 to all bytes), all writes fails (-EIO returned) + */ +enum test_page_type { + TEST_PAGE_TYPE_NORMAL, + TEST_PAGE_TYPE_ERASE_OK_WRITE_FAIL, + TEST_PAGE_TYPE_ERASE_OK_WRITE_CORRUPT, + TEST_PAGE_TYPE_ERASE_FAIL_WRITE_FAIL, + TEST_PAGE_TYPE_ERASE_CORRUPT_WRITE_FAIL, +}; + +/* Corruption pattern for write operations: bits 0, 1, and 20 in 32-bit word */ +static const uint32_t test_write_corruption_pattern = 0x00100003; + +static int test_write_byte_callback(const struct device *dev, + const struct flash_simulator_params *params, + off_t offset, + const uint8_t data) +{ + enum test_page_type page = (offset - params->base_offset) / + params->erase_unit; + + switch (page) { + case TEST_PAGE_TYPE_ERASE_OK_WRITE_FAIL: + case TEST_PAGE_TYPE_ERASE_FAIL_WRITE_FAIL: + case TEST_PAGE_TYPE_ERASE_CORRUPT_WRITE_FAIL: + return -EIO; + case TEST_PAGE_TYPE_ERASE_OK_WRITE_CORRUPT: { + /* Apply corruption pattern based on byte position within word */ + uint8_t byte_in_word = offset & 0x3; + uint8_t corruption_byte = + (test_write_corruption_pattern >> (byte_in_word * 8)) & 0xFF; + + return data | corruption_byte; + } + case TEST_PAGE_TYPE_NORMAL: + default: + return data; + } +} + +static int test_erase_unit_callback(const struct device *dev, + const struct flash_simulator_params *params, + off_t offset) +{ + enum test_page_type page = (offset - params->base_offset) / + params->erase_unit; + + switch (page) { + case TEST_PAGE_TYPE_ERASE_FAIL_WRITE_FAIL: + return -EIO; + case TEST_PAGE_TYPE_ERASE_CORRUPT_WRITE_FAIL: + /* Corrupt erase by writing 0x55 to all bytes */ + memset(flash_simulator_mock_flash(params, offset), + 0x55, params->erase_unit); + return 0; + case TEST_PAGE_TYPE_NORMAL: + case TEST_PAGE_TYPE_ERASE_OK_WRITE_FAIL: + case TEST_PAGE_TYPE_ERASE_OK_WRITE_CORRUPT: + default: + /* Normal erase behavior - callback must perform the erase */ + memset(flash_simulator_mock_flash(params, offset), + params->erase_value, params->erase_unit); + return 0; + } +} + +static struct flash_simulator_cb test_flash_sim_cbs = { + .write_byte = test_write_byte_callback, + .erase_unit = test_erase_unit_callback, +}; + +void *flash_sim_cbs_setup(void) +{ + zassert_true(device_is_ready(flash_dev), + "Simulated flash device not ready"); + + flash_simulator_set_callbacks(flash_dev, &test_flash_sim_cbs); + + return NULL; +} + +/* Disable callbacks after tests to restore default simulator behavior */ +static void flash_sim_cbs_teardown(void *fixture) +{ + ARG_UNUSED(fixture); + flash_simulator_set_callbacks(flash_dev, NULL); +} + +ZTEST(flash_sim_cbs, test_page_behaviors) +{ + int ret; + uint32_t write_buf[FLASH_SIMULATOR_ERASE_UNIT / sizeof(uint32_t)]; + uint32_t read_buf[FLASH_SIMULATOR_ERASE_UNIT / sizeof(uint32_t)]; + off_t page_offset; + size_t buf_size = sizeof(write_buf); + + /* Initialize write buffer with pseudo-random pattern */ + srand(0x12345678); + for (size_t i = 0; i < ARRAY_SIZE(write_buf); i++) { + write_buf[i] = ((uint32_t)rand() << 16) | (uint32_t)rand(); + } + + /* Test Page 0: TEST_PAGE_TYPE_NORMAL - behaves normal */ + page_offset = TEST_PAGE_TYPE_NORMAL * FLASH_SIMULATOR_ERASE_UNIT; + + ret = flash_erase(flash_dev, page_offset, FLASH_SIMULATOR_ERASE_UNIT); + zassert_equal(ret, 0, "Page 0: Erase should succeed"); + + ret = flash_write(flash_dev, page_offset, write_buf, buf_size); + zassert_equal(ret, 0, "Page 0: Write should succeed"); + + ret = flash_read(flash_dev, page_offset, read_buf, buf_size); + zassert_equal(ret, 0, "Page 0: Read should succeed"); + zassert_mem_equal(read_buf, write_buf, buf_size, + "Page 0: Data should match written data"); + + /* Test Page 1: TEST_PAGE_TYPE_ERASE_OK_WRITE_FAIL - erase OK, write fails */ + page_offset = TEST_PAGE_TYPE_ERASE_OK_WRITE_FAIL * FLASH_SIMULATOR_ERASE_UNIT; + + ret = flash_erase(flash_dev, page_offset, FLASH_SIMULATOR_ERASE_UNIT); + zassert_equal(ret, 0, "Page 1: Erase should succeed"); + + ret = flash_read(flash_dev, page_offset, read_buf, buf_size); + zassert_equal(ret, 0, "Page 1: Read after erase should succeed"); + for (size_t i = 0; i < ARRAY_SIZE(read_buf); i++) { + uint32_t expected = FLASH_SIMULATOR_ERASE_VALUE; + + expected |= (expected << 8) | (expected << 16) | (expected << 24); + zassert_equal(read_buf[i], expected, + "Page 1: After erase, data should be erase value"); + } + + ret = flash_write(flash_dev, page_offset, write_buf, buf_size); + zassert_equal(ret, -EIO, "Page 1: Write should fail with -EIO"); + + /* Test Page 2: TEST_PAGE_TYPE_ERASE_OK_WRITE_CORRUPT - erase OK, write corrupts */ + page_offset = TEST_PAGE_TYPE_ERASE_OK_WRITE_CORRUPT * FLASH_SIMULATOR_ERASE_UNIT; + + ret = flash_erase(flash_dev, page_offset, FLASH_SIMULATOR_ERASE_UNIT); + zassert_equal(ret, 0, "Page 2: Erase should succeed"); + + ret = flash_write(flash_dev, page_offset, write_buf, buf_size); + zassert_equal(ret, 0, "Page 2: Write should succeed (but corrupt data)"); + + ret = flash_read(flash_dev, page_offset, read_buf, buf_size); + zassert_equal(ret, 0, "Page 2: Read should succeed"); + + /* Verify corruption pattern: bits 0, 1, and 20 should be set */ + for (size_t i = 0; i < ARRAY_SIZE(read_buf); i++) { + uint32_t expected = write_buf[i] | test_write_corruption_pattern; + + zassert_equal(read_buf[i], expected, + "Page 2: Data should be corrupted with pattern 0x%08x", + test_write_corruption_pattern); + } + + /* Test Page 3: TEST_PAGE_TYPE_ERASE_FAIL_WRITE_FAIL - both fail */ + page_offset = TEST_PAGE_TYPE_ERASE_FAIL_WRITE_FAIL * FLASH_SIMULATOR_ERASE_UNIT; + + ret = flash_erase(flash_dev, page_offset, FLASH_SIMULATOR_ERASE_UNIT); + zassert_equal(ret, -EIO, "Page 3: Erase should fail with -EIO"); + + ret = flash_write(flash_dev, page_offset, write_buf, buf_size); + zassert_equal(ret, -EIO, "Page 3: Write should fail with -EIO"); + + /* Test Page 4: TEST_PAGE_TYPE_ERASE_CORRUPT_WRITE_FAIL - erase corrupts, write fails */ + page_offset = TEST_PAGE_TYPE_ERASE_CORRUPT_WRITE_FAIL * FLASH_SIMULATOR_ERASE_UNIT; + + ret = flash_erase(flash_dev, page_offset, FLASH_SIMULATOR_ERASE_UNIT); + zassert_equal(ret, 0, "Page 4: Erase should succeed (but corrupt)"); + + ret = flash_read(flash_dev, page_offset, read_buf, buf_size); + zassert_equal(ret, 0, "Page 4: Read after erase should succeed"); + for (size_t i = 0; i < ARRAY_SIZE(read_buf); i++) { + zassert_equal(read_buf[i], 0x55555555, + "Page 4: After erase, data should be 0x55555555 (corrupted erase)"); + } + + ret = flash_write(flash_dev, page_offset, write_buf, buf_size); + zassert_equal(ret, -EIO, "Page 4: Write should fail with -EIO"); +} + +ZTEST_SUITE(flash_sim_cbs, NULL, flash_sim_cbs_setup, NULL, NULL, flash_sim_cbs_teardown);