summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--hw/block/pflash_cfi02.c94
-rw-r--r--tests/pflash-cfi02-test.c70
2 files changed, 137 insertions, 27 deletions
diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c
index 39daa95833..5874bd55ad 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -31,7 +31,6 @@
* It does not support flash interleaving.
* It does not implement software data protection as found in many real chips
* It does not implement erase suspend/resume commands
- * It does not implement multiple sectors erase
*/
#include "qemu/osdep.h"
@@ -106,6 +105,7 @@ struct PFlashCFI02 {
MemoryRegion orig_mem;
int rom_mode;
int read_counter; /* used for lazy switch-back to rom mode */
+ int sectors_to_erase;
char *name;
void *storage;
};
@@ -136,6 +136,22 @@ static inline void toggle_dq6(PFlashCFI02 *pfl)
}
/*
+ * Turn on DQ3.
+ */
+static inline void assert_dq3(PFlashCFI02 *pfl)
+{
+ pfl->status |= 0x08;
+}
+
+/*
+ * Turn off DQ3.
+ */
+static inline void reset_dq3(PFlashCFI02 *pfl)
+{
+ pfl->status &= ~0x08;
+}
+
+/*
* Set up replicated mappings of the same region.
*/
static void pflash_setup_mappings(PFlashCFI02 *pfl)
@@ -163,11 +179,37 @@ static size_t pflash_regions_count(PFlashCFI02 *pfl)
return pfl->cfi_table[0x2c];
}
-static void pflash_timer (void *opaque)
+static void pflash_timer(void *opaque)
{
PFlashCFI02 *pfl = opaque;
trace_pflash_timer_expired(pfl->cmd);
+ if (pfl->cmd == 0x30) {
+ /*
+ * Sector erase. If DQ3 is 0 when the timer expires, then the 50
+ * us erase timeout has expired so we need to start the timer for the
+ * sector erase algorithm. Otherwise, the erase completed and we should
+ * go back to read array mode.
+ */
+ if ((pfl->status & 0x08) == 0) {
+ assert_dq3(pfl);
+ /*
+ * CFI address 0x21 is "Typical timeout per individual block erase
+ * 2^N ms"
+ */
+ uint64_t timeout = ((1ULL << pfl->cfi_table[0x21]) *
+ pfl->sectors_to_erase) * 1000000;
+ timer_mod(&pfl->timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + timeout);
+ DPRINTF("%s: erase timeout fired; erasing %d sectors\n",
+ __func__, pfl->sectors_to_erase);
+ return;
+ }
+ DPRINTF("%s: sector erase complete\n", __func__);
+ pfl->sectors_to_erase = 0;
+ reset_dq3(pfl);
+ }
+
/* Reset flash */
toggle_dq7(pfl);
if (pfl->bypass) {
@@ -299,6 +341,24 @@ static void pflash_update(PFlashCFI02 *pfl, int offset, int size)
}
}
+static void pflash_sector_erase(PFlashCFI02 *pfl, hwaddr offset)
+{
+ uint64_t sector_len = pflash_sector_len(pfl, offset);
+ offset &= ~(sector_len - 1);
+ DPRINTF("%s: start sector erase at %0*" PRIx64 "-%0*" PRIx64 "\n",
+ __func__, pfl->width * 2, offset,
+ pfl->width * 2, offset + sector_len - 1);
+ if (!pfl->ro) {
+ uint8_t *p = pfl->storage;
+ memset(p + offset, 0xff, sector_len);
+ pflash_update(pfl, offset, sector_len);
+ }
+ set_dq7(pfl, 0x00);
+ ++pfl->sectors_to_erase;
+ /* Set (or reset) the 50 us timer for additional erase commands. */
+ timer_mod(&pfl->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 50000);
+}
+
static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
unsigned int width)
{
@@ -306,7 +366,6 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
hwaddr boff;
uint8_t *p;
uint8_t cmd;
- uint32_t sector_len;
trace_pflash_io_write(offset, width, width << 1, value, pfl->wcycle);
cmd = value;
@@ -469,20 +528,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
break;
case 0x30:
/* Sector erase */
- p = pfl->storage;
- sector_len = pflash_sector_len(pfl, offset);
- offset &= ~(sector_len - 1);
- DPRINTF("%s: start sector erase at %0*" PRIx64 "-%0*" PRIx64 "\n",
- __func__, pfl->width * 2, offset,
- pfl->width * 2, offset + sector_len - 1);
- if (!pfl->ro) {
- memset(p + offset, 0xff, sector_len);
- pflash_update(pfl, offset, sector_len);
- }
- set_dq7(pfl, 0x00);
- /* Let's wait 1/2 second before sector erase is done */
- timer_mod(&pfl->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
- (NANOSECONDS_PER_SECOND / 2));
+ pflash_sector_erase(pfl, offset);
break;
default:
DPRINTF("%s: invalid command %02x (wc 5)\n", __func__, cmd);
@@ -496,7 +542,19 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
/* Ignore writes during chip erase */
return;
case 0x30:
- /* Ignore writes during sector erase */
+ /*
+ * If DQ3 is 0, additional sector erase commands can be
+ * written and anything else (other than an erase suspend) resets
+ * the device.
+ */
+ if ((pfl->status & 0x08) == 0) {
+ if (cmd == 0x30) {
+ pflash_sector_erase(pfl, offset);
+ } else {
+ goto reset_flash;
+ }
+ }
+ /* Ignore writes during the actual erase. */
return;
default:
/* Should never happen */
diff --git a/tests/pflash-cfi02-test.c b/tests/pflash-cfi02-test.c
index 00e2261742..303bc87820 100644
--- a/tests/pflash-cfi02-test.c
+++ b/tests/pflash-cfi02-test.c
@@ -35,6 +35,7 @@ typedef struct {
#define CFI_CMD 0x98
#define UNLOCK0_CMD 0xAA
#define UNLOCK1_CMD 0x55
+#define SECOND_UNLOCK_CMD 0x80
#define AUTOSELECT_CMD 0x90
#define RESET_CMD 0xF0
#define PROGRAM_CMD 0xA0
@@ -196,7 +197,7 @@ static void reset(const FlashConfig *c)
static void sector_erase(const FlashConfig *c, uint64_t byte_addr)
{
unlock(c);
- flash_cmd(c, UNLOCK0_ADDR, 0x80);
+ flash_cmd(c, UNLOCK0_ADDR, SECOND_UNLOCK_CMD);
unlock(c);
flash_write(c, byte_addr, replicate(c, SECTOR_ERASE_CMD));
}
@@ -235,7 +236,7 @@ static void program(const FlashConfig *c, uint64_t byte_addr, uint16_t data)
static void chip_erase(const FlashConfig *c)
{
unlock(c);
- flash_cmd(c, UNLOCK0_ADDR, 0x80);
+ flash_cmd(c, UNLOCK0_ADDR, SECOND_UNLOCK_CMD);
unlock(c);
flash_cmd(c, UNLOCK0_ADDR, CHIP_ERASE_CMD);
}
@@ -315,6 +316,8 @@ static void test_geometry(const void *opaque)
const uint64_t dq7 = replicate(c, 0x80);
const uint64_t dq6 = replicate(c, 0x40);
+ const uint64_t dq3 = replicate(c, 0x08);
+
uint64_t byte_addr = 0;
for (int region = 0; region < nb_erase_regions; ++region) {
uint64_t base = 0x2D + 4 * region;
@@ -330,18 +333,29 @@ static void test_geometry(const void *opaque)
/* Erase and program sector. */
for (uint32_t i = 0; i < nb_sectors; ++i) {
sector_erase(c, byte_addr);
- /* Read toggle. */
+
+ /* Check that DQ3 is 0. */
+ g_assert_cmphex(flash_read(c, byte_addr) & dq3, ==, 0);
+ qtest_clock_step_next(c->qtest); /* Step over the 50 us timeout. */
+
+ /* Check that DQ3 is 1. */
uint64_t status0 = flash_read(c, byte_addr);
+ g_assert_cmphex(status0 & dq3, ==, dq3);
+
/* DQ7 is 0 during an erase. */
g_assert_cmphex(status0 & dq7, ==, 0);
uint64_t status1 = flash_read(c, byte_addr);
+
/* DQ6 toggles during an erase. */
g_assert_cmphex(status0 & dq6, ==, ~status1 & dq6);
+
/* Wait for erase to complete. */
- qtest_clock_step_next(c->qtest);
+ wait_for_completion(c, byte_addr);
+
/* Ensure DQ6 has stopped toggling. */
g_assert_cmphex(flash_read(c, byte_addr), ==,
flash_read(c, byte_addr));
+
/* Now the data should be valid. */
g_assert_cmphex(flash_read(c, byte_addr), ==, bank_mask(c));
@@ -404,6 +418,44 @@ static void test_geometry(const void *opaque)
g_assert_cmphex(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF));
reset(c);
+ /*
+ * Program a word on each sector, erase one or two sectors per region, and
+ * verify that all of those, and only those, are erased.
+ */
+ byte_addr = 0;
+ for (int region = 0; region < nb_erase_regions; ++region) {
+ for (int i = 0; i < config->nb_blocs[region]; ++i) {
+ program(c, byte_addr, 0);
+ byte_addr += config->sector_len[region];
+ }
+ }
+ unlock(c);
+ flash_cmd(c, UNLOCK0_ADDR, SECOND_UNLOCK_CMD);
+ unlock(c);
+ byte_addr = 0;
+ const uint64_t erase_cmd = replicate(c, SECTOR_ERASE_CMD);
+ for (int region = 0; region < nb_erase_regions; ++region) {
+ flash_write(c, byte_addr, erase_cmd);
+ if (c->nb_blocs[region] > 1) {
+ flash_write(c, byte_addr + c->sector_len[region], erase_cmd);
+ }
+ byte_addr += c->sector_len[region] * c->nb_blocs[region];
+ }
+
+ qtest_clock_step_next(c->qtest); /* Step over the 50 us timeout. */
+ wait_for_completion(c, 0);
+ byte_addr = 0;
+ for (int region = 0; region < nb_erase_regions; ++region) {
+ for (int i = 0; i < config->nb_blocs[region]; ++i) {
+ if (i < 2) {
+ g_assert_cmphex(flash_read(c, byte_addr), ==, bank_mask(c));
+ } else {
+ g_assert_cmphex(flash_read(c, byte_addr), ==, 0);
+ }
+ byte_addr += config->sector_len[region];
+ }
+ }
+
qtest_quit(qtest);
}
@@ -428,17 +480,17 @@ static void test_cfi_in_autoselect(const void *opaque)
/* 1. Enter autoselect. */
unlock(c);
flash_cmd(c, UNLOCK0_ADDR, AUTOSELECT_CMD);
- g_assert_cmpint(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF));
+ g_assert_cmphex(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF));
/* 2. Enter CFI. */
flash_cmd(c, CFI_ADDR, CFI_CMD);
- g_assert_cmpint(flash_query(c, FLASH_ADDR(0x10)), ==, replicate(c, 'Q'));
- g_assert_cmpint(flash_query(c, FLASH_ADDR(0x11)), ==, replicate(c, 'R'));
- g_assert_cmpint(flash_query(c, FLASH_ADDR(0x12)), ==, replicate(c, 'Y'));
+ g_assert_cmphex(flash_query(c, FLASH_ADDR(0x10)), ==, replicate(c, 'Q'));
+ g_assert_cmphex(flash_query(c, FLASH_ADDR(0x11)), ==, replicate(c, 'R'));
+ g_assert_cmphex(flash_query(c, FLASH_ADDR(0x12)), ==, replicate(c, 'Y'));
/* 3. Exit CFI. */
reset(c);
- g_assert_cmpint(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF));
+ g_assert_cmphex(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF));
qtest_quit(qtest);
}