Commit a50547ac authored by Stephen Checkoway's avatar Stephen Checkoway Committed by Philippe Mathieu-Daudé
Browse files

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: default avatarStephen Checkoway <stephen.checkoway@oberlin.edu>
Message-Id: <20190426162624.55977-9-stephen.checkoway@oberlin.edu>
Acked-by: default avatarThomas Huth <thuth@redhat.com>
Acked-by: default avatarPhilippe Mathieu-Daudé <philmd@redhat.com>
[PMD: Rebased]
Signed-off-by: default avatarPhilippe Mathieu-Daudé <philmd@redhat.com>
parent a9791042
Loading
Loading
Loading
Loading
+76 −18
Original line number Diff line number Diff line
@@ -31,7 +31,6 @@
 * It does not support flash interleaving.
 * It does not implement software data protection as found in many real chips
 * It does not implement erase suspend/resume commands
 * It does not implement multiple sectors erase
 */

#include "qemu/osdep.h"
@@ -106,6 +105,7 @@ struct PFlashCFI02 {
    MemoryRegion orig_mem;
    int rom_mode;
    int read_counter; /* used for lazy switch-back to rom mode */
    int sectors_to_erase;
    char *name;
    void *storage;
};
@@ -135,6 +135,22 @@ static inline void toggle_dq6(PFlashCFI02 *pfl)
    pfl->status ^= 0x40;
}

/*
 * Turn on DQ3.
 */
static inline void assert_dq3(PFlashCFI02 *pfl)
{
    pfl->status |= 0x08;
}

/*
 * Turn off DQ3.
 */
static inline void reset_dq3(PFlashCFI02 *pfl)
{
    pfl->status &= ~0x08;
}

/*
 * Set up replicated mappings of the same region.
 */
@@ -168,6 +184,32 @@ 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 */
+61 −9
Original line number Diff line number Diff line
@@ -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);
}