2005-11-07 14:15:26 +03:00
/*
2005-04-17 02:20:36 +04:00
* NFTL mount code with extensive checks
*
2005-11-07 14:15:26 +03:00
* Author : Fabrice Bellard ( fabrice . bellard @ netgem . com )
2005-04-17 02:20:36 +04:00
* Copyright ( C ) 2000 Netgem S . A .
*
2005-11-07 14:15:26 +03:00
* $ Id : nftlmount . c , v 1.41 2005 / 11 / 07 11 : 14 : 21 gleixner Exp $
2005-04-17 02:20:36 +04:00
*
* 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 2 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 , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/kernel.h>
# include <asm/errno.h>
# include <linux/delay.h>
# include <linux/slab.h>
# include <linux/mtd/mtd.h>
# include <linux/mtd/nand.h>
# include <linux/mtd/nftl.h>
# define SECTORSIZE 512
2005-11-07 14:15:26 +03:00
char nftlmountrev [ ] = " $Revision: 1.41 $ " ;
2005-04-17 02:20:36 +04:00
2006-05-29 05:26:58 +04:00
extern int nftl_read_oob ( struct mtd_info * mtd , loff_t offs , size_t len ,
size_t * retlen , uint8_t * buf ) ;
extern int nftl_write_oob ( struct mtd_info * mtd , loff_t offs , size_t len ,
size_t * retlen , uint8_t * buf ) ;
2005-04-17 02:20:36 +04:00
/* find_boot_record: Find the NFTL Media Header and its Spare copy which contains the
* various device information of the NFTL partition and Bad Unit Table . Update
* the ReplUnitTable [ ] table accroding to the Bad Unit Table . ReplUnitTable [ ]
* is used for management of Erase Unit in other routines in nftl . c and nftlmount . c
*/
static int find_boot_record ( struct NFTLrecord * nftl )
{
struct nftl_uci1 h1 ;
unsigned int block , boot_record_count = 0 ;
size_t retlen ;
u8 buf [ SECTORSIZE ] ;
struct NFTLMediaHeader * mh = & nftl - > MediaHdr ;
2006-05-28 13:01:53 +04:00
struct mtd_info * mtd = nftl - > mbd . mtd ;
2005-04-17 02:20:36 +04:00
unsigned int i ;
2005-11-07 14:15:26 +03:00
/* Assume logical EraseSize == physical erasesize for starting the scan.
2005-04-17 02:20:36 +04:00
We ' ll sort it out later if we find a MediaHeader which says otherwise */
/* Actually, we won't. The new DiskOnChip driver has already scanned
the MediaHeader and adjusted the virtual erasesize it presents in
the mtd device accordingly . We could even get rid of
nftl - > EraseSize if there were any point in doing so . */
nftl - > EraseSize = nftl - > mbd . mtd - > erasesize ;
nftl - > nb_blocks = nftl - > mbd . mtd - > size / nftl - > EraseSize ;
nftl - > MediaUnit = BLOCK_NIL ;
nftl - > SpareMediaUnit = BLOCK_NIL ;
/* search for a valid boot record */
for ( block = 0 ; block < nftl - > nb_blocks ; block + + ) {
int ret ;
/* Check for ANAND header first. Then can whinge if it's found but later
checks fail */
2006-05-28 13:01:53 +04:00
ret = mtd - > read ( mtd , block * nftl - > EraseSize , SECTORSIZE ,
& retlen , buf ) ;
2005-04-17 02:20:36 +04:00
/* We ignore ret in case the ECC of the MediaHeader is invalid
( which is apparently acceptable ) */
if ( retlen ! = SECTORSIZE ) {
static int warncount = 5 ;
if ( warncount ) {
printk ( KERN_WARNING " Block read at 0x%x of mtd%d failed: %d \n " ,
block * nftl - > EraseSize , nftl - > mbd . mtd - > index , ret ) ;
if ( ! - - warncount )
printk ( KERN_WARNING " Further failures for this block will not be printed \n " ) ;
}
continue ;
}
if ( retlen < 6 | | memcmp ( buf , " ANAND " , 6 ) ) {
/* ANAND\0 not found. Continue */
#if 0
2005-11-07 14:15:26 +03:00
printk ( KERN_DEBUG " ANAND header not found at 0x%x in mtd%d \n " ,
2005-04-17 02:20:36 +04:00
block * nftl - > EraseSize , nftl - > mbd . mtd - > index ) ;
2005-11-07 14:15:26 +03:00
# endif
2005-04-17 02:20:36 +04:00
continue ;
}
/* To be safer with BIOS, also use erase mark as discriminant */
2006-05-29 05:26:58 +04:00
if ( ( ret = nftl_read_oob ( mtd , block * nftl - > EraseSize +
2006-05-28 13:01:53 +04:00
SECTORSIZE + 8 , 8 , & retlen ,
( char * ) & h1 ) < 0 ) ) {
2005-04-17 02:20:36 +04:00
printk ( KERN_WARNING " ANAND header found at 0x%x in mtd%d, but OOB data read failed (err %d) \n " ,
block * nftl - > EraseSize , nftl - > mbd . mtd - > index , ret ) ;
continue ;
}
#if 0 /* Some people seem to have devices without ECC or erase marks
on the Media Header blocks . There are enough other sanity
checks in here that we can probably do without it .
*/
if ( le16_to_cpu ( h1 . EraseMark | h1 . EraseMark1 ) ! = ERASE_MARK ) {
printk ( KERN_NOTICE " ANAND header found at 0x%x in mtd%d, but erase mark not present (0x%04x,0x%04x instead) \n " ,
2005-11-07 14:15:26 +03:00
block * nftl - > EraseSize , nftl - > mbd . mtd - > index ,
2005-04-17 02:20:36 +04:00
le16_to_cpu ( h1 . EraseMark ) , le16_to_cpu ( h1 . EraseMark1 ) ) ;
continue ;
}
/* Finally reread to check ECC */
2006-05-28 13:01:53 +04:00
if ( ( ret = mtd - > read ( mtd , block * nftl - > EraseSize , SECTORSIZE ,
& retlen , buf ) < 0 ) ) {
2005-04-17 02:20:36 +04:00
printk ( KERN_NOTICE " ANAND header found at 0x%x in mtd%d, but ECC read failed (err %d) \n " ,
block * nftl - > EraseSize , nftl - > mbd . mtd - > index , ret ) ;
continue ;
}
/* Paranoia. Check the ANAND header is still there after the ECC read */
if ( memcmp ( buf , " ANAND " , 6 ) ) {
printk ( KERN_NOTICE " ANAND header found at 0x%x in mtd%d, but went away on reread! \n " ,
block * nftl - > EraseSize , nftl - > mbd . mtd - > index ) ;
printk ( KERN_NOTICE " New data are: %02x %02x %02x %02x %02x %02x \n " ,
buf [ 0 ] , buf [ 1 ] , buf [ 2 ] , buf [ 3 ] , buf [ 4 ] , buf [ 5 ] ) ;
continue ;
}
# endif
/* OK, we like it. */
if ( boot_record_count ) {
/* We've already processed one. So we just check if
this one is the same as the first one we found */
if ( memcmp ( mh , buf , sizeof ( struct NFTLMediaHeader ) ) ) {
printk ( KERN_NOTICE " NFTL Media Headers at 0x%x and 0x%x disagree. \n " ,
nftl - > MediaUnit * nftl - > EraseSize , block * nftl - > EraseSize ) ;
/* if (debug) Print both side by side */
if ( boot_record_count < 2 ) {
/* We haven't yet seen two real ones */
return - 1 ;
}
continue ;
}
if ( boot_record_count = = 1 )
nftl - > SpareMediaUnit = block ;
/* Mark this boot record (NFTL MediaHeader) block as reserved */
nftl - > ReplUnitTable [ block ] = BLOCK_RESERVED ;
boot_record_count + + ;
continue ;
}
/* This is the first we've seen. Copy the media header structure into place */
memcpy ( mh , buf , sizeof ( struct NFTLMediaHeader ) ) ;
/* Do some sanity checks on it */
#if 0
The new DiskOnChip driver scans the MediaHeader itself , and presents a virtual
erasesize based on UnitSizeFactor . So the erasesize we read from the mtd
device is already correct .
if ( mh - > UnitSizeFactor = = 0 ) {
printk ( KERN_NOTICE " NFTL: UnitSizeFactor 0x00 detected. This violates the spec but we think we know what it means... \n " ) ;
} else if ( mh - > UnitSizeFactor < 0xfc ) {
printk ( KERN_NOTICE " Sorry, we don't support UnitSizeFactor 0x%02x \n " ,
mh - > UnitSizeFactor ) ;
return - 1 ;
} else if ( mh - > UnitSizeFactor ! = 0xff ) {
printk ( KERN_NOTICE " WARNING: Support for NFTL with UnitSizeFactor 0x%02x is experimental \n " ,
mh - > UnitSizeFactor ) ;
nftl - > EraseSize = nftl - > mbd . mtd - > erasesize < < ( 0xff - mh - > UnitSizeFactor ) ;
nftl - > nb_blocks = nftl - > mbd . mtd - > size / nftl - > EraseSize ;
}
# endif
nftl - > nb_boot_blocks = le16_to_cpu ( mh - > FirstPhysicalEUN ) ;
if ( ( nftl - > nb_boot_blocks + 2 ) > = nftl - > nb_blocks ) {
printk ( KERN_NOTICE " NFTL Media Header sanity check failed: \n " ) ;
2005-11-07 14:15:26 +03:00
printk ( KERN_NOTICE " nb_boot_blocks (%d) + 2 > nb_blocks (%d) \n " ,
2005-04-17 02:20:36 +04:00
nftl - > nb_boot_blocks , nftl - > nb_blocks ) ;
return - 1 ;
}
nftl - > numvunits = le32_to_cpu ( mh - > FormattedSize ) / nftl - > EraseSize ;
if ( nftl - > numvunits > ( nftl - > nb_blocks - nftl - > nb_boot_blocks - 2 ) ) {
printk ( KERN_NOTICE " NFTL Media Header sanity check failed: \n " ) ;
printk ( KERN_NOTICE " numvunits (%d) > nb_blocks (%d) - nb_boot_blocks(%d) - 2 \n " ,
nftl - > numvunits , nftl - > nb_blocks , nftl - > nb_boot_blocks ) ;
return - 1 ;
}
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
nftl - > mbd . size = nftl - > numvunits * ( nftl - > EraseSize / SECTORSIZE ) ;
/* If we're not using the last sectors in the device for some reason,
reduce nb_blocks accordingly so we forget they ' re there */
nftl - > nb_blocks = le16_to_cpu ( mh - > NumEraseUnits ) + le16_to_cpu ( mh - > FirstPhysicalEUN ) ;
/* XXX: will be suppressed */
nftl - > lastEUN = nftl - > nb_blocks - 1 ;
/* memory alloc */
nftl - > EUNtable = kmalloc ( nftl - > nb_blocks * sizeof ( u16 ) , GFP_KERNEL ) ;
if ( ! nftl - > EUNtable ) {
printk ( KERN_NOTICE " NFTL: allocation of EUNtable failed \n " ) ;
return - ENOMEM ;
}
nftl - > ReplUnitTable = kmalloc ( nftl - > nb_blocks * sizeof ( u16 ) , GFP_KERNEL ) ;
if ( ! nftl - > ReplUnitTable ) {
kfree ( nftl - > EUNtable ) ;
printk ( KERN_NOTICE " NFTL: allocation of ReplUnitTable failed \n " ) ;
return - ENOMEM ;
}
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
/* mark the bios blocks (blocks before NFTL MediaHeader) as reserved */
for ( i = 0 ; i < nftl - > nb_boot_blocks ; i + + )
nftl - > ReplUnitTable [ i ] = BLOCK_RESERVED ;
/* mark all remaining blocks as potentially containing data */
2005-11-07 14:15:26 +03:00
for ( ; i < nftl - > nb_blocks ; i + + ) {
2005-04-17 02:20:36 +04:00
nftl - > ReplUnitTable [ i ] = BLOCK_NOTEXPLORED ;
}
/* Mark this boot record (NFTL MediaHeader) block as reserved */
nftl - > ReplUnitTable [ block ] = BLOCK_RESERVED ;
/* read the Bad Erase Unit Table and modify ReplUnitTable[] accordingly */
for ( i = 0 ; i < nftl - > nb_blocks ; i + + ) {
#if 0
The new DiskOnChip driver already scanned the bad block table . Just query it .
if ( ( i & ( SECTORSIZE - 1 ) ) = = 0 ) {
/* read one sector for every SECTORSIZE of blocks */
2006-05-28 13:01:53 +04:00
if ( ( ret = mtd - > read ( nftl - > mbd . mtd , block * nftl - > EraseSize +
i + SECTORSIZE , SECTORSIZE , & retlen ,
buf ) ) < 0 ) {
2005-04-17 02:20:36 +04:00
printk ( KERN_NOTICE " Read of bad sector table failed (err %d) \n " ,
ret ) ;
kfree ( nftl - > ReplUnitTable ) ;
kfree ( nftl - > EUNtable ) ;
return - 1 ;
}
}
/* mark the Bad Erase Unit as RESERVED in ReplUnitTable */
if ( buf [ i & ( SECTORSIZE - 1 ) ] ! = 0xff )
nftl - > ReplUnitTable [ i ] = BLOCK_RESERVED ;
# endif
if ( nftl - > mbd . mtd - > block_isbad ( nftl - > mbd . mtd , i * nftl - > EraseSize ) )
nftl - > ReplUnitTable [ i ] = BLOCK_RESERVED ;
}
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
nftl - > MediaUnit = block ;
boot_record_count + + ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
} /* foreach (block) */
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
return boot_record_count ? 0 : - 1 ;
}
static int memcmpb ( void * a , int c , int n )
{
int i ;
for ( i = 0 ; i < n ; i + + ) {
if ( c ! = ( ( unsigned char * ) a ) [ i ] )
return 1 ;
}
return 0 ;
}
/* check_free_sector: check if a free sector is actually FREE, i.e. All 0xff in data and oob area */
2005-11-07 14:15:26 +03:00
static int check_free_sectors ( struct NFTLrecord * nftl , unsigned int address , int len ,
2005-04-17 02:20:36 +04:00
int check_oob )
{
u8 buf [ SECTORSIZE + nftl - > mbd . mtd - > oobsize ] ;
2006-05-23 19:21:03 +04:00
struct mtd_info * mtd = nftl - > mbd . mtd ;
size_t retlen ;
int i ;
2005-04-17 02:20:36 +04:00
for ( i = 0 ; i < len ; i + = SECTORSIZE ) {
2006-05-23 19:21:03 +04:00
if ( mtd - > read ( mtd , address , SECTORSIZE , & retlen , buf ) )
2005-04-17 02:20:36 +04:00
return - 1 ;
if ( memcmpb ( buf , 0xff , SECTORSIZE ) ! = 0 )
return - 1 ;
if ( check_oob ) {
2006-05-29 05:26:58 +04:00
if ( nftl_read_oob ( mtd , address , mtd - > oobsize ,
2006-05-23 19:21:03 +04:00
& retlen , & buf [ SECTORSIZE ] ) < 0 )
return - 1 ;
if ( memcmpb ( buf + SECTORSIZE , 0xff , mtd - > oobsize ) ! = 0 )
2005-04-17 02:20:36 +04:00
return - 1 ;
}
address + = SECTORSIZE ;
}
return 0 ;
}
/* NFTL_format: format a Erase Unit by erasing ALL Erase Zones in the Erase Unit and
* Update NFTL metadata . Each erase operation is checked with check_free_sectors
*
* Return : 0 when succeed , - 1 on error .
*
2005-11-07 14:15:26 +03:00
* ToDo : 1. Is it neceressary to check_free_sector after erasing ? ?
2005-04-17 02:20:36 +04:00
*/
int NFTL_formatblock ( struct NFTLrecord * nftl , int block )
{
size_t retlen ;
unsigned int nb_erases , erase_mark ;
struct nftl_uci1 uci ;
struct erase_info * instr = & nftl - > instr ;
2006-05-28 13:01:53 +04:00
struct mtd_info * mtd = nftl - > mbd . mtd ;
2005-04-17 02:20:36 +04:00
/* Read the Unit Control Information #1 for Wear-Leveling */
2006-05-29 05:26:58 +04:00
if ( nftl_read_oob ( mtd , block * nftl - > EraseSize + SECTORSIZE + 8 ,
2006-05-28 13:01:53 +04:00
8 , & retlen , ( char * ) & uci ) < 0 )
2005-04-17 02:20:36 +04:00
goto default_uci1 ;
erase_mark = le16_to_cpu ( ( uci . EraseMark | uci . EraseMark1 ) ) ;
if ( erase_mark ! = ERASE_MARK ) {
default_uci1 :
uci . EraseMark = cpu_to_le16 ( ERASE_MARK ) ;
uci . EraseMark1 = cpu_to_le16 ( ERASE_MARK ) ;
uci . WearInfo = cpu_to_le32 ( 0 ) ;
}
memset ( instr , 0 , sizeof ( struct erase_info ) ) ;
/* XXX: use async erase interface, XXX: test return code */
instr - > mtd = nftl - > mbd . mtd ;
instr - > addr = block * nftl - > EraseSize ;
instr - > len = nftl - > EraseSize ;
2006-05-28 13:01:53 +04:00
mtd - > erase ( mtd , instr ) ;
2005-04-17 02:20:36 +04:00
if ( instr - > state = = MTD_ERASE_FAILED ) {
printk ( " Error while formatting block %d \n " , block ) ;
goto fail ;
}
/* increase and write Wear-Leveling info */
nb_erases = le32_to_cpu ( uci . WearInfo ) ;
nb_erases + + ;
/* wrap (almost impossible with current flashs) or free block */
if ( nb_erases = = 0 )
nb_erases = 1 ;
/* check the "freeness" of Erase Unit before updating metadata
* FixMe : is this check really necessary ? since we have check the
* return code after the erase operation . */
if ( check_free_sectors ( nftl , instr - > addr , nftl - > EraseSize , 1 ) ! = 0 )
goto fail ;
uci . WearInfo = le32_to_cpu ( nb_erases ) ;
2006-05-29 05:26:58 +04:00
if ( nftl_write_oob ( mtd , block * nftl - > EraseSize + SECTORSIZE +
2006-05-28 13:01:53 +04:00
8 , 8 , & retlen , ( char * ) & uci ) < 0 )
2005-04-17 02:20:36 +04:00
goto fail ;
return 0 ;
fail :
/* could not format, update the bad block table (caller is responsible
for setting the ReplUnitTable to BLOCK_RESERVED on failure ) */
nftl - > mbd . mtd - > block_markbad ( nftl - > mbd . mtd , instr - > addr ) ;
return - 1 ;
}
/* check_sectors_in_chain: Check that each sector of a Virtual Unit Chain is correct.
* Mark as ' IGNORE ' each incorrect sector . This check is only done if the chain
* was being folded when NFTL was interrupted .
*
* The check_free_sectors in this function is neceressary . There is a possible
* situation that after writing the Data area , the Block Control Information is
* not updated according ( due to power failure or something ) which leaves the block
* in an umconsistent state . So we have to check if a block is really FREE in this
* case . */
static void check_sectors_in_chain ( struct NFTLrecord * nftl , unsigned int first_block )
{
2006-05-28 13:01:53 +04:00
struct mtd_info * mtd = nftl - > mbd . mtd ;
2005-04-17 02:20:36 +04:00
unsigned int block , i , status ;
struct nftl_bci bci ;
int sectors_per_block ;
size_t retlen ;
sectors_per_block = nftl - > EraseSize / SECTORSIZE ;
block = first_block ;
for ( ; ; ) {
for ( i = 0 ; i < sectors_per_block ; i + + ) {
2006-05-29 05:26:58 +04:00
if ( nftl_read_oob ( mtd ,
2006-05-28 13:01:53 +04:00
block * nftl - > EraseSize + i * SECTORSIZE ,
8 , & retlen , ( char * ) & bci ) < 0 )
2005-04-17 02:20:36 +04:00
status = SECTOR_IGNORE ;
else
status = bci . Status | bci . Status1 ;
switch ( status ) {
case SECTOR_FREE :
/* verify that the sector is really free. If not, mark
as ignore */
if ( memcmpb ( & bci , 0xff , 8 ) ! = 0 | |
2005-11-07 14:15:26 +03:00
check_free_sectors ( nftl , block * nftl - > EraseSize + i * SECTORSIZE ,
2005-04-17 02:20:36 +04:00
SECTORSIZE , 0 ) ! = 0 ) {
printk ( " Incorrect free sector %d in block %d: "
" marking it as ignored \n " ,
i , block ) ;
/* sector not free actually : mark it as SECTOR_IGNORE */
bci . Status = SECTOR_IGNORE ;
bci . Status1 = SECTOR_IGNORE ;
2006-05-29 05:26:58 +04:00
nftl_write_oob ( mtd , block *
2006-05-28 13:01:53 +04:00
nftl - > EraseSize +
i * SECTORSIZE , 8 ,
& retlen , ( char * ) & bci ) ;
2005-04-17 02:20:36 +04:00
}
break ;
default :
break ;
}
}
/* proceed to next Erase Unit on the chain */
block = nftl - > ReplUnitTable [ block ] ;
if ( ! ( block = = BLOCK_NIL | | block < nftl - > nb_blocks ) )
printk ( " incorrect ReplUnitTable[] : %d \n " , block ) ;
if ( block = = BLOCK_NIL | | block > = nftl - > nb_blocks )
break ;
}
}
/* calc_chain_lenght: Walk through a Virtual Unit Chain and estimate chain length */
static int calc_chain_length ( struct NFTLrecord * nftl , unsigned int first_block )
{
unsigned int length = 0 , block = first_block ;
for ( ; ; ) {
length + + ;
/* avoid infinite loops, although this is guaranted not to
happen because of the previous checks */
if ( length > = nftl - > nb_blocks ) {
printk ( " nftl: length too long %d ! \n " , length ) ;
break ;
}
block = nftl - > ReplUnitTable [ block ] ;
if ( ! ( block = = BLOCK_NIL | | block < nftl - > nb_blocks ) )
printk ( " incorrect ReplUnitTable[] : %d \n " , block ) ;
if ( block = = BLOCK_NIL | | block > = nftl - > nb_blocks )
break ;
}
return length ;
}
/* format_chain: Format an invalid Virtual Unit chain. It frees all the Erase Units in a
* Virtual Unit Chain , i . e . all the units are disconnected .
*
* It is not stricly correct to begin from the first block of the chain because
* if we stop the code , we may see again a valid chain if there was a first_block
* flag in a block inside it . But is it really a problem ?
*
* FixMe : Figure out what the last statesment means . What if power failure when we are
* in the for ( ; ; ) loop formatting blocks ? ?
*/
static void format_chain ( struct NFTLrecord * nftl , unsigned int first_block )
{
unsigned int block = first_block , block1 ;
printk ( " Formatting chain at block %d \n " , first_block ) ;
for ( ; ; ) {
block1 = nftl - > ReplUnitTable [ block ] ;
printk ( " Formatting block %d \n " , block ) ;
if ( NFTL_formatblock ( nftl , block ) < 0 ) {
/* cannot format !!!! Mark it as Bad Unit */
nftl - > ReplUnitTable [ block ] = BLOCK_RESERVED ;
} else {
nftl - > ReplUnitTable [ block ] = BLOCK_FREE ;
}
/* goto next block on the chain */
block = block1 ;
if ( ! ( block = = BLOCK_NIL | | block < nftl - > nb_blocks ) )
printk ( " incorrect ReplUnitTable[] : %d \n " , block ) ;
if ( block = = BLOCK_NIL | | block > = nftl - > nb_blocks )
break ;
}
}
/* check_and_mark_free_block: Verify that a block is free in the NFTL sense (valid erase mark) or
* totally free ( only 0xff ) .
*
* Definition : Free Erase Unit - - A properly erased / formatted Free Erase Unit should have meet the
* following critia :
* 1. */
static int check_and_mark_free_block ( struct NFTLrecord * nftl , int block )
{
2006-05-28 13:01:53 +04:00
struct mtd_info * mtd = nftl - > mbd . mtd ;
2005-04-17 02:20:36 +04:00
struct nftl_uci1 h1 ;
unsigned int erase_mark ;
size_t retlen ;
/* check erase mark. */
2006-05-29 05:26:58 +04:00
if ( nftl_read_oob ( mtd , block * nftl - > EraseSize + SECTORSIZE + 8 , 8 ,
2006-05-28 13:01:53 +04:00
& retlen , ( char * ) & h1 ) < 0 )
2005-04-17 02:20:36 +04:00
return - 1 ;
erase_mark = le16_to_cpu ( ( h1 . EraseMark | h1 . EraseMark1 ) ) ;
if ( erase_mark ! = ERASE_MARK ) {
/* if no erase mark, the block must be totally free. This is
possible in two cases : empty filsystem or interrupted erase ( very unlikely ) */
if ( check_free_sectors ( nftl , block * nftl - > EraseSize , nftl - > EraseSize , 1 ) ! = 0 )
return - 1 ;
/* free block : write erase mark */
h1 . EraseMark = cpu_to_le16 ( ERASE_MARK ) ;
h1 . EraseMark1 = cpu_to_le16 ( ERASE_MARK ) ;
h1 . WearInfo = cpu_to_le32 ( 0 ) ;
2006-05-29 05:26:58 +04:00
if ( nftl_write_oob ( mtd ,
2006-05-28 13:01:53 +04:00
block * nftl - > EraseSize + SECTORSIZE + 8 , 8 ,
& retlen , ( char * ) & h1 ) < 0 )
2005-04-17 02:20:36 +04:00
return - 1 ;
} else {
#if 0
/* if erase mark present, need to skip it when doing check */
for ( i = 0 ; i < nftl - > EraseSize ; i + = SECTORSIZE ) {
/* check free sector */
if ( check_free_sectors ( nftl , block * nftl - > EraseSize + i ,
SECTORSIZE , 0 ) ! = 0 )
return - 1 ;
2006-05-29 05:26:58 +04:00
if ( nftl_read_oob ( mtd , block * nftl - > EraseSize + i ,
2006-05-28 13:01:53 +04:00
16 , & retlen , buf ) < 0 )
2005-04-17 02:20:36 +04:00
return - 1 ;
if ( i = = SECTORSIZE ) {
/* skip erase mark */
if ( memcmpb ( buf , 0xff , 8 ) )
return - 1 ;
} else {
if ( memcmpb ( buf , 0xff , 16 ) )
return - 1 ;
}
}
# endif
}
return 0 ;
}
/* get_fold_mark: Read fold mark from Unit Control Information #2, we use FOLD_MARK_IN_PROGRESS
* to indicate that we are in the progression of a Virtual Unit Chain folding . If the UCI # 2
* is FOLD_MARK_IN_PROGRESS when mounting the NFTL , the ( previous ) folding process is interrupted
* for some reason . A clean up / check of the VUC is neceressary in this case .
*
* WARNING : return 0 if read error
*/
static int get_fold_mark ( struct NFTLrecord * nftl , unsigned int block )
{
2006-05-28 13:01:53 +04:00
struct mtd_info * mtd = nftl - > mbd . mtd ;
2005-04-17 02:20:36 +04:00
struct nftl_uci2 uci ;
size_t retlen ;
2006-05-29 05:26:58 +04:00
if ( nftl_read_oob ( mtd , block * nftl - > EraseSize + 2 * SECTORSIZE + 8 ,
2006-05-28 13:01:53 +04:00
8 , & retlen , ( char * ) & uci ) < 0 )
2005-04-17 02:20:36 +04:00
return 0 ;
return le16_to_cpu ( ( uci . FoldMark | uci . FoldMark1 ) ) ;
}
int NFTL_mount ( struct NFTLrecord * s )
{
int i ;
unsigned int first_logical_block , logical_block , rep_block , nb_erases , erase_mark ;
unsigned int block , first_block , is_first_block ;
int chain_length , do_format_chain ;
struct nftl_uci0 h0 ;
struct nftl_uci1 h1 ;
2006-05-28 13:01:53 +04:00
struct mtd_info * mtd = s - > mbd . mtd ;
2005-04-17 02:20:36 +04:00
size_t retlen ;
/* search for NFTL MediaHeader and Spare NFTL Media Header */
if ( find_boot_record ( s ) < 0 ) {
printk ( " Could not find valid boot record \n " ) ;
return - 1 ;
}
/* init the logical to physical table */
for ( i = 0 ; i < s - > nb_blocks ; i + + ) {
s - > EUNtable [ i ] = BLOCK_NIL ;
}
/* first pass : explore each block chain */
first_logical_block = 0 ;
for ( first_block = 0 ; first_block < s - > nb_blocks ; first_block + + ) {
/* if the block was not already explored, we can look at it */
if ( s - > ReplUnitTable [ first_block ] = = BLOCK_NOTEXPLORED ) {
block = first_block ;
chain_length = 0 ;
do_format_chain = 0 ;
for ( ; ; ) {
/* read the block header. If error, we format the chain */
2006-05-29 05:26:58 +04:00
if ( nftl_read_oob ( mtd ,
2006-05-28 13:01:53 +04:00
block * s - > EraseSize + 8 , 8 ,
& retlen , ( char * ) & h0 ) < 0 | |
2006-05-29 05:26:58 +04:00
nftl_read_oob ( mtd ,
2006-05-28 13:01:53 +04:00
block * s - > EraseSize +
SECTORSIZE + 8 , 8 ,
& retlen , ( char * ) & h1 ) < 0 ) {
2005-04-17 02:20:36 +04:00
s - > ReplUnitTable [ block ] = BLOCK_NIL ;
do_format_chain = 1 ;
break ;
}
logical_block = le16_to_cpu ( ( h0 . VirtUnitNum | h0 . SpareVirtUnitNum ) ) ;
rep_block = le16_to_cpu ( ( h0 . ReplUnitNum | h0 . SpareReplUnitNum ) ) ;
nb_erases = le32_to_cpu ( h1 . WearInfo ) ;
erase_mark = le16_to_cpu ( ( h1 . EraseMark | h1 . EraseMark1 ) ) ;
is_first_block = ! ( logical_block > > 15 ) ;
logical_block = logical_block & 0x7fff ;
/* invalid/free block test */
if ( erase_mark ! = ERASE_MARK | | logical_block > = s - > nb_blocks ) {
if ( chain_length = = 0 ) {
/* if not currently in a chain, we can handle it safely */
if ( check_and_mark_free_block ( s , block ) < 0 ) {
/* not really free: format it */
printk ( " Formatting block %d \n " , block ) ;
if ( NFTL_formatblock ( s , block ) < 0 ) {
/* could not format: reserve the block */
s - > ReplUnitTable [ block ] = BLOCK_RESERVED ;
} else {
s - > ReplUnitTable [ block ] = BLOCK_FREE ;
}
} else {
/* free block: mark it */
s - > ReplUnitTable [ block ] = BLOCK_FREE ;
}
/* directly examine the next block. */
goto examine_ReplUnitTable ;
} else {
/* the block was in a chain : this is bad. We
must format all the chain */
printk ( " Block %d: free but referenced in chain %d \n " ,
block , first_block ) ;
s - > ReplUnitTable [ block ] = BLOCK_NIL ;
do_format_chain = 1 ;
break ;
}
}
/* we accept only first blocks here */
if ( chain_length = = 0 ) {
/* this block is not the first block in chain :
ignore it , it will be included in a chain
later , or marked as not explored */
if ( ! is_first_block )
goto examine_ReplUnitTable ;
first_logical_block = logical_block ;
} else {
if ( logical_block ! = first_logical_block ) {
2005-11-07 14:15:26 +03:00
printk ( " Block %d: incorrect logical block: %d expected: %d \n " ,
2005-04-17 02:20:36 +04:00
block , logical_block , first_logical_block ) ;
/* the chain is incorrect : we must format it,
but we need to read it completly */
do_format_chain = 1 ;
}
if ( is_first_block ) {
/* we accept that a block is marked as first
block while being last block in a chain
only if the chain is being folded */
if ( get_fold_mark ( s , block ) ! = FOLD_MARK_IN_PROGRESS | |
rep_block ! = 0xffff ) {
printk ( " Block %d: incorrectly marked as first block in chain \n " ,
block ) ;
/* the chain is incorrect : we must format it,
but we need to read it completly */
do_format_chain = 1 ;
} else {
printk ( " Block %d: folding in progress - ignoring first block flag \n " ,
block ) ;
}
}
}
chain_length + + ;
if ( rep_block = = 0xffff ) {
/* no more blocks after */
s - > ReplUnitTable [ block ] = BLOCK_NIL ;
break ;
} else if ( rep_block > = s - > nb_blocks ) {
2005-11-07 14:15:26 +03:00
printk ( " Block %d: referencing invalid block %d \n " ,
2005-04-17 02:20:36 +04:00
block , rep_block ) ;
do_format_chain = 1 ;
s - > ReplUnitTable [ block ] = BLOCK_NIL ;
break ;
} else if ( s - > ReplUnitTable [ rep_block ] ! = BLOCK_NOTEXPLORED ) {
/* same problem as previous 'is_first_block' test:
we accept that the last block of a chain has
the first_block flag set if folding is in
progress . We handle here the case where the
last block appeared first */
if ( s - > ReplUnitTable [ rep_block ] = = BLOCK_NIL & &
s - > EUNtable [ first_logical_block ] = = rep_block & &
get_fold_mark ( s , first_block ) = = FOLD_MARK_IN_PROGRESS ) {
/* EUNtable[] will be set after */
printk ( " Block %d: folding in progress - ignoring first block flag \n " ,
rep_block ) ;
s - > ReplUnitTable [ block ] = rep_block ;
s - > EUNtable [ first_logical_block ] = BLOCK_NIL ;
} else {
2005-11-07 14:15:26 +03:00
printk ( " Block %d: referencing block %d already in another chain \n " ,
2005-04-17 02:20:36 +04:00
block , rep_block ) ;
/* XXX: should handle correctly fold in progress chains */
do_format_chain = 1 ;
s - > ReplUnitTable [ block ] = BLOCK_NIL ;
}
break ;
} else {
/* this is OK */
s - > ReplUnitTable [ block ] = rep_block ;
block = rep_block ;
}
}
/* the chain was completely explored. Now we can decide
what to do with it */
if ( do_format_chain ) {
/* invalid chain : format it */
format_chain ( s , first_block ) ;
} else {
unsigned int first_block1 , chain_to_format , chain_length1 ;
int fold_mark ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
/* valid chain : get foldmark */
fold_mark = get_fold_mark ( s , first_block ) ;
if ( fold_mark = = 0 ) {
/* cannot get foldmark : format the chain */
printk ( " Could read foldmark at block %d \n " , first_block ) ;
format_chain ( s , first_block ) ;
} else {
if ( fold_mark = = FOLD_MARK_IN_PROGRESS )
check_sectors_in_chain ( s , first_block ) ;
/* now handle the case where we find two chains at the
same virtual address : we select the longer one ,
because the shorter one is the one which was being
folded if the folding was not done in place */
first_block1 = s - > EUNtable [ first_logical_block ] ;
if ( first_block1 ! = BLOCK_NIL ) {
/* XXX: what to do if same length ? */
chain_length1 = calc_chain_length ( s , first_block1 ) ;
2005-11-07 14:15:26 +03:00
printk ( " Two chains at blocks %d (len=%d) and %d (len=%d) \n " ,
2005-04-17 02:20:36 +04:00
first_block1 , chain_length1 , first_block , chain_length ) ;
2005-11-07 14:15:26 +03:00
2005-04-17 02:20:36 +04:00
if ( chain_length > = chain_length1 ) {
chain_to_format = first_block1 ;
s - > EUNtable [ first_logical_block ] = first_block ;
} else {
chain_to_format = first_block ;
}
format_chain ( s , chain_to_format ) ;
} else {
s - > EUNtable [ first_logical_block ] = first_block ;
}
}
}
}
examine_ReplUnitTable : ;
}
/* second pass to format unreferenced blocks and init free block count */
s - > numfreeEUNs = 0 ;
s - > LastFreeEUN = le16_to_cpu ( s - > MediaHdr . FirstPhysicalEUN ) ;
for ( block = 0 ; block < s - > nb_blocks ; block + + ) {
if ( s - > ReplUnitTable [ block ] = = BLOCK_NOTEXPLORED ) {
printk ( " Unreferenced block %d, formatting it \n " , block ) ;
if ( NFTL_formatblock ( s , block ) < 0 )
s - > ReplUnitTable [ block ] = BLOCK_RESERVED ;
else
s - > ReplUnitTable [ block ] = BLOCK_FREE ;
}
if ( s - > ReplUnitTable [ block ] = = BLOCK_FREE ) {
s - > numfreeEUNs + + ;
s - > LastFreeEUN = block ;
}
}
return 0 ;
}