qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH v4 00/10] block/pflash_cfi02: Implement missing AMD pflash functionality
@ 2019-04-26 16:26 Stephen Checkoway
  2019-04-26 16:26 ` Stephen Checkoway
                   ` (10 more replies)
  0 siblings, 11 replies; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

The goal of this patch series implement the following AMD command-set parallel
flash functionality:
- flash interleaving;
- nonuniform sector sizes;
- erase suspend/resume commands; and
- multi-sector erase.

During refactoring and implementation, I discovered several bugs that are
fixed here as well:
- flash commands use only 11-bits of the address in most cases, but the
  current code uses all of them [1];
- entering CFI mode from autoselect mode and then exiting CFI mode should
  return the chip to autoselect mode, but the current code returns to read
  array mode; and
- reset command should be ignored during sector/chip erase, but the current
  code performs the reset.

The first patch in the series adds a test for the existing behavior. Tests for
additional behavior/bug fixes are added in the relevant patch.

1. I found firmware in the wild that relies on the 11-bit address behavior,
   probably due to a bug in the firmware itself.

Changes from v1:
- Fix missing spaces around *, -, and ?;
- Fix missing Signed-off-by line on patch 7; and
- Replace use of errc with g_printerr and exit.

Changes from v2:
- Remove global_qtest from tests; and
- Test the CFI table changes.

Changes from v3:
- Replace err.h/err() with glib functions; and
- Reformat qtest_initf lines.

Stephen Checkoway (10):
  block/pflash_cfi02: Add test for supported commands
  block/pflash_cfi02: Refactor, NFC intended
  block/pflash_cfi02: Fix command address comparison
  block/pflash_cfi02: Implement intereleaved flash devices
  block/pflash_cfi02: Implement nonuniform sector sizes
  block/pflash_cfi02: Fix CFI in autoselect mode
  block/pflash_cfi02: Fix reset command not ignored during erase
  block/pflash_cfi02: Implement multi-sector erase
  block/pflash_cfi02: Implement erase suspend/resume
  block/pflash_cfi02: Use the chip erase time specified in the CFI table

 hw/block/pflash_cfi02.c   | 843 +++++++++++++++++++++++++++-----------
 tests/Makefile.include    |   2 +
 tests/pflash-cfi02-test.c | 812 ++++++++++++++++++++++++++++++++++++
 3 files changed, 1420 insertions(+), 237 deletions(-)
 create mode 100644 tests/pflash-cfi02-test.c

-- 
2.20.1 (Apple Git-117)

^ permalink raw reply	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 00/10] block/pflash_cfi02: Implement missing AMD pflash functionality
  2019-04-26 16:26 [Qemu-devel] [PATCH v4 00/10] block/pflash_cfi02: Implement missing AMD pflash functionality Stephen Checkoway
@ 2019-04-26 16:26 ` Stephen Checkoway
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 01/10] block/pflash_cfi02: Add test for supported commands Stephen Checkoway
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

The goal of this patch series implement the following AMD command-set parallel
flash functionality:
- flash interleaving;
- nonuniform sector sizes;
- erase suspend/resume commands; and
- multi-sector erase.

During refactoring and implementation, I discovered several bugs that are
fixed here as well:
- flash commands use only 11-bits of the address in most cases, but the
  current code uses all of them [1];
- entering CFI mode from autoselect mode and then exiting CFI mode should
  return the chip to autoselect mode, but the current code returns to read
  array mode; and
- reset command should be ignored during sector/chip erase, but the current
  code performs the reset.

The first patch in the series adds a test for the existing behavior. Tests for
additional behavior/bug fixes are added in the relevant patch.

1. I found firmware in the wild that relies on the 11-bit address behavior,
   probably due to a bug in the firmware itself.

Changes from v1:
- Fix missing spaces around *, -, and ?;
- Fix missing Signed-off-by line on patch 7; and
- Replace use of errc with g_printerr and exit.

Changes from v2:
- Remove global_qtest from tests; and
- Test the CFI table changes.

Changes from v3:
- Replace err.h/err() with glib functions; and
- Reformat qtest_initf lines.

Stephen Checkoway (10):
  block/pflash_cfi02: Add test for supported commands
  block/pflash_cfi02: Refactor, NFC intended
  block/pflash_cfi02: Fix command address comparison
  block/pflash_cfi02: Implement intereleaved flash devices
  block/pflash_cfi02: Implement nonuniform sector sizes
  block/pflash_cfi02: Fix CFI in autoselect mode
  block/pflash_cfi02: Fix reset command not ignored during erase
  block/pflash_cfi02: Implement multi-sector erase
  block/pflash_cfi02: Implement erase suspend/resume
  block/pflash_cfi02: Use the chip erase time specified in the CFI table

 hw/block/pflash_cfi02.c   | 843 +++++++++++++++++++++++++++-----------
 tests/Makefile.include    |   2 +
 tests/pflash-cfi02-test.c | 812 ++++++++++++++++++++++++++++++++++++
 3 files changed, 1420 insertions(+), 237 deletions(-)
 create mode 100644 tests/pflash-cfi02-test.c

-- 
2.20.1 (Apple Git-117)



^ permalink raw reply	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 01/10] block/pflash_cfi02: Add test for supported commands
  2019-04-26 16:26 [Qemu-devel] [PATCH v4 00/10] block/pflash_cfi02: Implement missing AMD pflash functionality Stephen Checkoway
  2019-04-26 16:26 ` Stephen Checkoway
@ 2019-04-26 16:26 ` Stephen Checkoway
  2019-04-26 16:26   ` Stephen Checkoway
  2019-05-06  3:53   ` Thomas Huth
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 02/10] block/pflash_cfi02: Refactor, NFC intended Stephen Checkoway
                   ` (8 subsequent siblings)
  10 siblings, 2 replies; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

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 <stephen.checkoway@oberlin.edu>
---
 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 36fc73fef5..dbdb2c0082 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -263,6 +263,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)
@@ -773,6 +774,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..40af1bb523
--- /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_cmpint(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_cmpint(flash_read(FLASH_WIDTH * 0x0000), ==, 0x00BF);
+    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x0001), ==, 0x236D);
+    reset();
+
+    /* Check the erase blocks. */
+    flash_write(CFI_ADDR, CFI_CMD);
+    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x10), ==, 'Q');
+    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x11), ==, 'R');
+    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x12), ==, 'Y');
+    /* Num erase regions. */
+    g_assert_cmpint(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_cmpint(status0 & 0x80, ==, 0);
+        uint16_t status1 = flash_read(byte_addr);
+        /* DQ6 toggles during an erase. */
+        g_assert_cmpint(status0 & 0x40, !=, status1 & 0x40);
+        /* Wait for erase to complete. */
+        clock_step_next();
+        /* Ensure DQ6 has stopped toggling. */
+        g_assert_cmpint(flash_read(byte_addr), ==, flash_read(byte_addr));
+        /* Now the data should be valid. */
+        g_assert_cmpint(flash_read(byte_addr), ==, 0xFFFF);
+
+        /* Program a bit pattern. */
+        program(byte_addr, 0x5555);
+        g_assert_cmpint(flash_read(byte_addr), ==, 0x5555);
+        program(byte_addr, 0xAA55);
+        g_assert_cmpint(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_cmpint(status0 & 0x80, ==, 0);
+    uint16_t status1 = flash_read(0);
+    /* DQ6 toggles during an erase. */
+    g_assert_cmpint(status0 & 0x40, !=, status1 & 0x40);
+    /* Wait for erase to complete. */
+    clock_step_next();
+    /* Ensure DQ6 has stopped toggling. */
+    g_assert_cmpint(flash_read(0), ==, flash_read(0));
+    /* Now the data should be valid. */
+    g_assert_cmpint(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_cmpint(flash_read(0), ==, 0x0123);
+    g_assert_cmpint(flash_read(2), ==, 0x4567);
+    g_assert_cmpint(flash_read(4), ==, 0x89AB);
+    g_assert_cmpint(flash_read(6), ==, 0xCDEF);
+    g_assert_cmpint(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;
+}
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 01/10] block/pflash_cfi02: Add test for supported commands
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 01/10] block/pflash_cfi02: Add test for supported commands Stephen Checkoway
@ 2019-04-26 16:26   ` Stephen Checkoway
  2019-05-06  3:53   ` Thomas Huth
  1 sibling, 0 replies; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

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 <stephen.checkoway@oberlin.edu>
---
 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 36fc73fef5..dbdb2c0082 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -263,6 +263,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)
@@ -773,6 +774,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..40af1bb523
--- /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_cmpint(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_cmpint(flash_read(FLASH_WIDTH * 0x0000), ==, 0x00BF);
+    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x0001), ==, 0x236D);
+    reset();
+
+    /* Check the erase blocks. */
+    flash_write(CFI_ADDR, CFI_CMD);
+    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x10), ==, 'Q');
+    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x11), ==, 'R');
+    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x12), ==, 'Y');
+    /* Num erase regions. */
+    g_assert_cmpint(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_cmpint(status0 & 0x80, ==, 0);
+        uint16_t status1 = flash_read(byte_addr);
+        /* DQ6 toggles during an erase. */
+        g_assert_cmpint(status0 & 0x40, !=, status1 & 0x40);
+        /* Wait for erase to complete. */
+        clock_step_next();
+        /* Ensure DQ6 has stopped toggling. */
+        g_assert_cmpint(flash_read(byte_addr), ==, flash_read(byte_addr));
+        /* Now the data should be valid. */
+        g_assert_cmpint(flash_read(byte_addr), ==, 0xFFFF);
+
+        /* Program a bit pattern. */
+        program(byte_addr, 0x5555);
+        g_assert_cmpint(flash_read(byte_addr), ==, 0x5555);
+        program(byte_addr, 0xAA55);
+        g_assert_cmpint(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_cmpint(status0 & 0x80, ==, 0);
+    uint16_t status1 = flash_read(0);
+    /* DQ6 toggles during an erase. */
+    g_assert_cmpint(status0 & 0x40, !=, status1 & 0x40);
+    /* Wait for erase to complete. */
+    clock_step_next();
+    /* Ensure DQ6 has stopped toggling. */
+    g_assert_cmpint(flash_read(0), ==, flash_read(0));
+    /* Now the data should be valid. */
+    g_assert_cmpint(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_cmpint(flash_read(0), ==, 0x0123);
+    g_assert_cmpint(flash_read(2), ==, 0x4567);
+    g_assert_cmpint(flash_read(4), ==, 0x89AB);
+    g_assert_cmpint(flash_read(6), ==, 0xCDEF);
+    g_assert_cmpint(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;
+}
-- 
2.20.1 (Apple Git-117)



^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 02/10] block/pflash_cfi02: Refactor, NFC intended
  2019-04-26 16:26 [Qemu-devel] [PATCH v4 00/10] block/pflash_cfi02: Implement missing AMD pflash functionality Stephen Checkoway
  2019-04-26 16:26 ` Stephen Checkoway
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 01/10] block/pflash_cfi02: Add test for supported commands Stephen Checkoway
@ 2019-04-26 16:26 ` Stephen Checkoway
  2019-04-26 16:26   ` Stephen Checkoway
  2019-05-06  7:34   ` Philippe Mathieu-Daudé
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 03/10] block/pflash_cfi02: Fix command address comparison Stephen Checkoway
                   ` (7 subsequent siblings)
  10 siblings, 2 replies; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

Simplify and refactor for upcoming commits. In particular, 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 <stephen.checkoway@oberlin.edu>
---
 hw/block/pflash_cfi02.c | 221 +++++++++++++++++-----------------------
 1 file changed, 95 insertions(+), 126 deletions(-)

diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c
index f2c6201f81..4b7af71806 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -46,18 +46,19 @@
 #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
 
+/* Special write cycle for CFI queries. */
+#define WCYCLE_CFI 7
+
 struct PFlashCFI02 {
     /*< private >*/
     SysBusDevice parent_obj;
@@ -97,6 +98,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.
  */
@@ -126,7 +152,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 {
@@ -136,12 +162,34 @@ static void pflash_timer (void *opaque)
     pfl->cmd = 0;
 }
 
-static uint32_t pflash_read(PFlashCFI02 *pfl, hwaddr offset,
-                            int width, int be)
+/*
+ * 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);
+    /* XXX: Need a trace_pflash_data_read(offset, ret, width) */
+    switch (width) {
+    case 1:
+        trace_pflash_data_read8(offset, ret);
+        break;
+    case 2:
+        trace_pflash_data_read16(offset, ret);
+        break;
+    case 4:
+        trace_pflash_data_read32(offset, ret);
+        break;
+    }
+    return ret;
+}
+
+static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width)
+{
+    PFlashCFI02 *pfl = opaque;
     hwaddr boff;
-    uint32_t ret;
-    uint8_t *p;
+    uint64_t ret;
 
     ret = -1;
     trace_pflash_read(offset, pfl->cmd, width, pfl->wcycle);
@@ -166,39 +214,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 = pfl->storage;
-        switch (width) {
-        case 1:
-            ret = p[offset];
-            trace_pflash_data_read8(offset, ret);
-            break;
-        case 2:
-            if (be) {
-                ret = p[offset] << 8;
-                ret |= p[offset + 1];
-            } else {
-                ret = p[offset];
-                ret |= p[offset + 1] << 8;
-            }
-            trace_pflash_data_read16(offset, ret);
-            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;
-            }
-            trace_pflash_data_read32(offset, ret);
-            break;
-        }
+        ret = pflash_data_read(pfl, offset, width);
         break;
     case 0x90:
         /* flash ID read */
@@ -213,23 +230,23 @@ 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;
+            ret = pflash_data_read(pfl, offset, width);
         }
-        DPRINTF("%s: ID " TARGET_FMT_plx " %x\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 %x\n", __func__, ret);
+        DPRINTF("%s: status %" PRIx64 "\n", __func__, ret);
         /* Toggle bit 6 */
-        pfl->status ^= 0x40;
+        toggle_dq6(pfl);
         break;
     case 0x98:
         /* CFI query mode */
@@ -245,8 +262,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) {
@@ -259,9 +275,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;
@@ -277,7 +294,7 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset,
     trace_pflash_write(offset, value, width, pfl->wcycle);
     offset &= pfl->chip_len - 1;
 
-    DPRINTF("%s: offset " TARGET_FMT_plx " %08x %d\n", __func__,
+    DPRINTF("%s: offset " TARGET_FMT_plx " %08" PRIx64 " %d\n", __func__,
             offset, value, width);
     boff = offset & (pfl->sector_len - 1);
     if (pfl->width == 2)
@@ -295,7 +312,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;
         }
@@ -345,40 +362,22 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset,
             goto check_unlock0;
         case 0xA0:
             trace_pflash_data_write(offset, value, width, 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);
             }
-            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;
@@ -426,7 +425,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));
@@ -441,7 +440,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));
@@ -467,7 +466,7 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset,
             goto reset_flash;
         }
         break;
-    case 7: /* Special value for CFI queries */
+    case WCYCLE_CFI: /* Special value for CFI queries */
         DPRINTF("%s: invalid write in CFI query mode\n", __func__);
         goto reset_flash;
     default:
@@ -492,39 +491,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,
@@ -552,9 +521,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;
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 02/10] block/pflash_cfi02: Refactor, NFC intended
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 02/10] block/pflash_cfi02: Refactor, NFC intended Stephen Checkoway
@ 2019-04-26 16:26   ` Stephen Checkoway
  2019-05-06  7:34   ` Philippe Mathieu-Daudé
  1 sibling, 0 replies; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

Simplify and refactor for upcoming commits. In particular, 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 <stephen.checkoway@oberlin.edu>
---
 hw/block/pflash_cfi02.c | 221 +++++++++++++++++-----------------------
 1 file changed, 95 insertions(+), 126 deletions(-)

diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c
index f2c6201f81..4b7af71806 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -46,18 +46,19 @@
 #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
 
+/* Special write cycle for CFI queries. */
+#define WCYCLE_CFI 7
+
 struct PFlashCFI02 {
     /*< private >*/
     SysBusDevice parent_obj;
@@ -97,6 +98,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.
  */
@@ -126,7 +152,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 {
@@ -136,12 +162,34 @@ static void pflash_timer (void *opaque)
     pfl->cmd = 0;
 }
 
-static uint32_t pflash_read(PFlashCFI02 *pfl, hwaddr offset,
-                            int width, int be)
+/*
+ * 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);
+    /* XXX: Need a trace_pflash_data_read(offset, ret, width) */
+    switch (width) {
+    case 1:
+        trace_pflash_data_read8(offset, ret);
+        break;
+    case 2:
+        trace_pflash_data_read16(offset, ret);
+        break;
+    case 4:
+        trace_pflash_data_read32(offset, ret);
+        break;
+    }
+    return ret;
+}
+
+static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width)
+{
+    PFlashCFI02 *pfl = opaque;
     hwaddr boff;
-    uint32_t ret;
-    uint8_t *p;
+    uint64_t ret;
 
     ret = -1;
     trace_pflash_read(offset, pfl->cmd, width, pfl->wcycle);
@@ -166,39 +214,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 = pfl->storage;
-        switch (width) {
-        case 1:
-            ret = p[offset];
-            trace_pflash_data_read8(offset, ret);
-            break;
-        case 2:
-            if (be) {
-                ret = p[offset] << 8;
-                ret |= p[offset + 1];
-            } else {
-                ret = p[offset];
-                ret |= p[offset + 1] << 8;
-            }
-            trace_pflash_data_read16(offset, ret);
-            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;
-            }
-            trace_pflash_data_read32(offset, ret);
-            break;
-        }
+        ret = pflash_data_read(pfl, offset, width);
         break;
     case 0x90:
         /* flash ID read */
@@ -213,23 +230,23 @@ 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;
+            ret = pflash_data_read(pfl, offset, width);
         }
-        DPRINTF("%s: ID " TARGET_FMT_plx " %x\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 %x\n", __func__, ret);
+        DPRINTF("%s: status %" PRIx64 "\n", __func__, ret);
         /* Toggle bit 6 */
-        pfl->status ^= 0x40;
+        toggle_dq6(pfl);
         break;
     case 0x98:
         /* CFI query mode */
@@ -245,8 +262,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) {
@@ -259,9 +275,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;
@@ -277,7 +294,7 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset,
     trace_pflash_write(offset, value, width, pfl->wcycle);
     offset &= pfl->chip_len - 1;
 
-    DPRINTF("%s: offset " TARGET_FMT_plx " %08x %d\n", __func__,
+    DPRINTF("%s: offset " TARGET_FMT_plx " %08" PRIx64 " %d\n", __func__,
             offset, value, width);
     boff = offset & (pfl->sector_len - 1);
     if (pfl->width == 2)
@@ -295,7 +312,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;
         }
@@ -345,40 +362,22 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset,
             goto check_unlock0;
         case 0xA0:
             trace_pflash_data_write(offset, value, width, 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);
             }
-            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;
@@ -426,7 +425,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));
@@ -441,7 +440,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));
@@ -467,7 +466,7 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset,
             goto reset_flash;
         }
         break;
-    case 7: /* Special value for CFI queries */
+    case WCYCLE_CFI: /* Special value for CFI queries */
         DPRINTF("%s: invalid write in CFI query mode\n", __func__);
         goto reset_flash;
     default:
@@ -492,39 +491,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,
@@ -552,9 +521,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;
-- 
2.20.1 (Apple Git-117)



^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 03/10] block/pflash_cfi02: Fix command address comparison
  2019-04-26 16:26 [Qemu-devel] [PATCH v4 00/10] block/pflash_cfi02: Implement missing AMD pflash functionality Stephen Checkoway
                   ` (2 preceding siblings ...)
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 02/10] block/pflash_cfi02: Refactor, NFC intended Stephen Checkoway
@ 2019-04-26 16:26 ` Stephen Checkoway
  2019-04-26 16:26   ` Stephen Checkoway
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 04/10] block/pflash_cfi02: Implement intereleaved flash devices Stephen Checkoway
                   ` (6 subsequent siblings)
  10 siblings, 1 reply; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

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 <stephen.checkoway@oberlin.edu>
Acked-by: Thomas Huth <thuth@redhat.com>
---
 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 4b7af71806..e4bff0c8f8 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -296,11 +296,13 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
 
     DPRINTF("%s: offset " TARGET_FMT_plx " %08" PRIx64 " %d\n", __func__,
             offset, value, width);
-    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 */
@@ -519,6 +521,10 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
         return;
     }
 
+    /* Only 11 bits are used in the comparison. */
+    pfl->unlock_addr0 &= 0x7FF;
+    pfl->unlock_addr1 &= 0x7FF;
+
     chip_len = pfl->sector_len * pfl->nb_blocs;
 
     memory_region_init_rom_device(&pfl->orig_mem, OBJECT(pfl),
diff --git a/tests/pflash-cfi02-test.c b/tests/pflash-cfi02-test.c
index 40af1bb523..ea5f8b2648 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_cmpint(flash_read(6), ==, 0xCDEF);
     g_assert_cmpint(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);
 }
 
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 03/10] block/pflash_cfi02: Fix command address comparison
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 03/10] block/pflash_cfi02: Fix command address comparison Stephen Checkoway
@ 2019-04-26 16:26   ` Stephen Checkoway
  0 siblings, 0 replies; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

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 <stephen.checkoway@oberlin.edu>
Acked-by: Thomas Huth <thuth@redhat.com>
---
 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 4b7af71806..e4bff0c8f8 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -296,11 +296,13 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
 
     DPRINTF("%s: offset " TARGET_FMT_plx " %08" PRIx64 " %d\n", __func__,
             offset, value, width);
-    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 */
@@ -519,6 +521,10 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
         return;
     }
 
+    /* Only 11 bits are used in the comparison. */
+    pfl->unlock_addr0 &= 0x7FF;
+    pfl->unlock_addr1 &= 0x7FF;
+
     chip_len = pfl->sector_len * pfl->nb_blocs;
 
     memory_region_init_rom_device(&pfl->orig_mem, OBJECT(pfl),
diff --git a/tests/pflash-cfi02-test.c b/tests/pflash-cfi02-test.c
index 40af1bb523..ea5f8b2648 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_cmpint(flash_read(6), ==, 0xCDEF);
     g_assert_cmpint(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);
 }
 
-- 
2.20.1 (Apple Git-117)



