nftlcore.c 23.2 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1
/*
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 * Linux driver for NAND Flash Translation Layer
 *
 * Copyright © 1999 Machine Vision Holdings, Inc.
 * Copyright © 1999-2010 David Woodhouse <dwmw2@infradead.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
Linus Torvalds's avatar
Linus Torvalds committed
20 21 22 23 24 25 26 27 28 29 30 31 32
 */

#define PRERELEASE

#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/errno.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/hdreg.h>
33
#include <linux/blkdev.h>
Linus Torvalds's avatar
Linus Torvalds committed
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52

#include <linux/kmod.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/nftl.h>
#include <linux/mtd/blktrans.h>

/* maximum number of loops while examining next block, to have a
   chance to detect consistency problems (they should never happen
   because of the checks done in the mounting */

#define MAX_LOOPS 10000


static void nftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
{
	struct NFTLrecord *nftl;
	unsigned long temp;

53
	if (mtd->type != MTD_NANDFLASH || mtd->size > UINT_MAX)
Linus Torvalds's avatar
Linus Torvalds committed
54 55 56 57 58 59 60 61 62 63 64 65
		return;
	/* OK, this is moderately ugly.  But probably safe.  Alternatives? */
	if (memcmp(mtd->name, "DiskOnChip", 10))
		return;

	if (!mtd->block_isbad) {
		printk(KERN_ERR
"NFTL no longer supports the old DiskOnChip drivers loaded via docprobe.\n"
"Please use the new diskonchip driver under the NAND subsystem.\n");
		return;
	}

66
	pr_debug("NFTL: add_mtd for %s\n", mtd->name);
Linus Torvalds's avatar
Linus Torvalds committed
67

68
	nftl = kzalloc(sizeof(struct NFTLrecord), GFP_KERNEL);
Linus Torvalds's avatar
Linus Torvalds committed
69

70
	if (!nftl)
Linus Torvalds's avatar
Linus Torvalds committed
71 72 73 74
		return;

	nftl->mbd.mtd = mtd;
	nftl->mbd.devnum = -1;
75

Linus Torvalds's avatar
Linus Torvalds committed
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
	nftl->mbd.tr = tr;

        if (NFTL_mount(nftl) < 0) {
		printk(KERN_WARNING "NFTL: could not mount device\n");
		kfree(nftl);
		return;
        }

	/* OK, it's a new one. Set up all the data structures. */

	/* Calculate geometry */
	nftl->cylinders = 1024;
	nftl->heads = 16;

	temp = nftl->cylinders * nftl->heads;
	nftl->sectors = nftl->mbd.size / temp;
	if (nftl->mbd.size % temp) {
		nftl->sectors++;
		temp = nftl->cylinders * nftl->sectors;
		nftl->heads = nftl->mbd.size / temp;

		if (nftl->mbd.size % temp) {
			nftl->heads++;
			temp = nftl->heads * nftl->sectors;
			nftl->cylinders = nftl->mbd.size / temp;
		}
	}

	if (nftl->mbd.size != nftl->heads * nftl->cylinders * nftl->sectors) {
		/*
106
		  Oh no we don't have
Linus Torvalds's avatar
Linus Torvalds committed
107 108 109 110 111 112
		   mbd.size == heads * cylinders * sectors
		*/
		printk(KERN_WARNING "NFTL: cannot calculate a geometry to "
		       "match size of 0x%lx.\n", nftl->mbd.size);
		printk(KERN_WARNING "NFTL: using C:%d H:%d S:%d "
			"(== 0x%lx sects)\n",
113
			nftl->cylinders, nftl->heads , nftl->sectors,
Linus Torvalds's avatar
Linus Torvalds committed
114 115 116 117 118
			(long)nftl->cylinders * (long)nftl->heads *
			(long)nftl->sectors );
	}

	if (add_mtd_blktrans_dev(&nftl->mbd)) {
119 120
		kfree(nftl->ReplUnitTable);
		kfree(nftl->EUNtable);
Linus Torvalds's avatar
Linus Torvalds committed
121 122 123 124 125 126 127 128 129 130 131 132
		kfree(nftl);
		return;
	}
#ifdef PSYCHO_DEBUG
	printk(KERN_INFO "NFTL: Found new nftl%c\n", nftl->mbd.devnum + 'a');
#endif
}

static void nftl_remove_dev(struct mtd_blktrans_dev *dev)
{
	struct NFTLrecord *nftl = (void *)dev;

133
	pr_debug("NFTL: remove_dev (i=%d)\n", dev->devnum);
Linus Torvalds's avatar
Linus Torvalds committed
134 135

	del_mtd_blktrans_dev(dev);
136 137
	kfree(nftl->ReplUnitTable);
	kfree(nftl->EUNtable);
Linus Torvalds's avatar
Linus Torvalds committed
138 139
}

140 141 142 143 144 145
/*
 * Read oob data from flash
 */
int nftl_read_oob(struct mtd_info *mtd, loff_t offs, size_t len,
		  size_t *retlen, uint8_t *buf)
{
146
	loff_t mask = mtd->writesize - 1;
147 148 149
	struct mtd_oob_ops ops;
	int res;

150
	ops.mode = MTD_OPS_PLACE_OOB;
151
	ops.ooboffs = offs & mask;
152 153 154 155
	ops.ooblen = len;
	ops.oobbuf = buf;
	ops.datbuf = NULL;

156
	res = mtd_read_oob(mtd, offs & ~mask, &ops);
157
	*retlen = ops.oobretlen;
158 159 160 161 162 163 164 165 166
	return res;
}

/*
 * Write oob data to flash
 */
int nftl_write_oob(struct mtd_info *mtd, loff_t offs, size_t len,
		   size_t *retlen, uint8_t *buf)
{
167
	loff_t mask = mtd->writesize - 1;
168 169 170
	struct mtd_oob_ops ops;
	int res;

171
	ops.mode = MTD_OPS_PLACE_OOB;
172
	ops.ooboffs = offs & mask;
173 174 175 176
	ops.ooblen = len;
	ops.oobbuf = buf;
	ops.datbuf = NULL;

177
	res = mtd->write_oob(mtd, offs & ~mask, &ops);
178
	*retlen = ops.oobretlen;
179 180 181
	return res;
}

182 183
#ifdef CONFIG_NFTL_RW

184 185 186 187 188 189
/*
 * Write data and oob to flash
 */
static int nftl_write(struct mtd_info *mtd, loff_t offs, size_t len,
		      size_t *retlen, uint8_t *buf, uint8_t *oob)
{
190
	loff_t mask = mtd->writesize - 1;
191 192 193
	struct mtd_oob_ops ops;
	int res;

194
	ops.mode = MTD_OPS_PLACE_OOB;
195
	ops.ooboffs = offs & mask;
196 197 198 199 200
	ops.ooblen = mtd->oobsize;
	ops.oobbuf = oob;
	ops.datbuf = buf;
	ops.len = len;

201
	res = mtd->write_oob(mtd, offs & ~mask, &ops);
202 203 204 205
	*retlen = ops.retlen;
	return res;
}

Linus Torvalds's avatar
Linus Torvalds committed
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
/* Actual NFTL access routines */
/* NFTL_findfreeblock: Find a free Erase Unit on the NFTL partition. This function is used
 *	when the give Virtual Unit Chain
 */
static u16 NFTL_findfreeblock(struct NFTLrecord *nftl, int desperate )
{
	/* For a given Virtual Unit Chain: find or create a free block and
	   add it to the chain */
	/* We're passed the number of the last EUN in the chain, to save us from
	   having to look it up again */
	u16 pot = nftl->LastFreeEUN;
	int silly = nftl->nb_blocks;

	/* Normally, we force a fold to happen before we run out of free blocks completely */
	if (!desperate && nftl->numfreeEUNs < 2) {
221
		pr_debug("NFTL_findfreeblock: there are too few free EUNs\n");
222
		return BLOCK_NIL;
Linus Torvalds's avatar
Linus Torvalds committed
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
	}

	/* Scan for a free block */
	do {
		if (nftl->ReplUnitTable[pot] == BLOCK_FREE) {
			nftl->LastFreeEUN = pot;
			nftl->numfreeEUNs--;
			return pot;
		}

		/* This will probably point to the MediaHdr unit itself,
		   right at the beginning of the partition. But that unit
		   (and the backup unit too) should have the UCI set
		   up so that it's not selected for overwriting */
		if (++pot > nftl->lastEUN)
			pot = le16_to_cpu(nftl->MediaHdr.FirstPhysicalEUN);

		if (!silly--) {
			printk("Argh! No free blocks found! LastFreeEUN = %d, "
242
			       "FirstEUN = %d\n", nftl->LastFreeEUN,
Linus Torvalds's avatar
Linus Torvalds committed
243
			       le16_to_cpu(nftl->MediaHdr.FirstPhysicalEUN));
244
			return BLOCK_NIL;
Linus Torvalds's avatar
Linus Torvalds committed
245 246 247
		}
	} while (pot != nftl->LastFreeEUN);

248
	return BLOCK_NIL;
Linus Torvalds's avatar
Linus Torvalds committed
249 250 251 252
}

static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned pendingblock )
{
253
	struct mtd_info *mtd = nftl->mbd.mtd;
Linus Torvalds's avatar
Linus Torvalds committed
254 255 256 257 258 259 260 261 262
	u16 BlockMap[MAX_SECTORS_PER_UNIT];
	unsigned char BlockLastState[MAX_SECTORS_PER_UNIT];
	unsigned char BlockFreeFound[MAX_SECTORS_PER_UNIT];
	unsigned int thisEUN;
	int block;
	int silly;
	unsigned int targetEUN;
	struct nftl_oob oob;
	int inplace = 1;
263
	size_t retlen;
Linus Torvalds's avatar
Linus Torvalds committed
264 265 266 267 268 269 270 271 272 273 274

	memset(BlockMap, 0xff, sizeof(BlockMap));
	memset(BlockFreeFound, 0, sizeof(BlockFreeFound));

	thisEUN = nftl->EUNtable[thisVUC];

	if (thisEUN == BLOCK_NIL) {
		printk(KERN_WARNING "Trying to fold non-existent "
		       "Virtual Unit Chain %d!\n", thisVUC);
		return BLOCK_NIL;
	}
275

Linus Torvalds's avatar
Linus Torvalds committed
276 277 278
	/* Scan to find the Erase Unit which holds the actual data for each
	   512-byte block within the Chain.
	*/
279
	silly = MAX_LOOPS;
Linus Torvalds's avatar
Linus Torvalds committed
280 281
	targetEUN = BLOCK_NIL;
	while (thisEUN <= nftl->lastEUN ) {
282
		unsigned int status, foldmark;
Linus Torvalds's avatar
Linus Torvalds committed
283 284 285

		targetEUN = thisEUN;
		for (block = 0; block < nftl->EraseSize / 512; block ++) {
286
			nftl_read_oob(mtd, (thisEUN * nftl->EraseSize) +
287 288
				      (block * 512), 16 , &retlen,
				      (char *)&oob);
Linus Torvalds's avatar
Linus Torvalds committed
289
			if (block == 2) {
290 291
				foldmark = oob.u.c.FoldMark | oob.u.c.FoldMark1;
				if (foldmark == FOLD_MARK_IN_PROGRESS) {
292
					pr_debug("Write Inhibited on EUN %d\n", thisEUN);
Linus Torvalds's avatar
Linus Torvalds committed
293 294 295 296 297 298 299 300
					inplace = 0;
				} else {
					/* There's no other reason not to do inplace,
					   except ones that come later. So we don't need
					   to preserve inplace */
					inplace = 1;
				}
			}
301
			status = oob.b.Status | oob.b.Status1;
Linus Torvalds's avatar
Linus Torvalds committed
302 303 304 305 306 307 308 309 310 311 312
			BlockLastState[block] = status;

			switch(status) {
			case SECTOR_FREE:
				BlockFreeFound[block] = 1;
				break;

			case SECTOR_USED:
				if (!BlockFreeFound[block])
					BlockMap[block] = thisEUN;
				else
313
					printk(KERN_WARNING
Linus Torvalds's avatar
Linus Torvalds committed
314 315 316 317 318 319 320 321
					       "SECTOR_USED found after SECTOR_FREE "
					       "in Virtual Unit Chain %d for block %d\n",
					       thisVUC, block);
				break;
			case SECTOR_DELETED:
				if (!BlockFreeFound[block])
					BlockMap[block] = BLOCK_NIL;
				else
322
					printk(KERN_WARNING
Linus Torvalds's avatar
Linus Torvalds committed
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
					       "SECTOR_DELETED found after SECTOR_FREE "
					       "in Virtual Unit Chain %d for block %d\n",
					       thisVUC, block);
				break;

			case SECTOR_IGNORE:
				break;
			default:
				printk("Unknown status for block %d in EUN %d: %x\n",
				       block, thisEUN, status);
			}
		}

		if (!silly--) {
			printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%x\n",
			       thisVUC);
			return BLOCK_NIL;
		}
341

Linus Torvalds's avatar
Linus Torvalds committed
342 343 344 345 346 347
		thisEUN = nftl->ReplUnitTable[thisEUN];
	}

	if (inplace) {
		/* We're being asked to be a fold-in-place. Check
		   that all blocks which actually have data associated
348
		   with them (i.e. BlockMap[block] != BLOCK_NIL) are
Linus Torvalds's avatar
Linus Torvalds committed
349 350 351 352 353 354 355 356
		   either already present or SECTOR_FREE in the target
		   block. If not, we're going to have to fold out-of-place
		   anyway.
		*/
		for (block = 0; block < nftl->EraseSize / 512 ; block++) {
			if (BlockLastState[block] != SECTOR_FREE &&
			    BlockMap[block] != BLOCK_NIL &&
			    BlockMap[block] != targetEUN) {
357
				pr_debug("Setting inplace to 0. VUC %d, "
Linus Torvalds's avatar
Linus Torvalds committed
358 359 360
				      "block %d was %x lastEUN, "
				      "and is in EUN %d (%s) %d\n",
				      thisVUC, block, BlockLastState[block],
361
				      BlockMap[block],
Linus Torvalds's avatar
Linus Torvalds committed
362 363 364 365 366 367 368 369 370 371 372
				      BlockMap[block]== targetEUN ? "==" : "!=",
				      targetEUN);
				inplace = 0;
				break;
			}
		}

		if (pendingblock >= (thisVUC * (nftl->EraseSize / 512)) &&
		    pendingblock < ((thisVUC + 1)* (nftl->EraseSize / 512)) &&
		    BlockLastState[pendingblock - (thisVUC * (nftl->EraseSize / 512))] !=
		    SECTOR_FREE) {
373
			pr_debug("Pending write not free in EUN %d. "
Linus Torvalds's avatar
Linus Torvalds committed
374 375 376 377
			      "Folding out of place.\n", targetEUN);
			inplace = 0;
		}
	}
