From 60b725b6d436fadad3b96f1ca02d8d342e75bbac Mon Sep 17 00:00:00 2001 From: Stephen Checkoway Date: Fri, 26 Apr 2019 12:26:15 -0400 Subject: [PATCH 01/27] tests/pflash-cfi02: Add test for supported CFI commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Test the AMD command set for parallel flash chips. This test uses an ARM musicpal board with a pflash drive to test the following list of currently-supported commands. - Autoselect - CFI - Sector erase - Chip erase - Program - Unlock bypass - Reset Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-2-stephen.checkoway@oberlin.edu> Acked-by: Thomas Huth Reviewed-by: Philippe Mathieu-Daudé Tested-by: Philippe Mathieu-Daudé [PMD: reworded the patch subject, g_assert_cmpint -> cmphex] Signed-off-by: Philippe Mathieu-Daudé --- tests/Makefile.include | 2 + tests/pflash-cfi02-test.c | 225 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 tests/pflash-cfi02-test.c diff --git a/tests/Makefile.include b/tests/Makefile.include index db750dd6d0..d02132fb94 100644 --- a/tests/Makefile.include +++ b/tests/Makefile.include @@ -260,6 +260,7 @@ check-qtest-arm-y += tests/m25p80-test$(EXESUF) check-qtest-arm-y += tests/test-arm-mptimer$(EXESUF) check-qtest-arm-y += tests/boot-serial-test$(EXESUF) check-qtest-arm-y += tests/hexloader-test$(EXESUF) +check-qtest-arm-$(CONFIG_PFLASH_CFI02) += tests/pflash-cfi02-test$(EXESUF) check-qtest-aarch64-y = tests/numa-test$(EXESUF) check-qtest-aarch64-y += tests/boot-serial-test$(EXESUF) @@ -767,6 +768,7 @@ tests/device-introspect-test$(EXESUF): tests/device-introspect-test.o tests/rtc-test$(EXESUF): tests/rtc-test.o tests/m48t59-test$(EXESUF): tests/m48t59-test.o tests/hexloader-test$(EXESUF): tests/hexloader-test.o +tests/pflash-cfi02$(EXESUF): tests/pflash-cfi02-test.o tests/endianness-test$(EXESUF): tests/endianness-test.o tests/prom-env-test$(EXESUF): tests/prom-env-test.o $(libqos-obj-y) tests/rtas-test$(EXESUF): tests/rtas-test.o $(libqos-spapr-obj-y) diff --git a/tests/pflash-cfi02-test.c b/tests/pflash-cfi02-test.c new file mode 100644 index 0000000000..e7e16a8dd8 --- /dev/null +++ b/tests/pflash-cfi02-test.c @@ -0,0 +1,225 @@ +/* + * QTest testcase for parallel flash with AMD command set + * + * Copyright (c) 2019 Stephen Checkoway + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "libqtest.h" + +/* + * To test the pflash_cfi02 device, we run QEMU with the musicpal machine with + * a pflash drive. This enables us to test some flash configurations, but not + * all. In particular, we're limited to a 16-bit wide flash device. + */ + +#define MP_FLASH_SIZE_MAX (32 * 1024 * 1024) +#define BASE_ADDR (0x100000000ULL - MP_FLASH_SIZE_MAX) + +#define FLASH_WIDTH 2 +#define CFI_ADDR (FLASH_WIDTH * 0x55) +#define UNLOCK0_ADDR (FLASH_WIDTH * 0x5555) +#define UNLOCK1_ADDR (FLASH_WIDTH * 0x2AAA) + +#define CFI_CMD 0x98 +#define UNLOCK0_CMD 0xAA +#define UNLOCK1_CMD 0x55 +#define AUTOSELECT_CMD 0x90 +#define RESET_CMD 0xF0 +#define PROGRAM_CMD 0xA0 +#define SECTOR_ERASE_CMD 0x30 +#define CHIP_ERASE_CMD 0x10 +#define UNLOCK_BYPASS_CMD 0x20 +#define UNLOCK_BYPASS_RESET_CMD 0x00 + +static char image_path[] = "/tmp/qtest.XXXXXX"; + +static inline void flash_write(uint64_t byte_addr, uint16_t data) +{ + qtest_writew(global_qtest, BASE_ADDR + byte_addr, data); +} + +static inline uint16_t flash_read(uint64_t byte_addr) +{ + return qtest_readw(global_qtest, BASE_ADDR + byte_addr); +} + +static void unlock(void) +{ + flash_write(UNLOCK0_ADDR, UNLOCK0_CMD); + flash_write(UNLOCK1_ADDR, UNLOCK1_CMD); +} + +static void reset(void) +{ + flash_write(0, RESET_CMD); +} + +static void sector_erase(uint64_t byte_addr) +{ + unlock(); + flash_write(UNLOCK0_ADDR, 0x80); + unlock(); + flash_write(byte_addr, SECTOR_ERASE_CMD); +} + +static void wait_for_completion(uint64_t byte_addr) +{ + /* If DQ6 is toggling, step the clock and ensure the toggle stops. */ + if ((flash_read(byte_addr) & 0x40) ^ (flash_read(byte_addr) & 0x40)) { + /* Wait for erase or program to finish. */ + clock_step_next(); + /* Ensure that DQ6 has stopped toggling. */ + g_assert_cmphex(flash_read(byte_addr), ==, flash_read(byte_addr)); + } +} + +static void bypass_program(uint64_t byte_addr, uint16_t data) +{ + flash_write(UNLOCK0_ADDR, PROGRAM_CMD); + flash_write(byte_addr, data); + /* + * Data isn't valid until DQ6 stops toggling. We don't model this as + * writes are immediate, but if this changes in the future, we can wait + * until the program is complete. + */ + wait_for_completion(byte_addr); +} + +static void program(uint64_t byte_addr, uint16_t data) +{ + unlock(); + bypass_program(byte_addr, data); +} + +static void chip_erase(void) +{ + unlock(); + flash_write(UNLOCK0_ADDR, 0x80); + unlock(); + flash_write(UNLOCK0_ADDR, SECTOR_ERASE_CMD); +} + +static void test_flash(void) +{ + global_qtest = qtest_initf("-M musicpal,accel=qtest " + "-drive if=pflash,file=%s,format=raw,copy-on-read", + image_path); + /* Check the IDs. */ + unlock(); + flash_write(UNLOCK0_ADDR, AUTOSELECT_CMD); + g_assert_cmphex(flash_read(FLASH_WIDTH * 0x0000), ==, 0x00BF); + g_assert_cmphex(flash_read(FLASH_WIDTH * 0x0001), ==, 0x236D); + reset(); + + /* Check the erase blocks. */ + flash_write(CFI_ADDR, CFI_CMD); + g_assert_cmphex(flash_read(FLASH_WIDTH * 0x10), ==, 'Q'); + g_assert_cmphex(flash_read(FLASH_WIDTH * 0x11), ==, 'R'); + g_assert_cmphex(flash_read(FLASH_WIDTH * 0x12), ==, 'Y'); + /* Num erase regions. */ + g_assert_cmphex(flash_read(FLASH_WIDTH * 0x2C), >=, 1); + uint32_t nb_sectors = flash_read(FLASH_WIDTH * 0x2D) + + (flash_read(FLASH_WIDTH * 0x2E) << 8) + 1; + uint32_t sector_len = (flash_read(FLASH_WIDTH * 0x2F) << 8) + + (flash_read(FLASH_WIDTH * 0x30) << 16); + reset(); + + /* Erase and program sector. */ + for (uint32_t i = 0; i < nb_sectors; ++i) { + uint64_t byte_addr = i * sector_len; + sector_erase(byte_addr); + /* Read toggle. */ + uint16_t status0 = flash_read(byte_addr); + /* DQ7 is 0 during an erase. */ + g_assert_cmphex(status0 & 0x80, ==, 0); + uint16_t status1 = flash_read(byte_addr); + /* DQ6 toggles during an erase. */ + g_assert_cmphex(status0 & 0x40, !=, status1 & 0x40); + /* Wait for erase to complete. */ + clock_step_next(); + /* Ensure DQ6 has stopped toggling. */ + g_assert_cmphex(flash_read(byte_addr), ==, flash_read(byte_addr)); + /* Now the data should be valid. */ + g_assert_cmphex(flash_read(byte_addr), ==, 0xFFFF); + + /* Program a bit pattern. */ + program(byte_addr, 0x5555); + g_assert_cmphex(flash_read(byte_addr), ==, 0x5555); + program(byte_addr, 0xAA55); + g_assert_cmphex(flash_read(byte_addr), ==, 0x0055); + } + + /* Erase the chip. */ + chip_erase(); + /* Read toggle. */ + uint16_t status0 = flash_read(0); + /* DQ7 is 0 during an erase. */ + g_assert_cmphex(status0 & 0x80, ==, 0); + uint16_t status1 = flash_read(0); + /* DQ6 toggles during an erase. */ + g_assert_cmphex(status0 & 0x40, !=, status1 & 0x40); + /* Wait for erase to complete. */ + clock_step_next(); + /* Ensure DQ6 has stopped toggling. */ + g_assert_cmphex(flash_read(0), ==, flash_read(0)); + /* Now the data should be valid. */ + g_assert_cmphex(flash_read(0), ==, 0xFFFF); + + /* Unlock bypass */ + unlock(); + flash_write(UNLOCK0_ADDR, UNLOCK_BYPASS_CMD); + bypass_program(0, 0x0123); + bypass_program(2, 0x4567); + bypass_program(4, 0x89AB); + /* + * Test that bypass programming, unlike normal programming can use any + * address for the PROGRAM_CMD. + */ + flash_write(6, PROGRAM_CMD); + flash_write(6, 0xCDEF); + wait_for_completion(6); + flash_write(0, UNLOCK_BYPASS_RESET_CMD); + bypass_program(8, 0x55AA); /* Should fail. */ + g_assert_cmphex(flash_read(0), ==, 0x0123); + g_assert_cmphex(flash_read(2), ==, 0x4567); + g_assert_cmphex(flash_read(4), ==, 0x89AB); + g_assert_cmphex(flash_read(6), ==, 0xCDEF); + g_assert_cmphex(flash_read(8), ==, 0xFFFF); + + qtest_quit(global_qtest); +} + +static void cleanup(void *opaque) +{ + unlink(image_path); +} + +int main(int argc, char **argv) +{ + int fd = mkstemp(image_path); + if (fd == -1) { + g_printerr("Failed to create temporary file %s: %s\n", image_path, + strerror(errno)); + exit(EXIT_FAILURE); + } + if (ftruncate(fd, 8 * 1024 * 1024) < 0) { + int error_code = errno; + close(fd); + unlink(image_path); + g_printerr("Failed to truncate file %s to 8 MB: %s\n", image_path, + strerror(error_code)); + exit(EXIT_FAILURE); + } + close(fd); + + qtest_add_abrt_handler(cleanup, NULL); + g_test_init(&argc, &argv, NULL); + qtest_add_func("pflash-cfi02", test_flash); + int result = g_test_run(); + cleanup(NULL); + return result; +} From e8aa2d95ea2015ce95d712543c78e09a0130be3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 26 Jun 2019 18:39:10 +0200 Subject: [PATCH 02/27] hw/block/pflash: Simplify trace_pflash_io_read/write() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Call the read() trace function after the value is set, so we can log the returned value. Rename the I/O trace functions with '_io_' in their name. Reviewed-by: Stephen Checkoway Reviewed-by: Alistair Francis Message-Id: <20190627202719.17739-3-philmd@redhat.com> Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi01.c | 5 +++-- hw/block/pflash_cfi02.c | 6 ++---- hw/block/trace-events | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/hw/block/pflash_cfi01.c b/hw/block/pflash_cfi01.c index 35080d915f..74fc1bc2da 100644 --- a/hw/block/pflash_cfi01.c +++ b/hw/block/pflash_cfi01.c @@ -288,7 +288,6 @@ static uint32_t pflash_read(PFlashCFI01 *pfl, hwaddr offset, uint32_t ret; ret = -1; - trace_pflash_read(offset, pfl->cmd, width, pfl->wcycle); switch (pfl->cmd) { default: /* This should never happen : reset state & treat it as a read */ @@ -391,6 +390,8 @@ static uint32_t pflash_read(PFlashCFI01 *pfl, hwaddr offset, break; } + trace_pflash_io_read(offset, width, width << 1, ret, pfl->cmd, pfl->wcycle); + return ret; } @@ -453,7 +454,7 @@ static void pflash_write(PFlashCFI01 *pfl, hwaddr offset, cmd = value; - trace_pflash_write(offset, value, width, pfl->wcycle); + trace_pflash_io_write(offset, width, width << 1, value, pfl->wcycle); if (!pfl->wcycle) { /* Set the device in I/O access mode */ memory_region_rom_device_set_romd(&pfl->mem, false); diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index eb106f4996..f05cd507b3 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -145,7 +145,6 @@ static uint32_t pflash_read(PFlashCFI02 *pfl, hwaddr offset, uint8_t *p; ret = -1; - trace_pflash_read(offset, pfl->cmd, width, pfl->wcycle); /* Lazy reset to ROMD mode after a certain amount of read accesses */ if (!pfl->rom_mode && pfl->wcycle == 0 && ++pfl->read_counter > PFLASH_LAZY_ROMD_THRESHOLD) { @@ -241,6 +240,7 @@ static uint32_t pflash_read(PFlashCFI02 *pfl, hwaddr offset, } break; } + trace_pflash_io_read(offset, width, width << 1, ret, pfl->cmd, pfl->wcycle); return ret; } @@ -267,6 +267,7 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset, uint8_t *p; uint8_t cmd; + trace_pflash_io_write(offset, width, width << 1, value, pfl->wcycle); cmd = value; if (pfl->cmd != 0xA0 && cmd == 0xF0) { #if 0 @@ -275,11 +276,8 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset, #endif goto reset_flash; } - trace_pflash_write(offset, value, width, pfl->wcycle); offset &= pfl->chip_len - 1; - DPRINTF("%s: offset " TARGET_FMT_plx " %08x %d\n", __func__, - offset, value, width); boff = offset & (pfl->sector_len - 1); if (pfl->width == 2) boff = boff >> 1; diff --git a/hw/block/trace-events b/hw/block/trace-events index 97a17838ed..f637fe918e 100644 --- a/hw/block/trace-events +++ b/hw/block/trace-events @@ -7,9 +7,9 @@ fdc_ioport_write(uint8_t reg, uint8_t value) "write reg 0x%02x val 0x%02x" # pflash_cfi02.c # pflash_cfi01.c pflash_reset(void) "reset" -pflash_read(uint64_t offset, uint8_t cmd, int width, uint8_t wcycle) "offset:0x%04"PRIx64" cmd:0x%02x width:%d wcycle:%u" -pflash_write(uint64_t offset, uint32_t value, int width, uint8_t wcycle) "offset:0x%04"PRIx64" value:0x%03x width:%d wcycle:%u" pflash_timer_expired(uint8_t cmd) "command 0x%02x done" +pflash_io_read(uint64_t offset, int width, int fmt_width, uint32_t value, uint8_t cmd, uint8_t wcycle) "offset:0x%04"PRIx64" width:%d value:0x%0*x cmd:0x%02x wcycle:%u" +pflash_io_write(uint64_t offset, int width, int fmt_width, uint32_t value, uint8_t wcycle) "offset:0x%04"PRIx64" width:%d value:0x%0*x wcycle:%u" pflash_data_read8(uint64_t offset, uint32_t value) "data offset:0x%04"PRIx64" value:0x%02x" pflash_data_read16(uint64_t offset, uint32_t value) "data offset:0x%04"PRIx64" value:0x%04x" pflash_data_read32(uint64_t offset, uint32_t value) "data offset:0x%04"PRIx64" value:0x%08x" From c1474acd5d284d69d1543713ef00b90b9712619f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 26 Jun 2019 18:40:09 +0200 Subject: [PATCH 03/27] hw/block/pflash: Simplify trace_pflash_data_read/write() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use a field width format to have a single function to log the different width accesses. Reviewed-by: Alistair Francis Message-Id: <20190627202719.17739-4-philmd@redhat.com> Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi01.c | 6 ++---- hw/block/pflash_cfi02.c | 6 ++---- hw/block/trace-events | 6 ++---- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/hw/block/pflash_cfi01.c b/hw/block/pflash_cfi01.c index 74fc1bc2da..db4a246b22 100644 --- a/hw/block/pflash_cfi01.c +++ b/hw/block/pflash_cfi01.c @@ -248,7 +248,6 @@ static uint32_t pflash_data_read(PFlashCFI01 *pfl, hwaddr offset, switch (width) { case 1: ret = p[offset]; - trace_pflash_data_read8(offset, ret); break; case 2: if (be) { @@ -258,7 +257,6 @@ static uint32_t pflash_data_read(PFlashCFI01 *pfl, hwaddr offset, ret = p[offset]; ret |= p[offset + 1] << 8; } - trace_pflash_data_read16(offset, ret); break; case 4: if (be) { @@ -272,12 +270,12 @@ static uint32_t pflash_data_read(PFlashCFI01 *pfl, hwaddr offset, ret |= p[offset + 2] << 16; ret |= p[offset + 3] << 24; } - trace_pflash_data_read32(offset, ret); break; default: DPRINTF("BUG in %s\n", __func__); abort(); } + trace_pflash_data_read(offset, width << 1, ret); return ret; } @@ -415,7 +413,7 @@ static inline void pflash_data_write(PFlashCFI01 *pfl, hwaddr offset, { uint8_t *p = pfl->storage; - trace_pflash_data_write(offset, value, width, pfl->counter); + trace_pflash_data_write(offset, width << 1, value, pfl->counter); switch (width) { case 1: p[offset] = value; diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index f05cd507b3..6cdfc85264 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -172,7 +172,6 @@ static uint32_t pflash_read(PFlashCFI02 *pfl, hwaddr offset, switch (width) { case 1: ret = p[offset]; - trace_pflash_data_read8(offset, ret); break; case 2: if (be) { @@ -182,7 +181,6 @@ static uint32_t pflash_read(PFlashCFI02 *pfl, hwaddr offset, ret = p[offset]; ret |= p[offset + 1] << 8; } - trace_pflash_data_read16(offset, ret); break; case 4: if (be) { @@ -196,9 +194,9 @@ static uint32_t pflash_read(PFlashCFI02 *pfl, hwaddr offset, ret |= p[offset + 2] << 16; ret |= p[offset + 3] << 24; } - trace_pflash_data_read32(offset, ret); break; } + trace_pflash_data_read(offset, width << 1, ret); break; case 0x90: /* flash ID read */ @@ -343,7 +341,7 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset, /* We need another unlock sequence */ goto check_unlock0; case 0xA0: - trace_pflash_data_write(offset, value, width, 0); + trace_pflash_data_write(offset, width << 1, value, 0); p = pfl->storage; if (!pfl->ro) { switch (width) { diff --git a/hw/block/trace-events b/hw/block/trace-events index f637fe918e..13d1b21dd4 100644 --- a/hw/block/trace-events +++ b/hw/block/trace-events @@ -10,10 +10,8 @@ pflash_reset(void) "reset" pflash_timer_expired(uint8_t cmd) "command 0x%02x done" pflash_io_read(uint64_t offset, int width, int fmt_width, uint32_t value, uint8_t cmd, uint8_t wcycle) "offset:0x%04"PRIx64" width:%d value:0x%0*x cmd:0x%02x wcycle:%u" pflash_io_write(uint64_t offset, int width, int fmt_width, uint32_t value, uint8_t wcycle) "offset:0x%04"PRIx64" width:%d value:0x%0*x wcycle:%u" -pflash_data_read8(uint64_t offset, uint32_t value) "data offset:0x%04"PRIx64" value:0x%02x" -pflash_data_read16(uint64_t offset, uint32_t value) "data offset:0x%04"PRIx64" value:0x%04x" -pflash_data_read32(uint64_t offset, uint32_t value) "data offset:0x%04"PRIx64" value:0x%08x" -pflash_data_write(uint64_t offset, uint32_t value, int width, uint64_t counter) "data offset:0x%04"PRIx64" value:0x%08x width:%d counter:0x%016"PRIx64 +pflash_data_read(uint64_t offset, int width, uint32_t value) "data offset:0x%04"PRIx64" value:0x%0*x" +pflash_data_write(uint64_t offset, int width, uint32_t value, uint64_t counter) "data offset:0x%04"PRIx64" value:0x%0*x counter:0x%016"PRIx64 pflash_manufacturer_id(uint16_t id) "Read Manufacturer ID: 0x%04x" pflash_device_id(uint16_t id) "Read Device ID: 0x%04x" pflash_device_info(uint64_t offset) "Read Device Information offset:0x%04"PRIx64 From 6536987fd6aa52adbb6c128db1952e7c62f5c82b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 1 May 2019 18:15:35 +0200 Subject: [PATCH 04/27] hw/block/pflash_cfi02: Fix debug format string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Always compile the debug code to prevent format string to bitrot. Delete dead code. Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-3-stephen.checkoway@oberlin.edu> Reviewed-by: Philippe Mathieu-Daudé Tested-by: Philippe Mathieu-Daudé [PMD: Extracted from bigger patch, use PRIx32] Reviewed-by: Alistair Francis Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index 6cdfc85264..43796e551a 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -47,15 +47,13 @@ #include "hw/sysbus.h" #include "trace.h" -//#define PFLASH_DEBUG -#ifdef PFLASH_DEBUG +#define PFLASH_DEBUG false #define DPRINTF(fmt, ...) \ do { \ - fprintf(stderr, "PFLASH: " fmt , ## __VA_ARGS__); \ + if (PFLASH_DEBUG) { \ + fprintf(stderr, "PFLASH: " fmt, ## __VA_ARGS__); \ + } \ } while (0) -#else -#define DPRINTF(fmt, ...) do { } while (0) -#endif #define PFLASH_LAZY_ROMD_THRESHOLD 42 @@ -218,14 +216,14 @@ static uint32_t pflash_read(PFlashCFI02 *pfl, hwaddr offset, default: goto flash_read; } - DPRINTF("%s: ID " TARGET_FMT_plx " %x\n", __func__, boff, ret); + DPRINTF("%s: ID " TARGET_FMT_plx " %" PRIx32 "\n", __func__, boff, ret); break; case 0xA0: case 0x10: case 0x30: /* Status register read */ ret = pfl->status; - DPRINTF("%s: status %x\n", __func__, ret); + DPRINTF("%s: status %" PRIx32 "\n", __func__, ret); /* Toggle bit 6 */ pfl->status ^= 0x40; break; @@ -268,10 +266,6 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset, trace_pflash_io_write(offset, width, width << 1, value, pfl->wcycle); cmd = value; if (pfl->cmd != 0xA0 && cmd == 0xF0) { -#if 0 - DPRINTF("%s: flash reset asked (%02x %02x)\n", - __func__, pfl->cmd, cmd); -#endif goto reset_flash; } offset &= pfl->chip_len - 1; From aeaf6c20dbcfed5c459d9f6b39cb5fa2187f0b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 5 May 2019 22:42:08 +0200 Subject: [PATCH 05/27] hw/block/pflash_cfi02: Add an enum to define the write cycles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No change in functionality is intended with this commit. Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-3-stephen.checkoway@oberlin.edu> Reviewed-by: Philippe Mathieu-Daudé Tested-by: Philippe Mathieu-Daudé [PMD: Extracted from bigger patch] Reviewed-by: Alistair Francis Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index 43796e551a..303d225f23 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -57,6 +57,11 @@ do { \ #define PFLASH_LAZY_ROMD_THRESHOLD 42 +/* Special write cycles for CFI queries. */ +enum { + WCYCLE_CFI = 7, +}; + struct PFlashCFI02 { /*< private >*/ SysBusDevice parent_obj; @@ -286,7 +291,7 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset, if (boff == 0x55 && cmd == 0x98) { enter_CFI_mode: /* Enter CFI query mode */ - pfl->wcycle = 7; + pfl->wcycle = WCYCLE_CFI; pfl->cmd = 0x98; return; } @@ -458,7 +463,8 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset, goto reset_flash; } break; - case 7: /* Special value for CFI queries */ + /* Special values for CFI queries */ + case WCYCLE_CFI: DPRINTF("%s: invalid write in CFI query mode\n", __func__); goto reset_flash; default: From 1d311e738b223452bbfb8ce10cc3327db7bca3ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 1 May 2019 18:14:25 +0200 Subject: [PATCH 06/27] hw/block/pflash_cfi02: Add helpers to manipulate the status bits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pull out all of the code to modify the status into simple helper functions. Status handling becomes more complex once multiple chips are interleaved to produce a single device. No change in functionality is intended with this commit. Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-3-stephen.checkoway@oberlin.edu> Reviewed-by: Philippe Mathieu-Daudé Tested-by: Philippe Mathieu-Daudé [PMD: Extracted from bigger patch] Reviewed-by: Alistair Francis Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index 303d225f23..e9eea0ec08 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -101,6 +101,31 @@ struct PFlashCFI02 { void *storage; }; +/* + * Toggle status bit DQ7. + */ +static inline void toggle_dq7(PFlashCFI02 *pfl) +{ + pfl->status ^= 0x80; +} + +/* + * Set status bit DQ7 to bit 7 of value. + */ +static inline void set_dq7(PFlashCFI02 *pfl, uint8_t value) +{ + pfl->status &= 0x7F; + pfl->status |= value & 0x80; +} + +/* + * Toggle status bit DQ6. + */ +static inline void toggle_dq6(PFlashCFI02 *pfl) +{ + pfl->status ^= 0x40; +} + /* * Set up replicated mappings of the same region. */ @@ -130,7 +155,7 @@ static void pflash_timer (void *opaque) trace_pflash_timer_expired(pfl->cmd); /* Reset flash */ - pfl->status ^= 0x80; + toggle_dq7(pfl); if (pfl->bypass) { pfl->wcycle = 2; } else { @@ -229,8 +254,7 @@ static uint32_t pflash_read(PFlashCFI02 *pfl, hwaddr offset, /* Status register read */ ret = pfl->status; DPRINTF("%s: status %" PRIx32 "\n", __func__, ret); - /* Toggle bit 6 */ - pfl->status ^= 0x40; + toggle_dq6(pfl); break; case 0x98: /* CFI query mode */ @@ -374,7 +398,11 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset, break; } } - pfl->status = 0x00 | ~(value & 0x80); + /* + * While programming, status bit DQ7 should hold the opposite + * value from how it was programmed. + */ + set_dq7(pfl, ~value); /* Let's pretend write is immediate */ if (pfl->bypass) goto do_bypass; @@ -422,7 +450,7 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset, memset(pfl->storage, 0xFF, pfl->chip_len); pflash_update(pfl, 0, pfl->chip_len); } - pfl->status = 0x00; + set_dq7(pfl, 0x00); /* Let's wait 5 seconds before chip erase is done */ timer_mod(&pfl->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + (NANOSECONDS_PER_SECOND * 5)); @@ -437,7 +465,7 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset, memset(p + offset, 0xFF, pfl->sector_len); pflash_update(pfl, offset, pfl->sector_len); } - pfl->status = 0x00; + set_dq7(pfl, 0x00); /* Let's wait 1/2 second before sector erase is done */ timer_mod(&pfl->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + (NANOSECONDS_PER_SECOND / 2)); From 7f7bdcaff5ad88a79257d6b28f95d0491b7f9002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 5 May 2019 23:04:48 +0200 Subject: [PATCH 07/27] hw/block/pflash_cfi02: Simplify a statement using fall through MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-3-stephen.checkoway@oberlin.edu> Reviewed-by: Philippe Mathieu-Daudé Tested-by: Philippe Mathieu-Daudé [PMD: Extracted from bigger patch] Reviewed-by: Alistair Francis Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index e9eea0ec08..9e8c28af8f 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -239,10 +239,10 @@ static uint32_t pflash_read(PFlashCFI02 *pfl, hwaddr offset, case 0x0E: case 0x0F: ret = boff & 0x01 ? pfl->ident3 : pfl->ident2; - if (ret == (uint8_t)-1) { - goto flash_read; + if (ret != (uint8_t)-1) { + break; } - break; + /* Fall through to data read. */ default: goto flash_read; } From c3d25271b2117a1416f5818d9d2b399b4e1e77b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 5 May 2019 23:14:29 +0200 Subject: [PATCH 08/27] hw/block/pflash_cfi02: Use the ldst API in pflash_write() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The load/store API eases code review. Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-3-stephen.checkoway@oberlin.edu> Reviewed-by: Philippe Mathieu-Daudé Tested-by: Philippe Mathieu-Daudé [PMD: Extracted from bigger patch] Reviewed-by: Alistair Francis Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 38 ++++++++------------------------------ 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index 9e8c28af8f..ae38ed0bae 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -365,38 +365,16 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset, goto check_unlock0; case 0xA0: trace_pflash_data_write(offset, width << 1, value, 0); - p = pfl->storage; if (!pfl->ro) { - switch (width) { - case 1: - p[offset] &= value; - pflash_update(pfl, offset, 1); - break; - case 2: - if (be) { - p[offset] &= value >> 8; - p[offset + 1] &= value; - } else { - p[offset] &= value; - p[offset + 1] &= value >> 8; - } - pflash_update(pfl, offset, 2); - break; - case 4: - if (be) { - p[offset] &= value >> 24; - p[offset + 1] &= value >> 16; - p[offset + 2] &= value >> 8; - p[offset + 3] &= value; - } else { - p[offset] &= value; - p[offset + 1] &= value >> 8; - p[offset + 2] &= value >> 16; - p[offset + 3] &= value >> 24; - } - pflash_update(pfl, offset, 4); - break; + p = (uint8_t *)pfl->storage + offset; + if (pfl->be) { + uint64_t current = ldn_be_p(p, width); + stn_be_p(p, width, current & value); + } else { + uint64_t current = ldn_le_p(p, width); + stn_le_p(p, width, current & value); } + pflash_update(pfl, offset, width); } /* * While programming, status bit DQ7 should hold the opposite From 3e4bcf89b7acea62e248ee048c1c67767be88f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 5 May 2019 23:21:19 +0200 Subject: [PATCH 09/27] hw/block/pflash_cfi02: Use the ldst API in pflash_read() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The load/store API eases code review. Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-3-stephen.checkoway@oberlin.edu> Reviewed-by: Philippe Mathieu-Daudé Tested-by: Philippe Mathieu-Daudé [PMD: Extracted from bigger patch, simplified tracing] Reviewed-by: Alistair Francis Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 32 +++++--------------------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index ae38ed0bae..49afecb921 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -196,33 +196,11 @@ static uint32_t pflash_read(PFlashCFI02 *pfl, hwaddr offset, case 0x00: flash_read: /* Flash area read */ - p = pfl->storage; - switch (width) { - case 1: - ret = p[offset]; - break; - case 2: - if (be) { - ret = p[offset] << 8; - ret |= p[offset + 1]; - } else { - ret = p[offset]; - ret |= p[offset + 1] << 8; - } - break; - case 4: - if (be) { - ret = p[offset] << 24; - ret |= p[offset + 1] << 16; - ret |= p[offset + 2] << 8; - ret |= p[offset + 3]; - } else { - ret = p[offset]; - ret |= p[offset + 1] << 8; - ret |= p[offset + 2] << 16; - ret |= p[offset + 3] << 24; - } - break; + p = (uint8_t *)pfl->storage + offset; + if (pfl->be) { + ret = ldn_be_p(p, width); + } else { + ret = ldn_le_p(p, width); } trace_pflash_data_read(offset, width << 1, ret); break; From 06e8b8e3e1bb5f2d189197b050b23d9da3955a41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sun, 5 May 2019 23:24:51 +0200 Subject: [PATCH 10/27] hw/block/pflash_cfi02: Extract the pflash_data_read() function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract the code block in a new function, remove a goto statement. Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-3-stephen.checkoway@oberlin.edu> Reviewed-by: Philippe Mathieu-Daudé Tested-by: Philippe Mathieu-Daudé [PMD: Extracted from bigger patch, remove the XXX tracing comment] Reviewed-by: Alistair Francis Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index 49afecb921..c079a63880 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -165,12 +165,23 @@ static void pflash_timer (void *opaque) pfl->cmd = 0; } +/* + * Read data from flash. + */ +static uint64_t pflash_data_read(PFlashCFI02 *pfl, hwaddr offset, + unsigned int width) +{ + uint8_t *p = (uint8_t *)pfl->storage + offset; + uint64_t ret = pfl->be ? ldn_be_p(p, width) : ldn_le_p(p, width); + trace_pflash_data_read(offset, width << 1, ret); + return ret; +} + static uint32_t pflash_read(PFlashCFI02 *pfl, hwaddr offset, int width, int be) { hwaddr boff; uint32_t ret; - uint8_t *p; ret = -1; /* Lazy reset to ROMD mode after a certain amount of read accesses */ @@ -194,15 +205,8 @@ static uint32_t pflash_read(PFlashCFI02 *pfl, hwaddr offset, case 0x80: /* We accept reads during second unlock sequence... */ case 0x00: - flash_read: /* Flash area read */ - p = (uint8_t *)pfl->storage + offset; - if (pfl->be) { - ret = ldn_be_p(p, width); - } else { - ret = ldn_le_p(p, width); - } - trace_pflash_data_read(offset, width << 1, ret); + ret = pflash_data_read(pfl, offset, width); break; case 0x90: /* flash ID read */ @@ -222,7 +226,7 @@ static uint32_t pflash_read(PFlashCFI02 *pfl, hwaddr offset, } /* Fall through to data read. */ default: - goto flash_read; + ret = pflash_data_read(pfl, offset, width); } DPRINTF("%s: ID " TARGET_FMT_plx " %" PRIx32 "\n", __func__, boff, ret); break; From aff498cf30363788c8a248ec961829488d37f65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 1 May 2019 18:15:56 +0200 Subject: [PATCH 11/27] hw/block/pflash_cfi02: Unify the MemoryRegionOps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pflash_read()/pflash_write() can check the device endianess via the pfl->be variable, so remove the 'int be' argument. Since the big/little MemoryRegionOps are now identical, it is pointless to declare them both. Unify them. Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-3-stephen.checkoway@oberlin.edu> Reviewed-by: Philippe Mathieu-Daudé Tested-by: Philippe Mathieu-Daudé [PMD: Extracted from bigger patch to ease review] Reviewed-by: Alistair Francis Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 60 +++++++++++------------------------------ 1 file changed, 15 insertions(+), 45 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index c079a63880..e64dc69c6c 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -177,11 +177,11 @@ static uint64_t pflash_data_read(PFlashCFI02 *pfl, hwaddr offset, return ret; } -static uint32_t pflash_read(PFlashCFI02 *pfl, hwaddr offset, - int width, int be) +static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width) { + PFlashCFI02 *pfl = opaque; hwaddr boff; - uint32_t ret; + uint64_t ret; ret = -1; /* Lazy reset to ROMD mode after a certain amount of read accesses */ @@ -228,14 +228,14 @@ static uint32_t pflash_read(PFlashCFI02 *pfl, hwaddr offset, default: ret = pflash_data_read(pfl, offset, width); } - DPRINTF("%s: ID " TARGET_FMT_plx " %" PRIx32 "\n", __func__, boff, ret); + DPRINTF("%s: ID " TARGET_FMT_plx " %" PRIx64 "\n", __func__, boff, ret); break; case 0xA0: case 0x10: case 0x30: /* Status register read */ ret = pfl->status; - DPRINTF("%s: status %" PRIx32 "\n", __func__, ret); + DPRINTF("%s: status %" PRIx64 "\n", __func__, ret); toggle_dq6(pfl); break; case 0x98: @@ -253,8 +253,7 @@ static uint32_t pflash_read(PFlashCFI02 *pfl, hwaddr offset, } /* update flash content on disk */ -static void pflash_update(PFlashCFI02 *pfl, int offset, - int size) +static void pflash_update(PFlashCFI02 *pfl, int offset, int size) { int offset_end; if (pfl->blk) { @@ -267,9 +266,10 @@ static void pflash_update(PFlashCFI02 *pfl, int offset, } } -static void pflash_write(PFlashCFI02 *pfl, hwaddr offset, - uint32_t value, int width, int be) +static void pflash_write(void *opaque, hwaddr offset, uint64_t value, + unsigned int width) { + PFlashCFI02 *pfl = opaque; hwaddr boff; uint8_t *p; uint8_t cmd; @@ -477,39 +477,9 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset, pfl->cmd = 0; } -static uint64_t pflash_be_readfn(void *opaque, hwaddr addr, unsigned size) -{ - return pflash_read(opaque, addr, size, 1); -} - -static void pflash_be_writefn(void *opaque, hwaddr addr, - uint64_t value, unsigned size) -{ - pflash_write(opaque, addr, value, size, 1); -} - -static uint64_t pflash_le_readfn(void *opaque, hwaddr addr, unsigned size) -{ - return pflash_read(opaque, addr, size, 0); -} - -static void pflash_le_writefn(void *opaque, hwaddr addr, - uint64_t value, unsigned size) -{ - pflash_write(opaque, addr, value, size, 0); -} - -static const MemoryRegionOps pflash_cfi02_ops_be = { - .read = pflash_be_readfn, - .write = pflash_be_writefn, - .valid.min_access_size = 1, - .valid.max_access_size = 4, - .endianness = DEVICE_NATIVE_ENDIAN, -}; - -static const MemoryRegionOps pflash_cfi02_ops_le = { - .read = pflash_le_readfn, - .write = pflash_le_writefn, +static const MemoryRegionOps pflash_cfi02_ops = { + .read = pflash_read, + .write = pflash_write, .valid.min_access_size = 1, .valid.max_access_size = 4, .endianness = DEVICE_NATIVE_ENDIAN, @@ -537,9 +507,9 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) chip_len = pfl->sector_len * pfl->nb_blocs; - memory_region_init_rom_device(&pfl->orig_mem, OBJECT(pfl), pfl->be ? - &pflash_cfi02_ops_be : &pflash_cfi02_ops_le, - pfl, pfl->name, chip_len, &local_err); + memory_region_init_rom_device(&pfl->orig_mem, OBJECT(pfl), + &pflash_cfi02_ops, pfl, pfl->name, + chip_len, &local_err); if (local_err) { error_propagate(errp, local_err); return; From 6682bc1ee431dc6198b85ac71d537104cfc57fed Mon Sep 17 00:00:00 2001 From: Stephen Checkoway Date: Fri, 26 Apr 2019 12:26:17 -0400 Subject: [PATCH 12/27] hw/block/pflash_cfi02: Fix command address comparison MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Most AMD commands only examine 11 bits of the address. This masks the addresses used in the comparison to 11 bits. The exceptions are word or sector addresses which use offset directly rather than the shifted offset, boff. Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-4-stephen.checkoway@oberlin.edu> Acked-by: Thomas Huth Acked-by: Alistair Francis Acked-by: Philippe Mathieu-Daudé Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 8 +++++++- tests/pflash-cfi02-test.c | 12 ++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index e64dc69c6c..4be3837be5 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -281,11 +281,13 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, } offset &= pfl->chip_len - 1; - boff = offset & (pfl->sector_len - 1); + boff = offset; if (pfl->width == 2) boff = boff >> 1; else if (pfl->width == 4) boff = boff >> 2; + /* Only the least-significant 11 bits are used in most cases. */ + boff &= 0x7FF; switch (pfl->wcycle) { case 0: /* Set the device in I/O access mode if required */ @@ -538,6 +540,10 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) } } + /* Only 11 bits are used in the comparison. */ + pfl->unlock_addr0 &= 0x7FF; + pfl->unlock_addr1 &= 0x7FF; + pflash_setup_mappings(pfl); pfl->rom_mode = 1; sysbus_init_mmio(SYS_BUS_DEVICE(dev), &pfl->mem); diff --git a/tests/pflash-cfi02-test.c b/tests/pflash-cfi02-test.c index e7e16a8dd8..e090b2e3a0 100644 --- a/tests/pflash-cfi02-test.c +++ b/tests/pflash-cfi02-test.c @@ -21,8 +21,8 @@ #define FLASH_WIDTH 2 #define CFI_ADDR (FLASH_WIDTH * 0x55) -#define UNLOCK0_ADDR (FLASH_WIDTH * 0x5555) -#define UNLOCK1_ADDR (FLASH_WIDTH * 0x2AAA) +#define UNLOCK0_ADDR (FLASH_WIDTH * 0x555) +#define UNLOCK1_ADDR (FLASH_WIDTH * 0x2AA) #define CFI_CMD 0x98 #define UNLOCK0_CMD 0xAA @@ -190,6 +190,14 @@ static void test_flash(void) g_assert_cmphex(flash_read(6), ==, 0xCDEF); g_assert_cmphex(flash_read(8), ==, 0xFFFF); + /* Test ignored high order bits of address. */ + flash_write(FLASH_WIDTH * 0x5555, UNLOCK0_CMD); + flash_write(FLASH_WIDTH * 0x2AAA, UNLOCK1_CMD); + flash_write(FLASH_WIDTH * 0x5555, AUTOSELECT_CMD); + g_assert_cmpint(flash_read(FLASH_WIDTH * 0x0000), ==, 0x00BF); + g_assert_cmpint(flash_read(FLASH_WIDTH * 0x0001), ==, 0x236D); + reset(); + qtest_quit(global_qtest); } From 91d0231213629674c3a551d900c1b79a8f520caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Wed, 26 Jun 2019 21:54:46 +0200 Subject: [PATCH 13/27] tests/pflash-cfi02: Refactor to support testing multiple configurations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce the FlashConfig structure, to be able to run the same set of tests on different flash models/configurations. Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-6-stephen.checkoway@oberlin.edu> Reviewed-by: Philippe Mathieu-Daudé Tested-by: Philippe Mathieu-Daudé [PMD: Extracted from bigger patch] Reviewed-by: Alistair Francis Signed-off-by: Philippe Mathieu-Daudé --- tests/pflash-cfi02-test.c | 386 +++++++++++++++++++++++++++----------- 1 file changed, 277 insertions(+), 109 deletions(-) diff --git a/tests/pflash-cfi02-test.c b/tests/pflash-cfi02-test.c index e090b2e3a0..b00f5ca2e7 100644 --- a/tests/pflash-cfi02-test.c +++ b/tests/pflash-cfi02-test.c @@ -17,12 +17,18 @@ */ #define MP_FLASH_SIZE_MAX (32 * 1024 * 1024) +#define FLASH_SIZE (8 * 1024 * 1024) #define BASE_ADDR (0x100000000ULL - MP_FLASH_SIZE_MAX) -#define FLASH_WIDTH 2 -#define CFI_ADDR (FLASH_WIDTH * 0x55) -#define UNLOCK0_ADDR (FLASH_WIDTH * 0x555) -#define UNLOCK1_ADDR (FLASH_WIDTH * 0x2AA) +/* Use a newtype to keep flash addresses separate from byte addresses. */ +typedef struct { + uint64_t addr; +} faddr; +#define FLASH_ADDR(x) ((faddr) { .addr = (x) }) + +#define CFI_ADDR FLASH_ADDR(0x55) +#define UNLOCK0_ADDR FLASH_ADDR(0x555) +#define UNLOCK1_ADDR FLASH_ADDR(0x2AA) #define CFI_CMD 0x98 #define UNLOCK0_CMD 0xAA @@ -35,170 +41,313 @@ #define UNLOCK_BYPASS_CMD 0x20 #define UNLOCK_BYPASS_RESET_CMD 0x00 +typedef struct { + int bank_width; + + QTestState *qtest; +} FlashConfig; + static char image_path[] = "/tmp/qtest.XXXXXX"; -static inline void flash_write(uint64_t byte_addr, uint16_t data) +/* + * The pflash implementation allows some parameters to be unspecified. We want + * to test those configurations but we also need to know the real values in + * our testing code. So after we launch qemu, we'll need a new FlashConfig + * with the correct values filled in. + */ +static FlashConfig expand_config_defaults(const FlashConfig *c) { - qtest_writew(global_qtest, BASE_ADDR + byte_addr, data); + FlashConfig ret = *c; + + if (ret.bank_width == 0) { + ret.bank_width = 2; + } + + /* XXX: Limitations of test harness. */ + assert(ret.bank_width == 2); + return ret; } -static inline uint16_t flash_read(uint64_t byte_addr) +/* + * Return a bit mask suitable for extracting the least significant + * status/query response from an interleaved response. + */ +static inline uint64_t device_mask(const FlashConfig *c) { - return qtest_readw(global_qtest, BASE_ADDR + byte_addr); + return (uint64_t)-1; } -static void unlock(void) +/* + * Return a bit mask exactly as long as the bank_width. + */ +static inline uint64_t bank_mask(const FlashConfig *c) { - flash_write(UNLOCK0_ADDR, UNLOCK0_CMD); - flash_write(UNLOCK1_ADDR, UNLOCK1_CMD); + if (c->bank_width == 8) { + return (uint64_t)-1; + } + return (1ULL << (c->bank_width * 8)) - 1ULL; } -static void reset(void) +static inline void flash_write(const FlashConfig *c, uint64_t byte_addr, + uint64_t data) { - flash_write(0, RESET_CMD); -} - -static void sector_erase(uint64_t byte_addr) -{ - unlock(); - flash_write(UNLOCK0_ADDR, 0x80); - unlock(); - flash_write(byte_addr, SECTOR_ERASE_CMD); -} - -static void wait_for_completion(uint64_t byte_addr) -{ - /* If DQ6 is toggling, step the clock and ensure the toggle stops. */ - if ((flash_read(byte_addr) & 0x40) ^ (flash_read(byte_addr) & 0x40)) { - /* Wait for erase or program to finish. */ - clock_step_next(); - /* Ensure that DQ6 has stopped toggling. */ - g_assert_cmphex(flash_read(byte_addr), ==, flash_read(byte_addr)); + /* Sanity check our tests. */ + assert((data & ~bank_mask(c)) == 0); + uint64_t addr = BASE_ADDR + byte_addr; + switch (c->bank_width) { + case 1: + qtest_writeb(c->qtest, addr, data); + break; + case 2: + qtest_writew(c->qtest, addr, data); + break; + case 4: + qtest_writel(c->qtest, addr, data); + break; + case 8: + qtest_writeq(c->qtest, addr, data); + break; + default: + abort(); } } -static void bypass_program(uint64_t byte_addr, uint16_t data) +static inline uint64_t flash_read(const FlashConfig *c, uint64_t byte_addr) { - flash_write(UNLOCK0_ADDR, PROGRAM_CMD); - flash_write(byte_addr, data); + uint64_t addr = BASE_ADDR + byte_addr; + switch (c->bank_width) { + case 1: + return qtest_readb(c->qtest, addr); + case 2: + return qtest_readw(c->qtest, addr); + case 4: + return qtest_readl(c->qtest, addr); + case 8: + return qtest_readq(c->qtest, addr); + default: + abort(); + } +} + +/* + * Convert a flash address expressed in the maximum width of the device as a + * byte address. + */ +static inline uint64_t as_byte_addr(const FlashConfig *c, faddr flash_addr) +{ + /* + * Command addresses are always given as addresses in the maximum + * supported bus size for the flash chip. So an x8/x16 chip in x8 mode + * uses addresses 0xAAA and 0x555 to unlock because the least significant + * bit is ignored. (0x555 rather than 0x554 is traditional.) + * + * In general we need to multiply by the maximum device width. + */ + return flash_addr.addr * c->bank_width; +} + +/* + * Return the command value or expected status replicated across all devices. + */ +static inline uint64_t replicate(const FlashConfig *c, uint64_t data) +{ + /* Sanity check our tests. */ + assert((data & ~device_mask(c)) == 0); + return data; +} + +static inline void flash_cmd(const FlashConfig *c, faddr cmd_addr, + uint8_t cmd) +{ + flash_write(c, as_byte_addr(c, cmd_addr), replicate(c, cmd)); +} + +static inline uint64_t flash_query(const FlashConfig *c, faddr query_addr) +{ + return flash_read(c, as_byte_addr(c, query_addr)); +} + +static inline uint64_t flash_query_1(const FlashConfig *c, faddr query_addr) +{ + return flash_query(c, query_addr) & device_mask(c); +} + +static void unlock(const FlashConfig *c) +{ + flash_cmd(c, UNLOCK0_ADDR, UNLOCK0_CMD); + flash_cmd(c, UNLOCK1_ADDR, UNLOCK1_CMD); +} + +static void reset(const FlashConfig *c) +{ + flash_cmd(c, FLASH_ADDR(0), RESET_CMD); +} + +static void sector_erase(const FlashConfig *c, uint64_t byte_addr) +{ + unlock(c); + flash_cmd(c, UNLOCK0_ADDR, 0x80); + unlock(c); + flash_write(c, byte_addr, replicate(c, SECTOR_ERASE_CMD)); +} + +static void wait_for_completion(const FlashConfig *c, uint64_t byte_addr) +{ + /* If DQ6 is toggling, step the clock and ensure the toggle stops. */ + const uint64_t dq6 = replicate(c, 0x40); + if ((flash_read(c, byte_addr) & dq6) ^ (flash_read(c, byte_addr) & dq6)) { + /* Wait for erase or program to finish. */ + qtest_clock_step_next(c->qtest); + /* Ensure that DQ6 has stopped toggling. */ + g_assert_cmphex(flash_read(c, byte_addr), ==, flash_read(c, byte_addr)); + } +} + +static void bypass_program(const FlashConfig *c, uint64_t byte_addr, + uint16_t data) +{ + flash_cmd(c, UNLOCK0_ADDR, PROGRAM_CMD); + flash_write(c, byte_addr, data); /* * Data isn't valid until DQ6 stops toggling. We don't model this as * writes are immediate, but if this changes in the future, we can wait * until the program is complete. */ - wait_for_completion(byte_addr); + wait_for_completion(c, byte_addr); } -static void program(uint64_t byte_addr, uint16_t data) +static void program(const FlashConfig *c, uint64_t byte_addr, uint16_t data) { - unlock(); - bypass_program(byte_addr, data); + unlock(c); + bypass_program(c, byte_addr, data); } -static void chip_erase(void) +static void chip_erase(const FlashConfig *c) { - unlock(); - flash_write(UNLOCK0_ADDR, 0x80); - unlock(); - flash_write(UNLOCK0_ADDR, SECTOR_ERASE_CMD); + unlock(c); + flash_cmd(c, UNLOCK0_ADDR, 0x80); + unlock(c); + flash_cmd(c, UNLOCK0_ADDR, CHIP_ERASE_CMD); } -static void test_flash(void) +static void test_flash(const void *opaque) { - global_qtest = qtest_initf("-M musicpal,accel=qtest " - "-drive if=pflash,file=%s,format=raw,copy-on-read", - image_path); + const FlashConfig *config = opaque; + QTestState *qtest; + qtest = qtest_initf("-M musicpal,accel=qtest" + " -drive if=pflash,file=%s,format=raw,copy-on-read", + image_path); + FlashConfig explicit_config = expand_config_defaults(config); + explicit_config.qtest = qtest; + const FlashConfig *c = &explicit_config; + /* Check the IDs. */ - unlock(); - flash_write(UNLOCK0_ADDR, AUTOSELECT_CMD); - g_assert_cmphex(flash_read(FLASH_WIDTH * 0x0000), ==, 0x00BF); - g_assert_cmphex(flash_read(FLASH_WIDTH * 0x0001), ==, 0x236D); - reset(); + unlock(c); + flash_cmd(c, UNLOCK0_ADDR, AUTOSELECT_CMD); + g_assert_cmphex(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF)); + if (c->bank_width >= 2) { + /* + * XXX: The ID returned by the musicpal flash chip is 16 bits which + * wouldn't happen with an 8-bit device. It would probably be best to + * prohibit addresses larger than the device width in pflash_cfi02.c, + * but then we couldn't test smaller device widths at all. + */ + g_assert_cmphex(flash_query(c, FLASH_ADDR(1)), ==, + replicate(c, 0x236D)); + } + reset(c); /* Check the erase blocks. */ - flash_write(CFI_ADDR, CFI_CMD); - g_assert_cmphex(flash_read(FLASH_WIDTH * 0x10), ==, 'Q'); - g_assert_cmphex(flash_read(FLASH_WIDTH * 0x11), ==, 'R'); - g_assert_cmphex(flash_read(FLASH_WIDTH * 0x12), ==, 'Y'); - /* Num erase regions. */ - g_assert_cmphex(flash_read(FLASH_WIDTH * 0x2C), >=, 1); - uint32_t nb_sectors = flash_read(FLASH_WIDTH * 0x2D) + - (flash_read(FLASH_WIDTH * 0x2E) << 8) + 1; - uint32_t sector_len = (flash_read(FLASH_WIDTH * 0x2F) << 8) + - (flash_read(FLASH_WIDTH * 0x30) << 16); - reset(); + flash_cmd(c, CFI_ADDR, CFI_CMD); + g_assert_cmphex(flash_query(c, FLASH_ADDR(0x10)), ==, replicate(c, 'Q')); + g_assert_cmphex(flash_query(c, FLASH_ADDR(0x11)), ==, replicate(c, 'R')); + g_assert_cmphex(flash_query(c, FLASH_ADDR(0x12)), ==, replicate(c, 'Y')); + /* Num erase regions. */ + g_assert_cmphex(flash_query_1(c, FLASH_ADDR(0x2C)), >=, 1); + + uint32_t nb_sectors = flash_query_1(c, FLASH_ADDR(0x2D)) + + (flash_query_1(c, FLASH_ADDR(0x2E)) << 8) + 1; + uint32_t sector_len = (flash_query_1(c, FLASH_ADDR(0x2F)) << 8) + + (flash_query_1(c, FLASH_ADDR(0x30)) << 16); + reset(c); + + const uint64_t dq7 = replicate(c, 0x80); + const uint64_t dq6 = replicate(c, 0x40); /* Erase and program sector. */ for (uint32_t i = 0; i < nb_sectors; ++i) { uint64_t byte_addr = i * sector_len; - sector_erase(byte_addr); + sector_erase(c, byte_addr); /* Read toggle. */ - uint16_t status0 = flash_read(byte_addr); + uint64_t status0 = flash_read(c, byte_addr); /* DQ7 is 0 during an erase. */ - g_assert_cmphex(status0 & 0x80, ==, 0); - uint16_t status1 = flash_read(byte_addr); + g_assert_cmphex(status0 & dq7, ==, 0); + uint64_t status1 = flash_read(c, byte_addr); /* DQ6 toggles during an erase. */ - g_assert_cmphex(status0 & 0x40, !=, status1 & 0x40); + g_assert_cmphex(status0 & dq6, ==, ~status1 & dq6); /* Wait for erase to complete. */ - clock_step_next(); + qtest_clock_step_next(c->qtest); /* Ensure DQ6 has stopped toggling. */ - g_assert_cmphex(flash_read(byte_addr), ==, flash_read(byte_addr)); + g_assert_cmphex(flash_read(c, byte_addr), ==, flash_read(c, byte_addr)); /* Now the data should be valid. */ - g_assert_cmphex(flash_read(byte_addr), ==, 0xFFFF); + g_assert_cmphex(flash_read(c, byte_addr), ==, bank_mask(c)); /* Program a bit pattern. */ - program(byte_addr, 0x5555); - g_assert_cmphex(flash_read(byte_addr), ==, 0x5555); - program(byte_addr, 0xAA55); - g_assert_cmphex(flash_read(byte_addr), ==, 0x0055); + program(c, byte_addr, 0x55); + g_assert_cmphex(flash_read(c, byte_addr) & 0xFF, ==, 0x55); + program(c, byte_addr, 0xA5); + g_assert_cmphex(flash_read(c, byte_addr) & 0xFF, ==, 0x05); } /* Erase the chip. */ - chip_erase(); + chip_erase(c); /* Read toggle. */ - uint16_t status0 = flash_read(0); + uint64_t status0 = flash_read(c, 0); /* DQ7 is 0 during an erase. */ - g_assert_cmphex(status0 & 0x80, ==, 0); - uint16_t status1 = flash_read(0); + g_assert_cmphex(status0 & dq7, ==, 0); + uint64_t status1 = flash_read(c, 0); /* DQ6 toggles during an erase. */ - g_assert_cmphex(status0 & 0x40, !=, status1 & 0x40); + g_assert_cmphex(status0 & dq6, ==, ~status1 & dq6); /* Wait for erase to complete. */ - clock_step_next(); + qtest_clock_step_next(c->qtest); /* Ensure DQ6 has stopped toggling. */ - g_assert_cmphex(flash_read(0), ==, flash_read(0)); + g_assert_cmphex(flash_read(c, 0), ==, flash_read(c, 0)); /* Now the data should be valid. */ - g_assert_cmphex(flash_read(0), ==, 0xFFFF); + + for (uint32_t i = 0; i < nb_sectors; ++i) { + uint64_t byte_addr = i * sector_len; + g_assert_cmphex(flash_read(c, byte_addr), ==, bank_mask(c)); + } /* Unlock bypass */ - unlock(); - flash_write(UNLOCK0_ADDR, UNLOCK_BYPASS_CMD); - bypass_program(0, 0x0123); - bypass_program(2, 0x4567); - bypass_program(4, 0x89AB); + unlock(c); + flash_cmd(c, UNLOCK0_ADDR, UNLOCK_BYPASS_CMD); + bypass_program(c, 0 * c->bank_width, 0x01); + bypass_program(c, 1 * c->bank_width, 0x23); + bypass_program(c, 2 * c->bank_width, 0x45); /* * Test that bypass programming, unlike normal programming can use any * address for the PROGRAM_CMD. */ - flash_write(6, PROGRAM_CMD); - flash_write(6, 0xCDEF); - wait_for_completion(6); - flash_write(0, UNLOCK_BYPASS_RESET_CMD); - bypass_program(8, 0x55AA); /* Should fail. */ - g_assert_cmphex(flash_read(0), ==, 0x0123); - g_assert_cmphex(flash_read(2), ==, 0x4567); - g_assert_cmphex(flash_read(4), ==, 0x89AB); - g_assert_cmphex(flash_read(6), ==, 0xCDEF); - g_assert_cmphex(flash_read(8), ==, 0xFFFF); + flash_cmd(c, FLASH_ADDR(3 * c->bank_width), PROGRAM_CMD); + flash_write(c, 3 * c->bank_width, 0x67); + wait_for_completion(c, 3 * c->bank_width); + flash_cmd(c, FLASH_ADDR(0), UNLOCK_BYPASS_RESET_CMD); + bypass_program(c, 4 * c->bank_width, 0x89); /* Should fail. */ + g_assert_cmphex(flash_read(c, 0 * c->bank_width), ==, 0x01); + g_assert_cmphex(flash_read(c, 1 * c->bank_width), ==, 0x23); + g_assert_cmphex(flash_read(c, 2 * c->bank_width), ==, 0x45); + g_assert_cmphex(flash_read(c, 3 * c->bank_width), ==, 0x67); + g_assert_cmphex(flash_read(c, 4 * c->bank_width), ==, bank_mask(c)); /* Test ignored high order bits of address. */ - flash_write(FLASH_WIDTH * 0x5555, UNLOCK0_CMD); - flash_write(FLASH_WIDTH * 0x2AAA, UNLOCK1_CMD); - flash_write(FLASH_WIDTH * 0x5555, AUTOSELECT_CMD); - g_assert_cmpint(flash_read(FLASH_WIDTH * 0x0000), ==, 0x00BF); - g_assert_cmpint(flash_read(FLASH_WIDTH * 0x0001), ==, 0x236D); - reset(); + flash_cmd(c, FLASH_ADDR(0x5555), UNLOCK0_CMD); + flash_cmd(c, FLASH_ADDR(0x2AAA), UNLOCK1_CMD); + flash_cmd(c, FLASH_ADDR(0x5555), AUTOSELECT_CMD); + g_assert_cmphex(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF)); + reset(c); - qtest_quit(global_qtest); + qtest_quit(qtest); } static void cleanup(void *opaque) @@ -206,6 +355,17 @@ static void cleanup(void *opaque) unlink(image_path); } +/* + * XXX: Tests are limited to bank_width = 2 for now because that's what + * hw/arm/musicpal.c has. + */ +static const FlashConfig configuration[] = { + /* One x16 device. */ + { + .bank_width = 2, + }, +}; + int main(int argc, char **argv) { int fd = mkstemp(image_path); @@ -214,19 +374,27 @@ int main(int argc, char **argv) strerror(errno)); exit(EXIT_FAILURE); } - if (ftruncate(fd, 8 * 1024 * 1024) < 0) { + if (ftruncate(fd, FLASH_SIZE) < 0) { int error_code = errno; close(fd); unlink(image_path); - g_printerr("Failed to truncate file %s to 8 MB: %s\n", image_path, - strerror(error_code)); + g_printerr("Failed to truncate file %s to %u MB: %s\n", image_path, + FLASH_SIZE, strerror(error_code)); exit(EXIT_FAILURE); } close(fd); qtest_add_abrt_handler(cleanup, NULL); g_test_init(&argc, &argv, NULL); - qtest_add_func("pflash-cfi02", test_flash); + + size_t nb_configurations = sizeof configuration / sizeof configuration[0]; + for (size_t i = 0; i < nb_configurations; ++i) { + const FlashConfig *config = &configuration[i]; + char *path = g_strdup_printf("pflash-cfi02/%d", + config->bank_width); + qtest_add_data_func(path, config, test_flash); + g_free(path); + } int result = g_test_run(); cleanup(NULL); return result; From 1eb27d692ead211ff19667c5447fe9701c9fd992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sat, 18 May 2019 14:45:35 +0200 Subject: [PATCH 14/27] hw/block/pflash_cfi02: Remove pointless local variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We can directly use pfl->total_len, remove the local 'chip_len' variable. Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-6-stephen.checkoway@oberlin.edu> Reviewed-by: Philippe Mathieu-Daudé Tested-by: Philippe Mathieu-Daudé [PMD: Extracted from bigger patch] Reviewed-by: Alistair Francis Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index 4be3837be5..1a794fa83c 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -409,7 +409,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, /* Chip erase */ DPRINTF("%s: start chip erase\n", __func__); if (!pfl->ro) { - memset(pfl->storage, 0xFF, pfl->chip_len); + memset(pfl->storage, 0xff, pfl->chip_len); pflash_update(pfl, 0, pfl->chip_len); } set_dq7(pfl, 0x00); @@ -490,7 +490,6 @@ static const MemoryRegionOps pflash_cfi02_ops = { static void pflash_cfi02_realize(DeviceState *dev, Error **errp) { PFlashCFI02 *pfl = PFLASH_CFI02(dev); - uint32_t chip_len; int ret; Error *local_err = NULL; @@ -507,18 +506,17 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) return; } - chip_len = pfl->sector_len * pfl->nb_blocs; + pfl->chip_len = pfl->sector_len * pfl->nb_blocs; memory_region_init_rom_device(&pfl->orig_mem, OBJECT(pfl), &pflash_cfi02_ops, pfl, pfl->name, - chip_len, &local_err); + pfl->chip_len, &local_err); if (local_err) { error_propagate(errp, local_err); return; } pfl->storage = memory_region_get_ram_ptr(&pfl->orig_mem); - pfl->chip_len = chip_len; if (pfl->blk) { uint64_t perm; @@ -533,8 +531,8 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) } if (pfl->blk) { - if (!blk_check_size_and_read_all(pfl->blk, pfl->storage, chip_len, - errp)) { + if (!blk_check_size_and_read_all(pfl->blk, pfl->storage, + pfl->chip_len, errp)) { vmstate_unregister_ram(&pfl->orig_mem, DEVICE(pfl)); return; } @@ -594,7 +592,7 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) /* Max timeout for chip erase */ pfl->cfi_table[0x26] = 0x0D; /* Device size */ - pfl->cfi_table[0x27] = ctz32(chip_len); + pfl->cfi_table[0x27] = ctz32(pfl->chip_len); /* Flash device interface (8 & 16 bits) */ pfl->cfi_table[0x28] = 0x02; pfl->cfi_table[0x29] = 0x00; From 9ac45b886ac36937ff0969d63c5b819b4efaa337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sat, 18 May 2019 20:21:28 +0200 Subject: [PATCH 15/27] hw/block/pflash_cfi02: Document the current CFI values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-6-stephen.checkoway@oberlin.edu> Reviewed-by: Philippe Mathieu-Daudé [PMD: Extracted from bigger patch] Acked-by: Alistair Francis Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index 1a794fa83c..f1bac480f5 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -550,6 +550,7 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) pfl->wcycle = 0; pfl->cmd = 0; pfl->status = 0; + /* Hardcoded CFI table (mostly from SG29 Spansion flash) */ /* Standard "QRY" string */ pfl->cfi_table[0x10] = 'Q'; @@ -575,7 +576,7 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) pfl->cfi_table[0x1D] = 0x00; /* Vpp max (no Vpp pin) */ pfl->cfi_table[0x1E] = 0x00; - /* Reserved */ + /* Timeout per single byte/word write (128 ms) */ pfl->cfi_table[0x1F] = 0x07; /* Timeout for min size buffer write (NA) */ pfl->cfi_table[0x20] = 0x00; @@ -614,17 +615,25 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) pfl->cfi_table[0x32] = 'R'; pfl->cfi_table[0x33] = 'I'; + /* Extended version 1.0 */ pfl->cfi_table[0x34] = '1'; pfl->cfi_table[0x35] = '0'; + /* Address sensitive unlock required. */ pfl->cfi_table[0x36] = 0x00; + /* Erase suspend not supported. */ pfl->cfi_table[0x37] = 0x00; + /* Sector protect not supported. */ pfl->cfi_table[0x38] = 0x00; + /* Temporary sector unprotect not supported. */ pfl->cfi_table[0x39] = 0x00; + /* Sector protect/unprotect scheme. */ pfl->cfi_table[0x3a] = 0x00; + /* Simultaneous operation not supported. */ pfl->cfi_table[0x3b] = 0x00; + /* Burst mode not supported. */ pfl->cfi_table[0x3c] = 0x00; } From d6874c8391f733d86f0ae85cc564109d7f01691d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sat, 18 May 2019 20:00:36 +0200 Subject: [PATCH 16/27] hw/block/pflash_cfi02: Hold the PRI table offset in a variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Manufacturers are allowed to move the PRI table, this is why the offset is queryable via fixed offsets 0x15/0x16. Add a variable to hold the offset, so it will be easier to later move the PRI table. Reviewed-by: Alistair Francis Message-Id: <20190627202719.17739-17-philmd@redhat.com> Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index f1bac480f5..23d05a6308 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -552,6 +552,7 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) pfl->status = 0; /* Hardcoded CFI table (mostly from SG29 Spansion flash) */ + const uint16_t pri_ofs = 0x31; /* Standard "QRY" string */ pfl->cfi_table[0x10] = 'Q'; pfl->cfi_table[0x11] = 'R'; @@ -560,8 +561,8 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) pfl->cfi_table[0x13] = 0x02; pfl->cfi_table[0x14] = 0x00; /* Primary extended table address */ - pfl->cfi_table[0x15] = 0x31; - pfl->cfi_table[0x16] = 0x00; + pfl->cfi_table[0x15] = pri_ofs; + pfl->cfi_table[0x16] = pri_ofs >> 8; /* Alternate command set (none) */ pfl->cfi_table[0x17] = 0x00; pfl->cfi_table[0x18] = 0x00; @@ -609,32 +610,34 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) pfl->cfi_table[0x2E] = (pfl->nb_blocs - 1) >> 8; pfl->cfi_table[0x2F] = pfl->sector_len >> 8; pfl->cfi_table[0x30] = pfl->sector_len >> 16; + assert(0x30 < pri_ofs); /* Extended */ - pfl->cfi_table[0x31] = 'P'; - pfl->cfi_table[0x32] = 'R'; - pfl->cfi_table[0x33] = 'I'; + pfl->cfi_table[0x00 + pri_ofs] = 'P'; + pfl->cfi_table[0x01 + pri_ofs] = 'R'; + pfl->cfi_table[0x02 + pri_ofs] = 'I'; /* Extended version 1.0 */ - pfl->cfi_table[0x34] = '1'; - pfl->cfi_table[0x35] = '0'; + pfl->cfi_table[0x03 + pri_ofs] = '1'; + pfl->cfi_table[0x04 + pri_ofs] = '0'; /* Address sensitive unlock required. */ - pfl->cfi_table[0x36] = 0x00; + pfl->cfi_table[0x05 + pri_ofs] = 0x00; /* Erase suspend not supported. */ - pfl->cfi_table[0x37] = 0x00; + pfl->cfi_table[0x06 + pri_ofs] = 0x00; /* Sector protect not supported. */ - pfl->cfi_table[0x38] = 0x00; + pfl->cfi_table[0x07 + pri_ofs] = 0x00; /* Temporary sector unprotect not supported. */ - pfl->cfi_table[0x39] = 0x00; + pfl->cfi_table[0x08 + pri_ofs] = 0x00; /* Sector protect/unprotect scheme. */ - pfl->cfi_table[0x3a] = 0x00; + pfl->cfi_table[0x09 + pri_ofs] = 0x00; /* Simultaneous operation not supported. */ - pfl->cfi_table[0x3b] = 0x00; + pfl->cfi_table[0x0a + pri_ofs] = 0x00; /* Burst mode not supported. */ - pfl->cfi_table[0x3c] = 0x00; + pfl->cfi_table[0x0b + pri_ofs] = 0x00; + assert(0x0b + pri_ofs < ARRAY_SIZE(pfl->cfi_table)); } static Property pflash_cfi02_properties[] = { From c2c1bf44a9b7f620b0565d17e33e7c5a04a5c51a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sat, 18 May 2019 20:23:31 +0200 Subject: [PATCH 17/27] hw/block/pflash_cfi02: Document 'Page Mode' operations are not supported MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'page mode' feature entry was implicitly set as zero (not supported). Document it exists, so we won't discard it if we squeeze the CFI table. Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-6-stephen.checkoway@oberlin.edu> Reviewed-by: Philippe Mathieu-Daudé Tested-by: Philippe Mathieu-Daudé [PMD: Extracted from bigger patch] Reviewed-by: Alistair Francis Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index 23d05a6308..01d9c5d75a 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -637,7 +637,9 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) pfl->cfi_table[0x0a + pri_ofs] = 0x00; /* Burst mode not supported. */ pfl->cfi_table[0x0b + pri_ofs] = 0x00; - assert(0x0b + pri_ofs < ARRAY_SIZE(pfl->cfi_table)); + /* Page mode not supported. */ + pfl->cfi_table[0x0c + pri_ofs] = 0x00; + assert(0x0c + pri_ofs < ARRAY_SIZE(pfl->cfi_table)); } static Property pflash_cfi02_properties[] = { From 64659053557a6a19bb00bb226e9b4d8f78cead7b Mon Sep 17 00:00:00 2001 From: Stephen Checkoway Date: Fri, 26 Apr 2019 12:26:19 -0400 Subject: [PATCH 18/27] hw/block/pflash_cfi02: Implement nonuniform sector sizes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some flash chips support sectors of different sizes. For example, the AMD AM29LV160DT has 31 64 kB sectors, one 32 kB sector, two 8 kB sectors, and a 16 kB sector, in that order. The AM29LV160DB has those in the reverse order. The `num-blocks` and `sector-length` properties work exactly as they did before: a flash device with uniform sector lengths. To get non-uniform sector lengths for up to four regions, the following properties may be set - region 0. `num-blocks0` and `sector-length0`; - region 1. `num-blocks1` and `sector-length1`; - region 2. `num-blocks2` and `sector-length2`; and - region 3. `num-blocks3` and `sector-length3`. If the uniform and nonuniform properties are set, then both must specify a flash device with the same total size. It would be better to disallow both being set, or make `num-blocks0` and `sector-length0` alias `num-blocks` and `sector-length`, but that would make testing currently impossible. Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-6-stephen.checkoway@oberlin.edu> Acked-by: Thomas Huth Acked-by: Philippe Mathieu-Daudé Tested-by: Philippe Mathieu-Daudé [PMD: Rebased, add assert() on pri_offset] Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 141 +++++++++++++++++++++++++++------- tests/pflash-cfi02-test.c | 155 ++++++++++++++++++++++++++++---------- 2 files changed, 231 insertions(+), 65 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index 01d9c5d75a..1f096ec185 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -29,7 +29,6 @@ * - CFI queries * * It does not support flash interleaving. - * It does not implement boot blocs with reduced size * It does not implement software data protection as found in many real chips * It does not implement erase suspend/resume commands * It does not implement multiple sectors erase @@ -57,6 +56,13 @@ do { \ #define PFLASH_LAZY_ROMD_THRESHOLD 42 +/* + * The size of the cfi_table indirectly depends on this and the start of the + * PRI table directly depends on it. 4 is the maximum size (and also what + * seems common) without changing the PRT table address. + */ +#define PFLASH_MAX_ERASE_REGIONS 4 + /* Special write cycles for CFI queries. */ enum { WCYCLE_CFI = 7, @@ -68,8 +74,10 @@ struct PFlashCFI02 { /*< public >*/ BlockBackend *blk; - uint32_t sector_len; - uint32_t nb_blocs; + uint32_t uniform_nb_blocs; + uint32_t uniform_sector_len; + uint32_t nb_blocs[PFLASH_MAX_ERASE_REGIONS]; + uint32_t sector_len[PFLASH_MAX_ERASE_REGIONS]; uint32_t chip_len; uint8_t mappings; uint8_t width; @@ -86,7 +94,7 @@ struct PFlashCFI02 { uint16_t ident3; uint16_t unlock_addr0; uint16_t unlock_addr1; - uint8_t cfi_table[0x52]; + uint8_t cfi_table[0x4d]; QEMUTimer timer; /* The device replicates the flash memory across its memory space. Emulate * that by having a container (.mem) filled with an array of aliases @@ -177,6 +185,25 @@ static uint64_t pflash_data_read(PFlashCFI02 *pfl, hwaddr offset, return ret; } +/* + * offset should be a byte offset of the QEMU device and _not_ a device + * offset. + */ +static uint32_t pflash_sector_len(PFlashCFI02 *pfl, hwaddr offset) +{ + assert(offset < pfl->chip_len); + int nb_regions = pfl->cfi_table[0x2C]; + hwaddr addr = 0; + for (int i = 0; i < nb_regions; ++i) { + uint64_t region_size = (uint64_t)pfl->nb_blocs[i] * pfl->sector_len[i]; + if (addr <= offset && offset < addr + region_size) { + return pfl->sector_len[i]; + } + addr += region_size; + } + abort(); +} + static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width) { PFlashCFI02 *pfl = opaque; @@ -191,10 +218,11 @@ static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width) } offset &= pfl->chip_len - 1; boff = offset & 0xFF; - if (pfl->width == 2) + if (pfl->width == 2) { boff = boff >> 1; - else if (pfl->width == 4) + } else if (pfl->width == 4) { boff = boff >> 2; + } switch (pfl->cmd) { default: /* This should never happen : reset state & treat it as a read*/ @@ -273,6 +301,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, hwaddr boff; uint8_t *p; uint8_t cmd; + uint32_t sector_len; trace_pflash_io_write(offset, width, width << 1, value, pfl->wcycle); cmd = value; @@ -282,10 +311,11 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, offset &= pfl->chip_len - 1; boff = offset; - if (pfl->width == 2) + if (pfl->width == 2) { boff = boff >> 1; - else if (pfl->width == 4) + } else if (pfl->width == 4) { boff = boff >> 2; + } /* Only the least-significant 11 bits are used in most cases. */ boff &= 0x7FF; switch (pfl->wcycle) { @@ -420,12 +450,14 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, case 0x30: /* Sector erase */ p = pfl->storage; - offset &= ~(pfl->sector_len - 1); - DPRINTF("%s: start sector erase at " TARGET_FMT_plx "\n", __func__, - offset); + sector_len = pflash_sector_len(pfl, offset); + offset &= ~(sector_len - 1); + DPRINTF("%s: start sector erase at %0*" PRIx64 "-%0*" PRIx64 "\n", + __func__, pfl->width * 2, offset, + pfl->width * 2, offset + sector_len - 1); if (!pfl->ro) { - memset(p + offset, 0xFF, pfl->sector_len); - pflash_update(pfl, offset, pfl->sector_len); + memset(p + offset, 0xff, sector_len); + pflash_update(pfl, offset, sector_len); } set_dq7(pfl, 0x00); /* Let's wait 1/2 second before sector erase is done */ @@ -493,11 +525,11 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) int ret; Error *local_err = NULL; - if (pfl->sector_len == 0) { + if (pfl->uniform_sector_len == 0 && pfl->sector_len[0] == 0) { error_setg(errp, "attribute \"sector-length\" not specified or zero."); return; } - if (pfl->nb_blocs == 0) { + if (pfl->uniform_nb_blocs == 0 && pfl->nb_blocs[0] == 0) { error_setg(errp, "attribute \"num-blocks\" not specified or zero."); return; } @@ -506,7 +538,51 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) return; } - pfl->chip_len = pfl->sector_len * pfl->nb_blocs; + int nb_regions; + pfl->chip_len = 0; + for (nb_regions = 0; nb_regions < PFLASH_MAX_ERASE_REGIONS; ++nb_regions) { + if (pfl->nb_blocs[nb_regions] == 0) { + break; + } + uint64_t sector_len_per_device = pfl->sector_len[nb_regions]; + + /* + * The size of each flash sector must be a power of 2 and it must be + * aligned at the same power of 2. + */ + if (sector_len_per_device & 0xff || + sector_len_per_device >= (1 << 24) || + !is_power_of_2(sector_len_per_device)) + { + error_setg(errp, "unsupported configuration: " + "sector length[%d] per device = %" PRIx64 ".", + nb_regions, sector_len_per_device); + return; + } + if (pfl->chip_len & (sector_len_per_device - 1)) { + error_setg(errp, "unsupported configuration: " + "flash region %d not correctly aligned.", + nb_regions); + return; + } + + pfl->chip_len += (uint64_t)pfl->sector_len[nb_regions] * + pfl->nb_blocs[nb_regions]; + } + + uint64_t uniform_len = (uint64_t)pfl->uniform_nb_blocs * + pfl->uniform_sector_len; + if (nb_regions == 0) { + nb_regions = 1; + pfl->nb_blocs[0] = pfl->uniform_nb_blocs; + pfl->sector_len[0] = pfl->uniform_sector_len; + pfl->chip_len = uniform_len; + } else if (uniform_len != 0 && uniform_len != pfl->chip_len) { + error_setg(errp, "\"num-blocks\"*\"sector-length\" " + "different from \"num-blocks0\"*\'sector-length0\" + ... + " + "\"num-blocks3\"*\"sector-length3\""); + return; + } memory_region_init_rom_device(&pfl->orig_mem, OBJECT(pfl), &pflash_cfi02_ops, pfl, pfl->name, @@ -552,7 +628,7 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) pfl->status = 0; /* Hardcoded CFI table (mostly from SG29 Spansion flash) */ - const uint16_t pri_ofs = 0x31; + const uint16_t pri_ofs = 0x40; /* Standard "QRY" string */ pfl->cfi_table[0x10] = 'Q'; pfl->cfi_table[0x11] = 'R'; @@ -603,14 +679,17 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) // pfl->cfi_table[0x2A] = 0x05; pfl->cfi_table[0x2A] = 0x00; pfl->cfi_table[0x2B] = 0x00; - /* Number of erase block regions (uniform) */ - pfl->cfi_table[0x2C] = 0x01; - /* Erase block region 1 */ - pfl->cfi_table[0x2D] = pfl->nb_blocs - 1; - pfl->cfi_table[0x2E] = (pfl->nb_blocs - 1) >> 8; - pfl->cfi_table[0x2F] = pfl->sector_len >> 8; - pfl->cfi_table[0x30] = pfl->sector_len >> 16; - assert(0x30 < pri_ofs); + /* Number of erase block regions */ + pfl->cfi_table[0x2c] = nb_regions; + /* Erase block regions */ + for (int i = 0; i < nb_regions; ++i) { + uint32_t sector_len_per_device = pfl->sector_len[i]; + pfl->cfi_table[0x2d + 4 * i] = pfl->nb_blocs[i] - 1; + pfl->cfi_table[0x2e + 4 * i] = (pfl->nb_blocs[i] - 1) >> 8; + pfl->cfi_table[0x2f + 4 * i] = sector_len_per_device >> 8; + pfl->cfi_table[0x30 + 4 * i] = sector_len_per_device >> 16; + } + assert(0x2c + 4 * nb_regions < pri_ofs); /* Extended */ pfl->cfi_table[0x00 + pri_ofs] = 'P'; @@ -644,8 +723,16 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) static Property pflash_cfi02_properties[] = { DEFINE_PROP_DRIVE("drive", PFlashCFI02, blk), - DEFINE_PROP_UINT32("num-blocks", PFlashCFI02, nb_blocs, 0), - DEFINE_PROP_UINT32("sector-length", PFlashCFI02, sector_len, 0), + DEFINE_PROP_UINT32("num-blocks", PFlashCFI02, uniform_nb_blocs, 0), + DEFINE_PROP_UINT32("sector-length", PFlashCFI02, uniform_sector_len, 0), + DEFINE_PROP_UINT32("num-blocks0", PFlashCFI02, nb_blocs[0], 0), + DEFINE_PROP_UINT32("sector-length0", PFlashCFI02, sector_len[0], 0), + DEFINE_PROP_UINT32("num-blocks1", PFlashCFI02, nb_blocs[1], 0), + DEFINE_PROP_UINT32("sector-length1", PFlashCFI02, sector_len[1], 0), + DEFINE_PROP_UINT32("num-blocks2", PFlashCFI02, nb_blocs[2], 0), + DEFINE_PROP_UINT32("sector-length2", PFlashCFI02, sector_len[2], 0), + DEFINE_PROP_UINT32("num-blocks3", PFlashCFI02, nb_blocs[3], 0), + DEFINE_PROP_UINT32("sector-length3", PFlashCFI02, sector_len[3], 0), DEFINE_PROP_UINT8("width", PFlashCFI02, width, 0), DEFINE_PROP_UINT8("mappings", PFlashCFI02, mappings, 0), DEFINE_PROP_UINT8("big-endian", PFlashCFI02, be, 0), diff --git a/tests/pflash-cfi02-test.c b/tests/pflash-cfi02-test.c index b00f5ca2e7..1659eaebce 100644 --- a/tests/pflash-cfi02-test.c +++ b/tests/pflash-cfi02-test.c @@ -17,9 +17,11 @@ */ #define MP_FLASH_SIZE_MAX (32 * 1024 * 1024) -#define FLASH_SIZE (8 * 1024 * 1024) #define BASE_ADDR (0x100000000ULL - MP_FLASH_SIZE_MAX) +#define UNIFORM_FLASH_SIZE (8 * 1024 * 1024) +#define UNIFORM_FLASH_SECTOR_SIZE (64 * 1024) + /* Use a newtype to keep flash addresses separate from byte addresses. */ typedef struct { uint64_t addr; @@ -44,6 +46,10 @@ typedef struct { typedef struct { int bank_width; + /* Nonuniform block size. */ + int nb_blocs[4]; + int sector_len[4]; + QTestState *qtest; } FlashConfig; @@ -62,6 +68,10 @@ static FlashConfig expand_config_defaults(const FlashConfig *c) if (ret.bank_width == 0) { ret.bank_width = 2; } + if (ret.nb_blocs[0] == 0 && ret.sector_len[0] == 0) { + ret.sector_len[0] = UNIFORM_FLASH_SECTOR_SIZE; + ret.nb_blocs[0] = UNIFORM_FLASH_SIZE / UNIFORM_FLASH_SECTOR_SIZE; + } /* XXX: Limitations of test harness. */ assert(ret.bank_width == 2); @@ -230,13 +240,41 @@ static void chip_erase(const FlashConfig *c) flash_cmd(c, UNLOCK0_ADDR, CHIP_ERASE_CMD); } -static void test_flash(const void *opaque) +/* + * Test flash commands with a variety of device geometry. + */ +static void test_geometry(const void *opaque) { const FlashConfig *config = opaque; QTestState *qtest; qtest = qtest_initf("-M musicpal,accel=qtest" - " -drive if=pflash,file=%s,format=raw,copy-on-read", - image_path); + " -drive if=pflash,file=%s,format=raw,copy-on-read" + /* Device geometry properties. */ + " -global driver=cfi.pflash02," + "property=num-blocks0,value=%d" + " -global driver=cfi.pflash02," + "property=sector-length0,value=%d" + " -global driver=cfi.pflash02," + "property=num-blocks1,value=%d" + " -global driver=cfi.pflash02," + "property=sector-length1,value=%d" + " -global driver=cfi.pflash02," + "property=num-blocks2,value=%d" + " -global driver=cfi.pflash02," + "property=sector-length2,value=%d" + " -global driver=cfi.pflash02," + "property=num-blocks3,value=%d" + " -global driver=cfi.pflash02," + "property=sector-length3,value=%d", + image_path, + config->nb_blocs[0], + config->sector_len[0], + config->nb_blocs[1], + config->sector_len[1], + config->nb_blocs[2], + config->sector_len[2], + config->nb_blocs[3], + config->sector_len[3]); FlashConfig explicit_config = expand_config_defaults(config); explicit_config.qtest = qtest; const FlashConfig *c = &explicit_config; @@ -264,39 +302,56 @@ static void test_flash(const void *opaque) g_assert_cmphex(flash_query(c, FLASH_ADDR(0x12)), ==, replicate(c, 'Y')); /* Num erase regions. */ - g_assert_cmphex(flash_query_1(c, FLASH_ADDR(0x2C)), >=, 1); + int nb_erase_regions = flash_query_1(c, FLASH_ADDR(0x2C)); + g_assert_cmphex(nb_erase_regions, ==, + !!c->nb_blocs[0] + !!c->nb_blocs[1] + !!c->nb_blocs[2] + + !!c->nb_blocs[3]); + + /* Check device length. */ + uint32_t device_len = 1 << flash_query_1(c, FLASH_ADDR(0x27)); + g_assert_cmphex(device_len, ==, UNIFORM_FLASH_SIZE); - uint32_t nb_sectors = flash_query_1(c, FLASH_ADDR(0x2D)) + - (flash_query_1(c, FLASH_ADDR(0x2E)) << 8) + 1; - uint32_t sector_len = (flash_query_1(c, FLASH_ADDR(0x2F)) << 8) + - (flash_query_1(c, FLASH_ADDR(0x30)) << 16); reset(c); const uint64_t dq7 = replicate(c, 0x80); const uint64_t dq6 = replicate(c, 0x40); - /* Erase and program sector. */ - for (uint32_t i = 0; i < nb_sectors; ++i) { - uint64_t byte_addr = i * sector_len; - sector_erase(c, byte_addr); - /* Read toggle. */ - uint64_t status0 = flash_read(c, byte_addr); - /* DQ7 is 0 during an erase. */ - g_assert_cmphex(status0 & dq7, ==, 0); - uint64_t status1 = flash_read(c, byte_addr); - /* DQ6 toggles during an erase. */ - g_assert_cmphex(status0 & dq6, ==, ~status1 & dq6); - /* Wait for erase to complete. */ - qtest_clock_step_next(c->qtest); - /* Ensure DQ6 has stopped toggling. */ - g_assert_cmphex(flash_read(c, byte_addr), ==, flash_read(c, byte_addr)); - /* Now the data should be valid. */ - g_assert_cmphex(flash_read(c, byte_addr), ==, bank_mask(c)); + uint64_t byte_addr = 0; + for (int region = 0; region < nb_erase_regions; ++region) { + uint64_t base = 0x2D + 4 * region; + flash_cmd(c, CFI_ADDR, CFI_CMD); + uint32_t nb_sectors = flash_query_1(c, FLASH_ADDR(base + 0)) + + (flash_query_1(c, FLASH_ADDR(base + 1)) << 8) + 1; + uint32_t sector_len = (flash_query_1(c, FLASH_ADDR(base + 2)) << 8) + + (flash_query_1(c, FLASH_ADDR(base + 3)) << 16); + g_assert_cmphex(nb_sectors, ==, c->nb_blocs[region]); + g_assert_cmphex(sector_len, ==, c->sector_len[region]); + reset(c); - /* Program a bit pattern. */ - program(c, byte_addr, 0x55); - g_assert_cmphex(flash_read(c, byte_addr) & 0xFF, ==, 0x55); - program(c, byte_addr, 0xA5); - g_assert_cmphex(flash_read(c, byte_addr) & 0xFF, ==, 0x05); + /* Erase and program sector. */ + for (uint32_t i = 0; i < nb_sectors; ++i) { + sector_erase(c, byte_addr); + /* Read toggle. */ + uint64_t status0 = flash_read(c, byte_addr); + /* DQ7 is 0 during an erase. */ + g_assert_cmphex(status0 & dq7, ==, 0); + uint64_t status1 = flash_read(c, byte_addr); + /* DQ6 toggles during an erase. */ + g_assert_cmphex(status0 & dq6, ==, ~status1 & dq6); + /* Wait for erase to complete. */ + qtest_clock_step_next(c->qtest); + /* Ensure DQ6 has stopped toggling. */ + g_assert_cmphex(flash_read(c, byte_addr), ==, + flash_read(c, byte_addr)); + /* Now the data should be valid. */ + g_assert_cmphex(flash_read(c, byte_addr), ==, bank_mask(c)); + + /* Program a bit pattern. */ + program(c, byte_addr, 0x55); + g_assert_cmphex(flash_read(c, byte_addr) & 0xFF, ==, 0x55); + program(c, byte_addr, 0xA5); + g_assert_cmphex(flash_read(c, byte_addr) & 0xFF, ==, 0x05); + byte_addr += sector_len; + } } /* Erase the chip. */ @@ -314,9 +369,11 @@ static void test_flash(const void *opaque) g_assert_cmphex(flash_read(c, 0), ==, flash_read(c, 0)); /* Now the data should be valid. */ - for (uint32_t i = 0; i < nb_sectors; ++i) { - uint64_t byte_addr = i * sector_len; - g_assert_cmphex(flash_read(c, byte_addr), ==, bank_mask(c)); + for (int region = 0; region < nb_erase_regions; ++region) { + for (uint32_t i = 0; i < c->nb_blocs[region]; ++i) { + uint64_t byte_addr = i * c->sector_len[region]; + g_assert_cmphex(flash_read(c, byte_addr), ==, bank_mask(c)); + } } /* Unlock bypass */ @@ -364,6 +421,18 @@ static const FlashConfig configuration[] = { { .bank_width = 2, }, + /* Nonuniform sectors (top boot). */ + { + .bank_width = 2, + .nb_blocs = { 127, 1, 2, 1 }, + .sector_len = { 0x10000, 0x08000, 0x02000, 0x04000 }, + }, + /* Nonuniform sectors (bottom boot). */ + { + .bank_width = 2, + .nb_blocs = { 1, 2, 1, 127 }, + .sector_len = { 0x04000, 0x02000, 0x08000, 0x10000 }, + }, }; int main(int argc, char **argv) @@ -374,12 +443,12 @@ int main(int argc, char **argv) strerror(errno)); exit(EXIT_FAILURE); } - if (ftruncate(fd, FLASH_SIZE) < 0) { + if (ftruncate(fd, UNIFORM_FLASH_SIZE) < 0) { int error_code = errno; close(fd); unlink(image_path); g_printerr("Failed to truncate file %s to %u MB: %s\n", image_path, - FLASH_SIZE, strerror(error_code)); + UNIFORM_FLASH_SIZE, strerror(error_code)); exit(EXIT_FAILURE); } close(fd); @@ -390,9 +459,19 @@ int main(int argc, char **argv) size_t nb_configurations = sizeof configuration / sizeof configuration[0]; for (size_t i = 0; i < nb_configurations; ++i) { const FlashConfig *config = &configuration[i]; - char *path = g_strdup_printf("pflash-cfi02/%d", + char *path = g_strdup_printf("pflash-cfi02" + "/geometry/%dx%x-%dx%x-%dx%x-%dx%x" + "/%d", + config->nb_blocs[0], + config->sector_len[0], + config->nb_blocs[1], + config->sector_len[1], + config->nb_blocs[2], + config->sector_len[2], + config->nb_blocs[3], + config->sector_len[3], config->bank_width); - qtest_add_data_func(path, config, test_flash); + qtest_add_data_func(path, config, test_geometry); g_free(path); } int result = g_test_run(); From 102f0f79a51cfc289f7ad19a10be654925faeff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sat, 18 May 2019 20:57:02 +0200 Subject: [PATCH 19/27] hw/block/pflash_cfi02: Extract pflash_regions_count() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract the pflash_regions_count() function, the code will be easier to review. Reviewed-by: Alistair Francis Message-Id: <20190627202719.17739-20-philmd@redhat.com> Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index 1f096ec185..a0d3bd60dc 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -157,6 +157,11 @@ static void pflash_register_memory(PFlashCFI02 *pfl, int rom_mode) pfl->rom_mode = rom_mode; } +static size_t pflash_regions_count(PFlashCFI02 *pfl) +{ + return pfl->cfi_table[0x2c]; +} + static void pflash_timer (void *opaque) { PFlashCFI02 *pfl = opaque; @@ -192,9 +197,8 @@ static uint64_t pflash_data_read(PFlashCFI02 *pfl, hwaddr offset, static uint32_t pflash_sector_len(PFlashCFI02 *pfl, hwaddr offset) { assert(offset < pfl->chip_len); - int nb_regions = pfl->cfi_table[0x2C]; hwaddr addr = 0; - for (int i = 0; i < nb_regions; ++i) { + for (int i = 0; i < pflash_regions_count(pfl); ++i) { uint64_t region_size = (uint64_t)pfl->nb_blocs[i] * pfl->sector_len[i]; if (addr <= offset && offset < addr + region_size) { return pfl->sector_len[i]; From 8a508e7064bbe7d434a93f3284e6d93881c68a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 27 Jun 2019 15:44:24 +0200 Subject: [PATCH 20/27] hw/block/pflash_cfi02: Split if() condition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split the if() condition check and arrange the indentation to ease the review of the next patches. No logical change. Reviewed-by: Alistair Francis Message-Id: <20190627202719.17739-21-philmd@redhat.com> Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index a0d3bd60dc..08b2bc83cb 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -309,8 +309,10 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, trace_pflash_io_write(offset, width, width << 1, value, pfl->wcycle); cmd = value; - if (pfl->cmd != 0xA0 && cmd == 0xF0) { - goto reset_flash; + if (pfl->cmd != 0xA0) { + if (cmd == 0xF0) { + goto reset_flash; + } } offset &= pfl->chip_len - 1; From 46fb7809b5520e2cb77bdc86270b0c393e4b1210 Mon Sep 17 00:00:00 2001 From: Stephen Checkoway Date: Fri, 26 Apr 2019 12:26:20 -0400 Subject: [PATCH 21/27] hw/block/pflash_cfi02: Fix CFI in autoselect mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After a flash device enters CFI mode from autoselect mode, the reset command returns the device to autoselect mode. An additional reset command is necessary to return to read array mode. Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-7-stephen.checkoway@oberlin.edu> Tested-by: Philippe Mathieu-Daudé Acked-by: Thomas Huth Acked-by: Alistair Francis Acked-by: Philippe Mathieu-Daudé Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 22 ++++++++++++++++++---- tests/pflash-cfi02-test.c | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index 08b2bc83cb..13f76fa71d 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -66,6 +66,7 @@ do { \ /* Special write cycles for CFI queries. */ enum { WCYCLE_CFI = 7, + WCYCLE_AUTOSELECT_CFI = 8, }; struct PFlashCFI02 { @@ -311,6 +312,12 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, cmd = value; if (pfl->cmd != 0xA0) { if (cmd == 0xF0) { + if (pfl->wcycle == WCYCLE_AUTOSELECT_CFI) { + /* Return to autoselect mode. */ + pfl->wcycle = 3; + pfl->cmd = 0x90; + return; + } goto reset_flash; } } @@ -333,7 +340,6 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, /* We're in read mode */ check_unlock0: if (boff == 0x55 && cmd == 0x98) { - enter_CFI_mode: /* Enter CFI query mode */ pfl->wcycle = WCYCLE_CFI; pfl->cmd = 0x98; @@ -410,9 +416,16 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, /* Unlock bypass reset */ goto reset_flash; } - /* We can enter CFI query mode from autoselect mode */ - if (boff == 0x55 && cmd == 0x98) - goto enter_CFI_mode; + /* + * We can enter CFI query mode from autoselect mode, but we must + * return to autoselect mode after a reset. + */ + if (boff == 0x55 && cmd == 0x98) { + /* Enter autoselect CFI query mode */ + pfl->wcycle = WCYCLE_AUTOSELECT_CFI; + pfl->cmd = 0x98; + return; + } /* No break here */ default: DPRINTF("%s: invalid write for command %02x\n", @@ -493,6 +506,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, break; /* Special values for CFI queries */ case WCYCLE_CFI: + case WCYCLE_AUTOSELECT_CFI: DPRINTF("%s: invalid write in CFI query mode\n", __func__); goto reset_flash; default: diff --git a/tests/pflash-cfi02-test.c b/tests/pflash-cfi02-test.c index 1659eaebce..00e2261742 100644 --- a/tests/pflash-cfi02-test.c +++ b/tests/pflash-cfi02-test.c @@ -407,6 +407,42 @@ static void test_geometry(const void *opaque) qtest_quit(qtest); } +/* + * Test that + * 1. enter autoselect mode; + * 2. enter CFI mode; and then + * 3. exit CFI mode + * leaves the flash device in autoselect mode. + */ +static void test_cfi_in_autoselect(const void *opaque) +{ + const FlashConfig *config = opaque; + QTestState *qtest; + qtest = qtest_initf("-M musicpal,accel=qtest" + " -drive if=pflash,file=%s,format=raw,copy-on-read", + image_path); + FlashConfig explicit_config = expand_config_defaults(config); + explicit_config.qtest = qtest; + const FlashConfig *c = &explicit_config; + + /* 1. Enter autoselect. */ + unlock(c); + flash_cmd(c, UNLOCK0_ADDR, AUTOSELECT_CMD); + g_assert_cmpint(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF)); + + /* 2. Enter CFI. */ + flash_cmd(c, CFI_ADDR, CFI_CMD); + g_assert_cmpint(flash_query(c, FLASH_ADDR(0x10)), ==, replicate(c, 'Q')); + g_assert_cmpint(flash_query(c, FLASH_ADDR(0x11)), ==, replicate(c, 'R')); + g_assert_cmpint(flash_query(c, FLASH_ADDR(0x12)), ==, replicate(c, 'Y')); + + /* 3. Exit CFI. */ + reset(c); + g_assert_cmpint(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF)); + + qtest_quit(qtest); +} + static void cleanup(void *opaque) { unlink(image_path); @@ -474,6 +510,9 @@ int main(int argc, char **argv) qtest_add_data_func(path, config, test_geometry); g_free(path); } + + qtest_add_data_func("pflash-cfi02/cfi-in-autoselect", &configuration[0], + test_cfi_in_autoselect); int result = g_test_run(); cleanup(NULL); return result; From a97910423975b9fee1dbcdc1a2488804d4092c36 Mon Sep 17 00:00:00 2001 From: Stephen Checkoway Date: Fri, 26 Apr 2019 12:26:21 -0400 Subject: [PATCH 22/27] hw/block/pflash_cfi02: Fix reset command not ignored during erase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the flash device is performing a chip erase, all commands are ignored. When it is performing a sector erase, only the erase suspend command is valid, which is currently not supported. In particular, the reset command should not cause the device to reset to read array mode while programming is on going. Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-8-stephen.checkoway@oberlin.edu> Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Alistair Francis Tested-by: Philippe Mathieu-Daudé Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index 13f76fa71d..39daa95833 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -311,7 +311,8 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, trace_pflash_io_write(offset, width, width << 1, value, pfl->wcycle); cmd = value; if (pfl->cmd != 0xA0) { - if (cmd == 0xF0) { + /* Reset does nothing during chip erase and sector erase. */ + if (cmd == 0xF0 && pfl->cmd != 0x10 && pfl->cmd != 0x30) { if (pfl->wcycle == WCYCLE_AUTOSELECT_CFI) { /* Return to autoselect mode. */ pfl->wcycle = 3; From a50547aca54c0e55122c32c077cab747147a6b30 Mon Sep 17 00:00:00 2001 From: Stephen Checkoway Date: Fri, 26 Apr 2019 12:26:22 -0400 Subject: [PATCH 23/27] hw/block/pflash_cfi02: Implement multi-sector erase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After two unlock cycles and a sector erase command, the AMD flash chips start a 50 us erase time out. Any additional sector erase commands add a sector to be erased and restart the 50 us timeout. During the timeout, status bit DQ3 is cleared. After the time out, DQ3 is asserted during erasure. Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-9-stephen.checkoway@oberlin.edu> Acked-by: Thomas Huth Acked-by: Philippe Mathieu-Daudé [PMD: Rebased] Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 94 +++++++++++++++++++++++++++++++-------- tests/pflash-cfi02-test.c | 70 +++++++++++++++++++++++++---- 2 files changed, 137 insertions(+), 27 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index 39daa95833..5874bd55ad 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -31,7 +31,6 @@ * It does not support flash interleaving. * It does not implement software data protection as found in many real chips * It does not implement erase suspend/resume commands - * It does not implement multiple sectors erase */ #include "qemu/osdep.h" @@ -106,6 +105,7 @@ struct PFlashCFI02 { MemoryRegion orig_mem; int rom_mode; int read_counter; /* used for lazy switch-back to rom mode */ + int sectors_to_erase; char *name; void *storage; }; @@ -135,6 +135,22 @@ static inline void toggle_dq6(PFlashCFI02 *pfl) pfl->status ^= 0x40; } +/* + * Turn on DQ3. + */ +static inline void assert_dq3(PFlashCFI02 *pfl) +{ + pfl->status |= 0x08; +} + +/* + * Turn off DQ3. + */ +static inline void reset_dq3(PFlashCFI02 *pfl) +{ + pfl->status &= ~0x08; +} + /* * Set up replicated mappings of the same region. */ @@ -163,11 +179,37 @@ static size_t pflash_regions_count(PFlashCFI02 *pfl) return pfl->cfi_table[0x2c]; } -static void pflash_timer (void *opaque) +static void pflash_timer(void *opaque) { PFlashCFI02 *pfl = opaque; trace_pflash_timer_expired(pfl->cmd); + if (pfl->cmd == 0x30) { + /* + * Sector erase. If DQ3 is 0 when the timer expires, then the 50 + * us erase timeout has expired so we need to start the timer for the + * sector erase algorithm. Otherwise, the erase completed and we should + * go back to read array mode. + */ + if ((pfl->status & 0x08) == 0) { + assert_dq3(pfl); + /* + * CFI address 0x21 is "Typical timeout per individual block erase + * 2^N ms" + */ + uint64_t timeout = ((1ULL << pfl->cfi_table[0x21]) * + pfl->sectors_to_erase) * 1000000; + timer_mod(&pfl->timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + timeout); + DPRINTF("%s: erase timeout fired; erasing %d sectors\n", + __func__, pfl->sectors_to_erase); + return; + } + DPRINTF("%s: sector erase complete\n", __func__); + pfl->sectors_to_erase = 0; + reset_dq3(pfl); + } + /* Reset flash */ toggle_dq7(pfl); if (pfl->bypass) { @@ -299,6 +341,24 @@ static void pflash_update(PFlashCFI02 *pfl, int offset, int size) } } +static void pflash_sector_erase(PFlashCFI02 *pfl, hwaddr offset) +{ + uint64_t sector_len = pflash_sector_len(pfl, offset); + offset &= ~(sector_len - 1); + DPRINTF("%s: start sector erase at %0*" PRIx64 "-%0*" PRIx64 "\n", + __func__, pfl->width * 2, offset, + pfl->width * 2, offset + sector_len - 1); + if (!pfl->ro) { + uint8_t *p = pfl->storage; + memset(p + offset, 0xff, sector_len); + pflash_update(pfl, offset, sector_len); + } + set_dq7(pfl, 0x00); + ++pfl->sectors_to_erase; + /* Set (or reset) the 50 us timer for additional erase commands. */ + timer_mod(&pfl->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 50000); +} + static void pflash_write(void *opaque, hwaddr offset, uint64_t value, unsigned int width) { @@ -306,7 +366,6 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, hwaddr boff; uint8_t *p; uint8_t cmd; - uint32_t sector_len; trace_pflash_io_write(offset, width, width << 1, value, pfl->wcycle); cmd = value; @@ -469,20 +528,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, break; case 0x30: /* Sector erase */ - p = pfl->storage; - sector_len = pflash_sector_len(pfl, offset); - offset &= ~(sector_len - 1); - DPRINTF("%s: start sector erase at %0*" PRIx64 "-%0*" PRIx64 "\n", - __func__, pfl->width * 2, offset, - pfl->width * 2, offset + sector_len - 1); - if (!pfl->ro) { - memset(p + offset, 0xff, sector_len); - pflash_update(pfl, offset, sector_len); - } - set_dq7(pfl, 0x00); - /* Let's wait 1/2 second before sector erase is done */ - timer_mod(&pfl->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + - (NANOSECONDS_PER_SECOND / 2)); + pflash_sector_erase(pfl, offset); break; default: DPRINTF("%s: invalid command %02x (wc 5)\n", __func__, cmd); @@ -496,7 +542,19 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, /* Ignore writes during chip erase */ return; case 0x30: - /* Ignore writes during sector erase */ + /* + * If DQ3 is 0, additional sector erase commands can be + * written and anything else (other than an erase suspend) resets + * the device. + */ + if ((pfl->status & 0x08) == 0) { + if (cmd == 0x30) { + pflash_sector_erase(pfl, offset); + } else { + goto reset_flash; + } + } + /* Ignore writes during the actual erase. */ return; default: /* Should never happen */ diff --git a/tests/pflash-cfi02-test.c b/tests/pflash-cfi02-test.c index 00e2261742..303bc87820 100644 --- a/tests/pflash-cfi02-test.c +++ b/tests/pflash-cfi02-test.c @@ -35,6 +35,7 @@ typedef struct { #define CFI_CMD 0x98 #define UNLOCK0_CMD 0xAA #define UNLOCK1_CMD 0x55 +#define SECOND_UNLOCK_CMD 0x80 #define AUTOSELECT_CMD 0x90 #define RESET_CMD 0xF0 #define PROGRAM_CMD 0xA0 @@ -196,7 +197,7 @@ static void reset(const FlashConfig *c) static void sector_erase(const FlashConfig *c, uint64_t byte_addr) { unlock(c); - flash_cmd(c, UNLOCK0_ADDR, 0x80); + flash_cmd(c, UNLOCK0_ADDR, SECOND_UNLOCK_CMD); unlock(c); flash_write(c, byte_addr, replicate(c, SECTOR_ERASE_CMD)); } @@ -235,7 +236,7 @@ static void program(const FlashConfig *c, uint64_t byte_addr, uint16_t data) static void chip_erase(const FlashConfig *c) { unlock(c); - flash_cmd(c, UNLOCK0_ADDR, 0x80); + flash_cmd(c, UNLOCK0_ADDR, SECOND_UNLOCK_CMD); unlock(c); flash_cmd(c, UNLOCK0_ADDR, CHIP_ERASE_CMD); } @@ -315,6 +316,8 @@ static void test_geometry(const void *opaque) const uint64_t dq7 = replicate(c, 0x80); const uint64_t dq6 = replicate(c, 0x40); + const uint64_t dq3 = replicate(c, 0x08); + uint64_t byte_addr = 0; for (int region = 0; region < nb_erase_regions; ++region) { uint64_t base = 0x2D + 4 * region; @@ -330,18 +333,29 @@ static void test_geometry(const void *opaque) /* Erase and program sector. */ for (uint32_t i = 0; i < nb_sectors; ++i) { sector_erase(c, byte_addr); - /* Read toggle. */ + + /* Check that DQ3 is 0. */ + g_assert_cmphex(flash_read(c, byte_addr) & dq3, ==, 0); + qtest_clock_step_next(c->qtest); /* Step over the 50 us timeout. */ + + /* Check that DQ3 is 1. */ uint64_t status0 = flash_read(c, byte_addr); + g_assert_cmphex(status0 & dq3, ==, dq3); + /* DQ7 is 0 during an erase. */ g_assert_cmphex(status0 & dq7, ==, 0); uint64_t status1 = flash_read(c, byte_addr); + /* DQ6 toggles during an erase. */ g_assert_cmphex(status0 & dq6, ==, ~status1 & dq6); + /* Wait for erase to complete. */ - qtest_clock_step_next(c->qtest); + wait_for_completion(c, byte_addr); + /* Ensure DQ6 has stopped toggling. */ g_assert_cmphex(flash_read(c, byte_addr), ==, flash_read(c, byte_addr)); + /* Now the data should be valid. */ g_assert_cmphex(flash_read(c, byte_addr), ==, bank_mask(c)); @@ -404,6 +418,44 @@ static void test_geometry(const void *opaque) g_assert_cmphex(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF)); reset(c); + /* + * Program a word on each sector, erase one or two sectors per region, and + * verify that all of those, and only those, are erased. + */ + byte_addr = 0; + for (int region = 0; region < nb_erase_regions; ++region) { + for (int i = 0; i < config->nb_blocs[region]; ++i) { + program(c, byte_addr, 0); + byte_addr += config->sector_len[region]; + } + } + unlock(c); + flash_cmd(c, UNLOCK0_ADDR, SECOND_UNLOCK_CMD); + unlock(c); + byte_addr = 0; + const uint64_t erase_cmd = replicate(c, SECTOR_ERASE_CMD); + for (int region = 0; region < nb_erase_regions; ++region) { + flash_write(c, byte_addr, erase_cmd); + if (c->nb_blocs[region] > 1) { + flash_write(c, byte_addr + c->sector_len[region], erase_cmd); + } + byte_addr += c->sector_len[region] * c->nb_blocs[region]; + } + + qtest_clock_step_next(c->qtest); /* Step over the 50 us timeout. */ + wait_for_completion(c, 0); + byte_addr = 0; + for (int region = 0; region < nb_erase_regions; ++region) { + for (int i = 0; i < config->nb_blocs[region]; ++i) { + if (i < 2) { + g_assert_cmphex(flash_read(c, byte_addr), ==, bank_mask(c)); + } else { + g_assert_cmphex(flash_read(c, byte_addr), ==, 0); + } + byte_addr += config->sector_len[region]; + } + } + qtest_quit(qtest); } @@ -428,17 +480,17 @@ static void test_cfi_in_autoselect(const void *opaque) /* 1. Enter autoselect. */ unlock(c); flash_cmd(c, UNLOCK0_ADDR, AUTOSELECT_CMD); - g_assert_cmpint(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF)); + g_assert_cmphex(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF)); /* 2. Enter CFI. */ flash_cmd(c, CFI_ADDR, CFI_CMD); - g_assert_cmpint(flash_query(c, FLASH_ADDR(0x10)), ==, replicate(c, 'Q')); - g_assert_cmpint(flash_query(c, FLASH_ADDR(0x11)), ==, replicate(c, 'R')); - g_assert_cmpint(flash_query(c, FLASH_ADDR(0x12)), ==, replicate(c, 'Y')); + g_assert_cmphex(flash_query(c, FLASH_ADDR(0x10)), ==, replicate(c, 'Q')); + g_assert_cmphex(flash_query(c, FLASH_ADDR(0x11)), ==, replicate(c, 'R')); + g_assert_cmphex(flash_query(c, FLASH_ADDR(0x12)), ==, replicate(c, 'Y')); /* 3. Exit CFI. */ reset(c); - g_assert_cmpint(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF)); + g_assert_cmphex(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF)); qtest_quit(qtest); } From ddb6f2254871c7c686561c4b41d52e2f0413f9a1 Mon Sep 17 00:00:00 2001 From: Stephen Checkoway Date: Fri, 26 Apr 2019 12:26:23 -0400 Subject: [PATCH 24/27] hw/block/pflash_cfi02: Implement erase suspend/resume MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During a sector erase (but not a chip erase), the embeded erase program can be suspended. Once suspended, the sectors not selected for erasure may be read and programmed. Autoselect mode is allowed during erase suspend mode. Presumably, CFI queries are similarly allowed so this commit allows them as well. Since guest firmware can use status bits DQ7, DQ6, DQ3, and DQ2 to determine the current state of sector erasure, these bits are properly implemented. Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-10-stephen.checkoway@oberlin.edu> Acked-by: Thomas Huth Acked-by: Philippe Mathieu-Daudé Tested-by: Philippe Mathieu-Daudé [PMD: Rebased] Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 154 ++++++++++++++++++++++++++++++++++---- tests/pflash-cfi02-test.c | 110 +++++++++++++++++++++++++++ 2 files changed, 250 insertions(+), 14 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index 5874bd55ad..a3665da3b8 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -30,7 +30,6 @@ * * It does not support flash interleaving. * It does not implement software data protection as found in many real chips - * It does not implement erase suspend/resume commands */ #include "qemu/osdep.h" @@ -38,6 +37,7 @@ #include "hw/block/block.h" #include "hw/block/flash.h" #include "qapi/error.h" +#include "qemu/bitmap.h" #include "qemu/timer.h" #include "sysemu/block-backend.h" #include "qemu/host-utils.h" @@ -76,6 +76,7 @@ struct PFlashCFI02 { BlockBackend *blk; uint32_t uniform_nb_blocs; uint32_t uniform_sector_len; + uint32_t total_sectors; uint32_t nb_blocs[PFLASH_MAX_ERASE_REGIONS]; uint32_t sector_len[PFLASH_MAX_ERASE_REGIONS]; uint32_t chip_len; @@ -106,6 +107,8 @@ struct PFlashCFI02 { int rom_mode; int read_counter; /* used for lazy switch-back to rom mode */ int sectors_to_erase; + uint64_t erase_time_remaining; + unsigned long *sector_erase_map; char *name; void *storage; }; @@ -151,6 +154,14 @@ static inline void reset_dq3(PFlashCFI02 *pfl) pfl->status &= ~0x08; } +/* + * Toggle status bit DQ2. + */ +static inline void toggle_dq2(PFlashCFI02 *pfl) +{ + pfl->status ^= 0x04; +} + /* * Set up replicated mappings of the same region. */ @@ -179,6 +190,29 @@ static size_t pflash_regions_count(PFlashCFI02 *pfl) return pfl->cfi_table[0x2c]; } +/* + * Returns the time it takes to erase the number of sectors scheduled for + * erasure based on CFI address 0x21 which is "Typical timeout per individual + * block erase 2^N ms." + */ +static uint64_t pflash_erase_time(PFlashCFI02 *pfl) +{ + /* + * If there are no sectors to erase (which can happen if all of the sectors + * to be erased are protected), then erase takes 100 us. Protected sectors + * aren't supported so this should never happen. + */ + return ((1ULL << pfl->cfi_table[0x21]) * pfl->sectors_to_erase) * SCALE_US; +} + +/* + * Returns true if the device is currently in erase suspend mode. + */ +static inline bool pflash_erase_suspend_mode(PFlashCFI02 *pfl) +{ + return pfl->erase_time_remaining > 0; +} + static void pflash_timer(void *opaque) { PFlashCFI02 *pfl = opaque; @@ -193,12 +227,7 @@ static void pflash_timer(void *opaque) */ if ((pfl->status & 0x08) == 0) { assert_dq3(pfl); - /* - * CFI address 0x21 is "Typical timeout per individual block erase - * 2^N ms" - */ - uint64_t timeout = ((1ULL << pfl->cfi_table[0x21]) * - pfl->sectors_to_erase) * 1000000; + uint64_t timeout = pflash_erase_time(pfl); timer_mod(&pfl->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + timeout); DPRINTF("%s: erase timeout fired; erasing %d sectors\n", @@ -206,6 +235,7 @@ static void pflash_timer(void *opaque) return; } DPRINTF("%s: sector erase complete\n", __func__); + bitmap_zero(pfl->sector_erase_map, pfl->total_sectors); pfl->sectors_to_erase = 0; reset_dq3(pfl); } @@ -233,24 +263,44 @@ static uint64_t pflash_data_read(PFlashCFI02 *pfl, hwaddr offset, return ret; } +typedef struct { + uint32_t len; + uint32_t num; +} SectorInfo; + /* * offset should be a byte offset of the QEMU device and _not_ a device * offset. */ -static uint32_t pflash_sector_len(PFlashCFI02 *pfl, hwaddr offset) +static SectorInfo pflash_sector_info(PFlashCFI02 *pfl, hwaddr offset) { assert(offset < pfl->chip_len); hwaddr addr = 0; + uint32_t sector_num = 0; for (int i = 0; i < pflash_regions_count(pfl); ++i) { uint64_t region_size = (uint64_t)pfl->nb_blocs[i] * pfl->sector_len[i]; if (addr <= offset && offset < addr + region_size) { - return pfl->sector_len[i]; + return (SectorInfo) { + .len = pfl->sector_len[i], + .num = sector_num + (offset - addr) / pfl->sector_len[i], + }; } + sector_num += pfl->nb_blocs[i]; addr += region_size; } abort(); } +/* + * Returns true if the offset refers to a flash sector that is currently being + * erased. + */ +static bool pflash_sector_is_erasing(PFlashCFI02 *pfl, hwaddr offset) +{ + long sector_num = pflash_sector_info(pfl, offset).num; + return test_bit(sector_num, pfl->sector_erase_map); +} + static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width) { PFlashCFI02 *pfl = opaque; @@ -280,6 +330,15 @@ static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width) case 0x80: /* We accept reads during second unlock sequence... */ case 0x00: + if (pflash_erase_suspend_mode(pfl) && + pflash_sector_is_erasing(pfl, offset)) { + /* Toggle bit 2, but not 6. */ + toggle_dq2(pfl); + /* Status register read */ + ret = pfl->status; + DPRINTF("%s: status %" PRIx64 "\n", __func__, ret); + break; + } /* Flash area read */ ret = pflash_data_read(pfl, offset, width); break; @@ -305,13 +364,16 @@ static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width) } DPRINTF("%s: ID " TARGET_FMT_plx " %" PRIx64 "\n", __func__, boff, ret); break; - case 0xA0: case 0x10: case 0x30: + /* Toggle bit 2 during erase, but not program. */ + toggle_dq2(pfl); + case 0xA0: + /* Toggle bit 6 */ + toggle_dq6(pfl); /* Status register read */ ret = pfl->status; DPRINTF("%s: status %" PRIx64 "\n", __func__, ret); - toggle_dq6(pfl); break; case 0x98: /* CFI query mode */ @@ -343,7 +405,8 @@ static void pflash_update(PFlashCFI02 *pfl, int offset, int size) static void pflash_sector_erase(PFlashCFI02 *pfl, hwaddr offset) { - uint64_t sector_len = pflash_sector_len(pfl, offset); + SectorInfo sector_info = pflash_sector_info(pfl, offset); + uint64_t sector_len = sector_info.len; offset &= ~(sector_len - 1); DPRINTF("%s: start sector erase at %0*" PRIx64 "-%0*" PRIx64 "\n", __func__, pfl->width * 2, offset, @@ -355,6 +418,7 @@ static void pflash_sector_erase(PFlashCFI02 *pfl, hwaddr offset) } set_dq7(pfl, 0x00); ++pfl->sectors_to_erase; + set_bit(sector_info.num, pfl->sector_erase_map); /* Set (or reset) the 50 us timer for additional erase commands. */ timer_mod(&pfl->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 50000); } @@ -405,6 +469,25 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, pfl->cmd = 0x98; return; } + /* Handle erase resume in erase suspend mode, otherwise reset. */ + if (cmd == 0x30) { + if (pflash_erase_suspend_mode(pfl)) { + /* Resume the erase. */ + timer_mod(&pfl->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + pfl->erase_time_remaining); + pfl->erase_time_remaining = 0; + pfl->wcycle = 6; + pfl->cmd = 0x30; + set_dq7(pfl, 0x00); + assert_dq3(pfl); + return; + } + goto reset_flash; + } + /* Ignore erase suspend. */ + if (cmd == 0xB0) { + return; + } if (boff != pfl->unlock_addr0 || cmd != 0xAA) { DPRINTF("%s: unlock0 failed " TARGET_FMT_plx " %02x %04x\n", __func__, boff, cmd, pfl->unlock_addr0); @@ -450,6 +533,14 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, /* We need another unlock sequence */ goto check_unlock0; case 0xA0: + if (pflash_erase_suspend_mode(pfl) && + pflash_sector_is_erasing(pfl, offset)) { + /* Ignore writes to erasing sectors. */ + if (pfl->bypass) { + goto do_bypass; + } + goto reset_flash; + } trace_pflash_data_write(offset, width << 1, value, 0); if (!pfl->ro) { p = (uint8_t *)pfl->storage + offset; @@ -508,6 +599,10 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, } break; case 5: + if (pflash_erase_suspend_mode(pfl)) { + /* Erasing is not supported in erase suspend mode. */ + goto reset_flash; + } switch (cmd) { case 0x10: if (boff != pfl->unlock_addr0) { @@ -542,6 +637,30 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, /* Ignore writes during chip erase */ return; case 0x30: + if (cmd == 0xB0) { + /* + * If erase suspend happens during the erase timeout (so DQ3 is + * 0), then the device suspends erasing immediately. Set the + * remaining time to be the total time to erase. Otherwise, + * there is a maximum amount of time it can take to enter + * suspend mode. Let's ignore that and suspend immediately and + * set the remaining time to the actual time remaining on the + * timer. + */ + if ((pfl->status & 0x08) == 0) { + pfl->erase_time_remaining = pflash_erase_time(pfl); + } else { + int64_t delta = timer_expire_time_ns(&pfl->timer) - + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + /* Make sure we have a positive time remaining. */ + pfl->erase_time_remaining = delta <= 0 ? 1 : delta; + } + reset_dq3(pfl); + timer_del(&pfl->timer); + pfl->wcycle = 0; + pfl->cmd = 0; + return; + } /* * If DQ3 is 0, additional sector erase commands can be * written and anything else (other than an erase suspend) resets @@ -619,10 +738,12 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) int nb_regions; pfl->chip_len = 0; + pfl->total_sectors = 0; for (nb_regions = 0; nb_regions < PFLASH_MAX_ERASE_REGIONS; ++nb_regions) { if (pfl->nb_blocs[nb_regions] == 0) { break; } + pfl->total_sectors += pfl->nb_blocs[nb_regions]; uint64_t sector_len_per_device = pfl->sector_len[nb_regions]; /* @@ -656,6 +777,7 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) pfl->nb_blocs[0] = pfl->uniform_nb_blocs; pfl->sector_len[0] = pfl->uniform_sector_len; pfl->chip_len = uniform_len; + pfl->total_sectors = pfl->uniform_nb_blocs; } else if (uniform_len != 0 && uniform_len != pfl->chip_len) { error_setg(errp, "\"num-blocks\"*\"sector-length\" " "different from \"num-blocks0\"*\'sector-length0\" + ... + " @@ -697,6 +819,9 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) pfl->unlock_addr0 &= 0x7FF; pfl->unlock_addr1 &= 0x7FF; + /* Allocate memory for a bitmap for sectors being erased. */ + pfl->sector_erase_map = bitmap_new(pfl->total_sectors); + pflash_setup_mappings(pfl); pfl->rom_mode = 1; sysbus_init_mmio(SYS_BUS_DEVICE(dev), &pfl->mem); @@ -781,8 +906,8 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp) /* Address sensitive unlock required. */ pfl->cfi_table[0x05 + pri_ofs] = 0x00; - /* Erase suspend not supported. */ - pfl->cfi_table[0x06 + pri_ofs] = 0x00; + /* Erase suspend to read/write. */ + pfl->cfi_table[0x06 + pri_ofs] = 0x02; /* Sector protect not supported. */ pfl->cfi_table[0x07 + pri_ofs] = 0x00; /* Temporary sector unprotect not supported. */ @@ -829,6 +954,7 @@ static void pflash_cfi02_unrealize(DeviceState *dev, Error **errp) { PFlashCFI02 *pfl = PFLASH_CFI02(dev); timer_del(&pfl->timer); + g_free(pfl->sector_erase_map); } static void pflash_cfi02_class_init(ObjectClass *klass, void *data) diff --git a/tests/pflash-cfi02-test.c b/tests/pflash-cfi02-test.c index 303bc87820..d3b23f4f66 100644 --- a/tests/pflash-cfi02-test.c +++ b/tests/pflash-cfi02-test.c @@ -43,6 +43,8 @@ typedef struct { #define CHIP_ERASE_CMD 0x10 #define UNLOCK_BYPASS_CMD 0x20 #define UNLOCK_BYPASS_RESET_CMD 0x00 +#define ERASE_SUSPEND_CMD 0xB0 +#define ERASE_RESUME_CMD SECTOR_ERASE_CMD typedef struct { int bank_width; @@ -241,6 +243,16 @@ static void chip_erase(const FlashConfig *c) flash_cmd(c, UNLOCK0_ADDR, CHIP_ERASE_CMD); } +static void erase_suspend(const FlashConfig *c) +{ + flash_cmd(c, FLASH_ADDR(0), ERASE_SUSPEND_CMD); +} + +static void erase_resume(const FlashConfig *c) +{ + flash_cmd(c, FLASH_ADDR(0), ERASE_RESUME_CMD); +} + /* * Test flash commands with a variety of device geometry. */ @@ -312,11 +324,20 @@ static void test_geometry(const void *opaque) uint32_t device_len = 1 << flash_query_1(c, FLASH_ADDR(0x27)); g_assert_cmphex(device_len, ==, UNIFORM_FLASH_SIZE); + /* Check that erase suspend to read/write is supported. */ + uint16_t pri = flash_query_1(c, FLASH_ADDR(0x15)) + + (flash_query_1(c, FLASH_ADDR(0x16)) << 8); + g_assert_cmpint(pri, >=, 0x2D + 4 * nb_erase_regions); + g_assert_cmpint(flash_query(c, FLASH_ADDR(pri + 0)), ==, replicate(c, 'P')); + g_assert_cmpint(flash_query(c, FLASH_ADDR(pri + 1)), ==, replicate(c, 'R')); + g_assert_cmpint(flash_query(c, FLASH_ADDR(pri + 2)), ==, replicate(c, 'I')); + g_assert_cmpint(flash_query_1(c, FLASH_ADDR(pri + 6)), ==, 2); /* R/W */ reset(c); const uint64_t dq7 = replicate(c, 0x80); const uint64_t dq6 = replicate(c, 0x40); const uint64_t dq3 = replicate(c, 0x08); + const uint64_t dq2 = replicate(c, 0x04); uint64_t byte_addr = 0; for (int region = 0; region < nb_erase_regions; ++region) { @@ -456,6 +477,95 @@ static void test_geometry(const void *opaque) } } + /* Test erase suspend/resume during erase timeout. */ + sector_erase(c, 0); + /* + * Check that DQ 3 is 0 and DQ6 and DQ2 are toggling in the sector being + * erased as well as in a sector not being erased. + */ + byte_addr = c->sector_len[0]; + status0 = flash_read(c, 0); + status1 = flash_read(c, 0); + g_assert_cmpint(status0 & dq3, ==, 0); + g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6); + g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); + status0 = flash_read(c, byte_addr); + status1 = flash_read(c, byte_addr); + g_assert_cmpint(status0 & dq3, ==, 0); + g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6); + g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); + + /* + * Check that after suspending, DQ6 does not toggle but DQ2 does toggle in + * an erase suspended sector but that neither toggle (we should be + * getting data) in a sector not being erased. + */ + erase_suspend(c); + status0 = flash_read(c, 0); + status1 = flash_read(c, 0); + g_assert_cmpint(status0 & dq6, ==, status1 & dq6); + g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); + g_assert_cmpint(flash_read(c, byte_addr), ==, flash_read(c, byte_addr)); + + /* Check that after resuming, DQ3 is 1 and DQ6 and DQ2 toggle. */ + erase_resume(c); + status0 = flash_read(c, 0); + status1 = flash_read(c, 0); + g_assert_cmpint(status0 & dq3, ==, dq3); + g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6); + g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); + status0 = flash_read(c, byte_addr); + status1 = flash_read(c, byte_addr); + g_assert_cmpint(status0 & dq3, ==, dq3); + g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6); + g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); + wait_for_completion(c, 0); + + /* Repeat this process but this time suspend after the timeout. */ + sector_erase(c, 0); + qtest_clock_step_next(c->qtest); + /* + * Check that DQ 3 is 1 and DQ6 and DQ2 are toggling in the sector being + * erased as well as in a sector not being erased. + */ + byte_addr = c->sector_len[0]; + status0 = flash_read(c, 0); + status1 = flash_read(c, 0); + g_assert_cmpint(status0 & dq3, ==, dq3); + g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6); + g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); + status0 = flash_read(c, byte_addr); + status1 = flash_read(c, byte_addr); + g_assert_cmpint(status0 & dq3, ==, dq3); + g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6); + g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); + + /* + * Check that after suspending, DQ6 does not toggle but DQ2 does toggle in + * an erase suspended sector but that neither toggle (we should be + * getting data) in a sector not being erased. + */ + erase_suspend(c); + status0 = flash_read(c, 0); + status1 = flash_read(c, 0); + g_assert_cmpint(status0 & dq6, ==, status1 & dq6); + g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); + g_assert_cmpint(flash_read(c, byte_addr), ==, flash_read(c, byte_addr)); + + /* Check that after resuming, DQ3 is 1 and DQ6 and DQ2 toggle. */ + erase_resume(c); + status0 = flash_read(c, 0); + status1 = flash_read(c, 0); + g_assert_cmpint(status0 & dq3, ==, dq3); + g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6); + g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); + status0 = flash_read(c, byte_addr); + status1 = flash_read(c, byte_addr); + g_assert_cmpint(status0 & dq3, ==, dq3); + g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6); + g_assert_cmpint(status0 & dq2, ==, ~status1 & dq2); + wait_for_completion(c, 0); + qtest_quit(qtest); } From 80f2c625cbd67447a750c0c6c5d377dfa65d4d7d Mon Sep 17 00:00:00 2001 From: Stephen Checkoway Date: Fri, 26 Apr 2019 12:26:24 -0400 Subject: [PATCH 25/27] hw/block/pflash_cfi02: Use chip erase time specified in the CFI table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When erasing the chip, use the typical time specified in the CFI table rather than arbitrarily selecting 5 seconds. Since the currently unconfigurable value set in the table is 12, this means a chip erase takes 4096 ms so this isn't a big change in behavior. Signed-off-by: Stephen Checkoway Message-Id: <20190426162624.55977-11-stephen.checkoway@oberlin.edu> Tested-by: Philippe Mathieu-Daudé Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Alistair Francis Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index a3665da3b8..b2d37c33bb 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -617,9 +617,9 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, pflash_update(pfl, 0, pfl->chip_len); } set_dq7(pfl, 0x00); - /* Let's wait 5 seconds before chip erase is done */ + /* Wait the time specified at CFI address 0x22. */ timer_mod(&pfl->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + - (NANOSECONDS_PER_SECOND * 5)); + (1ULL << pfl->cfi_table[0x22]) * SCALE_MS); break; case 0x30: /* Sector erase */ From b03499371785cd9a0d8157ec8bd1c19a2bf8b5c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Sat, 18 May 2019 21:22:03 +0200 Subject: [PATCH 26/27] hw/block/pflash_cfi02: Document commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-by: Alistair Francis Message-Id: <20190627202719.17739-28-philmd@redhat.com> Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index b2d37c33bb..83084b9d72 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -327,7 +327,7 @@ static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width) pfl->wcycle = 0; pfl->cmd = 0; /* fall through to the read code */ - case 0x80: + case 0x80: /* Erase (unlock) */ /* We accept reads during second unlock sequence... */ case 0x00: if (pflash_erase_suspend_mode(pfl) && @@ -342,8 +342,7 @@ static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width) /* Flash area read */ ret = pflash_data_read(pfl, offset, width); break; - case 0x90: - /* flash ID read */ + case 0x90: /* flash ID read */ switch (boff) { case 0x00: case 0x01: @@ -364,11 +363,11 @@ static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width) } DPRINTF("%s: ID " TARGET_FMT_plx " %" PRIx64 "\n", __func__, boff, ret); break; - case 0x10: - case 0x30: + case 0x10: /* Chip Erase */ + case 0x30: /* Sector Erase */ /* Toggle bit 2 during erase, but not program. */ toggle_dq2(pfl); - case 0xA0: + case 0xA0: /* Program */ /* Toggle bit 6 */ toggle_dq6(pfl); /* Status register read */ @@ -470,7 +469,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, return; } /* Handle erase resume in erase suspend mode, otherwise reset. */ - if (cmd == 0x30) { + if (cmd == 0x30) { /* Erase Resume */ if (pflash_erase_suspend_mode(pfl)) { /* Resume the erase. */ timer_mod(&pfl->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + @@ -485,7 +484,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, goto reset_flash; } /* Ignore erase suspend. */ - if (cmd == 0xB0) { + if (cmd == 0xB0) { /* Erase Suspend */ return; } if (boff != pfl->unlock_addr0 || cmd != 0xAA) { @@ -516,9 +515,9 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, case 0x20: pfl->bypass = 1; goto do_bypass; - case 0x80: - case 0x90: - case 0xA0: + case 0x80: /* Erase */ + case 0x90: /* Autoselect */ + case 0xA0: /* Program */ pfl->cmd = cmd; DPRINTF("%s: starting command %02x\n", __func__, cmd); break; @@ -529,10 +528,10 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, break; case 3: switch (pfl->cmd) { - case 0x80: + case 0x80: /* Erase */ /* We need another unlock sequence */ goto check_unlock0; - case 0xA0: + case 0xA0: /* Program */ if (pflash_erase_suspend_mode(pfl) && pflash_sector_is_erasing(pfl, offset)) { /* Ignore writes to erasing sectors. */ @@ -562,7 +561,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, if (pfl->bypass) goto do_bypass; goto reset_flash; - case 0x90: + case 0x90: /* Autoselect */ if (pfl->bypass && cmd == 0x00) { /* Unlock bypass reset */ goto reset_flash; @@ -585,11 +584,11 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, } case 4: switch (pfl->cmd) { - case 0xA0: + case 0xA0: /* Program */ /* Ignore writes while flash data write is occurring */ /* As we suppose write is immediate, this should never happen */ return; - case 0x80: + case 0x80: /* Erase */ goto check_unlock1; default: /* Should never happen */ @@ -604,7 +603,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, goto reset_flash; } switch (cmd) { - case 0x10: + case 0x10: /* Chip Erase */ if (boff != pfl->unlock_addr0) { DPRINTF("%s: chip erase: invalid address " TARGET_FMT_plx "\n", __func__, offset); @@ -621,8 +620,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, timer_mod(&pfl->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + (1ULL << pfl->cfi_table[0x22]) * SCALE_MS); break; - case 0x30: - /* Sector erase */ + case 0x30: /* Sector erase */ pflash_sector_erase(pfl, offset); break; default: @@ -633,10 +631,10 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, break; case 6: switch (pfl->cmd) { - case 0x10: + case 0x10: /* Chip Erase */ /* Ignore writes during chip erase */ return; - case 0x30: + case 0x30: /* Sector erase */ if (cmd == 0xB0) { /* * If erase suspend happens during the erase timeout (so DQ3 is From 3ae0343db69c379beb5750b4ed70794bbed51b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 27 Jun 2019 19:45:08 +0200 Subject: [PATCH 27/27] hw/block/pflash_cfi02: Reduce I/O accesses to 16-bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parallel NOR flashes are limited to 16-bit bus accesses. Remove the 32-bit dead code. Reviewed-by: Alistair Francis Message-Id: <20190627202719.17739-29-philmd@redhat.com> Signed-off-by: Philippe Mathieu-Daudé --- hw/block/pflash_cfi02.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c index 83084b9d72..5392290c72 100644 --- a/hw/block/pflash_cfi02.c +++ b/hw/block/pflash_cfi02.c @@ -317,8 +317,6 @@ static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width) boff = offset & 0xFF; if (pfl->width == 2) { boff = boff >> 1; - } else if (pfl->width == 4) { - boff = boff >> 2; } switch (pfl->cmd) { default: @@ -449,8 +447,6 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, boff = offset; if (pfl->width == 2) { boff = boff >> 1; - } else if (pfl->width == 4) { - boff = boff >> 2; } /* Only the least-significant 11 bits are used in most cases. */ boff &= 0x7FF; @@ -710,6 +706,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value, static const MemoryRegionOps pflash_cfi02_ops = { .read = pflash_read, .write = pflash_write, + .impl.max_access_size = 2, .valid.min_access_size = 1, .valid.max_access_size = 4, .endianness = DEVICE_NATIVE_ENDIAN,