xref: /openbmc/linux/drivers/mtd/inftlcore.c (revision 7ae9fb1b7ecbb5d85d07857943f677fd1a559b18)
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