inftlmount.c 22.5 KB
Newer Older
1
/*
Linus Torvalds's avatar
Linus Torvalds committed
2 3 4
 * inftlmount.c -- INFTL mount code with extensive checks.
 *
 * Author: Greg Ungerer (gerg@snapgear.com)
5
 * Copyright © 2002-2003, Greg Ungerer (gerg@snapgear.com)
Linus Torvalds's avatar
Linus Torvalds committed
6 7
 *
 * Based heavily on the nftlmount.c code which is:
8
 * Author: Fabrice Bellard (fabrice.bellard@netgem.com)
9
 * Copyright © 2000 Netgem S.A.
Linus Torvalds's avatar
Linus Torvalds committed
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
 *
 * 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
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/errno.h>
#include <asm/io.h>
30
#include <linux/uaccess.h>
Linus Torvalds's avatar
Linus Torvalds committed
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nftl.h>
#include <linux/mtd/inftl.h>

/*
 * find_boot_record: Find the INFTL Media Header and its Spare copy which
 *	contains the various device information of the INFTL partition and
 *	Bad Unit Table. Update the PUtable[] table according to the Bad
 *	Unit Table. PUtable[] is used for management of Erase Unit in
 *	other routines in inftlcore.c and inftlmount.c.
 */
static int find_boot_record(struct INFTLrecord *inftl)
{
	struct inftl_unittail h1;
	//struct inftl_oob oob;
	unsigned int i, block;
	u8 buf[SECTORSIZE];
	struct INFTLMediaHeader *mh = &inftl->MediaHdr;
51
	struct mtd_info *mtd = inftl->mbd.mtd;
Linus Torvalds's avatar
Linus Torvalds committed
52 53 54
	struct INFTLPartition *ip;
	size_t retlen;

55
	pr_debug("INFTL: find_boot_record(inftl=%p)\n", inftl);
Linus Torvalds's avatar
Linus Torvalds committed
56 57 58 59 60 61 62

        /*
	 * Assume logical EraseSize == physical erasesize for starting the
	 * scan. We'll sort it out later if we find a MediaHeader which says
	 * otherwise.
	 */
	inftl->EraseSize = inftl->mbd.mtd->erasesize;
63
        inftl->nb_blocks = (u32)inftl->mbd.mtd->size / inftl->EraseSize;
Linus Torvalds's avatar
Linus Torvalds committed
64 65 66 67 68 69 70 71 72 73 74

	inftl->MediaUnit = BLOCK_NIL;

	/* Search for a valid boot record */
	for (block = 0; block < inftl->nb_blocks; block++) {
		int ret;

		/*
		 * Check for BNAND header first. Then whinge if it's found
		 * but later checks fail.
		 */
75 76
		ret = mtd_read(mtd, block * inftl->EraseSize, SECTORSIZE,
			       &retlen, buf);
Linus Torvalds's avatar
Linus Torvalds committed
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
		/* We ignore ret in case the ECC of the MediaHeader is invalid
		   (which is apparently acceptable) */
		if (retlen != SECTORSIZE) {
			static int warncount = 5;

			if (warncount) {
				printk(KERN_WARNING "INFTL: block read at 0x%x "
					"of mtd%d failed: %d\n",
					block * inftl->EraseSize,
					inftl->mbd.mtd->index, ret);
				if (!--warncount)
					printk(KERN_WARNING "INFTL: further "
						"failures for this block will "
						"not be printed\n");
			}
			continue;
		}

		if (retlen < 6 || memcmp(buf, "BNAND", 6)) {
			/* BNAND\0 not found. Continue */
			continue;
		}

		/* To be safer with BIOS, also use erase mark as discriminant */
101 102 103 104
		ret = inftl_read_oob(mtd,
				     block * inftl->EraseSize + SECTORSIZE + 8,
				     8, &retlen,(char *)&h1);
		if (ret < 0) {
Linus Torvalds's avatar
Linus Torvalds committed
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
			printk(KERN_WARNING "INFTL: ANAND header found at "
				"0x%x in mtd%d, but OOB data read failed "
				"(err %d)\n", block * inftl->EraseSize,
				inftl->mbd.mtd->index, ret);
			continue;
		}


		/*
		 * This is the first we've seen.
		 * Copy the media header structure into place.
		 */
		memcpy(mh, buf, sizeof(struct INFTLMediaHeader));

		/* Read the spare media header at offset 4096 */
120 121
		mtd_read(mtd, block * inftl->EraseSize + 4096, SECTORSIZE,
			 &retlen, buf);
Linus Torvalds's avatar
Linus Torvalds committed
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
		if (retlen != SECTORSIZE) {
			printk(KERN_WARNING "INFTL: Unable to read spare "
			       "Media Header\n");
			return -1;
		}
		/* Check if this one is the same as the first one we found. */
		if (memcmp(mh, buf, sizeof(struct INFTLMediaHeader))) {
			printk(KERN_WARNING "INFTL: Primary and spare Media "
			       "Headers disagree.\n");
			return -1;
		}

		mh->NoOfBootImageBlocks = le32_to_cpu(mh->NoOfBootImageBlocks);
		mh->NoOfBinaryPartitions = le32_to_cpu(mh->NoOfBinaryPartitions);
		mh->NoOfBDTLPartitions = le32_to_cpu(mh->NoOfBDTLPartitions);
		mh->BlockMultiplierBits = le32_to_cpu(mh->BlockMultiplierBits);
		mh->FormatFlags = le32_to_cpu(mh->FormatFlags);
		mh->PercentUsed = le32_to_cpu(mh->PercentUsed);

141 142 143 144 145 146 147 148 149 150 151 152 153 154
		pr_debug("INFTL: Media Header ->\n"
			 "    bootRecordID          = %s\n"
			 "    NoOfBootImageBlocks   = %d\n"
			 "    NoOfBinaryPartitions  = %d\n"
			 "    NoOfBDTLPartitions    = %d\n"
			 "    BlockMultiplerBits    = %d\n"
			 "    FormatFlgs            = %d\n"
			 "    OsakVersion           = 0x%x\n"
			 "    PercentUsed           = %d\n",
			 mh->bootRecordID, mh->NoOfBootImageBlocks,
			 mh->NoOfBinaryPartitions,
			 mh->NoOfBDTLPartitions,
			 mh->BlockMultiplierBits, mh->FormatFlags,
			 mh->OsakVersion, mh->PercentUsed);
Linus Torvalds's avatar
Linus Torvalds committed
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183

		if (mh->NoOfBDTLPartitions == 0) {
			printk(KERN_WARNING "INFTL: Media Header sanity check "
				"failed: NoOfBDTLPartitions (%d) == 0, "
				"must be at least 1\n", mh->NoOfBDTLPartitions);
			return -1;
		}

		if ((mh->NoOfBDTLPartitions + mh->NoOfBinaryPartitions) > 4) {
			printk(KERN_WARNING "INFTL: Media Header sanity check "
				"failed: Total Partitions (%d) > 4, "
				"BDTL=%d Binary=%d\n", mh->NoOfBDTLPartitions +
				mh->NoOfBinaryPartitions,
				mh->NoOfBDTLPartitions,
				mh->NoOfBinaryPartitions);
			return -1;
		}

		if (mh->BlockMultiplierBits > 1) {
			printk(KERN_WARNING "INFTL: sorry, we don't support "
				"UnitSizeFactor 0x%02x\n",
				mh->BlockMultiplierBits);
			return -1;
		} else if (mh->BlockMultiplierBits == 1) {
			printk(KERN_WARNING "INFTL: support for INFTL with "
				"UnitSizeFactor 0x%02x is experimental\n",
				mh->BlockMultiplierBits);
			inftl->EraseSize = inftl->mbd.mtd->erasesize <<
				mh->BlockMultiplierBits;
184
			inftl->nb_blocks = (u32)inftl->mbd.mtd->size / inftl->EraseSize;
Linus Torvalds's avatar
Linus Torvalds committed
185 186 187 188 189 190 191 192 193 194 195 196 197
			block >>= mh->BlockMultiplierBits;
		}

		/* Scan the partitions */
		for (i = 0; (i < 4); i++) {
			ip = &mh->Partitions[i];
			ip->virtualUnits = le32_to_cpu(ip->virtualUnits);
			ip->firstUnit = le32_to_cpu(ip->firstUnit);
			ip->lastUnit = le32_to_cpu(ip->lastUnit);
			ip->flags = le32_to_cpu(ip->flags);
			ip->spareUnits = le32_to_cpu(ip->spareUnits);
			ip->Reserved0 = le32_to_cpu(ip->Reserved0);

198 199 200 201 202 203 204 205 206
			pr_debug("    PARTITION[%d] ->\n"
				 "        virtualUnits    = %d\n"
				 "        firstUnit       = %d\n"
				 "        lastUnit        = %d\n"
				 "        flags           = 0x%x\n"
				 "        spareUnits      = %d\n",
				 i, ip->virtualUnits, ip->firstUnit,
				 ip->lastUnit, ip->flags,
				 ip->spareUnits);
Linus Torvalds's avatar
Linus Torvalds committed
207 208 209 210 211 212 213 214 215 216 217 218 219

			if (ip->Reserved0 != ip->firstUnit) {
				struct erase_info *instr = &inftl->instr;

				/*
				 * 	Most likely this is using the
				 * 	undocumented qiuck mount feature.
				 * 	We don't support that, we will need
				 * 	to erase the hidden block for full
				 * 	compatibility.
				 */
				instr->addr = ip->Reserved0 * inftl->EraseSize;
				instr->len = inftl->EraseSize;
220
				mtd_erase(mtd, instr);
Linus Torvalds's avatar
Linus Torvalds committed
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
			}
			if ((ip->lastUnit - ip->firstUnit + 1) < ip->virtualUnits) {
				printk(KERN_WARNING "INFTL: Media Header "
					"Partition %d sanity check failed\n"
					"    firstUnit %d : lastUnit %d  >  "
					"virtualUnits %d\n", i, ip->lastUnit,
					ip->firstUnit, ip->Reserved0);
				return -1;
			}
			if (ip->Reserved1 != 0) {
				printk(KERN_WARNING "INFTL: Media Header "
					"Partition %d sanity check failed: "
					"Reserved1 %d != 0\n",
					i, ip->Reserved1);
				return -1;
			}

			if (ip->flags & INFTL_BDTL)
				break;
		}

		if (i >= 4) {
			printk(KERN_WARNING "INFTL: Media Header Partition "
				"sanity check failed:\n       No partition "
				"marked as Disk Partition\n");
			return -1;
		}

		inftl->nb_boot_blocks = ip->firstUnit;
		inftl->numvunits = ip->virtualUnits;
		if (inftl->numvunits > (inftl->nb_blocks -
		    inftl->nb_boot_blocks - 2)) {
			printk(KERN_WARNING "INFTL: Media Header sanity check "
				"failed:\n        numvunits (%d) > nb_blocks "
				"(%d) - nb_boot_blocks(%d) - 2\n",
				inftl->numvunits, inftl->nb_blocks,
				inftl->nb_boot_blocks);
			return -1;
		}
260

Linus Torvalds's avatar
Linus Torvalds committed
261 262 263 264 265 266 267 268 269 270 271 272
		inftl->mbd.size  = inftl->numvunits *
			(inftl->EraseSize / SECTORSIZE);

		/*
		 * Block count is set to last used EUN (we won't need to keep
		 * any meta-data past that point).
		 */
		inftl->firstEUN = ip->firstUnit;
		inftl->lastEUN = ip->lastUnit;
		inftl->nb_blocks = ip->lastUnit + 1;

		/* Memory alloc */
273 274
		inftl->PUtable = kmalloc_array(inftl->nb_blocks, sizeof(u16),
					       GFP_KERNEL);
Linus Torvalds's avatar
Linus Torvalds committed
275 276 277 278 279 280 281
		if (!inftl->PUtable) {
			printk(KERN_WARNING "INFTL: allocation of PUtable "
				"failed (%zd bytes)\n",
				inftl->nb_blocks * sizeof(u16));
			return -ENOMEM;
		}

282 283
		inftl->VUtable = kmalloc_array(inftl->nb_blocks, sizeof(u16),
					       GFP_KERNEL);
Linus Torvalds's avatar
Linus Torvalds committed
284 285 286 287 288 289 290
		if (!inftl->VUtable) {
			kfree(inftl->PUtable);
			printk(KERN_WARNING "INFTL: allocation of VUtable "
				"failed (%zd bytes)\n",
				inftl->nb_blocks * sizeof(u16));
			return -ENOMEM;
		}
291

Linus Torvalds's avatar
Linus Torvalds committed
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
		/* Mark the blocks before INFTL MediaHeader as reserved */
		for (i = 0; i < inftl->nb_boot_blocks; i++)
			inftl->PUtable[i] = BLOCK_RESERVED;
		/* Mark all remaining blocks as potentially containing data */
		for (; i < inftl->nb_blocks; i++)
			inftl->PUtable[i] = BLOCK_NOTEXPLORED;

		/* Mark this boot record (NFTL MediaHeader) block as reserved */
		inftl->PUtable[block] = BLOCK_RESERVED;

		/* Read Bad Erase Unit Table and modify PUtable[] accordingly */
		for (i = 0; i < inftl->nb_blocks; i++) {
			int physblock;
			/* If any of the physical eraseblocks are bad, don't
			   use the unit. */
			for (physblock = 0; physblock < inftl->EraseSize; physblock += inftl->mbd.mtd->erasesize) {
308 309
				if (mtd_block_isbad(inftl->mbd.mtd,
						    i * inftl->EraseSize + physblock))
Linus Torvalds's avatar
Linus Torvalds committed
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
					inftl->PUtable[i] = BLOCK_RESERVED;
			}
		}

		inftl->MediaUnit = block;
		return 0;
	}

	/* Not found. */
	return -1;
}

