2005-06-16 12:49:33 +04:00
/*
* rfd_ftl . c - - resident flash disk ( flash translation layer )
*
* Copyright ( C ) 2005 Sean Young < sean @ mess . org >
*
* This type of flash translation layer ( FTL ) is used by the Embedded BIOS
* by General Software . It is known as the Resident Flash Disk ( RFD ) , see :
*
* http : //www.gensw.com/pages/prod/bios/rfd.htm
*
* based on ftl . c
*/
# include <linux/hdreg.h>
# include <linux/init.h>
# include <linux/mtd/blktrans.h>
# include <linux/mtd/mtd.h>
# include <linux/vmalloc.h>
2006-01-08 12:02:05 +03:00
# include <linux/slab.h>
2005-11-09 08:34:27 +03:00
# include <linux/jiffies.h>
2005-06-16 12:49:33 +04:00
# include <asm/types.h>
static int block_size = 0 ;
module_param ( block_size , int , 0 ) ;
MODULE_PARM_DESC ( block_size , " Block size to use by RFD, defaults to erase unit size " ) ;
# define PREFIX "rfd_ftl: "
2005-11-29 14:48:00 +03:00
/* This major has been assigned by device@lanana.org */
2005-06-16 12:49:33 +04:00
# ifndef RFD_FTL_MAJOR
2005-11-29 14:48:00 +03:00
# define RFD_FTL_MAJOR 256
2005-06-16 12:49:33 +04:00
# endif
/* Maximum number of partitions in an FTL region */
# define PART_BITS 4
/* An erase unit should start with this value */
# define RFD_MAGIC 0x9193
/* the second value is 0xffff or 0xffc8; function unknown */
/* the third value is always 0xffff, ignored */
/* next is an array of mapping for each corresponding sector */
# define HEADER_MAP_OFFSET 3
# define SECTOR_DELETED 0x0000
# define SECTOR_ZERO 0xfffe
# define SECTOR_FREE 0xffff
# define SECTOR_SIZE 512
# define SECTORS_PER_TRACK 63
struct block {
enum {
BLOCK_OK ,
BLOCK_ERASING ,
BLOCK_ERASED ,
2006-05-17 15:45:34 +04:00
BLOCK_UNUSED ,
2005-06-16 12:49:33 +04:00
BLOCK_FAILED
} state ;
int free_sectors ;
int used_sectors ;
int erases ;
u_long offset ;
} ;
struct partition {
struct mtd_blktrans_dev mbd ;
u_int block_size ; /* size of erase unit */
u_int total_blocks ; /* number of erase units */
u_int header_sectors_per_block ; /* header sectors in erase unit */
u_int data_sectors_per_block ; /* data sectors in erase unit */
u_int sector_count ; /* sectors in translated disk */
u_int header_size ; /* bytes in header sector */
int reserved_block ; /* block next up for reclaim */
int current_block ; /* block to write to */
u16 * header_cache ; /* cached header */
int is_reclaiming ;
int cylinders ;
int errors ;
u_long * sector_map ;
struct block * blocks ;
} ;
static int rfd_ftl_writesect ( struct mtd_blktrans_dev * dev , u_long sector , char * buf ) ;
static int build_block_map ( struct partition * part , int block_no )
{
struct block * block = & part - > blocks [ block_no ] ;
int i ;
2005-11-07 14:15:26 +03:00
2005-06-16 12:49:33 +04:00
block - > offset = part - > block_size * block_no ;
if ( le16_to_cpu ( part - > header_cache [ 0 ] ) ! = RFD_MAGIC ) {
2006-05-17 15:45:34 +04:00
block - > state = BLOCK_UNUSED ;
return - ENOENT ;
2005-06-16 12:49:33 +04:00
}
block - > state = BLOCK_OK ;
for ( i = 0 ; i < part - > data_sectors_per_block ; i + + ) {
u16 entry ;
2005-11-07 14:15:26 +03:00
2005-06-16 12:49:33 +04:00
entry = le16_to_cpu ( part - > header_cache [ HEADER_MAP_OFFSET + i ] ) ;
if ( entry = = SECTOR_DELETED )
continue ;
2005-11-07 14:15:26 +03:00
2005-06-16 12:49:33 +04:00
if ( entry = = SECTOR_FREE ) {
block - > free_sectors + + ;
continue ;
}
if ( entry = = SECTOR_ZERO )
entry = 0 ;
2005-11-07 14:15:26 +03:00
2005-06-16 12:49:33 +04:00
if ( entry > = part - > sector_count ) {
2006-05-17 15:45:34 +04:00
printk ( KERN_WARNING PREFIX
2005-06-16 12:49:33 +04:00
" '%s': unit #%d: entry %d corrupt, "
" sector %d out of range \n " ,
part - > mbd . mtd - > name , block_no , i , entry ) ;
continue ;
}
if ( part - > sector_map [ entry ] ! = - 1 ) {
2006-05-17 15:45:34 +04:00
printk ( KERN_WARNING PREFIX
2005-06-16 12:49:33 +04:00
" '%s': more than one entry for sector %d \n " ,
part - > mbd . mtd - > name , entry ) ;
part - > errors = 1 ;
continue ;
}
2005-11-07 14:15:26 +03:00
part - > sector_map [ entry ] = block - > offset +
2005-06-16 12:49:33 +04:00
( i + part - > header_sectors_per_block ) * SECTOR_SIZE ;
block - > used_sectors + + ;
}
if ( block - > free_sectors = = part - > data_sectors_per_block )
part - > reserved_block = block_no ;
return 0 ;
}
static int scan_header ( struct partition * part )
{
int sectors_per_block ;
int i , rc = - ENOMEM ;
int blocks_found ;
size_t retlen ;
sectors_per_block = part - > block_size / SECTOR_SIZE ;
2008-12-10 16:37:21 +03:00
part - > total_blocks = ( u32 ) part - > mbd . mtd - > size / part - > block_size ;
2005-06-16 12:49:33 +04:00
if ( part - > total_blocks < 2 )
return - ENOENT ;
/* each erase block has three bytes header, followed by the map */
2005-11-07 14:15:26 +03:00
part - > header_sectors_per_block =
( ( HEADER_MAP_OFFSET + sectors_per_block ) *
2006-05-17 15:45:34 +04:00
sizeof ( u16 ) + SECTOR_SIZE - 1 ) / SECTOR_SIZE ;
2005-06-16 12:49:33 +04:00
2005-11-07 14:15:26 +03:00
part - > data_sectors_per_block = sectors_per_block -
2005-06-16 12:49:33 +04:00
part - > header_sectors_per_block ;
2005-11-07 14:15:26 +03:00
part - > header_size = ( HEADER_MAP_OFFSET +
2005-06-16 12:49:33 +04:00
part - > data_sectors_per_block ) * sizeof ( u16 ) ;
part - > cylinders = ( part - > data_sectors_per_block *
( part - > total_blocks - 1 ) - 1 ) / SECTORS_PER_TRACK ;
part - > sector_count = part - > cylinders * SECTORS_PER_TRACK ;
part - > current_block = - 1 ;
part - > reserved_block = - 1 ;
part - > is_reclaiming = 0 ;
part - > header_cache = kmalloc ( part - > header_size , GFP_KERNEL ) ;
if ( ! part - > header_cache )
goto err ;
2005-11-07 14:15:26 +03:00
part - > blocks = kcalloc ( part - > total_blocks , sizeof ( struct block ) ,
2005-06-16 12:49:33 +04:00
GFP_KERNEL ) ;
if ( ! part - > blocks )
goto err ;
part - > sector_map = vmalloc ( part - > sector_count * sizeof ( u_long ) ) ;
if ( ! part - > sector_map ) {
printk ( KERN_ERR PREFIX " '%s': unable to allocate memory for "
" sector map " , part - > mbd . mtd - > name ) ;
goto err ;
}
2005-11-07 14:15:26 +03:00
for ( i = 0 ; i < part - > sector_count ; i + + )
2005-06-16 12:49:33 +04:00
part - > sector_map [ i ] = - 1 ;
for ( i = 0 , blocks_found = 0 ; i < part - > total_blocks ; i + + ) {
2005-11-07 14:15:26 +03:00
rc = part - > mbd . mtd - > read ( part - > mbd . mtd ,
2005-06-16 12:49:33 +04:00
i * part - > block_size , part - > header_size ,
& retlen , ( u_char * ) part - > header_cache ) ;
if ( ! rc & & retlen ! = part - > header_size )
rc = - EIO ;
2005-11-07 14:15:26 +03:00
if ( rc )
2005-06-16 12:49:33 +04:00
goto err ;
if ( ! build_block_map ( part , i ) )
blocks_found + + ;
}
if ( blocks_found = = 0 ) {
printk ( KERN_NOTICE PREFIX " no RFD magic found in '%s' \n " ,
part - > mbd . mtd - > name ) ;
rc = - ENOENT ;
goto err ;
}
if ( part - > reserved_block = = - 1 ) {
2006-05-17 15:45:34 +04:00
printk ( KERN_WARNING PREFIX " '%s': no empty erase unit found \n " ,
2005-06-16 12:49:33 +04:00
part - > mbd . mtd - > name ) ;
part - > errors = 1 ;
}
return 0 ;
err :
vfree ( part - > sector_map ) ;
kfree ( part - > header_cache ) ;
kfree ( part - > blocks ) ;
return rc ;
}
static int rfd_ftl_readsect ( struct mtd_blktrans_dev * dev , u_long sector , char * buf )
{
struct partition * part = ( struct partition * ) dev ;
u_long addr ;
size_t retlen ;
int rc ;
2005-11-07 14:15:26 +03:00
2005-06-16 12:49:33 +04:00
if ( sector > = part - > sector_count )
return - EIO ;
addr = part - > sector_map [ sector ] ;
if ( addr ! = - 1 ) {
rc = part - > mbd . mtd - > read ( part - > mbd . mtd , addr , SECTOR_SIZE ,
& retlen , ( u_char * ) buf ) ;
if ( ! rc & & retlen ! = SECTOR_SIZE )
rc = - EIO ;
if ( rc ) {
printk ( KERN_WARNING PREFIX " error reading '%s' at "
" 0x%lx \n " , part - > mbd . mtd - > name , addr ) ;
return rc ;
}
} else
memset ( buf , 0 , SECTOR_SIZE ) ;
2005-11-07 14:15:26 +03:00
2005-06-16 12:49:33 +04:00
return 0 ;
2005-11-07 14:15:26 +03:00
}
2005-06-16 12:49:33 +04:00
static void erase_callback ( struct erase_info * erase )
{
struct partition * part ;
u16 magic ;
int i , rc ;
size_t retlen ;
part = ( struct partition * ) erase - > priv ;
2008-12-10 16:37:21 +03:00
i = ( u32 ) erase - > addr / part - > block_size ;
if ( i > = part - > total_blocks | | part - > blocks [ i ] . offset ! = erase - > addr | |
erase - > addr > UINT_MAX ) {
printk ( KERN_ERR PREFIX " erase callback for unknown offset %llx "
" on '%s' \n " , ( unsigned long long ) erase - > addr , part - > mbd . mtd - > name ) ;
2005-06-16 12:49:33 +04:00
return ;
}
if ( erase - > state ! = MTD_ERASE_DONE ) {
2008-12-10 16:37:21 +03:00
printk ( KERN_WARNING PREFIX " erase failed at 0x%llx on '%s', "
" state %d \n " , ( unsigned long long ) erase - > addr ,
2005-06-16 12:49:33 +04:00
part - > mbd . mtd - > name , erase - > state ) ;
part - > blocks [ i ] . state = BLOCK_FAILED ;
part - > blocks [ i ] . free_sectors = 0 ;
part - > blocks [ i ] . used_sectors = 0 ;
kfree ( erase ) ;
return ;
}
2008-11-27 00:12:50 +03:00
magic = cpu_to_le16 ( RFD_MAGIC ) ;
2005-06-16 12:49:33 +04:00
part - > blocks [ i ] . state = BLOCK_ERASED ;
part - > blocks [ i ] . free_sectors = part - > data_sectors_per_block ;
part - > blocks [ i ] . used_sectors = 0 ;
part - > blocks [ i ] . erases + + ;
2005-11-07 14:15:26 +03:00
rc = part - > mbd . mtd - > write ( part - > mbd . mtd ,
part - > blocks [ i ] . offset , sizeof ( magic ) , & retlen ,
2005-06-16 12:49:33 +04:00
( u_char * ) & magic ) ;
2005-11-07 14:15:26 +03:00
2005-06-16 12:49:33 +04:00
if ( ! rc & & retlen ! = sizeof ( magic ) )
rc = - EIO ;
if ( rc ) {
2006-05-17 15:45:34 +04:00
printk ( KERN_ERR PREFIX " '%s': unable to write RFD "
2005-06-16 12:49:33 +04:00
" header at 0x%lx \n " ,
2005-11-07 14:15:26 +03:00
part - > mbd . mtd - > name ,
2005-06-16 12:49:33 +04:00
part - > blocks [ i ] . offset ) ;
part - > blocks [ i ] . state = BLOCK_FAILED ;
}
else
part - > blocks [ i ] . state = BLOCK_OK ;
kfree ( erase ) ;
}
static int erase_block ( struct partition * part , int block )
{
struct erase_info * erase ;
int rc = - ENOMEM ;
erase = kmalloc ( sizeof ( struct erase_info ) , GFP_KERNEL ) ;
if ( ! erase )
goto err ;
erase - > mtd = part - > mbd . mtd ;
erase - > callback = erase_callback ;
erase - > addr = part - > blocks [ block ] . offset ;
erase - > len = part - > block_size ;
erase - > priv = ( u_long ) part ;
part - > blocks [ block ] . state = BLOCK_ERASING ;
part - > blocks [ block ] . free_sectors = 0 ;
rc = part - > mbd . mtd - > erase ( part - > mbd . mtd , erase ) ;
if ( rc ) {
2008-12-10 16:37:21 +03:00
printk ( KERN_ERR PREFIX " erase of region %llx,%llx on '%s' "
" failed \n " , ( unsigned long long ) erase - > addr ,
( unsigned long long ) erase - > len , part - > mbd . mtd - > name ) ;
2005-06-16 12:49:33 +04:00
kfree ( erase ) ;
}
err :
return rc ;
}
static int move_block_contents ( struct partition * part , int block_no , u_long * old_sector )
{
void * sector_data ;
u16 * map ;
size_t retlen ;
int i , rc = - ENOMEM ;
part - > is_reclaiming = 1 ;
sector_data = kmalloc ( SECTOR_SIZE , GFP_KERNEL ) ;
if ( ! sector_data )
goto err3 ;
map = kmalloc ( part - > header_size , GFP_KERNEL ) ;
if ( ! map )
goto err2 ;
2005-11-07 14:15:26 +03:00
rc = part - > mbd . mtd - > read ( part - > mbd . mtd ,
part - > blocks [ block_no ] . offset , part - > header_size ,
2005-06-16 12:49:33 +04:00
& retlen , ( u_char * ) map ) ;
2005-11-07 14:15:26 +03:00
2005-06-16 12:49:33 +04:00
if ( ! rc & & retlen ! = part - > header_size )
rc = - EIO ;
if ( rc ) {
2006-05-17 15:45:34 +04:00
printk ( KERN_ERR PREFIX " error reading '%s' at "
2005-11-07 14:15:26 +03:00
" 0x%lx \n " , part - > mbd . mtd - > name ,
2005-06-16 12:49:33 +04:00
part - > blocks [ block_no ] . offset ) ;
goto err ;
}
for ( i = 0 ; i < part - > data_sectors_per_block ; i + + ) {
u16 entry = le16_to_cpu ( map [ HEADER_MAP_OFFSET + i ] ) ;
u_long addr ;
if ( entry = = SECTOR_FREE | | entry = = SECTOR_DELETED )
continue ;
2005-11-07 14:15:26 +03:00
if ( entry = = SECTOR_ZERO )
2005-06-16 12:49:33 +04:00
entry = 0 ;
/* already warned about and ignored in build_block_map() */
2005-11-07 14:15:26 +03:00
if ( entry > = part - > sector_count )
2005-06-16 12:49:33 +04:00
continue ;
addr = part - > blocks [ block_no ] . offset +
( i + part - > header_sectors_per_block ) * SECTOR_SIZE ;
if ( * old_sector = = addr ) {
* old_sector = - 1 ;
if ( ! part - > blocks [ block_no ] . used_sectors - - ) {
rc = erase_block ( part , block_no ) ;
break ;
}
continue ;
}
rc = part - > mbd . mtd - > read ( part - > mbd . mtd , addr ,
SECTOR_SIZE , & retlen , sector_data ) ;
2005-11-07 14:15:26 +03:00
2005-06-16 12:49:33 +04:00
if ( ! rc & & retlen ! = SECTOR_SIZE )
rc = - EIO ;
if ( rc ) {
2006-05-17 15:45:34 +04:00
printk ( KERN_ERR PREFIX " '%s': Unable to "
2005-06-16 12:49:33 +04:00
" read sector for relocation \n " ,
part - > mbd . mtd - > name ) ;
goto err ;
}
2005-11-07 14:15:26 +03:00
2005-06-16 12:49:33 +04:00
rc = rfd_ftl_writesect ( ( struct mtd_blktrans_dev * ) part ,
entry , sector_data ) ;
2005-11-07 14:15:26 +03:00
if ( rc )
2005-06-16 12:49:33 +04:00
goto err ;
}
err :
kfree ( map ) ;
err2 :
kfree ( sector_data ) ;
err3 :
part - > is_reclaiming = 0 ;
return rc ;
}
2005-11-07 14:15:26 +03:00
static int reclaim_block ( struct partition * part , u_long * old_sector )
2005-06-16 12:49:33 +04:00
{
int block , best_block , score , old_sector_block ;
int rc ;
2005-11-07 14:15:26 +03:00
2005-06-16 12:49:33 +04:00
/* we have a race if sync doesn't exist */
if ( part - > mbd . mtd - > sync )
part - > mbd . mtd - > sync ( part - > mbd . mtd ) ;
score = 0x7fffffff ; /* MAX_INT */
best_block = - 1 ;
if ( * old_sector ! = - 1 )
old_sector_block = * old_sector / part - > block_size ;
else
old_sector_block = - 1 ;
for ( block = 0 ; block < part - > total_blocks ; block + + ) {
int this_score ;
if ( block = = part - > reserved_block )
continue ;
/*
* Postpone reclaiming if there is a free sector as
* more removed sectors is more efficient ( have to move
* less ) .
*/
2005-11-07 14:15:26 +03:00
if ( part - > blocks [ block ] . free_sectors )
2005-06-16 12:49:33 +04:00
return 0 ;
this_score = part - > blocks [ block ] . used_sectors ;
2005-11-07 14:15:26 +03:00
if ( block = = old_sector_block )
2005-06-16 12:49:33 +04:00
this_score - - ;
else {
/* no point in moving a full block */
2005-11-07 14:15:26 +03:00
if ( part - > blocks [ block ] . used_sectors = =
2005-06-16 12:49:33 +04:00
part - > data_sectors_per_block )
continue ;
}
this_score + = part - > blocks [ block ] . erases ;
if ( this_score < score ) {
best_block = block ;
score = this_score ;
}
}
if ( best_block = = - 1 )
return - ENOSPC ;
part - > current_block = - 1 ;
part - > reserved_block = best_block ;
pr_debug ( " reclaim_block: reclaiming block #%d with %d used "
" %d free sectors \n " , best_block ,
part - > blocks [ best_block ] . used_sectors ,
part - > blocks [ best_block ] . free_sectors ) ;
if ( part - > blocks [ best_block ] . used_sectors )
rc = move_block_contents ( part , best_block , old_sector ) ;
else
rc = erase_block ( part , best_block ) ;
return rc ;
}
/*
* IMPROVE : It would be best to choose the block with the most deleted sectors ,
* because if we fill that one up first it ' ll have the most chance of having
* the least live sectors at reclaim .
*/
2006-05-17 15:45:34 +04:00
static int find_free_block ( struct partition * part )
2005-06-16 12:49:33 +04:00
{
int block , stop ;
block = part - > current_block = = - 1 ?
jiffies % part - > total_blocks : part - > current_block ;
stop = block ;
do {
2005-11-07 14:15:26 +03:00
if ( part - > blocks [ block ] . free_sectors & &
2005-06-16 12:49:33 +04:00
block ! = part - > reserved_block )
return block ;
2006-05-17 15:45:34 +04:00
if ( part - > blocks [ block ] . state = = BLOCK_UNUSED )
erase_block ( part , block ) ;
2005-06-16 12:49:33 +04:00
if ( + + block > = part - > total_blocks )
block = 0 ;
} while ( block ! = stop ) ;
return - 1 ;
}
2006-05-17 15:45:34 +04:00
static int find_writable_block ( struct partition * part , u_long * old_sector )
2005-06-16 12:49:33 +04:00
{
int rc , block ;
size_t retlen ;
block = find_free_block ( part ) ;
if ( block = = - 1 ) {
if ( ! part - > is_reclaiming ) {
rc = reclaim_block ( part , old_sector ) ;
if ( rc )
goto err ;
block = find_free_block ( part ) ;
}
if ( block = = - 1 ) {
rc = - ENOSPC ;
goto err ;
}
}
2005-11-07 14:15:26 +03:00
rc = part - > mbd . mtd - > read ( part - > mbd . mtd , part - > blocks [ block ] . offset ,
2005-06-16 12:49:33 +04:00
part - > header_size , & retlen , ( u_char * ) part - > header_cache ) ;
if ( ! rc & & retlen ! = part - > header_size )
rc = - EIO ;
if ( rc ) {
2006-05-17 15:45:34 +04:00
printk ( KERN_ERR PREFIX " '%s': unable to read header at "
2005-11-07 14:15:26 +03:00
" 0x%lx \n " , part - > mbd . mtd - > name ,
2005-06-16 12:49:33 +04:00
part - > blocks [ block ] . offset ) ;
goto err ;
}
part - > current_block = block ;
err :
return rc ;
2005-11-07 14:15:26 +03:00
}
2005-06-16 12:49:33 +04:00
static int mark_sector_deleted ( struct partition * part , u_long old_addr )
{
int block , offset , rc ;
u_long addr ;
size_t retlen ;
2008-11-27 00:12:50 +03:00
u16 del = cpu_to_le16 ( SECTOR_DELETED ) ;
2005-06-16 12:49:33 +04:00
block = old_addr / part - > block_size ;
2005-11-07 14:15:26 +03:00
offset = ( old_addr % part - > block_size ) / SECTOR_SIZE -
2005-06-16 12:49:33 +04:00
part - > header_sectors_per_block ;
addr = part - > blocks [ block ] . offset +
( HEADER_MAP_OFFSET + offset ) * sizeof ( u16 ) ;
rc = part - > mbd . mtd - > write ( part - > mbd . mtd , addr ,
sizeof ( del ) , & retlen , ( u_char * ) & del ) ;
if ( ! rc & & retlen ! = sizeof ( del ) )
rc = - EIO ;
if ( rc ) {
2006-05-17 15:45:34 +04:00
printk ( KERN_ERR PREFIX " error writing '%s' at "
2005-06-16 12:49:33 +04:00
" 0x%lx \n " , part - > mbd . mtd - > name , addr ) ;
2005-11-07 14:15:26 +03:00
if ( rc )
2005-06-16 12:49:33 +04:00
goto err ;
}
if ( block = = part - > current_block )
part - > header_cache [ offset + HEADER_MAP_OFFSET ] = del ;
part - > blocks [ block ] . used_sectors - - ;
if ( ! part - > blocks [ block ] . used_sectors & &
! part - > blocks [ block ] . free_sectors )
rc = erase_block ( part , block ) ;
err :
return rc ;
}
static int find_free_sector ( const struct partition * part , const struct block * block )
{
int i , stop ;
i = stop = part - > data_sectors_per_block - block - > free_sectors ;
do {
2005-11-07 14:15:26 +03:00
if ( le16_to_cpu ( part - > header_cache [ HEADER_MAP_OFFSET + i ] )
2005-06-16 12:49:33 +04:00
= = SECTOR_FREE )
return i ;
if ( + + i = = part - > data_sectors_per_block )
i = 0 ;
}
while ( i ! = stop ) ;
return - 1 ;
}
static int do_writesect ( struct mtd_blktrans_dev * dev , u_long sector , char * buf , ulong * old_addr )
{
struct partition * part = ( struct partition * ) dev ;
struct block * block ;
u_long addr ;
int i ;
int rc ;
size_t retlen ;
u16 entry ;
if ( part - > current_block = = - 1 | |
! part - > blocks [ part - > current_block ] . free_sectors ) {
2006-05-17 15:45:34 +04:00
rc = find_writable_block ( part , old_addr ) ;
2005-11-07 14:15:26 +03:00
if ( rc )
2005-06-16 12:49:33 +04:00
goto err ;
}
block = & part - > blocks [ part - > current_block ] ;
i = find_free_sector ( part , block ) ;
if ( i < 0 ) {
rc = - ENOSPC ;
goto err ;
}
2005-11-07 14:15:26 +03:00
addr = ( i + part - > header_sectors_per_block ) * SECTOR_SIZE +
2005-06-16 12:49:33 +04:00
block - > offset ;
2005-11-07 14:15:26 +03:00
rc = part - > mbd . mtd - > write ( part - > mbd . mtd ,
2005-06-16 12:49:33 +04:00
addr , SECTOR_SIZE , & retlen , ( u_char * ) buf ) ;
if ( ! rc & & retlen ! = SECTOR_SIZE )
rc = - EIO ;
if ( rc ) {
2006-05-17 15:45:34 +04:00
printk ( KERN_ERR PREFIX " error writing '%s' at 0x%lx \n " ,
2005-06-16 12:49:33 +04:00
part - > mbd . mtd - > name , addr ) ;
2005-11-07 14:15:26 +03:00
if ( rc )
2005-06-16 12:49:33 +04:00
goto err ;
}
part - > sector_map [ sector ] = addr ;
entry = cpu_to_le16 ( sector = = 0 ? SECTOR_ZERO : sector ) ;
part - > header_cache [ i + HEADER_MAP_OFFSET ] = entry ;
addr = block - > offset + ( HEADER_MAP_OFFSET + i ) * sizeof ( u16 ) ;
rc = part - > mbd . mtd - > write ( part - > mbd . mtd , addr ,
sizeof ( entry ) , & retlen , ( u_char * ) & entry ) ;
if ( ! rc & & retlen ! = sizeof ( entry ) )
rc = - EIO ;
if ( rc ) {
2006-05-17 15:45:34 +04:00
printk ( KERN_ERR PREFIX " error writing '%s' at 0x%lx \n " ,
2005-06-16 12:49:33 +04:00
part - > mbd . mtd - > name , addr ) ;
2005-11-07 14:15:26 +03:00
if ( rc )
2005-06-16 12:49:33 +04:00
goto err ;
}
block - > used_sectors + + ;
block - > free_sectors - - ;
err :
return rc ;
}
static int rfd_ftl_writesect ( struct mtd_blktrans_dev * dev , u_long sector , char * buf )
{
struct partition * part = ( struct partition * ) dev ;
u_long old_addr ;
int i ;
int rc = 0 ;
pr_debug ( " rfd_ftl_writesect(sector=0x%lx) \n " , sector ) ;
if ( part - > reserved_block = = - 1 ) {
rc = - EACCES ;
goto err ;
}
if ( sector > = part - > sector_count ) {
rc = - EIO ;
goto err ;
}
old_addr = part - > sector_map [ sector ] ;
for ( i = 0 ; i < SECTOR_SIZE ; i + + ) {
if ( ! buf [ i ] )
continue ;
rc = do_writesect ( dev , sector , buf , & old_addr ) ;
if ( rc )
goto err ;
break ;
}
2005-11-07 14:15:26 +03:00
if ( i = = SECTOR_SIZE )
2005-06-16 12:49:33 +04:00
part - > sector_map [ sector ] = - 1 ;
if ( old_addr ! = - 1 )
rc = mark_sector_deleted ( part , old_addr ) ;
err :
return rc ;
}
static int rfd_ftl_getgeo ( struct mtd_blktrans_dev * dev , struct hd_geometry * geo )
{
struct partition * part = ( struct partition * ) dev ;
geo - > heads = 1 ;
geo - > sectors = SECTORS_PER_TRACK ;
geo - > cylinders = part - > cylinders ;
return 0 ;
}
static void rfd_ftl_add_mtd ( struct mtd_blktrans_ops * tr , struct mtd_info * mtd )
{
struct partition * part ;
2008-12-10 16:37:21 +03:00
if ( mtd - > type ! = MTD_NORFLASH | | mtd - > size > UINT_MAX )
2005-06-16 12:49:33 +04:00
return ;
2006-12-13 11:34:52 +03:00
part = kzalloc ( sizeof ( struct partition ) , GFP_KERNEL ) ;
2005-06-16 12:49:33 +04:00
if ( ! part )
return ;
part - > mbd . mtd = mtd ;
if ( block_size )
part - > block_size = block_size ;
else {
if ( ! mtd - > erasesize ) {
2006-05-17 15:45:34 +04:00
printk ( KERN_WARNING PREFIX " please provide block_size " ) ;
2007-07-20 22:56:19 +04:00
goto out ;
} else
2005-06-16 12:49:33 +04:00
part - > block_size = mtd - > erasesize ;
}
if ( scan_header ( part ) = = 0 ) {
part - > mbd . size = part - > sector_count ;
part - > mbd . tr = tr ;
part - > mbd . devnum = - 1 ;
if ( ! ( mtd - > flags & MTD_WRITEABLE ) )
part - > mbd . readonly = 1 ;
else if ( part - > errors ) {
2006-05-17 15:45:34 +04:00
printk ( KERN_WARNING PREFIX " '%s': errors found, "
" setting read-only \n " , mtd - > name ) ;
2005-06-16 12:49:33 +04:00
part - > mbd . readonly = 1 ;
}
printk ( KERN_INFO PREFIX " name: '%s' type: %d flags %x \n " ,
mtd - > name , mtd - > type , mtd - > flags ) ;
if ( ! add_mtd_blktrans_dev ( ( void * ) part ) )
return ;
2005-11-07 14:15:26 +03:00
}
2007-07-20 22:56:19 +04:00
out :
2005-06-16 12:49:33 +04:00
kfree ( part ) ;
}
static void rfd_ftl_remove_dev ( struct mtd_blktrans_dev * dev )
{
struct partition * part = ( struct partition * ) dev ;
int i ;
for ( i = 0 ; i < part - > total_blocks ; i + + ) {
pr_debug ( " rfd_ftl_remove_dev:'%s': erase unit #%02d: %d erases \n " ,
part - > mbd . mtd - > name , i , part - > blocks [ i ] . erases ) ;
}
del_mtd_blktrans_dev ( dev ) ;
vfree ( part - > sector_map ) ;
kfree ( part - > header_cache ) ;
kfree ( part - > blocks ) ;
kfree ( part ) ;
}
2008-04-14 18:20:40 +04:00
static struct mtd_blktrans_ops rfd_ftl_tr = {
2005-06-16 12:49:33 +04:00
. name = " rfd " ,
. major = RFD_FTL_MAJOR ,
. part_bits = PART_BITS ,
2006-10-27 12:09:33 +04:00
. blksize = SECTOR_SIZE ,
2005-06-16 12:49:33 +04:00
. readsect = rfd_ftl_readsect ,
2005-11-07 14:15:26 +03:00
. writesect = rfd_ftl_writesect ,
2005-06-16 12:49:33 +04:00
. getgeo = rfd_ftl_getgeo ,
. add_mtd = rfd_ftl_add_mtd ,
. remove_dev = rfd_ftl_remove_dev ,
. owner = THIS_MODULE ,
} ;
static int __init init_rfd_ftl ( void )
{
return register_mtd_blktrans ( & rfd_ftl_tr ) ;
}
static void __exit cleanup_rfd_ftl ( void )
{
deregister_mtd_blktrans ( & rfd_ftl_tr ) ;
}
module_init ( init_rfd_ftl ) ;
module_exit ( cleanup_rfd_ftl ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Sean Young <sean@mess.org> " ) ;
MODULE_DESCRIPTION ( " Support code for RFD Flash Translation Layer, "
" used by General Software's Embedded BIOS " ) ;