^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 04/10] block/pflash_cfi02: Implement intereleaved flash devices
  2019-04-26 16:26 [Qemu-devel] [PATCH v4 00/10] block/pflash_cfi02: Implement missing AMD pflash functionality Stephen Checkoway
                   ` (3 preceding siblings ...)
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 03/10] block/pflash_cfi02: Fix command address comparison Stephen Checkoway
@ 2019-04-26 16:26 ` Stephen Checkoway
  2019-04-26 16:26   ` Stephen Checkoway
  2019-06-22 12:25   ` Philippe Mathieu-Daudé
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 05/10] block/pflash_cfi02: Implement nonuniform sector sizes Stephen Checkoway
                   ` (5 subsequent siblings)
  10 siblings, 2 replies; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

It's common for multiple narrow flash chips to be hooked up in parallel
to support wider buses. For example, four 8-bit wide flash chips (x8)
may be combined in parallel to produce a 32-bit wide device. Similarly,
two 16-bit wide chips (x16) may be combined.

This commit introduces `device-width` and `max-device-width` properties,
similar to pflash_cfi01, with the following meanings:
- `width`: The width of the logical, qemu device (same as before);
- `device-width`: The width of an individual flash chip, defaulting to
  `width`; and
- `max-device-width`: The maximum width of an individual flash chip,
  defaulting to `device-width`.

Nothing needs to change to support reading such interleaved devices but
commands (e.g., erase and programming) must be sent to all devices at
the same time or else the various chips will be in different states.

For example, a 4-byte wide logical device can be composed of four x8/x16
devices in x8 mode. That is, each device supports both x8 or x16 and
they're being used in the byte, rather than word, mode. This
configuration would have `width=4`, `device-width=1`, and
`max-device-width=2`.

In addition to commands being sent to all devices, guest firmware
expects the status and CFI queries to be replicated for each device.
(The one exception to the response replication is that each device gets
to report its own status bit DQ7 while programming because its value
depends on the value being programmed which will usually differ for each
device.)

Testing is limited to 16-bit wide devices due to the current inability
to override the properties set by `pflash_cfi02_register`, but multiple
configurations are tested.

Stop using global_qtest. Instead, package the qtest variable inside the
FlashConfig structure.

Signed-off-by: Stephen Checkoway <stephen.checkoway@oberlin.edu>
Acked-by: Thomas Huth <thuth@redhat.com>
---
 hw/block/pflash_cfi02.c   | 270 +++++++++++++++------
 tests/pflash-cfi02-test.c | 476 ++++++++++++++++++++++++++++++--------
 2 files changed, 576 insertions(+), 170 deletions(-)

diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c
index e4bff0c8f8..101628b4ec 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -28,7 +28,6 @@
  * - unlock bypass command
  * - 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
@@ -67,15 +66,19 @@ struct PFlashCFI02 {
     BlockBackend *blk;
     uint32_t sector_len;
     uint32_t nb_blocs;
-    uint32_t chip_len;
+    uint64_t total_len;
+    uint64_t interleave_multiplier;
     uint8_t mappings;
-    uint8_t width;
+    uint8_t bank_width; /* Width of the QEMU device in bytes. */
+    uint8_t device_width; /* Width of individual pflash chip. */
+    uint8_t max_device_width; /* Maximum width of individual pflash chip. */
     uint8_t be;
+    int device_shift; /* Amount to shift an offset to get a device address. */
     int wcycle; /* if 0, the flash is read normally */
     int bypass;
     int ro;
     uint8_t cmd;
-    uint8_t status;
+    uint64_t status;
     /* FIXME: implement array device properties */
     uint16_t ident0;
     uint16_t ident1;
@@ -103,16 +106,17 @@ struct PFlashCFI02 {
  */
 static inline void toggle_dq7(PFlashCFI02 *pfl)
 {
-    pfl->status ^= 0x80;
+    pfl->status ^= pfl->interleave_multiplier * 0x80;
 }
 
 /*
  * Set status bit DQ7 to bit 7 of value.
  */
-static inline void set_dq7(PFlashCFI02 *pfl, uint8_t value)
+static inline void set_dq7(PFlashCFI02 *pfl, uint64_t value)
 {
-    pfl->status &= 0x7F;
-    pfl->status |= value & 0x80;
+    uint64_t mask = pfl->interleave_multiplier * 0x80;
+    pfl->status &= ~mask;
+    pfl->status |= value & mask;
 }
 
 /*
@@ -120,7 +124,7 @@ static inline void set_dq7(PFlashCFI02 *pfl, uint8_t value)
  */
 static inline void toggle_dq6(PFlashCFI02 *pfl)
 {
-    pfl->status ^= 0x40;
+    pfl->status ^= pfl->interleave_multiplier * 0x40;
 }
 
 /*
@@ -188,7 +192,6 @@ static uint64_t pflash_data_read(PFlashCFI02 *pfl, hwaddr offset,
 static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width)
 {
     PFlashCFI02 *pfl = opaque;
-    hwaddr boff;
     uint64_t ret;
 
     ret = -1;
@@ -198,12 +201,10 @@ static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width)
         ++pfl->read_counter > PFLASH_LAZY_ROMD_THRESHOLD) {
         pflash_register_memory(pfl, 1);
     }
-    offset &= pfl->chip_len - 1;
-    boff = offset & 0xFF;
-    if (pfl->width == 2)
-        boff = boff >> 1;
-    else if (pfl->width == 4)
-        boff = boff >> 2;
+    /* Mask by the total length of the chip to account for alias mappings. */
+    offset &= pfl->total_len - 1;
+    hwaddr device_addr = offset >> pfl->device_shift;
+
     switch (pfl->cmd) {
     default:
         /* This should never happen : reset state & treat it as a read*/
@@ -215,29 +216,32 @@ static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width)
         /* We accept reads during second unlock sequence... */
     case 0x00:
         /* Flash area read */
-        ret = pflash_data_read(pfl, offset, width);
-        break;
+        return pflash_data_read(pfl, offset, width);
     case 0x90:
         /* flash ID read */
-        switch (boff) {
+        switch (device_addr & 0xFF) {
         case 0x00:
+            ret = pfl->ident0;
+            break;
         case 0x01:
-            ret = boff & 0x01 ? pfl->ident1 : pfl->ident0;
+            ret = pfl->ident1;
             break;
         case 0x02:
             ret = 0x00; /* Pretend all sectors are unprotected */
             break;
         case 0x0E:
         case 0x0F:
-            ret = boff & 0x01 ? pfl->ident3 : pfl->ident2;
+            ret = device_addr & 0x01 ? pfl->ident3 : pfl->ident2;
             if (ret != (uint8_t)-1) {
                 break;
             }
             /* Fall through to data read. */
         default:
-            ret = pflash_data_read(pfl, offset, width);
+            return pflash_data_read(pfl, offset, width);
         }
-        DPRINTF("%s: ID " TARGET_FMT_plx " %" PRIx64 "\n", __func__, boff, ret);
+        ret *= pfl->interleave_multiplier;
+        DPRINTF("%s: ID " TARGET_FMT_plx " %" PRIx64 "\n",
+                __func__, device_addr & 0xFF, ret);
         break;
     case 0xA0:
     case 0x10:
@@ -250,8 +254,8 @@ static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width)
         break;
     case 0x98:
         /* CFI query mode */
-        if (boff < sizeof(pfl->cfi_table)) {
-            ret = pfl->cfi_table[boff];
+        if (device_addr < sizeof(pfl->cfi_table)) {
+            ret = pfl->interleave_multiplier * pfl->cfi_table[device_addr];
         } else {
             ret = 0;
         }
@@ -279,30 +283,36 @@ 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;
 
     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;
+    if (pfl->cmd != 0xA0) {
+        if (value != pfl->interleave_multiplier * cmd) {
+            DPRINTF("%s: cmd 0x%02x not sent to all devices: expected="
+                    "0x%0*" PRIx64 " actual=0x%0*" PRIx64 "\n",
+                    __func__, cmd,
+                    pfl->bank_width * 2, pfl->interleave_multiplier * cmd,
+                    pfl->bank_width * 2, value);
+        }
+
+        if (cmd == 0xF0) {
+            goto reset_flash;
+        }
     }
+
     trace_pflash_write(offset, value, width, pfl->wcycle);
-    offset &= pfl->chip_len - 1;
-
-    DPRINTF("%s: offset " TARGET_FMT_plx " %08" PRIx64 " %d\n", __func__,
-            offset, value, width);
-    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;
+
+    /* Mask by the total length of the chip to account for alias mappings. */
+    offset &= pfl->total_len - 1;
+
+    DPRINTF("%s: offset " TARGET_FMT_plx " 0x%0*" PRIx64 "\n",
+            __func__, offset, width * 2, value);
+
+    hwaddr device_addr = (offset >> pfl->device_shift);
+    /* Address bits A11 and greater are don't cares for most commands. */
+    unsigned int masked_addr = device_addr & 0x7FF;
+
     switch (pfl->wcycle) {
     case 0:
         /* Set the device in I/O access mode if required */
@@ -311,16 +321,16 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
         pfl->read_counter = 0;
         /* We're in read mode */
     check_unlock0:
-        if (boff == 0x55 && cmd == 0x98) {
+        if (masked_addr == 0x55 && cmd == 0x98) {
         enter_CFI_mode:
             /* Enter CFI query mode */
             pfl->wcycle = WCYCLE_CFI;
             pfl->cmd = 0x98;
             return;
         }
-        if (boff != pfl->unlock_addr0 || cmd != 0xAA) {
-            DPRINTF("%s: unlock0 failed " TARGET_FMT_plx " %02x %04x\n",
-                    __func__, boff, cmd, pfl->unlock_addr0);
+        if (masked_addr != pfl->unlock_addr0 || cmd != 0xAA) {
+            DPRINTF("%s: unlock0 failed %04x %02x %04x\n",
+                    __func__, masked_addr, cmd, pfl->unlock_addr0);
             goto reset_flash;
         }
         DPRINTF("%s: unlock sequence started\n", __func__);
@@ -328,18 +338,18 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
     case 1:
         /* We started an unlock sequence */
     check_unlock1:
-        if (boff != pfl->unlock_addr1 || cmd != 0x55) {
-            DPRINTF("%s: unlock1 failed " TARGET_FMT_plx " %02x\n", __func__,
-                    boff, cmd);
+        if (masked_addr != pfl->unlock_addr1 || cmd != 0x55) {
+            DPRINTF("%s: unlock1 failed %03x %02x\n", __func__,
+                    masked_addr, cmd);
             goto reset_flash;
         }
         DPRINTF("%s: unlock sequence done\n", __func__);
         break;
     case 2:
         /* We finished an unlock sequence */
-        if (!pfl->bypass && boff != pfl->unlock_addr0) {
-            DPRINTF("%s: command failed " TARGET_FMT_plx " %02x\n", __func__,
-                    boff, cmd);
+        if (!pfl->bypass && masked_addr != pfl->unlock_addr0) {
+            DPRINTF("%s: command failed %03x %02x\n", __func__,
+                    masked_addr, cmd);
             goto reset_flash;
         }
         switch (cmd) {
@@ -390,8 +400,9 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
                 goto reset_flash;
             }
             /* We can enter CFI query mode from autoselect mode */
-            if (boff == 0x55 && cmd == 0x98)
+            if (masked_addr == 0x55 && cmd == 0x98) {
                 goto enter_CFI_mode;
+            }
             /* No break here */
         default:
             DPRINTF("%s: invalid write for command %02x\n",
@@ -416,7 +427,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
     case 5:
         switch (cmd) {
         case 0x10:
-            if (boff != pfl->unlock_addr0) {
+            if (masked_addr != pfl->unlock_addr0) {
                 DPRINTF("%s: chip erase: invalid address " TARGET_FMT_plx "\n",
                         __func__, offset);
                 goto reset_flash;
@@ -424,8 +435,8 @@ 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);
-                pflash_update(pfl, 0, pfl->chip_len);
+                memset(pfl->storage, 0xFF, pfl->total_len);
+                pflash_update(pfl, 0, pfl->total_len);
             }
             set_dq7(pfl, 0x00);
             /* Let's wait 5 seconds before chip erase is done */
@@ -521,22 +532,132 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
         return;
     }
 
+    if (pfl->bank_width == 0) {
+        error_setg(errp, "attribute \"width\" not specified or zero.");
+        return;
+    }
+
+    /*
+     * device-width defaults to width and max-device-width defaults to
+     * device-width. Check that the device-width and max-device-width
+     * configurations are supported.
+     */
+    if (pfl->device_width == 0) {
+        pfl->device_width = pfl->bank_width;
+    }
+    if (pfl->max_device_width == 0) {
+        pfl->max_device_width = pfl->device_width;
+    }
+    if (pfl->bank_width % pfl->device_width != 0) {
+        error_setg(errp,
+                   "attribute \"width\" (%u) not a multiple of attribute "
+                   "\"device-width\" (%u).",
+                   pfl->bank_width, pfl->device_width);
+        return;
+    }
+
+    /*
+     * Writing commands to the flash device and reading CFI responses or
+     * status values requires transforming a QEMU device offset into a
+     * flash device address given in terms of the device's maximum width. We
+     * can do this by shifting a QEMU device offset right a constant number of
+     * bits depending on the bank_width, device_width, and max_device_width.
+     *
+     * num_devices = bank_width / device_width is the number of interleaved
+     * flash devices. To compute a device byte address, we need to divide
+     * offset by num_devices (equivalently shift right by log2(num_devices)).
+     * To turn a device byte address into a device word address, we need to
+     * divide by max_device_width (equivalently shift right by
+     * log2(max_device_width)).
+     *
+     * In tabular form.
+     * ==================================================================
+     * bank_width   device_width    max_device_width    num_devices shift
+     * ------------------------------------------------------------------
+     * 1            1               1                   1           0
+     * 1            1               2                   1           1
+     * 2            1               1                   2           1
+     * 2            1               2                   2           2
+     * 2            2               2                   1           1
+     * 4            1               1                   4           2
+     * 4            1               2                   4           3
+     * 4            1               4                   4           4
+     * 4            2               2                   2           2
+     * 4            2               4                   2           3
+     * 4            4               4                   1           2
+     * ==================================================================
+     */
+    pfl->device_shift = ctz32(pfl->bank_width) - ctz32(pfl->device_width) +
+                        ctz32(pfl->max_device_width);
+    pfl->interleave_multiplier = 0;
+    for (unsigned int shift = 0; shift < pfl->bank_width;
+         shift += pfl->device_width) {
+        pfl->interleave_multiplier |= 1 << (shift * 8);
+    }
+
+    uint16_t device_interface_code;
+    if (pfl->max_device_width == 1 && pfl->device_width == 1) {
+        device_interface_code = 0; /* x8 only. */
+    } else if (pfl->max_device_width == 2 &&
+               (pfl->device_width == 1 || pfl->device_width == 2)) {
+        /* XXX: Some devices only support x16, this code doesn't model them. */
+        device_interface_code = 2; /* Supports x8 or x16. */
+    } else if (pfl->max_device_width == 4 && pfl->device_width == 1) {
+        /*
+         * XXX: this is x32-only. The standards I've seen don't specify a value
+         * for x8/x32 but do mention them.
+         */
+        device_interface_code = 3; /* x32 only. */
+    } else if (pfl->max_device_width == 4 &&
+               (pfl->device_width == 2 || pfl->device_width == 4)) {
+        device_interface_code = 4; /* Supports x16 or x32. */
+    } else {
+        error_setg(errp,
+                   "unsupported configuration: \"device-width\"=%u "
+                   "\"max-device-width\"=%u.",
+                   pfl->device_width, pfl->max_device_width);
+        return;
+    }
+
+    pfl->total_len = pfl->sector_len * pfl->nb_blocs;
+
+    /*
+     * If the flash is not a power of 2, then the code for handling multiple
+     * mappings will not work correctly.
+     */
+    if (!is_power_of_2(pfl->total_len)) {
+        error_setg(errp, "total pflash length (%" PRIx64 ") not a power of 2.",
+                   pfl->total_len);
+        return;
+    }
+
+    int num_devices = pfl->bank_width / pfl->device_width;
+    uint64_t sector_len_per_device = pfl->sector_len / num_devices;
+    uint64_t device_len = sector_len_per_device * pfl->nb_blocs;
+
+    if (sector_len_per_device & 0xff || sector_len_per_device >= (1 << 24)) {
+        error_setg(errp,
+                   "unsupported configuration: sector length per device = "
+                   "%" PRIx64 ".",
+                   sector_len_per_device);
+        return;
+    }
+
+    memory_region_init_rom_device(&pfl->orig_mem, OBJECT(pfl),
+                                  &pflash_cfi02_ops, pfl, pfl->name,
+                                  pfl->total_len, &local_err);
     /* Only 11 bits are used in the comparison. */
     pfl->unlock_addr0 &= 0x7FF;
     pfl->unlock_addr1 &= 0x7FF;
 
     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);
     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;
@@ -566,6 +687,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';
@@ -591,8 +713,8 @@ 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 */
-    pfl->cfi_table[0x1F] = 0x07;
+    /* Timeout per single byte/word write (16 us) */
+    pfl->cfi_table[0x1F] = 0x04;
     /* Timeout for min size buffer write (NA) */
     pfl->cfi_table[0x20] = 0x00;
     /* Typical timeout for block erase (512 ms) */
@@ -608,13 +730,13 @@ 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);
-    /* Flash device interface (8 & 16 bits) */
-    pfl->cfi_table[0x28] = 0x02;
-    pfl->cfi_table[0x29] = 0x00;
+    pfl->cfi_table[0x27] = ctz32(device_len);
+    /* Flash device interface  */
+    pfl->cfi_table[0x28] = device_interface_code;
+    pfl->cfi_table[0x29] = device_interface_code >> 8;
     /* Max number of bytes in multi-bytes write */
     /* XXX: disable buffered write as it's not supported */
-    //    pfl->cfi_table[0x2A] = 0x05;
+    /*    pfl->cfi_table[0x2A] = 0x05; */
     pfl->cfi_table[0x2A] = 0x00;
     pfl->cfi_table[0x2B] = 0x00;
     /* Number of erase block regions (uniform) */
@@ -622,8 +744,8 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
     /* 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;
+    pfl->cfi_table[0x2F] = sector_len_per_device >> 8;
+    pfl->cfi_table[0x30] = sector_len_per_device >> 16;
 
     /* Extended */
     pfl->cfi_table[0x31] = 'P';
@@ -648,7 +770,9 @@ 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_UINT8("width", PFlashCFI02, width, 0),
+    DEFINE_PROP_UINT8("width", PFlashCFI02, bank_width, 0),
+    DEFINE_PROP_UINT8("device-width", PFlashCFI02, device_width, 0),
+    DEFINE_PROP_UINT8("max-device-width", PFlashCFI02, max_device_width, 0),
     DEFINE_PROP_UINT8("mappings", PFlashCFI02, mappings, 0),
     DEFINE_PROP_UINT8("big-endian", PFlashCFI02, be, 0),
     DEFINE_PROP_UINT16("id0", PFlashCFI02, ident0, 0),
@@ -696,7 +820,7 @@ PFlashCFI02 *pflash_cfi02_register(hwaddr base,
                                    hwaddr size,
                                    BlockBackend *blk,
                                    uint32_t sector_len,
-                                   int nb_mappings, int width,
+                                   int nb_mappings, int bank_width,
                                    uint16_t id0, uint16_t id1,
                                    uint16_t id2, uint16_t id3,
                                    uint16_t unlock_addr0,
@@ -711,7 +835,7 @@ PFlashCFI02 *pflash_cfi02_register(hwaddr base,
     assert(size % sector_len == 0);
     qdev_prop_set_uint32(dev, "num-blocks", size / sector_len);
     qdev_prop_set_uint32(dev, "sector-length", sector_len);
-    qdev_prop_set_uint8(dev, "width", width);
+    qdev_prop_set_uint8(dev, "width", bank_width);
     qdev_prop_set_uint8(dev, "mappings", nb_mappings);
     qdev_prop_set_uint8(dev, "big-endian", !!be);
     qdev_prop_set_uint16(dev, "id0", id0);
diff --git a/tests/pflash-cfi02-test.c b/tests/pflash-cfi02-test.c
index ea5f8b2648..a1be26da73 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,381 @@
 #define UNLOCK_BYPASS_CMD 0x20
 #define UNLOCK_BYPASS_RESET_CMD 0x00
 
+typedef struct {
+    int bank_width;
+    int device_width;
+    int max_device_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.device_width == 0) {
+        ret.device_width = ret.bank_width;
+    }
+    if (ret.max_device_width == 0) {
+        ret.max_device_width = ret.device_width;
+    }
+    return ret;
+}
+
+/*
+ * 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)
+{
+    if (c->device_width == 8) {
+        return (uint64_t)-1;
+    }
+    return (1ULL << (c->device_width * 8)) - 1ULL;
+}
+
+/*
+ * Return a bit mask exactly as long as the bank_width.
+ */
+static inline uint64_t bank_mask(const FlashConfig *c)
+{
+    if (c->bank_width == 8) {
+        return (uint64_t)-1;
+    }
+    return (1ULL << (c->bank_width * 8)) - 1ULL;
+}
+
+static inline void flash_write(const FlashConfig *c, uint64_t byte_addr,
+                               uint64_t data)
+{
+    /* 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 inline uint64_t flash_read(const FlashConfig *c, uint64_t byte_addr)
+{
+    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.)
+     *
+     * Interleaving flash chips use the least significant bits of a byte
+     * address to refer to data from the individual chips. Two interleaved x8
+     * devices would use command addresses 0xAAA and 0x554. Two interleaved
+     * x16 devices would use 0x1554 and 0xAA8.
+     *
+     * More exotic configurations are possible. Two interleaved x8/x16 devices
+     * in x8 mode would also use 0x1554 and 0xAA8.
+     *
+     * In general we need to multiply an address by the number of devices,
+     * which is bank_width / device_width, and multiply that by the maximum
+     * device width.
+     */
+    int num_devices = c->bank_width / c->device_width;
+    return flash_addr.addr * (num_devices * c->max_device_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);
+    for (int i = c->device_width; i < c->bank_width; i += c->device_width) {
+        data |= data << (c->device_width * 8);
+    }
+    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 uint16_t flash_read(uint64_t byte_addr)
+static inline uint64_t flash_query_1(const FlashConfig *c, faddr query_addr)
 {
-    return qtest_readw(global_qtest, BASE_ADDR + byte_addr);
+    return flash_query(c, query_addr) & device_mask(c);
 }
 
-static void unlock(void)
+static void unlock(const FlashConfig *c)
 {
-    flash_write(UNLOCK0_ADDR, UNLOCK0_CMD);
-    flash_write(UNLOCK1_ADDR, UNLOCK1_CMD);
+    flash_cmd(c, UNLOCK0_ADDR, UNLOCK0_CMD);
+    flash_cmd(c, UNLOCK1_ADDR, UNLOCK1_CMD);
 }
 
-static void reset(void)
+static void reset(const FlashConfig *c)
 {
-    flash_write(0, RESET_CMD);
+    flash_cmd(c, FLASH_ADDR(0), RESET_CMD);
 }
 
-static void sector_erase(uint64_t byte_addr)
+static void sector_erase(const FlashConfig *c, uint64_t byte_addr)
 {
-    unlock();
-    flash_write(UNLOCK0_ADDR, 0x80);
-    unlock();
-    flash_write(byte_addr, SECTOR_ERASE_CMD);
+    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(uint64_t byte_addr)
+static void wait_for_completion(const FlashConfig *c, 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)) {
+    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. */
-        clock_step_next();
+        qtest_clock_step_next(c->qtest);
         /* Ensure that DQ6 has stopped toggling. */
-        g_assert_cmpint(flash_read(byte_addr), ==, flash_read(byte_addr));
+        g_assert_cmpint(flash_read(c, byte_addr), ==, flash_read(c, byte_addr));
     }
 }
 
-static void bypass_program(uint64_t byte_addr, uint16_t data)
+static void bypass_program(const FlashConfig *c, uint64_t byte_addr,
+                           uint16_t data)
 {
-    flash_write(UNLOCK0_ADDR, PROGRAM_CMD);
-    flash_write(byte_addr, 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)
+/*
+ * Check that the device interface code dic is appropriate for the given
+ * width.
+ *
+ * Device interface codes are specified in JEP173.
+ */
+static bool device_supports_width(uint16_t dic, int width)
 {
-    global_qtest = qtest_initf("-M musicpal,accel=qtest "
-                               "-drive if=pflash,file=%s,format=raw,copy-on-read",
-                               image_path);
+    switch (width) {
+    case 1:
+        /*
+         * x8-only, x8/x16, or x32
+         * XXX: Currently we use dic = 3 for an x8/x32 device even though
+         * that's only for x32. If there's a more appropriate value, both this
+         * test and pflash-cfi02.c should be modified.
+         */
+        return dic == 0 || dic == 2 || dic == 3;
+    case 2:
+        /* x16-only, x8/x16, or x16/x32. */
+        return dic == 1 || dic == 2 || dic == 4;
+    case 4:
+        /* x32-only or x16/x32. */
+        return dic == 3 || dic == 4;
+    }
+    g_test_incomplete("Device width test not supported");
+    return true;
+}
+
+static void test_flash(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"
+                        " -global driver=cfi.pflash02,"
+                        "property=device-width,value=%d"
+                        " -global driver=cfi.pflash02,"
+                        "property=max-device-width,value=%d",
+                        image_path,
+                        config->device_width,
+                        config->max_device_width);
+    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_cmpint(flash_read(FLASH_WIDTH * 0x0000), ==, 0x00BF);
-    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x0001), ==, 0x236D);
-    reset();
+    unlock(c);
+    flash_cmd(c, UNLOCK0_ADDR, AUTOSELECT_CMD);
+    g_assert_cmpint(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF));
+    if (c->device_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_cmpint(flash_query(c, FLASH_ADDR(1)), ==,
+                        replicate(c, 0x236D));
+    }
+    reset(c);
 
     /* Check the erase blocks. */
-    flash_write(CFI_ADDR, CFI_CMD);
-    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x10), ==, 'Q');
-    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x11), ==, 'R');
-    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x12), ==, 'Y');
+    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'));
+
     /* Num erase regions. */
-    g_assert_cmpint(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();
+    g_assert_cmpint(flash_query_1(c, FLASH_ADDR(0x2C)), >=, 1);
 
+    /* Check device length. */
+    uint32_t device_len = 1 << flash_query_1(c, FLASH_ADDR(0x27));
+    g_assert_cmpint(device_len * (c->bank_width / c->device_width), ==,
+                    FLASH_SIZE);
+
+    /* Check nb_sectors * sector_len is device_len. */
+    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);
+    g_assert_cmpint(nb_sectors * sector_len, ==, device_len);
+
+    /* Check the device interface code supports the width and max width. */
+    uint16_t device_interface_code = flash_query_1(c, FLASH_ADDR(0x28)) +
+                                     (flash_query_1(c, FLASH_ADDR(0x29)) << 8);
+    g_assert_true(device_supports_width(device_interface_code,
+                                        c->device_width));
+    g_assert_true(device_supports_width(device_interface_code,
+                                        c->max_device_width));
+    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_cmpint(status0 & 0x80, ==, 0);
-        uint16_t status1 = flash_read(byte_addr);
+        g_assert_cmpint(status0 & dq7, ==, 0);
+        uint64_t status1 = flash_read(c, byte_addr);
         /* DQ6 toggles during an erase. */
-        g_assert_cmpint(status0 & 0x40, !=, status1 & 0x40);
+        g_assert_cmpint(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_cmpint(flash_read(byte_addr), ==, flash_read(byte_addr));
+        g_assert_cmpint(flash_read(c, byte_addr), ==, flash_read(c, byte_addr));
         /* Now the data should be valid. */
-        g_assert_cmpint(flash_read(byte_addr), ==, 0xFFFF);
+        g_assert_cmpint(flash_read(c, byte_addr), ==, bank_mask(c));
 
         /* Program a bit pattern. */
-        program(byte_addr, 0x5555);
-        g_assert_cmpint(flash_read(byte_addr), ==, 0x5555);
-        program(byte_addr, 0xAA55);
-        g_assert_cmpint(flash_read(byte_addr), ==, 0x0055);
+        program(c, byte_addr, 0x55);
+        g_assert_cmpint(flash_read(c, byte_addr) & 0xFF, ==, 0x55);
+        program(c, byte_addr, 0xA5);
+        g_assert_cmpint(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_cmpint(status0 & 0x80, ==, 0);
-    uint16_t status1 = flash_read(0);
+    g_assert_cmpint(status0 & dq7, ==, 0);
+    uint64_t status1 = flash_read(c, 0);
     /* DQ6 toggles during an erase. */
-    g_assert_cmpint(status0 & 0x40, !=, status1 & 0x40);
+    g_assert_cmpint(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_cmpint(flash_read(0), ==, flash_read(0));
+    g_assert_cmpint(flash_read(c, 0), ==, flash_read(c, 0));
     /* Now the data should be valid. */
-    g_assert_cmpint(flash_read(0), ==, 0xFFFF);
+
+    for (uint32_t i = 0; i < nb_sectors; ++i) {
+        uint64_t byte_addr = i * sector_len;
+        g_assert_cmpint(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_cmpint(flash_read(0), ==, 0x0123);
-    g_assert_cmpint(flash_read(2), ==, 0x4567);
-    g_assert_cmpint(flash_read(4), ==, 0x89AB);
-    g_assert_cmpint(flash_read(6), ==, 0xCDEF);
-    g_assert_cmpint(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_cmpint(flash_read(c, 0 * c->bank_width), ==, 0x01);
+    g_assert_cmpint(flash_read(c, 1 * c->bank_width), ==, 0x23);
+    g_assert_cmpint(flash_read(c, 2 * c->bank_width), ==, 0x45);
+    g_assert_cmpint(flash_read(c, 3 * c->bank_width), ==, 0x67);
+    g_assert_cmpint(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_cmpint(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 +423,61 @@ 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,
+        .device_width = 2,
+        .max_device_width = 2,
+    },
+    /* Implicitly one x16 device. */
+    {
+        .bank_width = 2,
+        .device_width = 0,
+        .max_device_width = 0,
+    },
+    /* Implicitly one x16 device. */
+    {
+        .bank_width = 2,
+        .device_width = 2,
+        .max_device_width = 0,
+    },
+    /* Interleave two x8 devices. */
+    {
+        .bank_width = 2,
+        .device_width = 1,
+        .max_device_width = 1,
+    },
+    /* Interleave two implicit x8 devices. */
+    {
+        .bank_width = 2,
+        .device_width = 1,
+        .max_device_width = 0,
+    },
+    /* Interleave two x8/x16 devices in x8 mode. */
+    {
+        .bank_width = 2,
+        .device_width = 1,
+        .max_device_width = 2,
+    },
+    /* One x16/x32 device in x16 mode. */
+    {
+        .bank_width = 2,
+        .device_width = 2,
+        .max_device_width = 4,
+    },
+    /* Two x8/x32 devices in x8 mode; I am not sure if such devices exist. */
+    {
+        .bank_width = 2,
+        .device_width = 1,
+        .max_device_width = 4,
+    },
+};
+
 int main(int argc, char **argv)
 {
     int fd = mkstemp(image_path);
@@ -214,7 +486,7 @@ 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);
@@ -226,7 +498,17 @@ int main(int argc, char **argv)
 
     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-%d-%d",
+                                     config->bank_width,
+                                     config->device_width,
+                                     config->max_device_width);
+        qtest_add_data_func(path, config, test_flash);
+        g_free(path);
+    }
     int result = g_test_run();
     cleanup(NULL);
     return result;
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 04/10] block/pflash_cfi02: Implement intereleaved flash devices
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 04/10] block/pflash_cfi02: Implement intereleaved flash devices Stephen Checkoway
@ 2019-04-26 16:26   ` Stephen Checkoway
  2019-06-22 12:25   ` Philippe Mathieu-Daudé
  1 sibling, 0 replies; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

It's common for multiple narrow flash chips to be hooked up in parallel
to support wider buses. For example, four 8-bit wide flash chips (x8)
may be combined in parallel to produce a 32-bit wide device. Similarly,
two 16-bit wide chips (x16) may be combined.

This commit introduces `device-width` and `max-device-width` properties,
similar to pflash_cfi01, with the following meanings:
- `width`: The width of the logical, qemu device (same as before);
- `device-width`: The width of an individual flash chip, defaulting to
  `width`; and
- `max-device-width`: The maximum width of an individual flash chip,
  defaulting to `device-width`.

Nothing needs to change to support reading such interleaved devices but
commands (e.g., erase and programming) must be sent to all devices at
the same time or else the various chips will be in different states.

For example, a 4-byte wide logical device can be composed of four x8/x16
devices in x8 mode. That is, each device supports both x8 or x16 and
they're being used in the byte, rather than word, mode. This
configuration would have `width=4`, `device-width=1`, and
`max-device-width=2`.

In addition to commands being sent to all devices, guest firmware
expects the status and CFI queries to be replicated for each device.
(The one exception to the response replication is that each device gets
to report its own status bit DQ7 while programming because its value
depends on the value being programmed which will usually differ for each
device.)

Testing is limited to 16-bit wide devices due to the current inability
to override the properties set by `pflash_cfi02_register`, but multiple
configurations are tested.

Stop using global_qtest. Instead, package the qtest variable inside the
FlashConfig structure.

Signed-off-by: Stephen Checkoway <stephen.checkoway@oberlin.edu>
Acked-by: Thomas Huth <thuth@redhat.com>
---
 hw/block/pflash_cfi02.c   | 270 +++++++++++++++------
 tests/pflash-cfi02-test.c | 476 ++++++++++++++++++++++++++++++--------
 2 files changed, 576 insertions(+), 170 deletions(-)

diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c
index e4bff0c8f8..101628b4ec 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -28,7 +28,6 @@
  * - unlock bypass command
  * - 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
@@ -67,15 +66,19 @@ struct PFlashCFI02 {
     BlockBackend *blk;
     uint32_t sector_len;
     uint32_t nb_blocs;
-    uint32_t chip_len;
+    uint64_t total_len;
+    uint64_t interleave_multiplier;
     uint8_t mappings;
-    uint8_t width;
+    uint8_t bank_width; /* Width of the QEMU device in bytes. */
+    uint8_t device_width; /* Width of individual pflash chip. */
+    uint8_t max_device_width; /* Maximum width of individual pflash chip. */
     uint8_t be;
+    int device_shift; /* Amount to shift an offset to get a device address. */
     int wcycle; /* if 0, the flash is read normally */
     int bypass;
     int ro;
     uint8_t cmd;
-    uint8_t status;
+    uint64_t status;
     /* FIXME: implement array device properties */
     uint16_t ident0;
     uint16_t ident1;
@@ -103,16 +106,17 @@ struct PFlashCFI02 {
  */
 static inline void toggle_dq7(PFlashCFI02 *pfl)
 {
-    pfl->status ^= 0x80;
+    pfl->status ^= pfl->interleave_multiplier * 0x80;
 }
 
 /*
  * Set status bit DQ7 to bit 7 of value.
  */
-static inline void set_dq7(PFlashCFI02 *pfl, uint8_t value)
+static inline void set_dq7(PFlashCFI02 *pfl, uint64_t value)
 {
-    pfl->status &= 0x7F;
-    pfl->status |= value & 0x80;
+    uint64_t mask = pfl->interleave_multiplier * 0x80;
+    pfl->status &= ~mask;
+    pfl->status |= value & mask;
 }
 
 /*
@@ -120,7 +124,7 @@ static inline void set_dq7(PFlashCFI02 *pfl, uint8_t value)
  */
 static inline void toggle_dq6(PFlashCFI02 *pfl)
 {
-    pfl->status ^= 0x40;
+    pfl->status ^= pfl->interleave_multiplier * 0x40;
 }
 
 /*
@@ -188,7 +192,6 @@ static uint64_t pflash_data_read(PFlashCFI02 *pfl, hwaddr offset,
 static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width)
 {
     PFlashCFI02 *pfl = opaque;
-    hwaddr boff;
     uint64_t ret;
 
     ret = -1;
@@ -198,12 +201,10 @@ static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width)
         ++pfl->read_counter > PFLASH_LAZY_ROMD_THRESHOLD) {
         pflash_register_memory(pfl, 1);
     }
-    offset &= pfl->chip_len - 1;
-    boff = offset & 0xFF;
-    if (pfl->width == 2)
-        boff = boff >> 1;
-    else if (pfl->width == 4)
-        boff = boff >> 2;
+    /* Mask by the total length of the chip to account for alias mappings. */
+    offset &= pfl->total_len - 1;
+    hwaddr device_addr = offset >> pfl->device_shift;
+
     switch (pfl->cmd) {
     default:
         /* This should never happen : reset state & treat it as a read*/
@@ -215,29 +216,32 @@ static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width)
         /* We accept reads during second unlock sequence... */
     case 0x00:
         /* Flash area read */
-        ret = pflash_data_read(pfl, offset, width);
-        break;
+        return pflash_data_read(pfl, offset, width);
     case 0x90:
         /* flash ID read */
-        switch (boff) {
+        switch (device_addr & 0xFF) {
         case 0x00:
+            ret = pfl->ident0;
+            break;
         case 0x01:
-            ret = boff & 0x01 ? pfl->ident1 : pfl->ident0;
+            ret = pfl->ident1;
             break;
         case 0x02:
             ret = 0x00; /* Pretend all sectors are unprotected */
             break;
         case 0x0E:
         case 0x0F:
-            ret = boff & 0x01 ? pfl->ident3 : pfl->ident2;
+            ret = device_addr & 0x01 ? pfl->ident3 : pfl->ident2;
             if (ret != (uint8_t)-1) {
                 break;
             }
             /* Fall through to data read. */
         default:
-            ret = pflash_data_read(pfl, offset, width);
+            return pflash_data_read(pfl, offset, width);
         }
-        DPRINTF("%s: ID " TARGET_FMT_plx " %" PRIx64 "\n", __func__, boff, ret);
+        ret *= pfl->interleave_multiplier;
+        DPRINTF("%s: ID " TARGET_FMT_plx " %" PRIx64 "\n",
+                __func__, device_addr & 0xFF, ret);
         break;
     case 0xA0:
     case 0x10:
@@ -250,8 +254,8 @@ static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width)
         break;
     case 0x98:
         /* CFI query mode */
-        if (boff < sizeof(pfl->cfi_table)) {
-            ret = pfl->cfi_table[boff];
+        if (device_addr < sizeof(pfl->cfi_table)) {
+            ret = pfl->interleave_multiplier * pfl->cfi_table[device_addr];
         } else {
             ret = 0;
         }
@@ -279,30 +283,36 @@ 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;
 
     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;
+    if (pfl->cmd != 0xA0) {
+        if (value != pfl->interleave_multiplier * cmd) {
+            DPRINTF("%s: cmd 0x%02x not sent to all devices: expected="
+                    "0x%0*" PRIx64 " actual=0x%0*" PRIx64 "\n",
+                    __func__, cmd,
+                    pfl->bank_width * 2, pfl->interleave_multiplier * cmd,
+                    pfl->bank_width * 2, value);
+        }
+
+        if (cmd == 0xF0) {
+            goto reset_flash;
+        }
     }
+
     trace_pflash_write(offset, value, width, pfl->wcycle);
-    offset &= pfl->chip_len - 1;
-
-    DPRINTF("%s: offset " TARGET_FMT_plx " %08" PRIx64 " %d\n", __func__,
-            offset, value, width);
-    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;
+
+    /* Mask by the total length of the chip to account for alias mappings. */
+    offset &= pfl->total_len - 1;
+
+    DPRINTF("%s: offset " TARGET_FMT_plx " 0x%0*" PRIx64 "\n",
+            __func__, offset, width * 2, value);
+
+    hwaddr device_addr = (offset >> pfl->device_shift);
+    /* Address bits A11 and greater are don't cares for most commands. */
+    unsigned int masked_addr = device_addr & 0x7FF;
+
     switch (pfl->wcycle) {
     case 0:
         /* Set the device in I/O access mode if required */
@@ -311,16 +321,16 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
         pfl->read_counter = 0;
         /* We're in read mode */
     check_unlock0:
-        if (boff == 0x55 && cmd == 0x98) {
+        if (masked_addr == 0x55 && cmd == 0x98) {
         enter_CFI_mode:
             /* Enter CFI query mode */
             pfl->wcycle = WCYCLE_CFI;
             pfl->cmd = 0x98;
             return;
         }
-        if (boff != pfl->unlock_addr0 || cmd != 0xAA) {
-            DPRINTF("%s: unlock0 failed " TARGET_FMT_plx " %02x %04x\n",
-                    __func__, boff, cmd, pfl->unlock_addr0);
+        if (masked_addr != pfl->unlock_addr0 || cmd != 0xAA) {
+            DPRINTF("%s: unlock0 failed %04x %02x %04x\n",
+                    __func__, masked_addr, cmd, pfl->unlock_addr0);
             goto reset_flash;
         }
         DPRINTF("%s: unlock sequence started\n", __func__);
@@ -328,18 +338,18 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
     case 1:
         /* We started an unlock sequence */
     check_unlock1:
-        if (boff != pfl->unlock_addr1 || cmd != 0x55) {
-            DPRINTF("%s: unlock1 failed " TARGET_FMT_plx " %02x\n", __func__,
-                    boff, cmd);
+        if (masked_addr != pfl->unlock_addr1 || cmd != 0x55) {
+            DPRINTF("%s: unlock1 failed %03x %02x\n", __func__,
+                    masked_addr, cmd);
             goto reset_flash;
         }
         DPRINTF("%s: unlock sequence done\n", __func__);
         break;
     case 2:
         /* We finished an unlock sequence */
-        if (!pfl->bypass && boff != pfl->unlock_addr0) {
-            DPRINTF("%s: command failed " TARGET_FMT_plx " %02x\n", __func__,
-                    boff, cmd);
+        if (!pfl->bypass && masked_addr != pfl->unlock_addr0) {
+            DPRINTF("%s: command failed %03x %02x\n", __func__,
+                    masked_addr, cmd);
             goto reset_flash;
         }
         switch (cmd) {
@@ -390,8 +400,9 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
                 goto reset_flash;
             }
             /* We can enter CFI query mode from autoselect mode */
-            if (boff == 0x55 && cmd == 0x98)
+            if (masked_addr == 0x55 && cmd == 0x98) {
                 goto enter_CFI_mode;
+            }
             /* No break here */
         default:
             DPRINTF("%s: invalid write for command %02x\n",
@@ -416,7 +427,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
     case 5:
         switch (cmd) {
         case 0x10:
-            if (boff != pfl->unlock_addr0) {
+            if (masked_addr != pfl->unlock_addr0) {
                 DPRINTF("%s: chip erase: invalid address " TARGET_FMT_plx "\n",
                         __func__, offset);
                 goto reset_flash;
@@ -424,8 +435,8 @@ 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);
-                pflash_update(pfl, 0, pfl->chip_len);
+                memset(pfl->storage, 0xFF, pfl->total_len);
+                pflash_update(pfl, 0, pfl->total_len);
             }
             set_dq7(pfl, 0x00);
             /* Let's wait 5 seconds before chip erase is done */
@@ -521,22 +532,132 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
         return;
     }
 
+    if (pfl->bank_width == 0) {
+        error_setg(errp, "attribute \"width\" not specified or zero.");
+        return;
+    }
+
+    /*
+     * device-width defaults to width and max-device-width defaults to
+     * device-width. Check that the device-width and max-device-width
+     * configurations are supported.
+     */
+    if (pfl->device_width == 0) {
+        pfl->device_width = pfl->bank_width;
+    }
+    if (pfl->max_device_width == 0) {
+        pfl->max_device_width = pfl->device_width;
+    }
+    if (pfl->bank_width % pfl->device_width != 0) {
+        error_setg(errp,
+                   "attribute \"width\" (%u) not a multiple of attribute "
+                   "\"device-width\" (%u).",
+                   pfl->bank_width, pfl->device_width);
+        return;
+    }
+
+    /*
+     * Writing commands to the flash device and reading CFI responses or
+     * status values requires transforming a QEMU device offset into a
+     * flash device address given in terms of the device's maximum width. We
+     * can do this by shifting a QEMU device offset right a constant number of
+     * bits depending on the bank_width, device_width, and max_device_width.
+     *
+     * num_devices = bank_width / device_width is the number of interleaved
+     * flash devices. To compute a device byte address, we need to divide
+     * offset by num_devices (equivalently shift right by log2(num_devices)).
+     * To turn a device byte address into a device word address, we need to
+     * divide by max_device_width (equivalently shift right by
+     * log2(max_device_width)).
+     *
+     * In tabular form.
+     * ==================================================================
+     * bank_width   device_width    max_device_width    num_devices shift
+     * ------------------------------------------------------------------
+     * 1            1               1                   1           0
+     * 1            1               2                   1           1
+     * 2            1               1                   2           1
+     * 2            1               2                   2           2
+     * 2            2               2                   1           1
+     * 4            1               1                   4           2
+     * 4            1               2                   4           3
+     * 4            1               4                   4           4
+     * 4            2               2                   2           2
+     * 4            2               4                   2           3
+     * 4            4               4                   1           2
+     * ==================================================================
+     */
+    pfl->device_shift = ctz32(pfl->bank_width) - ctz32(pfl->device_width) +
+                        ctz32(pfl->max_device_width);
+    pfl->interleave_multiplier = 0;
+    for (unsigned int shift = 0; shift < pfl->bank_width;
+         shift += pfl->device_width) {
+        pfl->interleave_multiplier |= 1 << (shift * 8);
+    }
+
+    uint16_t device_interface_code;
+    if (pfl->max_device_width == 1 && pfl->device_width == 1) {
+        device_interface_code = 0; /* x8 only. */
+    } else if (pfl->max_device_width == 2 &&
+               (pfl->device_width == 1 || pfl->device_width == 2)) {
+        /* XXX: Some devices only support x16, this code doesn't model them. */
+        device_interface_code = 2; /* Supports x8 or x16. */
+    } else if (pfl->max_device_width == 4 && pfl->device_width == 1) {
+        /*
+         * XXX: this is x32-only. The standards I've seen don't specify a value
+         * for x8/x32 but do mention them.
+         */
+        device_interface_code = 3; /* x32 only. */
+    } else if (pfl->max_device_width == 4 &&
+               (pfl->device_width == 2 || pfl->device_width == 4)) {
+        device_interface_code = 4; /* Supports x16 or x32. */
+    } else {
+        error_setg(errp,
+                   "unsupported configuration: \"device-width\"=%u "
+                   "\"max-device-width\"=%u.",
+                   pfl->device_width, pfl->max_device_width);
+        return;
+    }
+
+    pfl->total_len = pfl->sector_len * pfl->nb_blocs;
+
+    /*
+     * If the flash is not a power of 2, then the code for handling multiple
+     * mappings will not work correctly.
+     */
+    if (!is_power_of_2(pfl->total_len)) {
+        error_setg(errp, "total pflash length (%" PRIx64 ") not a power of 2.",
+                   pfl->total_len);
+        return;
+    }
+
+    int num_devices = pfl->bank_width / pfl->device_width;
+    uint64_t sector_len_per_device = pfl->sector_len / num_devices;
+    uint64_t device_len = sector_len_per_device * pfl->nb_blocs;
+
+    if (sector_len_per_device & 0xff || sector_len_per_device >= (1 << 24)) {
+        error_setg(errp,
+                   "unsupported configuration: sector length per device = "
+                   "%" PRIx64 ".",
+                   sector_len_per_device);
+        return;
+    }
+
+    memory_region_init_rom_device(&pfl->orig_mem, OBJECT(pfl),
+                                  &pflash_cfi02_ops, pfl, pfl->name,
+                                  pfl->total_len, &local_err);
     /* Only 11 bits are used in the comparison. */
     pfl->unlock_addr0 &= 0x7FF;
     pfl->unlock_addr1 &= 0x7FF;
 
     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);
     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;
@@ -566,6 +687,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';
@@ -591,8 +713,8 @@ 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 */
-    pfl->cfi_table[0x1F] = 0x07;
+    /* Timeout per single byte/word write (16 us) */
+    pfl->cfi_table[0x1F] = 0x04;
     /* Timeout for min size buffer write (NA) */
     pfl->cfi_table[0x20] = 0x00;
     /* Typical timeout for block erase (512 ms) */
@@ -608,13 +730,13 @@ 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);
-    /* Flash device interface (8 & 16 bits) */
-    pfl->cfi_table[0x28] = 0x02;
-    pfl->cfi_table[0x29] = 0x00;
+    pfl->cfi_table[0x27] = ctz32(device_len);
+    /* Flash device interface  */
+    pfl->cfi_table[0x28] = device_interface_code;
+    pfl->cfi_table[0x29] = device_interface_code >> 8;
     /* Max number of bytes in multi-bytes write */
     /* XXX: disable buffered write as it's not supported */
-    //    pfl->cfi_table[0x2A] = 0x05;
+    /*    pfl->cfi_table[0x2A] = 0x05; */
     pfl->cfi_table[0x2A] = 0x00;
     pfl->cfi_table[0x2B] = 0x00;
     /* Number of erase block regions (uniform) */
@@ -622,8 +744,8 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
     /* 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;
+    pfl->cfi_table[0x2F] = sector_len_per_device >> 8;
+    pfl->cfi_table[0x30] = sector_len_per_device >> 16;
 
     /* Extended */
     pfl->cfi_table[0x31] = 'P';
@@ -648,7 +770,9 @@ 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_UINT8("width", PFlashCFI02, width, 0),
+    DEFINE_PROP_UINT8("width", PFlashCFI02, bank_width, 0),
+    DEFINE_PROP_UINT8("device-width", PFlashCFI02, device_width, 0),
+    DEFINE_PROP_UINT8("max-device-width", PFlashCFI02, max_device_width, 0),
     DEFINE_PROP_UINT8("mappings", PFlashCFI02, mappings, 0),
     DEFINE_PROP_UINT8("big-endian", PFlashCFI02, be, 0),
     DEFINE_PROP_UINT16("id0", PFlashCFI02, ident0, 0),
@@ -696,7 +820,7 @@ PFlashCFI02 *pflash_cfi02_register(hwaddr base,
                                    hwaddr size,
                                    BlockBackend *blk,
                                    uint32_t sector_len,
-                                   int nb_mappings, int width,
+                                   int nb_mappings, int bank_width,
                                    uint16_t id0, uint16_t id1,
                                    uint16_t id2, uint16_t id3,
                                    uint16_t unlock_addr0,
@@ -711,7 +835,7 @@ PFlashCFI02 *pflash_cfi02_register(hwaddr base,
     assert(size % sector_len == 0);
     qdev_prop_set_uint32(dev, "num-blocks", size / sector_len);
     qdev_prop_set_uint32(dev, "sector-length", sector_len);
-    qdev_prop_set_uint8(dev, "width", width);
+    qdev_prop_set_uint8(dev, "width", bank_width);
     qdev_prop_set_uint8(dev, "mappings", nb_mappings);
     qdev_prop_set_uint8(dev, "big-endian", !!be);
     qdev_prop_set_uint16(dev, "id0", id0);
diff --git a/tests/pflash-cfi02-test.c b/tests/pflash-cfi02-test.c
index ea5f8b2648..a1be26da73 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,381 @@
 #define UNLOCK_BYPASS_CMD 0x20
 #define UNLOCK_BYPASS_RESET_CMD 0x00
 
+typedef struct {
+    int bank_width;
+    int device_width;
+    int max_device_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.device_width == 0) {
+        ret.device_width = ret.bank_width;
+    }
+    if (ret.max_device_width == 0) {
+        ret.max_device_width = ret.device_width;
+    }
+    return ret;
+}
+
+/*
+ * 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)
+{
+    if (c->device_width == 8) {
+        return (uint64_t)-1;
+    }
+    return (1ULL << (c->device_width * 8)) - 1ULL;
+}
+
+/*
+ * Return a bit mask exactly as long as the bank_width.
+ */
+static inline uint64_t bank_mask(const FlashConfig *c)
+{
+    if (c->bank_width == 8) {
+        return (uint64_t)-1;
+    }
+    return (1ULL << (c->bank_width * 8)) - 1ULL;
+}
+
+static inline void flash_write(const FlashConfig *c, uint64_t byte_addr,
+                               uint64_t data)
+{
+    /* 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 inline uint64_t flash_read(const FlashConfig *c, uint64_t byte_addr)
+{
+    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.)
+     *
+     * Interleaving flash chips use the least significant bits of a byte
+     * address to refer to data from the individual chips. Two interleaved x8
+     * devices would use command addresses 0xAAA and 0x554. Two interleaved
+     * x16 devices would use 0x1554 and 0xAA8.
+     *
+     * More exotic configurations are possible. Two interleaved x8/x16 devices
+     * in x8 mode would also use 0x1554 and 0xAA8.
+     *
+     * In general we need to multiply an address by the number of devices,
+     * which is bank_width / device_width, and multiply that by the maximum
+     * device width.
+     */
+    int num_devices = c->bank_width / c->device_width;
+    return flash_addr.addr * (num_devices * c->max_device_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);
+    for (int i = c->device_width; i < c->bank_width; i += c->device_width) {
+        data |= data << (c->device_width * 8);
+    }
+    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 uint16_t flash_read(uint64_t byte_addr)
+static inline uint64_t flash_query_1(const FlashConfig *c, faddr query_addr)
 {
-    return qtest_readw(global_qtest, BASE_ADDR + byte_addr);
+    return flash_query(c, query_addr) & device_mask(c);
 }
 
-static void unlock(void)
+static void unlock(const FlashConfig *c)
 {
-    flash_write(UNLOCK0_ADDR, UNLOCK0_CMD);
-    flash_write(UNLOCK1_ADDR, UNLOCK1_CMD);
+    flash_cmd(c, UNLOCK0_ADDR, UNLOCK0_CMD);
+    flash_cmd(c, UNLOCK1_ADDR, UNLOCK1_CMD);
 }
 
-static void reset(void)
+static void reset(const FlashConfig *c)
 {
-    flash_write(0, RESET_CMD);
+    flash_cmd(c, FLASH_ADDR(0), RESET_CMD);
 }
 
-static void sector_erase(uint64_t byte_addr)
+static void sector_erase(const FlashConfig *c, uint64_t byte_addr)
 {
-    unlock();
-    flash_write(UNLOCK0_ADDR, 0x80);
-    unlock();
-    flash_write(byte_addr, SECTOR_ERASE_CMD);
+    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(uint64_t byte_addr)
+static void wait_for_completion(const FlashConfig *c, 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)) {
+    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. */
-        clock_step_next();
+        qtest_clock_step_next(c->qtest);
         /* Ensure that DQ6 has stopped toggling. */
-        g_assert_cmpint(flash_read(byte_addr), ==, flash_read(byte_addr));
+        g_assert_cmpint(flash_read(c, byte_addr), ==, flash_read(c, byte_addr));
     }
 }
 
-static void bypass_program(uint64_t byte_addr, uint16_t data)
+static void bypass_program(const FlashConfig *c, uint64_t byte_addr,
+                           uint16_t data)
 {
-    flash_write(UNLOCK0_ADDR, PROGRAM_CMD);
-    flash_write(byte_addr, 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)
+/*
+ * Check that the device interface code dic is appropriate for the given
+ * width.
+ *
+ * Device interface codes are specified in JEP173.
+ */
+static bool device_supports_width(uint16_t dic, int width)
 {
-    global_qtest = qtest_initf("-M musicpal,accel=qtest "
-                               "-drive if=pflash,file=%s,format=raw,copy-on-read",
-                               image_path);
+    switch (width) {
+    case 1:
+        /*
+         * x8-only, x8/x16, or x32
+         * XXX: Currently we use dic = 3 for an x8/x32 device even though
+         * that's only for x32. If there's a more appropriate value, both this
+         * test and pflash-cfi02.c should be modified.
+         */
+        return dic == 0 || dic == 2 || dic == 3;
+    case 2:
+        /* x16-only, x8/x16, or x16/x32. */
+        return dic == 1 || dic == 2 || dic == 4;
+    case 4:
+        /* x32-only or x16/x32. */
+        return dic == 3 || dic == 4;
+    }
+    g_test_incomplete("Device width test not supported");
+    return true;
+}
+
+static void test_flash(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"
+                        " -global driver=cfi.pflash02,"
+                        "property=device-width,value=%d"
+                        " -global driver=cfi.pflash02,"
+                        "property=max-device-width,value=%d",
+                        image_path,
+                        config->device_width,
+                        config->max_device_width);
+    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_cmpint(flash_read(FLASH_WIDTH * 0x0000), ==, 0x00BF);
-    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x0001), ==, 0x236D);
-    reset();
+    unlock(c);
+    flash_cmd(c, UNLOCK0_ADDR, AUTOSELECT_CMD);
+    g_assert_cmpint(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF));
+    if (c->device_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_cmpint(flash_query(c, FLASH_ADDR(1)), ==,
+                        replicate(c, 0x236D));
+    }
+    reset(c);
 
     /* Check the erase blocks. */
-    flash_write(CFI_ADDR, CFI_CMD);
-    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x10), ==, 'Q');
-    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x11), ==, 'R');
-    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x12), ==, 'Y');
+    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'));
+
     /* Num erase regions. */
-    g_assert_cmpint(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();
+    g_assert_cmpint(flash_query_1(c, FLASH_ADDR(0x2C)), >=, 1);
 
+    /* Check device length. */
+    uint32_t device_len = 1 << flash_query_1(c, FLASH_ADDR(0x27));
+    g_assert_cmpint(device_len * (c->bank_width / c->device_width), ==,
+                    FLASH_SIZE);
+
+    /* Check nb_sectors * sector_len is device_len. */
+    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);
+    g_assert_cmpint(nb_sectors * sector_len, ==, device_len);
+
+    /* Check the device interface code supports the width and max width. */
+    uint16_t device_interface_code = flash_query_1(c, FLASH_ADDR(0x28)) +
+                                     (flash_query_1(c, FLASH_ADDR(0x29)) << 8);
+    g_assert_true(device_supports_width(device_interface_code,
+                                        c->device_width));
+    g_assert_true(device_supports_width(device_interface_code,
+                                        c->max_device_width));
+    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_cmpint(status0 & 0x80, ==, 0);
-        uint16_t status1 = flash_read(byte_addr);
+        g_assert_cmpint(status0 & dq7, ==, 0);
+        uint64_t status1 = flash_read(c, byte_addr);
         /* DQ6 toggles during an erase. */
-        g_assert_cmpint(status0 & 0x40, !=, status1 & 0x40);
+        g_assert_cmpint(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_cmpint(flash_read(byte_addr), ==, flash_read(byte_addr));
+        g_assert_cmpint(flash_read(c, byte_addr), ==, flash_read(c, byte_addr));
         /* Now the data should be valid. */
-        g_assert_cmpint(flash_read(byte_addr), ==, 0xFFFF);
+        g_assert_cmpint(flash_read(c, byte_addr), ==, bank_mask(c));
 
         /* Program a bit pattern. */
-        program(byte_addr, 0x5555);
-        g_assert_cmpint(flash_read(byte_addr), ==, 0x5555);
-        program(byte_addr, 0xAA55);
-        g_assert_cmpint(flash_read(byte_addr), ==, 0x0055);
+        program(c, byte_addr, 0x55);
+        g_assert_cmpint(flash_read(c, byte_addr) & 0xFF, ==, 0x55);
+        program(c, byte_addr, 0xA5);
+        g_assert_cmpint(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_cmpint(status0 & 0x80, ==, 0);
-    uint16_t status1 = flash_read(0);
+    g_assert_cmpint(status0 & dq7, ==, 0);
+    uint64_t status1 = flash_read(c, 0);
     /* DQ6 toggles during an erase. */
-    g_assert_cmpint(status0 & 0x40, !=, status1 & 0x40);
+    g_assert_cmpint(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_cmpint(flash_read(0), ==, flash_read(0));
+    g_assert_cmpint(flash_read(c, 0), ==, flash_read(c, 0));
     /* Now the data should be valid. */
-    g_assert_cmpint(flash_read(0), ==, 0xFFFF);
+
+    for (uint32_t i = 0; i < nb_sectors; ++i) {
+        uint64_t byte_addr = i * sector_len;
+        g_assert_cmpint(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_cmpint(flash_read(0), ==, 0x0123);
-    g_assert_cmpint(flash_read(2), ==, 0x4567);
-    g_assert_cmpint(flash_read(4), ==, 0x89AB);
-    g_assert_cmpint(flash_read(6), ==, 0xCDEF);
-    g_assert_cmpint(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_cmpint(flash_read(c, 0 * c->bank_width), ==, 0x01);
+    g_assert_cmpint(flash_read(c, 1 * c->bank_width), ==, 0x23);
+    g_assert_cmpint(flash_read(c, 2 * c->bank_width), ==, 0x45);
+    g_assert_cmpint(flash_read(c, 3 * c->bank_width), ==, 0x67);
+    g_assert_cmpint(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_cmpint(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 +423,61 @@ 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,
+        .device_width = 2,
+        .max_device_width = 2,
+    },
+    /* Implicitly one x16 device. */
+    {
+        .bank_width = 2,
+        .device_width = 0,
+        .max_device_width = 0,
+    },
+    /* Implicitly one x16 device. */
+    {
+        .bank_width = 2,
+        .device_width = 2,
+        .max_device_width = 0,
+    },
+    /* Interleave two x8 devices. */
+    {
+        .bank_width = 2,
+        .device_width = 1,
+        .max_device_width = 1,
+    },
+    /* Interleave two implicit x8 devices. */
+    {
+        .bank_width = 2,
+        .device_width = 1,
+        .max_device_width = 0,
+    },
+    /* Interleave two x8/x16 devices in x8 mode. */
+    {
+        .bank_width = 2,
+        .device_width = 1,
+        .max_device_width = 2,
+    },
+    /* One x16/x32 device in x16 mode. */
+    {
+        .bank_width = 2,
+        .device_width = 2,
+        .max_device_width = 4,
+    },
+    /* Two x8/x32 devices in x8 mode; I am not sure if such devices exist. */
+    {
+        .bank_width = 2,
+        .device_width = 1,
+        .max_device_width = 4,
+    },
+};
+
 int main(int argc, char **argv)
 {
     int fd = mkstemp(image_path);
@@ -214,7 +486,7 @@ 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);
@@ -226,7 +498,17 @@ int main(int argc, char **argv)
 
     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-%d-%d",
+                                     config->bank_width,
+                                     config->device_width,
+                                     config->max_device_width);
+        qtest_add_data_func(path, config, test_flash);
+        g_free(path);
+    }
     int result = g_test_run();
     cleanup(NULL);
     return result;
-- 
2.20.1 (Apple Git-117)



^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 05/10] block/pflash_cfi02: Implement nonuniform sector sizes
  2019-04-26 16:26 [Qemu-devel] [PATCH v4 00/10] block/pflash_cfi02: Implement missing AMD pflash functionality Stephen Checkoway
                   ` (4 preceding siblings ...)
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 04/10] block/pflash_cfi02: Implement intereleaved flash devices Stephen Checkoway
@ 2019-04-26 16:26 ` Stephen Checkoway
  2019-04-26 16:26   ` Stephen Checkoway
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 06/10] block/pflash_cfi02: Fix CFI in autoselect mode Stephen Checkoway
                   ` (4 subsequent siblings)
  10 siblings, 1 reply; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

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 <stephen.checkoway@oberlin.edu>
Acked-by: Thomas Huth <thuth@redhat.com>
---
 hw/block/pflash_cfi02.c   | 177 +++++++++++++++++++++++++-----------
 tests/pflash-cfi02-test.c | 185 ++++++++++++++++++++++++++++----------
 2 files changed, 265 insertions(+), 97 deletions(-)

diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c
index 101628b4ec..c4efbe8cdf 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -28,7 +28,6 @@
  * - unlock bypass command
  * - CFI queries
  *
- * 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
@@ -55,6 +54,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 cycle for CFI queries. */
 #define WCYCLE_CFI 7
 
@@ -64,8 +70,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];
     uint64_t total_len;
     uint64_t interleave_multiplier;
     uint8_t mappings;
@@ -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
@@ -189,6 +197,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->total_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;
@@ -285,6 +312,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
     PFlashCFI02 *pfl = opaque;
     uint8_t *p;
     uint8_t cmd;
+    uint32_t sector_len;
 
     cmd = value;
     if (pfl->cmd != 0xA0) {
@@ -446,12 +474,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->bank_width * 2, offset,
+                    pfl->bank_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 */
@@ -515,15 +545,14 @@ 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;
 
-    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;
     }
@@ -619,7 +648,53 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
         return;
     }
 
-    pfl->total_len = pfl->sector_len * pfl->nb_blocs;
+    int num_devices = pfl->bank_width / pfl->device_width;
+    int nb_regions;
+    pfl->total_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] /
+                                         num_devices;
+
+        /*
+         * 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->total_len / num_devices) & (sector_len_per_device - 1)) {
+            error_setg(errp, "unsupported configuration: "
+                       "flash region %d not correctly aligned.",
+                       nb_regions);
+            return;
+        }
+
+        pfl->total_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->total_len = uniform_len;
+    } else if (uniform_len != 0 && uniform_len != pfl->total_len) {
+        error_setg(errp, "\"num-blocks\"*\"sector-length\" "
+                   "different from \"num-blocks0\"*\'sector-length0\" + ... + "
+                   "\"num-blocks3\"*\"sector-length3\"");
+        return;
+    }
 
     /*
      * If the flash is not a power of 2, then the code for handling multiple
@@ -631,18 +706,6 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
         return;
     }
 
-    int num_devices = pfl->bank_width / pfl->device_width;
-    uint64_t sector_len_per_device = pfl->sector_len / num_devices;
-    uint64_t device_len = sector_len_per_device * pfl->nb_blocs;
-
-    if (sector_len_per_device & 0xff || sector_len_per_device >= (1 << 24)) {
-        error_setg(errp,
-                   "unsupported configuration: sector length per device = "
-                   "%" PRIx64 ".",
-                   sector_len_per_device);
-        return;
-    }
-
     memory_region_init_rom_device(&pfl->orig_mem, OBJECT(pfl),
                                   &pflash_cfi02_ops, pfl, pfl->name,
                                   pfl->total_len, &local_err);
@@ -650,8 +713,6 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
     pfl->unlock_addr0 &= 0x7FF;
     pfl->unlock_addr1 &= 0x7FF;
 
-    chip_len = pfl->sector_len * pfl->nb_blocs;
-
     if (local_err) {
         error_propagate(errp, local_err);
         return;
@@ -672,8 +733,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->total_len, errp)) {
             vmstate_unregister_ram(&pfl->orig_mem, DEVICE(pfl));
             return;
         }
@@ -697,7 +758,7 @@ 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[0x15] = 0x40;
     pfl->cfi_table[0x16] = 0x00;
     /* Alternate command set (none) */
     pfl->cfi_table[0x17] = 0x00;
@@ -730,7 +791,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(device_len);
+    pfl->cfi_table[0x27] = ctz32(pfl->total_len / num_devices);
     /* Flash device interface  */
     pfl->cfi_table[0x28] = device_interface_code;
     pfl->cfi_table[0x29] = device_interface_code >> 8;
@@ -739,37 +800,49 @@ 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] = sector_len_per_device >> 8;
-    pfl->cfi_table[0x30] = sector_len_per_device >> 16;
+    /* 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] / num_devices;
+        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;
+    }
 
     /* Extended */
-    pfl->cfi_table[0x31] = 'P';
-    pfl->cfi_table[0x32] = 'R';
-    pfl->cfi_table[0x33] = 'I';
+    pfl->cfi_table[0x40] = 'P';
+    pfl->cfi_table[0x41] = 'R';
+    pfl->cfi_table[0x42] = 'I';
 
-    pfl->cfi_table[0x34] = '1';
-    pfl->cfi_table[0x35] = '0';
+    pfl->cfi_table[0x43] = '1'; /* version 1.0 */
+    pfl->cfi_table[0x44] = '0';
 
-    pfl->cfi_table[0x36] = 0x00;
-    pfl->cfi_table[0x37] = 0x00;
-    pfl->cfi_table[0x38] = 0x00;
-    pfl->cfi_table[0x39] = 0x00;
+    pfl->cfi_table[0x45] = 0x00; /* Address sensitive unlock required. */
+    pfl->cfi_table[0x46] = 0x00; /* Erase suspend not supported. */
+    pfl->cfi_table[0x47] = 0x00; /* Sector protect not supported. */
+    pfl->cfi_table[0x48] = 0x00; /* Temporary sector unprotect not supported. */
 
-    pfl->cfi_table[0x3a] = 0x00;
+    pfl->cfi_table[0x49] = 0x00; /* Sector protect/unprotect scheme. */
 
-    pfl->cfi_table[0x3b] = 0x00;
-    pfl->cfi_table[0x3c] = 0x00;
+    pfl->cfi_table[0x4a] = 0x00; /* Simultaneous operation not supported. */
+    pfl->cfi_table[0x4b] = 0x00; /* Burst mode not supported. */
+    pfl->cfi_table[0x4c] = 0x00; /* Page mode not supported. */
 }
 
 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, bank_width, 0),
     DEFINE_PROP_UINT8("device-width", PFlashCFI02, device_width, 0),
     DEFINE_PROP_UINT8("max-device-width", PFlashCFI02, max_device_width, 0),
diff --git a/tests/pflash-cfi02-test.c b/tests/pflash-cfi02-test.c
index a1be26da73..703f084c5d 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;
@@ -42,10 +44,15 @@ typedef struct {
 #define UNLOCK_BYPASS_RESET_CMD 0x00
 
 typedef struct {
+    /* Interleave configuration. */
     int bank_width;
     int device_width;
     int max_device_width;
 
+    /* Nonuniform block size. */
+    int nb_blocs[4];
+    int sector_len[4];
+
     QTestState *qtest;
 } FlashConfig;
 
@@ -61,12 +68,22 @@ static FlashConfig expand_config_defaults(const FlashConfig *c)
 {
     FlashConfig ret = *c;
 
+    if (ret.bank_width == 0) {
+        ret.bank_width = 2;
+    }
     if (ret.device_width == 0) {
         ret.device_width = ret.bank_width;
     }
     if (ret.max_device_width == 0) {
         ret.max_device_width = ret.device_width;
     }
+    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);
     return ret;
 }
 
@@ -158,8 +175,8 @@ static inline uint64_t as_byte_addr(const FlashConfig *c, faddr flash_addr)
      * which is bank_width / device_width, and multiply that by the maximum
      * device width.
      */
-    int num_devices = c->bank_width / c->device_width;
-    return flash_addr.addr * (num_devices * c->max_device_width);
+    int nb_devices = c->bank_width / c->device_width;
+    return flash_addr.addr * (nb_devices * c->max_device_width);
 }
 
 /*
@@ -277,22 +294,52 @@ static bool device_supports_width(uint16_t dic, int width)
     return true;
 }
 
-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"
+                        /* Interleave properties. */
                         " -global driver=cfi.pflash02,"
                         "property=device-width,value=%d"
                         " -global driver=cfi.pflash02,"
-                        "property=max-device-width,value=%d",
+                        "property=max-device-width,value=%d"
+                        /* 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->device_width,
-                        config->max_device_width);
+                        config->max_device_width,
+                        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;
+    int nb_devices = c->bank_width / c->device_width;
 
     /* Check the IDs. */
     unlock(c);
@@ -317,19 +364,14 @@ static void test_flash(const void *opaque)
     g_assert_cmpint(flash_query(c, FLASH_ADDR(0x12)), ==, replicate(c, 'Y'));
 
     /* Num erase regions. */
-    g_assert_cmpint(flash_query_1(c, FLASH_ADDR(0x2C)), >=, 1);
+    int nb_erase_regions = flash_query_1(c, FLASH_ADDR(0x2C));
+    g_assert_cmpint(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_cmpint(device_len * (c->bank_width / c->device_width), ==,
-                    FLASH_SIZE);
-
-    /* Check nb_sectors * sector_len is device_len. */
-    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);
-    g_assert_cmpint(nb_sectors * sector_len, ==, device_len);
+    g_assert_cmpint(device_len * nb_devices, ==, UNIFORM_FLASH_SIZE);
 
     /* Check the device interface code supports the width and max width. */
     uint16_t device_interface_code = flash_query_1(c, FLASH_ADDR(0x28)) +
@@ -339,32 +381,47 @@ static void test_flash(const void *opaque)
     g_assert_true(device_supports_width(device_interface_code,
                                         c->max_device_width));
     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_cmpint(status0 & dq7, ==, 0);
-        uint64_t status1 = flash_read(c, byte_addr);
-        /* DQ6 toggles during an erase. */
-        g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6);
-        /* Wait for erase to complete. */
-        qtest_clock_step_next(c->qtest);
-        /* Ensure DQ6 has stopped toggling. */
-        g_assert_cmpint(flash_read(c, byte_addr), ==, flash_read(c, byte_addr));
-        /* Now the data should be valid. */
-        g_assert_cmpint(flash_read(c, byte_addr), ==, bank_mask(c));
 
-        /* Program a bit pattern. */
-        program(c, byte_addr, 0x55);
-        g_assert_cmpint(flash_read(c, byte_addr) & 0xFF, ==, 0x55);
-        program(c, byte_addr, 0xA5);
-        g_assert_cmpint(flash_read(c, byte_addr) & 0xFF, ==, 0x05);
+    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);
+        sector_len *= nb_devices;
+        g_assert_cmpint(nb_sectors, ==, c->nb_blocs[region]);
+        g_assert_cmpint(sector_len, ==, c->sector_len[region]);
+        reset(c);
+
+        /* 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_cmpint(status0 & dq7, ==, 0);
+            uint64_t status1 = flash_read(c, byte_addr);
+            /* DQ6 toggles during an erase. */
+            g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6);
+            /* Wait for erase to complete. */
+            qtest_clock_step_next(c->qtest);
+            /* Ensure DQ6 has stopped toggling. */
+            g_assert_cmpint(flash_read(c, byte_addr), ==,
+                            flash_read(c, byte_addr));
+            /* Now the data should be valid. */
+            g_assert_cmpint(flash_read(c, byte_addr), ==, bank_mask(c));
+
+            /* Program a bit pattern. */
+            program(c, byte_addr, 0x55);
+            g_assert_cmpint(flash_read(c, byte_addr) & 0xFF, ==, 0x55);
+            program(c, byte_addr, 0xA5);
+            g_assert_cmpint(flash_read(c, byte_addr) & 0xFF, ==, 0x05);
+            byte_addr += sector_len;
+        }
     }
 
     /* Erase the chip. */
@@ -382,9 +439,11 @@ static void test_flash(const void *opaque)
     g_assert_cmpint(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_cmpint(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_cmpint(flash_read(c, byte_addr), ==, bank_mask(c));
+        }
     }
 
     /* Unlock bypass */
@@ -476,6 +535,32 @@ static const FlashConfig configuration[] = {
         .device_width = 1,
         .max_device_width = 4,
     },
+    /* Nonuniform sectors (top boot). */
+    {
+        .bank_width = 2,
+        .nb_blocs = { 127, 1, 2, 1 },
+        .sector_len = { 0x10000, 0x08000, 0x02000, 0x04000 },
+    },
+    /* Nonuniform sectors (top boot) with two x8 devices. */
+    {
+        .bank_width = 2,
+        .device_width = 1,
+        .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 },
+    },
+    /* Nonuniform sectors (bottom boot) with two x8 devices. */
+    {
+        .bank_width = 2,
+        .device_width = 1,
+        .nb_blocs = { 1, 2, 1, 127 },
+        .sector_len = { 0x04000, 0x02000, 0x08000, 0x10000 },
+    },
 };
 
 int main(int argc, char **argv)
@@ -486,7 +571,7 @@ 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);
@@ -502,11 +587,21 @@ 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-%d-%d",
+        char *path = g_strdup_printf("pflash-cfi02"
+                                     "/geometry/%dx%x-%dx%x-%dx%x-%dx%x"
+                                     "/%d-%d-%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,
                                      config->device_width,
                                      config->max_device_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();
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 05/10] block/pflash_cfi02: Implement nonuniform sector sizes
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 05/10] block/pflash_cfi02: Implement nonuniform sector sizes Stephen Checkoway
@ 2019-04-26 16:26   ` Stephen Checkoway
  0 siblings, 0 replies; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

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 <stephen.checkoway@oberlin.edu>
Acked-by: Thomas Huth <thuth@redhat.com>
---
 hw/block/pflash_cfi02.c   | 177 +++++++++++++++++++++++++-----------
 tests/pflash-cfi02-test.c | 185 ++++++++++++++++++++++++++++----------
 2 files changed, 265 insertions(+), 97 deletions(-)

diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c
index 101628b4ec..c4efbe8cdf 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -28,7 +28,6 @@
  * - unlock bypass command
  * - CFI queries
  *
- * 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
@@ -55,6 +54,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 cycle for CFI queries. */
 #define WCYCLE_CFI 7
 
@@ -64,8 +70,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];
     uint64_t total_len;
     uint64_t interleave_multiplier;
     uint8_t mappings;
@@ -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
@@ -189,6 +197,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->total_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;
@@ -285,6 +312,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
     PFlashCFI02 *pfl = opaque;
     uint8_t *p;
     uint8_t cmd;
+    uint32_t sector_len;
 
     cmd = value;
     if (pfl->cmd != 0xA0) {
@@ -446,12 +474,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->bank_width * 2, offset,
+                    pfl->bank_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 */
@@ -515,15 +545,14 @@ 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;
 
-    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;
     }
@@ -619,7 +648,53 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
         return;
     }
 
-    pfl->total_len = pfl->sector_len * pfl->nb_blocs;
+    int num_devices = pfl->bank_width / pfl->device_width;
+    int nb_regions;
+    pfl->total_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] /
+                                         num_devices;
+
+        /*
+         * 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->total_len / num_devices) & (sector_len_per_device - 1)) {
+            error_setg(errp, "unsupported configuration: "
+                       "flash region %d not correctly aligned.",
+                       nb_regions);
+            return;
+        }
+
+        pfl->total_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->total_len = uniform_len;
+    } else if (uniform_len != 0 && uniform_len != pfl->total_len) {
+        error_setg(errp, "\"num-blocks\"*\"sector-length\" "
+                   "different from \"num-blocks0\"*\'sector-length0\" + ... + "
+                   "\"num-blocks3\"*\"sector-length3\"");
+        return;
+    }
 
     /*
      * If the flash is not a power of 2, then the code for handling multiple
@@ -631,18 +706,6 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
         return;
     }
 
-    int num_devices = pfl->bank_width / pfl->device_width;
-    uint64_t sector_len_per_device = pfl->sector_len / num_devices;
-    uint64_t device_len = sector_len_per_device * pfl->nb_blocs;
-
-    if (sector_len_per_device & 0xff || sector_len_per_device >= (1 << 24)) {
-        error_setg(errp,
-                   "unsupported configuration: sector length per device = "
-                   "%" PRIx64 ".",
-                   sector_len_per_device);
-        return;
-    }
-
     memory_region_init_rom_device(&pfl->orig_mem, OBJECT(pfl),
                                   &pflash_cfi02_ops, pfl, pfl->name,
                                   pfl->total_len, &local_err);
@@ -650,8 +713,6 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
     pfl->unlock_addr0 &= 0x7FF;
     pfl->unlock_addr1 &= 0x7FF;
 
-    chip_len = pfl->sector_len * pfl->nb_blocs;
-
     if (local_err) {
         error_propagate(errp, local_err);
         return;
@@ -672,8 +733,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->total_len, errp)) {
             vmstate_unregister_ram(&pfl->orig_mem, DEVICE(pfl));
             return;
         }
@@ -697,7 +758,7 @@ 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[0x15] = 0x40;
     pfl->cfi_table[0x16] = 0x00;
     /* Alternate command set (none) */
     pfl->cfi_table[0x17] = 0x00;