378

Linus Torvalds's avatar
Linus Torvalds committed
379
	if (!inplace) {
380
		pr_debug("Cannot fold Virtual Unit Chain %d in place. "
Linus Torvalds's avatar
Linus Torvalds committed
381 382 383 384
		      "Trying out-of-place\n", thisVUC);
		/* We need to find a targetEUN to fold into. */
		targetEUN = NFTL_findfreeblock(nftl, 1);
		if (targetEUN == BLOCK_NIL) {
385
			/* Ouch. Now we're screwed. We need to do a
Linus Torvalds's avatar
Linus Torvalds committed
386 387
			   fold-in-place of another chain to make room
			   for this one. We need a better way of selecting
388
			   which chain to fold, because makefreeblock will
Linus Torvalds's avatar
Linus Torvalds committed
389 390 391 392 393 394 395
			   only ask us to fold the same one again.
			*/
			printk(KERN_WARNING
			       "NFTL_findfreeblock(desperate) returns 0xffff.\n");
			return BLOCK_NIL;
		}
	} else {
396 397 398 399 400 401
		/* We put a fold mark in the chain we are folding only if we
               fold in place to help the mount check code. If we do not fold in
               place, it is possible to find the valid chain by selecting the
               longer one */
		oob.u.c.FoldMark = oob.u.c.FoldMark1 = cpu_to_le16(FOLD_MARK_IN_PROGRESS);
		oob.u.c.unused = 0xffffffff;
402
		nftl_write_oob(mtd, (nftl->EraseSize * targetEUN) + 2 * 512 + 8,
403 404
			       8, &retlen, (char *)&oob.u);
	}
Linus Torvalds's avatar
Linus Torvalds committed
405 406 407 408 409

	/* OK. We now know the location of every block in the Virtual Unit Chain,
	   and the Erase Unit into which we are supposed to be copying.
	   Go for it.
	*/
410
	pr_debug("Folding chain %d into unit %d\n", thisVUC, targetEUN);
Linus Torvalds's avatar
Linus Torvalds committed
411 412 413 414 415 416 417 418 419 420
	for (block = 0; block < nftl->EraseSize / 512 ; block++) {
		unsigned char movebuf[512];
		int ret;

		/* If it's in the target EUN already, or if it's pending write, do nothing */
		if (BlockMap[block] == targetEUN ||
		    (pendingblock == (thisVUC * (nftl->EraseSize / 512) + block))) {
			continue;
		}

421
		/* copy only in non free block (free blocks can only
Linus Torvalds's avatar
Linus Torvalds committed
422
                   happen in case of media errors or deleted blocks) */
423 424 425
		if (BlockMap[block] == BLOCK_NIL)
			continue;

426 427 428 429 430
		ret = mtd_read(mtd,
			       (nftl->EraseSize * BlockMap[block]) + (block * 512),
			       512,
			       &retlen,
			       movebuf);
431
		if (ret < 0 && !mtd_is_bitflip(ret)) {
432 433 434 435 436
			ret = mtd_read(mtd,
				       (nftl->EraseSize * BlockMap[block]) + (block * 512),
				       512,
				       &retlen,
				       movebuf);
437 438 439
			if (ret != -EIO)
				printk("Error went away on retry.\n");
		}
Linus Torvalds's avatar
Linus Torvalds committed
440 441
		memset(&oob, 0xff, sizeof(struct nftl_oob));
		oob.b.Status = oob.b.Status1 = SECTOR_USED;
442

443 444
		nftl_write(nftl->mbd.mtd, (nftl->EraseSize * targetEUN) +
			   (block * 512), 512, &retlen, movebuf, (char *)&oob);
Linus Torvalds's avatar
Linus Torvalds committed
445
	}
446

447 448
	/* add the header so that it is now a valid chain */
	oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC);
449
	oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum = BLOCK_NIL;
450

451
	nftl_write_oob(mtd, (nftl->EraseSize * targetEUN) + 8,
452
		       8, &retlen, (char *)&oob.u);
Linus Torvalds's avatar
Linus Torvalds committed
453 454 455

	/* OK. We've moved the whole lot into the new block. Now we have to free the original blocks. */

456
	/* At this point, we have two different chains for this Virtual Unit, and no way to tell
Linus Torvalds's avatar
Linus Torvalds committed
457 458 459 460 461
	   them apart. If we crash now, we get confused. However, both contain the same data, so we
	   shouldn't actually lose data in this case. It's just that when we load up on a medium which
	   has duplicate chains, we need to free one of the chains because it's not necessary any more.
	*/
	thisEUN = nftl->EUNtable[thisVUC];
462
	pr_debug("Want to erase\n");
Linus Torvalds's avatar
Linus Torvalds committed
463

464
	/* For each block in the old chain (except the targetEUN of course),
Linus Torvalds's avatar
Linus Torvalds committed
465 466 467 468
	   free it and make it available for future use */
	while (thisEUN <= nftl->lastEUN && thisEUN != targetEUN) {
		unsigned int EUNtmp;

469
		EUNtmp = nftl->ReplUnitTable[thisEUN];
Linus Torvalds's avatar
Linus Torvalds committed
470

471
		if (NFTL_formatblock(nftl, thisEUN) < 0) {
Linus Torvalds's avatar
Linus Torvalds committed
472 473 474
			/* could not erase : mark block as reserved
			 */
			nftl->ReplUnitTable[thisEUN] = BLOCK_RESERVED;
475
		} else {
Linus Torvalds's avatar
Linus Torvalds committed
476 477 478
			/* correctly erased : mark it as free */
			nftl->ReplUnitTable[thisEUN] = BLOCK_FREE;
			nftl->numfreeEUNs++;
479 480
		}
		thisEUN = EUNtmp;
Linus Torvalds's avatar
Linus Torvalds committed
481
	}
482

Linus Torvalds's avatar
Linus Torvalds committed
483 484 485 486 487 488 489 490 491
	/* Make this the new start of chain for thisVUC */
	nftl->ReplUnitTable[targetEUN] = BLOCK_NIL;
	nftl->EUNtable[thisVUC] = targetEUN;

	return targetEUN;
}

static u16 NFTL_makefreeblock( struct NFTLrecord *nftl , unsigned pendingblock)
{
492
	/* This is the part that needs some cleverness applied.
Linus Torvalds's avatar
Linus Torvalds committed
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532
	   For now, I'm doing the minimum applicable to actually
	   get the thing to work.
	   Wear-levelling and other clever stuff needs to be implemented
	   and we also need to do some assessment of the results when
	   the system loses power half-way through the routine.
	*/
	u16 LongestChain = 0;
	u16 ChainLength = 0, thislen;
	u16 chain, EUN;

	for (chain = 0; chain < le32_to_cpu(nftl->MediaHdr.FormattedSize) / nftl->EraseSize; chain++) {
		EUN = nftl->EUNtable[chain];
		thislen = 0;

		while (EUN <= nftl->lastEUN) {
			thislen++;
			//printk("VUC %d reaches len %d with EUN %d\n", chain, thislen, EUN);
			EUN = nftl->ReplUnitTable[EUN] & 0x7fff;
			if (thislen > 0xff00) {
				printk("Endless loop in Virtual Chain %d: Unit %x\n",
				       chain, EUN);
			}
			if (thislen > 0xff10) {
				/* Actually, don't return failure. Just ignore this chain and
				   get on with it. */
				thislen = 0;
				break;
			}
		}

		if (thislen > ChainLength) {
			//printk("New longest chain is %d with length %d\n", chain, thislen);
			ChainLength = thislen;
			LongestChain = chain;
		}
	}

	if (ChainLength < 2) {
		printk(KERN_WARNING "No Virtual Unit Chains available for folding. "
		       "Failing request\n");
533
		return BLOCK_NIL;
Linus Torvalds's avatar
Linus Torvalds committed
534 535 536 537 538
	}

	return NFTL_foldchain (nftl, LongestChain, pendingblock);
}

