Blame view

drivers/mtd/nftlcore.c 23.3 KB
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
1
  /*
a1452a377   David Woodhouse   mtd: Update copyr...
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
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
20
21
22
   */
  
  #define PRERELEASE
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
23
24
25
26
27
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <asm/errno.h>
  #include <asm/io.h>
  #include <asm/uaccess.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
28
29
  #include <linux/delay.h>
  #include <linux/slab.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
30
31
  #include <linux/init.h>
  #include <linux/hdreg.h>
e7f521636   Scott James Remnant   [MTD] Auto-load n...
32
  #include <linux/blkdev.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
  
  #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;
69423d99f   Adrian Hunter   [MTD] update inte...
51
  	if (mtd->type != MTD_NANDFLASH || mtd->size > UINT_MAX)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
  		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.
  "
  "Please use the new diskonchip driver under the NAND subsystem.
  ");
  		return;
  	}
  
  	DEBUG(MTD_DEBUG_LEVEL1, "NFTL: add_mtd for %s
  ", mtd->name);
95b93a0cd   Burman Yan   [MTD] replace kma...
68
  	nftl = kzalloc(sizeof(struct NFTLrecord), GFP_KERNEL);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
69
70
71
72
73
74
  
  	if (!nftl) {
  		printk(KERN_WARNING "NFTL: out of memory for data structures
  ");
  		return;
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
75
76
77
  
  	nftl->mbd.mtd = mtd;
  	nftl->mbd.devnum = -1;
191876729   Richard Purdie   [MTD] Allow varia...
78

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
79
  	nftl->mbd.tr = tr;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
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
106
107
108
109
  
          if (NFTL_mount(nftl) < 0) {
  		printk(KERN_WARNING "NFTL: could not mount device
  ");
  		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) {
  		/*
97894cda5   Thomas Gleixner   [MTD] core: Clean...
110
  		  Oh no we don't have
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
111
112
113
114
115
116
117
118
  		   mbd.size == heads * cylinders * sectors
  		*/
  		printk(KERN_WARNING "NFTL: cannot calculate a geometry to "
  		       "match size of 0x%lx.
  ", nftl->mbd.size);
  		printk(KERN_WARNING "NFTL: using C:%d H:%d S:%d "
  			"(== 0x%lx sects)
  ",
97894cda5   Thomas Gleixner   [MTD] core: Clean...
119
  			nftl->cylinders, nftl->heads , nftl->sectors,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
120
121
122
123
124
  			(long)nftl->cylinders * (long)nftl->heads *
  			(long)nftl->sectors );
  	}
  
  	if (add_mtd_blktrans_dev(&nftl->mbd)) {
fa671646f   Jesper Juhl   [PATCH] kfree cle...
125
126
  		kfree(nftl->ReplUnitTable);
  		kfree(nftl->EUNtable);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
  		kfree(nftl);
  		return;
  	}
  #ifdef PSYCHO_DEBUG
  	printk(KERN_INFO "NFTL: Found new nftl%c
  ", nftl->mbd.devnum + 'a');
  #endif
  }
  
  static void nftl_remove_dev(struct mtd_blktrans_dev *dev)
  {
  	struct NFTLrecord *nftl = (void *)dev;
  
  	DEBUG(MTD_DEBUG_LEVEL1, "NFTL: remove_dev (i=%d)
  ", dev->devnum);
  
  	del_mtd_blktrans_dev(dev);
fa671646f   Jesper Juhl   [PATCH] kfree cle...
144
145
  	kfree(nftl->ReplUnitTable);
  	kfree(nftl->EUNtable);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
146
  }
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
147
148
149
150
151
152
  /*
   * 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)
  {
16f05c2b6   Dimitri Gorokhovik   mtd: nftl: fix of...
153
  	loff_t mask = mtd->writesize - 1;
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
154
155
156
157
  	struct mtd_oob_ops ops;
  	int res;
  
  	ops.mode = MTD_OOB_PLACE;
16f05c2b6   Dimitri Gorokhovik   mtd: nftl: fix of...
158
  	ops.ooboffs = offs & mask;
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
159
160
161
  	ops.ooblen = len;
  	ops.oobbuf = buf;
  	ops.datbuf = NULL;
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
162

16f05c2b6   Dimitri Gorokhovik   mtd: nftl: fix of...
163
  	res = mtd->read_oob(mtd, offs & ~mask, &ops);
7014568ba   Vitaly Wool   [MTD] [NAND] remo...
164
  	*retlen = ops.oobretlen;
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
165
166
167
168
169
170
171
172
173
  	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)
  {
16f05c2b6   Dimitri Gorokhovik   mtd: nftl: fix of...
174
  	loff_t mask = mtd->writesize - 1;
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
175
176
177
178
  	struct mtd_oob_ops ops;
  	int res;
  
  	ops.mode = MTD_OOB_PLACE;
16f05c2b6   Dimitri Gorokhovik   mtd: nftl: fix of...
179
  	ops.ooboffs = offs & mask;
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
180
181
182
  	ops.ooblen = len;
  	ops.oobbuf = buf;
  	ops.datbuf = NULL;
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
183

16f05c2b6   Dimitri Gorokhovik   mtd: nftl: fix of...
184
  	res = mtd->write_oob(mtd, offs & ~mask, &ops);
7014568ba   Vitaly Wool   [MTD] [NAND] remo...
185
  	*retlen = ops.oobretlen;
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
186
187
  	return res;
  }
553a80120   Frederik Deweerdt   [MTD] fix nftl_wr...
188
  #ifdef CONFIG_NFTL_RW
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
189
190
191
192
193
194
  /*
   * 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)
  {
16f05c2b6   Dimitri Gorokhovik   mtd: nftl: fix of...
195
  	loff_t mask = mtd->writesize - 1;
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
196
197
198
199
  	struct mtd_oob_ops ops;
  	int res;
  
  	ops.mode = MTD_OOB_PLACE;
16f05c2b6   Dimitri Gorokhovik   mtd: nftl: fix of...
200
  	ops.ooboffs = offs & mask;
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
201
202
203
204
  	ops.ooblen = mtd->oobsize;
  	ops.oobbuf = oob;
  	ops.datbuf = buf;
  	ops.len = len;
16f05c2b6   Dimitri Gorokhovik   mtd: nftl: fix of...
205
  	res = mtd->write_oob(mtd, offs & ~mask, &ops);
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
206
207
208
  	*retlen = ops.retlen;
  	return res;
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
  /* 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) {
  		DEBUG(MTD_DEBUG_LEVEL1, "NFTL_findfreeblock: there are too few free EUNs
  ");
70ec3bb8e   Julia Lawall   mtd: Use BLOCK_NI...
226
  		return BLOCK_NIL;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
  	}
  
  	/* 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, "
97894cda5   Thomas Gleixner   [MTD] core: Clean...
246
247
  			       "FirstEUN = %d
  ", nftl->LastFreeEUN,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
248
  			       le16_to_cpu(nftl->MediaHdr.FirstPhysicalEUN));
70ec3bb8e   Julia Lawall   mtd: Use BLOCK_NI...
249
  			return BLOCK_NIL;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
250
251
  		}
  	} while (pot != nftl->LastFreeEUN);
70ec3bb8e   Julia Lawall   mtd: Use BLOCK_NI...
252
  	return BLOCK_NIL;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
253
254
255
256
  }
  
  static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned pendingblock )
  {
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
257
  	struct mtd_info *mtd = nftl->mbd.mtd;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
258
259
260
261
262
263
264
265
266
  	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;
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
267
  	size_t retlen;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
268
269
270
271
272
273
274
275
276
277
278
279
  
  	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!
  ", thisVUC);
  		return BLOCK_NIL;
  	}
97894cda5   Thomas Gleixner   [MTD] core: Clean...
280

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
281
282
283
  	/* Scan to find the Erase Unit which holds the actual data for each
  	   512-byte block within the Chain.
  	*/
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
284
  	silly = MAX_LOOPS;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
285
286
  	targetEUN = BLOCK_NIL;
  	while (thisEUN <= nftl->lastEUN ) {
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
287
  		unsigned int status, foldmark;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
288
289
290
  
  		targetEUN = thisEUN;
  		for (block = 0; block < nftl->EraseSize / 512; block ++) {
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
291
  			nftl_read_oob(mtd, (thisEUN * nftl->EraseSize) +
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
292
293
  				      (block * 512), 16 , &retlen,
  				      (char *)&oob);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
294
  			if (block == 2) {
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
295
296
297
298
299
  				foldmark = oob.u.c.FoldMark | oob.u.c.FoldMark1;
  				if (foldmark == FOLD_MARK_IN_PROGRESS) {
  					DEBUG(MTD_DEBUG_LEVEL1,
  					      "Write Inhibited on EUN %d
  ", thisEUN);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
300
301
302
303
304
305
306
307
  					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;
  				}
  			}
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
308
  			status = oob.b.Status | oob.b.Status1;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
309
310
311
312
313
314
315
316
317
318
319
  			BlockLastState[block] = status;
  
  			switch(status) {
  			case SECTOR_FREE:
  				BlockFreeFound[block] = 1;
  				break;
  
  			case SECTOR_USED:
  				if (!BlockFreeFound[block])
  					BlockMap[block] = thisEUN;
  				else
97894cda5   Thomas Gleixner   [MTD] core: Clean...
320
  					printk(KERN_WARNING
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
321
322
323
324
325
326
327
328
329
  					       "SECTOR_USED found after SECTOR_FREE "
  					       "in Virtual Unit Chain %d for block %d
  ",
  					       thisVUC, block);
  				break;
  			case SECTOR_DELETED:
  				if (!BlockFreeFound[block])
  					BlockMap[block] = BLOCK_NIL;
  				else
97894cda5   Thomas Gleixner   [MTD] core: Clean...
330
  					printk(KERN_WARNING
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
  					       "SECTOR_DELETED found after SECTOR_FREE "
  					       "in Virtual Unit Chain %d for block %d
  ",
  					       thisVUC, block);
  				break;
  
  			case SECTOR_IGNORE:
  				break;
  			default:
  				printk("Unknown status for block %d in EUN %d: %x
  ",
  				       block, thisEUN, status);
  			}
  		}
  
  		if (!silly--) {
  			printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%x
  ",
  			       thisVUC);
  			return BLOCK_NIL;
  		}
97894cda5   Thomas Gleixner   [MTD] core: Clean...
352

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
353
354
355
356
357
358
  		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
97894cda5   Thomas Gleixner   [MTD] core: Clean...
359
  		   with them (i.e. BlockMap[block] != BLOCK_NIL) are
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
360
361
362
363
364
365
366
367
368
369
370
371
372
  		   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) {
  				DEBUG(MTD_DEBUG_LEVEL1, "Setting inplace to 0. VUC %d, "
  				      "block %d was %x lastEUN, "
  				      "and is in EUN %d (%s) %d
  ",
  				      thisVUC, block, BlockLastState[block],
97894cda5   Thomas Gleixner   [MTD] core: Clean...
373
  				      BlockMap[block],
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
  				      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) {
  			DEBUG(MTD_DEBUG_LEVEL1, "Pending write not free in EUN %d. "
  			      "Folding out of place.
  ", targetEUN);
  			inplace = 0;
  		}
  	}
97894cda5   Thomas Gleixner   [MTD] core: Clean...
391

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
392
393
394
395
396
397
398
  	if (!inplace) {
  		DEBUG(MTD_DEBUG_LEVEL1, "Cannot fold Virtual Unit Chain %d in place. "
  		      "Trying out-of-place
  ", thisVUC);
  		/* We need to find a targetEUN to fold into. */
  		targetEUN = NFTL_findfreeblock(nftl, 1);
  		if (targetEUN == BLOCK_NIL) {
97894cda5   Thomas Gleixner   [MTD] core: Clean...
399
  			/* Ouch. Now we're screwed. We need to do a
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
400
401
  			   fold-in-place of another chain to make room
  			   for this one. We need a better way of selecting
97894cda5   Thomas Gleixner   [MTD] core: Clean...
402
  			   which chain to fold, because makefreeblock will
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
403
404
405
406
407
408
409
410
  			   only ask us to fold the same one again.
  			*/
  			printk(KERN_WARNING
  			       "NFTL_findfreeblock(desperate) returns 0xffff.
  ");
  			return BLOCK_NIL;
  		}
  	} else {
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
411
412
413
414
415
416
  		/* 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;
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
417
  		nftl_write_oob(mtd, (nftl->EraseSize * targetEUN) + 2 * 512 + 8,
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
418
419
  			       8, &retlen, (char *)&oob.u);
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
  
  	/* 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.
  	*/
  	DEBUG(MTD_DEBUG_LEVEL1,"Folding chain %d into unit %d
  ", thisVUC, targetEUN);
  	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;
  		}
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
436
  		/* copy only in non free block (free blocks can only
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
437
                     happen in case of media errors or deleted blocks) */
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
438
439
440
441
442
  		if (BlockMap[block] == BLOCK_NIL)
  			continue;
  
  		ret = mtd->read(mtd, (nftl->EraseSize * BlockMap[block]) + (block * 512),
  				512, &retlen, movebuf);
9a1fcdfd4   Thomas Gleixner   [MTD] NAND Signal...
443
  		if (ret < 0 && ret != -EUCLEAN) {
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
444
445
446
447
448
449
450
  			ret = mtd->read(mtd, (nftl->EraseSize * BlockMap[block])
  					+ (block * 512), 512, &retlen,
  					movebuf);
  			if (ret != -EIO)
  				printk("Error went away on retry.
  ");
  		}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
451
452
  		memset(&oob, 0xff, sizeof(struct nftl_oob));
  		oob.b.Status = oob.b.Status1 = SECTOR_USED;
9223a456d   Thomas Gleixner   [MTD] Remove read...
453

8593fbc68   Thomas Gleixner   [MTD] Rework the ...
454
455
  		nftl_write(nftl->mbd.mtd, (nftl->EraseSize * targetEUN) +
  			   (block * 512), 512, &retlen, movebuf, (char *)&oob);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
456
  	}
97894cda5   Thomas Gleixner   [MTD] core: Clean...
457

f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
458
459
  	/* add the header so that it is now a valid chain */
  	oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC);
70ec3bb8e   Julia Lawall   mtd: Use BLOCK_NI...
460
  	oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum = BLOCK_NIL;
97894cda5   Thomas Gleixner   [MTD] core: Clean...
461

8593fbc68   Thomas Gleixner   [MTD] Rework the ...
462
  	nftl_write_oob(mtd, (nftl->EraseSize * targetEUN) + 8,
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
463
  		       8, &retlen, (char *)&oob.u);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
464
465
  
  	/* OK. We've moved the whole lot into the new block. Now we have to free the original blocks. */
97894cda5   Thomas Gleixner   [MTD] core: Clean...
466
  	/* At this point, we have two different chains for this Virtual Unit, and no way to tell
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
467
468
469
470
471
472
473
  	   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];
  	DEBUG(MTD_DEBUG_LEVEL1,"Want to erase
  ");
97894cda5   Thomas Gleixner   [MTD] core: Clean...
474
  	/* For each block in the old chain (except the targetEUN of course),
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
475
476
477
  	   free it and make it available for future use */
  	while (thisEUN <= nftl->lastEUN && thisEUN != targetEUN) {
  		unsigned int EUNtmp;
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
478
  		EUNtmp = nftl->ReplUnitTable[thisEUN];
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
479

f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
480
  		if (NFTL_formatblock(nftl, thisEUN) < 0) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
481
482
483
  			/* could not erase : mark block as reserved
  			 */
  			nftl->ReplUnitTable[thisEUN] = BLOCK_RESERVED;
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
484
  		} else {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
485
486
487
  			/* correctly erased : mark it as free */
  			nftl->ReplUnitTable[thisEUN] = BLOCK_FREE;
  			nftl->numfreeEUNs++;
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
488
489
  		}
  		thisEUN = EUNtmp;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
490
  	}
97894cda5   Thomas Gleixner   [MTD] core: Clean...
491

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
492
493
494
495
496
497
498
499
500
  	/* 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)
  {
97894cda5   Thomas Gleixner   [MTD] core: Clean...
501
  	/* This is the part that needs some cleverness applied.
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
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
533
534
535
536
537
538
539
540
541
542
543
544
545
  	   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
  ", chain, thislen, EUN);
  			EUN = nftl->ReplUnitTable[EUN] & 0x7fff;
  			if (thislen > 0xff00) {
  				printk("Endless loop in Virtual Chain %d: Unit %x
  ",
  				       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
  ", chain, thislen);
  			ChainLength = thislen;
  			LongestChain = chain;
  		}
  	}
  
  	if (ChainLength < 2) {
  		printk(KERN_WARNING "No Virtual Unit Chains available for folding. "
  		       "Failing request
  ");
70ec3bb8e   Julia Lawall   mtd: Use BLOCK_NI...
546
  		return BLOCK_NIL;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
547
548
549
550
  	}
  
  	return NFTL_foldchain (nftl, LongestChain, pendingblock);
  }
97894cda5   Thomas Gleixner   [MTD] core: Clean...
551
  /* NFTL_findwriteunit: Return the unit number into which we can write
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
552
553
554
555
556
557
                         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);
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
558
  	struct mtd_info *mtd = nftl->mbd.mtd;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
559
560
561
562
563
564
565
566
567
568
  	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.
  		*/
97894cda5   Thomas Gleixner   [MTD] core: Clean...
569
  		/* This condition catches the 0x[7f]fff cases, as well as
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
570
571
572
573
  		   being a sanity check for past-end-of-media access
  		*/
  		lastEUN = BLOCK_NIL;
  		writeEUN = nftl->EUNtable[thisVUC];
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
574
  		silly = MAX_LOOPS;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
575
576
577
  		while (writeEUN <= nftl->lastEUN) {
  			struct nftl_bci bci;
  			size_t retlen;
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
578
  			unsigned int status;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
579
580
  
  			lastEUN = writeEUN;
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
581
  			nftl_read_oob(mtd,
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
582
583
  				      (writeEUN * nftl->EraseSize) + blockofs,
  				      8, &retlen, (char *)&bci);
97894cda5   Thomas Gleixner   [MTD] core: Clean...
584

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
585
586
587
  			DEBUG(MTD_DEBUG_LEVEL2, "Status of block %d in EUN %d is %x
  ",
  			      block , writeEUN, le16_to_cpu(bci.Status));
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
588
  			status = bci.Status | bci.Status1;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
589
590
591
592
593
594
595
596
597
598
  			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.
97894cda5   Thomas Gleixner   [MTD] core: Clean...
599
  				break;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
600
  			}
97894cda5   Thomas Gleixner   [MTD] core: Clean...
601
602
  
  			if (!silly--) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
603
604
605
606
  				printk(KERN_WARNING
  				       "Infinite loop in Virtual Unit Chain 0x%x
  ",
  				       thisVUC);
70ec3bb8e   Julia Lawall   mtd: Use BLOCK_NI...
607
  				return BLOCK_NIL;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
608
609
610
611
612
  			}
  
  			/* Skip to next block in chain */
  			writeEUN = nftl->ReplUnitTable[writeEUN];
  		}
97894cda5   Thomas Gleixner   [MTD] core: Clean...
613
  		/* OK. We didn't find one in the existing chain, or there
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
614
615
616
617
618
619
620
621
622
623
624
625
626
  		   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];
97894cda5   Thomas Gleixner   [MTD] core: Clean...
627

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
628
629
  			//printk("Write to VirtualUnitChain %d, calling makefreeblock()
  ", thisVUC);
70ec3bb8e   Julia Lawall   mtd: Use BLOCK_NI...
630
  			writeEUN = NFTL_makefreeblock(nftl, BLOCK_NIL);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
631
632
  
  			if (writeEUN == BLOCK_NIL) {
97894cda5   Thomas Gleixner   [MTD] core: Clean...
633
  				/* OK, we accept that the above comment is
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
634
635
636
637
638
639
640
641
642
643
644
  				   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.
  				*/
  				DEBUG(MTD_DEBUG_LEVEL1, "Using desperate==1 to find free EUN to accommodate write to VUC %d
  ", thisVUC);
  				writeEUN = NFTL_findfreeblock(nftl, 1);
  			}
  			if (writeEUN == BLOCK_NIL) {
  				/* Ouch. This should never happen - we should
97894cda5   Thomas Gleixner   [MTD] core: Clean...
645
646
  				   always be able to make some room somehow.
  				   If we get here, we've allocated more storage
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
647
648
649
650
651
652
  				   space than actual media, or our makefreeblock
  				   routine is missing something.
  				*/
  				printk(KERN_WARNING "Cannot make free space.
  ");
  				return BLOCK_NIL;
97894cda5   Thomas Gleixner   [MTD] core: Clean...
653
  			}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
654
655
656
657
658
659
660
  			//printk("Restarting scan
  ");
  			lastEUN = BLOCK_NIL;
  			continue;
  		}
  
  		/* We've found a free block. Insert it into the chain. */
97894cda5   Thomas Gleixner   [MTD] core: Clean...
661

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
662
  		if (lastEUN != BLOCK_NIL) {
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
663
  			thisVUC |= 0x8000; /* It's a replacement block */
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
664
  		} else {
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
665
666
  			/* The first block in a new chain */
  			nftl->EUNtable[thisVUC] = writeEUN;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
667
668
669
670
671
672
673
  		}
  
  		/* set up the actual EUN we're writing into */
  		/* Both in our cache... */
  		nftl->ReplUnitTable[writeEUN] = BLOCK_NIL;
  
  		/* ... and on the flash itself */
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
674
  		nftl_read_oob(mtd, writeEUN * nftl->EraseSize + 8, 8,
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
675
  			      &retlen, (char *)&oob.u);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
676
677
  
  		oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC);
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
678
  		nftl_write_oob(mtd, writeEUN * nftl->EraseSize + 8, 8,
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
679
  			       &retlen, (char *)&oob.u);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
680

f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
681
  		/* we link the new block to the chain only after the
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
682
683
                     block is ready. It avoids the case where the chain
                     could point to a free block */
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
684
  		if (lastEUN != BLOCK_NIL) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
685
686
687
  			/* Both in our cache... */
  			nftl->ReplUnitTable[lastEUN] = writeEUN;
  			/* ... and on the flash itself */
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
688
  			nftl_read_oob(mtd, (lastEUN * nftl->EraseSize) + 8,
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
689
  				      8, &retlen, (char *)&oob.u);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
690
691
692
  
  			oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum
  				= cpu_to_le16(writeEUN);
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
693
  			nftl_write_oob(mtd, (lastEUN * nftl->EraseSize) + 8,
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
694
  				       8, &retlen, (char *)&oob.u);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
695
696
697
698
699
700
701
702
703
  		}
  
  		return writeEUN;
  
  	} while (silly2--);
  
  	printk(KERN_WARNING "Error folding to make room for Virtual Unit Chain 0x%x
  ",
  	       thisVUC);
70ec3bb8e   Julia Lawall   mtd: Use BLOCK_NI...
704
  	return BLOCK_NIL;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
  }
  
  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
  ");
  		/* 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;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
728

8593fbc68   Thomas Gleixner   [MTD] Rework the ...
729
730
  	nftl_write(nftl->mbd.mtd, (writeEUN * nftl->EraseSize) + blockofs,
  		   512, &retlen, (char *)buffer, (char *)&oob);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
731
732
733
734
735
736
737
738
  	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;
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
739
  	struct mtd_info *mtd = nftl->mbd.mtd;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
740
741
742
  	u16 lastgoodEUN;
  	u16 thisEUN = nftl->EUNtable[block / (nftl->EraseSize / 512)];
  	unsigned long blockofs = (block * 512) & (nftl->EraseSize - 1);
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
743
  	unsigned int status;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
744
  	int silly = MAX_LOOPS;
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
745
746
  	size_t retlen;
  	struct nftl_bci bci;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
747
748
  
  	lastgoodEUN = BLOCK_NIL;
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
749
  	if (thisEUN != BLOCK_NIL) {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
750
  		while (thisEUN < nftl->nb_blocks) {
8593fbc68   Thomas Gleixner   [MTD] Rework the ...
751
  			if (nftl_read_oob(mtd, (thisEUN * nftl->EraseSize) +
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
752
753
  					  blockofs, 8, &retlen,
  					  (char *)&bci) < 0)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
  				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
  ",
  				       block, thisEUN, status);
  				break;
  			}
  
  			if (!silly--) {
  				printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%lx
  ",
  				       block / (nftl->EraseSize / 512));
  				return 1;
  			}
  			thisEUN = nftl->ReplUnitTable[thisEUN];
  		}
f4a43cfce   Thomas Gleixner   [MTD] Remove sill...
785
  	}
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
786
787
788
789
790
791
792
793
  
   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;
9a1fcdfd4   Thomas Gleixner   [MTD] NAND Signal...
794
795
796
  		int res = mtd->read(mtd, ptr, 512, &retlen, buffer);
  
  		if (res < 0 && res != -EUCLEAN)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
  			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,
191876729   Richard Purdie   [MTD] Allow varia...
824
  	.blksize 	= 512,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
825
826
827
828
829
830
831
832
833
  	.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,
  };
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
834
835
  static int __init init_nftl(void)
  {
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
836
837
838
839
840
841
842
843
844
845
846
847
848
849
  	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");
e7f521636   Scott James Remnant   [MTD] Auto-load n...
850
  MODULE_ALIAS_BLOCKDEV_MAJOR(NFTL_MAJOR);