This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH v3] 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>

gdb/:
2011-03-24  Mike Frysinger  <vapier@gentoo.org>

	* NEWS: Mention new cfi device simulation.

sim/common/:
2011-03-24  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.
---
v3
	- fix style issues
	- add comments all over
	- add NEWS entry

 gdb/NEWS                  |    2 +
 sim/common/Make-common.in |    3 +
 sim/common/aclocal.m4     |    2 +-
 sim/common/dv-cfi.c       |  780 +++++++++++++++++++++++++++++++++++++++++++++
 sim/common/dv-cfi.h       |   60 ++++
 5 files changed, 846 insertions(+), 1 deletions(-)
 create mode 100644 sim/common/dv-cfi.c
 create mode 100644 sim/common/dv-cfi.h

diff --git a/gdb/NEWS b/gdb/NEWS
index 2288497..a09395c 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -182,6 +182,8 @@ Analog Devices, Inc. Blackfin Processor	bfin-*
 
   ** The --map-info flag lists all known core mappings.
 
+  ** CFI flashes may be simulated via the "cfi" device.
+
 *** Changes in GDB 7.2
 
 * Shared library support for remote targets by default
diff --git a/sim/common/Make-common.in b/sim/common/Make-common.in
index 2f0c807..42e3192 100644
--- a/sim/common/Make-common.in
+++ b/sim/common/Make-common.in
@@ -579,6 +579,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..6121d1b
--- /dev/null
+++ b/sim/common/dv-cfi.c
@@ -0,0 +1,780 @@
+/* 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;
+    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.  */
+#ifdef HAVE_MMAP
+  if (fd != -1 && fd_writable)
+    {
+      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;
+    }
+#endif
+  if (!cfi->data)
+    {
+      ssize_t read_len;
+
+      cfi->data = HW_NALLOC (me, unsigned char, cfi->dev_size);
+      if (fd != -1)
+	read_len = read (fd, cfi->data, cfi->dev_size);
+      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
-- 
1.7.4.1


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]