static int memcmpb(void *a, int c, int n)
{
	int i;
	for (i = 0; i < n; i++) {
		if (c != ((unsigned char *)a)[i])
			return 1;
	}
	return 0;
}

/*
 * check_free_sector: check if a free sector is actually FREE,
 *	i.e. All 0xff in data and oob area.
 */
static int check_free_sectors(struct INFTLrecord *inftl, unsigned int address,
	int len, int check_oob)
{
339
	struct mtd_info *mtd = inftl->mbd.mtd;
Linus Torvalds's avatar
Linus Torvalds committed
340
	size_t retlen;
Kees Cook's avatar
Kees Cook committed
341 342 343 344 345 346
	int i, ret;
	u8 *buf;

	buf = kmalloc(SECTORSIZE + mtd->oobsize, GFP_KERNEL);
	if (!buf)
		return -1;
Linus Torvalds's avatar
Linus Torvalds committed
347

Kees Cook's avatar
Kees Cook committed
348
	ret = -1;
Linus Torvalds's avatar
Linus Torvalds committed
349
	for (i = 0; i < len; i += SECTORSIZE) {
350
		if (mtd_read(mtd, address, SECTORSIZE, &retlen, buf))
Kees Cook's avatar
Kees Cook committed
351
			goto out;
Linus Torvalds's avatar
Linus Torvalds committed
352
		if (memcmpb(buf, 0xff, SECTORSIZE) != 0)
Kees Cook's avatar
Kees Cook committed
353
			goto out;
Linus Torvalds's avatar
Linus Torvalds committed
354 355

		if (check_oob) {
356 357
			if(inftl_read_oob(mtd, address, mtd->oobsize,
					  &retlen, &buf[SECTORSIZE]) < 0)
Kees Cook's avatar
Kees Cook committed
358
				goto out;
359
			if (memcmpb(buf + SECTORSIZE, 0xff, mtd->oobsize) != 0)
Kees Cook's avatar
Kees Cook committed
360
				goto out;
Linus Torvalds's avatar
Linus Torvalds committed
361 362 363 364
		}
		address += SECTORSIZE;
	}

Kees Cook's avatar
Kees Cook committed
365 366 367 368 369
	ret = 0;

out:
	kfree(buf);
	return ret;
Linus Torvalds's avatar
Linus Torvalds committed
370 371 372 373 374 375 376 377 378
}

/*
 * INFTL_format: format a Erase Unit by erasing ALL Erase Zones in the Erase
 *		 Unit and Update INFTL metadata. Each erase operation is
 *		 checked with check_free_sectors.
 *
 * Return: 0 when succeed, -1 on error.
 *
Brian Norris's avatar
Brian Norris committed
379
 * ToDo: 1. Is it necessary to check_free_sector after erasing ??
Linus Torvalds's avatar
Linus Torvalds committed
380 381 382 383 384 385
 */
int INFTL_formatblock(struct INFTLrecord *inftl, int block)
{
	size_t retlen;
	struct inftl_unittail uci;
	struct erase_info *instr = &inftl->instr;
386
	struct mtd_info *mtd = inftl->mbd.mtd;
Linus Torvalds's avatar
Linus Torvalds committed
387 388
	int physblock;

389
	pr_debug("INFTL: INFTL_formatblock(inftl=%p,block=%d)\n", inftl, block);
Linus Torvalds's avatar
Linus Torvalds committed
390 391 392 393 394 395 396 397 398 399 400 401

	memset(instr, 0, sizeof(struct erase_info));

	/* FIXME: Shouldn't we be setting the 'discarded' flag to zero
	   _first_? */

	/* Use async erase interface, test return code */
	instr->addr = block * inftl->EraseSize;
	instr->len = inftl->mbd.mtd->erasesize;
	/* Erase one physical eraseblock at a time, even though the NAND api
	   allows us to group them.  This way we if we have a failure, we can
	   mark only the failed block in the bbt. */
402 403
	for (physblock = 0; physblock < inftl->EraseSize;
	     physblock += instr->len, instr->addr += instr->len) {
404
		int ret;
Linus Torvalds's avatar
Linus Torvalds committed
405

406 407
		ret = mtd_erase(inftl->mbd.mtd, instr);
		if (ret) {
Linus Torvalds's avatar
Linus Torvalds committed
408 409 410 411 412 413
			printk(KERN_WARNING "INFTL: error while formatting block %d\n",
				block);
			goto fail;
		}

		/*
414 415 416 417
		 * Check the "freeness" of Erase Unit before updating metadata.
		 * FixMe: is this check really necessary? Since we have check
		 * the return code after the erase operation.
		 */
Linus Torvalds's avatar
Linus Torvalds committed
418 419 420 421 422 423 424 425 426 427 428
		if (check_free_sectors(inftl, instr->addr, instr->len, 1) != 0)
			goto fail;
	}

	uci.EraseMark = cpu_to_le16(ERASE_MARK);
	uci.EraseMark1 = cpu_to_le16(ERASE_MARK);
	uci.Reserved[0] = 0;
	uci.Reserved[1] = 0;
	uci.Reserved[2] = 0;
	uci.Reserved[3] = 0;
	instr->addr = block * inftl->EraseSize + SECTORSIZE * 2;
429
	if (inftl_write_oob(mtd, instr->addr + 8, 8, &retlen, (char *)&uci) < 0)
Linus Torvalds's avatar
Linus Torvalds committed
430 431 432 433 434
		goto fail;
	return 0;
fail:
	/* could not format, update the bad block table (caller is responsible
	   for setting the PUtable to BLOCK_RESERVED on failure) */
435
	mtd_block_markbad(inftl->mbd.mtd, instr->addr);
Linus Torvalds's avatar
Linus Torvalds committed
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
	return -1;
}

