mirror of https://github.com/proxmox/mirror_qemu
hw/block/pflash_cfi02: Implement multi-sector erase
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> Message-Id: <20190426162624.55977-9-stephen.checkoway@oberlin.edu> Acked-by: Thomas Huth <thuth@redhat.com> Acked-by: Philippe Mathieu-Daudé <philmd@redhat.com> [PMD: Rebased] Signed-off-by: Philippe Mathieu-Daudé <philmd@redhat.com>master
parent
a979104239
commit
a50547aca5
|
@ -31,7 +31,6 @@
|
||||||
* It does not support flash interleaving.
|
* It does not support flash interleaving.
|
||||||
* It does not implement software data protection as found in many real chips
|
* 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 erase suspend/resume commands
|
||||||
* It does not implement multiple sectors erase
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "qemu/osdep.h"
|
#include "qemu/osdep.h"
|
||||||
|
@ -106,6 +105,7 @@ struct PFlashCFI02 {
|
||||||
MemoryRegion orig_mem;
|
MemoryRegion orig_mem;
|
||||||
int rom_mode;
|
int rom_mode;
|
||||||
int read_counter; /* used for lazy switch-back to rom mode */
|
int read_counter; /* used for lazy switch-back to rom mode */
|
||||||
|
int sectors_to_erase;
|
||||||
char *name;
|
char *name;
|
||||||
void *storage;
|
void *storage;
|
||||||
};
|
};
|
||||||
|
@ -135,6 +135,22 @@ static inline void toggle_dq6(PFlashCFI02 *pfl)
|
||||||
pfl->status ^= 0x40;
|
pfl->status ^= 0x40;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Turn on DQ3.
|
||||||
|
*/
|
||||||
|
static inline void assert_dq3(PFlashCFI02 *pfl)
|
||||||
|
{
|
||||||
|
pfl->status |= 0x08;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Turn off DQ3.
|
||||||
|
*/
|
||||||
|
static inline void reset_dq3(PFlashCFI02 *pfl)
|
||||||
|
{
|
||||||
|
pfl->status &= ~0x08;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set up replicated mappings of the same region.
|
* Set up replicated mappings of the same region.
|
||||||
*/
|
*/
|
||||||
|
@ -163,11 +179,37 @@ static size_t pflash_regions_count(PFlashCFI02 *pfl)
|
||||||
return pfl->cfi_table[0x2c];
|
return pfl->cfi_table[0x2c];
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pflash_timer (void *opaque)
|
static void pflash_timer(void *opaque)
|
||||||
{
|
{
|
||||||
PFlashCFI02 *pfl = opaque;
|
PFlashCFI02 *pfl = opaque;
|
||||||
|
|
||||||
trace_pflash_timer_expired(pfl->cmd);
|
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 */
|
/* Reset flash */
|
||||||
toggle_dq7(pfl);
|
toggle_dq7(pfl);
|
||||||
if (pfl->bypass) {
|
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,
|
static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
|
||||||
unsigned int width)
|
unsigned int width)
|
||||||
{
|
{
|
||||||
|
@ -306,7 +366,6 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
|
||||||
hwaddr boff;
|
hwaddr boff;
|
||||||
uint8_t *p;
|
uint8_t *p;
|
||||||
uint8_t cmd;
|
uint8_t cmd;
|
||||||
uint32_t sector_len;
|
|
||||||
|
|
||||||
trace_pflash_io_write(offset, width, width << 1, value, pfl->wcycle);
|
trace_pflash_io_write(offset, width, width << 1, value, pfl->wcycle);
|
||||||
cmd = value;
|
cmd = value;
|
||||||
|
@ -469,20 +528,7 @@ static void pflash_write(void *opaque, hwaddr offset, uint64_t value,
|
||||||
break;
|
break;
|
||||||
case 0x30:
|
case 0x30:
|
||||||
/* Sector erase */
|
/* Sector erase */
|
||||||
p = pfl->storage;
|
pflash_sector_erase(pfl, offset);
|
||||||
sector_len = pflash_sector_len(pfl, offset);
|
|
||||||
offset &= ~(sector_len - 1);
|
|
||||||
DPRINTF("%s: start sector erase at %0*" PRIx64 "-%0*" PRIx64 "\n",
|
|
||||||
__func__, pfl->width * 2, offset,
|
|
||||||
pfl->width * 2, offset + sector_len - 1);
|
|
||||||
if (!pfl->ro) {
|
|
||||||
memset(p + offset, 0xff, 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));
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
DPRINTF("%s: invalid command %02x (wc 5)\n", __func__, cmd);
|
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 */
|
/* Ignore writes during chip erase */
|
||||||
return;
|
return;
|
||||||
case 0x30:
|
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;
|
return;
|
||||||
default:
|
default:
|
||||||
/* Should never happen */
|
/* Should never happen */
|
||||||
|
|
|
@ -35,6 +35,7 @@ typedef struct {
|
||||||
#define CFI_CMD 0x98
|
#define CFI_CMD 0x98
|
||||||
#define UNLOCK0_CMD 0xAA
|
#define UNLOCK0_CMD 0xAA
|
||||||
#define UNLOCK1_CMD 0x55
|
#define UNLOCK1_CMD 0x55
|
||||||
|
#define SECOND_UNLOCK_CMD 0x80
|
||||||
#define AUTOSELECT_CMD 0x90
|
#define AUTOSELECT_CMD 0x90
|
||||||
#define RESET_CMD 0xF0
|
#define RESET_CMD 0xF0
|
||||||
#define PROGRAM_CMD 0xA0
|
#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)
|
static void sector_erase(const FlashConfig *c, uint64_t byte_addr)
|
||||||
{
|
{
|
||||||
unlock(c);
|
unlock(c);
|
||||||
flash_cmd(c, UNLOCK0_ADDR, 0x80);
|
flash_cmd(c, UNLOCK0_ADDR, SECOND_UNLOCK_CMD);
|
||||||
unlock(c);
|
unlock(c);
|
||||||
flash_write(c, byte_addr, replicate(c, SECTOR_ERASE_CMD));
|
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)
|
static void chip_erase(const FlashConfig *c)
|
||||||
{
|
{
|
||||||
unlock(c);
|
unlock(c);
|
||||||
flash_cmd(c, UNLOCK0_ADDR, 0x80);
|
flash_cmd(c, UNLOCK0_ADDR, SECOND_UNLOCK_CMD);
|
||||||
unlock(c);
|
unlock(c);
|
||||||
flash_cmd(c, UNLOCK0_ADDR, CHIP_ERASE_CMD);
|
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 dq7 = replicate(c, 0x80);
|
||||||
const uint64_t dq6 = replicate(c, 0x40);
|
const uint64_t dq6 = replicate(c, 0x40);
|
||||||
|
const uint64_t dq3 = replicate(c, 0x08);
|
||||||
|
|
||||||
uint64_t byte_addr = 0;
|
uint64_t byte_addr = 0;
|
||||||
for (int region = 0; region < nb_erase_regions; ++region) {
|
for (int region = 0; region < nb_erase_regions; ++region) {
|
||||||
uint64_t base = 0x2D + 4 * region;
|
uint64_t base = 0x2D + 4 * region;
|
||||||
|
@ -330,18 +333,29 @@ static void test_geometry(const void *opaque)
|
||||||
/* Erase and program sector. */
|
/* Erase and program sector. */
|
||||||
for (uint32_t i = 0; i < nb_sectors; ++i) {
|
for (uint32_t i = 0; i < nb_sectors; ++i) {
|
||||||
sector_erase(c, byte_addr);
|
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);
|
uint64_t status0 = flash_read(c, byte_addr);
|
||||||
|
g_assert_cmphex(status0 & dq3, ==, dq3);
|
||||||
|
|
||||||
/* DQ7 is 0 during an erase. */
|
/* DQ7 is 0 during an erase. */
|
||||||
g_assert_cmphex(status0 & dq7, ==, 0);
|
g_assert_cmphex(status0 & dq7, ==, 0);
|
||||||
uint64_t status1 = flash_read(c, byte_addr);
|
uint64_t status1 = flash_read(c, byte_addr);
|
||||||
|
|
||||||
/* DQ6 toggles during an erase. */
|
/* DQ6 toggles during an erase. */
|
||||||
g_assert_cmphex(status0 & dq6, ==, ~status1 & dq6);
|
g_assert_cmphex(status0 & dq6, ==, ~status1 & dq6);
|
||||||
|
|
||||||
/* Wait for erase to complete. */
|
/* Wait for erase to complete. */
|
||||||
qtest_clock_step_next(c->qtest);
|
wait_for_completion(c, byte_addr);
|
||||||
|
|
||||||
/* Ensure DQ6 has stopped toggling. */
|
/* Ensure DQ6 has stopped toggling. */
|
||||||
g_assert_cmphex(flash_read(c, byte_addr), ==,
|
g_assert_cmphex(flash_read(c, byte_addr), ==,
|
||||||
flash_read(c, byte_addr));
|
flash_read(c, byte_addr));
|
||||||
|
|
||||||
/* Now the data should be valid. */
|
/* Now the data should be valid. */
|
||||||
g_assert_cmphex(flash_read(c, byte_addr), ==, bank_mask(c));
|
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));
|
g_assert_cmphex(flash_query(c, FLASH_ADDR(0)), ==, replicate(c, 0xBF));
|
||||||
reset(c);
|
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);
|
qtest_quit(qtest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,17 +480,17 @@ static void test_cfi_in_autoselect(const void *opaque)
|
||||||
/* 1. Enter autoselect. */
|
/* 1. Enter autoselect. */
|
||||||
unlock(c);
|
unlock(c);
|
||||||
flash_cmd(c, UNLOCK0_ADDR, AUTOSELECT_CMD);
|
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. */
|
/* 2. Enter CFI. */
|
||||||
flash_cmd(c, CFI_ADDR, CFI_CMD);
|
flash_cmd(c, CFI_ADDR, CFI_CMD);
|
||||||
g_assert_cmpint(flash_query(c, FLASH_ADDR(0x10)), ==, replicate(c, 'Q'));
|
g_assert_cmphex(flash_query(c, FLASH_ADDR(0x10)), ==, replicate(c, 'Q'));
|
||||||
g_assert_cmpint(flash_query(c, FLASH_ADDR(0x11)), ==, replicate(c, 'R'));
|
g_assert_cmphex(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(0x12)), ==, replicate(c, 'Y'));
|
||||||
|
|
||||||
/* 3. Exit CFI. */
|
/* 3. Exit CFI. */
|
||||||
reset(c);
|
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);
|
qtest_quit(qtest);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue