forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
785 lines
25 KiB
785 lines
25 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* NFTL mount code with extensive checks |
|
* |
|
* Author: Fabrice Bellard ([email protected]) |
|
* Copyright © 2000 Netgem S.A. |
|
* Copyright © 1999-2010 David Woodhouse <[email protected]> |
|
*/ |
|
|
|
#include <linux/kernel.h> |
|
#include <asm/errno.h> |
|
#include <linux/delay.h> |
|
#include <linux/slab.h> |
|
#include <linux/mtd/mtd.h> |
|
#include <linux/mtd/rawnand.h> |
|
#include <linux/mtd/nftl.h> |
|
|
|
#define SECTORSIZE 512 |
|
|
|
/* 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 according 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; |
|
struct mtd_info *mtd = nftl->mbd.mtd; |
|
unsigned int i; |
|
|
|
/* Assume logical EraseSize == physical erasesize for starting the scan. |
|
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 = (u32)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 */ |
|
ret = mtd_read(mtd, block * nftl->EraseSize, SECTORSIZE, |
|
&retlen, buf); |
|
/* 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 |
|
printk(KERN_DEBUG "ANAND header not found at 0x%x in mtd%d\n", |
|
block * nftl->EraseSize, nftl->mbd.mtd->index); |
|
#endif |
|
continue; |
|
} |
|
|
|
/* To be safer with BIOS, also use erase mark as discriminant */ |
|
ret = nftl_read_oob(mtd, block * nftl->EraseSize + |
|
SECTORSIZE + 8, 8, &retlen, |
|
(char *)&h1); |
|
if (ret < 0) { |
|
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", |
|
block * nftl->EraseSize, nftl->mbd.mtd->index, |
|
le16_to_cpu(h1.EraseMark), le16_to_cpu(h1.EraseMark1)); |
|
continue; |
|
} |
|
|
|
/* Finally reread to check ECC */ |
|
ret = mtd->read(mtd, block * nftl->EraseSize, SECTORSIZE, |
|
&retlen, buf); |
|
if (ret < 0) { |
|
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: %6ph\n", buf); |
|
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 = (u32)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"); |
|
printk(KERN_NOTICE "nb_boot_blocks (%d) + 2 > nb_blocks (%d)\n", |
|
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; |
|
} |
|
|
|
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_array(nftl->nb_blocks, sizeof(u16), |
|
GFP_KERNEL); |
|
if (!nftl->EUNtable) { |
|
printk(KERN_NOTICE "NFTL: allocation of EUNtable failed\n"); |
|
return -ENOMEM; |
|
} |
|
|
|
nftl->ReplUnitTable = kmalloc_array(nftl->nb_blocks, |
|
sizeof(u16), |
|
GFP_KERNEL); |
|
if (!nftl->ReplUnitTable) { |
|
kfree(nftl->EUNtable); |
|
printk(KERN_NOTICE "NFTL: allocation of ReplUnitTable failed\n"); |
|
return -ENOMEM; |
|
} |
|
|
|
/* 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 */ |
|
for (; i < nftl->nb_blocks; i++) { |
|
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 */ |
|
ret = mtd->read(nftl->mbd.mtd, |
|
block * nftl->EraseSize + i + |
|
SECTORSIZE, SECTORSIZE, |
|
&retlen, buf); |
|
if (ret < 0) { |
|
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 (mtd_block_isbad(nftl->mbd.mtd, |
|
i * nftl->EraseSize)) |
|
nftl->ReplUnitTable[i] = BLOCK_RESERVED; |
|
} |
|
|
|
nftl->MediaUnit = block; |
|
boot_record_count++; |
|
|
|
} /* foreach (block) */ |
|
|
|
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 */ |
|
static int check_free_sectors(struct NFTLrecord *nftl, unsigned int address, int len, |
|
int check_oob) |
|
{ |
|
struct mtd_info *mtd = nftl->mbd.mtd; |
|
size_t retlen; |
|
int i, ret; |
|
u8 *buf; |
|
|
|
buf = kmalloc(SECTORSIZE + mtd->oobsize, GFP_KERNEL); |
|
if (!buf) |
|
return -1; |
|
|
|
ret = -1; |
|
for (i = 0; i < len; i += SECTORSIZE) { |
|
if (mtd_read(mtd, address, SECTORSIZE, &retlen, buf)) |
|
goto out; |
|
if (memcmpb(buf, 0xff, SECTORSIZE) != 0) |
|
goto out; |
|
|
|
if (check_oob) { |
|
if(nftl_read_oob(mtd, address, mtd->oobsize, |
|
&retlen, &buf[SECTORSIZE]) < 0) |
|
goto out; |
|
if (memcmpb(buf + SECTORSIZE, 0xff, mtd->oobsize) != 0) |
|
goto out; |
|
} |
|
address += SECTORSIZE; |
|
} |
|
|
|
ret = 0; |
|
|
|
out: |
|
kfree(buf); |
|
return ret; |
|
} |
|
|
|
/* 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. |
|
* |
|
* ToDo: 1. Is it necessary to check_free_sector after erasing ?? |
|
*/ |
|
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; |
|
struct mtd_info *mtd = nftl->mbd.mtd; |
|
|
|
/* Read the Unit Control Information #1 for Wear-Leveling */ |
|
if (nftl_read_oob(mtd, block * nftl->EraseSize + SECTORSIZE + 8, |
|
8, &retlen, (char *)&uci) < 0) |
|
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->addr = block * nftl->EraseSize; |
|
instr->len = nftl->EraseSize; |
|
if (mtd_erase(mtd, instr)) { |
|
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 flash) 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); |
|
if (nftl_write_oob(mtd, block * nftl->EraseSize + SECTORSIZE + |
|
8, 8, &retlen, (char *)&uci) < 0) |
|
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) */ |
|
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 necessary. 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 inconsistent 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) |
|
{ |
|
struct mtd_info *mtd = nftl->mbd.mtd; |
|
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++) { |
|
if (nftl_read_oob(mtd, |
|
block * nftl->EraseSize + i * SECTORSIZE, |
|
8, &retlen, (char *)&bci) < 0) |
|
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 || |
|
check_free_sectors(nftl, block * nftl->EraseSize + i * SECTORSIZE, |
|
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; |
|
nftl_write_oob(mtd, block * |
|
nftl->EraseSize + |
|
i * SECTORSIZE, 8, |
|
&retlen, (char *)&bci); |
|
} |
|
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_length: 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 guaranteed 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 strictly 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 statement 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 criteria: |
|
* 1. */ |
|
static int check_and_mark_free_block(struct NFTLrecord *nftl, int block) |
|
{ |
|
struct mtd_info *mtd = nftl->mbd.mtd; |
|
struct nftl_uci1 h1; |
|
unsigned int erase_mark; |
|
size_t retlen; |
|
|
|
/* check erase mark. */ |
|
if (nftl_read_oob(mtd, block * nftl->EraseSize + SECTORSIZE + 8, 8, |
|
&retlen, (char *)&h1) < 0) |
|
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 filesystem 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); |
|
if (nftl_write_oob(mtd, |
|
block * nftl->EraseSize + SECTORSIZE + 8, 8, |
|
&retlen, (char *)&h1) < 0) |
|
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; |
|
|
|
if (nftl_read_oob(mtd, block * nftl->EraseSize + i, |
|
16, &retlen, buf) < 0) |
|
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 necessary in this case. |
|
* |
|
* WARNING: return 0 if read error |
|
*/ |
|
static int get_fold_mark(struct NFTLrecord *nftl, unsigned int block) |
|
{ |
|
struct mtd_info *mtd = nftl->mbd.mtd; |
|
struct nftl_uci2 uci; |
|
size_t retlen; |
|
|
|
if (nftl_read_oob(mtd, block * nftl->EraseSize + 2 * SECTORSIZE + 8, |
|
8, &retlen, (char *)&uci) < 0) |
|
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, erase_mark; |
|
unsigned int block, first_block, is_first_block; |
|
int chain_length, do_format_chain; |
|
struct nftl_uci0 h0; |
|
struct nftl_uci1 h1; |
|
struct mtd_info *mtd = s->mbd.mtd; |
|
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 */ |
|
if (nftl_read_oob(mtd, |
|
block * s->EraseSize + 8, 8, |
|
&retlen, (char *)&h0) < 0 || |
|
nftl_read_oob(mtd, |
|
block * s->EraseSize + |
|
SECTORSIZE + 8, 8, |
|
&retlen, (char *)&h1) < 0) { |
|
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)); |
|
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) { |
|
printk("Block %d: incorrect logical block: %d expected: %d\n", |
|
block, logical_block, first_logical_block); |
|
/* the chain is incorrect : we must format it, |
|
but we need to read it completely */ |
|
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 completely */ |
|
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) { |
|
printk("Block %d: referencing invalid block %d\n", |
|
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 { |
|
printk("Block %d: referencing block %d already in another chain\n", |
|
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; |
|
|
|
/* 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); |
|
printk("Two chains at blocks %d (len=%d) and %d (len=%d)\n", |
|
first_block1, chain_length1, first_block, chain_length); |
|
|
|
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; |
|
}
|
|
|