539
/* NFTL_findwriteunit: Return the unit number into which we can write
Linus Torvalds's avatar
Linus Torvalds committed
540 541 542 543 544 545
                       for this block. Make it available if it isn't already
*/
static inline u16 NFTL_findwriteunit(struct NFTLrecord *nftl, unsigned block)
{
	u16 lastEUN;
	u16 thisVUC = block / (nftl->EraseSize / 512);
546
	struct mtd_info *mtd = nftl->mbd.mtd;
Linus Torvalds's avatar
Linus Torvalds committed
547 548 549 550 551 552 553 554 555 556 557
	unsigned int writeEUN;
	unsigned long blockofs = (block * 512) & (nftl->EraseSize -1);
	size_t retlen;
	int silly, silly2 = 3;
	struct nftl_oob oob;

	do {
		/* Scan the media to find a unit in the VUC which has
		   a free space for the block in question.
		*/

558
		/* This condition catches the 0x[7f]fff cases, as well as
Linus Torvalds's avatar
Linus Torvalds committed
559 560 561 562
		   being a sanity check for past-end-of-media access
		*/
		lastEUN = BLOCK_NIL;
		writeEUN = nftl->EUNtable[thisVUC];
563
		silly = MAX_LOOPS;
Linus Torvalds's avatar
Linus Torvalds committed
564 565 566
		while (writeEUN <= nftl->lastEUN) {
			struct nftl_bci bci;
			size_t retlen;
567
			unsigned int status;
Linus Torvalds's avatar
Linus Torvalds committed
568 569 570

			lastEUN = writeEUN;

571
			nftl_read_oob(mtd,
572 573
				      (writeEUN * nftl->EraseSize) + blockofs,
				      8, &retlen, (char *)&bci);
574

575
			pr_debug("Status of block %d in EUN %d is %x\n",
Linus Torvalds's avatar
Linus Torvalds committed
576 577
			      block , writeEUN, le16_to_cpu(bci.Status));

578
			status = bci.Status | bci.Status1;
Linus Torvalds's avatar
Linus Torvalds committed
579 580 581 582 583 584 585 586 587 588
			switch(status) {
			case SECTOR_FREE:
				return writeEUN;

			case SECTOR_DELETED:
			case SECTOR_USED:
			case SECTOR_IGNORE:
				break;
			default:
				// Invalid block. Don't use it any more. Must implement.
589
				break;
Linus Torvalds's avatar
Linus Torvalds committed
590
			}
591 592

			if (!silly--) {
Linus Torvalds's avatar
Linus Torvalds committed
593 594 595
				printk(KERN_WARNING
				       "Infinite loop in Virtual Unit Chain 0x%x\n",
				       thisVUC);
596
				return BLOCK_NIL;
Linus Torvalds's avatar
Linus Torvalds committed
597 598 599 600 601 602
			}

			/* Skip to next block in chain */
			writeEUN = nftl->ReplUnitTable[writeEUN];
		}

603
		/* OK. We didn't find one in the existing chain, or there
Linus Torvalds's avatar
Linus Torvalds committed
604 605 606 607 608 609 610 611 612 613 614 615 616
		   is no existing chain. */

		/* Try to find an already-free block */
		writeEUN = NFTL_findfreeblock(nftl, 0);

		if (writeEUN == BLOCK_NIL) {
			/* That didn't work - there were no free blocks just
			   waiting to be picked up. We're going to have to fold
			   a chain to make room.
			*/

			/* First remember the start of this chain */
			//u16 startEUN = nftl->EUNtable[thisVUC];
617

Linus Torvalds's avatar
Linus Torvalds committed
618
			//printk("Write to VirtualUnitChain %d, calling makefreeblock()\n", thisVUC);
619
			writeEUN = NFTL_makefreeblock(nftl, BLOCK_NIL);
Linus Torvalds's avatar
Linus Torvalds committed
620 621

			if (writeEUN == BLOCK_NIL) {
622
				/* OK, we accept that the above comment is
Linus Torvalds's avatar
Linus Torvalds committed
623 624 625 626 627
				   lying - there may have been free blocks
				   last time we called NFTL_findfreeblock(),
				   but they are reserved for when we're
				   desperate. Well, now we're desperate.
				*/
628
				pr_debug("Using desperate==1 to find free EUN to accommodate write to VUC %d\n", thisVUC);
Linus Torvalds's avatar
Linus Torvalds committed
629 630 631 632
				writeEUN = NFTL_findfreeblock(nftl, 1);
			}
			if (writeEUN == BLOCK_NIL) {
				/* Ouch. This should never happen - we should
633 634
				   always be able to make some room somehow.
				   If we get here, we've allocated more storage
Linus Torvalds's avatar
Linus Torvalds committed
635 636 637 638 639
				   space than actual media, or our makefreeblock
				   routine is missing something.
				*/
				printk(KERN_WARNING "Cannot make free space.\n");
				return BLOCK_NIL;
640
			}
Linus Torvalds's avatar
Linus Torvalds committed
641 642 643 644 645 646
			//printk("Restarting scan\n");
			lastEUN = BLOCK_NIL;
			continue;
		}

		/* We've found a free block. Insert it into the chain. */
647

Linus Torvalds's avatar
Linus Torvalds committed
648
		if (lastEUN != BLOCK_NIL) {
649
			thisVUC |= 0x8000; /* It's a replacement block */
Linus Torvalds's avatar
Linus Torvalds committed
650
		} else {
651 652
			/* The first block in a new chain */
			nftl->EUNtable[thisVUC] = writeEUN;
Linus Torvalds's avatar
Linus Torvalds committed
653 654 655 656 657 658 659
		}

		/* set up the actual EUN we're writing into */
		/* Both in our cache... */
		nftl->ReplUnitTable[writeEUN] = BLOCK_NIL;

		/* ... and on the flash itself */
660
		nftl_read_oob(mtd, writeEUN * nftl->EraseSize + 8, 8,
661
			      &retlen, (char *)&oob.u);
Linus Torvalds's avatar
Linus Torvalds committed
662 663 664

		oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC);

665
		nftl_write_oob(mtd, writeEUN * nftl->EraseSize + 8, 8,
666
			       &retlen, (char *)&oob.u);
Linus Torvalds's avatar
Linus Torvalds committed
667

668
		/* we link the new block to the chain only after the
Linus Torvalds's avatar
Linus Torvalds committed
669 670
                   block is ready. It avoids the case where the chain
                   could point to a free block */
671
		if (lastEUN != BLOCK_NIL) {
Linus Torvalds's avatar
Linus Torvalds committed
672 673 674
			/* Both in our cache... */
			nftl->ReplUnitTable[lastEUN] = writeEUN;
			/* ... and on the flash itself */
675
			nftl_read_oob(mtd, (lastEUN * nftl->EraseSize) + 8,
676
				      8, &retlen, (char *)&oob.u);
Linus Torvalds's avatar
Linus Torvalds committed
677 678 679 680

			oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum
				= cpu_to_le16(writeEUN);

681
			nftl_write_oob(mtd, (lastEUN * nftl->EraseSize) + 8,
682
				       8, &retlen, (char *)&oob.u);
Linus Torvalds's avatar
Linus Torvalds committed
683 684 685 686 687 688 689 690
		}

		return writeEUN;

	} while (silly2--);

	printk(KERN_WARNING "Error folding to make room for Virtual Unit Chain 0x%x\n",
	       thisVUC);