@@ -730,7 +791,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(device_len);
+    pfl->cfi_table[0x27] = ctz32(pfl->total_len / num_devices);
     /* Flash device interface  */
     pfl->cfi_table[0x28] = device_interface_code;
     pfl->cfi_table[0x29] = device_interface_code >> 8;
@@ -739,37 +800,49 @@ 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] = sector_len_per_device >> 8;
-    pfl->cfi_table[0x30] = sector_len_per_device >> 16;
+    /* 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] / num_devices;
+        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;
+    }
 
     /* Extended */
-    pfl->cfi_table[0x31] = 'P';
-    pfl->cfi_table[0x32] = 'R';
-    pfl->cfi_table[0x33] = 'I';
+    pfl->cfi_table[0x40] = 'P';
+    pfl->cfi_table[0x41] = 'R';
+    pfl->cfi_table[0x42] = 'I';
 
-    pfl->cfi_table[0x34] = '1';
-    pfl->cfi_table[0x35] = '0';
+    pfl->cfi_table[0x43] = '1'; /* version 1.0 */
+    pfl->cfi_table[0x44] = '0';
 
-    pfl->cfi_table[0x36] = 0x00;
-    pfl->cfi_table[0x37] = 0x00;
-    pfl->cfi_table[0x38] = 0x00;
-    pfl->cfi_table[0x39] = 0x00;
+    pfl->cfi_table[0x45] = 0x00; /* Address sensitive unlock required. */
+    pfl->cfi_table[0x46] = 0x00; /* Erase suspend not supported. */
+    pfl->cfi_table[0x47] = 0x00; /* Sector protect not supported. */
+    pfl->cfi_table[0x48] = 0x00; /* Temporary sector unprotect not supported. */
 
-    pfl->cfi_table[0x3a] = 0x00;
+    pfl->cfi_table[0x49] = 0x00; /* Sector protect/unprotect scheme. */
 
-    pfl->cfi_table[0x3b] = 0x00;
-    pfl->cfi_table[0x3c] = 0x00;
+    pfl->cfi_table[0x4a] = 0x00; /* Simultaneous operation not supported. */
+    pfl->cfi_table[0x4b] = 0x00; /* Burst mode not supported. */
+    pfl->cfi_table[0x4c] = 0x00; /* Page mode not supported. */
 }
 
 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, bank_width, 0),
     DEFINE_PROP_UINT8("device-width", PFlashCFI02, device_width, 0),
     DEFINE_PROP_UINT8("max-device-width", PFlashCFI02, max_device_width, 0),
diff --git a/tests/pflash-cfi02-test.c b/tests/pflash-cfi02-test.c
index a1be26da73..703f084c5d 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;
@@ -42,10 +44,15 @@ typedef struct {
 #define UNLOCK_BYPASS_RESET_CMD 0x00
 
 typedef struct {
+    /* Interleave configuration. */
     int bank_width;
     int device_width;
     int max_device_width;
 
+    /* Nonuniform block size. */
+    int nb_blocs[4];
+    int sector_len[4];
+
     QTestState *qtest;
 } FlashConfig;
 
@@ -61,12 +68,22 @@ static FlashConfig expand_config_defaults(const FlashConfig *c)
 {
     FlashConfig ret = *c;
 
+    if (ret.bank_width == 0) {
+        ret.bank_width = 2;
+    }
     if (ret.device_width == 0) {
         ret.device_width = ret.bank_width;
     }
     if (ret.max_device_width == 0) {
         ret.max_device_width = ret.device_width;
     }
+    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);
     return ret;
 }
 
@@ -158,8 +175,8 @@ static inline uint64_t as_byte_addr(const FlashConfig *c, faddr flash_addr)
      * which is bank_width / device_width, and multiply that by the maximum
      * device width.
      */
-    int num_devices = c->bank_width / c->device_width;
-    return flash_addr.addr * (num_devices * c->max_device_width);
+    int nb_devices = c->bank_width / c->device_width;
+    return flash_addr.addr * (nb_devices * c->max_device_width);
 }
 
 /*
@@ -277,22 +294,52 @@ static bool device_supports_width(uint16_t dic, int width)
     return true;
 }
 
-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"
+                        /* Interleave properties. */
                         " -global driver=cfi.pflash02,"
                         "property=device-width,value=%d"
                         " -global driver=cfi.pflash02,"
-                        "property=max-device-width,value=%d",
+                        "property=max-device-width,value=%d"
+                        /* 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->device_width,
-                        config->max_device_width);
+                        config->max_device_width,
+                        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;
+    int nb_devices = c->bank_width / c->device_width;
 
     /* Check the IDs. */
     unlock(c);
@@ -317,19 +364,14 @@ static void test_flash(const void *opaque)
     g_assert_cmpint(flash_query(c, FLASH_ADDR(0x12)), ==, replicate(c, 'Y'));
 
     /* Num erase regions. */
-    g_assert_cmpint(flash_query_1(c, FLASH_ADDR(0x2C)), >=, 1);
+    int nb_erase_regions = flash_query_1(c, FLASH_ADDR(0x2C));
+    g_assert_cmpint(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_cmpint(device_len * (c->bank_width / c->device_width), ==,
-                    FLASH_SIZE);
-
-    /* Check nb_sectors * sector_len is device_len. */
-    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);
-    g_assert_cmpint(nb_sectors * sector_len, ==, device_len);
+    g_assert_cmpint(device_len * nb_devices, ==, UNIFORM_FLASH_SIZE);
 
     /* Check the device interface code supports the width and max width. */
     uint16_t device_interface_code = flash_query_1(c, FLASH_ADDR(0x28)) +
@@ -339,32 +381,47 @@ static void test_flash(const void *opaque)
     g_assert_true(device_supports_width(device_interface_code,
                                         c->max_device_width));
     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_cmpint(status0 & dq7, ==, 0);
-        uint64_t status1 = flash_read(c, byte_addr);
-        /* DQ6 toggles during an erase. */
-        g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6);
-        /* Wait for erase to complete. */
-        qtest_clock_step_next(c->qtest);
-        /* Ensure DQ6 has stopped toggling. */
-        g_assert_cmpint(flash_read(c, byte_addr), ==, flash_read(c, byte_addr));
-        /* Now the data should be valid. */
-        g_assert_cmpint(flash_read(c, byte_addr), ==, bank_mask(c));
 
-        /* Program a bit pattern. */
-        program(c, byte_addr, 0x55);
-        g_assert_cmpint(flash_read(c, byte_addr) & 0xFF, ==, 0x55);
-        program(c, byte_addr, 0xA5);
-        g_assert_cmpint(flash_read(c, byte_addr) & 0xFF, ==, 0x05);
+    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);
+        sector_len *= nb_devices;
+        g_assert_cmpint(nb_sectors, ==, c->nb_blocs[region]);
+        g_assert_cmpint(sector_len, ==, c->sector_len[region]);
+        reset(c);
+
+        /* 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_cmpint(status0 & dq7, ==, 0);
+            uint64_t status1 = flash_read(c, byte_addr);
+            /* DQ6 toggles during an erase. */
+            g_assert_cmpint(status0 & dq6, ==, ~status1 & dq6);
+            /* Wait for erase to complete. */
+            qtest_clock_step_next(c->qtest);
+            /* Ensure DQ6 has stopped toggling. */
+            g_assert_cmpint(flash_read(c, byte_addr), ==,
+                            flash_read(c, byte_addr));
+            /* Now the data should be valid. */
+            g_assert_cmpint(flash_read(c, byte_addr), ==, bank_mask(c));
+
+            /* Program a bit pattern. */
+            program(c, byte_addr, 0x55);
+            g_assert_cmpint(flash_read(c, byte_addr) & 0xFF, ==, 0x55);
+            program(c, byte_addr, 0xA5);
+            g_assert_cmpint(flash_read(c, byte_addr) & 0xFF, ==, 0x05);
+            byte_addr += sector_len;
+        }
     }
 
     /* Erase the chip. */
@@ -382,9 +439,11 @@ static void test_flash(const void *opaque)
     g_assert_cmpint(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_cmpint(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_cmpint(flash_read(c, byte_addr), ==, bank_mask(c));
+        }
     }
 
     /* Unlock bypass */
@@ -476,6 +535,32 @@ static const FlashConfig configuration[] = {
         .device_width = 1,
         .max_device_width = 4,
     },
+    /* Nonuniform sectors (top boot). */
+    {
+        .bank_width = 2,
+        .nb_blocs = { 127, 1, 2, 1 },
+        .sector_len = { 0x10000, 0x08000, 0x02000, 0x04000 },
+    },
+    /* Nonuniform sectors (top boot) with two x8 devices. */
+    {
+        .bank_width = 2,
+        .device_width = 1,
+        .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 },
+    },
+    /* Nonuniform sectors (bottom boot) with two x8 devices. */
+    {
+        .bank_width = 2,
+        .device_width = 1,
+        .nb_blocs = { 1, 2, 1, 127 },
+        .sector_len = { 0x04000, 0x02000, 0x08000, 0x10000 },
+    },
 };
 
 int main(int argc, char **argv)
@@ -486,7 +571,7 @@ 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);
@@ -502,11 +587,21 @@ 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-%d-%d",
+        char *path = g_strdup_printf("pflash-cfi02"
+                                     "/geometry/%dx%x-%dx%x-%dx%x-%dx%x"
+                                     "/%d-%d-%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,
                                      config->device_width,
                                      config->max_device_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();
-- 
2.20.1 (Apple Git-117)



^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 06/10] block/pflash_cfi02: Fix CFI in autoselect mode
  2019-04-26 16:26 [Qemu-devel] [PATCH v4 00/10] block/pflash_cfi02: Implement missing AMD pflash functionality Stephen Checkoway
                   ` (5 preceding siblings ...)
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 05/10] block/pflash_cfi02: Implement nonuniform sector sizes Stephen Checkoway
@ 2019-04-26 16:26 ` Stephen Checkoway
  2019-04-26 16:26   ` Stephen Checkoway
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 07/10] block/pflash_cfi02: Fix reset command not ignored during erase Stephen Checkoway
                   ` (3 subsequent siblings)
  10 siblings, 1 reply; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

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 <stephen.checkoway@oberlin.edu>
Acked-by: Thomas Huth <thuth@redhat.com>
---
 hw/block/pflash_cfi02.c   | 21 +++++++++++++++++----
 tests/pflash-cfi02-test.c | 39 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 56 insertions(+), 4 deletions(-)

diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c
index c4efbe8cdf..be10036886 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -61,8 +61,9 @@ do {                                                       \
  */
 #define PFLASH_MAX_ERASE_REGIONS 4
 
-/* Special write cycle for CFI queries. */
+/* Special write cycles for CFI queries. */
 #define WCYCLE_CFI 7
+#define WCYCLE_AUTOSELECT_CFI 8
 
 struct PFlashCFI02 {
     /*< private >*/
@@ -325,6 +326,12 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
         }
 
         if (cmd == 0xF0) {
+            if (pfl->wcycle == WCYCLE_AUTOSELECT_CFI) {
+                /* Return to autoselect mode. */
+                pfl->wcycle = 3;
+                pfl->cmd = 0x90;
+                return;
+            }
             goto reset_flash;
         }
     }
@@ -350,7 +357,6 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
         /* We're in read mode */
     check_unlock0:
         if (masked_addr == 0x55 && cmd == 0x98) {
-        enter_CFI_mode:
             /* Enter CFI query mode */
             pfl->wcycle = WCYCLE_CFI;
             pfl->cmd = 0x98;
@@ -427,9 +433,15 @@ 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 */
+            /*
+             * We can enter CFI query mode from autoselect mode, but we must
+             * return to autoselect mode after a reset.
+             */
             if (masked_addr == 0x55 && cmd == 0x98) {
-                goto enter_CFI_mode;
+                /* Enter autoselect CFI query mode */
+                pfl->wcycle = WCYCLE_AUTOSELECT_CFI;
+                pfl->cmd = 0x98;
+                return;
             }
             /* No break here */
         default:
@@ -510,6 +522,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
         }
         break;
     case WCYCLE_CFI: /* Special value for CFI queries */
+    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 703f084c5d..c2798bbb36 100644
--- a/tests/pflash-cfi02-test.c
+++ b/tests/pflash-cfi02-test.c
@@ -477,6 +477,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);
@@ -604,6 +640,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;
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 06/10] block/pflash_cfi02: Fix CFI in autoselect mode
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 06/10] block/pflash_cfi02: Fix CFI in autoselect mode Stephen Checkoway
@ 2019-04-26 16:26   ` Stephen Checkoway
  0 siblings, 0 replies; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

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 <stephen.checkoway@oberlin.edu>
Acked-by: Thomas Huth <thuth@redhat.com>
---
 hw/block/pflash_cfi02.c   | 21 +++++++++++++++++----
 tests/pflash-cfi02-test.c | 39 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 56 insertions(+), 4 deletions(-)

diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c
index c4efbe8cdf..be10036886 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -61,8 +61,9 @@ do {                                                       \
  */
 #define PFLASH_MAX_ERASE_REGIONS 4
 
-/* Special write cycle for CFI queries. */
+/* Special write cycles for CFI queries. */
 #define WCYCLE_CFI 7
+#define WCYCLE_AUTOSELECT_CFI 8
 
 struct PFlashCFI02 {
     /*< private >*/
@@ -325,6 +326,12 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
         }
 
         if (cmd == 0xF0) {
+            if (pfl->wcycle == WCYCLE_AUTOSELECT_CFI) {
+                /* Return to autoselect mode. */
+                pfl->wcycle = 3;
+                pfl->cmd = 0x90;
+                return;
+            }
             goto reset_flash;
         }
     }
@@ -350,7 +357,6 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
         /* We're in read mode */
     check_unlock0:
         if (masked_addr == 0x55 && cmd == 0x98) {
-        enter_CFI_mode:
             /* Enter CFI query mode */
             pfl->wcycle = WCYCLE_CFI;
             pfl->cmd = 0x98;
@@ -427,9 +433,15 @@ 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 */
+            /*
+             * We can enter CFI query mode from autoselect mode, but we must
+             * return to autoselect mode after a reset.
+             */
             if (masked_addr == 0x55 && cmd == 0x98) {
-                goto enter_CFI_mode;
+                /* Enter autoselect CFI query mode */
+                pfl->wcycle = WCYCLE_AUTOSELECT_CFI;
+                pfl->cmd = 0x98;
+                return;
             }
             /* No break here */
         default:
@@ -510,6 +522,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
         }
         break;
     case WCYCLE_CFI: /* Special value for CFI queries */
+    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 703f084c5d..c2798bbb36 100644
--- a/tests/pflash-cfi02-test.c
+++ b/tests/pflash-cfi02-test.c
@@ -477,6 +477,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);
@@ -604,6 +640,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;
-- 
2.20.1 (Apple Git-117)



^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 07/10] block/pflash_cfi02: Fix reset command not ignored during erase
  2019-04-26 16:26 [Qemu-devel] [PATCH v4 00/10] block/pflash_cfi02: Implement missing AMD pflash functionality Stephen Checkoway
                   ` (6 preceding siblings ...)
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 06/10] block/pflash_cfi02: Fix CFI in autoselect mode Stephen Checkoway
@ 2019-04-26 16:26 ` Stephen Checkoway
  2019-04-26 16:26   ` Stephen Checkoway
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 08/10] block/pflash_cfi02: Implement multi-sector erase Stephen Checkoway
                   ` (2 subsequent siblings)
  10 siblings, 1 reply; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

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 <stephen.checkoway@oberlin.edu>
---
 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 be10036886..cb1160eb35 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -325,7 +325,8 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
                     pfl->bank_width * 2, value);
         }
 
-        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;
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 07/10] block/pflash_cfi02: Fix reset command not ignored during erase
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 07/10] block/pflash_cfi02: Fix reset command not ignored during erase Stephen Checkoway
@ 2019-04-26 16:26   ` Stephen Checkoway
  0 siblings, 0 replies; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

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 <stephen.checkoway@oberlin.edu>
---
 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 be10036886..cb1160eb35 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -325,7 +325,8 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
                     pfl->bank_width * 2, value);
         }
 
-        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;
-- 
2.20.1 (Apple Git-117)



^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 08/10] block/pflash_cfi02: Implement multi-sector erase
  2019-04-26 16:26 [Qemu-devel] [PATCH v4 00/10] block/pflash_cfi02: Implement missing AMD pflash functionality Stephen Checkoway
                   ` (7 preceding siblings ...)
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 07/10] block/pflash_cfi02: Fix reset command not ignored during erase Stephen Checkoway
@ 2019-04-26 16:26 ` Stephen Checkoway
  2019-04-26 16:26   ` Stephen Checkoway
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 09/10] block/pflash_cfi02: Implement erase suspend/resume Stephen Checkoway
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 10/10] block/pflash_cfi02: Use the chip erase time specified in the CFI table Stephen Checkoway
  10 siblings, 1 reply; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

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 <stephen.checkoway@oberlin.edu>
Acked-by: Thomas Huth <thuth@redhat.com>
---
 hw/block/pflash_cfi02.c   | 94 +++++++++++++++++++++++++++++++--------
 tests/pflash-cfi02-test.c | 59 ++++++++++++++++++++++--
 2 files changed, 131 insertions(+), 22 deletions(-)

diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c
index cb1160eb35..21ceb0823b 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -30,7 +30,6 @@
  *
  * 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;
 };
@@ -136,6 +136,22 @@ static inline void toggle_dq6(PFlashCFI02 *pfl)
     pfl->status ^= pfl->interleave_multiplier * 0x40;
 }
 
+/*
+ * Turn on DQ3.
+ */
+static inline void assert_dq3(PFlashCFI02 *pfl)
+{
+    pfl->status |= pfl->interleave_multiplier * 0x08;
+}
+
+/*
+ * Turn off DQ3.
+ */
+static inline void reset_dq3(PFlashCFI02 *pfl)
+{
+    pfl->status &= ~(pfl->interleave_multiplier * 0x08);
+}
+
 /*
  * Set up replicated mappings of the same region.
  */
@@ -159,11 +175,37 @@ static void pflash_register_memory(PFlashCFI02 *pfl, int rom_mode)
     pfl->rom_mode = rom_mode;
 }
 
-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) {
@@ -307,13 +349,30 @@ 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->bank_width * 2, offset,
+            pfl->bank_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)
 {
     PFlashCFI02 *pfl = opaque;
     uint8_t *p;
     uint8_t cmd;
-    uint32_t sector_len;
 
     cmd = value;
     if (pfl->cmd != 0xA0) {
@@ -486,20 +545,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->bank_width * 2, offset,
-                    pfl->bank_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);
@@ -513,7 +559,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 c2798bbb36..0384593792 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
@@ -222,7 +223,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));
 }
@@ -261,7 +262,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);
 }
@@ -383,6 +384,7 @@ static void test_geometry(const void *opaque)
     reset(c);
     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) {
@@ -400,18 +402,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_cmpint(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_cmpint(status0 & dq3, ==, dq3);
+
             /* DQ7 is 0 during an erase. */
             g_assert_cmpint(status0 & dq7, ==, 0);
             uint64_t status1 = flash_read(c, byte_addr);
+
             /* DQ6 toggles during an erase. */
             g_assert_cmpint(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_cmpint(flash_read(c, byte_addr), ==,
                             flash_read(c, byte_addr));
+
             /* Now the data should be valid. */
             g_assert_cmpint(flash_read(c, byte_addr), ==, bank_mask(c));
 
@@ -474,6 +487,44 @@ static void test_geometry(const void *opaque)
     g_assert_cmpint(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_cmpint(flash_read(c, byte_addr), ==, bank_mask(c));
+            } else {
+                g_assert_cmpint(flash_read(c, byte_addr), ==, 0);
+            }
+            byte_addr += config->sector_len[region];
+        }
+    }
+
     qtest_quit(qtest);
 }
 
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 08/10] block/pflash_cfi02: Implement multi-sector erase
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 08/10] block/pflash_cfi02: Implement multi-sector erase Stephen Checkoway
@ 2019-04-26 16:26   ` Stephen Checkoway
  0 siblings, 0 replies; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

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 <stephen.checkoway@oberlin.edu>
Acked-by: Thomas Huth <thuth@redhat.com>
---
 hw/block/pflash_cfi02.c   | 94 +++++++++++++++++++++++++++++++--------
 tests/pflash-cfi02-test.c | 59 ++++++++++++++++++++++--
 2 files changed, 131 insertions(+), 22 deletions(-)

diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c
index cb1160eb35..21ceb0823b 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -30,7 +30,6 @@
  *
  * 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;
 };
@@ -136,6 +136,22 @@ static inline void toggle_dq6(PFlashCFI02 *pfl)
     pfl->status ^= pfl->interleave_multiplier * 0x40;
 }
 
+/*
+ * Turn on DQ3.
+ */
+static inline void assert_dq3(PFlashCFI02 *pfl)
+{
+    pfl->status |= pfl->interleave_multiplier * 0x08;
+}
+
+/*
+ * Turn off DQ3.
+ */
+static inline void reset_dq3(PFlashCFI02 *pfl)
+{
+    pfl->status &= ~(pfl->interleave_multiplier * 0x08);
+}
+
 /*
  * Set up replicated mappings of the same region.
  */
@@ -159,11 +175,37 @@ static void pflash_register_memory(PFlashCFI02 *pfl, int rom_mode)
     pfl->rom_mode = rom_mode;
 }
 
-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) {
@@ -307,13 +349,30 @@ 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->bank_width * 2, offset,
+            pfl->bank_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)
 {
     PFlashCFI02 *pfl = opaque;
     uint8_t *p;
     uint8_t cmd;
-    uint32_t sector_len;
 
     cmd = value;
     if (pfl->cmd != 0xA0) {
@@ -486,20 +545,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->bank_width * 2, offset,
-                    pfl->bank_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);
@@ -513,7 +559,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 c2798bbb36..0384593792 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
@@ -222,7 +223,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));
 }
@@ -261,7 +262,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);
 }
@@ -383,6 +384,7 @@ static void test_geometry(const void *opaque)
     reset(c);
     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) {
@@ -400,18 +402,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_cmpint(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_cmpint(status0 & dq3, ==, dq3);
+
             /* DQ7 is 0 during an erase. */
             g_assert_cmpint(status0 & dq7, ==, 0);
             uint64_t status1 = flash_read(c, byte_addr);
+
             /* DQ6 toggles during an erase. */
             g_assert_cmpint(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_cmpint(flash_read(c, byte_addr), ==,
                             flash_read(c, byte_addr));
+
             /* Now the data should be valid. */
             g_assert_cmpint(flash_read(c, byte_addr), ==, bank_mask(c));
 
@@ -474,6 +487,44 @@ static void test_geometry(const void *opaque)
     g_assert_cmpint(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_cmpint(flash_read(c, byte_addr), ==, bank_mask(c));
+            } else {
+                g_assert_cmpint(flash_read(c, byte_addr), ==, 0);
+            }
+            byte_addr += config->sector_len[region];
+        }
+    }
+
     qtest_quit(qtest);
 }
 
-- 
2.20.1 (Apple Git-117)



^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 09/10] block/pflash_cfi02: Implement erase suspend/resume
  2019-04-26 16:26 [Qemu-devel] [PATCH v4 00/10] block/pflash_cfi02: Implement missing AMD pflash functionality Stephen Checkoway
                   ` (8 preceding siblings ...)
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 08/10] block/pflash_cfi02: Implement multi-sector erase Stephen Checkoway
@ 2019-04-26 16:26 ` Stephen Checkoway
  2019-04-26 16:26   ` Stephen Checkoway
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 10/10] block/pflash_cfi02: Use the chip erase time specified in the CFI table Stephen Checkoway
  10 siblings, 1 reply; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

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 <stephen.checkoway@oberlin.edu>
Acked-by: Thomas Huth <thuth@redhat.com>
---
 hw/block/pflash_cfi02.c   | 153 ++++++++++++++++++++++++++++++++++----
 tests/pflash-cfi02-test.c | 112 ++++++++++++++++++++++++++++
 2 files changed, 251 insertions(+), 14 deletions(-)

diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c
index 21ceb0823b..d9087cafff 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -29,7 +29,6 @@
  * - CFI queries
  *
  * 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"
@@ -37,6 +36,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"
@@ -72,6 +72,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];
     uint64_t total_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;
 };
@@ -152,6 +155,14 @@ static inline void reset_dq3(PFlashCFI02 *pfl)
     pfl->status &= ~(pfl->interleave_multiplier * 0x08);
 }
 
+/*
+ * Toggle status bit DQ2.
+ */
+static inline void toggle_dq2(PFlashCFI02 *pfl)
+{
+    pfl->status ^= pfl->interleave_multiplier * 0x04;
+}
+
 /*
  * Set up replicated mappings of the same region.
  */
@@ -175,6 +186,29 @@ static void pflash_register_memory(PFlashCFI02 *pfl, int rom_mode)
     pfl->rom_mode = rom_mode;
 }
 
+/*
+ * 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;
@@ -189,12 +223,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",
@@ -202,6 +231,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);
     }
@@ -240,25 +270,45 @@ 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->total_len);
     int nb_regions = pfl->cfi_table[0x2C];
     hwaddr addr = 0;
+    uint32_t sector_num = 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];
+            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;
@@ -285,6 +335,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 */
         return pflash_data_read(pfl, offset, width);
     case 0x90:
@@ -313,14 +372,16 @@ static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width)
         DPRINTF("%s: ID " TARGET_FMT_plx " %" PRIx64 "\n",
                 __func__, device_addr & 0xFF, 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 bit 6 */
-        toggle_dq6(pfl);
         break;
     case 0x98:
         /* CFI query mode */
@@ -351,7 +412,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->bank_width * 2, offset,
@@ -363,6 +425,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);
 }
@@ -422,6 +485,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 (masked_addr != pfl->unlock_addr0 || cmd != 0xAA) {
             DPRINTF("%s: unlock0 failed %04x %02x %04x\n",
                     __func__, masked_addr, cmd, pfl->unlock_addr0);
@@ -467,6 +549,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, value, width, 0);
             if (!pfl->ro) {
                 p = (uint8_t *)pfl->storage + offset;
@@ -525,6 +615,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 (masked_addr != pfl->unlock_addr0) {
@@ -559,6 +653,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
@@ -723,10 +841,12 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
     int num_devices = pfl->bank_width / pfl->device_width;
     int nb_regions;
     pfl->total_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] /
                                          num_devices;
 
@@ -761,6 +881,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->total_len = uniform_len;
+        pfl->total_sectors = pfl->uniform_nb_blocs;
     } else if (uniform_len != 0 && uniform_len != pfl->total_len) {
         error_setg(errp, "\"num-blocks\"*\"sector-length\" "
                    "different from \"num-blocks0\"*\'sector-length0\" + ... + "
@@ -785,6 +906,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);
+
     if (local_err) {
         error_propagate(errp, local_err);
         return;
@@ -892,7 +1016,7 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
     pfl->cfi_table[0x44] = '0';
 
     pfl->cfi_table[0x45] = 0x00; /* Address sensitive unlock required. */
-    pfl->cfi_table[0x46] = 0x00; /* Erase suspend not supported. */
+    pfl->cfi_table[0x46] = 0x02; /* Erase suspend to read/write. */
     pfl->cfi_table[0x47] = 0x00; /* Sector protect not supported. */
     pfl->cfi_table[0x48] = 0x00; /* Temporary sector unprotect not supported. */
 
@@ -934,6 +1058,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 0384593792..7de8e297f8 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 {
     /* Interleave configuration. */
@@ -267,6 +269,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);
+}
+
 /*
  * Check that the device interface code dic is appropriate for the given
  * width.
@@ -381,10 +393,21 @@ static void test_geometry(const void *opaque)
                                         c->device_width));
     g_assert_true(device_supports_width(device_interface_code,
                                         c->max_device_width));
+
+    /* 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) {
@@ -525,6 +548,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);
 }
 
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 09/10] block/pflash_cfi02: Implement erase suspend/resume
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 09/10] block/pflash_cfi02: Implement erase suspend/resume Stephen Checkoway
@ 2019-04-26 16:26   ` Stephen Checkoway
  0 siblings, 0 replies; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

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 <stephen.checkoway@oberlin.edu>
Acked-by: Thomas Huth <thuth@redhat.com>
---
 hw/block/pflash_cfi02.c   | 153 ++++++++++++++++++++++++++++++++++----
 tests/pflash-cfi02-test.c | 112 ++++++++++++++++++++++++++++
 2 files changed, 251 insertions(+), 14 deletions(-)

diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c
index 21ceb0823b..d9087cafff 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -29,7 +29,6 @@
  * - CFI queries
  *
  * 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"
@@ -37,6 +36,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"
@@ -72,6 +72,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];
     uint64_t total_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;
 };
@@ -152,6 +155,14 @@ static inline void reset_dq3(PFlashCFI02 *pfl)
     pfl->status &= ~(pfl->interleave_multiplier * 0x08);
 }
 
+/*
+ * Toggle status bit DQ2.
+ */
+static inline void toggle_dq2(PFlashCFI02 *pfl)
+{
+    pfl->status ^= pfl->interleave_multiplier * 0x04;
+}
+
 /*
  * Set up replicated mappings of the same region.
  */
@@ -175,6 +186,29 @@ static void pflash_register_memory(PFlashCFI02 *pfl, int rom_mode)
     pfl->rom_mode = rom_mode;
 }
 
+/*
+ * 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;
@@ -189,12 +223,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",
@@ -202,6 +231,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);
     }
@@ -240,25 +270,45 @@ 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->total_len);
     int nb_regions = pfl->cfi_table[0x2C];
     hwaddr addr = 0;
+    uint32_t sector_num = 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];
+            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;
@@ -285,6 +335,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 */
         return pflash_data_read(pfl, offset, width);
     case 0x90:
@@ -313,14 +372,16 @@ static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width)
         DPRINTF("%s: ID " TARGET_FMT_plx " %" PRIx64 "\n",
                 __func__, device_addr & 0xFF, 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 bit 6 */
-        toggle_dq6(pfl);
         break;
     case 0x98:
         /* CFI query mode */
@@ -351,7 +412,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->bank_width * 2, offset,
@@ -363,6 +425,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);
 }
@@ -422,6 +485,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 (masked_addr != pfl->unlock_addr0 || cmd != 0xAA) {
             DPRINTF("%s: unlock0 failed %04x %02x %04x\n",
                     __func__, masked_addr, cmd, pfl->unlock_addr0);
@@ -467,6 +549,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, value, width, 0);
             if (!pfl->ro) {
                 p = (uint8_t *)pfl->storage + offset;
@@ -525,6 +615,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 (masked_addr != pfl->unlock_addr0) {
@@ -559,6 +653,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
@@ -723,10 +841,12 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
     int num_devices = pfl->bank_width / pfl->device_width;
     int nb_regions;
     pfl->total_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] /
                                          num_devices;
 
@@ -761,6 +881,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->total_len = uniform_len;
+        pfl->total_sectors = pfl->uniform_nb_blocs;
     } else if (uniform_len != 0 && uniform_len != pfl->total_len) {
         error_setg(errp, "\"num-blocks\"*\"sector-length\" "
                    "different from \"num-blocks0\"*\'sector-length0\" + ... + "
@@ -785,6 +906,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);
+
     if (local_err) {
         error_propagate(errp, local_err);
         return;
@@ -892,7 +1016,7 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
     pfl->cfi_table[0x44] = '0';
 
     pfl->cfi_table[0x45] = 0x00; /* Address sensitive unlock required. */
-    pfl->cfi_table[0x46] = 0x00; /* Erase suspend not supported. */
+    pfl->cfi_table[0x46] = 0x02; /* Erase suspend to read/write. */
     pfl->cfi_table[0x47] = 0x00; /* Sector protect not supported. */
     pfl->cfi_table[0x48] = 0x00; /* Temporary sector unprotect not supported. */
 
@@ -934,6 +1058,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 0384593792..7de8e297f8 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 {
     /* Interleave configuration. */
@@ -267,6 +269,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);
+}
+
 /*
  * Check that the device interface code dic is appropriate for the given
  * width.
@@ -381,10 +393,21 @@ static void test_geometry(const void *opaque)
                                         c->device_width));
     g_assert_true(device_supports_width(device_interface_code,
                                         c->max_device_width));
+
+    /* 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) {
@@ -525,6 +548,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);
 }
 
-- 
2.20.1 (Apple Git-117)



^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 10/10] block/pflash_cfi02: Use the chip erase time specified in the CFI table
  2019-04-26 16:26 [Qemu-devel] [PATCH v4 00/10] block/pflash_cfi02: Implement missing AMD pflash functionality Stephen Checkoway
                   ` (9 preceding siblings ...)
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 09/10] block/pflash_cfi02: Implement erase suspend/resume Stephen Checkoway
@ 2019-04-26 16:26 ` Stephen Checkoway
  2019-04-26 16:26   ` Stephen Checkoway
  2019-05-05 18:57   ` Philippe Mathieu-Daudé
  10 siblings, 2 replies; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

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 <stephen.checkoway@oberlin.edu>
---
 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 d9087cafff..76c8af4365 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -633,9 +633,9 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
                 pflash_update(pfl, 0, pfl->total_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 */
-- 
2.20.1 (Apple Git-117)

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* [Qemu-devel] [PATCH v4 10/10] block/pflash_cfi02: Use the chip erase time specified in the CFI table
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 10/10] block/pflash_cfi02: Use the chip erase time specified in the CFI table Stephen Checkoway
@ 2019-04-26 16:26   ` Stephen Checkoway
  2019-05-05 18:57   ` Philippe Mathieu-Daudé
  1 sibling, 0 replies; 31+ messages in thread
From: Stephen Checkoway @ 2019-04-26 16:26 UTC (permalink / raw)
  To: QEMU Developers, Thomas Huth, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini
  Cc: Stephen Checkoway

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 <stephen.checkoway@oberlin.edu>
---
 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 d9087cafff..76c8af4365 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -633,9 +633,9 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
                 pflash_update(pfl, 0, pfl->total_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 */
-- 
2.20.1 (Apple Git-117)



^ permalink raw reply related	[flat|nested] 31+ messages in thread

* Re: [Qemu-devel] [PATCH v4 10/10] block/pflash_cfi02: Use the chip erase time specified in the CFI table
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 10/10] block/pflash_cfi02: Use the chip erase time specified in the CFI table Stephen Checkoway
  2019-04-26 16:26   ` Stephen Checkoway
@ 2019-05-05 18:57   ` Philippe Mathieu-Daudé
  2019-05-05 18:57     ` Philippe Mathieu-Daudé
  1 sibling, 1 reply; 31+ messages in thread
From: Philippe Mathieu-Daudé @ 2019-05-05 18:57 UTC (permalink / raw)
  To: Stephen Checkoway, QEMU Developers, Thomas Huth, Kevin Wolf,
	Max Reitz, open list:Block layer core, Markus Armbruster,
	Laszlo Ersek, Laurent Vivier, Paolo Bonzini

On 4/26/19 6:26 PM, Stephen Checkoway wrote:
> 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 <stephen.checkoway@oberlin.edu>
> ---
>  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 d9087cafff..76c8af4365 100644
> --- a/hw/block/pflash_cfi02.c
> +++ b/hw/block/pflash_cfi02.c
> @@ -633,9 +633,9 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
>                  pflash_update(pfl, 0, pfl->total_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 */
> 

Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com>

^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Qemu-devel] [PATCH v4 10/10] block/pflash_cfi02: Use the chip erase time specified in the CFI table
  2019-05-05 18:57   ` Philippe Mathieu-Daudé
@ 2019-05-05 18:57     ` Philippe Mathieu-Daudé
  0 siblings, 0 replies; 31+ messages in thread
From: Philippe Mathieu-Daudé @ 2019-05-05 18:57 UTC (permalink / raw)
  To: Stephen Checkoway, QEMU Developers, Thomas Huth, Kevin Wolf,
	Max Reitz, open list:Block layer core, Markus Armbruster,
	Laszlo Ersek, Laurent Vivier, Paolo Bonzini

On 4/26/19 6:26 PM, Stephen Checkoway wrote:
> 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 <stephen.checkoway@oberlin.edu>
> ---
>  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 d9087cafff..76c8af4365 100644
> --- a/hw/block/pflash_cfi02.c
> +++ b/hw/block/pflash_cfi02.c
> @@ -633,9 +633,9 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
>                  pflash_update(pfl, 0, pfl->total_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 */
> 

Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com>


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Qemu-devel] [PATCH v4 01/10] block/pflash_cfi02: Add test for supported commands
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 01/10] block/pflash_cfi02: Add test for supported commands Stephen Checkoway
  2019-04-26 16:26   ` Stephen Checkoway
@ 2019-05-06  3:53   ` Thomas Huth
  1 sibling, 0 replies; 31+ messages in thread
From: Thomas Huth @ 2019-05-06  3:53 UTC (permalink / raw)
  To: Stephen Checkoway, QEMU Developers, Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini

On 26/04/2019 18.26, Stephen Checkoway wrote:
> 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 <stephen.checkoway@oberlin.edu>
> ---
>  tests/Makefile.include    |   2 +
>  tests/pflash-cfi02-test.c | 225 ++++++++++++++++++++++++++++++++++++++
>  2 files changed, 227 insertions(+)
>  create mode 100644 tests/pflash-cfi02-test.c

Acked-by: Thomas Huth <thuth@redhat.com>


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Qemu-devel] [PATCH v4 02/10] block/pflash_cfi02: Refactor, NFC intended
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 02/10] block/pflash_cfi02: Refactor, NFC intended Stephen Checkoway
  2019-04-26 16:26   ` Stephen Checkoway
