]>
Commit | Line | Data |
---|---|---|
60b725b6 SC |
1 | /* |
2 | * QTest testcase for parallel flash with AMD command set | |
3 | * | |
4 | * Copyright (c) 2019 Stephen Checkoway | |
5 | * | |
6 | * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
7 | * See the COPYING file in the top-level directory. | |
8 | */ | |
9 | ||
10 | #include "qemu/osdep.h" | |
11 | #include "libqtest.h" | |
12 | ||
13 | /* | |
14 | * To test the pflash_cfi02 device, we run QEMU with the musicpal machine with | |
15 | * a pflash drive. This enables us to test some flash configurations, but not | |
16 | * all. In particular, we're limited to a 16-bit wide flash device. | |
17 | */ | |
18 | ||
19 | #define MP_FLASH_SIZE_MAX (32 * 1024 * 1024) | |
20 | #define BASE_ADDR (0x100000000ULL - MP_FLASH_SIZE_MAX) | |
21 | ||
64659053 SC |
22 | #define UNIFORM_FLASH_SIZE (8 * 1024 * 1024) |
23 | #define UNIFORM_FLASH_SECTOR_SIZE (64 * 1024) | |
24 | ||
91d02312 PMD |
25 | /* Use a newtype to keep flash addresses separate from byte addresses. */ |
26 | typedef struct { | |
27 | uint64_t addr; | |
28 | } faddr; | |
29 | #define FLASH_ADDR(x) ((faddr) { .addr = (x) }) | |
30 | ||
31 | #define CFI_ADDR FLASH_ADDR(0x55) | |
32 | #define UNLOCK0_ADDR FLASH_ADDR(0x555) | |
33 | #define UNLOCK1_ADDR FLASH_ADDR(0x2AA) | |
60b725b6 SC |
34 | |
35 | #define CFI_CMD 0x98 | |
36 | #define UNLOCK0_CMD 0xAA | |
37 | #define UNLOCK1_CMD 0x55 | |
a50547ac | 38 | #define SECOND_UNLOCK_CMD 0x80 |
60b725b6 SC |
39 | #define AUTOSELECT_CMD 0x90 |
40 | #define RESET_CMD 0xF0 | |
41 | #define PROGRAM_CMD 0xA0 | |
42 | #define SECTOR_ERASE_CMD 0x30 | |
43 | #define CHIP_ERASE_CMD 0x10 | |
44 | #define UNLOCK_BYPASS_CMD 0x20 | |
45 | #define UNLOCK_BYPASS_RESET_CMD 0x00 | |
ddb6f225 SC |
46 | #define ERASE_SUSPEND_CMD 0xB0 |
47 | #define ERASE_RESUME_CMD SECTOR_ERASE_CMD | |
60b725b6 | 48 | |
91d02312 PMD |
49 | typedef struct { |
50 | int bank_width; | |
51 | ||
64659053 SC |
52 | /* Nonuniform block size. */ |
53 | int nb_blocs[4]; | |
54 | int sector_len[4]; | |
55 | ||
91d02312 PMD |
56 | QTestState *qtest; |
57 | } FlashConfig; | |
58 | ||
60b725b6 SC |
59 | static char image_path[] = "/tmp/qtest.XXXXXX"; |
60 | ||
91d02312 PMD |
61 | /* |
62 | * The pflash implementation allows some parameters to be unspecified. We want | |
63 | * to test those configurations but we also need to know the real values in | |
64 | * our testing code. So after we launch qemu, we'll need a new FlashConfig | |
65 | * with the correct values filled in. | |
66 | */ | |
67 | static FlashConfig expand_config_defaults(const FlashConfig *c) | |
68 | { | |
69 | FlashConfig ret = *c; | |
70 | ||
71 | if (ret.bank_width == 0) { | |
72 | ret.bank_width = 2; | |
73 | } | |
64659053 SC |
74 | if (ret.nb_blocs[0] == 0 && ret.sector_len[0] == 0) { |
75 | ret.sector_len[0] = UNIFORM_FLASH_SECTOR_SIZE; | |
76 | ret.nb_blocs[0] = UNIFORM_FLASH_SIZE / UNIFORM_FLASH_SECTOR_SIZE; | |
77 | } | |
91d02312 PMD |
78 | |
79 | /* XXX: Limitations of test harness. */ | |
80 | assert(ret.bank_width == 2); | |
81 | return ret; | |
82 | } | |
83 | ||
84 | /* | |
85 | * Return a bit mask suitable for extracting the least significant | |
86 | * status/query response from an interleaved response. | |
87 | */ | |
88 | static inline uint64_t device_mask(const FlashConfig *c) | |
89 | { | |
90 | return (uint64_t)-1; | |
91 | } | |
92 | ||
93 | /* | |
94 | * Return a bit mask exactly as long as the bank_width. | |
95 | */ | |
96 | static inline uint64_t bank_mask(const FlashConfig *c) | |
97 | { | |
98 | if (c->bank_width == 8) { | |
99 | return (uint64_t)-1; | |
100 | } | |
101 | return (1ULL << (c->bank_width * 8)) - 1ULL; | |
102 | } | |
103 | ||
104 | static inline void flash_write(const FlashConfig *c, uint64_t byte_addr, | |
105 | uint64_t data) | |
106 | { | |
107 | /* Sanity check our tests. */ | |
108 | assert((data & ~bank_mask(c)) == 0); | |
109 | uint64_t addr = BASE_ADDR + byte_addr; | |
110 | switch (c->bank_width) { | |
111 | case 1: | |
112 | qtest_writeb(c->qtest, addr, data); | |
113 | break; | |
114 | case 2: | |
115 | qtest_writew(c->qtest, addr, data); | |
116 | break; | |
117 | case 4: | |
118 | qtest_writel(c->qtest, addr, data); | |
119 | break; | |
120 | case 8: | |
121 | qtest_writeq(c->qtest, addr, data); | |
122 | break; | |
123 | default: | |
124 | abort(); | |
125 | } | |
126 | } | |
127 | ||
128 | static inline uint64_t flash_read(const FlashConfig *c, uint64_t byte_addr) | |
129 | { | |
130 | uint64_t addr = BASE_ADDR + byte_addr; | |
131 | switch (c->bank_width) { | |
132 | case 1: | |
133 | return qtest_readb(c->qtest, addr); | |
134 | case 2: | |
135 | return qtest_readw(c->qtest, addr); | |
136 | case 4: | |
137 | return qtest_readl(c->qtest, addr); | |
138 | case 8: | |
139 | return qtest_readq(c->qtest, addr); | |
140 | default: | |
141 | abort(); | |
142 | } | |
143 | } | |
144 | ||
145 | /* | |
146 | * Convert a flash address expressed in the maximum width of the device as a | |
147 | * byte address. | |
148 | */ | |
149 | static inline uint64_t as_byte_addr(const FlashConfig *c, faddr flash_addr) | |
60b725b6 | 150 | { |
91d02312 PMD |
151 | /* |
152 | * Command addresses are always given as addresses in the maximum | |
153 | * supported bus size for the flash chip. So an x8/x16 chip in x8 mode | |
154 | * uses addresses 0xAAA and 0x555 to unlock because the least significant | |
155 | * bit is ignored. (0x555 rather than 0x554 is traditional.) | |
156 | * | |
157 | * In general we need to multiply by the maximum device width. | |
158 | */ | |
159 | return flash_addr.addr * c->bank_width; | |
60b725b6 SC |
160 | } |
161 | ||
91d02312 PMD |
162 | /* |
163 | * Return the command value or expected status replicated across all devices. | |
164 | */ | |
165 | static inline uint64_t replicate(const FlashConfig *c, uint64_t data) | |
60b725b6 | 166 | { |
91d02312 PMD |
167 | /* Sanity check our tests. */ |
168 | assert((data & ~device_mask(c)) == 0); | |
169 | return data; | |
60b725b6 SC |
170 | } |
171 | ||
91d02312 PMD |
172 | static inline void flash_cmd(const FlashConfig *c, faddr cmd_addr, |
173 | uint8_t cmd) | |
60b725b6 | 174 | { |
91d02312 | 175 | flash_write(c, as_byte_addr(c, cmd_addr), replicate(c, cmd)); |
60b725b6 SC |
176 | } |
177 | ||
91d02312 | 178 | static inline uint64_t flash_query(const FlashConfig *c, faddr query_addr) |
60b725b6 | 179 | { |
91d02312 | 180 | return flash_read(c, as_byte_addr(c, query_addr)); |
60b725b6 SC |
181 | } |
182 | ||
91d02312 | 183 | static inline uint64_t flash_query_1(const FlashConfig *c, faddr query_addr) |
60b725b6 | 184 | { |
91d02312 | 185 | return flash_query(c, query_addr) & device_mask(c); |
60b725b6 SC |
186 | } |
187 | ||
91d02312 PMD |
188 | static void unlock(const FlashConfig *c) |
189 | { | |
190 | flash_cmd(c, UNLOCK0_ADDR, UNLOCK0_CMD); | |
191 | flash_cmd(c, UNLOCK1_ADDR, UNLOCK1_CMD); | |
192 | } | |
193 | ||
194 | static void reset(const FlashConfig *c) | |
195 | { | |
196 | flash_cmd(c, FLASH_ADDR(0), RESET_CMD); | |
197 | } | |
198 | ||
199 | static void sector_erase(const FlashConfig *c, uint64_t byte_addr) | |
200 | { | |
201 | unlock(c); | |
a50547ac | 202 | flash_cmd(c, UNLOCK0_ADDR, SECOND_UNLOCK_CMD); |
91d02312 PMD |
203 | unlock(c); |
204 | flash_write(c, byte_addr, replicate(c, SECTOR_ERASE_CMD)); | |
205 | } | |
206 | ||
207 | static void wait_for_completion(const FlashConfig *c, uint64_t byte_addr) | |
60b725b6 SC |
208 | { |
209 | /* If DQ6 is toggling, step the clock and ensure the toggle stops. */ | |
91d02312 PMD |
210 | const uint64_t dq6 = replicate(c, 0x40); |
211 | if ((flash_read(c, byte_addr) & dq6) ^ (flash_read(c, byte_addr) & dq6)) { | |
60b725b6 | 212 | /* Wait for erase or program to finish. */ |
91d02312 | 213 | qtest_clock_step_next(c->qtest); |
60b725b6 | 214 | /* Ensure that DQ6 has stopped toggling. */ |
91d02312 | 215 | g_assert_cmphex(flash_read(c, byte_addr), ==, flash_read(c, byte_addr)); |
60b725b6 SC |
216 | } |
217 | } | |
218 | ||
91d02312 PMD |
219 | static void bypass_program(const FlashConfig *c, uint64_t byte_addr, |
220 | uint16_t data) | |
60b725b6 | 221 | { |
91d02312 PMD |
222 | flash_cmd(c, UNLOCK0_ADDR, PROGRAM_CMD); |
223 | flash_write(c, byte_addr, data); | |
60b725b6 SC |
224 | /* |
225 | * Data isn't valid until DQ6 stops toggling. We don't model this as | |
226 | * writes are immediate, but if this changes in the future, we can wait | |
227 | * until the program is complete. | |
228 | */ | |
91d02312 | 229 | wait_for_completion(c, byte_addr); |
60b725b6 SC |
230 | } |
231 | ||
91d02312 | 232 | static void program(const FlashConfig *c, uint64_t byte_addr, uint16_t data) |
60b725b6 | 233 | { |
91d02312 PMD |
234 | unlock(c); |
235 | bypass_program(c, byte_addr, data); | |
60b725b6 SC |
236 | } |
237 | ||
91d02312 | 238 | static void chip_erase(const FlashConfig *c) |
60b725b6 | 239 | { |
91d02312 | 240 | unlock(c); |
a50547ac | 241 | flash_cmd(c, UNLOCK0_ADDR, SECOND_UNLOCK_CMD); |
91d02312 PMD |
242 | unlock(c); |
243 | flash_cmd(c, UNLOCK0_ADDR, CHIP_ERASE_CMD); | |
60b725b6 SC |
244 | } |
245 | ||
ddb6f225 SC |
246 | static void erase_suspend(const FlashConfig *c) |
247 | { | |
248 | flash_cmd(c, FLASH_ADDR(0), ERASE_SUSPEND_CMD); | |
249 | } | |
250 | ||
251 | static void erase_resume(const FlashConfig *c) | |
252 | { | |
253 | flash_cmd(c, FLASH_ADDR(0), ERASE_RESUME_CMD); | |
254 | } | |
255 | ||
64659053 SC |
256 | /* |
257 | * Test flash commands with a variety of device geometry. | |
258 | */ | |
259 | static void test_geometry(const void *opaque) | |
60b725b6 | 260 | { |
91d02312 PMD |
261 | const FlashConfig *config = opaque; |
262 | QTestState *qtest; | |
263 | qtest = qtest_initf("-M musicpal,accel=qtest" | |
64659053 SC |
264 | " -drive if=pflash,file=%s,format=raw,copy-on-read" |
265 | /* Device geometry properties. */ | |
266 | " -global driver=cfi.pflash02," | |
267 | "property=num-blocks0,value=%d" | |
268 | " -global driver=cfi.pflash02," | |
269 | "property=sector-length0,value=%d" | |
270 | " -global driver=cfi.pflash02," | |
271 | "property=num-blocks1,value=%d" | |
272 | " -global driver=cfi.pflash02," | |
273 | "property=sector-length1,value=%d" | |
274 | " -global driver=cfi.pflash02," | |
275 | "property=num-blocks2,value=%d" | |
276 | " -global driver=cfi.pflash02," | |
277 | "property=sector-length2,value=%d" | |
278 | " -global driver=cfi.pflash02," | |
279 | "property=num-blocks3,value=%d" | |
280 | " -global driver=cfi.pflash02," | |
281 | "property=sector-length3,value=%d", | |
282 | image_path, | |
283 | config->nb_blocs[0], | |
284 | config->sector_len[0], | |
285 | config->nb_blocs[1], | |
286 | config->sector_len[1], | |
287 | config->nb_blocs[2], | |
288 | config->sector_len[2], | |
289 | config->nb_blocs[3], | |
290 | config->sector_len[3]); | |
91d02312 PMD |
291 | FlashConfig explicit_config = expand_config_defaults(config); |
292 | explicit_config.qtest = qtest; | |
293 | const FlashConfig *c = &explicit_config; | |
294 | ||
60b725b6 | 295 | /* Check the IDs. */ |
91d02312 PMD |
296 | unlock(c); |
297 | flash_cmd(c, UNLOCK0_ADDR, AUTOSELECT_CMD); | |
298 | g_assert_cmphex(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF)); | |
299 | if (c->bank_width >= 2) { | |
300 | /* | |
301 | * XXX: The ID returned by the musicpal flash chip is 16 bits which | |
302 | * wouldn't happen with an 8-bit device. It would probably be best to | |
303 | * prohibit addresses larger than the device width in pflash_cfi02.c, | |
304 | * but then we couldn't test smaller device widths at all. | |
305 | */ | |
306 | g_assert_cmphex(flash_query(c, FLASH_ADDR(1)), ==, | |
307 | replicate(c, 0x236D)); | |
308 | } | |
309 | reset(c); | |
60b725b6 SC |
310 | |
311 | /* Check the erase blocks. */ | |
91d02312 PMD |
312 | flash_cmd(c, CFI_ADDR, CFI_CMD); |
313 | g_assert_cmphex(flash_query(c, FLASH_ADDR(0x10)), ==, replicate(c, 'Q')); | |
314 | g_assert_cmphex(flash_query(c, FLASH_ADDR(0x11)), ==, replicate(c, 'R')); | |
315 | g_assert_cmphex(flash_query(c, FLASH_ADDR(0x12)), ==, replicate(c, 'Y')); | |
316 | ||
60b725b6 | 317 | /* Num erase regions. */ |
64659053 SC |
318 | int nb_erase_regions = flash_query_1(c, FLASH_ADDR(0x2C)); |
319 | g_assert_cmphex(nb_erase_regions, ==, | |
320 | !!c->nb_blocs[0] + !!c->nb_blocs[1] + !!c->nb_blocs[2] + | |
321 | !!c->nb_blocs[3]); | |
322 | ||
323 | /* Check device length. */ | |
324 | uint32_t device_len = 1 << flash_query_1(c, FLASH_ADDR(0x27)); | |
325 | g_assert_cmphex(device_len, ==, UNIFORM_FLASH_SIZE); | |
60b725b6 | 326 | |
ddb6f225 SC |
327 | /* Check that erase suspend to read/write is supported. */ |
328 | uint16_t pri = flash_query_1(c, FLASH_ADDR(0x15)) + | |
329 | (flash_query_1(c, FLASH_ADDR(0x16)) << 8); | |
330 | g_assert_cmpint(pri, >=, 0x2D + 4 * nb_erase_regions); | |
331 | g_assert_cmpint(flash_query(c, FLASH_ADDR(pri + 0)), ==, replicate(c, 'P')); | |
332 | g_assert_cmpint(flash_query(c, FLASH_ADDR(pri + 1)), ==, replicate(c, 'R')); | |
333 | g_assert_cmpint(flash_query(c, FLASH_ADDR(pri + 2)), ==, replicate(c, 'I')); | |
334 | g_assert_cmpint(flash_query_1(c, FLASH_ADDR(pri + 6)), ==, 2); /* R/W */ | |
91d02312 PMD |
335 | reset(c); |
336 | ||
337 | const uint64_t dq7 = replicate(c, 0x80); | |
338 | const uint64_t dq6 = replicate(c, 0x40); | |
a50547ac | 339 | const uint64_t dq3 = replicate(c, 0x08); |
ddb6f225 | 340 | const uint64_t dq2 = replicate(c, 0x04); |
a50547ac | 341 | |
64659053 SC |
342 | uint64_t byte_addr = 0; |
343 | for (int region = 0; region < nb_erase_regions; ++region) { | |
344 | uint64_t base = 0x2D + 4 * region; | |
345 | flash_cmd(c, CFI_ADDR, CFI_CMD); | |
346 | uint32_t nb_sectors = flash_query_1(c, FLASH_ADDR(base + 0)) + | |
347 | (flash_query_1(c, FLASH_ADDR(base + 1)) << 8) + 1; | |
348 | uint32_t sector_len = (flash_query_1(c, FLASH_ADDR(base + 2)) << 8) + | |
349 | (flash_query_1(c, FLASH_ADDR(base + 3)) << 16); | |
350 | g_assert_cmphex(nb_sectors, ==, c->nb_blocs[region]); | |
351 | g_assert_cmphex(sector_len, ==, c->sector_len[region]); | |
352 | reset(c); | |
353 | ||
354 | /* Erase and program sector. */ | |
355 | for (uint32_t i = 0; i < nb_sectors; ++i) { | |
356 | sector_erase(c, byte_addr); | |
a50547ac SC |
357 | |
358 | /* Check that DQ3 is 0. */ | |
359 | g_assert_cmphex(flash_read(c, byte_addr) & dq3, ==, 0); | |
360 | qtest_clock_step_next(c->qtest); /* Step over the 50 us timeout. */ | |
361 | ||
362 | /* Check that DQ3 is 1. */ | |
64659053 | 363 | uint64_t status0 = flash_read(c, byte_addr); |
a50547ac SC |
364 | g_assert_cmphex(status0 & dq3, ==, dq3); |
365 | ||
64659053 SC |
366 | /* DQ7 is 0 during an erase. */ |
367 | g_assert_cmphex(status0 & dq7, ==, 0); | |
368 | uint64_t status1 = flash_read(c, byte_addr); | |
a50547ac | 369 | |
64659053 SC |
370 | /* DQ6 toggles during an erase. */ |
371 | g_assert_cmphex(status0 & dq6, ==, ~status1 & dq6); | |
a50547ac | 372 | |
64659053 | 373 | /* Wait for erase to complete. */ |
a50547ac SC |
374 | wait_for_completion(c, byte_addr); |
375 | ||
64659053 SC |
376 | /* Ensure DQ6 has stopped toggling. */ |
377 | g_assert_cmphex(flash_read(c, byte_addr), ==, | |
378 | flash_read(c, byte_addr)); | |
a50547ac | 379 | |
64659053 SC |
380 | /* Now the data should be valid. */ |
381 | g_assert_cmphex(flash_read(c, byte_addr), ==, bank_mask(c)); | |
382 | ||
383 | /* Program a bit pattern. */ | |
384 | program(c, byte_addr, 0x55); | |
385 | g_assert_cmphex(flash_read(c, byte_addr) & 0xFF, ==, 0x55); | |
386 | program(c, byte_addr, 0xA5); | |
387 | g_assert_cmphex(flash_read(c, byte_addr) & 0xFF, ==, 0x05); | |
388 | byte_addr += sector_len; | |
389 | } | |
60b725b6 SC |
390 | } |
391 | ||
392 | /* Erase the chip. */ | |
91d02312 | 393 | chip_erase(c); |
60b725b6 | 394 | /* Read toggle. */ |
91d02312 | 395 | uint64_t status0 = flash_read(c, 0); |
60b725b6 | 396 | /* DQ7 is 0 during an erase. */ |
91d02312 PMD |
397 | g_assert_cmphex(status0 & dq7, ==, 0); |
398 | uint64_t status1 = flash_read(c, 0); | |
60b725b6 | 399 | /* DQ6 toggles during an erase. */ |
91d02312 | 400 | g_assert_cmphex(status0 & dq6, ==, ~status1 & dq6); |
60b725b6 | 401 | /* Wait for erase to complete. */ |
91d02312 | 402 | qtest_clock_step_next(c->qtest); |
60b725b6 | 403 | /* Ensure DQ6 has stopped toggling. */ |
91d02312 | 404 | g_assert_cmphex(flash_read(c, 0), ==, flash_read(c, 0)); |
60b725b6 | 405 | /* Now the data should be valid. */ |
91d02312 | 406 | |
64659053 SC |
407 | for (int region = 0; region < nb_erase_regions; ++region) { |
408 | for (uint32_t i = 0; i < c->nb_blocs[region]; ++i) { | |
409 | uint64_t byte_addr = i * c->sector_len[region]; | |
410 | g_assert_cmphex(flash_read(c, byte_addr), ==, bank_mask(c)); | |
411 | } | |
91d02312 | 412 | } |
60b725b6 SC |
413 | |
414 | /* Unlock bypass */ | |
91d02312 PMD |
415 | unlock(c); |
416 | flash_cmd(c, UNLOCK0_ADDR, UNLOCK_BYPASS_CMD); | |
417 | bypass_program(c, 0 * c->bank_width, 0x01); | |
418 | bypass_program(c, 1 * c->bank_width, 0x23); | |
419 | bypass_program(c, 2 * c->bank_width, 0x45); | |
60b725b6 SC |
420 | /* |
421 | * Test that bypass programming, unlike normal programming can use any | |
422 | * address for the PROGRAM_CMD. | |
423 | */ | |
91d02312 PMD |
424 | flash_cmd(c, FLASH_ADDR(3 * c->bank_width), PROGRAM_CMD); |
425 | flash_write(c, 3 * c->bank_width, 0x67); | |
426 | wait_for_completion(c, 3 * c->bank_width); | |
427 | flash_cmd(c, FLASH_ADDR(0), UNLOCK_BYPASS_RESET_CMD); | |
428 | bypass_program(c, 4 * c->bank_width, 0x89); /* Should fail. */ | |
429 | g_assert_cmphex(flash_read(c, 0 * c->bank_width), ==, 0x01); | |
430 | g_assert_cmphex(flash_read(c, 1 * c->bank_width), ==, 0x23); | |
431 | g_assert_cmphex(flash_read(c, 2 * c->bank_width), ==, 0x45); | |
432 | g_assert_cmphex(flash_read(c, 3 * c->bank_width), ==, 0x67); | |
433 | g_assert_cmphex(flash_read(c, 4 * c->bank_width), ==, bank_mask(c)); | |
60b725b6 | 434 | |
6682bc1e | 435 | /* Test ignored high order bits of address. */ |
91d02312 PMD |
436 | flash_cmd(c, FLASH_ADDR(0x5555), UNLOCK0_CMD); |
437 | flash_cmd(c, FLASH_ADDR(0x2AAA), UNLOCK1_CMD); | |
438 | flash_cmd(c, FLASH_ADDR(0x5555), AUTOSELECT_CMD); | |
439 | g_assert_cmphex(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF)); | |
440 | reset(c); | |
6682bc1e | 441 | |
a50547ac SC |
442 | /* |
443 | * Program a word on each sector, erase one or two sectors per region, and | |
444 | * verify that all of those, and only those, are erased. | |
445 | */ | |
446 | byte_addr = 0; | |
447 | for (int region = 0; region < nb_erase_regions; ++region) { | |
448 | for (int i = 0; i < config->nb_blocs[region]; ++i) { | |
449 | program(c, byte_addr, 0); | |
450 | byte_addr += config->sector_len[region]; | |
451 | } | |
452 | } | |
453 | unlock(c); | |
454 | flash_cmd(c, UNLOCK0_ADDR, SECOND_UNLOCK_CMD); | |
455 | unlock(c); | |
456 | byte_addr = 0; | |
457 | const uint64_t erase_cmd = replicate(c, SECTOR_ERASE_CMD); | |
458 | for (int region = 0; region < nb_erase_regions; ++region) { | |
459 | flash_write(c, byte_addr, erase_cmd); | |
460 | if (c->nb_blocs[region] > 1) { | |
461 | flash_write(c, byte_addr + c->sector_len[region], erase_cmd); | |
462 | } | |
463 | byte_addr += c->sector_len[region] * c->nb_blocs[region]; | |
464 | } | |
465 | ||
466 | qtest_clock_step_next(c->qtest); /* Step over the 50 us timeout. */ | |
467 | wait_for_completion(c, 0); | |
468 | byte_addr = 0; | |
469 | for (int region = 0; region < nb_erase_regions; ++region) { | |
470 | for (int i = 0; i < config->nb_blocs[region]; ++i) { | |
471 | if (i < 2) { | |
472 | g_assert_cmphex(flash_read(c, byte_addr), ==, bank_mask(c)); | |
473 | } else { | |
474 | g_assert_cmphex(flash_read(c, byte_addr), ==, 0); | |
475 | } | |
476 | byte_addr += config->sector_len[region]; | |
477 | } | |
478 | } | |
479 | ||
ddb6f225 SC |
480 | /* Test erase suspend/resume during erase timeout. */ |
481 | sector_erase(c, 0); | |
482 | /* | |
483 | * Check that DQ 3 is 0 and DQ6 and DQ2 are toggling in the sector being | |
484 | * erased as well as in a sector not being erased. | |
485 | */ | |
486 | byte_addr = c->sector_len[0]; | |
487 | status0 = flash_read(c, 0); | |
488 | status1 = flash_read(c, 0); | |
489 | g_assert_cmpint(status0 & dq3, ==, 0); | |
490 | g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6); | |
491 | g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); | |
492 | status0 = flash_read(c, byte_addr); | |
493 | status1 = flash_read(c, byte_addr); | |
494 | g_assert_cmpint(status0 & dq3, ==, 0); | |
495 | g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6); | |
496 | g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); | |
497 | ||
498 | /* | |
499 | * Check that after suspending, DQ6 does not toggle but DQ2 does toggle in | |
500 | * an erase suspended sector but that neither toggle (we should be | |
501 | * getting data) in a sector not being erased. | |
502 | */ | |
503 | erase_suspend(c); | |
504 | status0 = flash_read(c, 0); | |
505 | status1 = flash_read(c, 0); | |
506 | g_assert_cmpint(status0 & dq6, ==, status1 & dq6); | |
507 | g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); | |
508 | g_assert_cmpint(flash_read(c, byte_addr), ==, flash_read(c, byte_addr)); | |
509 | ||
510 | /* Check that after resuming, DQ3 is 1 and DQ6 and DQ2 toggle. */ | |
511 | erase_resume(c); | |
512 | status0 = flash_read(c, 0); | |
513 | status1 = flash_read(c, 0); | |
514 | g_assert_cmpint(status0 & dq3, ==, dq3); | |
515 | g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6); | |
516 | g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); | |
517 | status0 = flash_read(c, byte_addr); | |
518 | status1 = flash_read(c, byte_addr); | |
519 | g_assert_cmpint(status0 & dq3, ==, dq3); | |
520 | g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6); | |
521 | g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); | |
522 | wait_for_completion(c, 0); | |
523 | ||
524 | /* Repeat this process but this time suspend after the timeout. */ | |
525 | sector_erase(c, 0); | |
526 | qtest_clock_step_next(c->qtest); | |
527 | /* | |
528 | * Check that DQ 3 is 1 and DQ6 and DQ2 are toggling in the sector being | |
529 | * erased as well as in a sector not being erased. | |
530 | */ | |
531 | byte_addr = c->sector_len[0]; | |
532 | status0 = flash_read(c, 0); | |
533 | status1 = flash_read(c, 0); | |
534 | g_assert_cmpint(status0 & dq3, ==, dq3); | |
535 | g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6); | |
536 | g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); | |
537 | status0 = flash_read(c, byte_addr); | |
538 | status1 = flash_read(c, byte_addr); | |
539 | g_assert_cmpint(status0 & dq3, ==, dq3); | |
540 | g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6); | |
541 | g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); | |
542 | ||
543 | /* | |
544 | * Check that after suspending, DQ6 does not toggle but DQ2 does toggle in | |
545 | * an erase suspended sector but that neither toggle (we should be | |
546 | * getting data) in a sector not being erased. | |
547 | */ | |
548 | erase_suspend(c); | |
549 | status0 = flash_read(c, 0); | |
550 | status1 = flash_read(c, 0); | |
551 | g_assert_cmpint(status0 & dq6, ==, status1 & dq6); | |
552 | g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); | |
553 | g_assert_cmpint(flash_read(c, byte_addr), ==, flash_read(c, byte_addr)); | |
554 | ||
555 | /* Check that after resuming, DQ3 is 1 and DQ6 and DQ2 toggle. */ | |
556 | erase_resume(c); | |
557 | status0 = flash_read(c, 0); | |
558 | status1 = flash_read(c, 0); | |
559 | g_assert_cmpint(status0 & dq3, ==, dq3); | |
560 | g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6); | |
561 | g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); | |
562 | status0 = flash_read(c, byte_addr); | |
563 | status1 = flash_read(c, byte_addr); | |
564 | g_assert_cmpint(status0 & dq3, ==, dq3); | |
565 | g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6); | |
566 | g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); | |
567 | wait_for_completion(c, 0); | |
568 | ||
91d02312 | 569 | qtest_quit(qtest); |
60b725b6 SC |
570 | } |
571 | ||
46fb7809 SC |
572 | /* |
573 | * Test that | |
574 | * 1. enter autoselect mode; | |
575 | * 2. enter CFI mode; and then | |
576 | * 3. exit CFI mode | |
577 | * leaves the flash device in autoselect mode. | |
578 | */ | |
579 | static void test_cfi_in_autoselect(const void *opaque) | |
580 | { | |
581 | const FlashConfig *config = opaque; | |
582 | QTestState *qtest; | |
583 | qtest = qtest_initf("-M musicpal,accel=qtest" | |
584 | " -drive if=pflash,file=%s,format=raw,copy-on-read", | |
585 | image_path); | |
586 | FlashConfig explicit_config = expand_config_defaults(config); | |
587 | explicit_config.qtest = qtest; | |
588 | const FlashConfig *c = &explicit_config; | |
589 | ||
590 | /* 1. Enter autoselect. */ | |
591 | unlock(c); | |
592 | flash_cmd(c, UNLOCK0_ADDR, AUTOSELECT_CMD); | |
a50547ac | 593 | g_assert_cmphex(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF)); |
46fb7809 SC |
594 | |
595 | /* 2. Enter CFI. */ | |
596 | flash_cmd(c, CFI_ADDR, CFI_CMD); | |
a50547ac SC |
597 | g_assert_cmphex(flash_query(c, FLASH_ADDR(0x10)), ==, replicate(c, 'Q')); |
598 | g_assert_cmphex(flash_query(c, FLASH_ADDR(0x11)), ==, replicate(c, 'R')); | |
599 | g_assert_cmphex(flash_query(c, FLASH_ADDR(0x12)), ==, replicate(c, 'Y')); | |
46fb7809 SC |
600 | |
601 | /* 3. Exit CFI. */ | |
602 | reset(c); | |
a50547ac | 603 | g_assert_cmphex(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF)); |
46fb7809 SC |
604 | |
605 | qtest_quit(qtest); | |
606 | } | |
607 | ||
60b725b6 SC |
608 | static void cleanup(void *opaque) |
609 | { | |
610 | unlink(image_path); | |
611 | } | |
612 | ||
91d02312 PMD |
613 | /* |
614 | * XXX: Tests are limited to bank_width = 2 for now because that's what | |
615 | * hw/arm/musicpal.c has. | |
616 | */ | |
617 | static const FlashConfig configuration[] = { | |
618 | /* One x16 device. */ | |
619 | { | |
620 | .bank_width = 2, | |
621 | }, | |
64659053 SC |
622 | /* Nonuniform sectors (top boot). */ |
623 | { | |
624 | .bank_width = 2, | |
625 | .nb_blocs = { 127, 1, 2, 1 }, | |
626 | .sector_len = { 0x10000, 0x08000, 0x02000, 0x04000 }, | |
627 | }, | |
628 | /* Nonuniform sectors (bottom boot). */ | |
629 | { | |
630 | .bank_width = 2, | |
631 | .nb_blocs = { 1, 2, 1, 127 }, | |
632 | .sector_len = { 0x04000, 0x02000, 0x08000, 0x10000 }, | |
633 | }, | |
91d02312 PMD |
634 | }; |
635 | ||
60b725b6 SC |
636 | int main(int argc, char **argv) |
637 | { | |
638 | int fd = mkstemp(image_path); | |
639 | if (fd == -1) { | |
640 | g_printerr("Failed to create temporary file %s: %s\n", image_path, | |
641 | strerror(errno)); | |
642 | exit(EXIT_FAILURE); | |
643 | } | |
64659053 | 644 | if (ftruncate(fd, UNIFORM_FLASH_SIZE) < 0) { |
60b725b6 SC |
645 | int error_code = errno; |
646 | close(fd); | |
647 | unlink(image_path); | |
91d02312 | 648 | g_printerr("Failed to truncate file %s to %u MB: %s\n", image_path, |
64659053 | 649 | UNIFORM_FLASH_SIZE, strerror(error_code)); |
60b725b6 SC |
650 | exit(EXIT_FAILURE); |
651 | } | |
652 | close(fd); | |
653 | ||
654 | qtest_add_abrt_handler(cleanup, NULL); | |
655 | g_test_init(&argc, &argv, NULL); | |
91d02312 PMD |
656 | |
657 | size_t nb_configurations = sizeof configuration / sizeof configuration[0]; | |
658 | for (size_t i = 0; i < nb_configurations; ++i) { | |
659 | const FlashConfig *config = &configuration[i]; | |
64659053 SC |
660 | char *path = g_strdup_printf("pflash-cfi02" |
661 | "/geometry/%dx%x-%dx%x-%dx%x-%dx%x" | |
662 | "/%d", | |
663 | config->nb_blocs[0], | |
664 | config->sector_len[0], | |
665 | config->nb_blocs[1], | |
666 | config->sector_len[1], | |
667 | config->nb_blocs[2], | |
668 | config->sector_len[2], | |
669 | config->nb_blocs[3], | |
670 | config->sector_len[3], | |
91d02312 | 671 | config->bank_width); |
64659053 | 672 | qtest_add_data_func(path, config, test_geometry); |
91d02312 PMD |
673 | g_free(path); |
674 | } | |
46fb7809 SC |
675 | |
676 | qtest_add_data_func("pflash-cfi02/cfi-in-autoselect", &configuration[0], | |
677 | test_cfi_in_autoselect); | |
60b725b6 SC |
678 | int result = g_test_run(); |
679 | cleanup(NULL); | |
680 | return result; | |
681 | } |