/*
 * format_chain: Format an invalid Virtual Unit chain. It frees all the Erase
 *	Units in a Virtual Unit Chain, i.e. all the units are disconnected.
 *
 *	Since the chain is invalid then we will have to erase it from its
 *	head (normally for INFTL we go from the oldest). But if it has a
 *	loop then there is no oldest...
 */
static void format_chain(struct INFTLrecord *inftl, unsigned int first_block)
{
	unsigned int block = first_block, block1;

	printk(KERN_WARNING "INFTL: formatting chain at block %d\n",
		first_block);

	for (;;) {
		block1 = inftl->PUtable[block];

		printk(KERN_WARNING "INFTL: formatting block %d\n", block);
		if (INFTL_formatblock(inftl, block) < 0) {
			/*
			 * Cannot format !!!! Mark it as Bad Unit,
			 */
			inftl->PUtable[block] = BLOCK_RESERVED;
		} else {
			inftl->PUtable[block] = BLOCK_FREE;
		}

		/* Goto next block on the chain */
		block = block1;

		if (block == BLOCK_NIL || block >= inftl->lastEUN)
			break;
	}
}

void INFTL_dumptables(struct INFTLrecord *s)
{
	int i;

479
	pr_debug("-------------------------------------------"
Linus Torvalds's avatar
Linus Torvalds committed
480 481
		"----------------------------------\n");

482
	pr_debug("VUtable[%d] ->", s->nb_blocks);
Linus Torvalds's avatar
Linus Torvalds committed
483 484
	for (i = 0; i < s->nb_blocks; i++) {
		if ((i % 8) == 0)
485 486
			pr_debug("\n%04x: ", i);
		pr_debug("%04x ", s->VUtable[i]);
Linus Torvalds's avatar
Linus Torvalds committed
487 488
	}

489
	pr_debug("\n-------------------------------------------"
Linus Torvalds's avatar
Linus Torvalds committed
490 491
		"----------------------------------\n");

492
	pr_debug("PUtable[%d-%d=%d] ->", s->firstEUN, s->lastEUN, s->nb_blocks);
Linus Torvalds's avatar
Linus Torvalds committed
493 494
	for (i = 0; i <= s->lastEUN; i++) {
		if ((i % 8) == 0)
495 496
			pr_debug("\n%04x: ", i);
		pr_debug("%04x ", s->PUtable[i]);
Linus Torvalds's avatar
Linus Torvalds committed
497 498
	}

499
	pr_debug("\n-------------------------------------------"
Linus Torvalds's avatar
Linus Torvalds committed
500 501
		"----------------------------------\n");

502
	pr_debug("INFTL ->\n"
Linus Torvalds's avatar
Linus Torvalds committed
503 504 505 506 507 508 509 510 511 512 513 514 515
		"  EraseSize       = %d\n"
		"  h/s/c           = %d/%d/%d\n"
		"  numvunits       = %d\n"
		"  firstEUN        = %d\n"
		"  lastEUN         = %d\n"
		"  numfreeEUNs     = %d\n"
		"  LastFreeEUN     = %d\n"
		"  nb_blocks       = %d\n"
		"  nb_boot_blocks  = %d",
		s->EraseSize, s->heads, s->sectors, s->cylinders,
		s->numvunits, s->firstEUN, s->lastEUN, s->numfreeEUNs,
		s->LastFreeEUN, s->nb_blocks, s->nb_boot_blocks);

516
	pr_debug("\n-------------------------------------------"
Linus Torvalds's avatar
Linus Torvalds committed
517 518 519 520 521 522 523
		"----------------------------------\n");
}

