@ -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 16 bit 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.0 V ( for all )
timeouts : typ : 1 µ s , not supported , 1 ms , not supported
max : 1 µ s , 1 ms , 1 ms , 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 } ,
} ;