11a59d1b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds * inftlcore.c -- Linux driver for Inverse Flash Translation Layer (INFTL)
41da177e4SLinus Torvalds *
5a1452a37SDavid Woodhouse * Copyright © 2002, Greg Ungerer (gerg@snapgear.com)
61da177e4SLinus Torvalds *
71da177e4SLinus Torvalds * Based heavily on the nftlcore.c code which is:
8a1452a37SDavid Woodhouse * Copyright © 1999 Machine Vision Holdings, Inc.
9a1452a37SDavid Woodhouse * Copyright © 1999 David Woodhouse <dwmw2@infradead.org>
101da177e4SLinus Torvalds */
111da177e4SLinus Torvalds
121da177e4SLinus Torvalds #include <linux/kernel.h>
131da177e4SLinus Torvalds #include <linux/module.h>
141da177e4SLinus Torvalds #include <linux/delay.h>
151da177e4SLinus Torvalds #include <linux/slab.h>
161da177e4SLinus Torvalds #include <linux/sched.h>
171da177e4SLinus Torvalds #include <linux/init.h>
181da177e4SLinus Torvalds #include <linux/kmod.h>
191da177e4SLinus Torvalds #include <linux/hdreg.h>
201da177e4SLinus Torvalds #include <linux/mtd/mtd.h>
211da177e4SLinus Torvalds #include <linux/mtd/nftl.h>
221da177e4SLinus Torvalds #include <linux/mtd/inftl.h>
23d4092d76SBoris Brezillon #include <linux/mtd/rawnand.h>
247c0f6ba6SLinus Torvalds #include <linux/uaccess.h>
251da177e4SLinus Torvalds #include <asm/errno.h>
261da177e4SLinus Torvalds #include <asm/io.h>
271da177e4SLinus Torvalds
281da177e4SLinus Torvalds /*
291da177e4SLinus Torvalds * Maximum number of loops while examining next block, to have a
301da177e4SLinus Torvalds * chance to detect consistency problems (they should never happen
311da177e4SLinus Torvalds * because of the checks done in the mounting.
321da177e4SLinus Torvalds */
331da177e4SLinus Torvalds #define MAX_LOOPS 10000
341da177e4SLinus Torvalds
inftl_add_mtd(struct mtd_blktrans_ops * tr,struct mtd_info * mtd)351da177e4SLinus Torvalds static void inftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
361da177e4SLinus Torvalds {
371da177e4SLinus Torvalds struct INFTLrecord *inftl;
381da177e4SLinus Torvalds unsigned long temp;
391da177e4SLinus Torvalds
40818b9739SHuang Shijie if (!mtd_type_is_nand(mtd) || mtd->size > UINT_MAX)
411da177e4SLinus Torvalds return;
421da177e4SLinus Torvalds /* OK, this is moderately ugly. But probably safe. Alternatives? */
431da177e4SLinus Torvalds if (memcmp(mtd->name, "DiskOnChip", 10))
441da177e4SLinus Torvalds return;
451da177e4SLinus Torvalds
463c3c10bbSArtem Bityutskiy if (!mtd->_block_isbad) {
471da177e4SLinus Torvalds printk(KERN_ERR
481da177e4SLinus Torvalds "INFTL no longer supports the old DiskOnChip drivers loaded via docprobe.\n"
491da177e4SLinus Torvalds "Please use the new diskonchip driver under the NAND subsystem.\n");
501da177e4SLinus Torvalds return;
511da177e4SLinus Torvalds }
521da177e4SLinus Torvalds
53289c0522SBrian Norris pr_debug("INFTL: add_mtd for %s\n", mtd->name);
541da177e4SLinus Torvalds
5595b93a0cSBurman Yan inftl = kzalloc(sizeof(*inftl), GFP_KERNEL);
561da177e4SLinus Torvalds
570870066dSBrian Norris if (!inftl)
581da177e4SLinus Torvalds return;
591da177e4SLinus Torvalds
601da177e4SLinus Torvalds inftl->mbd.mtd = mtd;
611da177e4SLinus Torvalds inftl->mbd.devnum = -1;
6219187672SRichard Purdie
631da177e4SLinus Torvalds inftl->mbd.tr = tr;
641da177e4SLinus Torvalds
651da177e4SLinus Torvalds if (INFTL_mount(inftl) < 0) {
661da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: could not mount device\n");
671da177e4SLinus Torvalds kfree(inftl);
681da177e4SLinus Torvalds return;
691da177e4SLinus Torvalds }
701da177e4SLinus Torvalds
711da177e4SLinus Torvalds /* OK, it's a new one. Set up all the data structures. */
721da177e4SLinus Torvalds
731da177e4SLinus Torvalds /* Calculate geometry */
741da177e4SLinus Torvalds inftl->cylinders = 1024;
751da177e4SLinus Torvalds inftl->heads = 16;
761da177e4SLinus Torvalds
771da177e4SLinus Torvalds temp = inftl->cylinders * inftl->heads;
781da177e4SLinus Torvalds inftl->sectors = inftl->mbd.size / temp;
791da177e4SLinus Torvalds if (inftl->mbd.size % temp) {
801da177e4SLinus Torvalds inftl->sectors++;
811da177e4SLinus Torvalds temp = inftl->cylinders * inftl->sectors;
821da177e4SLinus Torvalds inftl->heads = inftl->mbd.size / temp;
831da177e4SLinus Torvalds
841da177e4SLinus Torvalds if (inftl->mbd.size % temp) {
851da177e4SLinus Torvalds inftl->heads++;
861da177e4SLinus Torvalds temp = inftl->heads * inftl->sectors;
871da177e4SLinus Torvalds inftl->cylinders = inftl->mbd.size / temp;
881da177e4SLinus Torvalds }
891da177e4SLinus Torvalds }
901da177e4SLinus Torvalds
911da177e4SLinus Torvalds if (inftl->mbd.size != inftl->heads * inftl->cylinders * inftl->sectors) {
921da177e4SLinus Torvalds /*
931da177e4SLinus Torvalds Oh no we don't have
941da177e4SLinus Torvalds mbd.size == heads * cylinders * sectors
951da177e4SLinus Torvalds */
961da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: cannot calculate a geometry to "
971da177e4SLinus Torvalds "match size of 0x%lx.\n", inftl->mbd.size);
981da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: using C:%d H:%d S:%d "
991da177e4SLinus Torvalds "(== 0x%lx sects)\n",
1001da177e4SLinus Torvalds inftl->cylinders, inftl->heads , inftl->sectors,
1011da177e4SLinus Torvalds (long)inftl->cylinders * (long)inftl->heads *
1021da177e4SLinus Torvalds (long)inftl->sectors );
1031da177e4SLinus Torvalds }
1041da177e4SLinus Torvalds
1051da177e4SLinus Torvalds if (add_mtd_blktrans_dev(&inftl->mbd)) {
1061da177e4SLinus Torvalds kfree(inftl->PUtable);
1071da177e4SLinus Torvalds kfree(inftl->VUtable);
1081da177e4SLinus Torvalds kfree(inftl);
1091da177e4SLinus Torvalds return;
1101da177e4SLinus Torvalds }
1111da177e4SLinus Torvalds #ifdef PSYCHO_DEBUG
1128b68a126SEric Sesterhenn / snakebyte printk(KERN_INFO "INFTL: Found new inftl%c\n", inftl->mbd.devnum + 'a');
1131da177e4SLinus Torvalds #endif
1141da177e4SLinus Torvalds return;
1151da177e4SLinus Torvalds }
1161da177e4SLinus Torvalds
inftl_remove_dev(struct mtd_blktrans_dev * dev)1171da177e4SLinus Torvalds static void inftl_remove_dev(struct mtd_blktrans_dev *dev)
1181da177e4SLinus Torvalds {
1191da177e4SLinus Torvalds struct INFTLrecord *inftl = (void *)dev;
1201da177e4SLinus Torvalds
121289c0522SBrian Norris pr_debug("INFTL: remove_dev (i=%d)\n", dev->devnum);
1221da177e4SLinus Torvalds
1231da177e4SLinus Torvalds del_mtd_blktrans_dev(dev);
1241da177e4SLinus Torvalds
1251da177e4SLinus Torvalds kfree(inftl->PUtable);
1261da177e4SLinus Torvalds kfree(inftl->VUtable);
1271da177e4SLinus Torvalds }
1281da177e4SLinus Torvalds
1291da177e4SLinus Torvalds /*
1301da177e4SLinus Torvalds * Actual INFTL access routines.
1311da177e4SLinus Torvalds */
1321da177e4SLinus Torvalds
1331da177e4SLinus Torvalds /*
1348593fbc6SThomas Gleixner * Read oob data from flash
1358593fbc6SThomas Gleixner */
inftl_read_oob(struct mtd_info * mtd,loff_t offs,size_t len,size_t * retlen,uint8_t * buf)1368593fbc6SThomas Gleixner int inftl_read_oob(struct mtd_info *mtd, loff_t offs, size_t len,
1378593fbc6SThomas Gleixner size_t *retlen, uint8_t *buf)
1388593fbc6SThomas Gleixner {
139745df179SMichał Kępień struct mtd_oob_ops ops = { };
1408593fbc6SThomas Gleixner int res;
1418593fbc6SThomas Gleixner
1420612b9ddSBrian Norris ops.mode = MTD_OPS_PLACE_OOB;
1438593fbc6SThomas Gleixner ops.ooboffs = offs & (mtd->writesize - 1);
1448593fbc6SThomas Gleixner ops.ooblen = len;
1458593fbc6SThomas Gleixner ops.oobbuf = buf;
1468593fbc6SThomas Gleixner ops.datbuf = NULL;
1478593fbc6SThomas Gleixner
148fd2819bbSArtem Bityutskiy res = mtd_read_oob(mtd, offs & ~(mtd->writesize - 1), &ops);
1497014568bSVitaly Wool *retlen = ops.oobretlen;
1508593fbc6SThomas Gleixner return res;
1518593fbc6SThomas Gleixner }
1528593fbc6SThomas Gleixner
1538593fbc6SThomas Gleixner /*
1548593fbc6SThomas Gleixner * Write oob data to flash
1558593fbc6SThomas Gleixner */
inftl_write_oob(struct mtd_info * mtd,loff_t offs,size_t len,size_t * retlen,uint8_t * buf)1568593fbc6SThomas Gleixner int inftl_write_oob(struct mtd_info *mtd, loff_t offs, size_t len,
1578593fbc6SThomas Gleixner size_t *retlen, uint8_t *buf)
1588593fbc6SThomas Gleixner {
159745df179SMichał Kępień struct mtd_oob_ops ops = { };
1608593fbc6SThomas Gleixner int res;
1618593fbc6SThomas Gleixner
1620612b9ddSBrian Norris ops.mode = MTD_OPS_PLACE_OOB;
1638593fbc6SThomas Gleixner ops.ooboffs = offs & (mtd->writesize - 1);
1648593fbc6SThomas Gleixner ops.ooblen = len;
1658593fbc6SThomas Gleixner ops.oobbuf = buf;
1668593fbc6SThomas Gleixner ops.datbuf = NULL;
1678593fbc6SThomas Gleixner
168a2cc5ba0SArtem Bityutskiy res = mtd_write_oob(mtd, offs & ~(mtd->writesize - 1), &ops);
1697014568bSVitaly Wool *retlen = ops.oobretlen;
1708593fbc6SThomas Gleixner return res;
1718593fbc6SThomas Gleixner }
1728593fbc6SThomas Gleixner
1738593fbc6SThomas Gleixner /*
1748593fbc6SThomas Gleixner * Write data and oob to flash
1758593fbc6SThomas Gleixner */
inftl_write(struct mtd_info * mtd,loff_t offs,size_t len,size_t * retlen,uint8_t * buf,uint8_t * oob)1768593fbc6SThomas Gleixner static int inftl_write(struct mtd_info *mtd, loff_t offs, size_t len,
1778593fbc6SThomas Gleixner size_t *retlen, uint8_t *buf, uint8_t *oob)
1788593fbc6SThomas Gleixner {
179745df179SMichał Kępień struct mtd_oob_ops ops = { };
1808593fbc6SThomas Gleixner int res;
1818593fbc6SThomas Gleixner
1820612b9ddSBrian Norris ops.mode = MTD_OPS_PLACE_OOB;
1838593fbc6SThomas Gleixner ops.ooboffs = offs;
1848593fbc6SThomas Gleixner ops.ooblen = mtd->oobsize;
1858593fbc6SThomas Gleixner ops.oobbuf = oob;
1868593fbc6SThomas Gleixner ops.datbuf = buf;
1878593fbc6SThomas Gleixner ops.len = len;
1888593fbc6SThomas Gleixner
189a2cc5ba0SArtem Bityutskiy res = mtd_write_oob(mtd, offs & ~(mtd->writesize - 1), &ops);
1908593fbc6SThomas Gleixner *retlen = ops.retlen;
1918593fbc6SThomas Gleixner return res;
1928593fbc6SThomas Gleixner }
1938593fbc6SThomas Gleixner
1948593fbc6SThomas Gleixner /*
1951da177e4SLinus Torvalds * INFTL_findfreeblock: Find a free Erase Unit on the INFTL partition.
1961da177e4SLinus Torvalds * This function is used when the give Virtual Unit Chain.
1971da177e4SLinus Torvalds */
INFTL_findfreeblock(struct INFTLrecord * inftl,int desperate)1981da177e4SLinus Torvalds static u16 INFTL_findfreeblock(struct INFTLrecord *inftl, int desperate)
1991da177e4SLinus Torvalds {
2001da177e4SLinus Torvalds u16 pot = inftl->LastFreeEUN;
2011da177e4SLinus Torvalds int silly = inftl->nb_blocks;
2021da177e4SLinus Torvalds
2030a32a102SBrian Norris pr_debug("INFTL: INFTL_findfreeblock(inftl=%p,desperate=%d)\n",
2040a32a102SBrian Norris inftl, desperate);
2051da177e4SLinus Torvalds
2061da177e4SLinus Torvalds /*
2071da177e4SLinus Torvalds * Normally, we force a fold to happen before we run out of free
2081da177e4SLinus Torvalds * blocks completely.
2091da177e4SLinus Torvalds */
2101da177e4SLinus Torvalds if (!desperate && inftl->numfreeEUNs < 2) {
2110a32a102SBrian Norris pr_debug("INFTL: there are too few free EUNs (%d)\n",
2120a32a102SBrian Norris inftl->numfreeEUNs);
21370ec3bb8SJulia Lawall return BLOCK_NIL;
2141da177e4SLinus Torvalds }
2151da177e4SLinus Torvalds
2161da177e4SLinus Torvalds /* Scan for a free block */
2171da177e4SLinus Torvalds do {
2181da177e4SLinus Torvalds if (inftl->PUtable[pot] == BLOCK_FREE) {
2191da177e4SLinus Torvalds inftl->LastFreeEUN = pot;
2201da177e4SLinus Torvalds return pot;
2211da177e4SLinus Torvalds }
2221da177e4SLinus Torvalds
2231da177e4SLinus Torvalds if (++pot > inftl->lastEUN)
2241da177e4SLinus Torvalds pot = 0;
2251da177e4SLinus Torvalds
2261da177e4SLinus Torvalds if (!silly--) {
2271da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: no free blocks found! "
2281da177e4SLinus Torvalds "EUN range = %d - %d\n", 0, inftl->LastFreeEUN);
2291da177e4SLinus Torvalds return BLOCK_NIL;
2301da177e4SLinus Torvalds }
2311da177e4SLinus Torvalds } while (pot != inftl->LastFreeEUN);
2321da177e4SLinus Torvalds
2331da177e4SLinus Torvalds return BLOCK_NIL;
2341da177e4SLinus Torvalds }
2351da177e4SLinus Torvalds
INFTL_foldchain(struct INFTLrecord * inftl,unsigned thisVUC,unsigned pendingblock)2361da177e4SLinus Torvalds static u16 INFTL_foldchain(struct INFTLrecord *inftl, unsigned thisVUC, unsigned pendingblock)
2371da177e4SLinus Torvalds {
2381da177e4SLinus Torvalds u16 BlockMap[MAX_SECTORS_PER_UNIT];
2391da177e4SLinus Torvalds unsigned char BlockDeleted[MAX_SECTORS_PER_UNIT];
2401da177e4SLinus Torvalds unsigned int thisEUN, prevEUN, status;
241f4a43cfcSThomas Gleixner struct mtd_info *mtd = inftl->mbd.mtd;
2421da177e4SLinus Torvalds int block, silly;
2431da177e4SLinus Torvalds unsigned int targetEUN;
2441da177e4SLinus Torvalds struct inftl_oob oob;
2451da177e4SLinus Torvalds size_t retlen;
2461da177e4SLinus Torvalds
2470a32a102SBrian Norris pr_debug("INFTL: INFTL_foldchain(inftl=%p,thisVUC=%d,pending=%d)\n",
2480a32a102SBrian Norris inftl, thisVUC, pendingblock);
2491da177e4SLinus Torvalds
2501da177e4SLinus Torvalds memset(BlockMap, 0xff, sizeof(BlockMap));
2511da177e4SLinus Torvalds memset(BlockDeleted, 0, sizeof(BlockDeleted));
2521da177e4SLinus Torvalds
2531da177e4SLinus Torvalds thisEUN = targetEUN = inftl->VUtable[thisVUC];
2541da177e4SLinus Torvalds
2551da177e4SLinus Torvalds if (thisEUN == BLOCK_NIL) {
2561da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: trying to fold non-existent "
2571da177e4SLinus Torvalds "Virtual Unit Chain %d!\n", thisVUC);
2581da177e4SLinus Torvalds return BLOCK_NIL;
2591da177e4SLinus Torvalds }
2601da177e4SLinus Torvalds
2611da177e4SLinus Torvalds /*
2621da177e4SLinus Torvalds * Scan to find the Erase Unit which holds the actual data for each
2631da177e4SLinus Torvalds * 512-byte block within the Chain.
2641da177e4SLinus Torvalds */
2651da177e4SLinus Torvalds silly = MAX_LOOPS;
2661da177e4SLinus Torvalds while (thisEUN < inftl->nb_blocks) {
2671da177e4SLinus Torvalds for (block = 0; block < inftl->EraseSize/SECTORSIZE; block ++) {
26870ec3bb8SJulia Lawall if ((BlockMap[block] != BLOCK_NIL) ||
26970ec3bb8SJulia Lawall BlockDeleted[block])
2701da177e4SLinus Torvalds continue;
2711da177e4SLinus Torvalds
2728593fbc6SThomas Gleixner if (inftl_read_oob(mtd, (thisEUN * inftl->EraseSize)
2731da177e4SLinus Torvalds + (block * SECTORSIZE), 16, &retlen,
2741da177e4SLinus Torvalds (char *)&oob) < 0)
2751da177e4SLinus Torvalds status = SECTOR_IGNORE;
2761da177e4SLinus Torvalds else
2771da177e4SLinus Torvalds status = oob.b.Status | oob.b.Status1;
2781da177e4SLinus Torvalds
2791da177e4SLinus Torvalds switch(status) {
2801da177e4SLinus Torvalds case SECTOR_FREE:
2811da177e4SLinus Torvalds case SECTOR_IGNORE:
2821da177e4SLinus Torvalds break;
2831da177e4SLinus Torvalds case SECTOR_USED:
2841da177e4SLinus Torvalds BlockMap[block] = thisEUN;
2851da177e4SLinus Torvalds continue;
2861da177e4SLinus Torvalds case SECTOR_DELETED:
2871da177e4SLinus Torvalds BlockDeleted[block] = 1;
2881da177e4SLinus Torvalds continue;
2891da177e4SLinus Torvalds default:
2901da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: unknown status "
2911da177e4SLinus Torvalds "for block %d in EUN %d: %x\n",
2921da177e4SLinus Torvalds block, thisEUN, status);
2931da177e4SLinus Torvalds break;
2941da177e4SLinus Torvalds }
2951da177e4SLinus Torvalds }
2961da177e4SLinus Torvalds
2971da177e4SLinus Torvalds if (!silly--) {
2981da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: infinite loop in Virtual "
2991da177e4SLinus Torvalds "Unit Chain 0x%x\n", thisVUC);
3001da177e4SLinus Torvalds return BLOCK_NIL;
3011da177e4SLinus Torvalds }
3021da177e4SLinus Torvalds
3031da177e4SLinus Torvalds thisEUN = inftl->PUtable[thisEUN];
3041da177e4SLinus Torvalds }
3051da177e4SLinus Torvalds
3061da177e4SLinus Torvalds /*
3071da177e4SLinus Torvalds * OK. We now know the location of every block in the Virtual Unit
3081da177e4SLinus Torvalds * Chain, and the Erase Unit into which we are supposed to be copying.
3091da177e4SLinus Torvalds * Go for it.
3101da177e4SLinus Torvalds */
3110a32a102SBrian Norris pr_debug("INFTL: folding chain %d into unit %d\n", thisVUC, targetEUN);
3121da177e4SLinus Torvalds
3131da177e4SLinus Torvalds for (block = 0; block < inftl->EraseSize/SECTORSIZE ; block++) {
3141da177e4SLinus Torvalds unsigned char movebuf[SECTORSIZE];
3151da177e4SLinus Torvalds int ret;
3161da177e4SLinus Torvalds
3171da177e4SLinus Torvalds /*
3181da177e4SLinus Torvalds * If it's in the target EUN already, or if it's pending write,
3191da177e4SLinus Torvalds * do nothing.
3201da177e4SLinus Torvalds */
3211da177e4SLinus Torvalds if (BlockMap[block] == targetEUN || (pendingblock ==
3221da177e4SLinus Torvalds (thisVUC * (inftl->EraseSize / SECTORSIZE) + block))) {
3231da177e4SLinus Torvalds continue;
3241da177e4SLinus Torvalds }
3251da177e4SLinus Torvalds
3261da177e4SLinus Torvalds /*
3271da177e4SLinus Torvalds * Copy only in non free block (free blocks can only
3281da177e4SLinus Torvalds * happen in case of media errors or deleted blocks).
3291da177e4SLinus Torvalds */
3301da177e4SLinus Torvalds if (BlockMap[block] == BLOCK_NIL)
3311da177e4SLinus Torvalds continue;
3321da177e4SLinus Torvalds
333329ad399SArtem Bityutskiy ret = mtd_read(mtd,
334329ad399SArtem Bityutskiy (inftl->EraseSize * BlockMap[block]) + (block * SECTORSIZE),
335329ad399SArtem Bityutskiy SECTORSIZE,
336329ad399SArtem Bityutskiy &retlen,
337f4a43cfcSThomas Gleixner movebuf);
338d57f4054SBrian Norris if (ret < 0 && !mtd_is_bitflip(ret)) {
339329ad399SArtem Bityutskiy ret = mtd_read(mtd,
340329ad399SArtem Bityutskiy (inftl->EraseSize * BlockMap[block]) + (block * SECTORSIZE),
341329ad399SArtem Bityutskiy SECTORSIZE,
342329ad399SArtem Bityutskiy &retlen,
343329ad399SArtem Bityutskiy movebuf);
3441da177e4SLinus Torvalds if (ret != -EIO)
3450a32a102SBrian Norris pr_debug("INFTL: error went away on retry?\n");
3461da177e4SLinus Torvalds }
3471da177e4SLinus Torvalds memset(&oob, 0xff, sizeof(struct inftl_oob));
3481da177e4SLinus Torvalds oob.b.Status = oob.b.Status1 = SECTOR_USED;
3499223a456SThomas Gleixner
3508593fbc6SThomas Gleixner inftl_write(inftl->mbd.mtd, (inftl->EraseSize * targetEUN) +
3511da177e4SLinus Torvalds (block * SECTORSIZE), SECTORSIZE, &retlen,
3529223a456SThomas Gleixner movebuf, (char *)&oob);
3531da177e4SLinus Torvalds }
3541da177e4SLinus Torvalds
3551da177e4SLinus Torvalds /*
3561da177e4SLinus Torvalds * Newest unit in chain now contains data from _all_ older units.
3571da177e4SLinus Torvalds * So go through and erase each unit in chain, oldest first. (This
3581da177e4SLinus Torvalds * is important, by doing oldest first if we crash/reboot then it
359*077dc37dSJilin Yuan * is relatively simple to clean up the mess).
3601da177e4SLinus Torvalds */
3610a32a102SBrian Norris pr_debug("INFTL: want to erase virtual chain %d\n", thisVUC);
3621da177e4SLinus Torvalds
3631da177e4SLinus Torvalds for (;;) {
3641da177e4SLinus Torvalds /* Find oldest unit in chain. */
3651da177e4SLinus Torvalds thisEUN = inftl->VUtable[thisVUC];
3661da177e4SLinus Torvalds prevEUN = BLOCK_NIL;
3671da177e4SLinus Torvalds while (inftl->PUtable[thisEUN] != BLOCK_NIL) {
3681da177e4SLinus Torvalds prevEUN = thisEUN;
3691da177e4SLinus Torvalds thisEUN = inftl->PUtable[thisEUN];
3701da177e4SLinus Torvalds }
3711da177e4SLinus Torvalds
3721da177e4SLinus Torvalds /* Check if we are all done */
3731da177e4SLinus Torvalds if (thisEUN == targetEUN)
3741da177e4SLinus Torvalds break;
3751da177e4SLinus Torvalds
37663fd7f30SDaniel Rosenthal /* Unlink the last block from the chain. */
37763fd7f30SDaniel Rosenthal inftl->PUtable[prevEUN] = BLOCK_NIL;
37863fd7f30SDaniel Rosenthal
37963fd7f30SDaniel Rosenthal /* Now try to erase it. */
3801da177e4SLinus Torvalds if (INFTL_formatblock(inftl, thisEUN) < 0) {
3811da177e4SLinus Torvalds /*
3821da177e4SLinus Torvalds * Could not erase : mark block as reserved.
3831da177e4SLinus Torvalds */
3841da177e4SLinus Torvalds inftl->PUtable[thisEUN] = BLOCK_RESERVED;
3851da177e4SLinus Torvalds } else {
3861da177e4SLinus Torvalds /* Correctly erased : mark it as free */
3871da177e4SLinus Torvalds inftl->PUtable[thisEUN] = BLOCK_FREE;
3881da177e4SLinus Torvalds inftl->numfreeEUNs++;
3891da177e4SLinus Torvalds }
3901da177e4SLinus Torvalds }
3911da177e4SLinus Torvalds
3921da177e4SLinus Torvalds return targetEUN;
3931da177e4SLinus Torvalds }
3941da177e4SLinus Torvalds
INFTL_makefreeblock(struct INFTLrecord * inftl,unsigned pendingblock)3951da177e4SLinus Torvalds static u16 INFTL_makefreeblock(struct INFTLrecord *inftl, unsigned pendingblock)
3961da177e4SLinus Torvalds {
3971da177e4SLinus Torvalds /*
3981da177e4SLinus Torvalds * This is the part that needs some cleverness applied.
3991da177e4SLinus Torvalds * For now, I'm doing the minimum applicable to actually
4001da177e4SLinus Torvalds * get the thing to work.
4011da177e4SLinus Torvalds * Wear-levelling and other clever stuff needs to be implemented
4021da177e4SLinus Torvalds * and we also need to do some assessment of the results when
4031da177e4SLinus Torvalds * the system loses power half-way through the routine.
4041da177e4SLinus Torvalds */
4051da177e4SLinus Torvalds u16 LongestChain = 0;
4061da177e4SLinus Torvalds u16 ChainLength = 0, thislen;
4071da177e4SLinus Torvalds u16 chain, EUN;
4081da177e4SLinus Torvalds
409289c0522SBrian Norris pr_debug("INFTL: INFTL_makefreeblock(inftl=%p,"
4101da177e4SLinus Torvalds "pending=%d)\n", inftl, pendingblock);
4111da177e4SLinus Torvalds
4121da177e4SLinus Torvalds for (chain = 0; chain < inftl->nb_blocks; chain++) {
4131da177e4SLinus Torvalds EUN = inftl->VUtable[chain];
4141da177e4SLinus Torvalds thislen = 0;
4151da177e4SLinus Torvalds
4161da177e4SLinus Torvalds while (EUN <= inftl->lastEUN) {
4171da177e4SLinus Torvalds thislen++;
4181da177e4SLinus Torvalds EUN = inftl->PUtable[EUN];
4191da177e4SLinus Torvalds if (thislen > 0xff00) {
4201da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: endless loop in "
4211da177e4SLinus Torvalds "Virtual Chain %d: Unit %x\n",
4221da177e4SLinus Torvalds chain, EUN);
4231da177e4SLinus Torvalds /*
4241da177e4SLinus Torvalds * Actually, don't return failure.
4251da177e4SLinus Torvalds * Just ignore this chain and get on with it.
4261da177e4SLinus Torvalds */
4271da177e4SLinus Torvalds thislen = 0;
4281da177e4SLinus Torvalds break;
4291da177e4SLinus Torvalds }
4301da177e4SLinus Torvalds }
4311da177e4SLinus Torvalds
4321da177e4SLinus Torvalds if (thislen > ChainLength) {
4331da177e4SLinus Torvalds ChainLength = thislen;
4341da177e4SLinus Torvalds LongestChain = chain;
4351da177e4SLinus Torvalds }
4361da177e4SLinus Torvalds }
4371da177e4SLinus Torvalds
4381da177e4SLinus Torvalds if (ChainLength < 2) {
4391da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: no Virtual Unit Chains available "
4401da177e4SLinus Torvalds "for folding. Failing request\n");
4411da177e4SLinus Torvalds return BLOCK_NIL;
4421da177e4SLinus Torvalds }
4431da177e4SLinus Torvalds
4441da177e4SLinus Torvalds return INFTL_foldchain(inftl, LongestChain, pendingblock);
4451da177e4SLinus Torvalds }
4461da177e4SLinus Torvalds
nrbits(unsigned int val,int bitcount)4471da177e4SLinus Torvalds static int nrbits(unsigned int val, int bitcount)
4481da177e4SLinus Torvalds {
4491da177e4SLinus Torvalds int i, total = 0;
4501da177e4SLinus Torvalds
4511da177e4SLinus Torvalds for (i = 0; (i < bitcount); i++)
4521da177e4SLinus Torvalds total += (((0x1 << i) & val) ? 1 : 0);
4531da177e4SLinus Torvalds return total;
4541da177e4SLinus Torvalds }
4551da177e4SLinus Torvalds
4561da177e4SLinus Torvalds /*
4571da177e4SLinus Torvalds * INFTL_findwriteunit: Return the unit number into which we can write
4581da177e4SLinus Torvalds * for this block. Make it available if it isn't already.
4591da177e4SLinus Torvalds */
INFTL_findwriteunit(struct INFTLrecord * inftl,unsigned block)4601da177e4SLinus Torvalds static inline u16 INFTL_findwriteunit(struct INFTLrecord *inftl, unsigned block)
4611da177e4SLinus Torvalds {
4621da177e4SLinus Torvalds unsigned int thisVUC = block / (inftl->EraseSize / SECTORSIZE);
4631da177e4SLinus Torvalds unsigned int thisEUN, writeEUN, prev_block, status;
4641da177e4SLinus Torvalds unsigned long blockofs = (block * SECTORSIZE) & (inftl->EraseSize -1);
465f4a43cfcSThomas Gleixner struct mtd_info *mtd = inftl->mbd.mtd;
4661da177e4SLinus Torvalds struct inftl_oob oob;
4671da177e4SLinus Torvalds struct inftl_bci bci;
4681da177e4SLinus Torvalds unsigned char anac, nacs, parity;
4691da177e4SLinus Torvalds size_t retlen;
4701da177e4SLinus Torvalds int silly, silly2 = 3;
4711da177e4SLinus Torvalds
4720a32a102SBrian Norris pr_debug("INFTL: INFTL_findwriteunit(inftl=%p,block=%d)\n",
4730a32a102SBrian Norris inftl, block);
4741da177e4SLinus Torvalds
4751da177e4SLinus Torvalds do {
4761da177e4SLinus Torvalds /*
4771da177e4SLinus Torvalds * Scan the media to find a unit in the VUC which has
4781da177e4SLinus Torvalds * a free space for the block in question.
4791da177e4SLinus Torvalds */
4801da177e4SLinus Torvalds writeEUN = BLOCK_NIL;
4811da177e4SLinus Torvalds thisEUN = inftl->VUtable[thisVUC];
4821da177e4SLinus Torvalds silly = MAX_LOOPS;
4831da177e4SLinus Torvalds
4841da177e4SLinus Torvalds while (thisEUN <= inftl->lastEUN) {
4858593fbc6SThomas Gleixner inftl_read_oob(mtd, (thisEUN * inftl->EraseSize) +
4861da177e4SLinus Torvalds blockofs, 8, &retlen, (char *)&bci);
4871da177e4SLinus Torvalds
4881da177e4SLinus Torvalds status = bci.Status | bci.Status1;
4890a32a102SBrian Norris pr_debug("INFTL: status of block %d in EUN %d is %x\n",
4900a32a102SBrian Norris block , writeEUN, status);
4911da177e4SLinus Torvalds
4921da177e4SLinus Torvalds switch(status) {
4931da177e4SLinus Torvalds case SECTOR_FREE:
4941da177e4SLinus Torvalds writeEUN = thisEUN;
4951da177e4SLinus Torvalds break;
4961da177e4SLinus Torvalds case SECTOR_DELETED:
4971da177e4SLinus Torvalds case SECTOR_USED:
4981da177e4SLinus Torvalds /* Can't go any further */
4991da177e4SLinus Torvalds goto hitused;
5001da177e4SLinus Torvalds case SECTOR_IGNORE:
5011da177e4SLinus Torvalds break;
5021da177e4SLinus Torvalds default:
5031da177e4SLinus Torvalds /*
5041da177e4SLinus Torvalds * Invalid block. Don't use it any more.
5051da177e4SLinus Torvalds * Must implement.
5061da177e4SLinus Torvalds */
5071da177e4SLinus Torvalds break;
5081da177e4SLinus Torvalds }
5091da177e4SLinus Torvalds
5101da177e4SLinus Torvalds if (!silly--) {
5111da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: infinite loop in "
5121da177e4SLinus Torvalds "Virtual Unit Chain 0x%x\n", thisVUC);
51370ec3bb8SJulia Lawall return BLOCK_NIL;
5141da177e4SLinus Torvalds }
5151da177e4SLinus Torvalds
5161da177e4SLinus Torvalds /* Skip to next block in chain */
5171da177e4SLinus Torvalds thisEUN = inftl->PUtable[thisEUN];
5181da177e4SLinus Torvalds }
5191da177e4SLinus Torvalds
5201da177e4SLinus Torvalds hitused:
5211da177e4SLinus Torvalds if (writeEUN != BLOCK_NIL)
5221da177e4SLinus Torvalds return writeEUN;
5231da177e4SLinus Torvalds
5241da177e4SLinus Torvalds
5251da177e4SLinus Torvalds /*
5261da177e4SLinus Torvalds * OK. We didn't find one in the existing chain, or there
5271da177e4SLinus Torvalds * is no existing chain. Allocate a new one.
5281da177e4SLinus Torvalds */
5291da177e4SLinus Torvalds writeEUN = INFTL_findfreeblock(inftl, 0);
5301da177e4SLinus Torvalds
5311da177e4SLinus Torvalds if (writeEUN == BLOCK_NIL) {
5321da177e4SLinus Torvalds /*
5331da177e4SLinus Torvalds * That didn't work - there were no free blocks just
5341da177e4SLinus Torvalds * waiting to be picked up. We're going to have to fold
5351da177e4SLinus Torvalds * a chain to make room.
5361da177e4SLinus Torvalds */
5376ad08dddSMohanlal Jangir thisEUN = INFTL_makefreeblock(inftl, block);
5381da177e4SLinus Torvalds
5391da177e4SLinus Torvalds /*
5401da177e4SLinus Torvalds * Hopefully we free something, lets try again.
5411da177e4SLinus Torvalds * This time we are desperate...
5421da177e4SLinus Torvalds */
5430a32a102SBrian Norris pr_debug("INFTL: using desperate==1 to find free EUN "
5440a32a102SBrian Norris "to accommodate write to VUC %d\n",
5450a32a102SBrian Norris thisVUC);
5461da177e4SLinus Torvalds writeEUN = INFTL_findfreeblock(inftl, 1);
5471da177e4SLinus Torvalds if (writeEUN == BLOCK_NIL) {
5481da177e4SLinus Torvalds /*
5491da177e4SLinus Torvalds * Ouch. This should never happen - we should
5501da177e4SLinus Torvalds * always be able to make some room somehow.
5511da177e4SLinus Torvalds * If we get here, we've allocated more storage
5521da177e4SLinus Torvalds * space than actual media, or our makefreeblock
5531da177e4SLinus Torvalds * routine is missing something.
5541da177e4SLinus Torvalds */
5551da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: cannot make free "
5561da177e4SLinus Torvalds "space.\n");
5571da177e4SLinus Torvalds #ifdef DEBUG
5581da177e4SLinus Torvalds INFTL_dumptables(inftl);
5591da177e4SLinus Torvalds INFTL_dumpVUchains(inftl);
5601da177e4SLinus Torvalds #endif
5611da177e4SLinus Torvalds return BLOCK_NIL;
5621da177e4SLinus Torvalds }
5631da177e4SLinus Torvalds }
5641da177e4SLinus Torvalds
5651da177e4SLinus Torvalds /*
5661da177e4SLinus Torvalds * Insert new block into virtual chain. Firstly update the
5671da177e4SLinus Torvalds * block headers in flash...
5681da177e4SLinus Torvalds */
5691da177e4SLinus Torvalds anac = 0;
5701da177e4SLinus Torvalds nacs = 0;
5711da177e4SLinus Torvalds thisEUN = inftl->VUtable[thisVUC];
5721da177e4SLinus Torvalds if (thisEUN != BLOCK_NIL) {
5738593fbc6SThomas Gleixner inftl_read_oob(mtd, thisEUN * inftl->EraseSize
5741da177e4SLinus Torvalds + 8, 8, &retlen, (char *)&oob.u);
5751da177e4SLinus Torvalds anac = oob.u.a.ANAC + 1;
5761da177e4SLinus Torvalds nacs = oob.u.a.NACs + 1;
5771da177e4SLinus Torvalds }
5781da177e4SLinus Torvalds
5791da177e4SLinus Torvalds prev_block = inftl->VUtable[thisVUC];
5801da177e4SLinus Torvalds if (prev_block < inftl->nb_blocks)
5811da177e4SLinus Torvalds prev_block -= inftl->firstEUN;
5821da177e4SLinus Torvalds
5831da177e4SLinus Torvalds parity = (nrbits(thisVUC, 16) & 0x1) ? 0x1 : 0;
5841da177e4SLinus Torvalds parity |= (nrbits(prev_block, 16) & 0x1) ? 0x2 : 0;
5851da177e4SLinus Torvalds parity |= (nrbits(anac, 8) & 0x1) ? 0x4 : 0;
5861da177e4SLinus Torvalds parity |= (nrbits(nacs, 8) & 0x1) ? 0x8 : 0;
5871da177e4SLinus Torvalds
5881da177e4SLinus Torvalds oob.u.a.virtualUnitNo = cpu_to_le16(thisVUC);
5891da177e4SLinus Torvalds oob.u.a.prevUnitNo = cpu_to_le16(prev_block);
5901da177e4SLinus Torvalds oob.u.a.ANAC = anac;
5911da177e4SLinus Torvalds oob.u.a.NACs = nacs;
5921da177e4SLinus Torvalds oob.u.a.parityPerField = parity;
5931da177e4SLinus Torvalds oob.u.a.discarded = 0xaa;
5941da177e4SLinus Torvalds
5958593fbc6SThomas Gleixner inftl_write_oob(mtd, writeEUN * inftl->EraseSize + 8, 8,
5961da177e4SLinus Torvalds &retlen, (char *)&oob.u);
5971da177e4SLinus Torvalds
5981da177e4SLinus Torvalds /* Also back up header... */
5991da177e4SLinus Torvalds oob.u.b.virtualUnitNo = cpu_to_le16(thisVUC);
6001da177e4SLinus Torvalds oob.u.b.prevUnitNo = cpu_to_le16(prev_block);
6011da177e4SLinus Torvalds oob.u.b.ANAC = anac;
6021da177e4SLinus Torvalds oob.u.b.NACs = nacs;
6031da177e4SLinus Torvalds oob.u.b.parityPerField = parity;
6041da177e4SLinus Torvalds oob.u.b.discarded = 0xaa;
6051da177e4SLinus Torvalds
6068593fbc6SThomas Gleixner inftl_write_oob(mtd, writeEUN * inftl->EraseSize +
6071da177e4SLinus Torvalds SECTORSIZE * 4 + 8, 8, &retlen, (char *)&oob.u);
6081da177e4SLinus Torvalds
6091da177e4SLinus Torvalds inftl->PUtable[writeEUN] = inftl->VUtable[thisVUC];
6101da177e4SLinus Torvalds inftl->VUtable[thisVUC] = writeEUN;
6111da177e4SLinus Torvalds
6121da177e4SLinus Torvalds inftl->numfreeEUNs--;
6131da177e4SLinus Torvalds return writeEUN;
6141da177e4SLinus Torvalds
6151da177e4SLinus Torvalds } while (silly2--);
6161da177e4SLinus Torvalds
6171da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: error folding to make room for Virtual "
6181da177e4SLinus Torvalds "Unit Chain 0x%x\n", thisVUC);
61970ec3bb8SJulia Lawall return BLOCK_NIL;
6201da177e4SLinus Torvalds }
6211da177e4SLinus Torvalds
6221da177e4SLinus Torvalds /*
6231da177e4SLinus Torvalds * Given a Virtual Unit Chain, see if it can be deleted, and if so do it.
6241da177e4SLinus Torvalds */
INFTL_trydeletechain(struct INFTLrecord * inftl,unsigned thisVUC)6251da177e4SLinus Torvalds static void INFTL_trydeletechain(struct INFTLrecord *inftl, unsigned thisVUC)
6261da177e4SLinus Torvalds {
627f4a43cfcSThomas Gleixner struct mtd_info *mtd = inftl->mbd.mtd;
6281da177e4SLinus Torvalds unsigned char BlockUsed[MAX_SECTORS_PER_UNIT];
6291da177e4SLinus Torvalds unsigned char BlockDeleted[MAX_SECTORS_PER_UNIT];
6301da177e4SLinus Torvalds unsigned int thisEUN, status;
6311da177e4SLinus Torvalds int block, silly;
6321da177e4SLinus Torvalds struct inftl_bci bci;
6331da177e4SLinus Torvalds size_t retlen;
6341da177e4SLinus Torvalds
635289c0522SBrian Norris pr_debug("INFTL: INFTL_trydeletechain(inftl=%p,"
6361da177e4SLinus Torvalds "thisVUC=%d)\n", inftl, thisVUC);
6371da177e4SLinus Torvalds
6381da177e4SLinus Torvalds memset(BlockUsed, 0, sizeof(BlockUsed));
6391da177e4SLinus Torvalds memset(BlockDeleted, 0, sizeof(BlockDeleted));
6401da177e4SLinus Torvalds
6411da177e4SLinus Torvalds thisEUN = inftl->VUtable[thisVUC];
6421da177e4SLinus Torvalds if (thisEUN == BLOCK_NIL) {
6431da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: trying to delete non-existent "
6441da177e4SLinus Torvalds "Virtual Unit Chain %d!\n", thisVUC);
6451da177e4SLinus Torvalds return;
6461da177e4SLinus Torvalds }
6471da177e4SLinus Torvalds
6481da177e4SLinus Torvalds /*
6491da177e4SLinus Torvalds * Scan through the Erase Units to determine whether any data is in
6501da177e4SLinus Torvalds * each of the 512-byte blocks within the Chain.
6511da177e4SLinus Torvalds */
6521da177e4SLinus Torvalds silly = MAX_LOOPS;
6531da177e4SLinus Torvalds while (thisEUN < inftl->nb_blocks) {
6541da177e4SLinus Torvalds for (block = 0; block < inftl->EraseSize/SECTORSIZE; block++) {
6551da177e4SLinus Torvalds if (BlockUsed[block] || BlockDeleted[block])
6561da177e4SLinus Torvalds continue;
6571da177e4SLinus Torvalds
6588593fbc6SThomas Gleixner if (inftl_read_oob(mtd, (thisEUN * inftl->EraseSize)
6591da177e4SLinus Torvalds + (block * SECTORSIZE), 8 , &retlen,
6601da177e4SLinus Torvalds (char *)&bci) < 0)
6611da177e4SLinus Torvalds status = SECTOR_IGNORE;
6621da177e4SLinus Torvalds else
6631da177e4SLinus Torvalds status = bci.Status | bci.Status1;
6641da177e4SLinus Torvalds
6651da177e4SLinus Torvalds switch(status) {
6661da177e4SLinus Torvalds case SECTOR_FREE:
6671da177e4SLinus Torvalds case SECTOR_IGNORE:
6681da177e4SLinus Torvalds break;
6691da177e4SLinus Torvalds case SECTOR_USED:
6701da177e4SLinus Torvalds BlockUsed[block] = 1;
6711da177e4SLinus Torvalds continue;
6721da177e4SLinus Torvalds case SECTOR_DELETED:
6731da177e4SLinus Torvalds BlockDeleted[block] = 1;
6741da177e4SLinus Torvalds continue;
6751da177e4SLinus Torvalds default:
6761da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: unknown status "
6771da177e4SLinus Torvalds "for block %d in EUN %d: 0x%x\n",
6781da177e4SLinus Torvalds block, thisEUN, status);
6791da177e4SLinus Torvalds }
6801da177e4SLinus Torvalds }
6811da177e4SLinus Torvalds
6821da177e4SLinus Torvalds if (!silly--) {
6831da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: infinite loop in Virtual "
6841da177e4SLinus Torvalds "Unit Chain 0x%x\n", thisVUC);
6851da177e4SLinus Torvalds return;
6861da177e4SLinus Torvalds }
6871da177e4SLinus Torvalds
6881da177e4SLinus Torvalds thisEUN = inftl->PUtable[thisEUN];
6891da177e4SLinus Torvalds }
6901da177e4SLinus Torvalds
6911da177e4SLinus Torvalds for (block = 0; block < inftl->EraseSize/SECTORSIZE; block++)
6921da177e4SLinus Torvalds if (BlockUsed[block])
6931da177e4SLinus Torvalds return;
6941da177e4SLinus Torvalds
6951da177e4SLinus Torvalds /*
6961da177e4SLinus Torvalds * For each block in the chain free it and make it available
6971da177e4SLinus Torvalds * for future use. Erase from the oldest unit first.
6981da177e4SLinus Torvalds */
699289c0522SBrian Norris pr_debug("INFTL: deleting empty VUC %d\n", thisVUC);
7001da177e4SLinus Torvalds
7011da177e4SLinus Torvalds for (;;) {
7021da177e4SLinus Torvalds u16 *prevEUN = &inftl->VUtable[thisVUC];
7031da177e4SLinus Torvalds thisEUN = *prevEUN;
7041da177e4SLinus Torvalds
7051da177e4SLinus Torvalds /* If the chain is all gone already, we're done */
7061da177e4SLinus Torvalds if (thisEUN == BLOCK_NIL) {
707289c0522SBrian Norris pr_debug("INFTL: Empty VUC %d for deletion was already absent\n", thisEUN);
7081da177e4SLinus Torvalds return;
7091da177e4SLinus Torvalds }
7101da177e4SLinus Torvalds
7111da177e4SLinus Torvalds /* Find oldest unit in chain. */
7121da177e4SLinus Torvalds while (inftl->PUtable[thisEUN] != BLOCK_NIL) {
7131da177e4SLinus Torvalds BUG_ON(thisEUN >= inftl->nb_blocks);
7141da177e4SLinus Torvalds
7151da177e4SLinus Torvalds prevEUN = &inftl->PUtable[thisEUN];
7161da177e4SLinus Torvalds thisEUN = *prevEUN;
7171da177e4SLinus Torvalds }
7181da177e4SLinus Torvalds
719289c0522SBrian Norris pr_debug("Deleting EUN %d from VUC %d\n",
7201da177e4SLinus Torvalds thisEUN, thisVUC);
7211da177e4SLinus Torvalds
7221da177e4SLinus Torvalds if (INFTL_formatblock(inftl, thisEUN) < 0) {
7231da177e4SLinus Torvalds /*
7241da177e4SLinus Torvalds * Could not erase : mark block as reserved.
7251da177e4SLinus Torvalds */
7261da177e4SLinus Torvalds inftl->PUtable[thisEUN] = BLOCK_RESERVED;
7271da177e4SLinus Torvalds } else {
7281da177e4SLinus Torvalds /* Correctly erased : mark it as free */
7291da177e4SLinus Torvalds inftl->PUtable[thisEUN] = BLOCK_FREE;
7301da177e4SLinus Torvalds inftl->numfreeEUNs++;
7311da177e4SLinus Torvalds }
7321da177e4SLinus Torvalds
7331da177e4SLinus Torvalds /* Now sort out whatever was pointing to it... */
7341da177e4SLinus Torvalds *prevEUN = BLOCK_NIL;
7351da177e4SLinus Torvalds
7361da177e4SLinus Torvalds /* Ideally we'd actually be responsive to new
7371da177e4SLinus Torvalds requests while we're doing this -- if there's
7381da177e4SLinus Torvalds free space why should others be made to wait? */
7391da177e4SLinus Torvalds cond_resched();
7401da177e4SLinus Torvalds }
7411da177e4SLinus Torvalds
7421da177e4SLinus Torvalds inftl->VUtable[thisVUC] = BLOCK_NIL;
7431da177e4SLinus Torvalds }
7441da177e4SLinus Torvalds
INFTL_deleteblock(struct INFTLrecord * inftl,unsigned block)7451da177e4SLinus Torvalds static int INFTL_deleteblock(struct INFTLrecord *inftl, unsigned block)
7461da177e4SLinus Torvalds {
7471da177e4SLinus Torvalds unsigned int thisEUN = inftl->VUtable[block / (inftl->EraseSize / SECTORSIZE)];
7481da177e4SLinus Torvalds unsigned long blockofs = (block * SECTORSIZE) & (inftl->EraseSize - 1);
749f4a43cfcSThomas Gleixner struct mtd_info *mtd = inftl->mbd.mtd;
7501da177e4SLinus Torvalds unsigned int status;
7511da177e4SLinus Torvalds int silly = MAX_LOOPS;
7521da177e4SLinus Torvalds size_t retlen;
7531da177e4SLinus Torvalds struct inftl_bci bci;
7541da177e4SLinus Torvalds
755289c0522SBrian Norris pr_debug("INFTL: INFTL_deleteblock(inftl=%p,"
7561da177e4SLinus Torvalds "block=%d)\n", inftl, block);
7571da177e4SLinus Torvalds
7581da177e4SLinus Torvalds while (thisEUN < inftl->nb_blocks) {
7598593fbc6SThomas Gleixner if (inftl_read_oob(mtd, (thisEUN * inftl->EraseSize) +
7601da177e4SLinus Torvalds blockofs, 8, &retlen, (char *)&bci) < 0)
7611da177e4SLinus Torvalds status = SECTOR_IGNORE;
7621da177e4SLinus Torvalds else
7631da177e4SLinus Torvalds status = bci.Status | bci.Status1;
7641da177e4SLinus Torvalds
7651da177e4SLinus Torvalds switch (status) {
7661da177e4SLinus Torvalds case SECTOR_FREE:
7671da177e4SLinus Torvalds case SECTOR_IGNORE:
7681da177e4SLinus Torvalds break;
7691da177e4SLinus Torvalds case SECTOR_DELETED:
7701da177e4SLinus Torvalds thisEUN = BLOCK_NIL;
7711da177e4SLinus Torvalds goto foundit;
7721da177e4SLinus Torvalds case SECTOR_USED:
7731da177e4SLinus Torvalds goto foundit;
7741da177e4SLinus Torvalds default:
7751da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: unknown status for "
7761da177e4SLinus Torvalds "block %d in EUN %d: 0x%x\n",
7771da177e4SLinus Torvalds block, thisEUN, status);
7781da177e4SLinus Torvalds break;
7791da177e4SLinus Torvalds }
7801da177e4SLinus Torvalds
7811da177e4SLinus Torvalds if (!silly--) {
7821da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: infinite loop in Virtual "
7831da177e4SLinus Torvalds "Unit Chain 0x%x\n",
7841da177e4SLinus Torvalds block / (inftl->EraseSize / SECTORSIZE));
7851da177e4SLinus Torvalds return 1;
7861da177e4SLinus Torvalds }
7871da177e4SLinus Torvalds thisEUN = inftl->PUtable[thisEUN];
7881da177e4SLinus Torvalds }
7891da177e4SLinus Torvalds
7901da177e4SLinus Torvalds foundit:
7911da177e4SLinus Torvalds if (thisEUN != BLOCK_NIL) {
7921da177e4SLinus Torvalds loff_t ptr = (thisEUN * inftl->EraseSize) + blockofs;
7931da177e4SLinus Torvalds
7948593fbc6SThomas Gleixner if (inftl_read_oob(mtd, ptr, 8, &retlen, (char *)&bci) < 0)
7951da177e4SLinus Torvalds return -EIO;
7961da177e4SLinus Torvalds bci.Status = bci.Status1 = SECTOR_DELETED;
7978593fbc6SThomas Gleixner if (inftl_write_oob(mtd, ptr, 8, &retlen, (char *)&bci) < 0)
7981da177e4SLinus Torvalds return -EIO;
7991da177e4SLinus Torvalds INFTL_trydeletechain(inftl, block / (inftl->EraseSize / SECTORSIZE));
8001da177e4SLinus Torvalds }
8011da177e4SLinus Torvalds return 0;
8021da177e4SLinus Torvalds }
8031da177e4SLinus Torvalds
inftl_writeblock(struct mtd_blktrans_dev * mbd,unsigned long block,char * buffer)8041da177e4SLinus Torvalds static int inftl_writeblock(struct mtd_blktrans_dev *mbd, unsigned long block,
8051da177e4SLinus Torvalds char *buffer)
8061da177e4SLinus Torvalds {
8071da177e4SLinus Torvalds struct INFTLrecord *inftl = (void *)mbd;
8081da177e4SLinus Torvalds unsigned int writeEUN;
8091da177e4SLinus Torvalds unsigned long blockofs = (block * SECTORSIZE) & (inftl->EraseSize - 1);
8101da177e4SLinus Torvalds size_t retlen;
8111da177e4SLinus Torvalds struct inftl_oob oob;
8121da177e4SLinus Torvalds char *p, *pend;
8131da177e4SLinus Torvalds
814289c0522SBrian Norris pr_debug("INFTL: inftl_writeblock(inftl=%p,block=%ld,"
8151da177e4SLinus Torvalds "buffer=%p)\n", inftl, block, buffer);
8161da177e4SLinus Torvalds
8171da177e4SLinus Torvalds /* Is block all zero? */
8181da177e4SLinus Torvalds pend = buffer + SECTORSIZE;
8191da177e4SLinus Torvalds for (p = buffer; p < pend && !*p; p++)
8201da177e4SLinus Torvalds ;
8211da177e4SLinus Torvalds
8221da177e4SLinus Torvalds if (p < pend) {
8231da177e4SLinus Torvalds writeEUN = INFTL_findwriteunit(inftl, block);
8241da177e4SLinus Torvalds
8251da177e4SLinus Torvalds if (writeEUN == BLOCK_NIL) {
8261da177e4SLinus Torvalds printk(KERN_WARNING "inftl_writeblock(): cannot find "
8271da177e4SLinus Torvalds "block to write to\n");
8281da177e4SLinus Torvalds /*
8291da177e4SLinus Torvalds * If we _still_ haven't got a block to use,
8301da177e4SLinus Torvalds * we're screwed.
8311da177e4SLinus Torvalds */
8321da177e4SLinus Torvalds return 1;
8331da177e4SLinus Torvalds }
8341da177e4SLinus Torvalds
8351da177e4SLinus Torvalds memset(&oob, 0xff, sizeof(struct inftl_oob));
8361da177e4SLinus Torvalds oob.b.Status = oob.b.Status1 = SECTOR_USED;
8379223a456SThomas Gleixner
8388593fbc6SThomas Gleixner inftl_write(inftl->mbd.mtd, (writeEUN * inftl->EraseSize) +
8391da177e4SLinus Torvalds blockofs, SECTORSIZE, &retlen, (char *)buffer,
8409223a456SThomas Gleixner (char *)&oob);
8411da177e4SLinus Torvalds /*
8421da177e4SLinus Torvalds * need to write SECTOR_USED flags since they are not written
8431da177e4SLinus Torvalds * in mtd_writeecc
8441da177e4SLinus Torvalds */
8451da177e4SLinus Torvalds } else {
8461da177e4SLinus Torvalds INFTL_deleteblock(inftl, block);
8471da177e4SLinus Torvalds }
8481da177e4SLinus Torvalds
8491da177e4SLinus Torvalds return 0;
8501da177e4SLinus Torvalds }
8511da177e4SLinus Torvalds
inftl_readblock(struct mtd_blktrans_dev * mbd,unsigned long block,char * buffer)8521da177e4SLinus Torvalds static int inftl_readblock(struct mtd_blktrans_dev *mbd, unsigned long block,
8531da177e4SLinus Torvalds char *buffer)
8541da177e4SLinus Torvalds {
8551da177e4SLinus Torvalds struct INFTLrecord *inftl = (void *)mbd;
8561da177e4SLinus Torvalds unsigned int thisEUN = inftl->VUtable[block / (inftl->EraseSize / SECTORSIZE)];
8571da177e4SLinus Torvalds unsigned long blockofs = (block * SECTORSIZE) & (inftl->EraseSize - 1);
858f4a43cfcSThomas Gleixner struct mtd_info *mtd = inftl->mbd.mtd;
8591da177e4SLinus Torvalds unsigned int status;
8601da177e4SLinus Torvalds int silly = MAX_LOOPS;
8611da177e4SLinus Torvalds struct inftl_bci bci;
8621da177e4SLinus Torvalds size_t retlen;
8631da177e4SLinus Torvalds
864289c0522SBrian Norris pr_debug("INFTL: inftl_readblock(inftl=%p,block=%ld,"
8651da177e4SLinus Torvalds "buffer=%p)\n", inftl, block, buffer);
8661da177e4SLinus Torvalds
8671da177e4SLinus Torvalds while (thisEUN < inftl->nb_blocks) {
8688593fbc6SThomas Gleixner if (inftl_read_oob(mtd, (thisEUN * inftl->EraseSize) +
8691da177e4SLinus Torvalds blockofs, 8, &retlen, (char *)&bci) < 0)
8701da177e4SLinus Torvalds status = SECTOR_IGNORE;
8711da177e4SLinus Torvalds else
8721da177e4SLinus Torvalds status = bci.Status | bci.Status1;
8731da177e4SLinus Torvalds
8741da177e4SLinus Torvalds switch (status) {
8751da177e4SLinus Torvalds case SECTOR_DELETED:
8761da177e4SLinus Torvalds thisEUN = BLOCK_NIL;
8771da177e4SLinus Torvalds goto foundit;
8781da177e4SLinus Torvalds case SECTOR_USED:
8791da177e4SLinus Torvalds goto foundit;
8801da177e4SLinus Torvalds case SECTOR_FREE:
8811da177e4SLinus Torvalds case SECTOR_IGNORE:
8821da177e4SLinus Torvalds break;
8831da177e4SLinus Torvalds default:
8841da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: unknown status for "
8851da177e4SLinus Torvalds "block %ld in EUN %d: 0x%04x\n",
8861da177e4SLinus Torvalds block, thisEUN, status);
8871da177e4SLinus Torvalds break;
8881da177e4SLinus Torvalds }
8891da177e4SLinus Torvalds
8901da177e4SLinus Torvalds if (!silly--) {
8911da177e4SLinus Torvalds printk(KERN_WARNING "INFTL: infinite loop in "
8921da177e4SLinus Torvalds "Virtual Unit Chain 0x%lx\n",
8931da177e4SLinus Torvalds block / (inftl->EraseSize / SECTORSIZE));
8941da177e4SLinus Torvalds return 1;
8951da177e4SLinus Torvalds }
8961da177e4SLinus Torvalds
8971da177e4SLinus Torvalds thisEUN = inftl->PUtable[thisEUN];
8981da177e4SLinus Torvalds }
8991da177e4SLinus Torvalds
9001da177e4SLinus Torvalds foundit:
9011da177e4SLinus Torvalds if (thisEUN == BLOCK_NIL) {
9021da177e4SLinus Torvalds /* The requested block is not on the media, return all 0x00 */
9031da177e4SLinus Torvalds memset(buffer, 0, SECTORSIZE);
9041da177e4SLinus Torvalds } else {
9051da177e4SLinus Torvalds size_t retlen;
9061da177e4SLinus Torvalds loff_t ptr = (thisEUN * inftl->EraseSize) + blockofs;
907329ad399SArtem Bityutskiy int ret = mtd_read(mtd, ptr, SECTORSIZE, &retlen, buffer);
9089a1fcdfdSThomas Gleixner
9099a1fcdfdSThomas Gleixner /* Handle corrected bit flips gracefully */
910d57f4054SBrian Norris if (ret < 0 && !mtd_is_bitflip(ret))
9111da177e4SLinus Torvalds return -EIO;
9121da177e4SLinus Torvalds }
9131da177e4SLinus Torvalds return 0;
9141da177e4SLinus Torvalds }
9151da177e4SLinus Torvalds
inftl_getgeo(struct mtd_blktrans_dev * dev,struct hd_geometry * geo)9161da177e4SLinus Torvalds static int inftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
9171da177e4SLinus Torvalds {
9181da177e4SLinus Torvalds struct INFTLrecord *inftl = (void *)dev;
9191da177e4SLinus Torvalds
9201da177e4SLinus Torvalds geo->heads = inftl->heads;
9211da177e4SLinus Torvalds geo->sectors = inftl->sectors;
9221da177e4SLinus Torvalds geo->cylinders = inftl->cylinders;
9231da177e4SLinus Torvalds
9241da177e4SLinus Torvalds return 0;
9251da177e4SLinus Torvalds }
9261da177e4SLinus Torvalds
9271da177e4SLinus Torvalds static struct mtd_blktrans_ops inftl_tr = {
9281da177e4SLinus Torvalds .name = "inftl",
9291da177e4SLinus Torvalds .major = INFTL_MAJOR,
9301da177e4SLinus Torvalds .part_bits = INFTL_PARTN_BITS,
93119187672SRichard Purdie .blksize = 512,
9321da177e4SLinus Torvalds .getgeo = inftl_getgeo,
9331da177e4SLinus Torvalds .readsect = inftl_readblock,
9341da177e4SLinus Torvalds .writesect = inftl_writeblock,
9351da177e4SLinus Torvalds .add_mtd = inftl_add_mtd,
9361da177e4SLinus Torvalds .remove_dev = inftl_remove_dev,
9371da177e4SLinus Torvalds .owner = THIS_MODULE,
9381da177e4SLinus Torvalds };
9391da177e4SLinus Torvalds
940f7e39bb7SDejin Zheng module_mtd_blktrans(inftl_tr);
9411da177e4SLinus Torvalds
9421da177e4SLinus Torvalds MODULE_LICENSE("GPL");
9431da177e4SLinus Torvalds MODULE_AUTHOR("Greg Ungerer <gerg@snapgear.com>, David Woodhouse <dwmw2@infradead.org>, Fabrice Bellard <fabrice.bellard@netgem.com> et al.");
9441da177e4SLinus Torvalds MODULE_DESCRIPTION("Support code for Inverse Flash Translation Layer, used on M-Systems DiskOnChip 2000, Millennium and Millennium Plus");
945