blob: 8a544890173d3c857c953eb149c68b14b145ba90 [file] [log] [blame]
Thomas Gleixner97894cd2005-11-07 11:15:26 +00001/*
Linus Torvalds1da177e2005-04-16 15:20:36 -07002 * inftlcore.c -- Linux driver for Inverse Flash Translation Layer (INFTL)
3 *
4 * (C) Copyright 2002, Greg Ungerer (gerg@snapgear.com)
5 *
6 * Based heavily on the nftlcore.c code which is:
7 * (c) 1999 Machine Vision Holdings, Inc.
8 * Author: David Woodhouse <dwmw2@infradead.org>
9 *
Thomas Gleixner97894cd2005-11-07 11:15:26 +000010 * $Id: inftlcore.c,v 1.19 2005/11/07 11:14:20 gleixner Exp $
Linus Torvalds1da177e2005-04-16 15:20:36 -070011 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 */
26
27#include <linux/config.h>
28#include <linux/kernel.h>
29#include <linux/module.h>
30#include <linux/delay.h>
31#include <linux/slab.h>
32#include <linux/sched.h>
33#include <linux/init.h>
34#include <linux/kmod.h>
35#include <linux/hdreg.h>
36#include <linux/mtd/mtd.h>
37#include <linux/mtd/nftl.h>
38#include <linux/mtd/inftl.h>
39#include <asm/uaccess.h>
40#include <asm/errno.h>
41#include <asm/io.h>
42
43/*
44 * Maximum number of loops while examining next block, to have a
45 * chance to detect consistency problems (they should never happen
46 * because of the checks done in the mounting.
47 */
48#define MAX_LOOPS 10000
49
50extern void INFTL_dumptables(struct INFTLrecord *inftl);
51extern void INFTL_dumpVUchains(struct INFTLrecord *inftl);
52
53static void inftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
54{
55 struct INFTLrecord *inftl;
56 unsigned long temp;
57
58 if (mtd->type != MTD_NANDFLASH)
59 return;
60 /* OK, this is moderately ugly. But probably safe. Alternatives? */
61 if (memcmp(mtd->name, "DiskOnChip", 10))
62 return;
63
64 if (!mtd->block_isbad) {
65 printk(KERN_ERR
66"INFTL no longer supports the old DiskOnChip drivers loaded via docprobe.\n"
67"Please use the new diskonchip driver under the NAND subsystem.\n");
68 return;
69 }
70
71 DEBUG(MTD_DEBUG_LEVEL3, "INFTL: add_mtd for %s\n", mtd->name);
72
73 inftl = kmalloc(sizeof(*inftl), GFP_KERNEL);
74
75 if (!inftl) {
76 printk(KERN_WARNING "INFTL: Out of memory for data structures\n");
77 return;
78 }
79 memset(inftl, 0, sizeof(*inftl));
80
81 inftl->mbd.mtd = mtd;
82 inftl->mbd.devnum = -1;
83 inftl->mbd.blksize = 512;
84 inftl->mbd.tr = tr;
85 memcpy(&inftl->oobinfo, &mtd->oobinfo, sizeof(struct nand_oobinfo));
86 inftl->oobinfo.useecc = MTD_NANDECC_PLACEONLY;
87
88 if (INFTL_mount(inftl) < 0) {
89 printk(KERN_WARNING "INFTL: could not mount device\n");
90 kfree(inftl);
91 return;
92 }
93
94 /* OK, it's a new one. Set up all the data structures. */
95
96 /* Calculate geometry */
97 inftl->cylinders = 1024;
98 inftl->heads = 16;
99
100 temp = inftl->cylinders * inftl->heads;
101 inftl->sectors = inftl->mbd.size / temp;
102 if (inftl->mbd.size % temp) {
103 inftl->sectors++;
104 temp = inftl->cylinders * inftl->sectors;
105 inftl->heads = inftl->mbd.size / temp;
106
107 if (inftl->mbd.size % temp) {
108 inftl->heads++;
109 temp = inftl->heads * inftl->sectors;
110 inftl->cylinders = inftl->mbd.size / temp;
111 }
112 }
113
114 if (inftl->mbd.size != inftl->heads * inftl->cylinders * inftl->sectors) {
115 /*
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000116 Oh no we don't have
Linus Torvalds1da177e2005-04-16 15:20:36 -0700117 mbd.size == heads * cylinders * sectors
118 */
119 printk(KERN_WARNING "INFTL: cannot calculate a geometry to "
120 "match size of 0x%lx.\n", inftl->mbd.size);
121 printk(KERN_WARNING "INFTL: using C:%d H:%d S:%d "
122 "(== 0x%lx sects)\n",
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000123 inftl->cylinders, inftl->heads , inftl->sectors,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700124 (long)inftl->cylinders * (long)inftl->heads *
125 (long)inftl->sectors );
126 }
127
128 if (add_mtd_blktrans_dev(&inftl->mbd)) {
Jesper Juhlfa671642005-11-07 01:01:27 -0800129 kfree(inftl->PUtable);
130 kfree(inftl->VUtable);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700131 kfree(inftl);
132 return;
133 }
134#ifdef PSYCHO_DEBUG
135 printk(KERN_INFO "INFTL: Found new nftl%c\n", nftl->mbd.devnum + 'a');
136#endif
137 return;
138}
139
140static void inftl_remove_dev(struct mtd_blktrans_dev *dev)
141{
142 struct INFTLrecord *inftl = (void *)dev;
143
144 DEBUG(MTD_DEBUG_LEVEL3, "INFTL: remove_dev (i=%d)\n", dev->devnum);
145
146 del_mtd_blktrans_dev(dev);
147
Jesper Juhlfa671642005-11-07 01:01:27 -0800148 kfree(inftl->PUtable);
149 kfree(inftl->VUtable);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700150 kfree(inftl);
151}
152
153/*
154 * Actual INFTL access routines.
155 */
156
157/*
158 * INFTL_findfreeblock: Find a free Erase Unit on the INFTL partition.
159 * This function is used when the give Virtual Unit Chain.
160 */
161static u16 INFTL_findfreeblock(struct INFTLrecord *inftl, int desperate)
162{
163 u16 pot = inftl->LastFreeEUN;
164 int silly = inftl->nb_blocks;
165
166 DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_findfreeblock(inftl=%p,"
167 "desperate=%d)\n", inftl, desperate);
168
169 /*
170 * Normally, we force a fold to happen before we run out of free
171 * blocks completely.
172 */
173 if (!desperate && inftl->numfreeEUNs < 2) {
174 DEBUG(MTD_DEBUG_LEVEL1, "INFTL: there are too few free "
175 "EUNs (%d)\n", inftl->numfreeEUNs);
176 return 0xffff;
177 }
178
179 /* Scan for a free block */
180 do {
181 if (inftl->PUtable[pot] == BLOCK_FREE) {
182 inftl->LastFreeEUN = pot;
183 return pot;
184 }
185
186 if (++pot > inftl->lastEUN)
187 pot = 0;
188
189 if (!silly--) {
190 printk(KERN_WARNING "INFTL: no free blocks found! "
191 "EUN range = %d - %d\n", 0, inftl->LastFreeEUN);
192 return BLOCK_NIL;
193 }
194 } while (pot != inftl->LastFreeEUN);
195
196 return BLOCK_NIL;
197}
198
199static u16 INFTL_foldchain(struct INFTLrecord *inftl, unsigned thisVUC, unsigned pendingblock)
200{
201 u16 BlockMap[MAX_SECTORS_PER_UNIT];
202 unsigned char BlockDeleted[MAX_SECTORS_PER_UNIT];
203 unsigned int thisEUN, prevEUN, status;
204 int block, silly;
205 unsigned int targetEUN;
206 struct inftl_oob oob;
207 size_t retlen;
208
209 DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_foldchain(inftl=%p,thisVUC=%d,"
210 "pending=%d)\n", inftl, thisVUC, pendingblock);
211
212 memset(BlockMap, 0xff, sizeof(BlockMap));
213 memset(BlockDeleted, 0, sizeof(BlockDeleted));
214
215 thisEUN = targetEUN = inftl->VUtable[thisVUC];
216
217 if (thisEUN == BLOCK_NIL) {
218 printk(KERN_WARNING "INFTL: trying to fold non-existent "
219 "Virtual Unit Chain %d!\n", thisVUC);
220 return BLOCK_NIL;
221 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000222
Linus Torvalds1da177e2005-04-16 15:20:36 -0700223 /*
224 * Scan to find the Erase Unit which holds the actual data for each
225 * 512-byte block within the Chain.
226 */
227 silly = MAX_LOOPS;
228 while (thisEUN < inftl->nb_blocks) {
229 for (block = 0; block < inftl->EraseSize/SECTORSIZE; block ++) {
230 if ((BlockMap[block] != 0xffff) || BlockDeleted[block])
231 continue;
232
233 if (MTD_READOOB(inftl->mbd.mtd, (thisEUN * inftl->EraseSize)
234 + (block * SECTORSIZE), 16 , &retlen,
235 (char *)&oob) < 0)
236 status = SECTOR_IGNORE;
237 else
238 status = oob.b.Status | oob.b.Status1;
239
240 switch(status) {
241 case SECTOR_FREE:
242 case SECTOR_IGNORE:
243 break;
244 case SECTOR_USED:
245 BlockMap[block] = thisEUN;
246 continue;
247 case SECTOR_DELETED:
248 BlockDeleted[block] = 1;
249 continue;
250 default:
251 printk(KERN_WARNING "INFTL: unknown status "
252 "for block %d in EUN %d: %x\n",
253 block, thisEUN, status);
254 break;
255 }
256 }
257
258 if (!silly--) {
259 printk(KERN_WARNING "INFTL: infinite loop in Virtual "
260 "Unit Chain 0x%x\n", thisVUC);
261 return BLOCK_NIL;
262 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000263
Linus Torvalds1da177e2005-04-16 15:20:36 -0700264 thisEUN = inftl->PUtable[thisEUN];
265 }
266
267 /*
268 * OK. We now know the location of every block in the Virtual Unit
269 * Chain, and the Erase Unit into which we are supposed to be copying.
270 * Go for it.
271 */
272 DEBUG(MTD_DEBUG_LEVEL1, "INFTL: folding chain %d into unit %d\n",
273 thisVUC, targetEUN);
274
275 for (block = 0; block < inftl->EraseSize/SECTORSIZE ; block++) {
276 unsigned char movebuf[SECTORSIZE];
277 int ret;
278
279 /*
280 * If it's in the target EUN already, or if it's pending write,
281 * do nothing.
282 */
283 if (BlockMap[block] == targetEUN || (pendingblock ==
284 (thisVUC * (inftl->EraseSize / SECTORSIZE) + block))) {
285 continue;
286 }
287
288 /*
289 * Copy only in non free block (free blocks can only
290 * happen in case of media errors or deleted blocks).
291 */
292 if (BlockMap[block] == BLOCK_NIL)
293 continue;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000294
Linus Torvalds1da177e2005-04-16 15:20:36 -0700295 ret = MTD_READ(inftl->mbd.mtd, (inftl->EraseSize *
296 BlockMap[block]) + (block * SECTORSIZE), SECTORSIZE,
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000297 &retlen, movebuf);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700298 if (ret < 0) {
299 ret = MTD_READ(inftl->mbd.mtd, (inftl->EraseSize *
300 BlockMap[block]) + (block * SECTORSIZE),
301 SECTORSIZE, &retlen, movebuf);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000302 if (ret != -EIO)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700303 DEBUG(MTD_DEBUG_LEVEL1, "INFTL: error went "
304 "away on retry?\n");
305 }
306 memset(&oob, 0xff, sizeof(struct inftl_oob));
307 oob.b.Status = oob.b.Status1 = SECTOR_USED;
308 MTD_WRITEECC(inftl->mbd.mtd, (inftl->EraseSize * targetEUN) +
309 (block * SECTORSIZE), SECTORSIZE, &retlen,
310 movebuf, (char *)&oob, &inftl->oobinfo);
311 }
312
313 /*
314 * Newest unit in chain now contains data from _all_ older units.
315 * So go through and erase each unit in chain, oldest first. (This
316 * is important, by doing oldest first if we crash/reboot then it
317 * it is relatively simple to clean up the mess).
318 */
319 DEBUG(MTD_DEBUG_LEVEL1, "INFTL: want to erase virtual chain %d\n",
320 thisVUC);
321
322 for (;;) {
323 /* Find oldest unit in chain. */
324 thisEUN = inftl->VUtable[thisVUC];
325 prevEUN = BLOCK_NIL;
326 while (inftl->PUtable[thisEUN] != BLOCK_NIL) {
327 prevEUN = thisEUN;
328 thisEUN = inftl->PUtable[thisEUN];
329 }
330
331 /* Check if we are all done */
332 if (thisEUN == targetEUN)
333 break;
334
335 if (INFTL_formatblock(inftl, thisEUN) < 0) {
336 /*
337 * Could not erase : mark block as reserved.
338 */
339 inftl->PUtable[thisEUN] = BLOCK_RESERVED;
340 } else {
341 /* Correctly erased : mark it as free */
342 inftl->PUtable[thisEUN] = BLOCK_FREE;
343 inftl->PUtable[prevEUN] = BLOCK_NIL;
344 inftl->numfreeEUNs++;
345 }
346 }
347
348 return targetEUN;
349}
350
351static u16 INFTL_makefreeblock(struct INFTLrecord *inftl, unsigned pendingblock)
352{
353 /*
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000354 * This is the part that needs some cleverness applied.
Linus Torvalds1da177e2005-04-16 15:20:36 -0700355 * For now, I'm doing the minimum applicable to actually
356 * get the thing to work.
357 * Wear-levelling and other clever stuff needs to be implemented
358 * and we also need to do some assessment of the results when
359 * the system loses power half-way through the routine.
360 */
361 u16 LongestChain = 0;
362 u16 ChainLength = 0, thislen;
363 u16 chain, EUN;
364
365 DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_makefreeblock(inftl=%p,"
366 "pending=%d)\n", inftl, pendingblock);
367
368 for (chain = 0; chain < inftl->nb_blocks; chain++) {
369 EUN = inftl->VUtable[chain];
370 thislen = 0;
371
372 while (EUN <= inftl->lastEUN) {
373 thislen++;
374 EUN = inftl->PUtable[EUN];
375 if (thislen > 0xff00) {
376 printk(KERN_WARNING "INFTL: endless loop in "
377 "Virtual Chain %d: Unit %x\n",
378 chain, EUN);
379 /*
380 * Actually, don't return failure.
381 * Just ignore this chain and get on with it.
382 */
383 thislen = 0;
384 break;
385 }
386 }
387
388 if (thislen > ChainLength) {
389 ChainLength = thislen;
390 LongestChain = chain;
391 }
392 }
393
394 if (ChainLength < 2) {
395 printk(KERN_WARNING "INFTL: no Virtual Unit Chains available "
396 "for folding. Failing request\n");
397 return BLOCK_NIL;
398 }
399
400 return INFTL_foldchain(inftl, LongestChain, pendingblock);
401}
402
403static int nrbits(unsigned int val, int bitcount)
404{
405 int i, total = 0;
406
407 for (i = 0; (i < bitcount); i++)
408 total += (((0x1 << i) & val) ? 1 : 0);
409 return total;
410}
411
412/*
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000413 * INFTL_findwriteunit: Return the unit number into which we can write
Linus Torvalds1da177e2005-04-16 15:20:36 -0700414 * for this block. Make it available if it isn't already.
415 */
416static inline u16 INFTL_findwriteunit(struct INFTLrecord *inftl, unsigned block)
417{
418 unsigned int thisVUC = block / (inftl->EraseSize / SECTORSIZE);
419 unsigned int thisEUN, writeEUN, prev_block, status;
420 unsigned long blockofs = (block * SECTORSIZE) & (inftl->EraseSize -1);
421 struct inftl_oob oob;
422 struct inftl_bci bci;
423 unsigned char anac, nacs, parity;
424 size_t retlen;
425 int silly, silly2 = 3;
426
427 DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_findwriteunit(inftl=%p,"
428 "block=%d)\n", inftl, block);
429
430 do {
431 /*
432 * Scan the media to find a unit in the VUC which has
433 * a free space for the block in question.
434 */
435 writeEUN = BLOCK_NIL;
436 thisEUN = inftl->VUtable[thisVUC];
437 silly = MAX_LOOPS;
438
439 while (thisEUN <= inftl->lastEUN) {
440 MTD_READOOB(inftl->mbd.mtd, (thisEUN * inftl->EraseSize) +
441 blockofs, 8, &retlen, (char *)&bci);
442
443 status = bci.Status | bci.Status1;
444 DEBUG(MTD_DEBUG_LEVEL3, "INFTL: status of block %d in "
445 "EUN %d is %x\n", block , writeEUN, status);
446
447 switch(status) {
448 case SECTOR_FREE:
449 writeEUN = thisEUN;
450 break;
451 case SECTOR_DELETED:
452 case SECTOR_USED:
453 /* Can't go any further */
454 goto hitused;
455 case SECTOR_IGNORE:
456 break;
457 default:
458 /*
459 * Invalid block. Don't use it any more.
460 * Must implement.
461 */
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000462 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700463 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000464
465 if (!silly--) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700466 printk(KERN_WARNING "INFTL: infinite loop in "
467 "Virtual Unit Chain 0x%x\n", thisVUC);
468 return 0xffff;
469 }
470
471 /* Skip to next block in chain */
472 thisEUN = inftl->PUtable[thisEUN];
473 }
474
475hitused:
476 if (writeEUN != BLOCK_NIL)
477 return writeEUN;
478
479
480 /*
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000481 * OK. We didn't find one in the existing chain, or there
Linus Torvalds1da177e2005-04-16 15:20:36 -0700482 * is no existing chain. Allocate a new one.
483 */
484 writeEUN = INFTL_findfreeblock(inftl, 0);
485
486 if (writeEUN == BLOCK_NIL) {
487 /*
488 * That didn't work - there were no free blocks just
489 * waiting to be picked up. We're going to have to fold
490 * a chain to make room.
491 */
492 thisEUN = INFTL_makefreeblock(inftl, 0xffff);
493
494 /*
495 * Hopefully we free something, lets try again.
496 * This time we are desperate...
497 */
498 DEBUG(MTD_DEBUG_LEVEL1, "INFTL: using desperate==1 "
499 "to find free EUN to accommodate write to "
500 "VUC %d\n", thisVUC);
501 writeEUN = INFTL_findfreeblock(inftl, 1);
502 if (writeEUN == BLOCK_NIL) {
503 /*
504 * Ouch. This should never happen - we should
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000505 * always be able to make some room somehow.
506 * If we get here, we've allocated more storage
Linus Torvalds1da177e2005-04-16 15:20:36 -0700507 * space than actual media, or our makefreeblock
508 * routine is missing something.
509 */
510 printk(KERN_WARNING "INFTL: cannot make free "
511 "space.\n");
512#ifdef DEBUG
513 INFTL_dumptables(inftl);
514 INFTL_dumpVUchains(inftl);
515#endif
516 return BLOCK_NIL;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000517 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700518 }
519
520 /*
521 * Insert new block into virtual chain. Firstly update the
522 * block headers in flash...
523 */
524 anac = 0;
525 nacs = 0;
526 thisEUN = inftl->VUtable[thisVUC];
527 if (thisEUN != BLOCK_NIL) {
528 MTD_READOOB(inftl->mbd.mtd, thisEUN * inftl->EraseSize
529 + 8, 8, &retlen, (char *)&oob.u);
530 anac = oob.u.a.ANAC + 1;
531 nacs = oob.u.a.NACs + 1;
532 }
533
534 prev_block = inftl->VUtable[thisVUC];
535 if (prev_block < inftl->nb_blocks)
536 prev_block -= inftl->firstEUN;
537
538 parity = (nrbits(thisVUC, 16) & 0x1) ? 0x1 : 0;
539 parity |= (nrbits(prev_block, 16) & 0x1) ? 0x2 : 0;
540 parity |= (nrbits(anac, 8) & 0x1) ? 0x4 : 0;
541 parity |= (nrbits(nacs, 8) & 0x1) ? 0x8 : 0;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000542
Linus Torvalds1da177e2005-04-16 15:20:36 -0700543 oob.u.a.virtualUnitNo = cpu_to_le16(thisVUC);
544 oob.u.a.prevUnitNo = cpu_to_le16(prev_block);
545 oob.u.a.ANAC = anac;
546 oob.u.a.NACs = nacs;
547 oob.u.a.parityPerField = parity;
548 oob.u.a.discarded = 0xaa;
549
550 MTD_WRITEOOB(inftl->mbd.mtd, writeEUN * inftl->EraseSize + 8, 8,
551 &retlen, (char *)&oob.u);
552
553 /* Also back up header... */
554 oob.u.b.virtualUnitNo = cpu_to_le16(thisVUC);
555 oob.u.b.prevUnitNo = cpu_to_le16(prev_block);
556 oob.u.b.ANAC = anac;
557 oob.u.b.NACs = nacs;
558 oob.u.b.parityPerField = parity;
559 oob.u.b.discarded = 0xaa;
560
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000561 MTD_WRITEOOB(inftl->mbd.mtd, writeEUN * inftl->EraseSize +
Linus Torvalds1da177e2005-04-16 15:20:36 -0700562 SECTORSIZE * 4 + 8, 8, &retlen, (char *)&oob.u);
563
564 inftl->PUtable[writeEUN] = inftl->VUtable[thisVUC];
565 inftl->VUtable[thisVUC] = writeEUN;
566
567 inftl->numfreeEUNs--;
568 return writeEUN;
569
570 } while (silly2--);
571
572 printk(KERN_WARNING "INFTL: error folding to make room for Virtual "
573 "Unit Chain 0x%x\n", thisVUC);
574 return 0xffff;
575}
576
577/*
578 * Given a Virtual Unit Chain, see if it can be deleted, and if so do it.
579 */
580static void INFTL_trydeletechain(struct INFTLrecord *inftl, unsigned thisVUC)
581{
582 unsigned char BlockUsed[MAX_SECTORS_PER_UNIT];
583 unsigned char BlockDeleted[MAX_SECTORS_PER_UNIT];
584 unsigned int thisEUN, status;
585 int block, silly;
586 struct inftl_bci bci;
587 size_t retlen;
588
589 DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_trydeletechain(inftl=%p,"
590 "thisVUC=%d)\n", inftl, thisVUC);
591
592 memset(BlockUsed, 0, sizeof(BlockUsed));
593 memset(BlockDeleted, 0, sizeof(BlockDeleted));
594
595 thisEUN = inftl->VUtable[thisVUC];
596 if (thisEUN == BLOCK_NIL) {
597 printk(KERN_WARNING "INFTL: trying to delete non-existent "
598 "Virtual Unit Chain %d!\n", thisVUC);
599 return;
600 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000601
Linus Torvalds1da177e2005-04-16 15:20:36 -0700602 /*
603 * Scan through the Erase Units to determine whether any data is in
604 * each of the 512-byte blocks within the Chain.
605 */
606 silly = MAX_LOOPS;
607 while (thisEUN < inftl->nb_blocks) {
608 for (block = 0; block < inftl->EraseSize/SECTORSIZE; block++) {
609 if (BlockUsed[block] || BlockDeleted[block])
610 continue;
611
612 if (MTD_READOOB(inftl->mbd.mtd, (thisEUN * inftl->EraseSize)
613 + (block * SECTORSIZE), 8 , &retlen,
614 (char *)&bci) < 0)
615 status = SECTOR_IGNORE;
616 else
617 status = bci.Status | bci.Status1;
618
619 switch(status) {
620 case SECTOR_FREE:
621 case SECTOR_IGNORE:
622 break;
623 case SECTOR_USED:
624 BlockUsed[block] = 1;
625 continue;
626 case SECTOR_DELETED:
627 BlockDeleted[block] = 1;
628 continue;
629 default:
630 printk(KERN_WARNING "INFTL: unknown status "
631 "for block %d in EUN %d: 0x%x\n",
632 block, thisEUN, status);
633 }
634 }
635
636 if (!silly--) {
637 printk(KERN_WARNING "INFTL: infinite loop in Virtual "
638 "Unit Chain 0x%x\n", thisVUC);
639 return;
640 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000641
Linus Torvalds1da177e2005-04-16 15:20:36 -0700642 thisEUN = inftl->PUtable[thisEUN];
643 }
644
645 for (block = 0; block < inftl->EraseSize/SECTORSIZE; block++)
646 if (BlockUsed[block])
647 return;
648
649 /*
650 * For each block in the chain free it and make it available
651 * for future use. Erase from the oldest unit first.
652 */
653 DEBUG(MTD_DEBUG_LEVEL1, "INFTL: deleting empty VUC %d\n", thisVUC);
654
655 for (;;) {
656 u16 *prevEUN = &inftl->VUtable[thisVUC];
657 thisEUN = *prevEUN;
658
659 /* If the chain is all gone already, we're done */
660 if (thisEUN == BLOCK_NIL) {
661 DEBUG(MTD_DEBUG_LEVEL2, "INFTL: Empty VUC %d for deletion was already absent\n", thisEUN);
662 return;
663 }
664
665 /* Find oldest unit in chain. */
666 while (inftl->PUtable[thisEUN] != BLOCK_NIL) {
667 BUG_ON(thisEUN >= inftl->nb_blocks);
668
669 prevEUN = &inftl->PUtable[thisEUN];
670 thisEUN = *prevEUN;
671 }
672
673 DEBUG(MTD_DEBUG_LEVEL3, "Deleting EUN %d from VUC %d\n",
674 thisEUN, thisVUC);
675
676 if (INFTL_formatblock(inftl, thisEUN) < 0) {
677 /*
678 * Could not erase : mark block as reserved.
679 */
680 inftl->PUtable[thisEUN] = BLOCK_RESERVED;
681 } else {
682 /* Correctly erased : mark it as free */
683 inftl->PUtable[thisEUN] = BLOCK_FREE;
684 inftl->numfreeEUNs++;
685 }
686
687 /* Now sort out whatever was pointing to it... */
688 *prevEUN = BLOCK_NIL;
689
690 /* Ideally we'd actually be responsive to new
691 requests while we're doing this -- if there's
692 free space why should others be made to wait? */
693 cond_resched();
694 }
695
696 inftl->VUtable[thisVUC] = BLOCK_NIL;
697}
698
699static int INFTL_deleteblock(struct INFTLrecord *inftl, unsigned block)
700{
701 unsigned int thisEUN = inftl->VUtable[block / (inftl->EraseSize / SECTORSIZE)];
702 unsigned long blockofs = (block * SECTORSIZE) & (inftl->EraseSize - 1);
703 unsigned int status;
704 int silly = MAX_LOOPS;
705 size_t retlen;
706 struct inftl_bci bci;
707
708 DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_deleteblock(inftl=%p,"
709 "block=%d)\n", inftl, block);
710
711 while (thisEUN < inftl->nb_blocks) {
712 if (MTD_READOOB(inftl->mbd.mtd, (thisEUN * inftl->EraseSize) +
713 blockofs, 8, &retlen, (char *)&bci) < 0)
714 status = SECTOR_IGNORE;
715 else
716 status = bci.Status | bci.Status1;
717
718 switch (status) {
719 case SECTOR_FREE:
720 case SECTOR_IGNORE:
721 break;
722 case SECTOR_DELETED:
723 thisEUN = BLOCK_NIL;
724 goto foundit;
725 case SECTOR_USED:
726 goto foundit;
727 default:
728 printk(KERN_WARNING "INFTL: unknown status for "
729 "block %d in EUN %d: 0x%x\n",
730 block, thisEUN, status);
731 break;
732 }
733
734 if (!silly--) {
735 printk(KERN_WARNING "INFTL: infinite loop in Virtual "
736 "Unit Chain 0x%x\n",
737 block / (inftl->EraseSize / SECTORSIZE));
738 return 1;
739 }
740 thisEUN = inftl->PUtable[thisEUN];
741 }
742
743foundit:
744 if (thisEUN != BLOCK_NIL) {
745 loff_t ptr = (thisEUN * inftl->EraseSize) + blockofs;
746
747 if (MTD_READOOB(inftl->mbd.mtd, ptr, 8, &retlen, (char *)&bci) < 0)
748 return -EIO;
749 bci.Status = bci.Status1 = SECTOR_DELETED;
750 if (MTD_WRITEOOB(inftl->mbd.mtd, ptr, 8, &retlen, (char *)&bci) < 0)
751 return -EIO;
752 INFTL_trydeletechain(inftl, block / (inftl->EraseSize / SECTORSIZE));
753 }
754 return 0;
755}
756
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000757static int inftl_writeblock(struct mtd_blktrans_dev *mbd, unsigned long block,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700758 char *buffer)
759{
760 struct INFTLrecord *inftl = (void *)mbd;
761 unsigned int writeEUN;
762 unsigned long blockofs = (block * SECTORSIZE) & (inftl->EraseSize - 1);
763 size_t retlen;
764 struct inftl_oob oob;
765 char *p, *pend;
766
767 DEBUG(MTD_DEBUG_LEVEL3, "INFTL: inftl_writeblock(inftl=%p,block=%ld,"
768 "buffer=%p)\n", inftl, block, buffer);
769
770 /* Is block all zero? */
771 pend = buffer + SECTORSIZE;
772 for (p = buffer; p < pend && !*p; p++)
773 ;
774
775 if (p < pend) {
776 writeEUN = INFTL_findwriteunit(inftl, block);
777
778 if (writeEUN == BLOCK_NIL) {
779 printk(KERN_WARNING "inftl_writeblock(): cannot find "
780 "block to write to\n");
781 /*
782 * If we _still_ haven't got a block to use,
783 * we're screwed.
784 */
785 return 1;
786 }
787
788 memset(&oob, 0xff, sizeof(struct inftl_oob));
789 oob.b.Status = oob.b.Status1 = SECTOR_USED;
790 MTD_WRITEECC(inftl->mbd.mtd, (writeEUN * inftl->EraseSize) +
791 blockofs, SECTORSIZE, &retlen, (char *)buffer,
792 (char *)&oob, &inftl->oobinfo);
793 /*
794 * need to write SECTOR_USED flags since they are not written
795 * in mtd_writeecc
796 */
797 } else {
798 INFTL_deleteblock(inftl, block);
799 }
800
801 return 0;
802}
803
804static int inftl_readblock(struct mtd_blktrans_dev *mbd, unsigned long block,
805 char *buffer)
806{
807 struct INFTLrecord *inftl = (void *)mbd;
808 unsigned int thisEUN = inftl->VUtable[block / (inftl->EraseSize / SECTORSIZE)];
809 unsigned long blockofs = (block * SECTORSIZE) & (inftl->EraseSize - 1);
810 unsigned int status;
811 int silly = MAX_LOOPS;
812 struct inftl_bci bci;
813 size_t retlen;
814
815 DEBUG(MTD_DEBUG_LEVEL3, "INFTL: inftl_readblock(inftl=%p,block=%ld,"
816 "buffer=%p)\n", inftl, block, buffer);
817
818 while (thisEUN < inftl->nb_blocks) {
819 if (MTD_READOOB(inftl->mbd.mtd, (thisEUN * inftl->EraseSize) +
820 blockofs, 8, &retlen, (char *)&bci) < 0)
821 status = SECTOR_IGNORE;
822 else
823 status = bci.Status | bci.Status1;
824
825 switch (status) {
826 case SECTOR_DELETED:
827 thisEUN = BLOCK_NIL;
828 goto foundit;
829 case SECTOR_USED:
830 goto foundit;
831 case SECTOR_FREE:
832 case SECTOR_IGNORE:
833 break;
834 default:
835 printk(KERN_WARNING "INFTL: unknown status for "
836 "block %ld in EUN %d: 0x%04x\n",
837 block, thisEUN, status);
838 break;
839 }
840
841 if (!silly--) {
842 printk(KERN_WARNING "INFTL: infinite loop in "
843 "Virtual Unit Chain 0x%lx\n",
844 block / (inftl->EraseSize / SECTORSIZE));
845 return 1;
846 }
847
848 thisEUN = inftl->PUtable[thisEUN];
849 }
850
851foundit:
852 if (thisEUN == BLOCK_NIL) {
853 /* The requested block is not on the media, return all 0x00 */
854 memset(buffer, 0, SECTORSIZE);
855 } else {
856 size_t retlen;
857 loff_t ptr = (thisEUN * inftl->EraseSize) + blockofs;
858 if (MTD_READ(inftl->mbd.mtd, ptr, SECTORSIZE, &retlen,
859 buffer))
860 return -EIO;
861 }
862 return 0;
863}
864
865static int inftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
866{
867 struct INFTLrecord *inftl = (void *)dev;
868
869 geo->heads = inftl->heads;
870 geo->sectors = inftl->sectors;
871 geo->cylinders = inftl->cylinders;
872
873 return 0;
874}
875
876static struct mtd_blktrans_ops inftl_tr = {
877 .name = "inftl",
878 .major = INFTL_MAJOR,
879 .part_bits = INFTL_PARTN_BITS,
880 .getgeo = inftl_getgeo,
881 .readsect = inftl_readblock,
882 .writesect = inftl_writeblock,
883 .add_mtd = inftl_add_mtd,
884 .remove_dev = inftl_remove_dev,
885 .owner = THIS_MODULE,
886};
887
888extern char inftlmountrev[];
889
890static int __init init_inftl(void)
891{
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000892 printk(KERN_INFO "INFTL: inftlcore.c $Revision: 1.19 $, "
Linus Torvalds1da177e2005-04-16 15:20:36 -0700893 "inftlmount.c %s\n", inftlmountrev);
894
895 return register_mtd_blktrans(&inftl_tr);
896}
897
898static void __exit cleanup_inftl(void)
899{
900 deregister_mtd_blktrans(&inftl_tr);
901}
902
903module_init(init_inftl);
904module_exit(cleanup_inftl);
905
906MODULE_LICENSE("GPL");
907MODULE_AUTHOR("Greg Ungerer <gerg@snapgear.com>, David Woodhouse <dwmw2@infradead.org>, Fabrice Bellard <fabrice.bellard@netgem.com> et al.");
908MODULE_DESCRIPTION("Support code for Inverse Flash Translation Layer, used on M-Systems DiskOnChip 2000, Millennium and Millennium Plus");