xref: /qemu/tests/qtest/pflash-cfi02-test.c (revision 6682bc1ee431dc6198b85ac71d537104cfc57fed)
160b725b6SStephen Checkoway /*
260b725b6SStephen Checkoway  * QTest testcase for parallel flash with AMD command set
360b725b6SStephen Checkoway  *
460b725b6SStephen Checkoway  * Copyright (c) 2019 Stephen Checkoway
560b725b6SStephen Checkoway  *
660b725b6SStephen Checkoway  * This work is licensed under the terms of the GNU GPL, version 2 or later.
760b725b6SStephen Checkoway  * See the COPYING file in the top-level directory.
860b725b6SStephen Checkoway  */
960b725b6SStephen Checkoway 
1060b725b6SStephen Checkoway #include "qemu/osdep.h"
1160b725b6SStephen Checkoway #include "libqtest.h"
1260b725b6SStephen Checkoway 
1360b725b6SStephen Checkoway /*
1460b725b6SStephen Checkoway  * To test the pflash_cfi02 device, we run QEMU with the musicpal machine with
1560b725b6SStephen Checkoway  * a pflash drive. This enables us to test some flash configurations, but not
1660b725b6SStephen Checkoway  * all. In particular, we're limited to a 16-bit wide flash device.
1760b725b6SStephen Checkoway  */
1860b725b6SStephen Checkoway 
1960b725b6SStephen Checkoway #define MP_FLASH_SIZE_MAX (32 * 1024 * 1024)
2060b725b6SStephen Checkoway #define BASE_ADDR (0x100000000ULL - MP_FLASH_SIZE_MAX)
2160b725b6SStephen Checkoway 
2260b725b6SStephen Checkoway #define FLASH_WIDTH 2
2360b725b6SStephen Checkoway #define CFI_ADDR (FLASH_WIDTH * 0x55)
24*6682bc1eSStephen Checkoway #define UNLOCK0_ADDR (FLASH_WIDTH * 0x555)
25*6682bc1eSStephen Checkoway #define UNLOCK1_ADDR (FLASH_WIDTH * 0x2AA)
2660b725b6SStephen Checkoway 
2760b725b6SStephen Checkoway #define CFI_CMD 0x98
2860b725b6SStephen Checkoway #define UNLOCK0_CMD 0xAA
2960b725b6SStephen Checkoway #define UNLOCK1_CMD 0x55
3060b725b6SStephen Checkoway #define AUTOSELECT_CMD 0x90
3160b725b6SStephen Checkoway #define RESET_CMD 0xF0
3260b725b6SStephen Checkoway #define PROGRAM_CMD 0xA0
3360b725b6SStephen Checkoway #define SECTOR_ERASE_CMD 0x30
3460b725b6SStephen Checkoway #define CHIP_ERASE_CMD 0x10
3560b725b6SStephen Checkoway #define UNLOCK_BYPASS_CMD 0x20
3660b725b6SStephen Checkoway #define UNLOCK_BYPASS_RESET_CMD 0x00
3760b725b6SStephen Checkoway 
3860b725b6SStephen Checkoway static char image_path[] = "/tmp/qtest.XXXXXX";
3960b725b6SStephen Checkoway 
4060b725b6SStephen Checkoway static inline void flash_write(uint64_t byte_addr, uint16_t data)
4160b725b6SStephen Checkoway {
4260b725b6SStephen Checkoway     qtest_writew(global_qtest, BASE_ADDR + byte_addr, data);
4360b725b6SStephen Checkoway }
4460b725b6SStephen Checkoway 
4560b725b6SStephen Checkoway static inline uint16_t flash_read(uint64_t byte_addr)
4660b725b6SStephen Checkoway {
4760b725b6SStephen Checkoway     return qtest_readw(global_qtest, BASE_ADDR + byte_addr);
4860b725b6SStephen Checkoway }
4960b725b6SStephen Checkoway 
5060b725b6SStephen Checkoway static void unlock(void)
5160b725b6SStephen Checkoway {
5260b725b6SStephen Checkoway     flash_write(UNLOCK0_ADDR, UNLOCK0_CMD);
5360b725b6SStephen Checkoway     flash_write(UNLOCK1_ADDR, UNLOCK1_CMD);
5460b725b6SStephen Checkoway }
5560b725b6SStephen Checkoway 
5660b725b6SStephen Checkoway static void reset(void)
5760b725b6SStephen Checkoway {
5860b725b6SStephen Checkoway     flash_write(0, RESET_CMD);
5960b725b6SStephen Checkoway }
6060b725b6SStephen Checkoway 
6160b725b6SStephen Checkoway static void sector_erase(uint64_t byte_addr)
6260b725b6SStephen Checkoway {
6360b725b6SStephen Checkoway     unlock();
6460b725b6SStephen Checkoway     flash_write(UNLOCK0_ADDR, 0x80);
6560b725b6SStephen Checkoway     unlock();
6660b725b6SStephen Checkoway     flash_write(byte_addr, SECTOR_ERASE_CMD);
6760b725b6SStephen Checkoway }
6860b725b6SStephen Checkoway 
6960b725b6SStephen Checkoway static void wait_for_completion(uint64_t byte_addr)
7060b725b6SStephen Checkoway {
7160b725b6SStephen Checkoway     /* If DQ6 is toggling, step the clock and ensure the toggle stops. */
7260b725b6SStephen Checkoway     if ((flash_read(byte_addr) & 0x40) ^ (flash_read(byte_addr) & 0x40)) {
7360b725b6SStephen Checkoway         /* Wait for erase or program to finish. */
7460b725b6SStephen Checkoway         clock_step_next();
7560b725b6SStephen Checkoway         /* Ensure that DQ6 has stopped toggling. */
7660b725b6SStephen Checkoway         g_assert_cmphex(flash_read(byte_addr), ==, flash_read(byte_addr));
7760b725b6SStephen Checkoway     }
7860b725b6SStephen Checkoway }
7960b725b6SStephen Checkoway 
8060b725b6SStephen Checkoway static void bypass_program(uint64_t byte_addr, uint16_t data)
8160b725b6SStephen Checkoway {
8260b725b6SStephen Checkoway     flash_write(UNLOCK0_ADDR, PROGRAM_CMD);
8360b725b6SStephen Checkoway     flash_write(byte_addr, data);
8460b725b6SStephen Checkoway     /*
8560b725b6SStephen Checkoway      * Data isn't valid until DQ6 stops toggling. We don't model this as
8660b725b6SStephen Checkoway      * writes are immediate, but if this changes in the future, we can wait
8760b725b6SStephen Checkoway      * until the program is complete.
8860b725b6SStephen Checkoway      */
8960b725b6SStephen Checkoway     wait_for_completion(byte_addr);
9060b725b6SStephen Checkoway }
9160b725b6SStephen Checkoway 
9260b725b6SStephen Checkoway static void program(uint64_t byte_addr, uint16_t data)
9360b725b6SStephen Checkoway {
9460b725b6SStephen Checkoway     unlock();
9560b725b6SStephen Checkoway     bypass_program(byte_addr, data);
9660b725b6SStephen Checkoway }
9760b725b6SStephen Checkoway 
9860b725b6SStephen Checkoway static void chip_erase(void)
9960b725b6SStephen Checkoway {
10060b725b6SStephen Checkoway     unlock();
10160b725b6SStephen Checkoway     flash_write(UNLOCK0_ADDR, 0x80);
10260b725b6SStephen Checkoway     unlock();
10360b725b6SStephen Checkoway     flash_write(UNLOCK0_ADDR, SECTOR_ERASE_CMD);
10460b725b6SStephen Checkoway }
10560b725b6SStephen Checkoway 
10660b725b6SStephen Checkoway static void test_flash(void)
10760b725b6SStephen Checkoway {
10860b725b6SStephen Checkoway     global_qtest = qtest_initf("-M musicpal,accel=qtest "
10960b725b6SStephen Checkoway                                "-drive if=pflash,file=%s,format=raw,copy-on-read",
11060b725b6SStephen Checkoway                                image_path);
11160b725b6SStephen Checkoway     /* Check the IDs. */
11260b725b6SStephen Checkoway     unlock();
11360b725b6SStephen Checkoway     flash_write(UNLOCK0_ADDR, AUTOSELECT_CMD);
11460b725b6SStephen Checkoway     g_assert_cmphex(flash_read(FLASH_WIDTH * 0x0000), ==, 0x00BF);
11560b725b6SStephen Checkoway     g_assert_cmphex(flash_read(FLASH_WIDTH * 0x0001), ==, 0x236D);
11660b725b6SStephen Checkoway     reset();
11760b725b6SStephen Checkoway 
11860b725b6SStephen Checkoway     /* Check the erase blocks. */
11960b725b6SStephen Checkoway     flash_write(CFI_ADDR, CFI_CMD);
12060b725b6SStephen Checkoway     g_assert_cmphex(flash_read(FLASH_WIDTH * 0x10), ==, 'Q');
12160b725b6SStephen Checkoway     g_assert_cmphex(flash_read(FLASH_WIDTH * 0x11), ==, 'R');
12260b725b6SStephen Checkoway     g_assert_cmphex(flash_read(FLASH_WIDTH * 0x12), ==, 'Y');
12360b725b6SStephen Checkoway     /* Num erase regions. */
12460b725b6SStephen Checkoway     g_assert_cmphex(flash_read(FLASH_WIDTH * 0x2C), >=, 1);
12560b725b6SStephen Checkoway     uint32_t nb_sectors = flash_read(FLASH_WIDTH * 0x2D) +
12660b725b6SStephen Checkoway                           (flash_read(FLASH_WIDTH * 0x2E) << 8) + 1;
12760b725b6SStephen Checkoway     uint32_t sector_len = (flash_read(FLASH_WIDTH * 0x2F) << 8) +
12860b725b6SStephen Checkoway                           (flash_read(FLASH_WIDTH * 0x30) << 16);
12960b725b6SStephen Checkoway     reset();
13060b725b6SStephen Checkoway 
13160b725b6SStephen Checkoway     /* Erase and program sector. */
13260b725b6SStephen Checkoway     for (uint32_t i = 0; i < nb_sectors; ++i) {
13360b725b6SStephen Checkoway         uint64_t byte_addr = i * sector_len;
13460b725b6SStephen Checkoway         sector_erase(byte_addr);
13560b725b6SStephen Checkoway         /* Read toggle. */
13660b725b6SStephen Checkoway         uint16_t status0 = flash_read(byte_addr);
13760b725b6SStephen Checkoway         /* DQ7 is 0 during an erase. */
13860b725b6SStephen Checkoway         g_assert_cmphex(status0 & 0x80, ==, 0);
13960b725b6SStephen Checkoway         uint16_t status1 = flash_read(byte_addr);
14060b725b6SStephen Checkoway         /* DQ6 toggles during an erase. */
14160b725b6SStephen Checkoway         g_assert_cmphex(status0 & 0x40, !=, status1 & 0x40);
14260b725b6SStephen Checkoway         /* Wait for erase to complete. */
14360b725b6SStephen Checkoway         clock_step_next();
14460b725b6SStephen Checkoway         /* Ensure DQ6 has stopped toggling. */
14560b725b6SStephen Checkoway         g_assert_cmphex(flash_read(byte_addr), ==, flash_read(byte_addr));
14660b725b6SStephen Checkoway         /* Now the data should be valid. */
14760b725b6SStephen Checkoway         g_assert_cmphex(flash_read(byte_addr), ==, 0xFFFF);
14860b725b6SStephen Checkoway 
14960b725b6SStephen Checkoway         /* Program a bit pattern. */
15060b725b6SStephen Checkoway         program(byte_addr, 0x5555);
15160b725b6SStephen Checkoway         g_assert_cmphex(flash_read(byte_addr), ==, 0x5555);
15260b725b6SStephen Checkoway         program(byte_addr, 0xAA55);
15360b725b6SStephen Checkoway         g_assert_cmphex(flash_read(byte_addr), ==, 0x0055);
15460b725b6SStephen Checkoway     }
15560b725b6SStephen Checkoway 
15660b725b6SStephen Checkoway     /* Erase the chip. */
15760b725b6SStephen Checkoway     chip_erase();
15860b725b6SStephen Checkoway     /* Read toggle. */
15960b725b6SStephen Checkoway     uint16_t status0 = flash_read(0);
16060b725b6SStephen Checkoway     /* DQ7 is 0 during an erase. */
16160b725b6SStephen Checkoway     g_assert_cmphex(status0 & 0x80, ==, 0);
16260b725b6SStephen Checkoway     uint16_t status1 = flash_read(0);
16360b725b6SStephen Checkoway     /* DQ6 toggles during an erase. */
16460b725b6SStephen Checkoway     g_assert_cmphex(status0 & 0x40, !=, status1 & 0x40);
16560b725b6SStephen Checkoway     /* Wait for erase to complete. */
16660b725b6SStephen Checkoway     clock_step_next();
16760b725b6SStephen Checkoway     /* Ensure DQ6 has stopped toggling. */
16860b725b6SStephen Checkoway     g_assert_cmphex(flash_read(0), ==, flash_read(0));
16960b725b6SStephen Checkoway     /* Now the data should be valid. */
17060b725b6SStephen Checkoway     g_assert_cmphex(flash_read(0), ==, 0xFFFF);
17160b725b6SStephen Checkoway 
17260b725b6SStephen Checkoway     /* Unlock bypass */
17360b725b6SStephen Checkoway     unlock();
17460b725b6SStephen Checkoway     flash_write(UNLOCK0_ADDR, UNLOCK_BYPASS_CMD);
17560b725b6SStephen Checkoway     bypass_program(0, 0x0123);
17660b725b6SStephen Checkoway     bypass_program(2, 0x4567);
17760b725b6SStephen Checkoway     bypass_program(4, 0x89AB);
17860b725b6SStephen Checkoway     /*
17960b725b6SStephen Checkoway      * Test that bypass programming, unlike normal programming can use any
18060b725b6SStephen Checkoway      * address for the PROGRAM_CMD.
18160b725b6SStephen Checkoway      */
18260b725b6SStephen Checkoway     flash_write(6, PROGRAM_CMD);
18360b725b6SStephen Checkoway     flash_write(6, 0xCDEF);
18460b725b6SStephen Checkoway     wait_for_completion(6);
18560b725b6SStephen Checkoway     flash_write(0, UNLOCK_BYPASS_RESET_CMD);
18660b725b6SStephen Checkoway     bypass_program(8, 0x55AA); /* Should fail. */
18760b725b6SStephen Checkoway     g_assert_cmphex(flash_read(0), ==, 0x0123);
18860b725b6SStephen Checkoway     g_assert_cmphex(flash_read(2), ==, 0x4567);
18960b725b6SStephen Checkoway     g_assert_cmphex(flash_read(4), ==, 0x89AB);
19060b725b6SStephen Checkoway     g_assert_cmphex(flash_read(6), ==, 0xCDEF);
19160b725b6SStephen Checkoway     g_assert_cmphex(flash_read(8), ==, 0xFFFF);
19260b725b6SStephen Checkoway 
193*6682bc1eSStephen Checkoway     /* Test ignored high order bits of address. */
194*6682bc1eSStephen Checkoway     flash_write(FLASH_WIDTH * 0x5555, UNLOCK0_CMD);
195*6682bc1eSStephen Checkoway     flash_write(FLASH_WIDTH * 0x2AAA, UNLOCK1_CMD);
196*6682bc1eSStephen Checkoway     flash_write(FLASH_WIDTH * 0x5555, AUTOSELECT_CMD);
197*6682bc1eSStephen Checkoway     g_assert_cmpint(flash_read(FLASH_WIDTH * 0x0000), ==, 0x00BF);
198*6682bc1eSStephen Checkoway     g_assert_cmpint(flash_read(FLASH_WIDTH * 0x0001), ==, 0x236D);
199*6682bc1eSStephen Checkoway     reset();
200*6682bc1eSStephen Checkoway 
20160b725b6SStephen Checkoway     qtest_quit(global_qtest);
20260b725b6SStephen Checkoway }
20360b725b6SStephen Checkoway 
20460b725b6SStephen Checkoway static void cleanup(void *opaque)
20560b725b6SStephen Checkoway {
20660b725b6SStephen Checkoway     unlink(image_path);
20760b725b6SStephen Checkoway }
20860b725b6SStephen Checkoway 
20960b725b6SStephen Checkoway int main(int argc, char **argv)
21060b725b6SStephen Checkoway {
21160b725b6SStephen Checkoway     int fd = mkstemp(image_path);
21260b725b6SStephen Checkoway     if (fd == -1) {
21360b725b6SStephen Checkoway         g_printerr("Failed to create temporary file %s: %s\n", image_path,
21460b725b6SStephen Checkoway                    strerror(errno));
21560b725b6SStephen Checkoway         exit(EXIT_FAILURE);
21660b725b6SStephen Checkoway     }
21760b725b6SStephen Checkoway     if (ftruncate(fd, 8 * 1024 * 1024) < 0) {
21860b725b6SStephen Checkoway         int error_code = errno;
21960b725b6SStephen Checkoway         close(fd);
22060b725b6SStephen Checkoway         unlink(image_path);
22160b725b6SStephen Checkoway         g_printerr("Failed to truncate file %s to 8 MB: %s\n", image_path,
22260b725b6SStephen Checkoway                    strerror(error_code));
22360b725b6SStephen Checkoway         exit(EXIT_FAILURE);
22460b725b6SStephen Checkoway     }
22560b725b6SStephen Checkoway     close(fd);
22660b725b6SStephen Checkoway 
22760b725b6SStephen Checkoway     qtest_add_abrt_handler(cleanup, NULL);
22860b725b6SStephen Checkoway     g_test_init(&argc, &argv, NULL);
22960b725b6SStephen Checkoway     qtest_add_func("pflash-cfi02", test_flash);
23060b725b6SStephen Checkoway     int result = g_test_run();
23160b725b6SStephen Checkoway     cleanup(NULL);
23260b725b6SStephen Checkoway     return result;
23360b725b6SStephen Checkoway }
234