691
	return BLOCK_NIL;
Linus Torvalds's avatar
Linus Torvalds committed
692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714
}

static int nftl_writeblock(struct mtd_blktrans_dev *mbd, unsigned long block,
			   char *buffer)
{
	struct NFTLrecord *nftl = (void *)mbd;
	u16 writeEUN;
	unsigned long blockofs = (block * 512) & (nftl->EraseSize - 1);
	size_t retlen;
	struct nftl_oob oob;

	writeEUN = NFTL_findwriteunit(nftl, block);

	if (writeEUN == BLOCK_NIL) {
		printk(KERN_WARNING
		       "NFTL_writeblock(): Cannot find block to write to\n");
		/* If we _still_ haven't got a block to use, we're screwed */
		return 1;
	}

	memset(&oob, 0xff, sizeof(struct nftl_oob));
	oob.b.Status = oob.b.Status1 = SECTOR_USED;

715 716
	nftl_write(nftl->mbd.mtd, (writeEUN * nftl->EraseSize) + blockofs,
		   512, &retlen, (char *)buffer, (char *)&oob);
Linus Torvalds's avatar
Linus Torvalds committed
717 718 719 720 721 722 723 724
	return 0;
}
#endif /* CONFIG_NFTL_RW */

static int nftl_readblock(struct mtd_blktrans_dev *mbd, unsigned long block,
			  char *buffer)
{
	struct NFTLrecord *nftl = (void *)mbd;
725
	struct mtd_info *mtd = nftl->mbd.mtd;
Linus Torvalds's avatar
Linus Torvalds committed
726 727 728
	u16 lastgoodEUN;
	u16 thisEUN = nftl->EUNtable[block / (nftl->EraseSize / 512)];
	unsigned long blockofs = (block * 512) & (nftl->EraseSize - 1);
729
	unsigned int status;
Linus Torvalds's avatar
Linus Torvalds committed
730
	int silly = MAX_LOOPS;
731 732
	size_t retlen;
	struct nftl_bci bci;
Linus Torvalds's avatar
Linus Torvalds committed
733 734 735

	lastgoodEUN = BLOCK_NIL;

736
	if (thisEUN != BLOCK_NIL) {
Linus Torvalds's avatar
Linus Torvalds committed
737
		while (thisEUN < nftl->nb_blocks) {
738
			if (nftl_read_oob(mtd, (thisEUN * nftl->EraseSize) +
739 740
					  blockofs, 8, &retlen,
					  (char *)&bci) < 0)
Linus Torvalds's avatar
Linus Torvalds committed
741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769
				status = SECTOR_IGNORE;
			else
				status = bci.Status | bci.Status1;

			switch (status) {
			case SECTOR_FREE:
				/* no modification of a sector should follow a free sector */
				goto the_end;
			case SECTOR_DELETED:
				lastgoodEUN = BLOCK_NIL;
				break;
			case SECTOR_USED:
				lastgoodEUN = thisEUN;
				break;
			case SECTOR_IGNORE:
				break;
			default:
				printk("Unknown status for block %ld in EUN %d: %x\n",
				       block, thisEUN, status);
				break;
			}

			if (!silly--) {
				printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%lx\n",
				       block / (nftl->EraseSize / 512));
				return 1;
			}
			thisEUN = nftl->ReplUnitTable[thisEUN];
		}
770
	}
