aboutsummaryrefslogtreecommitdiff
path: root/sim/common
diff options
context:
space:
mode:
authorMike Frysinger <vapier@gentoo.org>2011-03-29 17:57:21 +0000
committerMike Frysinger <vapier@gentoo.org>2011-03-29 17:57:21 +0000
commit66ee2731167db3db9cc506a6268d1a02ed7b3ffc (patch)
tree7a998c174e5a3fbda6664c8708ae12894ba71d2e /sim/common
parent53832f31471912a20ce0a1d0182ecd0633b3f1ec (diff)
downloadgdb-66ee2731167db3db9cc506a6268d1a02ed7b3ffc.zip
gdb-66ee2731167db3db9cc506a6268d1a02ed7b3ffc.tar.gz
gdb-66ee2731167db3db9cc506a6268d1a02ed7b3ffc.tar.bz2
sim: cfi: new flash device simulation
This simulates a CFI flash. Its pretty configurable via the device tree. For now, only basic read/write/erase operations are supported for the Intel command set, but it's easy enough to extend support. It's certainly enough to trick Das U-Boot into using it for probing, reading, writing, and erasing. Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Diffstat (limited to 'sim/common')
-rw-r--r--sim/common/ChangeLog6
-rw-r--r--sim/common/Make-common.in3
-rw-r--r--sim/common/aclocal.m42
-rw-r--r--sim/common/dv-cfi.c799
-rw-r--r--sim/common/dv-cfi.h60
5 files changed, 869 insertions, 1 deletions
diff --git a/sim/common/ChangeLog b/sim/common/ChangeLog
index a80ec8f..99ce8d8 100644
--- a/sim/common/ChangeLog
+++ b/sim/common/ChangeLog
@@ -1,3 +1,9 @@
+2011-03-29 Mike Frysinger <vapier@gentoo.org>
+
+ * aclocal.m4 (SIM_AC_OPTION_HARDWARE): Add cfi to default list.
+ * Make-common.in (dv-cfi.o): New rule.
+ * dv-cfi.c, dv-cfi.h: New files.
+
2011-03-21 Kevin Buettner <kevinb@redhat.com>
* gennltvals.sh: Search sys/_default_fcntl.h, in addition to
diff --git a/sim/common/Make-common.in b/sim/common/Make-common.in
index 13aebe8..351f4a0 100644
--- a/sim/common/Make-common.in
+++ b/sim/common/Make-common.in
@@ -575,6 +575,9 @@ hw-tree.o: $(srccom)/hw-tree.c $(hw_main_headers) $(hw-tree_h)
# Devices.
+dv-cfi.o: $(srccom)/dv-cfi.c $(hw_main_headers) $(sim_main_headers)
+ $(CC) -c $(srccom)/dv-cfi.c $(ALL_CFLAGS)
+
dv-core.o: $(srccom)/dv-core.c $(hw_main_headers) $(sim_main_headers)
$(CC) -c $(srccom)/dv-core.c $(ALL_CFLAGS)
diff --git a/sim/common/aclocal.m4 b/sim/common/aclocal.m4
index bda20ce..cec0155 100644
--- a/sim/common/aclocal.m4
+++ b/sim/common/aclocal.m4
@@ -573,7 +573,7 @@ fi
if test "[$2]"; then
hardware="[$2]"
else
- hardware="core pal glue"
+ hardware="cfi core pal glue"
fi
hardware="$hardware [$3]"
sim_hw_cflags="-DWITH_HW=1"
diff --git a/sim/common/dv-cfi.c b/sim/common/dv-cfi.c
new file mode 100644
index 0000000..52dcf45
--- /dev/null
+++ b/sim/common/dv-cfi.c
@@ -0,0 +1,799 @@
+/* Common Flash Memory Interface (CFI) model.
+ http://www.spansion.com/Support/AppNotes/CFI_Spec_AN_03.pdf
+ http://www.spansion.com/Support/AppNotes/cfi_100_20011201.pdf
+
+ Copyright (C) 2010-2011 Free Software Foundation, Inc.
+ Contributed by Analog Devices, Inc.
+
+ This file is part of simulators.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* TODO: support vendor query tables. */
+
+#include "cconfig.h"
+
+#include <math.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+
+#include "sim-main.h"
+#include "devices.h"
+#include "dv-cfi.h"
+
+/* Flashes are simple state machines, so here we cover all the
+ different states a device might be in at any particular time. */
+enum cfi_state
+{
+ CFI_STATE_READ,
+ CFI_STATE_READ_ID,
+ CFI_STATE_CFI_QUERY,
+ CFI_STATE_PROTECT,
+ CFI_STATE_STATUS,
+ CFI_STATE_ERASE,
+ CFI_STATE_WRITE,
+ CFI_STATE_WRITE_BUFFER,
+ CFI_STATE_WRITE_BUFFER_CONFIRM,
+};
+
+/* This is the structure that all CFI conforming devices must provided
+ when asked for it. This allows a single driver to dynamically support
+ different flash geometries without having to hardcode specs.
+
+ If you want to start mucking about here, you should just grab the
+ CFI spec and review that (see top of this file for URIs). */
+struct cfi_query
+{
+ /* This is always 'Q' 'R' 'Y'. */
+ unsigned char qry[3];
+ /* Primary vendor ID. */
+ unsigned char p_id[2];
+ /* Primary query table address. */
+ unsigned char p_adr[2];
+ /* Alternate vendor ID. */
+ unsigned char a_id[2];
+ /* Alternate query table address. */
+ unsigned char a_adr[2];
+ union
+ {
+ /* Voltage levels. */
+ unsigned char voltages[4];
+ struct
+ {
+ /* Normal min voltage level. */
+ unsigned char vcc_min;
+ /* Normal max voltage level. */
+ unsigned char vcc_max;
+ /* Programming min volage level. */
+ unsigned char vpp_min;
+ /* Programming max volage level. */
+ unsigned char vpp_max;
+ };
+ };
+ union
+ {
+ /* Operational timeouts. */
+ unsigned char timeouts[8];
+ struct
+ {
+ /* Typical timeout for writing a single "unit". */
+ unsigned char timeout_typ_unit_write;
+ /* Typical timeout for writing a single "buffer". */
+ unsigned char timeout_typ_buf_write;
+ /* Typical timeout for erasing a block. */
+ unsigned char timeout_typ_block_erase;
+ /* Typical timeout for erasing the chip. */
+ unsigned char timeout_typ_chip_erase;
+ /* Max timeout for writing a single "unit". */
+ unsigned char timeout_max_unit_write;
+ /* Max timeout for writing a single "buffer". */
+ unsigned char timeout_max_buf_write;
+ /* Max timeout for erasing a block. */
+ unsigned char timeout_max_block_erase;
+ /* Max timeout for erasing the chip. */
+ unsigned char timeout_max_chip_erase;
+ };
+ };
+ /* Flash size is 2^dev_size bytes. */
+ unsigned char dev_size;
+ /* Flash device interface description. */
+ unsigned char iface_desc[2];
+ /* Max length of a single buffer write is 2^max_buf_write_len bytes. */
+ unsigned char max_buf_write_len[2];
+ /* Number of erase regions. */
+ unsigned char num_erase_regions;
+ /* The erase regions would now be an array after this point, but since
+ it is dynamic, we'll provide that from "struct cfi" when requested. */
+ /*unsigned char erase_region_info;*/
+};
+
+/* Flashes may have regions with different erase sizes. There is one
+ structure per erase region. */
+struct cfi_erase_region
+{
+ unsigned blocks;
+ unsigned size;
+ unsigned start;
+ unsigned end;
+};
+
+struct cfi;
+
+/* Flashes are accessed via commands -- you write a certain number to
+ a special address to change the flash state and access info other
+ than the data. Diff companies have implemented their own command
+ set. This structure abstracts the different command sets so that
+ we can support multiple ones with just a single sim driver. */
+struct cfi_cmdset
+{
+ unsigned id;
+ void (*setup) (struct hw *me, struct cfi *cfi);
+ bool (*write) (struct hw *me, struct cfi *cfi, const void *source,
+ unsigned offset, unsigned value, unsigned nr_bytes);
+ bool (*read) (struct hw *me, struct cfi *cfi, void *dest,
+ unsigned offset, unsigned shifted_offset, unsigned nr_bytes);
+};
+
+/* The per-flash state. Much of this comes from the device tree which
+ people declare themselves. See top of attach_cfi_regs() for more
+ info. */
+struct cfi
+{
+ unsigned width, dev_size, status;
+ enum cfi_state state;
+ unsigned char *data, *mmap;
+
+ struct cfi_query query;
+ const struct cfi_cmdset *cmdset;
+
+ unsigned char *erase_region_info;
+ struct cfi_erase_region *erase_regions;
+};
+
+/* Helpful strings which are used with HW_TRACE. */
+static const char * const state_names[] =
+{
+ "READ", "READ_ID", "CFI_QUERY", "PROTECT", "STATUS", "ERASE", "WRITE",
+ "WRITE_BUFFER", "WRITE_BUFFER_CONFIRM",
+};
+
+/* Erase the block specified by the offset into the given CFI flash. */
+static void
+cfi_erase_block (struct hw *me, struct cfi *cfi, unsigned offset)
+{
+ unsigned i;
+ struct cfi_erase_region *region;
+
+ /* If no erase regions, then we can only do whole chip erase. */
+ /* XXX: Is this within spec ? Or must there always be at least one ? */
+ if (!cfi->query.num_erase_regions)
+ memset (cfi->data, 0xff, cfi->dev_size);
+
+ for (i = 0; i < cfi->query.num_erase_regions; ++i)
+ {
+ region = &cfi->erase_regions[i];
+
+ if (offset >= region->end)
+ continue;
+
+ /* XXX: Does spec require the erase addr to be erase block aligned ?
+ Maybe this is check is overly cautious ... */
+ offset &= ~(region->size - 1);
+ memset (cfi->data + offset, 0xff, region->size);
+ break;
+ }
+}
+
+/* Depending on the bus width, addresses might be bit shifted. This
+ helps us normalize everything without cluttering up the rest of
+ the code. */
+static unsigned
+cfi_unshift_addr (struct cfi *cfi, unsigned addr)
+{
+ switch (cfi->width)
+ {
+ case 4: addr >>= 1; /* fallthrough. */
+ case 2: addr >>= 1;
+ }
+ return addr;
+}
+
+/* CFI requires all values to be little endian in its structure, so
+ this helper writes a 16bit value into a little endian byte buffer. */
+static void
+cfi_encode_16bit (unsigned char *data, unsigned num)
+{
+ data[0] = num;
+ data[1] = num >> 8;
+}
+
+/* The functions required to implement the Intel command set. */
+
+static bool
+cmdset_intel_write (struct hw *me, struct cfi *cfi, const void *source,
+ unsigned offset, unsigned value, unsigned nr_bytes)
+{
+ switch (cfi->state)
+ {
+ case CFI_STATE_READ:
+ case CFI_STATE_READ_ID:
+ switch (value)
+ {
+ case INTEL_CMD_ERASE_BLOCK:
+ cfi->state = CFI_STATE_ERASE;
+ break;
+ case INTEL_CMD_WRITE:
+ case INTEL_CMD_WRITE_ALT:
+ cfi->state = CFI_STATE_WRITE;
+ break;
+ case INTEL_CMD_STATUS_CLEAR:
+ cfi->status = INTEL_SR_DWS;
+ break;
+ case INTEL_CMD_LOCK_SETUP:
+ cfi->state = CFI_STATE_PROTECT;
+ break;
+ default:
+ return false;
+ }
+ break;
+
+ case CFI_STATE_ERASE:
+ if (value == INTEL_CMD_ERASE_CONFIRM)
+ {
+ cfi_erase_block (me, cfi, offset);
+ cfi->status &= ~(INTEL_SR_PS | INTEL_SR_ES);
+ }
+ else
+ cfi->status |= INTEL_SR_PS | INTEL_SR_ES;
+ cfi->state = CFI_STATE_STATUS;
+ break;
+
+ case CFI_STATE_PROTECT:
+ switch (value)
+ {
+ case INTEL_CMD_LOCK_BLOCK:
+ case INTEL_CMD_UNLOCK_BLOCK:
+ case INTEL_CMD_LOCK_DOWN_BLOCK:
+ /* XXX: Handle the command. */
+ break;
+ default:
+ /* Kick out. */
+ cfi->status |= INTEL_SR_PS | INTEL_SR_ES;
+ break;
+ }
+ cfi->state = CFI_STATE_STATUS;
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+cmdset_intel_read (struct hw *me, struct cfi *cfi, void *dest,
+ unsigned offset, unsigned shifted_offset, unsigned nr_bytes)
+{
+ unsigned char *sdest = dest;
+
+ switch (cfi->state)
+ {
+ case CFI_STATE_STATUS:
+ case CFI_STATE_ERASE:
+ *sdest = cfi->status;
+ break;
+
+ case CFI_STATE_READ_ID:
+ switch (shifted_offset & 0x1ff)
+ {
+ case 0x00: /* Manufacturer Code. */
+ cfi_encode_16bit (dest, INTEL_ID_MANU);
+ break;
+ case 0x01: /* Device ID Code. */
+ /* XXX: Push to device tree ? */
+ cfi_encode_16bit (dest, 0xad);
+ break;
+ case 0x02: /* Block lock state. */
+ /* XXX: This is per-block ... */
+ *sdest = 0x00;
+ break;
+ case 0x05: /* Read Configuration Register. */
+ cfi_encode_16bit (dest, (1 << 15));
+ break;
+ default:
+ return false;
+ }
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+static void
+cmdset_intel_setup (struct hw *me, struct cfi *cfi)
+{
+ cfi->status = INTEL_SR_DWS;
+}
+
+static const struct cfi_cmdset cfi_cmdset_intel =
+{
+ CFI_CMDSET_INTEL, cmdset_intel_setup, cmdset_intel_write, cmdset_intel_read,
+};
+
+/* All of the supported command sets get listed here. We then walk this
+ array to see if the user requested command set is implemented. */
+static const struct cfi_cmdset * const cfi_cmdsets[] =
+{
+ &cfi_cmdset_intel,
+};
+
+/* All writes to the flash address space come here. Using the state
+ machine, we figure out what to do with this specific write. All
+ common code sits here and if there is a request we can't process,
+ we hand it off to the command set-specific write function. */
+static unsigned
+cfi_io_write_buffer (struct hw *me, const void *source, int space,
+ address_word addr, unsigned nr_bytes)
+{
+ struct cfi *cfi = hw_data (me);
+ const unsigned char *ssource = source;
+ enum cfi_state old_state;
+ unsigned offset, shifted_offset, value;
+
+ offset = addr & (cfi->dev_size - 1);
+ shifted_offset = cfi_unshift_addr (cfi, offset);
+
+ if (cfi->width != nr_bytes)
+ {
+ HW_TRACE ((me, "write 0x%08lx length %u does not match flash width %u",
+ (unsigned long) addr, nr_bytes, cfi->width));
+ return nr_bytes;
+ }
+
+ if (cfi->state == CFI_STATE_WRITE)
+ {
+ /* NOR flash can only go from 1 to 0. */
+ unsigned i;
+
+ HW_TRACE ((me, "program %#x length %u", offset, nr_bytes));
+
+ for (i = 0; i < nr_bytes; ++i)
+ cfi->data[offset + i] &= ssource[i];
+
+ cfi->state = CFI_STATE_STATUS;
+
+ return nr_bytes;
+ }
+
+ value = ssource[0];
+
+ old_state = cfi->state;
+
+ if (value == CFI_CMD_READ || value == CFI_CMD_RESET)
+ {
+ cfi->state = CFI_STATE_READ;
+ goto done;
+ }
+
+ switch (cfi->state)
+ {
+ case CFI_STATE_READ:
+ case CFI_STATE_READ_ID:
+ if (value == CFI_CMD_CFI_QUERY)
+ {
+ if (shifted_offset == CFI_ADDR_CFI_QUERY_START)
+ cfi->state = CFI_STATE_CFI_QUERY;
+ goto done;
+ }
+
+ if (value == CFI_CMD_READ_ID)
+ {
+ cfi->state = CFI_STATE_READ_ID;
+ goto done;
+ }
+
+ /* Fall through. */
+
+ default:
+ if (!cfi->cmdset->write (me, cfi, source, offset, value, nr_bytes))
+ HW_TRACE ((me, "unhandled command %#x at %#x", value, offset));
+ break;
+ }
+
+ done:
+ HW_TRACE ((me, "write 0x%08lx command {%#x,%#x,%#x,%#x}; state %s -> %s",
+ (unsigned long) addr, ssource[0],
+ nr_bytes > 1 ? ssource[1] : 0,
+ nr_bytes > 2 ? ssource[2] : 0,
+ nr_bytes > 3 ? ssource[3] : 0,
+ state_names[old_state], state_names[cfi->state]));
+
+ return nr_bytes;
+}
+
+/* All reads to the flash address space come here. Using the state
+ machine, we figure out what to return -- actual data stored in the
+ flash, the CFI query structure, some status info, or something else ?
+ Any requests that we can't handle are passed to the command set-
+ specific read function. */
+static unsigned
+cfi_io_read_buffer (struct hw *me, void *dest, int space,
+ address_word addr, unsigned nr_bytes)
+{
+ struct cfi *cfi = hw_data (me);
+ unsigned char *sdest = dest;
+ unsigned offset, shifted_offset;
+
+ offset = addr & (cfi->dev_size - 1);
+ shifted_offset = cfi_unshift_addr (cfi, offset);
+
+ /* XXX: Is this OK to enforce ? */
+#if 0
+ if (cfi->state != CFI_STATE_READ && cfi->width != nr_bytes)
+ {
+ HW_TRACE ((me, "read 0x%08lx length %u does not match flash width %u",
+ (unsigned long) addr, nr_bytes, cfi->width));
+ return nr_bytes;
+ }
+#endif
+
+ HW_TRACE ((me, "%s read 0x%08lx length %u",
+ state_names[cfi->state], (unsigned long) addr, nr_bytes));
+
+ switch (cfi->state)
+ {
+ case CFI_STATE_READ:
+ memcpy (dest, cfi->data + offset, nr_bytes);
+ break;
+
+ case CFI_STATE_CFI_QUERY:
+ if (shifted_offset >= CFI_ADDR_CFI_QUERY_RESULT &&
+ shifted_offset < CFI_ADDR_CFI_QUERY_RESULT + sizeof (cfi->query) +
+ (cfi->query.num_erase_regions * 4))
+ {
+ unsigned char *qry;
+
+ shifted_offset -= CFI_ADDR_CFI_QUERY_RESULT;
+ if (shifted_offset >= sizeof (cfi->query))
+ {
+ qry = cfi->erase_region_info;
+ shifted_offset -= sizeof (cfi->query);
+ }
+ else
+ qry = (void *) &cfi->query;
+
+ sdest[0] = qry[shifted_offset];
+ memset (sdest + 1, 0, nr_bytes - 1);
+
+ break;
+ }
+
+ default:
+ if (!cfi->cmdset->read (me, cfi, dest, offset, shifted_offset, nr_bytes))
+ HW_TRACE ((me, "unhandled state %s", state_names[cfi->state]));
+ break;
+ }
+
+ return nr_bytes;
+}
+
+/* Clean up any state when this device is removed (e.g. when shutting
+ down, or when reloading via gdb). */
+static void
+cfi_delete_callback (struct hw *me)
+{
+#ifdef HAVE_MMAP
+ struct cfi *cfi = hw_data (me);
+
+ if (cfi->mmap)
+ munmap (cfi->mmap, cfi->dev_size);
+#endif
+}
+
+/* Helper function to easily add CFI erase regions to the existing set. */
+static void
+cfi_add_erase_region (struct hw *me, struct cfi *cfi,
+ unsigned blocks, unsigned size)
+{
+ unsigned num_regions = cfi->query.num_erase_regions;
+ struct cfi_erase_region *region;
+ unsigned char *qry_region;
+
+ /* Store for our own usage. */
+ region = &cfi->erase_regions[num_regions];
+ region->blocks = blocks;
+ region->size = size;
+ if (num_regions == 0)
+ region->start = 0;
+ else
+ region->start = region[-1].end;
+ region->end = region->start + (blocks * size);
+
+ /* Regions are 4 bytes long. */
+ qry_region = cfi->erase_region_info + 4 * num_regions;
+
+ /* [0][1] = number erase blocks - 1 */
+ if (blocks > 0xffff + 1)
+ hw_abort (me, "erase blocks %u too big to fit into region info", blocks);
+ cfi_encode_16bit (&qry_region[0], blocks - 1);
+
+ /* [2][3] = block size / 256 bytes */
+ if (size > 0xffff * 256)
+ hw_abort (me, "erase size %u too big to fit into region info", size);
+ cfi_encode_16bit (&qry_region[2], size / 256);
+
+ /* Yet another region. */
+ cfi->query.num_erase_regions = num_regions + 1;
+}
+
+/* Device tree options:
+ Required:
+ .../reg <addr> <len>
+ .../cmdset <primary; integer> [alt; integer]
+ Optional:
+ .../size <device size (must be pow of 2)>
+ .../width <8|16|32>
+ .../write_size <integer (must be pow of 2)>
+ .../erase_regions <number blocks> <block size> \
+ [<number blocks> <block size> ...]
+ .../voltage <vcc min> <vcc max> <vpp min> <vpp max>
+ .../timeouts <typ unit write> <typ buf write> \
+ <typ block erase> <typ chip erase> \
+ <max unit write> <max buf write> \
+ <max block erase> <max chip erase>
+ .../file <file> [ro|rw]
+ Defaults:
+ size: <len> from "reg"
+ width: 8
+ write_size: 0 (not supported)
+ erase_region: 1 (can only erase whole chip)
+ voltage: 0.0V (for all)
+ timeouts: typ: 1µs, not supported, 1ms, not supported
+ max: 1µs, 1ms, 1ms, not supported
+
+ TODO: Verify user args are valid (e.g. voltage is 8 bits). */
+static void
+attach_cfi_regs (struct hw *me, struct cfi *cfi)
+{
+ address_word attach_address;
+ int attach_space;
+ unsigned attach_size;
+ reg_property_spec reg;
+ bool fd_writable;
+ int i, ret, fd;
+ signed_cell ival;
+
+ if (hw_find_property (me, "reg") == NULL)
+ hw_abort (me, "Missing \"reg\" property");
+ if (hw_find_property (me, "cmdset") == NULL)
+ hw_abort (me, "Missing \"cmdset\" property");
+
+ if (!hw_find_reg_array_property (me, "reg", 0, &reg))
+ hw_abort (me, "\"reg\" property must contain three addr/size entries");
+
+ hw_unit_address_to_attach_address (hw_parent (me),
+ &reg.address,
+ &attach_space, &attach_address, me);
+ hw_unit_size_to_attach_size (hw_parent (me), &reg.size, &attach_size, me);
+
+ hw_attach_address (hw_parent (me),
+ 0, attach_space, attach_address, attach_size, me);
+
+ /* Extract the desired flash command set. */
+ ret = hw_find_integer_array_property (me, "cmdset", 0, &ival);
+ if (ret != 1 && ret != 2)
+ hw_abort (me, "\"cmdset\" property takes 1 or 2 entries");
+ cfi_encode_16bit (cfi->query.p_id, ival);
+
+ for (i = 0; i < ARRAY_SIZE (cfi_cmdsets); ++i)
+ if (cfi_cmdsets[i]->id == ival)
+ cfi->cmdset = cfi_cmdsets[i];
+ if (cfi->cmdset == NULL)
+ hw_abort (me, "cmdset %u not supported", ival);
+
+ if (ret == 2)
+ {
+ hw_find_integer_array_property (me, "cmdset", 1, &ival);
+ cfi_encode_16bit (cfi->query.a_id, ival);
+ }
+
+ /* Extract the desired device size. */
+ if (hw_find_property (me, "size"))
+ cfi->dev_size = hw_find_integer_property (me, "size");
+ else
+ cfi->dev_size = attach_size;
+ cfi->query.dev_size = log2 (cfi->dev_size);
+
+ /* Extract the desired flash width. */
+ if (hw_find_property (me, "width"))
+ {
+ cfi->width = hw_find_integer_property (me, "width");
+ if (cfi->width != 8 && cfi->width != 16 && cfi->width != 32)
+ hw_abort (me, "\"width\" must be 8 or 16 or 32, not %u", cfi->width);
+ }
+ else
+ /* Default to 8 bit. */
+ cfi->width = 8;
+ /* Turn 8/16/32 into 1/2/4. */
+ cfi->width /= 8;
+
+ /* Extract optional write buffer size. */
+ if (hw_find_property (me, "write_size"))
+ {
+ ival = hw_find_integer_property (me, "write_size");
+ cfi_encode_16bit (cfi->query.max_buf_write_len, log2 (ival));
+ }
+
+ /* Extract optional erase regions. */
+ if (hw_find_property (me, "erase_regions"))
+ {
+ ret = hw_find_integer_array_property (me, "erase_regions", 0, &ival);
+ if (ret % 2)
+ hw_abort (me, "\"erase_regions\" must be specified in sets of 2");
+
+ cfi->erase_region_info = HW_NALLOC (me, unsigned char, ret / 2);
+ cfi->erase_regions = HW_NALLOC (me, struct cfi_erase_region, ret / 2);
+
+ for (i = 0; i < ret; i += 2)
+ {
+ unsigned blocks, size;
+
+ hw_find_integer_array_property (me, "erase_regions", i, &ival);
+ blocks = ival;
+
+ hw_find_integer_array_property (me, "erase_regions", i + 1, &ival);
+ size = ival;
+
+ cfi_add_erase_region (me, cfi, blocks, size);
+ }
+ }
+
+ /* Extract optional voltages. */
+ if (hw_find_property (me, "voltage"))
+ {
+ unsigned num = ARRAY_SIZE (cfi->query.voltages);
+
+ ret = hw_find_integer_array_property (me, "voltage", 0, &ival);
+ if (ret > num)
+ hw_abort (me, "\"voltage\" may have only %u arguments", num);
+
+ for (i = 0; i < ret; ++i)
+ {
+ hw_find_integer_array_property (me, "voltage", i, &ival);
+ cfi->query.voltages[i] = ival;
+ }
+ }
+
+ /* Extract optional timeouts. */
+ if (hw_find_property (me, "timeout"))
+ {
+ unsigned num = ARRAY_SIZE (cfi->query.timeouts);
+
+ ret = hw_find_integer_array_property (me, "timeout", 0, &ival);
+ if (ret > num)
+ hw_abort (me, "\"timeout\" may have only %u arguments", num);
+
+ for (i = 0; i < ret; ++i)
+ {
+ hw_find_integer_array_property (me, "timeout", i, &ival);
+ cfi->query.timeouts[i] = ival;
+ }
+ }
+
+ /* Extract optional file. */
+ fd = -1;
+ fd_writable = false;
+ if (hw_find_property (me, "file"))
+ {
+ const char *file;
+
+ ret = hw_find_string_array_property (me, "file", 0, &file);
+ if (ret > 2)
+ hw_abort (me, "\"file\" may take only one argument");
+ if (ret == 2)
+ {
+ const char *writable;
+
+ hw_find_string_array_property (me, "file", 1, &writable);
+ fd_writable = !strcmp (writable, "rw");
+ }
+
+ fd = open (file, fd_writable ? O_RDWR : O_RDONLY);
+ if (fd < 0)
+ hw_abort (me, "unable to read file `%s': %s", file, strerror (errno));
+ }
+
+ /* Figure out where our initial flash data is coming from. */
+ if (fd != -1 && fd_writable)
+ {
+#ifdef HAVE_MMAP
+ posix_fallocate (fd, 0, cfi->dev_size);
+
+ cfi->mmap = mmap (NULL, cfi->dev_size,
+ PROT_READ | (fd_writable ? PROT_WRITE : 0),
+ MAP_SHARED, fd, 0);
+
+ if (cfi->mmap == MAP_FAILED)
+ cfi->mmap = NULL;
+ else
+ cfi->data = cfi->mmap;
+#else
+ sim_io_eprintf (hw_system (me),
+ "cfi: sorry, file write support requires mmap()\n");
+#endif
+ }
+ if (!cfi->data)
+ {
+ size_t read_len;
+
+ cfi->data = HW_NALLOC (me, unsigned char, cfi->dev_size);
+
+ if (fd != -1)
+ {
+ /* Use stdio to avoid EINTR issues with read(). */
+ FILE *fp = fdopen (fd, "r");
+
+ if (fp)
+ read_len = fread (cfi->data, 1, cfi->dev_size, fp);
+ else
+ read_len = 0;
+
+ /* Don't need to fclose() with fdopen("r"). */
+ }
+ else
+ read_len = 0;
+
+ memset (cfi->data, 0xff, cfi->dev_size - read_len);
+ }
+
+ close (fd);
+}
+
+/* Once we've been declared in the device tree, this is the main
+ entry point. So allocate state, attach memory addresses, and
+ all that fun stuff. */
+static void
+cfi_finish (struct hw *me)
+{
+ struct cfi *cfi;
+
+ cfi = HW_ZALLOC (me, struct cfi);
+
+ set_hw_data (me, cfi);
+ set_hw_io_read_buffer (me, cfi_io_read_buffer);
+ set_hw_io_write_buffer (me, cfi_io_write_buffer);
+ set_hw_delete (me, cfi_delete_callback);
+
+ attach_cfi_regs (me, cfi);
+
+ /* Initialize the CFI. */
+ cfi->state = CFI_STATE_READ;
+ memcpy (cfi->query.qry, "QRY", 3);
+ cfi->cmdset->setup (me, cfi);
+}
+
+/* Every device is required to declare this. */
+const struct hw_descriptor dv_cfi_descriptor[] =
+{
+ {"cfi", cfi_finish,},
+ {NULL, NULL},
+};
diff --git a/sim/common/dv-cfi.h b/sim/common/dv-cfi.h
new file mode 100644
index 0000000..bd2db2d
--- /dev/null
+++ b/sim/common/dv-cfi.h
@@ -0,0 +1,60 @@
+/* Common Flash Memory Interface (CFI) model.
+
+ Copyright (C) 2010 Free Software Foundation, Inc.
+ Contributed by Analog Devices, Inc.
+
+ This file is part of simulators.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef DV_CFI_H
+#define DV_CFI_H
+
+/* CFI standard. */
+#define CFI_CMD_CFI_QUERY 0x98
+#define CFI_ADDR_CFI_QUERY_START 0x55
+#define CFI_ADDR_CFI_QUERY_RESULT 0x10
+
+#define CFI_CMD_READ 0xFF
+#define CFI_CMD_RESET 0xF0
+#define CFI_CMD_READ_ID 0x90
+
+/* Intel specific. */
+#define CFI_CMDSET_INTEL 0x0001
+#define INTEL_CMD_STATUS_CLEAR 0x50
+#define INTEL_CMD_STATUS_READ 0x70
+#define INTEL_CMD_WRITE 0x40
+#define INTEL_CMD_WRITE_ALT 0x10
+#define INTEL_CMD_WRITE_BUFFER 0xE8
+#define INTEL_CMD_WRITE_BUFFER_CONFIRM 0xD0
+#define INTEL_CMD_LOCK_SETUP 0x60
+#define INTEL_CMD_LOCK_BLOCK 0x01
+#define INTEL_CMD_UNLOCK_BLOCK 0xD0
+#define INTEL_CMD_LOCK_DOWN_BLOCK 0x2F
+#define INTEL_CMD_ERASE_BLOCK 0x20
+#define INTEL_CMD_ERASE_CONFIRM 0xD0
+
+/* Intel Status Register bits. */
+#define INTEL_SR_BWS (1 << 0) /* BEFP Write. */
+#define INTEL_SR_BLS (1 << 1) /* Block Locked. */
+#define INTEL_SR_PSS (1 << 2) /* Program Suspend. */
+#define INTEL_SR_VPPS (1 << 3) /* Vpp. */
+#define INTEL_SR_PS (1 << 4) /* Program. */
+#define INTEL_SR_ES (1 << 5) /* Erase. */
+#define INTEL_SR_ESS (1 << 6) /* Erase Suspend. */
+#define INTEL_SR_DWS (1 << 7) /* Device Write. */
+
+#define INTEL_ID_MANU 0x89
+
+#endif