mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-30 05:23:49 +08:00
66ee273116
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>
800 lines
22 KiB
C
800 lines
22 KiB
C
/* 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},
|
||
};
|