#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
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;
nftl->EraseSize = nftl->mbd.mtd->erasesize;
nftl->nb_blocks = (u32)nftl->mbd.mtd->size / nftl->EraseSize;
nftl->MediaUnit = BLOCK_NIL;
nftl->SpareMediaUnit = BLOCK_NIL;
for (block = 0; block < nftl->nb_blocks; block++) {
int ret;
ret = mtd_read(mtd, block * nftl->EraseSize, SECTORSIZE,
&retlen, buf);
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)) {
#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;
}
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
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;
}
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;
}
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
if (boot_record_count) {
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 (boot_record_count < 2) {
return -1;
}
continue;
}
if (boot_record_count == 1)
nftl->SpareMediaUnit = block;
nftl->ReplUnitTable[block] = BLOCK_RESERVED;
boot_record_count++;
continue;
}
memcpy(mh, buf, sizeof(struct NFTLMediaHeader));
#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);
nftl->nb_blocks = le16_to_cpu(mh->NumEraseUnits) + le16_to_cpu(mh->FirstPhysicalEUN);
nftl->lastEUN = nftl->nb_blocks - 1;
nftl->EUNtable = kmalloc_array(nftl->nb_blocks, sizeof(u16),
GFP_KERNEL);
if (!nftl->EUNtable)
return -ENOMEM;
nftl->ReplUnitTable = kmalloc_array(nftl->nb_blocks,
sizeof(u16),
GFP_KERNEL);
if (!nftl->ReplUnitTable) {
kfree(nftl->EUNtable);
return -ENOMEM;
}
for (i = 0; i < nftl->nb_boot_blocks; i++)
nftl->ReplUnitTable[i] = BLOCK_RESERVED;
for (; i < nftl->nb_blocks; i++) {
nftl->ReplUnitTable[i] = BLOCK_NOTEXPLORED;
}
nftl->ReplUnitTable[block] = BLOCK_RESERVED;
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) {
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;
}
}
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++;
}
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;
}
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 -ENOMEM;
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;
}
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;
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));
instr->addr = block * nftl->EraseSize;
instr->len = nftl->EraseSize;
if (mtd_erase(mtd, instr)) {
printk("Error while formatting block %d\n", block);
goto fail;
}
nb_erases = le32_to_cpu(uci.WearInfo);
nb_erases++;
if (nb_erases == 0)
nb_erases = 1;
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:
mtd_block_markbad(nftl->mbd.mtd, instr->addr);
return -1;
}
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:
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);
bci.Status = SECTOR_IGNORE;
bci.Status1 = SECTOR_IGNORE;
nftl_write_oob(mtd, block *
nftl->EraseSize +
i * SECTORSIZE, 8,
&retlen, (char *)&bci);
}
break;
default:
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;
}
}
static int calc_chain_length(struct NFTLrecord *nftl, unsigned int first_block)
{
unsigned int length = 0, block = first_block;
for (;;) {
length++;
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;
}
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) {
nftl->ReplUnitTable[block] = BLOCK_RESERVED;
} else {
nftl->ReplUnitTable[block] = BLOCK_FREE;
}
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;
}
}
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;
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 (check_free_sectors (nftl, block * nftl->EraseSize, nftl->EraseSize, 1) != 0)
return -1;
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
for (i = 0; i < nftl->EraseSize; i += SECTORSIZE) {
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) {
if (memcmpb(buf, 0xff, 8))
return -1;
} else {
if (memcmpb(buf, 0xff, 16))
return -1;
}
}
#endif
}
return 0;
}
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;
if (find_boot_record(s) < 0) {
printk("Could not find valid boot record\n");
return -1;
}
for (i = 0; i < s->nb_blocks; i++) {
s->EUNtable[i] = BLOCK_NIL;
}
first_logical_block = 0;
for (first_block = 0; first_block < s->nb_blocks; first_block++) {
if (s->ReplUnitTable[first_block] == BLOCK_NOTEXPLORED) {
block = first_block;
chain_length = 0;
do_format_chain = 0;
for (;;) {
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;
if (erase_mark != ERASE_MARK || logical_block >= s->nb_blocks) {
if (chain_length == 0) {
if (check_and_mark_free_block(s, block) < 0) {
printk("Formatting block %d\n", block);
if (NFTL_formatblock(s, block) < 0) {
s->ReplUnitTable[block] = BLOCK_RESERVED;
} else {
s->ReplUnitTable[block] = BLOCK_FREE;
}
} else {
s->ReplUnitTable[block] = BLOCK_FREE;
}
goto examine_ReplUnitTable;
} else {
printk("Block %d: free but referenced in chain %d\n",
block, first_block);
s->ReplUnitTable[block] = BLOCK_NIL;
do_format_chain = 1;
break;
}
}
if (chain_length == 0) {
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);
do_format_chain = 1;
}
if (is_first_block) {
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);
do_format_chain = 1;
} else {
printk("Block %d: folding in progress - ignoring first block flag\n",
block);
}
}
}
chain_length++;
if (rep_block == 0xffff) {
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) {
if (s->ReplUnitTable[rep_block] == BLOCK_NIL &&
s->EUNtable[first_logical_block] == rep_block &&
get_fold_mark(s, first_block) == FOLD_MARK_IN_PROGRESS) {
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);
do_format_chain = 1;
s->ReplUnitTable[block] = BLOCK_NIL;
}
break;
} else {
s->ReplUnitTable[block] = rep_block;
block = rep_block;
}
}
if (do_format_chain) {
format_chain(s, first_block);
} else {
unsigned int first_block1, chain_to_format, chain_length1;
int fold_mark;
fold_mark = get_fold_mark(s, first_block);
if (fold_mark == 0) {
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);
first_block1 = s->EUNtable[first_logical_block];
if (first_block1 != BLOCK_NIL) {
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:;
}
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;
}