Linus Torvalds's avatar
Linus Torvalds committed
771 772 773 774 775 776 777 778

 the_end:
	if (lastgoodEUN == BLOCK_NIL) {
		/* the requested block is not on the media, return all 0x00 */
		memset(buffer, 0, 512);
	} else {
		loff_t ptr = (lastgoodEUN * nftl->EraseSize) + blockofs;
		size_t retlen;
779
		int res = mtd_read(mtd, ptr, 512, &retlen, buffer);
780

781
		if (res < 0 && !mtd_is_bitflip(res))
Linus Torvalds's avatar
Linus Torvalds committed
782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808
			return -EIO;
	}
	return 0;
}

static int nftl_getgeo(struct mtd_blktrans_dev *dev,  struct hd_geometry *geo)
{
	struct NFTLrecord *nftl = (void *)dev;

	geo->heads = nftl->heads;
	geo->sectors = nftl->sectors;
	geo->cylinders = nftl->cylinders;

	return 0;
}

/****************************************************************************
 *
 * Module stuff
 *
 ****************************************************************************/


static struct mtd_blktrans_ops nftl_tr = {
	.name		= "nftl",
	.major		= NFTL_MAJOR,
	.part_bits	= NFTL_PARTN_BITS,
809
	.blksize 	= 512,
Linus Torvalds's avatar
Linus Torvalds committed
810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835
	.getgeo		= nftl_getgeo,
	.readsect	= nftl_readblock,
#ifdef CONFIG_NFTL_RW
	.writesect	= nftl_writeblock,
#endif
	.add_mtd	= nftl_add_mtd,
	.remove_dev	= nftl_remove_dev,
	.owner		= THIS_MODULE,
};

static int __init init_nftl(void)
{
	return register_mtd_blktrans(&nftl_tr);
}

static void __exit cleanup_nftl(void)
{
	deregister_mtd_blktrans(&nftl_tr);
}

module_init(init_nftl);
module_exit(cleanup_nftl);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>, Fabrice Bellard <fabrice.bellard@netgem.com> et al.");
MODULE_DESCRIPTION("Support code for NAND Flash Translation Layer, used on M-Systems DiskOnChip 2000 and Millennium");
836
MODULE_ALIAS_BLOCKDEV_MAJOR(NFTL_MAJOR);