[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-block] [PATCH v3 3/3] Add partial flash interleaving to AMD CFI de
From: |
Mike Nawrocki |
Subject: |
[Qemu-block] [PATCH v3 3/3] Add partial flash interleaving to AMD CFI devices |
Date: |
Mon, 13 Nov 2017 12:31:20 -0500 |
This mirrors the interleaving support already in place in
pflash_cfi01.c, using the max_device_width and device_width properties.
Signed-off-by: Mike Nawrocki <address@hidden>
---
hw/block/pflash_cfi02.c | 244 +++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 209 insertions(+), 35 deletions(-)
diff --git a/hw/block/pflash_cfi02.c b/hw/block/pflash_cfi02.c
index 29eb0429a3..edf683bce5 100644
--- a/hw/block/pflash_cfi02.c
+++ b/hw/block/pflash_cfi02.c
@@ -28,11 +28,14 @@
* - 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
* It does not implement multiple sectors erase
+ *
+ * Flash interleaving is partially supported using the device_width and
+ * max_device_width properties, as in pflash_cfi01.c
+ * Issuing commands to individual members of the flash array is not supported.
*/
#include "qemu/osdep.h"
@@ -40,6 +43,7 @@
#include "hw/block/flash.h"
#include "qapi/error.h"
#include "qemu/timer.h"
+#include "qemu/bitops.h"
#include "sysemu/block-backend.h"
#include "exec/address-spaces.h"
#include "qemu/host-utils.h"
@@ -69,7 +73,9 @@ struct pflash_t {
uint32_t nb_blocs;
uint32_t chip_len;
uint8_t mappings;
- uint8_t width;
+ uint8_t bank_width;
+ uint8_t device_width; /* If 0, device width not specified. */
+ uint8_t max_device_width; /* max device width in bytes */
uint8_t be;
int wcycle; /* if 0, the flash is read normally */
int bypass;
@@ -138,6 +144,63 @@ static void pflash_timer (void *opaque)
pfl->cmd = 0;
}
+static uint64_t pflash_cfi_query(pflash_t *pfl, hwaddr offset)
+{
+ int i;
+ uint64_t resp = 0;
+ hwaddr boff;
+
+ /* Adjust incoming offset to match expected device-width
+ * addressing. CFI query addresses are always specified in terms of
+ * the maximum supported width of the device. This means that x8
+ * devices and x8/x16 devices in x8 mode behave differently. For
+ * devices that are not used at their max width, we will be
+ * provided with addresses that use higher address bits than
+ * expected (based on the max width), so we will shift them lower
+ * so that they will match the addresses used when
+ * device_width==max_device_width.
+ */
+ boff = offset >> (ctz32(pfl->bank_width) +
+ ctz32(pfl->max_device_width) - ctz32(pfl->device_width));
+
+ if (boff > pfl->cfi_len) {
+ return 0;
+ }
+ /* Now we will construct the CFI response generated by a single
+ * device, then replicate that for all devices that make up the
+ * bus. For wide parts used in x8 mode, CFI query responses
+ * are different than native byte-wide parts.
+ */
+ resp = pfl->cfi_table[boff];
+ if (pfl->device_width != pfl->max_device_width) {
+ /* The only case currently supported is x8 mode for a
+ * wider part.
+ */
+ if (pfl->device_width != 1 || pfl->bank_width > 8) {
+ DPRINTF("%s: Unsupported device configuration: "
+ "device_width=%d, max_device_width=%d\n",
+ __func__, pfl->device_width,
+ pfl->max_device_width);
+ return 0;
+ }
+ /* CFI query data is repeated, rather than zero padded for
+ * wide devices used in x8 mode.
+ */
+ for (i = 1; i < pfl->max_device_width; i++) {
+ resp = deposit64(resp, 8 * i, 8, pfl->cfi_table[boff]);
+ }
+ }
+ /* Replicate responses for each device in bank. */
+ if (pfl->device_width < pfl->bank_width) {
+ for (i = pfl->device_width;
+ i < pfl->bank_width; i += pfl->device_width) {
+ resp = deposit64(resp, 8 * i, 8 * pfl->device_width, resp);
+ }
+ }
+
+ return resp;
+}
+
static uint64_t _flash_read(pflash_t *pfl, hwaddr offset,
int width, int be)
{
@@ -200,9 +263,66 @@ static uint64_t _flash_read(pflash_t *pfl, hwaddr offset,
return ret;
}
+/* Perform a device id query based on the bank width of the flash. */
+static uint64_t pflash_devid_query(pflash_t *pfl, hwaddr offset,
+ int width, int be)
+{
+ int i;
+ uint64_t resp;
+ hwaddr boff;
+
+ /* Adjust incoming offset to match expected device-width
+ * addressing. Device ID read addresses are always specified in
+ * terms of the maximum supported width of the device. This means
+ * that x8 devices and x8/x16 devices in x8 mode behave
+ * differently. For devices that are not used at their max width,
+ * we will be provided with addresses that use higher address bits
+ * than expected (based on the max width), so we will shift them
+ * lower so that they will match the addresses used when
+ * device_width==max_device_width.
+ */
+ boff = offset >> (ctz32(pfl->bank_width) +
+ ctz32(pfl->max_device_width) - ctz32(pfl->device_width));
+
+ /* Mask off upper bits which may be used in to query block
+ * or sector lock status at other addresses.
+ */
+ switch (boff & 0xFF) {
+ case 0:
+ resp = pfl->ident0;
+ DPRINTF("%s: Manufacturer Code %04x\n", __func__, resp);
+ break;
+ case 1:
+ resp = pfl->ident1;
+ DPRINTF("%s: Device ID Code %04x\n", __func__, resp);
+ break;
+ case 2:
+ resp = 0x00; /* Pretend all sectors are unprotected */
+ break;
+ case 0xE:
+ case 0xF:
+ resp = boff & 0x01 ? pfl->ident3 : pfl->ident2;
+ if (resp != (uint8_t)-1) {
+ break;
+ }
+ default:
+ return _flash_read(pfl, offset, width, be);
+ }
+ /* Replicate responses for each device in bank. */
+ if (pfl->device_width < pfl->bank_width) {
+ for (i = pfl->device_width;
+ i < pfl->bank_width; i += pfl->device_width) {
+ resp = deposit64(resp, 8 * i, 8 * pfl->device_width, resp);
+ }
+ }
+
+ return resp;
+}
+
static uint64_t pflash_read(pflash_t *pfl, hwaddr offset,
int width, int be)
{
+ int i;
hwaddr boff;
uint64_t ret;
@@ -215,11 +335,11 @@ static uint64_t pflash_read(pflash_t *pfl, hwaddr offset,
}
offset &= pfl->chip_len - 1;
boff = offset & 0xFF;
- if (pfl->width == 2)
+ if (pfl->bank_width == 2)
boff = boff >> 1;
- else if (pfl->width == 4)
+ else if (pfl->bank_width == 4)
boff = boff >> 2;
- else if (pfl->width == 8)
+ else if (pfl->bank_width == 8)
boff = boff >> 3;
switch (pfl->cmd) {
default:
@@ -231,47 +351,76 @@ static uint64_t pflash_read(pflash_t *pfl, hwaddr offset,
case 0x80:
/* We accept reads during second unlock sequence... */
case 0x00:
- flash_read:
- /* Flash area read */
ret = _flash_read(pfl, offset, width, be);
break;
case 0x90:
/* flash ID read */
- switch (boff) {
- case 0x00:
- case 0x01:
- ret = boff & 0x01 ? pfl->ident1 : pfl->ident0;
- break;
- case 0x02:
- ret = 0x00; /* Pretend all sectors are unprotected */
- break;
- case 0x0E:
- case 0x0F:
- ret = boff & 0x01 ? pfl->ident3 : pfl->ident2;
- if (ret == (uint8_t)-1) {
- goto flash_read;
+ if (!pfl->device_width) {
+ /* Preserve old behavior if device width not specified */
+ switch (boff) {
+ case 0x00:
+ case 0x01:
+ ret = boff & 0x01 ? pfl->ident1 : pfl->ident0;
+ break;
+ case 0x02:
+ ret = 0x00; /* Pretend all sectors are unprotected */
+ break;
+ case 0x0E:
+ case 0x0F:
+ ret = boff & 0x01 ? pfl->ident3 : pfl->ident2;
+ if (ret != (uint8_t)-1) {
+ break;
+ }
+ default:
+ ret = _flash_read(pfl, offset, width, be);
+ }
+ } else {
+ /* If we have a read larger than the bank_width, combine multiple
+ * manufacturer/device ID queries into a single response.
+ */
+ int i;
+ for (i = 0; i < width; i += pfl->bank_width) {
+ ret = deposit64(ret, i * 8, pfl->bank_width * 8,
+ pflash_devid_query(pfl,
+ offset + i * pfl->bank_width,
+ width, be));
}
- break;
- default:
- goto flash_read;
}
- DPRINTF("%s: ID " TARGET_FMT_plx " %x\n", __func__, boff, ret);
break;
case 0xA0:
case 0x10:
case 0x30:
/* Status register read */
ret = pfl->status;
- DPRINTF("%s: status %x\n", __func__, ret);
+ for (i = pfl->device_width;
+ i < pfl->bank_width; i += pfl->device_width) {
+ ret = deposit64(ret, 8 * i, 8 * pfl->device_width, ret);
+ }
+ DPRINTF("%s: status %" PRIx64 "\n", __func__, ret);
/* Toggle bit 6 */
pfl->status ^= 0x40;
break;
case 0x98:
/* CFI query mode */
- if (boff > pfl->cfi_len)
- ret = 0;
- else
- ret = pfl->cfi_table[boff];
+ if (!pfl->device_width) {
+ /* Preserve old behavior if device width not specified */
+ if (boff > pfl->cfi_len) {
+ ret = 0;
+ } else {
+ ret = pfl->cfi_table[boff];
+ }
+ } else {
+ /* If we have a read larger than the bank_width, combine multiple
+ * CFI queries into a single response.
+ */
+ int i;
+ for (i = 0; i < width; i += pfl->bank_width) {
+ ret = deposit64(ret, i * 8, pfl->bank_width * 8,
+ pflash_cfi_query(pfl,
+ offset + i *
pfl->bank_width));
+ }
+ }
+
break;
}
@@ -315,11 +464,11 @@ static void pflash_write(pflash_t *pfl, hwaddr offset,
DPRINTF("%s: offset " TARGET_FMT_plx " %08" PRIx64 " %d\n",
__func__, offset, value, width);
boff = offset & (pfl->sector_len - 1);
- if (pfl->width == 2)
+ if (pfl->bank_width == 2)
boff = boff >> 1;
- else if (pfl->width == 4)
+ else if (pfl->bank_width == 4)
boff = boff >> 2;
- else if (pfl->width == 8)
+ else if (pfl->bank_width == 8)
boff = boff >> 3;
switch (pfl->wcycle) {
case 0:
@@ -654,6 +803,13 @@ static void pflash_cfi02_realize(DeviceState *dev, Error
**errp)
}
}
+ /* Default to devices being used at their maximum device width. This was
+ * assumed before the device_width support was added.
+ */
+ if (!pfl->max_device_width) {
+ pfl->max_device_width = pfl->device_width;
+ }
+
pflash_setup_mappings(pfl);
pfl->rom_mode = 1;
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &pfl->mem);
@@ -745,7 +901,25 @@ static Property pflash_cfi02_properties[] = {
DEFINE_PROP_DRIVE("drive", struct pflash_t, blk),
DEFINE_PROP_UINT32("num-blocks", struct pflash_t, nb_blocs, 0),
DEFINE_PROP_UINT32("sector-length", struct pflash_t, sector_len, 0),
- DEFINE_PROP_UINT8("width", struct pflash_t, width, 0),
+ /* width here is the overall width of this QEMU device in bytes.
+ * The QEMU device may be emulating a number of flash devices
+ * wired up in parallel; the width of each individual flash
+ * device should be specified via device-width. If the individual
+ * devices have a maximum width which is greater than the width
+ * they are being used for, this maximum width should be set via
+ * max-device-width (which otherwise defaults to device-width).
+ * So for instance a 32-bit wide QEMU flash device made from four
+ * 16-bit flash devices used in 8-bit wide mode would be configured
+ * with width = 4, device-width = 1, max-device-width = 2.
+ *
+ * If device-width is not specified we default to backwards
+ * compatible behaviour which is a bad emulation of two
+ * 16 bit devices making up a 32 bit wide QEMU device. This
+ * is deprecated for new uses of this device.
+ */
+ DEFINE_PROP_UINT8("width", struct pflash_t, bank_width, 0),
+ DEFINE_PROP_UINT8("device-width", struct pflash_t, device_width, 0),
+ DEFINE_PROP_UINT8("max-device-width", struct pflash_t, max_device_width,
0),
DEFINE_PROP_UINT8("mappings", struct pflash_t, mappings, 0),
DEFINE_PROP_UINT8("big-endian", struct pflash_t, be, 0),
DEFINE_PROP_UINT16("id0", struct pflash_t, ident0, 0),
@@ -785,7 +959,7 @@ pflash_t *pflash_cfi02_register(hwaddr base,
DeviceState *qdev, const char *name,
hwaddr size,
BlockBackend *blk, uint32_t sector_len,
- int nb_blocs, int nb_mappings, int width,
+ int nb_blocs, int nb_mappings, int bank_width,
uint16_t id0, uint16_t id1,
uint16_t id2, uint16_t id3,
uint16_t unlock_addr0, uint16_t unlock_addr1,
@@ -798,7 +972,7 @@ pflash_t *pflash_cfi02_register(hwaddr base,
}
qdev_prop_set_uint32(dev, "num-blocks", nb_blocs);
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);
--
2.14.2
[Qemu-block] [PATCH v3 2/3] Enable 8-byte wide access to AMD CFI devices, Mike Nawrocki, 2017/11/13
[Qemu-block] [PATCH v3 3/3] Add partial flash interleaving to AMD CFI devices,
Mike Nawrocki <=