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>
This commit is contained in:
parent
53832f3147
commit
66ee273116
@ -1,3 +1,7 @@
|
||||
2011-03-29 Mike Frysinger <vapier@gentoo.org>
|
||||
|
||||
* NEWS: Mention new cfi device simulation.
|
||||
|
||||
2011-03-29 Tom Tromey <tromey@redhat.com>
|
||||
|
||||
* dwarf2read.c (fixup_partial_die): Handle linkage name on
|
||||
|
2
gdb/NEWS
2
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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
2
sim/common/aclocal.m4
vendored
2
sim/common/aclocal.m4
vendored
@ -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"
|
||||
|
799
sim/common/dv-cfi.c
Normal file
799
sim/common/dv-cfi.c
Normal file
@ -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, ®))
|
||||
hw_abort (me, "\"reg\" property must contain three addr/size entries");
|
||||
|
||||
hw_unit_address_to_attach_address (hw_parent (me),
|
||||
®.address,
|
||||
&attach_space, &attach_address, me);
|
||||
hw_unit_size_to_attach_size (hw_parent (me), ®.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},
|
||||
};
|
60
sim/common/dv-cfi.h
Normal file
60
sim/common/dv-cfi.h
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user