@ 2019-05-06  7:34   ` Philippe Mathieu-Daudé
  1 sibling, 0 replies; 31+ messages in thread
From: Philippe Mathieu-Daudé @ 2019-05-06  7:34 UTC (permalink / raw)
  To: Stephen Checkoway, QEMU Developers, Thomas Huth, Kevin Wolf,
	Max Reitz, open list:Block layer core, Markus Armbruster,
	Laszlo Ersek, Laurent Vivier, Paolo Bonzini

On 4/26/19 6:26 PM, Stephen Checkoway wrote:
> Simplify and refactor for upcoming commits. In particular, 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.

As this patch is hard to digest, I splitted it in various atomic changes
in another series:
https://lists.gnu.org/archive/html/qemu-devel/2019-05/msg00975.html

> 
> Signed-off-by: Stephen Checkoway <stephen.checkoway@oberlin.edu>
> ---
>  hw/block/pflash_cfi02.c | 221 +++++++++++++++++-----------------------
>  1 file changed, 95 insertions(+), 126 deletions(-)
> 
> diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c
> index f2c6201f81..4b7af71806 100644
> --- a/hw/block/pflash_cfi02.c
> +++ b/hw/block/pflash_cfi02.c
> @@ -46,18 +46,19 @@
>  #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
>  
> +/* Special write cycle for CFI queries. */
> +#define WCYCLE_CFI 7
> +
>  struct PFlashCFI02 {
>      /*< private >*/
>      SysBusDevice parent_obj;
> @@ -97,6 +98,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.
>   */
> @@ -126,7 +152,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 {
> @@ -136,12 +162,34 @@ static void pflash_timer (void *opaque)
>      pfl->cmd = 0;
>  }
>  
> -static uint32_t pflash_read(PFlashCFI02 *pfl, hwaddr offset,
> -                            int width, int be)
> +/*
> + * 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);
> +    /* XXX: Need a trace_pflash_data_read(offset, ret, width) */
> +    switch (width) {
> +    case 1:
> +        trace_pflash_data_read8(offset, ret);
> +        break;
> +    case 2:
> +        trace_pflash_data_read16(offset, ret);
> +        break;
> +    case 4:
> +        trace_pflash_data_read32(offset, ret);
> +        break;
> +    }
> +    return ret;
> +}
> +
> +static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width)
> +{
> +    PFlashCFI02 *pfl = opaque;
>      hwaddr boff;
> -    uint32_t ret;
> -    uint8_t *p;
> +    uint64_t ret;
>  
>      ret = -1;
>      trace_pflash_read(offset, pfl->cmd, width, pfl->wcycle);
> @@ -166,39 +214,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 = pfl->storage;
> -        switch (width) {
> -        case 1:
> -            ret = p[offset];
> -            trace_pflash_data_read8(offset, ret);
> -            break;
> -        case 2:
> -            if (be) {
> -                ret = p[offset] << 8;
> -                ret |= p[offset + 1];
> -            } else {
> -                ret = p[offset];
> -                ret |= p[offset + 1] << 8;
> -            }
> -            trace_pflash_data_read16(offset, ret);
> -            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;
> -            }
> -            trace_pflash_data_read32(offset, ret);
> -            break;
> -        }
> +        ret = pflash_data_read(pfl, offset, width);
>          break;
>      case 0x90:
>          /* flash ID read */
> @@ -213,23 +230,23 @@ 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;
> +            ret = pflash_data_read(pfl, offset, width);
>          }
> -        DPRINTF("%s: ID " TARGET_FMT_plx " %x\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 %x\n", __func__, ret);
> +        DPRINTF("%s: status %" PRIx64 "\n", __func__, ret);
>          /* Toggle bit 6 */
> -        pfl->status ^= 0x40;
> +        toggle_dq6(pfl);
>          break;
>      case 0x98:
>          /* CFI query mode */
> @@ -245,8 +262,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) {
> @@ -259,9 +275,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;
> @@ -277,7 +294,7 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset,
>      trace_pflash_write(offset, value, width, pfl->wcycle);
>      offset &= pfl->chip_len - 1;
>  
> -    DPRINTF("%s: offset " TARGET_FMT_plx " %08x %d\n", __func__,
> +    DPRINTF("%s: offset " TARGET_FMT_plx " %08" PRIx64 " %d\n", __func__,
>              offset, value, width);
>      boff = offset & (pfl->sector_len - 1);
>      if (pfl->width == 2)
> @@ -295,7 +312,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;
>          }
> @@ -345,40 +362,22 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset,
>              goto check_unlock0;
>          case 0xA0:
>              trace_pflash_data_write(offset, value, width, 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);
>              }
> -            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;
> @@ -426,7 +425,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));
> @@ -441,7 +440,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));
> @@ -467,7 +466,7 @@ static void pflash_write(PFlashCFI02 *pfl, hwaddr offset,
>              goto reset_flash;
>          }
>          break;
> -    case 7: /* Special value for CFI queries */
> +    case WCYCLE_CFI: /* Special value for CFI queries */
>          DPRINTF("%s: invalid write in CFI query mode\n", __func__);
>          goto reset_flash;
>      default:
> @@ -492,39 +491,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,
> @@ -552,9 +521,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;
> 


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Qemu-devel] [PATCH v4 04/10] block/pflash_cfi02: Implement intereleaved flash devices
  2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 04/10] block/pflash_cfi02: Implement intereleaved flash devices Stephen Checkoway
  2019-04-26 16:26   ` Stephen Checkoway
@ 2019-06-22 12:25   ` Philippe Mathieu-Daudé
  2019-06-24 19:05     ` Philippe Mathieu-Daudé
  1 sibling, 1 reply; 31+ messages in thread
From: Philippe Mathieu-Daudé @ 2019-06-22 12:25 UTC (permalink / raw)
  To: Stephen Checkoway, QEMU Developers, Thomas Huth,
	Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini,
	Peter Maydell

Hi Stephen,

This series haven't fall through the cracks, however it is taking me
longer than expected to review it.

On 4/26/19 6:26 PM, Stephen Checkoway wrote:
> It's common for multiple narrow flash chips to be hooked up in parallel
> to support wider buses. For example, four 8-bit wide flash chips (x8)
> may be combined in parallel to produce a 32-bit wide device. Similarly,
> two 16-bit wide chips (x16) may be combined.
> 
> This commit introduces `device-width` and `max-device-width` properties,
> similar to pflash_cfi01, with the following meanings:
> - `width`: The width of the logical, qemu device (same as before);
> - `device-width`: The width of an individual flash chip, defaulting to
>   `width`; and
> - `max-device-width`: The maximum width of an individual flash chip,
>   defaulting to `device-width`.
> 
> Nothing needs to change to support reading such interleaved devices but
> commands (e.g., erase and programming) must be sent to all devices at
> the same time or else the various chips will be in different states.

After some thoughts on this, I'd rather we model how hardware manage
interleaved devices: do it at the bus level, and instanciate N devices
in an interleaved config.
I believe that would drastically reduce this device complexity, and we
would match the real internal state machine.
Also this could be reused by other parallel devices used in a such config.

> For example, a 4-byte wide logical device can be composed of four x8/x16
> devices in x8 mode. That is, each device supports both x8 or x16 and
> they're being used in the byte, rather than word, mode. This
> configuration would have `width=4`, `device-width=1`, and
> `max-device-width=2`.


I'm thinking of this draft:

FlashDevice # x8
  MemoryRegionOps
    .valid.max_access_size = 1

FlashDevice # x16
  MemoryRegionOps
    .valid.min_access_size = 2
    .valid.max_access_size = 2

FlashDevice # x8/x16
  MemoryRegionOps
    .valid.min_access_size = 1
    .valid.max_access_size = 2

We might use .impl.min_access_size = 2 and consider all NOR flash using
16-bit words internally.
    .impl.max_access_size = 2 is implicit.

So for you example we'd instanciate one:

InterleaverDevice
  Property
    .bus_width = 4 # 4-byte wide logical device, `width=4`
    .device_width = 1 # `device-width=1`
  MemoryRegionOps
    .valid.max_access_size = .bus_width # 4, set at realize()
    .impl.max_access_size = .device_width # 1, set at realize()

Then instanciate 4 pflash devices, and link them to the interleaver
using object_property_set_link().

typedef struct {
    SysBusDevice parent_obj;
    MemoryRegion iomem;
    char *name;
    /*
     * On a 64-bit wide bus we can have at most
     * 8 devices in 8-bit access mode.
     */
    MemoryRegion device[8];
    unsigned device_count;
    unsigned device_index_mask;
    /* Properties */
    unsigned bus_width;
    unsigned device_width;
} InterleaverDeviceState;

static Property interleaver_properties[] = {
    DEFINE_PROP_LINK("device[0]", InterleaverDeviceState,
                     device[0],
                     TYPE_MEMORY_REGION, MemoryRegion *),
    ...
    DEFINE_PROP_LINK("device[7]", InterleaverDeviceState,
                     device[7],
                     TYPE_MEMORY_REGION, MemoryRegion *),
    DEFINE_PROP_END_OF_LIST(),
};

Then previous to call InterleaverDevice.realize():

In the board realize():


    for (i = 0; i < interleaved_devices; i++) {
        pflash[i] = create_pflash(...);
        ...
    }

    ild = ... create InterleaverDevice ...
    for (i = 0; i < interleaved_devices; i++) {
        char *propname = g_strdup_printf("device[%u]", i);


        object_property_set_link(OBJECT(&ild->device[i]),
                                 OBJECT(pflash[i]),
                                 propname, &err);
        ...
    }

Finally,

static void interleaved_realize(DeviceState *dev, Error **errp)
{
    InterleaverDeviceState *s = INTERLEAVER_DEVICE(opaque);

    s->device_count = s->bus_width / s->device_width;
    s->device_index_mask = ~(s->device_count - 1);
    ...
}

static void interleaved_write(void *opaque, hwaddr offset,
                              uint64_t value, unsigned size)
{
    InterleaverDeviceState *s = INTERLEAVER_DEVICE(opaque);
    MemoryRegion *mr;

    /*
     * Since we set .impl.max_access_size = device_width,
     * access_with_adjusted_size() always call this with
     * size = device_width.
     *
     * Adjust the address (offset).
     */
    offset >>= size;
    /* Access the N interleaved device */
    mr = s->device[offset & s->device_index_mask];
    memory_region_dispatch_write(mr, offset, &value, size,
                                 MEMTXATTRS_UNSPECIFIED);
}

I'll try a PoC.

