1 /*
2  * Copyright (c) 2025 Koppel Electronic
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 #include <stdlib.h>
7 #include <zephyr/ztest.h>
8 #include <zephyr/drivers/flash.h>
9 #include <zephyr/drivers/flash/flash_simulator.h>
10 #include <zephyr/device.h>
11 
12 /* Test the flash simulator callbacks to modify the behaviour of the
13  * flash simulator on the fly.
14  */
15 
16 /* configuration derived from DT */
17 #ifdef CONFIG_ARCH_POSIX
18 #define SOC_NV_FLASH_NODE DT_CHILD(DT_INST(0, zephyr_sim_flash), flash_0)
19 #else
20 #define SOC_NV_FLASH_NODE DT_CHILD(DT_INST(0, zephyr_sim_flash), flash_sim_0)
21 #endif /* CONFIG_ARCH_POSIX */
22 #define FLASH_SIMULATOR_BASE_OFFSET DT_REG_ADDR(SOC_NV_FLASH_NODE)
23 #define FLASH_SIMULATOR_ERASE_UNIT DT_PROP(SOC_NV_FLASH_NODE, erase_block_size)
24 #define FLASH_SIMULATOR_PROG_UNIT DT_PROP(SOC_NV_FLASH_NODE, write_block_size)
25 #define FLASH_SIMULATOR_FLASH_SIZE DT_REG_SIZE(SOC_NV_FLASH_NODE)
26 
27 #define FLASH_SIMULATOR_ERASE_VALUE \
28 		DT_PROP(DT_PARENT(SOC_NV_FLASH_NODE), erase_value)
29 
30 #if (defined(CONFIG_ARCH_POSIX) || defined(CONFIG_BOARD_QEMU_X86))
31 static const struct device *const flash_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_flash_controller));
32 #else
33 static const struct device *const flash_dev = DEVICE_DT_GET(DT_NODELABEL(sim_flash_controller));
34 #endif
35 
36 
37 /* We are simulating the broken FLASH memory.
38  * Simulate different types of errors depending on the erase page.
39  * Page 0: behaves normal
40  * Page 1: erase works as expected, all writes fails (no write, -EIO returned)
41  * Page 2: erase works as expected, all writes silently corrupt data (bits 0, 1, 20 in sticks to 1)
42  * Page 3: erase fails (no erase, -EIO returned), all writes fails (-EIO returned)
43  * Page 4: erase fails silently (erases writes 0x55 to all bytes), all writes fails (-EIO returned)
44  */
45 enum test_page_type {
46 	TEST_PAGE_TYPE_NORMAL,
47 	TEST_PAGE_TYPE_ERASE_OK_WRITE_FAIL,
48 	TEST_PAGE_TYPE_ERASE_OK_WRITE_CORRUPT,
49 	TEST_PAGE_TYPE_ERASE_FAIL_WRITE_FAIL,
50 	TEST_PAGE_TYPE_ERASE_CORRUPT_WRITE_FAIL,
51 };
52 
53 /* Corruption pattern for write operations: bits 0, 1, and 20 in 32-bit word */
54 static const uint32_t test_write_corruption_pattern = 0x00100003;
55 
test_write_byte_callback(const struct device * dev,off_t offset,uint8_t data)56 static int test_write_byte_callback(const struct device *dev, off_t offset, uint8_t data)
57 {
58 	const struct flash_simulator_params *params = flash_simulator_get_params(dev);
59 	enum test_page_type page = offset / params->erase_unit;
60 
61 	switch (page) {
62 	case TEST_PAGE_TYPE_ERASE_OK_WRITE_FAIL:
63 	case TEST_PAGE_TYPE_ERASE_FAIL_WRITE_FAIL:
64 	case TEST_PAGE_TYPE_ERASE_CORRUPT_WRITE_FAIL:
65 		return -EIO;
66 	case TEST_PAGE_TYPE_ERASE_OK_WRITE_CORRUPT: {
67 		/* Apply corruption pattern based on byte position within word */
68 		uint8_t byte_in_word = offset & 0x3;
69 		uint8_t corruption_byte =
70 			(test_write_corruption_pattern >> (byte_in_word * 8)) & 0xFF;
71 
72 		return data | corruption_byte;
73 	}
74 	case TEST_PAGE_TYPE_NORMAL:
75 	default:
76 		return data;
77 	}
78 }
79 
test_erase_unit_callback(const struct device * dev,off_t offset)80 static int test_erase_unit_callback(const struct device *dev, off_t offset)
81 {
82 	const struct flash_simulator_params *params = flash_simulator_get_params(dev);
83 	enum test_page_type page = offset / params->erase_unit;
84 	size_t flash_size;
85 	uint8_t *flash_mock = flash_simulator_get_memory(dev, &flash_size);
86 
87 	zassert_true(offset < flash_size, "Offset 0x%lx out of range (flash size 0x%zx)",
88 		    (long)offset, flash_size);
89 
90 	switch (page) {
91 	case TEST_PAGE_TYPE_ERASE_FAIL_WRITE_FAIL:
92 		return -EIO;
93 	case TEST_PAGE_TYPE_ERASE_CORRUPT_WRITE_FAIL:
94 		/* Corrupt erase by writing 0x55 to all bytes */
95 		memset(flash_mock + offset, 0x55, params->erase_unit);
96 		return 0;
97 	case TEST_PAGE_TYPE_NORMAL:
98 	case TEST_PAGE_TYPE_ERASE_OK_WRITE_FAIL:
99 	case TEST_PAGE_TYPE_ERASE_OK_WRITE_CORRUPT:
100 	default:
101 		/* Normal erase behavior - callback must perform the erase */
102 		memset(flash_mock + offset, params->erase_value, params->erase_unit);
103 		return 0;
104 	}
105 }
106 
107 static struct flash_simulator_cb test_flash_sim_cbs = {
108 	.write_byte = test_write_byte_callback,
109 	.erase_unit = test_erase_unit_callback,
110 };
111 
flash_sim_cbs_setup(void)112 void *flash_sim_cbs_setup(void)
113 {
114 	zassert_true(device_is_ready(flash_dev),
115 		     "Simulated flash device not ready");
116 
117 	flash_simulator_set_callbacks(flash_dev, &test_flash_sim_cbs);
118 
119 	return NULL;
120 }
121 
122 /* Disable callbacks after tests to restore default simulator behavior */
flash_sim_cbs_teardown(void * fixture)123 static void flash_sim_cbs_teardown(void *fixture)
124 {
125 	ARG_UNUSED(fixture);
126 	flash_simulator_set_callbacks(flash_dev, NULL);
127 }
128 
ZTEST(flash_sim_cbs,test_page_behaviors)129 ZTEST(flash_sim_cbs, test_page_behaviors)
130 {
131 	int ret;
132 	uint32_t write_buf[FLASH_SIMULATOR_ERASE_UNIT / sizeof(uint32_t)];
133 	uint32_t read_buf[FLASH_SIMULATOR_ERASE_UNIT / sizeof(uint32_t)];
134 	off_t page_offset;
135 	size_t buf_size = sizeof(write_buf);
136 
137 	/* Initialize write buffer with pseudo-random pattern */
138 	srand(0x12345678);
139 	for (size_t i = 0; i < ARRAY_SIZE(write_buf); i++) {
140 		write_buf[i] = ((uint32_t)rand() << 16) | (uint32_t)rand();
141 	}
142 
143 	/* Test Page 0: TEST_PAGE_TYPE_NORMAL - behaves normal */
144 	page_offset = TEST_PAGE_TYPE_NORMAL * FLASH_SIMULATOR_ERASE_UNIT;
145 
146 	ret = flash_erase(flash_dev, page_offset, FLASH_SIMULATOR_ERASE_UNIT);
147 	zassert_equal(ret, 0, "Page 0: Erase should succeed");
148 
149 	ret = flash_write(flash_dev, page_offset, write_buf, buf_size);
150 	zassert_equal(ret, 0, "Page 0: Write should succeed");
151 
152 	ret = flash_read(flash_dev, page_offset, read_buf, buf_size);
153 	zassert_equal(ret, 0, "Page 0: Read should succeed");
154 	zassert_mem_equal(read_buf, write_buf, buf_size,
155 			  "Page 0: Data should match written data");
156 
157 	/* Test Page 1: TEST_PAGE_TYPE_ERASE_OK_WRITE_FAIL - erase OK, write fails */
158 	page_offset = TEST_PAGE_TYPE_ERASE_OK_WRITE_FAIL * FLASH_SIMULATOR_ERASE_UNIT;
159 
160 	ret = flash_erase(flash_dev, page_offset, FLASH_SIMULATOR_ERASE_UNIT);
161 	zassert_equal(ret, 0, "Page 1: Erase should succeed");
162 
163 	ret = flash_read(flash_dev, page_offset, read_buf, buf_size);
164 	zassert_equal(ret, 0, "Page 1: Read after erase should succeed");
165 	for (size_t i = 0; i < ARRAY_SIZE(read_buf); i++) {
166 		uint32_t expected = FLASH_SIMULATOR_ERASE_VALUE;
167 
168 		expected |= (expected << 8) | (expected << 16) | (expected << 24);
169 		zassert_equal(read_buf[i], expected,
170 			      "Page 1: After erase, data should be erase value");
171 	}
172 
173 	ret = flash_write(flash_dev, page_offset, write_buf, buf_size);
174 	zassert_equal(ret, -EIO, "Page 1: Write should fail with -EIO");
175 
176 	/* Test Page 2: TEST_PAGE_TYPE_ERASE_OK_WRITE_CORRUPT - erase OK, write corrupts */
177 	page_offset = TEST_PAGE_TYPE_ERASE_OK_WRITE_CORRUPT * FLASH_SIMULATOR_ERASE_UNIT;
178 
179 	ret = flash_erase(flash_dev, page_offset, FLASH_SIMULATOR_ERASE_UNIT);
180 	zassert_equal(ret, 0, "Page 2: Erase should succeed");
181 
182 	ret = flash_write(flash_dev, page_offset, write_buf, buf_size);
183 	zassert_equal(ret, 0, "Page 2: Write should succeed (but corrupt data)");
184 
185 	ret = flash_read(flash_dev, page_offset, read_buf, buf_size);
186 	zassert_equal(ret, 0, "Page 2: Read should succeed");
187 
188 	/* Verify corruption pattern: bits 0, 1, and 20 should be set */
189 	for (size_t i = 0; i < ARRAY_SIZE(read_buf); i++) {
190 		uint32_t expected = write_buf[i] | test_write_corruption_pattern;
191 
192 		zassert_equal(read_buf[i], expected,
193 			      "Page 2: Data should be corrupted with pattern 0x%08x",
194 			      test_write_corruption_pattern);
195 	}
196 
197 	/* Test Page 3: TEST_PAGE_TYPE_ERASE_FAIL_WRITE_FAIL - both fail */
198 	page_offset = TEST_PAGE_TYPE_ERASE_FAIL_WRITE_FAIL * FLASH_SIMULATOR_ERASE_UNIT;
199 
200 	ret = flash_erase(flash_dev, page_offset, FLASH_SIMULATOR_ERASE_UNIT);
201 	zassert_equal(ret, -EIO, "Page 3: Erase should fail with -EIO");
202 
203 	ret = flash_write(flash_dev, page_offset, write_buf, buf_size);
204 	zassert_equal(ret, -EIO, "Page 3: Write should fail with -EIO");
205 
206 	/* Test Page 4: TEST_PAGE_TYPE_ERASE_CORRUPT_WRITE_FAIL - erase corrupts, write fails */
207 	page_offset = TEST_PAGE_TYPE_ERASE_CORRUPT_WRITE_FAIL * FLASH_SIMULATOR_ERASE_UNIT;
208 
209 	ret = flash_erase(flash_dev, page_offset, FLASH_SIMULATOR_ERASE_UNIT);
210 	zassert_equal(ret, 0, "Page 4: Erase should succeed (but corrupt)");
211 
212 	ret = flash_read(flash_dev, page_offset, read_buf, buf_size);
213 	zassert_equal(ret, 0, "Page 4: Read after erase should succeed");
214 	for (size_t i = 0; i < ARRAY_SIZE(read_buf); i++) {
215 		zassert_equal(read_buf[i], 0x55555555,
216 			      "Page 4: After erase, data should be 0x55555555 (corrupted erase)");
217 	}
218 
219 	ret = flash_write(flash_dev, page_offset, write_buf, buf_size);
220 	zassert_equal(ret, -EIO, "Page 4: Write should fail with -EIO");
221 }
222 
223 ZTEST_SUITE(flash_sim_cbs, NULL, flash_sim_cbs_setup, NULL, NULL, flash_sim_cbs_teardown);
224