void INFTL_dumpVUchains(struct INFTLrecord *s)
{
	int logical, block, i;

524
	pr_debug("-------------------------------------------"
Linus Torvalds's avatar
Linus Torvalds committed
525 526
		"----------------------------------\n");

527
	pr_debug("INFTL Virtual Unit Chains:\n");
Linus Torvalds's avatar
Linus Torvalds committed
528 529
	for (logical = 0; logical < s->nb_blocks; logical++) {
		block = s->VUtable[logical];
530
		if (block >= s->nb_blocks)
Linus Torvalds's avatar
Linus Torvalds committed
531
			continue;
532
		pr_debug("  LOGICAL %d --> %d ", logical, block);
Linus Torvalds's avatar
Linus Torvalds committed
533 534 535 536
		for (i = 0; i < s->nb_blocks; i++) {
			if (s->PUtable[block] == BLOCK_NIL)
				break;
			block = s->PUtable[block];
537
			pr_debug("%d ", block);
Linus Torvalds's avatar
Linus Torvalds committed
538
		}
539
		pr_debug("\n");
Linus Torvalds's avatar
Linus Torvalds committed
540 541
	}

542
	pr_debug("-------------------------------------------"
Linus Torvalds's avatar
Linus Torvalds committed
543 544 545 546 547
		"----------------------------------\n");
}