> In addition to commands being sent to all devices, guest firmware
> expects the status and CFI queries to be replicated for each device.
> (The one exception to the response replication is that each device gets
> to report its own status bit DQ7 while programming because its value
> depends on the value being programmed which will usually differ for each
> device.)
> 
> Testing is limited to 16-bit wide devices due to the current inability
> to override the properties set by `pflash_cfi02_register`, but multiple
> configurations are tested.
> 
> Stop using global_qtest. Instead, package the qtest variable inside the
> FlashConfig structure.
> 
> Signed-off-by: Stephen Checkoway <stephen.checkoway@oberlin.edu>
> Acked-by: Thomas Huth <thuth@redhat.com>
> ---
>  hw/block/pflash_cfi02.c   | 270 +++++++++++++++------
>  tests/pflash-cfi02-test.c | 476 ++++++++++++++++++++++++++++++--------
>  2 files changed, 576 insertions(+), 170 deletions(-)
> 
> diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c
> index e4bff0c8f8..101628b4ec 100644
> --- a/hw/block/pflash_cfi02.c
> +++ b/hw/block/pflash_cfi02.c
> @@ -28,7 +28,6 @@
>   * - unlock bypass command
>   * - 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
> @@ -67,15 +66,19 @@ struct PFlashCFI02 {
>      BlockBackend *blk;
>      uint32_t sector_len;
>      uint32_t nb_blocs;
> -    uint32_t chip_len;
> +    uint64_t total_len;
> +    uint64_t interleave_multiplier;
>      uint8_t mappings;
> -    uint8_t width;
> +    uint8_t bank_width; /* Width of the QEMU device in bytes. */
> +    uint8_t device_width; /* Width of individual pflash chip. */
> +    uint8_t max_device_width; /* Maximum width of individual pflash chip. */
>      uint8_t be;
> +    int device_shift; /* Amount to shift an offset to get a device address. */
>      int wcycle; /* if 0, the flash is read normally */
>      int bypass;
>      int ro;
>      uint8_t cmd;
> -    uint8_t status;
> +    uint64_t status;
>      /* FIXME: implement array device properties */
>      uint16_t ident0;
>      uint16_t ident1;
> @@ -103,16 +106,17 @@ struct PFlashCFI02 {
>   */
>  static inline void toggle_dq7(PFlashCFI02 *pfl)
>  {
> -    pfl->status ^= 0x80;
> +    pfl->status ^= pfl->interleave_multiplier * 0x80;
>  }
>  
>  /*
>   * Set status bit DQ7 to bit 7 of value.
>   */
> -static inline void set_dq7(PFlashCFI02 *pfl, uint8_t value)
> +static inline void set_dq7(PFlashCFI02 *pfl, uint64_t value)
>  {
> -    pfl->status &= 0x7F;
> -    pfl->status |= value & 0x80;
> +    uint64_t mask = pfl->interleave_multiplier * 0x80;
> +    pfl->status &= ~mask;
> +    pfl->status |= value & mask;
>  }
>  
>  /*
> @@ -120,7 +124,7 @@ static inline void set_dq7(PFlashCFI02 *pfl, uint8_t value)
>   */
>  static inline void toggle_dq6(PFlashCFI02 *pfl)
>  {
> -    pfl->status ^= 0x40;
> +    pfl->status ^= pfl->interleave_multiplier * 0x40;
>  }
>  
>  /*
> @@ -188,7 +192,6 @@ static uint64_t pflash_data_read(PFlashCFI02 *pfl, hwaddr offset,
>  static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width)
>  {
>      PFlashCFI02 *pfl = opaque;
> -    hwaddr boff;
>      uint64_t ret;
>  
>      ret = -1;
> @@ -198,12 +201,10 @@ static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width)
>          ++pfl->read_counter > PFLASH_LAZY_ROMD_THRESHOLD) {
>          pflash_register_memory(pfl, 1);
>      }
> -    offset &= pfl->chip_len - 1;
> -    boff = offset & 0xFF;
> -    if (pfl->width == 2)
> -        boff = boff >> 1;
> -    else if (pfl->width == 4)
> -        boff = boff >> 2;
> +    /* Mask by the total length of the chip to account for alias mappings. */
> +    offset &= pfl->total_len - 1;
> +    hwaddr device_addr = offset >> pfl->device_shift;
> +
>      switch (pfl->cmd) {
>      default:
>          /* This should never happen : reset state & treat it as a read*/
> @@ -215,29 +216,32 @@ static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width)
>          /* We accept reads during second unlock sequence... */
>      case 0x00:
>          /* Flash area read */
> -        ret = pflash_data_read(pfl, offset, width);
> -        break;
> +        return pflash_data_read(pfl, offset, width);
>      case 0x90:
>          /* flash ID read */
> -        switch (boff) {
> +        switch (device_addr & 0xFF) {
>          case 0x00:
> +            ret = pfl->ident0;
> +            break;
>          case 0x01:
> -            ret = boff & 0x01 ? pfl->ident1 : pfl->ident0;
> +            ret = pfl->ident1;
>              break;
>          case 0x02:
>              ret = 0x00; /* Pretend all sectors are unprotected */
>              break;
>          case 0x0E:
>          case 0x0F:
> -            ret = boff & 0x01 ? pfl->ident3 : pfl->ident2;
> +            ret = device_addr & 0x01 ? pfl->ident3 : pfl->ident2;
>              if (ret != (uint8_t)-1) {
>                  break;
>              }
>              /* Fall through to data read. */
>          default:
> -            ret = pflash_data_read(pfl, offset, width);
> +            return pflash_data_read(pfl, offset, width);
>          }
> -        DPRINTF("%s: ID " TARGET_FMT_plx " %" PRIx64 "\n", __func__, boff, ret);
> +        ret *= pfl->interleave_multiplier;
> +        DPRINTF("%s: ID " TARGET_FMT_plx " %" PRIx64 "\n",
> +                __func__, device_addr & 0xFF, ret);
>          break;
>      case 0xA0:
>      case 0x10:
> @@ -250,8 +254,8 @@ static uint64_t pflash_read(void *opaque, hwaddr offset, unsigned int width)
>          break;
>      case 0x98:
>          /* CFI query mode */
> -        if (boff < sizeof(pfl->cfi_table)) {
> -            ret = pfl->cfi_table[boff];
> +        if (device_addr < sizeof(pfl->cfi_table)) {
> +            ret = pfl->interleave_multiplier * pfl->cfi_table[device_addr];
>          } else {
>              ret = 0;
>          }
> @@ -279,30 +283,36 @@ 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;
>  
>      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;
> +    if (pfl->cmd != 0xA0) {
> +        if (value != pfl->interleave_multiplier * cmd) {
> +            DPRINTF("%s: cmd 0x%02x not sent to all devices: expected="
> +                    "0x%0*" PRIx64 " actual=0x%0*" PRIx64 "\n",
> +                    __func__, cmd,
> +                    pfl->bank_width * 2, pfl->interleave_multiplier * cmd,
> +                    pfl->bank_width * 2, value);
> +        }
> +
> +        if (cmd == 0xF0) {
> +            goto reset_flash;
> +        }
>      }
> +
>      trace_pflash_write(offset, value, width, pfl->wcycle);
> -    offset &= pfl->chip_len - 1;
> -
> -    DPRINTF("%s: offset " TARGET_FMT_plx " %08" PRIx64 " %d\n", __func__,
> -            offset, value, width);
> -    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;
> +
> +    /* Mask by the total length of the chip to account for alias mappings. */
> +    offset &= pfl->total_len - 1;
> +
> +    DPRINTF("%s: offset " TARGET_FMT_plx " 0x%0*" PRIx64 "\n",
> +            __func__, offset, width * 2, value);
> +
> +    hwaddr device_addr = (offset >> pfl->device_shift);
> +    /* Address bits A11 and greater are don't cares for most commands. */
> +    unsigned int masked_addr = device_addr & 0x7FF;
> +
>      switch (pfl->wcycle) {
>      case 0:
>          /* Set the device in I/O access mode if required */
> @@ -311,16 +321,16 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
>          pfl->read_counter = 0;
>          /* We're in read mode */
>      check_unlock0:
> -        if (boff == 0x55 && cmd == 0x98) {
> +        if (masked_addr == 0x55 && cmd == 0x98) {
>          enter_CFI_mode:
>              /* Enter CFI query mode */
>              pfl->wcycle = WCYCLE_CFI;
>              pfl->cmd = 0x98;
>              return;
>          }
> -        if (boff != pfl->unlock_addr0 || cmd != 0xAA) {
> -            DPRINTF("%s: unlock0 failed " TARGET_FMT_plx " %02x %04x\n",
> -                    __func__, boff, cmd, pfl->unlock_addr0);
> +        if (masked_addr != pfl->unlock_addr0 || cmd != 0xAA) {
> +            DPRINTF("%s: unlock0 failed %04x %02x %04x\n",
> +                    __func__, masked_addr, cmd, pfl->unlock_addr0);
>              goto reset_flash;
>          }
>          DPRINTF("%s: unlock sequence started\n", __func__);
> @@ -328,18 +338,18 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
>      case 1:
>          /* We started an unlock sequence */
>      check_unlock1:
> -        if (boff != pfl->unlock_addr1 || cmd != 0x55) {
> -            DPRINTF("%s: unlock1 failed " TARGET_FMT_plx " %02x\n", __func__,
> -                    boff, cmd);
> +        if (masked_addr != pfl->unlock_addr1 || cmd != 0x55) {
> +            DPRINTF("%s: unlock1 failed %03x %02x\n", __func__,
> +                    masked_addr, cmd);
>              goto reset_flash;
>          }
>          DPRINTF("%s: unlock sequence done\n", __func__);
>          break;
>      case 2:
>          /* We finished an unlock sequence */
> -        if (!pfl->bypass && boff != pfl->unlock_addr0) {
> -            DPRINTF("%s: command failed " TARGET_FMT_plx " %02x\n", __func__,
> -                    boff, cmd);
> +        if (!pfl->bypass && masked_addr != pfl->unlock_addr0) {
> +            DPRINTF("%s: command failed %03x %02x\n", __func__,
> +                    masked_addr, cmd);
>              goto reset_flash;
>          }
>          switch (cmd) {
> @@ -390,8 +400,9 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
>                  goto reset_flash;
>              }
>              /* We can enter CFI query mode from autoselect mode */
> -            if (boff == 0x55 && cmd == 0x98)
> +            if (masked_addr == 0x55 && cmd == 0x98) {
>                  goto enter_CFI_mode;
> +            }
>              /* No break here */
>          default:
>              DPRINTF("%s: invalid write for command %02x\n",
> @@ -416,7 +427,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
>      case 5:
>          switch (cmd) {
>          case 0x10:
> -            if (boff != pfl->unlock_addr0) {
> +            if (masked_addr != pfl->unlock_addr0) {
>                  DPRINTF("%s: chip erase: invalid address " TARGET_FMT_plx "\n",
>                          __func__, offset);
>                  goto reset_flash;
> @@ -424,8 +435,8 @@ 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);
> -                pflash_update(pfl, 0, pfl->chip_len);
> +                memset(pfl->storage, 0xFF, pfl->total_len);
> +                pflash_update(pfl, 0, pfl->total_len);
>              }
>              set_dq7(pfl, 0x00);
>              /* Let's wait 5 seconds before chip erase is done */
> @@ -521,22 +532,132 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
>          return;
>      }
>  
> +    if (pfl->bank_width == 0) {
> +        error_setg(errp, "attribute \"width\" not specified or zero.");
> +        return;
> +    }
> +
> +    /*
> +     * device-width defaults to width and max-device-width defaults to
> +     * device-width. Check that the device-width and max-device-width
> +     * configurations are supported.
> +     */
> +    if (pfl->device_width == 0) {
> +        pfl->device_width = pfl->bank_width;
> +    }
> +    if (pfl->max_device_width == 0) {
> +        pfl->max_device_width = pfl->device_width;
> +    }
> +    if (pfl->bank_width % pfl->device_width != 0) {
> +        error_setg(errp,
> +                   "attribute \"width\" (%u) not a multiple of attribute "
> +                   "\"device-width\" (%u).",
> +                   pfl->bank_width, pfl->device_width);
> +        return;
> +    }
> +
> +    /*
> +     * Writing commands to the flash device and reading CFI responses or
> +     * status values requires transforming a QEMU device offset into a
> +     * flash device address given in terms of the device's maximum width. We
> +     * can do this by shifting a QEMU device offset right a constant number of
> +     * bits depending on the bank_width, device_width, and max_device_width.
> +     *
> +     * num_devices = bank_width / device_width is the number of interleaved
> +     * flash devices. To compute a device byte address, we need to divide
> +     * offset by num_devices (equivalently shift right by log2(num_devices)).
> +     * To turn a device byte address into a device word address, we need to
> +     * divide by max_device_width (equivalently shift right by
> +     * log2(max_device_width)).
> +     *
> +     * In tabular form.
> +     * ==================================================================
> +     * bank_width   device_width    max_device_width    num_devices shift
> +     * ------------------------------------------------------------------
> +     * 1            1               1                   1           0
> +     * 1            1               2                   1           1
> +     * 2            1               1                   2           1
> +     * 2            1               2                   2           2
> +     * 2            2               2                   1           1
> +     * 4            1               1                   4           2
> +     * 4            1               2                   4           3
> +     * 4            1               4                   4           4
> +     * 4            2               2                   2           2
> +     * 4            2               4                   2           3
> +     * 4            4               4                   1           2
> +     * ==================================================================
> +     */
> +    pfl->device_shift = ctz32(pfl->bank_width) - ctz32(pfl->device_width) +
> +                        ctz32(pfl->max_device_width);
> +    pfl->interleave_multiplier = 0;
> +    for (unsigned int shift = 0; shift < pfl->bank_width;
> +         shift += pfl->device_width) {
> +        pfl->interleave_multiplier |= 1 << (shift * 8);
> +    }
> +
> +    uint16_t device_interface_code;
> +    if (pfl->max_device_width == 1 && pfl->device_width == 1) {
> +        device_interface_code = 0; /* x8 only. */
> +    } else if (pfl->max_device_width == 2 &&
> +               (pfl->device_width == 1 || pfl->device_width == 2)) {
> +        /* XXX: Some devices only support x16, this code doesn't model them. */
> +        device_interface_code = 2; /* Supports x8 or x16. */
> +    } else if (pfl->max_device_width == 4 && pfl->device_width == 1) {
> +        /*
> +         * XXX: this is x32-only. The standards I've seen don't specify a value
> +         * for x8/x32 but do mention them.
> +         */
> +        device_interface_code = 3; /* x32 only. */
> +    } else if (pfl->max_device_width == 4 &&
> +               (pfl->device_width == 2 || pfl->device_width == 4)) {
> +        device_interface_code = 4; /* Supports x16 or x32. */
> +    } else {
> +        error_setg(errp,
> +                   "unsupported configuration: \"device-width\"=%u "
> +                   "\"max-device-width\"=%u.",
> +                   pfl->device_width, pfl->max_device_width);
> +        return;
> +    }
> +
> +    pfl->total_len = pfl->sector_len * pfl->nb_blocs;
> +
> +    /*
> +     * If the flash is not a power of 2, then the code for handling multiple
> +     * mappings will not work correctly.
> +     */
> +    if (!is_power_of_2(pfl->total_len)) {
> +        error_setg(errp, "total pflash length (%" PRIx64 ") not a power of 2.",
> +                   pfl->total_len);
> +        return;
> +    }
> +
> +    int num_devices = pfl->bank_width / pfl->device_width;
> +    uint64_t sector_len_per_device = pfl->sector_len / num_devices;
> +    uint64_t device_len = sector_len_per_device * pfl->nb_blocs;
> +
> +    if (sector_len_per_device & 0xff || sector_len_per_device >= (1 << 24)) {
> +        error_setg(errp,
> +                   "unsupported configuration: sector length per device = "
> +                   "%" PRIx64 ".",
> +                   sector_len_per_device);
> +        return;
> +    }
> +
> +    memory_region_init_rom_device(&pfl->orig_mem, OBJECT(pfl),
> +                                  &pflash_cfi02_ops, pfl, pfl->name,
> +                                  pfl->total_len, &local_err);
>      /* Only 11 bits are used in the comparison. */
>      pfl->unlock_addr0 &= 0x7FF;
>      pfl->unlock_addr1 &= 0x7FF;
>  
>      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);
>      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;
> @@ -566,6 +687,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';
> @@ -591,8 +713,8 @@ 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 */
> -    pfl->cfi_table[0x1F] = 0x07;
> +    /* Timeout per single byte/word write (16 us) */
> +    pfl->cfi_table[0x1F] = 0x04;
>      /* Timeout for min size buffer write (NA) */
>      pfl->cfi_table[0x20] = 0x00;
>      /* Typical timeout for block erase (512 ms) */
> @@ -608,13 +730,13 @@ 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);
> -    /* Flash device interface (8 & 16 bits) */
> -    pfl->cfi_table[0x28] = 0x02;
> -    pfl->cfi_table[0x29] = 0x00;
> +    pfl->cfi_table[0x27] = ctz32(device_len);
> +    /* Flash device interface  */
> +    pfl->cfi_table[0x28] = device_interface_code;
> +    pfl->cfi_table[0x29] = device_interface_code >> 8;
>      /* Max number of bytes in multi-bytes write */
>      /* XXX: disable buffered write as it's not supported */
> -    //    pfl->cfi_table[0x2A] = 0x05;
> +    /*    pfl->cfi_table[0x2A] = 0x05; */
>      pfl->cfi_table[0x2A] = 0x00;
>      pfl->cfi_table[0x2B] = 0x00;
>      /* Number of erase block regions (uniform) */
> @@ -622,8 +744,8 @@ static void pflash_cfi02_realize(DeviceState *dev, Error **errp)
>      /* 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;
> +    pfl->cfi_table[0x2F] = sector_len_per_device >> 8;
> +    pfl->cfi_table[0x30] = sector_len_per_device >> 16;
>  
>      /* Extended */
>      pfl->cfi_table[0x31] = 'P';
> @@ -648,7 +770,9 @@ 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_UINT8("width", PFlashCFI02, width, 0),
> +    DEFINE_PROP_UINT8("width", PFlashCFI02, bank_width, 0),
> +    DEFINE_PROP_UINT8("device-width", PFlashCFI02, device_width, 0),
> +    DEFINE_PROP_UINT8("max-device-width", PFlashCFI02, max_device_width, 0),
>      DEFINE_PROP_UINT8("mappings", PFlashCFI02, mappings, 0),
>      DEFINE_PROP_UINT8("big-endian", PFlashCFI02, be, 0),
>      DEFINE_PROP_UINT16("id0", PFlashCFI02, ident0, 0),
> @@ -696,7 +820,7 @@ PFlashCFI02 *pflash_cfi02_register(hwaddr base,
>                                     hwaddr size,
>                                     BlockBackend *blk,
>                                     uint32_t sector_len,
> -                                   int nb_mappings, int width,
> +                                   int nb_mappings, int bank_width,
>                                     uint16_t id0, uint16_t id1,
>                                     uint16_t id2, uint16_t id3,
>                                     uint16_t unlock_addr0,
> @@ -711,7 +835,7 @@ PFlashCFI02 *pflash_cfi02_register(hwaddr base,
>      assert(size % sector_len == 0);
>      qdev_prop_set_uint32(dev, "num-blocks", size / sector_len);
>      qdev_prop_set_uint32(dev, "sector-length", sector_len);
> -    qdev_prop_set_uint8(dev, "width", width);
> +    qdev_prop_set_uint8(dev, "width", bank_width);
>      qdev_prop_set_uint8(dev, "mappings", nb_mappings);
>      qdev_prop_set_uint8(dev, "big-endian", !!be);
>      qdev_prop_set_uint16(dev, "id0", id0);
> diff --git a/tests/pflash-cfi02-test.c b/tests/pflash-cfi02-test.c
> index ea5f8b2648..a1be26da73 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,381 @@
>  #define UNLOCK_BYPASS_CMD 0x20
>  #define UNLOCK_BYPASS_RESET_CMD 0x00
>  
> +typedef struct {
> +    int bank_width;
> +    int device_width;
> +    int max_device_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.device_width == 0) {
> +        ret.device_width = ret.bank_width;
> +    }
> +    if (ret.max_device_width == 0) {
> +        ret.max_device_width = ret.device_width;
> +    }
> +    return ret;
> +}
> +
> +/*
> + * 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)
> +{
> +    if (c->device_width == 8) {
> +        return (uint64_t)-1;
> +    }
> +    return (1ULL << (c->device_width * 8)) - 1ULL;
> +}
> +
> +/*
> + * Return a bit mask exactly as long as the bank_width.
> + */
> +static inline uint64_t bank_mask(const FlashConfig *c)
> +{
> +    if (c->bank_width == 8) {
> +        return (uint64_t)-1;
> +    }
> +    return (1ULL << (c->bank_width * 8)) - 1ULL;
> +}
> +
> +static inline void flash_write(const FlashConfig *c, uint64_t byte_addr,
> +                               uint64_t data)
> +{
> +    /* 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 inline uint64_t flash_read(const FlashConfig *c, uint64_t byte_addr)
> +{
> +    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.)
> +     *
> +     * Interleaving flash chips use the least significant bits of a byte
> +     * address to refer to data from the individual chips. Two interleaved x8
> +     * devices would use command addresses 0xAAA and 0x554. Two interleaved
> +     * x16 devices would use 0x1554 and 0xAA8.
> +     *
> +     * More exotic configurations are possible. Two interleaved x8/x16 devices
> +     * in x8 mode would also use 0x1554 and 0xAA8.
> +     *
> +     * In general we need to multiply an address by the number of devices,
> +     * which is bank_width / device_width, and multiply that by the maximum
> +     * device width.
> +     */
> +    int num_devices = c->bank_width / c->device_width;
> +    return flash_addr.addr * (num_devices * c->max_device_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);
> +    for (int i = c->device_width; i < c->bank_width; i += c->device_width) {
> +        data |= data << (c->device_width * 8);
> +    }
> +    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 uint16_t flash_read(uint64_t byte_addr)
> +static inline uint64_t flash_query_1(const FlashConfig *c, faddr query_addr)
>  {
> -    return qtest_readw(global_qtest, BASE_ADDR + byte_addr);
> +    return flash_query(c, query_addr) & device_mask(c);
>  }
>  
> -static void unlock(void)
> +static void unlock(const FlashConfig *c)
>  {
> -    flash_write(UNLOCK0_ADDR, UNLOCK0_CMD);
> -    flash_write(UNLOCK1_ADDR, UNLOCK1_CMD);
> +    flash_cmd(c, UNLOCK0_ADDR, UNLOCK0_CMD);
> +    flash_cmd(c, UNLOCK1_ADDR, UNLOCK1_CMD);
>  }
>  
> -static void reset(void)
> +static void reset(const FlashConfig *c)
>  {
> -    flash_write(0, RESET_CMD);
> +    flash_cmd(c, FLASH_ADDR(0), RESET_CMD);
>  }
>  
> -static void sector_erase(uint64_t byte_addr)
> +static void sector_erase(const FlashConfig *c, uint64_t byte_addr)
>  {
> -    unlock();
> -    flash_write(UNLOCK0_ADDR, 0x80);
> -    unlock();
> -    flash_write(byte_addr, SECTOR_ERASE_CMD);
> +    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(uint64_t byte_addr)
> +static void wait_for_completion(const FlashConfig *c, 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)) {
> +    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. */
> -        clock_step_next();
> +        qtest_clock_step_next(c->qtest);
>          /* Ensure that DQ6 has stopped toggling. */
> -        g_assert_cmpint(flash_read(byte_addr), ==, flash_read(byte_addr));
> +        g_assert_cmpint(flash_read(c, byte_addr), ==, flash_read(c, byte_addr));
>      }
>  }
>  
> -static void bypass_program(uint64_t byte_addr, uint16_t data)
> +static void bypass_program(const FlashConfig *c, uint64_t byte_addr,
> +                           uint16_t data)
>  {
> -    flash_write(UNLOCK0_ADDR, PROGRAM_CMD);
> -    flash_write(byte_addr, 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)
> +/*
> + * Check that the device interface code dic is appropriate for the given
> + * width.
> + *
> + * Device interface codes are specified in JEP173.
> + */
> +static bool device_supports_width(uint16_t dic, int width)
>  {
> -    global_qtest = qtest_initf("-M musicpal,accel=qtest "
> -                               "-drive if=pflash,file=%s,format=raw,copy-on-read",
> -                               image_path);
> +    switch (width) {
> +    case 1:
> +        /*
> +         * x8-only, x8/x16, or x32
> +         * XXX: Currently we use dic = 3 for an x8/x32 device even though
> +         * that's only for x32. If there's a more appropriate value, both this
> +         * test and pflash-cfi02.c should be modified.
> +         */
> +        return dic == 0 || dic == 2 || dic == 3;
> +    case 2:
> +        /* x16-only, x8/x16, or x16/x32. */
> +        return dic == 1 || dic == 2 || dic == 4;
> +    case 4:
> +        /* x32-only or x16/x32. */
> +        return dic == 3 || dic == 4;
> +    }
> +    g_test_incomplete("Device width test not supported");
> +    return true;
> +}
> +
> +static void test_flash(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"
> +                        " -global driver=cfi.pflash02,"
> +                        "property=device-width,value=%d"
> +                        " -global driver=cfi.pflash02,"
> +                        "property=max-device-width,value=%d",
> +                        image_path,
> +                        config->device_width,
> +                        config->max_device_width);
> +    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_cmpint(flash_read(FLASH_WIDTH * 0x0000), ==, 0x00BF);
> -    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x0001), ==, 0x236D);
> -    reset();
> +    unlock(c);
> +    flash_cmd(c, UNLOCK0_ADDR, AUTOSELECT_CMD);
> +    g_assert_cmpint(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF));
> +    if (c->device_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_cmpint(flash_query(c, FLASH_ADDR(1)), ==,
> +                        replicate(c, 0x236D));
> +    }
> +    reset(c);
>  
>      /* Check the erase blocks. */
> -    flash_write(CFI_ADDR, CFI_CMD);
> -    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x10), ==, 'Q');
> -    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x11), ==, 'R');
> -    g_assert_cmpint(flash_read(FLASH_WIDTH * 0x12), ==, 'Y');
> +    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'));
> +
>      /* Num erase regions. */
> -    g_assert_cmpint(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();
> +    g_assert_cmpint(flash_query_1(c, FLASH_ADDR(0x2C)), >=, 1);
>  
> +    /* Check device length. */
> +    uint32_t device_len = 1 << flash_query_1(c, FLASH_ADDR(0x27));
> +    g_assert_cmpint(device_len * (c->bank_width / c->device_width), ==,
> +                    FLASH_SIZE);
> +
> +    /* Check nb_sectors * sector_len is device_len. */
> +    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);
> +    g_assert_cmpint(nb_sectors * sector_len, ==, device_len);
> +
> +    /* Check the device interface code supports the width and max width. */
> +    uint16_t device_interface_code = flash_query_1(c, FLASH_ADDR(0x28)) +
> +                                     (flash_query_1(c, FLASH_ADDR(0x29)) << 8);
> +    g_assert_true(device_supports_width(device_interface_code,
> +                                        c->device_width));
> +    g_assert_true(device_supports_width(device_interface_code,
> +                                        c->max_device_width));
> +    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_cmpint(status0 & 0x80, ==, 0);
> -        uint16_t status1 = flash_read(byte_addr);
> +        g_assert_cmpint(status0 & dq7, ==, 0);
> +        uint64_t status1 = flash_read(c, byte_addr);
>          /* DQ6 toggles during an erase. */
> -        g_assert_cmpint(status0 & 0x40, !=, status1 & 0x40);
> +        g_assert_cmpint(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_cmpint(flash_read(byte_addr), ==, flash_read(byte_addr));
> +        g_assert_cmpint(flash_read(c, byte_addr), ==, flash_read(c, byte_addr));
>          /* Now the data should be valid. */
> -        g_assert_cmpint(flash_read(byte_addr), ==, 0xFFFF);
> +        g_assert_cmpint(flash_read(c, byte_addr), ==, bank_mask(c));
>  
>          /* Program a bit pattern. */
> -        program(byte_addr, 0x5555);
> -        g_assert_cmpint(flash_read(byte_addr), ==, 0x5555);
> -        program(byte_addr, 0xAA55);
> -        g_assert_cmpint(flash_read(byte_addr), ==, 0x0055);
> +        program(c, byte_addr, 0x55);
> +        g_assert_cmpint(flash_read(c, byte_addr) & 0xFF, ==, 0x55);
> +        program(c, byte_addr, 0xA5);
> +        g_assert_cmpint(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_cmpint(status0 & 0x80, ==, 0);
> -    uint16_t status1 = flash_read(0);
> +    g_assert_cmpint(status0 & dq7, ==, 0);
> +    uint64_t status1 = flash_read(c, 0);
>      /* DQ6 toggles during an erase. */
> -    g_assert_cmpint(status0 & 0x40, !=, status1 & 0x40);
> +    g_assert_cmpint(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_cmpint(flash_read(0), ==, flash_read(0));
> +    g_assert_cmpint(flash_read(c, 0), ==, flash_read(c, 0));
>      /* Now the data should be valid. */
> -    g_assert_cmpint(flash_read(0), ==, 0xFFFF);
> +
> +    for (uint32_t i = 0; i < nb_sectors; ++i) {
> +        uint64_t byte_addr = i * sector_len;
> +        g_assert_cmpint(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_cmpint(flash_read(0), ==, 0x0123);
> -    g_assert_cmpint(flash_read(2), ==, 0x4567);
> -    g_assert_cmpint(flash_read(4), ==, 0x89AB);
> -    g_assert_cmpint(flash_read(6), ==, 0xCDEF);
> -    g_assert_cmpint(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_cmpint(flash_read(c, 0 * c->bank_width), ==, 0x01);
> +    g_assert_cmpint(flash_read(c, 1 * c->bank_width), ==, 0x23);
> +    g_assert_cmpint(flash_read(c, 2 * c->bank_width), ==, 0x45);
> +    g_assert_cmpint(flash_read(c, 3 * c->bank_width), ==, 0x67);
> +    g_assert_cmpint(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_cmpint(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 +423,61 @@ 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,
> +        .device_width = 2,
> +        .max_device_width = 2,
> +    },
> +    /* Implicitly one x16 device. */
> +    {
> +        .bank_width = 2,
> +        .device_width = 0,
> +        .max_device_width = 0,
> +    },
> +    /* Implicitly one x16 device. */
> +    {
> +        .bank_width = 2,
> +        .device_width = 2,
> +        .max_device_width = 0,
> +    },
> +    /* Interleave two x8 devices. */
> +    {
> +        .bank_width = 2,
> +        .device_width = 1,
> +        .max_device_width = 1,
> +    },
> +    /* Interleave two implicit x8 devices. */
> +    {
> +        .bank_width = 2,
> +        .device_width = 1,
> +        .max_device_width = 0,
> +    },
> +    /* Interleave two x8/x16 devices in x8 mode. */
> +    {
> +        .bank_width = 2,
> +        .device_width = 1,
> +        .max_device_width = 2,
> +    },
> +    /* One x16/x32 device in x16 mode. */
> +    {
> +        .bank_width = 2,
> +        .device_width = 2,
> +        .max_device_width = 4,
> +    },
> +    /* Two x8/x32 devices in x8 mode; I am not sure if such devices exist. */
> +    {
> +        .bank_width = 2,
> +        .device_width = 1,
> +        .max_device_width = 4,
> +    },
> +};
> +
>  int main(int argc, char **argv)
>  {
>      int fd = mkstemp(image_path);
> @@ -214,7 +486,7 @@ 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);
> @@ -226,7 +498,17 @@ int main(int argc, char **argv)
>  
>      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-%d-%d",
> +                                     config->bank_width,
> +                                     config->device_width,
> +                                     config->max_device_width);
> +        qtest_add_data_func(path, config, test_flash);
> +        g_free(path);
> +    }
>      int result = g_test_run();
>      cleanup(NULL);
>      return result;
> 


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Qemu-devel] [PATCH v4 04/10] block/pflash_cfi02: Implement intereleaved flash devices
  2019-06-22 12:25   ` Philippe Mathieu-Daudé
@ 2019-06-24 19:05     ` Philippe Mathieu-Daudé
  2019-06-24 19:36       ` Stephen Checkoway
  0 siblings, 1 reply; 31+ messages in thread
From: Philippe Mathieu-Daudé @ 2019-06-24 19:05 UTC (permalink / raw)
  To: Stephen Checkoway, QEMU Developers, Thomas Huth,
	Philippe Mathieu-Daudé,
	Kevin Wolf, Max Reitz, open list:Block layer core,
	Markus Armbruster, Laszlo Ersek, Laurent Vivier, Paolo Bonzini,
	Peter Maydell

On 6/22/19 2:25 PM, Philippe Mathieu-Daudé wrote:
> Hi Stephen,
> 
> This series haven't fall through the cracks, however it is taking me
> longer than expected to review it.
> 
> On 4/26/19 6:26 PM, Stephen Checkoway wrote:
>> It's common for multiple narrow flash chips to be hooked up in parallel
>> to support wider buses. For example, four 8-bit wide flash chips (x8)
>> may be combined in parallel to produce a 32-bit wide device. Similarly,
>> two 16-bit wide chips (x16) may be combined.
>>
>> This commit introduces `device-width` and `max-device-width` properties,
>> similar to pflash_cfi01, with the following meanings:
>> - `width`: The width of the logical, qemu device (same as before);
>> - `device-width`: The width of an individual flash chip, defaulting to
>>   `width`; and
>> - `max-device-width`: The maximum width of an individual flash chip,
>>   defaulting to `device-width`.
>>
>> Nothing needs to change to support reading such interleaved devices but
>> commands (e.g., erase and programming) must be sent to all devices at
>> the same time or else the various chips will be in different states.
> 
> After some thoughts on this, I'd rather we model how hardware manage
> interleaved devices: do it at the bus level, and instanciate N devices
> in an interleaved config.
> I believe that would drastically reduce this device complexity, and we
> would match the real internal state machine.
> Also this could be reused by other parallel devices used in a such config.
> 
>> For example, a 4-byte wide logical device can be composed of four x8/x16
>> devices in x8 mode. That is, each device supports both x8 or x16 and
>> they're being used in the byte, rather than word, mode. This
>> configuration would have `width=4`, `device-width=1`, and
>> `max-device-width=2`.
> 
> 
> I'm thinking of this draft:
> 
> FlashDevice # x8
>   MemoryRegionOps
>     .valid.max_access_size = 1
> 
> FlashDevice # x16
>   MemoryRegionOps
>     .valid.min_access_size = 2
>     .valid.max_access_size = 2
> 
> FlashDevice # x8/x16
>   MemoryRegionOps
>     .valid.min_access_size = 1
>     .valid.max_access_size = 2
> 
> We might use .impl.min_access_size = 2 and consider all NOR flash using
> 16-bit words internally.
>     .impl.max_access_size = 2 is implicit.
> 
> So for you example we'd instanciate one:
> 
> InterleaverDevice
>   Property
>     .bus_width = 4 # 4-byte wide logical device, `width=4`
>     .device_width = 1 # `device-width=1`
>   MemoryRegionOps
>     .valid.max_access_size = .bus_width # 4, set at realize()
>     .impl.max_access_size = .device_width # 1, set at realize()
> 
> Then instanciate 4 pflash devices, and link them to the interleaver
> using object_property_set_link().
> 
> typedef struct {
>     SysBusDevice parent_obj;
>     MemoryRegion iomem;
>     char *name;
>     /*
>      * On a 64-bit wide bus we can have at most
>      * 8 devices in 8-bit access mode.
>      */
>     MemoryRegion device[8];
>     unsigned device_count;
>     unsigned device_index_mask;
>     /* Properties */
>     unsigned bus_width;
>     unsigned device_width;
> } InterleaverDeviceState;
> 
> static Property interleaver_properties[] = {
>     DEFINE_PROP_LINK("device[0]", InterleaverDeviceState,
>                      device[0],
>                      TYPE_MEMORY_REGION, MemoryRegion *),
>     ...
>     DEFINE_PROP_LINK("device[7]", InterleaverDeviceState,
>                      device[7],
>                      TYPE_MEMORY_REGION, MemoryRegion *),
>     DEFINE_PROP_END_OF_LIST(),
> };
> 
> Then previous to call InterleaverDevice.realize():
> 
> In the board realize():
> 
> 
>     for (i = 0; i < interleaved_devices; i++) {
>         pflash[i] = create_pflash(...);
>         ...
>     }
> 
>     ild = ... create InterleaverDevice ...
>     for (i = 0; i < interleaved_devices; i++) {
>         char *propname = g_strdup_printf("device[%u]", i);
> 
> 
>         object_property_set_link(OBJECT(&ild->device[i]),
>                                  OBJECT(pflash[i]),
>                                  propname, &err);
>         ...
>     }
> 
> Finally,
> 
> static void interleaved_realize(DeviceState *dev, Error **errp)
> {
>     InterleaverDeviceState *s = INTERLEAVER_DEVICE(opaque);
> 
>     s->device_count = s->bus_width / s->device_width;
>     s->device_index_mask = ~(s->device_count - 1);
>     ...
> }
> 
> static void interleaved_write(void *opaque, hwaddr offset,
>                               uint64_t value, unsigned size)
> {
>     InterleaverDeviceState *s = INTERLEAVER_DEVICE(opaque);
>     MemoryRegion *mr;
> 
>     /*
>      * Since we set .impl.max_access_size = device_width,
>      * access_with_adjusted_size() always call this with
>      * size = device_width.
>      *
>      * Adjust the address (offset).
>      */
>     offset >>= size;
>     /* Access the N interleaved device */
>     mr = s->device[offset & s->device_index_mask];
>     memory_region_dispatch_write(mr, offset, &value, size,
>                                  MEMTXATTRS_UNSPECIFIED);
> }
> 
> I'll try a PoC.

So I have a PoC, but then realize I can not use the same flash dump...

I need to deinterleave is. This is easily fixed with few lines of
Python, then if I want to store/share the dump (aka 'backend storage') I
have to re-interleave it.

I wonder if it would be possible/easy to add a 'interleave' option to
blockdev to be able to have 2 pflash devices sharing the same backend...
Is it worthwhile? Kevin/Markus/Max any thought on this?

Thanks,

Phil.

>> In addition to commands being sent to all devices, guest firmware
>> expects the status and CFI queries to be replicated for each device.
>> (The one exception to the response replication is that each device gets
>> to report its own status bit DQ7 while programming because its value
>> depends on the value being programmed which will usually differ for each
>> device.)
>>
>> Testing is limited to 16-bit wide devices due to the current inability
>> to override the properties set by `pflash_cfi02_register`, but multiple
>> configurations are tested.
>>
>> Stop using global_qtest. Instead, package the qtest variable inside the
>> FlashConfig structure.
>>
>> Signed-off-by: Stephen Checkoway <stephen.checkoway@oberlin.edu>
>> Acked-by: Thomas Huth <thuth@redhat.com>
>> ---
>>  hw/block/pflash_cfi02.c   | 270 +++++++++++++++------
>>  tests/pflash-cfi02-test.c | 476 ++++++++++++++++++++++++++++++--------
>>  2 files changed, 576 insertions(+), 170 deletions(-)


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Qemu-devel] [PATCH v4 04/10] block/pflash_cfi02: Implement intereleaved flash devices
  2019-06-24 19:05     ` Philippe Mathieu-Daudé
@ 2019-06-24 19:36       ` Stephen Checkoway
  2019-06-25  8:32         ` Markus Armbruster
  0 siblings, 1 reply; 31+ messages in thread
From: Stephen Checkoway @ 2019-06-24 19:36 UTC (permalink / raw)
  To: Philippe Mathieu-Daudé
  Cc: Kevin Wolf, Laszlo Ersek, Thomas Huth, Stephen Checkoway,
	open list:Block layer core, Peter Maydell, Laurent Vivier,
	Markus Armbruster, QEMU Developers, Max Reitz, Paolo Bonzini,
	Philippe Mathieu-Daudé



> On Jun 24, 2019, at 12:05, Philippe Mathieu-Daudé <f4bug@amsat.org> wrote:
> 
>> On 6/22/19 2:25 PM, Philippe Mathieu-Daudé wrote:
>> Hi Stephen,
>> 
>> This series haven't fall through the cracks, however it is taking me
>> longer than expected to review it.
>> 
>>> On 4/26/19 6:26 PM, Stephen Checkoway wrote:
>>> It's common for multiple narrow flash chips to be hooked up in parallel
>>> to support wider buses. For example, four 8-bit wide flash chips (x8)
>>> may be combined in parallel to produce a 32-bit wide device. Similarly,
>>> two 16-bit wide chips (x16) may be combined.
>>> 
>>> This commit introduces `device-width` and `max-device-width` properties,
>>> similar to pflash_cfi01, with the following meanings:
>>> - `width`: The width of the logical, qemu device (same as before);
>>> - `device-width`: The width of an individual flash chip, defaulting to
>>>  `width`; and
>>> - `max-device-width`: The maximum width of an individual flash chip,
>>>  defaulting to `device-width`.
>>> 
>>> Nothing needs to change to support reading such interleaved devices but
>>> commands (e.g., erase and programming) must be sent to all devices at
>>> the same time or else the various chips will be in different states.
>> 
>> After some thoughts on this, I'd rather we model how hardware manage
>> interleaved devices: do it at the bus level, and instanciate N devices
>> in an interleaved config.
>> I believe that would drastically reduce this device complexity, and we
>> would match the real internal state machine.
>> Also this could be reused by other parallel devices used in a such config.
>> 
>>> For example, a 4-byte wide logical device can be composed of four x8/x16
>>> devices in x8 mode. That is, each device supports both x8 or x16 and
>>> they're being used in the byte, rather than word, mode. This
>>> configuration would have `width=4`, `device-width=1`, and
>>> `max-device-width=2`.
>> 
>> 
>> I'm thinking of this draft:
>> 
>> FlashDevice # x8
>>  MemoryRegionOps
>>    .valid.max_access_size = 1
>> 
>> FlashDevice # x16
>>  MemoryRegionOps
>>    .valid.min_access_size = 2
>>    .valid.max_access_size = 2
>> 
>> FlashDevice # x8/x16
>>  MemoryRegionOps
>>    .valid.min_access_size = 1
>>    .valid.max_access_size = 2
>> 
>> We might use .impl.min_access_size = 2 and consider all NOR flash using
>> 16-bit words internally.
>>    .impl.max_access_size = 2 is implicit.
>> 
>> So for you example we'd instanciate one:
>> 
>> InterleaverDevice
>>  Property
>>    .bus_width = 4 # 4-byte wide logical device, `width=4`
>>    .device_width = 1 # `device-width=1`
>>  MemoryRegionOps
>>    .valid.max_access_size = .bus_width # 4, set at realize()
>>    .impl.max_access_size = .device_width # 1, set at realize()
>> 
>> Then instanciate 4 pflash devices, and link them to the interleaver
>> using object_property_set_link().
>> 
>> typedef struct {
>>    SysBusDevice parent_obj;
>>    MemoryRegion iomem;
>>    char *name;
>>    /*
>>     * On a 64-bit wide bus we can have at most
>>     * 8 devices in 8-bit access mode.
>>     */
>>    MemoryRegion device[8];
>>    unsigned device_count;
>>    unsigned device_index_mask;
>>    /* Properties */
>>    unsigned bus_width;
>>    unsigned device_width;
>> } InterleaverDeviceState;
>> 
>> static Property interleaver_properties[] = {
>>    DEFINE_PROP_LINK("device[0]", InterleaverDeviceState,
>>                     device[0],
>>                     TYPE_MEMORY_REGION, MemoryRegion *),
>>    ...
>>    DEFINE_PROP_LINK("device[7]", InterleaverDeviceState,
>>                     device[7],
>>                     TYPE_MEMORY_REGION, MemoryRegion *),
>>    DEFINE_PROP_END_OF_LIST(),
>> };
>> 
>> Then previous to call InterleaverDevice.realize():
>> 
>> In the board realize():
>> 
>> 
>>    for (i = 0; i < interleaved_devices; i++) {
>>        pflash[i] = create_pflash(...);
>>        ...
>>    }
>> 
>>    ild = ... create InterleaverDevice ...
>>    for (i = 0; i < interleaved_devices; i++) {
>>        char *propname = g_strdup_printf("device[%u]", i);
>> 
>> 
>>        object_property_set_link(OBJECT(&ild->device[i]),
>>                                 OBJECT(pflash[i]),
>>                                 propname, &err);
>>        ...
>>    }
>> 
>> Finally,
>> 
>> static void interleaved_realize(DeviceState *dev, Error **errp)
>> {
>>    InterleaverDeviceState *s = INTERLEAVER_DEVICE(opaque);
>> 
>>    s->device_count = s->bus_width / s->device_width;
>>    s->device_index_mask = ~(s->device_count - 1);
>>    ...
>> }
>> 
>> static void interleaved_write(void *opaque, hwaddr offset,
>>                              uint64_t value, unsigned size)
>> {
>>    InterleaverDeviceState *s = INTERLEAVER_DEVICE(opaque);
>>    MemoryRegion *mr;
>> 
>>    /*
>>     * Since we set .impl.max_access_size = device_width,
>>     * access_with_adjusted_size() always call this with
>>     * size = device_width.
>>     *
>>     * Adjust the address (offset).
>>     */
>>    offset >>= size;
>>    /* Access the N interleaved device */
>>    mr = s->device[offset & s->device_index_mask];
>>    memory_region_dispatch_write(mr, offset, &value, size,
>>                                 MEMTXATTRS_UNSPECIFIED);
>> }
>> 
>> I'll try a PoC.
> 
> So I have a PoC, but then realize I can not use the same flash dump...
> 
> I need to deinterleave is. This is easily fixed with few lines of
> Python, then if I want to store/share the dump (aka 'backend storage') I
> have to re-interleave it.
> 
> I wonder if it would be possible/easy to add a 'interleave' option to
> blockdev to be able to have 2 pflash devices sharing the same backend...
> Is it worthwhile? Kevin/Markus/Max any thought on this?

Hi Phil,

Sorry for the delay, I’ve been traveling.

I considered something like this approach and I think it could work. Ultimately, I opted not to go that route for a few reasons:
- duplicated CFI tables and other state waste (a small amount of) memory when the flash chips are the same (the usual case in my limited experience)
- it adds an extra layer of read/write calls plus recombining from/splitting into the component parts
- duplicated timers firing to walk the programming/erasing state machine forward for each chip
- the firmware or data stored in the chips is likely already interleaved necessitating either splitting it up before use or adding functionality to a lower layer to split it (as you’ve suggested here)

None of the above seem like a big deal separately or together but I didn’t find the advantages of this approach to be sufficiently compelling to justify it. Namely, it allows using a heterogeneous set of flash chips to implement a logical device.

Nevertheless, if that’s the route you think is best, I have no objections.

Cheers,

Steve

> 
> Thanks,
> 
> Phil.
> 
>>> In addition to commands being sent to all devices, guest firmware
>>> expects the status and CFI queries to be replicated for each device.
>>> (The one exception to the response replication is that each device gets
>>> to report its own status bit DQ7 while programming because its value
>>> depends on the value being programmed which will usually differ for each
>>> device.)
>>> 
>>> Testing is limited to 16-bit wide devices due to the current inability
>>> to override the properties set by `pflash_cfi02_register`, but multiple
>>> configurations are tested.
>>> 
>>> Stop using global_qtest. Instead, package the qtest variable inside the
>>> FlashConfig structure.
>>> 
>>> Signed-off-by: Stephen Checkoway <stephen.checkoway@oberlin.edu>
>>> Acked-by: Thomas Huth <thuth@redhat.com>
>>> ---
>>> hw/block/pflash_cfi02.c   | 270 +++++++++++++++------
>>> tests/pflash-cfi02-test.c | 476 ++++++++++++++++++++++++++++++--------
>>> 2 files changed, 576 insertions(+), 170 deletions(-)


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Qemu-devel] [PATCH v4 04/10] block/pflash_cfi02: Implement intereleaved flash devices
  2019-06-24 19:36       ` Stephen Checkoway
@ 2019-06-25  8:32         ` Markus Armbruster
  2019-06-25 16:41           ` Stephen Checkoway
  0 siblings, 1 reply; 31+ messages in thread
From: Markus Armbruster @ 2019-06-25  8:32 UTC (permalink / raw)
  To: Stephen Checkoway
  Cc: Kevin Wolf, Peter Maydell, Thomas Huth, Stephen Checkoway,
	open list:Block layer core, Laurent Vivier,
	Philippe Mathieu-Daudé, Philippe Mathieu-Daudé,
	QEMU Developers, Paolo Bonzini, Max Reitz, Laszlo Ersek

Stephen Checkoway <scheckow@oberlin.edu> writes:

>> On Jun 24, 2019, at 12:05, Philippe Mathieu-Daudé <f4bug@amsat.org> wrote:
>> 
>>> On 6/22/19 2:25 PM, Philippe Mathieu-Daudé wrote:
>>> Hi Stephen,
>>> 
>>> This series haven't fall through the cracks, however it is taking me
>>> longer than expected to review it.
>>> 
>>>> On 4/26/19 6:26 PM, Stephen Checkoway wrote:
>>>> It's common for multiple narrow flash chips to be hooked up in parallel
>>>> to support wider buses. For example, four 8-bit wide flash chips (x8)
>>>> may be combined in parallel to produce a 32-bit wide device. Similarly,
>>>> two 16-bit wide chips (x16) may be combined.
>>>> 
>>>> This commit introduces `device-width` and `max-device-width` properties,
>>>> similar to pflash_cfi01, with the following meanings:
>>>> - `width`: The width of the logical, qemu device (same as before);
>>>> - `device-width`: The width of an individual flash chip, defaulting to
>>>>  `width`; and
>>>> - `max-device-width`: The maximum width of an individual flash chip,
>>>>  defaulting to `device-width`.
>>>> 
>>>> Nothing needs to change to support reading such interleaved devices but
>>>> commands (e.g., erase and programming) must be sent to all devices at
>>>> the same time or else the various chips will be in different states.
>>> 
>>> After some thoughts on this, I'd rather we model how hardware manage
>>> interleaved devices: do it at the bus level, and instanciate N devices
>>> in an interleaved config.
>>> I believe that would drastically reduce this device complexity, and we
>>> would match the real internal state machine.
>>> Also this could be reused by other parallel devices used in a such config.
>>> 
>>>> For example, a 4-byte wide logical device can be composed of four x8/x16
>>>> devices in x8 mode. That is, each device supports both x8 or x16 and
>>>> they're being used in the byte, rather than word, mode. This
>>>> configuration would have `width=4`, `device-width=1`, and
>>>> `max-device-width=2`.
>>> 
>>> 
>>> I'm thinking of this draft:
>>> 
>>> FlashDevice # x8
>>>  MemoryRegionOps
>>>    .valid.max_access_size = 1
>>> 
>>> FlashDevice # x16
>>>  MemoryRegionOps
>>>    .valid.min_access_size = 2
>>>    .valid.max_access_size = 2
>>> 
>>> FlashDevice # x8/x16
>>>  MemoryRegionOps
>>>    .valid.min_access_size = 1
>>>    .valid.max_access_size = 2
>>> 
>>> We might use .impl.min_access_size = 2 and consider all NOR flash using
>>> 16-bit words internally.
>>>    .impl.max_access_size = 2 is implicit.
>>> 
>>> So for you example we'd instanciate one:
>>> 
>>> InterleaverDevice
>>>  Property
>>>    .bus_width = 4 # 4-byte wide logical device, `width=4`
>>>    .device_width = 1 # `device-width=1`
>>>  MemoryRegionOps
>>>    .valid.max_access_size = .bus_width # 4, set at realize()
>>>    .impl.max_access_size = .device_width # 1, set at realize()
>>> 
>>> Then instanciate 4 pflash devices, and link them to the interleaver
>>> using object_property_set_link().
>>> 
>>> typedef struct {
>>>    SysBusDevice parent_obj;
>>>    MemoryRegion iomem;
>>>    char *name;
>>>    /*
>>>     * On a 64-bit wide bus we can have at most
>>>     * 8 devices in 8-bit access mode.
>>>     */
>>>    MemoryRegion device[8];
>>>    unsigned device_count;
>>>    unsigned device_index_mask;
>>>    /* Properties */
>>>    unsigned bus_width;
>>>    unsigned device_width;
>>> } InterleaverDeviceState;
>>> 
>>> static Property interleaver_properties[] = {
>>>    DEFINE_PROP_LINK("device[0]", InterleaverDeviceState,
>>>                     device[0],
>>>                     TYPE_MEMORY_REGION, MemoryRegion *),
>>>    ...
>>>    DEFINE_PROP_LINK("device[7]", InterleaverDeviceState,
>>>                     device[7],
>>>                     TYPE_MEMORY_REGION, MemoryRegion *),
>>>    DEFINE_PROP_END_OF_LIST(),
>>> };
>>> 
>>> Then previous to call InterleaverDevice.realize():
>>> 
>>> In the board realize():
>>> 
>>> 
>>>    for (i = 0; i < interleaved_devices; i++) {
>>>        pflash[i] = create_pflash(...);
>>>        ...
>>>    }
>>> 
>>>    ild = ... create InterleaverDevice ...
>>>    for (i = 0; i < interleaved_devices; i++) {
>>>        char *propname = g_strdup_printf("device[%u]", i);
>>> 
>>> 
>>>        object_property_set_link(OBJECT(&ild->device[i]),
>>>                                 OBJECT(pflash[i]),
>>>                                 propname, &err);
>>>        ...
>>>    }
>>> 
>>> Finally,
>>> 
>>> static void interleaved_realize(DeviceState *dev, Error **errp)

I guess you mean interleaver_realize().

>>> {
>>>    InterleaverDeviceState *s = INTERLEAVER_DEVICE(opaque);
>>> 
>>>    s->device_count = s->bus_width / s->device_width;
>>>    s->device_index_mask = ~(s->device_count - 1);
>>>    ...
>>> }
>>> 
>>> static void interleaved_write(void *opaque, hwaddr offset,
>>>                              uint64_t value, unsigned size)

Likewise.

>>> {
>>>    InterleaverDeviceState *s = INTERLEAVER_DEVICE(opaque);
>>>    MemoryRegion *mr;
>>> 
>>>    /*
>>>     * Since we set .impl.max_access_size = device_width,
>>>     * access_with_adjusted_size() always call this with
>>>     * size = device_width.
>>>     *
>>>     * Adjust the address (offset).
>>>     */
>>>    offset >>= size;
>>>    /* Access the N interleaved device */
>>>    mr = s->device[offset & s->device_index_mask];
>>>    memory_region_dispatch_write(mr, offset, &value, size,
>>>                                 MEMTXATTRS_UNSPECIFIED);
>>> }

What makes this idea interesting is the separation of concerns: we
capture the "interleave memory" aspect in its own device, which we can
then use with any kind of memory device (i.e. any device that provides
the interface the interleaver expects).  The memory devices remain
oblivious of the interleave aspect.

If we needed interleave for just one memory device model, baking it into
that device model would likely be simpler.  I think that's how we ended
up baking it into the cfi.pflash* devices.

>>> 
>>> I'll try a PoC.
>> 
>> So I have a PoC, but then realize I can not use the same flash dump...
>> 
>> I need to deinterleave is. This is easily fixed with few lines of
>> Python, then if I want to store/share the dump (aka 'backend storage') I
>> have to re-interleave it.
>> 
>> I wonder if it would be possible/easy to add a 'interleave' option to
>> blockdev to be able to have 2 pflash devices sharing the same backend...
>> Is it worthwhile? Kevin/Markus/Max any thought on this?

I'm not sure I understand completely, so let me restate the problem and
your solution idea.

"Flash memory is narrow, and needs to be interleaved to a more
convenient width" is an implementation detail.  For the most part, you
want to hide this detail, and view the combination of interleaver logic
+ narrow memory as a unit.  In particular, when connecting to a block
backend for persistence, you want to connect this whole unit, without
having to know anything about its internal interleaving.

You obviously have to connect the block backend to the interleaver.
But what do you connect to the memory devices then?

One idea is to have an interleaver block filter node.  Each memory
device gets connected to the block backend via a suitably configured
interleaver block filter node, which provides access to its own stripes.
Together, they cover the whole block backend.

Is this reasonably close to what you mean?

Here's another possible idea: factor persistence out of the memory
devices as well.

Our persistent memory devices are funny beasts: they pretend to be block
devices just to gain convenient means for implementing persistence.

Their access pattern is quite different from real block devices: they
read the complete image at initialization time, then only ever write.

Unless the device's unit of writes happens to be a multiple of the block
backend's block size, there's write amplification: we write the blocks
that contain the written range.  Due to the way the block layer works,
this can even result in a read-modify-write cycle (I think).

Now consider the following composite device:

           sysbus
             |
    +--------|--------+
    |        |	      |
    |    persister ------ block backend
    |        | 	      |
    |   interleaver   |
    |    /  ...  \    |
    | mem   ...   mem |
    +-----------------+

If we ignore the internal composition, we have a device similar to the
cfi.pflash*: it's a TYPE_SYS_BUS_DEVICE with a BlockBackend property.

Internally, the persister takes care of (1) initializing the contents,
and (2) persisting writes to the block backend.  The interleaver takes
care of routing reads and writes to the memory devices, adjusting width
as necessary.

Glossed over here: the guest interface.  I figure the interleaver and
the mem devices cooperate to support wide access.  I haven't thought
through the details.

Of course, device decomposition is not the only way to separate
concerns!  Perhaps factoring out persistence and interleaving into a
library for use by the device models that need it would be simpler.
Can't say without trying it.

> Hi Phil,
>
> Sorry for the delay, I’ve been traveling.
>
> I considered something like this approach and I think it could work. Ultimately, I opted not to go that route for a few reasons:
> - duplicated CFI tables and other state waste (a small amount of) memory when the flash chips are the same (the usual case in my limited experience)

Is the state de-duplication 100% transparent to the guest?

> - it adds an extra layer of read/write calls plus recombining from/splitting into the component parts

I suspect the layer exists in a monolithic device model as well.  Going
to a composite device model then turns direct calls into indirect ones.
This is obviously not free.

I figure the persister could stay out of the read path.

> - duplicated timers firing to walk the programming/erasing state machine forward for each chip
> - the firmware or data stored in the chips is likely already interleaved necessitating either splitting it up before use or adding functionality to a lower layer to split it (as you’ve suggested here)
>
> None of the above seem like a big deal separately or together but I didn’t find the advantages of this approach to be sufficiently compelling to justify it. Namely, it allows using a heterogeneous set of flash chips to implement a logical device.

As long as persistence and interleaving apply to flash memory only, we
can probably afford some emulation inefficiency.  Software engineering
concerns are likely more important.  Regardless, I agree with you that
the effort to separate things needs to be justified.

> Nevertheless, if that’s the route you think is best, I have no objections.

[...]


^ permalink raw reply	[flat|nested] 31+ messages in thread

* Re: [Qemu-devel] [PATCH v4 04/10] block/pflash_cfi02: Implement intereleaved flash devices
  2019-06-25  8:32         ` Markus Armbruster
@ 2019-06-25 16:41           ` Stephen Checkoway
  0 siblings, 0 replies; 31+ messages in thread
From: Stephen Checkoway @ 2019-06-25 16:41 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Kevin Wolf, Peter Maydell, Thomas Huth,
	open list:Block layer core, Laurent Vivier,
	Philippe Mathieu-Daudé, Philippe Mathieu-Daudé,
	QEMU Developers, Paolo Bonzini, Max Reitz, Laszlo Ersek



> On Jun 25, 2019, at 04:32, Markus Armbruster <armbru@redhat.com> wrote:
> 
> Stephen Checkoway <scheckow@oberlin.edu> writes:
> 
>>> On Jun 24, 2019, at 12:05, Philippe Mathieu-Daudé <f4bug@amsat.org> wrote:
>>> 
>>>> On 6/22/19 2:25 PM, Philippe Mathieu-Daudé wrote:
>>>> Hi Stephen,
>>>> 
>>>> This series haven't fall through the cracks, however it is taking me
>>>> longer than expected to review it.
>>>> 
>>>>> On 4/26/19 6:26 PM, Stephen Checkoway wrote:
>>>>> It's common for multiple narrow flash chips to be hooked up in parallel
>>>>> to support wider buses. For example, four 8-bit wide flash chips (x8)
>>>>> may be combined in parallel to produce a 32-bit wide device. Similarly,
>>>>> two 16-bit wide chips (x16) may be combined.
>>>>> 
>>>>> This commit introduces `device-width` and `max-device-width` properties,
>>>>> similar to pflash_cfi01, with the following meanings:
>>>>> - `width`: The width of the logical, qemu device (same as before);
>>>>> - `device-width`: The width of an individual flash chip, defaulting to
>>>>> `width`; and
>>>>> - `max-device-width`: The maximum width of an individual flash chip,
>>>>> defaulting to `device-width`.
>>>>> 
>>>>> Nothing needs to change to support reading such interleaved devices but
>>>>> commands (e.g., erase and programming) must be sent to all devices at
>>>>> the same time or else the various chips will be in different states.
>>>> 
>>>> After some thoughts on this, I'd rather we model how hardware manage
>>>> interleaved devices: do it at the bus level, and instanciate N devices
>>>> in an interleaved config.
>>>> I believe that would drastically reduce this device complexity, and we
>>>> would match the real internal state machine.
>>>> Also this could be reused by other parallel devices used in a such config.
>>>> 
>>>>> For example, a 4-byte wide logical device can be composed of four x8/x16
>>>>> devices in x8 mode. That is, each device supports both x8 or x16 and
>>>>> they're being used in the byte, rather than word, mode. This
>>>>> configuration would have `width=4`, `device-width=1`, and
>>>>> `max-device-width=2`.
>>>> 
>>>> 
>>>> I'm thinking of this draft:
>>>> 
>>>> FlashDevice # x8
>>>> MemoryRegionOps
>>>>   .valid.max_access_size = 1
>>>> 
>>>> FlashDevice # x16
>>>> MemoryRegionOps
>>>>   .valid.min_access_size = 2
>>>>   .valid.max_access_size = 2
>>>> 
>>>> FlashDevice # x8/x16
>>>> MemoryRegionOps
>>>>   .valid.min_access_size = 1
>>>>   .valid.max_access_size = 2
>>>> 
>>>> We might use .impl.min_access_size = 2 and consider all NOR flash using
>>>> 16-bit words internally.
>>>>   .impl.max_access_size = 2 is implicit.
>>>> 
>>>> So for you example we'd instanciate one:
>>>> 
>>>> InterleaverDevice
>>>> Property
>>>>   .bus_width = 4 # 4-byte wide logical device, `width=4`
>>>>   .device_width = 1 # `device-width=1`
>>>> MemoryRegionOps
>>>>   .valid.max_access_size = .bus_width # 4, set at realize()
>>>>   .impl.max_access_size = .device_width # 1, set at realize()
>>>> 
>>>> Then instanciate 4 pflash devices, and link them to the interleaver
>>>> using object_property_set_link().
>>>> 
>>>> typedef struct {
>>>>   SysBusDevice parent_obj;
>>>>   MemoryRegion iomem;
>>>>   char *name;
>>>>   /*
>>>>    * On a 64-bit wide bus we can have at most
>>>>    * 8 devices in 8-bit access mode.
>>>>    */
>>>>   MemoryRegion device[8];
>>>>   unsigned device_count;
>>>>   unsigned device_index_mask;
>>>>   /* Properties */
>>>>   unsigned bus_width;
>>>>   unsigned device_width;
>>>> } InterleaverDeviceState;
>>>> 
>>>> static Property interleaver_properties[] = {
>>>>   DEFINE_PROP_LINK("device[0]", InterleaverDeviceState,
>>>>                    device[0],
>>>>                    TYPE_MEMORY_REGION, MemoryRegion *),
>>>>   ...
>>>>   DEFINE_PROP_LINK("device[7]", InterleaverDeviceState,
>>>>                    device[7],
>>>>                    TYPE_MEMORY_REGION, MemoryRegion *),
>>>>   DEFINE_PROP_END_OF_LIST(),
>>>> };
>>>> 
>>>> Then previous to call InterleaverDevice.realize():
>>>> 
>>>> In the board realize():
>>>> 
>>>> 
>>>>   for (i = 0; i < interleaved_devices; i++) {
>>>>       pflash[i] = create_pflash(...);
>>>>       ...
>>>>   }
>>>> 
>>>>   ild = ... create InterleaverDevice ...
>>>>   for (i = 0; i < interleaved_devices; i++) {
>>>>       char *propname = g_strdup_printf("device[%u]", i);
>>>> 
>>>> 
>>>>       object_property_set_link(OBJECT(&ild->device[i]),
>>>>                                OBJECT(pflash[i]),
>>>>                                propname, &err);
>>>>       ...
>>>>   }
>>>> 
>>>> Finally,
>>>> 
>>>> static void interleaved_realize(DeviceState *dev, Error **errp)
> 
> I guess you mean interleaver_realize().
> 
>>>> {
>>>>   InterleaverDeviceState *s = INTERLEAVER_DEVICE(opaque);
>>>> 
>>>>   s->device_count = s->bus_width / s->device_width;
>>>>   s->device_index_mask = ~(s->device_count - 1);
>>>>   ...
>>>> }
>>>> 
>>>> static void interleaved_write(void *opaque, hwaddr offset,
>>>>                             uint64_t value, unsigned size)
> 
> Likewise.
> 
>>>> {
>>>>   InterleaverDeviceState *s = INTERLEAVER_DEVICE(opaque);
>>>>   MemoryRegion *mr;
>>>> 
>>>>   /*
>>>>    * Since we set .impl.max_access_size = device_width,
>>>>    * access_with_adjusted_size() always call this with
>>>>    * size = device_width.
>>>>    *
>>>>    * Adjust the address (offset).
>>>>    */
>>>>   offset >>= size;
>>>>   /* Access the N interleaved device */
>>>>   mr = s->device[offset & s->device_index_mask];
>>>>   memory_region_dispatch_write(mr, offset, &value, size,
>>>>                                MEMTXATTRS_UNSPECIFIED);
>>>> }
> 
> What makes this idea interesting is the separation of concerns: we
> capture the "interleave memory" aspect in its own device, which we can
> then use with any kind of memory device (i.e. any device that provides
> the interface the interleaver expects).  The memory devices remain
> oblivious of the interleave aspect.
> 
> If we needed interleave for just one memory device model, baking it into
> that device model would likely be simpler.  I think that's how we ended
> up baking it into the cfi.pflash* devices.
> 
>>>> 
>>>> I'll try a PoC.
>>> 
>>> So I have a PoC, but then realize I can not use the same flash dump...
>>> 
>>> I need to deinterleave is. This is easily fixed with few lines of
>>> Python, then if I want to store/share the dump (aka 'backend storage') I
>>> have to re-interleave it.
>>> 
>>> I wonder if it would be possible/easy to add a 'interleave' option to
>>> blockdev to be able to have 2 pflash devices sharing the same backend...
>>> Is it worthwhile? Kevin/Markus/Max any thought on this?
> 
> I'm not sure I understand completely, so let me restate the problem and
> your solution idea.
> 
> "Flash memory is narrow, and needs to be interleaved to a more
> convenient width" is an implementation detail.  For the most part, you
> want to hide this detail, and view the combination of interleaver logic
> + narrow memory as a unit.  In particular, when connecting to a block
> backend for persistence, you want to connect this whole unit, without
> having to know anything about its internal interleaving.
> 
> You obviously have to connect the block backend to the interleaver.
> But what do you connect to the memory devices then?
> 
> One idea is to have an interleaver block filter node.  Each memory
> device gets connected to the block backend via a suitably configured
> interleaver block filter node, which provides access to its own stripes.
> Together, they cover the whole block backend.
> 
> Is this reasonably close to what you mean?
> 
> Here's another possible idea: factor persistence out of the memory
> devices as well.
> 
> Our persistent memory devices are funny beasts: they pretend to be block
> devices just to gain convenient means for implementing persistence.
> 
> Their access pattern is quite different from real block devices: they
> read the complete image at initialization time, then only ever write.
> 
> Unless the device's unit of writes happens to be a multiple of the block
> backend's block size, there's write amplification: we write the blocks
> that contain the written range.  Due to the way the block layer works,
> this can even result in a read-modify-write cycle (I think).
> 
> Now consider the following composite device:
> 
>           sysbus
>             |
>    +--------|--------+
>    |        |	      |
>    |    persister ------ block backend
>    |        | 	      |
>    |   interleaver   |
>    |    /  ...  \    |
>    | mem   ...   mem |
>    +-----------------+
> 
> If we ignore the internal composition, we have a device similar to the
> cfi.pflash*: it's a TYPE_SYS_BUS_DEVICE with a BlockBackend property.
> 
> Internally, the persister takes care of (1) initializing the contents,
> and (2) persisting writes to the block backend.  The interleaver takes
> care of routing reads and writes to the memory devices, adjusting width
> as necessary.
> 
> Glossed over here: the guest interface.  I figure the interleaver and
> the mem devices cooperate to support wide access.  I haven't thought
> through the details.
> 
> Of course, device decomposition is not the only way to separate
> concerns!  Perhaps factoring out persistence and interleaving into a
> library for use by the device models that need it would be simpler.
> Can't say without trying it.
> 
>> Hi Phil,
>> 
>> Sorry for the delay, I’ve been traveling.
>> 
>> I considered something like this approach and I think it could work. Ultimately, I opted not to go that route for a few reasons:
>> - duplicated CFI tables and other state waste (a small amount of) memory when the flash chips are the same (the usual case in my limited experience)
> 
> Is the state de-duplication 100% transparent to the guest?

It isn't. I think there are four basic cases where it wouldn't be transparent, but I don't think any are useful to support:

1. Two (or more) models of flash chips can be interleaved. These would have different entries in the common flash interface (CFI) tables that the guests could query. Probably the most important values in the tables are sector sizes and the erase blocks. I can't imagine trying to construct a system where those differed. But there are also IDs that can be read out.

2. Each chip has a state machine driven by read and write cycles and guests could send different commands to different chips. I can't think of a use case for erasing a sector of one chip but not another, for example. In principle it could be done, but it would make for some pretty terrible guest logic.

3. Physical differences in the chips could lead to differing times for operations like programming (which we don't currently model) and erasing (which we do). Guests could detect one of the chips had finished erasing a sector before another. I'm not sure what use that could serve.

4. Sort of a combination of 1 and 3: chips with different timings (e.g., one that erases fast and one that erases slowly) could be used and the difference could be measured.

I don't find any of those compelling enough to add the complexity to support them (plus pay the minor costs of duplicating state).

> - it adds an extra layer of read/write calls plus recombining from/splitting into the component parts
> 
> I suspect the layer exists in a monolithic device model as well.  Going
> to a composite device model then turns direct calls into indirect ones.
> This is obviously not free.

I'm not sure I follow this. My implementation for read is essentially two lines (plus some for tracing that should be simplified but I haven't looked much into QEMU's trace functionality). https://github.com/stevecheckoway/qemu/blob/pflash02/hw/block/pflash_cfi02.c#L253

uint8_t *p = (uint8_t *)pfl->storage + offset;
uint64_t ret = pfl->be ? ldn_be_p(p, width) : ldn_le_p(p, width);

With an extra interleave layer, I'd imagine it would need to call other read functions at the narrow width on the linked devices and then combine the result.


-- 
Stephen Checkoway







^ permalink raw reply	[flat|nested] 31+ messages in thread

end of thread, other threads:[~2019-06-25 16:42 UTC | newest]

Thread overview: 31+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-04-26 16:26 [Qemu-devel] [PATCH v4 00/10] block/pflash_cfi02: Implement missing AMD pflash functionality Stephen Checkoway
2019-04-26 16:26 ` Stephen Checkoway
2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 01/10] block/pflash_cfi02: Add test for supported commands Stephen Checkoway
2019-04-26 16:26   ` Stephen Checkoway
2019-05-06  3:53   ` Thomas Huth
2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 02/10] block/pflash_cfi02: Refactor, NFC intended Stephen Checkoway
2019-04-26 16:26   ` Stephen Checkoway
2019-05-06  7:34   ` Philippe Mathieu-Daudé
2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 03/10] block/pflash_cfi02: Fix command address comparison Stephen Checkoway
2019-04-26 16:26   ` Stephen Checkoway
2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 04/10] block/pflash_cfi02: Implement intereleaved flash devices Stephen Checkoway
2019-04-26 16:26   ` Stephen Checkoway
2019-06-22 12:25   ` Philippe Mathieu-Daudé
2019-06-24 19:05     ` Philippe Mathieu-Daudé
2019-06-24 19:36       ` Stephen Checkoway
2019-06-25  8:32         ` Markus Armbruster
2019-06-25 16:41           ` Stephen Checkoway
2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 05/10] block/pflash_cfi02: Implement nonuniform sector sizes Stephen Checkoway
2019-04-26 16:26   ` Stephen Checkoway
2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 06/10] block/pflash_cfi02: Fix CFI in autoselect mode Stephen Checkoway
2019-04-26 16:26   ` Stephen Checkoway
2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 07/10] block/pflash_cfi02: Fix reset command not ignored during erase Stephen Checkoway
2019-04-26 16:26   ` Stephen Checkoway
2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 08/10] block/pflash_cfi02: Implement multi-sector erase Stephen Checkoway
2019-04-26 16:26   ` Stephen Checkoway
2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 09/10] block/pflash_cfi02: Implement erase suspend/resume Stephen Checkoway
2019-04-26 16:26   ` Stephen Checkoway
2019-04-26 16:26 ` [Qemu-devel] [PATCH v4 10/10] block/pflash_cfi02: Use the chip erase time specified in the CFI table Stephen Checkoway
2019-04-26 16:26   ` Stephen Checkoway
2019-05-05 18:57   ` Philippe Mathieu-Daudé
2019-05-05 18:57     ` Philippe Mathieu-Daudé

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).