int INFTL_mount(struct INFTLrecord *s)
{
548
	struct mtd_info *mtd = s->mbd.mtd;
Linus Torvalds's avatar
Linus Torvalds committed
549 550 551 552 553 554 555 556 557
	unsigned int block, first_block, prev_block, last_block;
	unsigned int first_logical_block, logical_block, erase_mark;
	int chain_length, do_format_chain;
	struct inftl_unithead1 h0;
	struct inftl_unittail h1;
	size_t retlen;
	int i;
	u8 *ANACtable, ANAC;

558
	pr_debug("INFTL: INFTL_mount(inftl=%p)\n", s);
Linus Torvalds's avatar
Linus Torvalds committed
559 560 561 562

	/* Search for INFTL MediaHeader and Spare INFTL Media Header */
	if (find_boot_record(s) < 0) {
		printk(KERN_WARNING "INFTL: could not find valid boot record?\n");
563
		return -ENXIO;
Linus Torvalds's avatar
Linus Torvalds committed
564 565 566 567 568 569 570 571 572
	}

	/* Init the logical to physical table */
	for (i = 0; i < s->nb_blocks; i++)
		s->VUtable[i] = BLOCK_NIL;

	logical_block = block = BLOCK_NIL;

	/* Temporary buffer to store ANAC numbers. */
573
	ANACtable = kcalloc(s->nb_blocks, sizeof(u8), GFP_KERNEL);
574 575 576 577 578 579
	if (!ANACtable) {
		printk(KERN_WARNING "INFTL: allocation of ANACtable "
				"failed (%zd bytes)\n",
				s->nb_blocks * sizeof(u8));
		return -ENOMEM;
	}
Linus Torvalds's avatar
Linus Torvalds committed
580 581 582 583 584 585 586 587

	/*
	 * First pass is to explore each physical unit, and construct the
	 * virtual chains that exist (newest physical unit goes into VUtable).
	 * Any block that is in any way invalid will be left in the
	 * NOTEXPLORED state. Then at the end we will try to format it and
	 * mark it as free.
	 */
588
	pr_debug("INFTL: pass 1, explore each unit\n");
Linus Torvalds's avatar
Linus Torvalds committed
589 590 591 592 593 594 595 596 597 598 599
	for (first_block = s->firstEUN; first_block <= s->lastEUN; first_block++) {
		if (s->PUtable[first_block] != BLOCK_NOTEXPLORED)
			continue;

		do_format_chain = 0;
		first_logical_block = BLOCK_NIL;
		last_block = BLOCK_NIL;
		block = first_block;

		for (chain_length = 0; ; chain_length++) {

600
			if ((chain_length == 0) &&
Linus Torvalds's avatar
Linus Torvalds committed
601 602 603 604 605
			    (s->PUtable[block] != BLOCK_NOTEXPLORED)) {
				/* Nothing to do here, onto next block */
				break;
			}

606 607 608 609 610
			if (inftl_read_oob(mtd, block * s->EraseSize + 8,
					   8, &retlen, (char *)&h0) < 0 ||
			    inftl_read_oob(mtd, block * s->EraseSize +
					   2 * SECTORSIZE + 8, 8, &retlen,
					   (char *)&h1) < 0) {
Linus Torvalds's avatar
Linus Torvalds committed
611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719
				/* Should never happen? */
				do_format_chain++;
				break;
			}

			logical_block = le16_to_cpu(h0.virtualUnitNo);
			prev_block = le16_to_cpu(h0.prevUnitNo);
			erase_mark = le16_to_cpu((h1.EraseMark | h1.EraseMark1));
			ANACtable[block] = h0.ANAC;

			/* Previous block is relative to start of Partition */
			if (prev_block < s->nb_blocks)
				prev_block += s->firstEUN;

			/* Already explored partial chain? */
			if (s->PUtable[block] != BLOCK_NOTEXPLORED) {
				/* Check if chain for this logical */
				if (logical_block == first_logical_block) {
					if (last_block != BLOCK_NIL)
						s->PUtable[last_block] = block;
				}
				break;
			}

			/* Check for invalid block */
			if (erase_mark != ERASE_MARK) {
				printk(KERN_WARNING "INFTL: corrupt block %d "
					"in chain %d, chain length %d, erase "
					"mark 0x%x?\n", block, first_block,
					chain_length, erase_mark);
				/*
				 * Assume end of chain, probably incomplete
				 * fold/erase...
				 */
				if (chain_length == 0)
					do_format_chain++;
				break;
			}

			/* Check for it being free already then... */
			if ((logical_block == BLOCK_FREE) ||
			    (logical_block == BLOCK_NIL)) {
				s->PUtable[block] = BLOCK_FREE;
				break;
			}

			/* Sanity checks on block numbers */
			if ((logical_block >= s->nb_blocks) ||
			    ((prev_block >= s->nb_blocks) &&
			     (prev_block != BLOCK_NIL))) {
				if (chain_length > 0) {
					printk(KERN_WARNING "INFTL: corrupt "
						"block %d in chain %d?\n",
						block, first_block);
					do_format_chain++;
				}
				break;
			}

			if (first_logical_block == BLOCK_NIL) {
				first_logical_block = logical_block;
			} else {
				if (first_logical_block != logical_block) {
					/* Normal for folded chain... */
					break;
				}
			}

			/*
			 * Current block is valid, so if we followed a virtual
			 * chain to get here then we can set the previous
			 * block pointer in our PUtable now. Then move onto
			 * the previous block in the chain.
			 */
			s->PUtable[block] = BLOCK_NIL;
			if (last_block != BLOCK_NIL)
				s->PUtable[last_block] = block;
			last_block = block;
			block = prev_block;

			/* Check for end of chain */
			if (block == BLOCK_NIL)
				break;

			/* Validate next block before following it... */
			if (block > s->lastEUN) {
				printk(KERN_WARNING "INFTL: invalid previous "
					"block %d in chain %d?\n", block,
					first_block);
				do_format_chain++;
				break;
			}
		}

		if (do_format_chain) {
			format_chain(s, first_block);
			continue;
		}

		/*
		 * Looks like a valid chain then. It may not really be the
		 * newest block in the chain, but it is the newest we have
		 * found so far. We might update it in later iterations of
		 * this loop if we find something newer.
		 */
		s->VUtable[first_logical_block] = first_block;
		logical_block = BLOCK_NIL;
	}

720
	INFTL_dumptables(s);
Linus Torvalds's avatar
Linus Torvalds committed
721 722 723 724 725 726

	/*
	 * Second pass, check for infinite loops in chains. These are
	 * possible because we don't update the previous pointers when
	 * we fold chains. No big deal, just fix them up in PUtable.
	 */
727
	pr_debug("INFTL: pass 2, validate virtual chains\n");
Linus Torvalds's avatar
Linus Torvalds committed
728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744
	for (logical_block = 0; logical_block < s->numvunits; logical_block++) {
		block = s->VUtable[logical_block];
		last_block = BLOCK_NIL;

		/* Check for free/reserved/nil */
		if (block >= BLOCK_RESERVED)
			continue;

		ANAC = ANACtable[block];
		for (i = 0; i < s->numvunits; i++) {
			if (s->PUtable[block] == BLOCK_NIL)
				break;
			if (s->PUtable[block] > s->lastEUN) {
				printk(KERN_WARNING "INFTL: invalid prev %d, "
					"in virtual chain %d\n",
					s->PUtable[block], logical_block);
				s->PUtable[block] = BLOCK_NIL;
745

Linus Torvalds's avatar
Linus Torvalds committed
746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771
			}
			if (ANACtable[block] != ANAC) {
				/*
				 * Chain must point back to itself. This is ok,
				 * but we will need adjust the tables with this
				 * newest block and oldest block.
				 */
				s->VUtable[logical_block] = block;
				s->PUtable[last_block] = BLOCK_NIL;
				break;
			}

			ANAC--;
			last_block = block;
			block = s->PUtable[block];
		}

		if (i >= s->nb_blocks) {
			/*
			 * Uhoo, infinite chain with valid ANACS!
			 * Format whole chain...
			 */
			format_chain(s, first_block);
		}
	}

772 773
	INFTL_dumptables(s);
	INFTL_dumpVUchains(s);
Linus Torvalds's avatar
Linus Torvalds committed
774 775 776 777 778 779 780

	/*
	 * Third pass, format unreferenced blocks and init free block count.
	 */
	s->numfreeEUNs = 0;
	s->LastFreeEUN = BLOCK_NIL;

781
	pr_debug("INFTL: pass 3, format unused blocks\n");
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
	for (block = s->firstEUN; block <= s->lastEUN; block++) {
		if (s->PUtable[block] == BLOCK_NOTEXPLORED) {
			printk("INFTL: unreferenced block %d, formatting it\n",
				block);
			if (INFTL_formatblock(s, block) < 0)
				s->PUtable[block] = BLOCK_RESERVED;
			else
				s->PUtable[block] = BLOCK_FREE;
		}
		if (s->PUtable[block] == BLOCK_FREE) {
			s->numfreeEUNs++;
			if (s->LastFreeEUN == BLOCK_NIL)
				s->LastFreeEUN = block;
		}
	}

	kfree(ANACtable);
	return 0;
}