blob: f3b3239746c8464aad7b68a15f104cb64b530d62 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
David Woodhousea1452a32010-08-08 20:58:20 +01002 * Linux driver for NAND Flash Translation Layer
3 *
4 * Copyright © 1999 Machine Vision Holdings, Inc.
5 * Copyright © 1999-2010 David Woodhouse <dwmw2@infradead.org>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Linus Torvalds1da177e2005-04-16 15:20:36 -070020 */
21
22#define PRERELEASE
23
Linus Torvalds1da177e2005-04-16 15:20:36 -070024#include <linux/kernel.h>
25#include <linux/module.h>
26#include <asm/errno.h>
27#include <asm/io.h>
28#include <asm/uaccess.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070029#include <linux/delay.h>
30#include <linux/slab.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070031#include <linux/init.h>
32#include <linux/hdreg.h>
Scott James Remnante7f52162009-03-02 17:43:04 +000033#include <linux/blkdev.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070034
35#include <linux/kmod.h>
36#include <linux/mtd/mtd.h>
37#include <linux/mtd/nand.h>
38#include <linux/mtd/nftl.h>
39#include <linux/mtd/blktrans.h>
40
41/* maximum number of loops while examining next block, to have a
42 chance to detect consistency problems (they should never happen
43 because of the checks done in the mounting */
44
45#define MAX_LOOPS 10000
46
47
48static void nftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
49{
50 struct NFTLrecord *nftl;
51 unsigned long temp;
52
Adrian Hunter69423d92008-12-10 13:37:21 +000053 if (mtd->type != MTD_NANDFLASH || mtd->size > UINT_MAX)
Linus Torvalds1da177e2005-04-16 15:20:36 -070054 return;
55 /* OK, this is moderately ugly. But probably safe. Alternatives? */
56 if (memcmp(mtd->name, "DiskOnChip", 10))
57 return;
58
59 if (!mtd->block_isbad) {
60 printk(KERN_ERR
61"NFTL no longer supports the old DiskOnChip drivers loaded via docprobe.\n"
62"Please use the new diskonchip driver under the NAND subsystem.\n");
63 return;
64 }
65
66 DEBUG(MTD_DEBUG_LEVEL1, "NFTL: add_mtd for %s\n", mtd->name);
67
Burman Yan95b93a02006-11-15 21:10:29 +020068 nftl = kzalloc(sizeof(struct NFTLrecord), GFP_KERNEL);
Linus Torvalds1da177e2005-04-16 15:20:36 -070069
Brian Norris08700662011-06-07 16:01:54 -070070 if (!nftl)
Linus Torvalds1da177e2005-04-16 15:20:36 -070071 return;
Linus Torvalds1da177e2005-04-16 15:20:36 -070072
73 nftl->mbd.mtd = mtd;
74 nftl->mbd.devnum = -1;
Richard Purdie19187672006-10-27 09:09:33 +010075
Linus Torvalds1da177e2005-04-16 15:20:36 -070076 nftl->mbd.tr = tr;
Linus Torvalds1da177e2005-04-16 15:20:36 -070077
78 if (NFTL_mount(nftl) < 0) {
79 printk(KERN_WARNING "NFTL: could not mount device\n");
80 kfree(nftl);
81 return;
82 }
83
84 /* OK, it's a new one. Set up all the data structures. */
85
86 /* Calculate geometry */
87 nftl->cylinders = 1024;
88 nftl->heads = 16;
89
90 temp = nftl->cylinders * nftl->heads;
91 nftl->sectors = nftl->mbd.size / temp;
92 if (nftl->mbd.size % temp) {
93 nftl->sectors++;
94 temp = nftl->cylinders * nftl->sectors;
95 nftl->heads = nftl->mbd.size / temp;
96
97 if (nftl->mbd.size % temp) {
98 nftl->heads++;
99 temp = nftl->heads * nftl->sectors;
100 nftl->cylinders = nftl->mbd.size / temp;
101 }
102 }
103
104 if (nftl->mbd.size != nftl->heads * nftl->cylinders * nftl->sectors) {
105 /*
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000106 Oh no we don't have
Linus Torvalds1da177e2005-04-16 15:20:36 -0700107 mbd.size == heads * cylinders * sectors
108 */
109 printk(KERN_WARNING "NFTL: cannot calculate a geometry to "
110 "match size of 0x%lx.\n", nftl->mbd.size);
111 printk(KERN_WARNING "NFTL: using C:%d H:%d S:%d "
112 "(== 0x%lx sects)\n",
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000113 nftl->cylinders, nftl->heads , nftl->sectors,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700114 (long)nftl->cylinders * (long)nftl->heads *
115 (long)nftl->sectors );
116 }
117
118 if (add_mtd_blktrans_dev(&nftl->mbd)) {
Jesper Juhlfa671642005-11-07 01:01:27 -0800119 kfree(nftl->ReplUnitTable);
120 kfree(nftl->EUNtable);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700121 kfree(nftl);
122 return;
123 }
124#ifdef PSYCHO_DEBUG
125 printk(KERN_INFO "NFTL: Found new nftl%c\n", nftl->mbd.devnum + 'a');
126#endif
127}
128
129static void nftl_remove_dev(struct mtd_blktrans_dev *dev)
130{
131 struct NFTLrecord *nftl = (void *)dev;
132
133 DEBUG(MTD_DEBUG_LEVEL1, "NFTL: remove_dev (i=%d)\n", dev->devnum);
134
135 del_mtd_blktrans_dev(dev);
Jesper Juhlfa671642005-11-07 01:01:27 -0800136 kfree(nftl->ReplUnitTable);
137 kfree(nftl->EUNtable);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700138}
139
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200140/*
141 * Read oob data from flash
142 */
143int nftl_read_oob(struct mtd_info *mtd, loff_t offs, size_t len,
144 size_t *retlen, uint8_t *buf)
145{
Dimitri Gorokhovik16f05c22009-09-03 14:04:22 +0100146 loff_t mask = mtd->writesize - 1;
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200147 struct mtd_oob_ops ops;
148 int res;
149
150 ops.mode = MTD_OOB_PLACE;
Dimitri Gorokhovik16f05c22009-09-03 14:04:22 +0100151 ops.ooboffs = offs & mask;
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200152 ops.ooblen = len;
153 ops.oobbuf = buf;
154 ops.datbuf = NULL;
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200155
Dimitri Gorokhovik16f05c22009-09-03 14:04:22 +0100156 res = mtd->read_oob(mtd, offs & ~mask, &ops);
Vitaly Wool70145682006-11-03 18:20:38 +0300157 *retlen = ops.oobretlen;
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200158 return res;
159}
160
161/*
162 * Write oob data to flash
163 */
164int nftl_write_oob(struct mtd_info *mtd, loff_t offs, size_t len,
165 size_t *retlen, uint8_t *buf)
166{
Dimitri Gorokhovik16f05c22009-09-03 14:04:22 +0100167 loff_t mask = mtd->writesize - 1;
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200168 struct mtd_oob_ops ops;
169 int res;
170
171 ops.mode = MTD_OOB_PLACE;
Dimitri Gorokhovik16f05c22009-09-03 14:04:22 +0100172 ops.ooboffs = offs & mask;
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200173 ops.ooblen = len;
174 ops.oobbuf = buf;
175 ops.datbuf = NULL;
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200176
Dimitri Gorokhovik16f05c22009-09-03 14:04:22 +0100177 res = mtd->write_oob(mtd, offs & ~mask, &ops);
Vitaly Wool70145682006-11-03 18:20:38 +0300178 *retlen = ops.oobretlen;
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200179 return res;
180}
181
Frederik Deweerdt553a8012006-10-02 09:42:25 +0100182#ifdef CONFIG_NFTL_RW
183
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200184/*
185 * Write data and oob to flash
186 */
187static int nftl_write(struct mtd_info *mtd, loff_t offs, size_t len,
188 size_t *retlen, uint8_t *buf, uint8_t *oob)
189{
Dimitri Gorokhovik16f05c22009-09-03 14:04:22 +0100190 loff_t mask = mtd->writesize - 1;
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200191 struct mtd_oob_ops ops;
192 int res;
193
194 ops.mode = MTD_OOB_PLACE;
Dimitri Gorokhovik16f05c22009-09-03 14:04:22 +0100195 ops.ooboffs = offs & mask;
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200196 ops.ooblen = mtd->oobsize;
197 ops.oobbuf = oob;
198 ops.datbuf = buf;
199 ops.len = len;
200
Dimitri Gorokhovik16f05c22009-09-03 14:04:22 +0100201 res = mtd->write_oob(mtd, offs & ~mask, &ops);
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200202 *retlen = ops.retlen;
203 return res;
204}
205
Linus Torvalds1da177e2005-04-16 15:20:36 -0700206/* Actual NFTL access routines */
207/* NFTL_findfreeblock: Find a free Erase Unit on the NFTL partition. This function is used
208 * when the give Virtual Unit Chain
209 */
210static u16 NFTL_findfreeblock(struct NFTLrecord *nftl, int desperate )
211{
212 /* For a given Virtual Unit Chain: find or create a free block and
213 add it to the chain */
214 /* We're passed the number of the last EUN in the chain, to save us from
215 having to look it up again */
216 u16 pot = nftl->LastFreeEUN;
217 int silly = nftl->nb_blocks;
218
219 /* Normally, we force a fold to happen before we run out of free blocks completely */
220 if (!desperate && nftl->numfreeEUNs < 2) {
221 DEBUG(MTD_DEBUG_LEVEL1, "NFTL_findfreeblock: there are too few free EUNs\n");
Julia Lawall70ec3bb2009-06-27 09:55:32 +0200222 return BLOCK_NIL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700223 }
224
225 /* Scan for a free block */
226 do {
227 if (nftl->ReplUnitTable[pot] == BLOCK_FREE) {
228 nftl->LastFreeEUN = pot;
229 nftl->numfreeEUNs--;
230 return pot;
231 }
232
233 /* This will probably point to the MediaHdr unit itself,
234 right at the beginning of the partition. But that unit
235 (and the backup unit too) should have the UCI set
236 up so that it's not selected for overwriting */
237 if (++pot > nftl->lastEUN)
238 pot = le16_to_cpu(nftl->MediaHdr.FirstPhysicalEUN);
239
240 if (!silly--) {
241 printk("Argh! No free blocks found! LastFreeEUN = %d, "
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000242 "FirstEUN = %d\n", nftl->LastFreeEUN,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700243 le16_to_cpu(nftl->MediaHdr.FirstPhysicalEUN));
Julia Lawall70ec3bb2009-06-27 09:55:32 +0200244 return BLOCK_NIL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700245 }
246 } while (pot != nftl->LastFreeEUN);
247
Julia Lawall70ec3bb2009-06-27 09:55:32 +0200248 return BLOCK_NIL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700249}
250
251static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned pendingblock )
252{
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200253 struct mtd_info *mtd = nftl->mbd.mtd;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700254 u16 BlockMap[MAX_SECTORS_PER_UNIT];
255 unsigned char BlockLastState[MAX_SECTORS_PER_UNIT];
256 unsigned char BlockFreeFound[MAX_SECTORS_PER_UNIT];
257 unsigned int thisEUN;
258 int block;
259 int silly;
260 unsigned int targetEUN;
261 struct nftl_oob oob;
262 int inplace = 1;
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200263 size_t retlen;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700264
265 memset(BlockMap, 0xff, sizeof(BlockMap));
266 memset(BlockFreeFound, 0, sizeof(BlockFreeFound));
267
268 thisEUN = nftl->EUNtable[thisVUC];
269
270 if (thisEUN == BLOCK_NIL) {
271 printk(KERN_WARNING "Trying to fold non-existent "
272 "Virtual Unit Chain %d!\n", thisVUC);
273 return BLOCK_NIL;
274 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000275
Linus Torvalds1da177e2005-04-16 15:20:36 -0700276 /* Scan to find the Erase Unit which holds the actual data for each
277 512-byte block within the Chain.
278 */
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200279 silly = MAX_LOOPS;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700280 targetEUN = BLOCK_NIL;
281 while (thisEUN <= nftl->lastEUN ) {
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200282 unsigned int status, foldmark;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700283
284 targetEUN = thisEUN;
285 for (block = 0; block < nftl->EraseSize / 512; block ++) {
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200286 nftl_read_oob(mtd, (thisEUN * nftl->EraseSize) +
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200287 (block * 512), 16 , &retlen,
288 (char *)&oob);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700289 if (block == 2) {
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200290 foldmark = oob.u.c.FoldMark | oob.u.c.FoldMark1;
291 if (foldmark == FOLD_MARK_IN_PROGRESS) {
292 DEBUG(MTD_DEBUG_LEVEL1,
293 "Write Inhibited on EUN %d\n", thisEUN);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700294 inplace = 0;
295 } else {
296 /* There's no other reason not to do inplace,
297 except ones that come later. So we don't need
298 to preserve inplace */
299 inplace = 1;
300 }
301 }
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200302 status = oob.b.Status | oob.b.Status1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700303 BlockLastState[block] = status;
304
305 switch(status) {
306 case SECTOR_FREE:
307 BlockFreeFound[block] = 1;
308 break;
309
310 case SECTOR_USED:
311 if (!BlockFreeFound[block])
312 BlockMap[block] = thisEUN;
313 else
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000314 printk(KERN_WARNING
Linus Torvalds1da177e2005-04-16 15:20:36 -0700315 "SECTOR_USED found after SECTOR_FREE "
316 "in Virtual Unit Chain %d for block %d\n",
317 thisVUC, block);
318 break;
319 case SECTOR_DELETED:
320 if (!BlockFreeFound[block])
321 BlockMap[block] = BLOCK_NIL;
322 else
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000323 printk(KERN_WARNING
Linus Torvalds1da177e2005-04-16 15:20:36 -0700324 "SECTOR_DELETED found after SECTOR_FREE "
325 "in Virtual Unit Chain %d for block %d\n",
326 thisVUC, block);
327 break;
328
329 case SECTOR_IGNORE:
330 break;
331 default:
332 printk("Unknown status for block %d in EUN %d: %x\n",
333 block, thisEUN, status);
334 }
335 }
336
337 if (!silly--) {
338 printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%x\n",
339 thisVUC);
340 return BLOCK_NIL;
341 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000342
Linus Torvalds1da177e2005-04-16 15:20:36 -0700343 thisEUN = nftl->ReplUnitTable[thisEUN];
344 }
345
346 if (inplace) {
347 /* We're being asked to be a fold-in-place. Check
348 that all blocks which actually have data associated
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000349 with them (i.e. BlockMap[block] != BLOCK_NIL) are
Linus Torvalds1da177e2005-04-16 15:20:36 -0700350 either already present or SECTOR_FREE in the target
351 block. If not, we're going to have to fold out-of-place
352 anyway.
353 */
354 for (block = 0; block < nftl->EraseSize / 512 ; block++) {
355 if (BlockLastState[block] != SECTOR_FREE &&
356 BlockMap[block] != BLOCK_NIL &&
357 BlockMap[block] != targetEUN) {
358 DEBUG(MTD_DEBUG_LEVEL1, "Setting inplace to 0. VUC %d, "
359 "block %d was %x lastEUN, "
360 "and is in EUN %d (%s) %d\n",
361 thisVUC, block, BlockLastState[block],
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000362 BlockMap[block],
Linus Torvalds1da177e2005-04-16 15:20:36 -0700363 BlockMap[block]== targetEUN ? "==" : "!=",
364 targetEUN);
365 inplace = 0;
366 break;
367 }
368 }
369
370 if (pendingblock >= (thisVUC * (nftl->EraseSize / 512)) &&
371 pendingblock < ((thisVUC + 1)* (nftl->EraseSize / 512)) &&
372 BlockLastState[pendingblock - (thisVUC * (nftl->EraseSize / 512))] !=
373 SECTOR_FREE) {
374 DEBUG(MTD_DEBUG_LEVEL1, "Pending write not free in EUN %d. "
375 "Folding out of place.\n", targetEUN);
376 inplace = 0;
377 }
378 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000379
Linus Torvalds1da177e2005-04-16 15:20:36 -0700380 if (!inplace) {
381 DEBUG(MTD_DEBUG_LEVEL1, "Cannot fold Virtual Unit Chain %d in place. "
382 "Trying out-of-place\n", thisVUC);
383 /* We need to find a targetEUN to fold into. */
384 targetEUN = NFTL_findfreeblock(nftl, 1);
385 if (targetEUN == BLOCK_NIL) {
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000386 /* Ouch. Now we're screwed. We need to do a
Linus Torvalds1da177e2005-04-16 15:20:36 -0700387 fold-in-place of another chain to make room
388 for this one. We need a better way of selecting
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000389 which chain to fold, because makefreeblock will
Linus Torvalds1da177e2005-04-16 15:20:36 -0700390 only ask us to fold the same one again.
391 */
392 printk(KERN_WARNING
393 "NFTL_findfreeblock(desperate) returns 0xffff.\n");
394 return BLOCK_NIL;
395 }
396 } else {
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200397 /* We put a fold mark in the chain we are folding only if we
398 fold in place to help the mount check code. If we do not fold in
399 place, it is possible to find the valid chain by selecting the
400 longer one */
401 oob.u.c.FoldMark = oob.u.c.FoldMark1 = cpu_to_le16(FOLD_MARK_IN_PROGRESS);
402 oob.u.c.unused = 0xffffffff;
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200403 nftl_write_oob(mtd, (nftl->EraseSize * targetEUN) + 2 * 512 + 8,
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200404 8, &retlen, (char *)&oob.u);
405 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700406
407 /* OK. We now know the location of every block in the Virtual Unit Chain,
408 and the Erase Unit into which we are supposed to be copying.
409 Go for it.
410 */
411 DEBUG(MTD_DEBUG_LEVEL1,"Folding chain %d into unit %d\n", thisVUC, targetEUN);
412 for (block = 0; block < nftl->EraseSize / 512 ; block++) {
413 unsigned char movebuf[512];
414 int ret;
415
416 /* If it's in the target EUN already, or if it's pending write, do nothing */
417 if (BlockMap[block] == targetEUN ||
418 (pendingblock == (thisVUC * (nftl->EraseSize / 512) + block))) {
419 continue;
420 }
421
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200422 /* copy only in non free block (free blocks can only
Linus Torvalds1da177e2005-04-16 15:20:36 -0700423 happen in case of media errors or deleted blocks) */
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200424 if (BlockMap[block] == BLOCK_NIL)
425 continue;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000426
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200427 ret = mtd->read(mtd, (nftl->EraseSize * BlockMap[block]) + (block * 512),
428 512, &retlen, movebuf);
Thomas Gleixner9a1fcdf2006-05-29 14:56:39 +0200429 if (ret < 0 && ret != -EUCLEAN) {
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200430 ret = mtd->read(mtd, (nftl->EraseSize * BlockMap[block])
431 + (block * 512), 512, &retlen,
432 movebuf);
433 if (ret != -EIO)
434 printk("Error went away on retry.\n");
435 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700436 memset(&oob, 0xff, sizeof(struct nftl_oob));
437 oob.b.Status = oob.b.Status1 = SECTOR_USED;
Thomas Gleixner9223a452006-05-23 17:21:03 +0200438
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200439 nftl_write(nftl->mbd.mtd, (nftl->EraseSize * targetEUN) +
440 (block * 512), 512, &retlen, movebuf, (char *)&oob);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700441 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000442
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200443 /* add the header so that it is now a valid chain */
444 oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC);
Julia Lawall70ec3bb2009-06-27 09:55:32 +0200445 oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum = BLOCK_NIL;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000446
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200447 nftl_write_oob(mtd, (nftl->EraseSize * targetEUN) + 8,
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200448 8, &retlen, (char *)&oob.u);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700449
450 /* OK. We've moved the whole lot into the new block. Now we have to free the original blocks. */
451
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000452 /* At this point, we have two different chains for this Virtual Unit, and no way to tell
Linus Torvalds1da177e2005-04-16 15:20:36 -0700453 them apart. If we crash now, we get confused. However, both contain the same data, so we
454 shouldn't actually lose data in this case. It's just that when we load up on a medium which
455 has duplicate chains, we need to free one of the chains because it's not necessary any more.
456 */
457 thisEUN = nftl->EUNtable[thisVUC];
458 DEBUG(MTD_DEBUG_LEVEL1,"Want to erase\n");
459
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000460 /* For each block in the old chain (except the targetEUN of course),
Linus Torvalds1da177e2005-04-16 15:20:36 -0700461 free it and make it available for future use */
462 while (thisEUN <= nftl->lastEUN && thisEUN != targetEUN) {
463 unsigned int EUNtmp;
464
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200465 EUNtmp = nftl->ReplUnitTable[thisEUN];
Linus Torvalds1da177e2005-04-16 15:20:36 -0700466
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200467 if (NFTL_formatblock(nftl, thisEUN) < 0) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700468 /* could not erase : mark block as reserved
469 */
470 nftl->ReplUnitTable[thisEUN] = BLOCK_RESERVED;
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200471 } else {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700472 /* correctly erased : mark it as free */
473 nftl->ReplUnitTable[thisEUN] = BLOCK_FREE;
474 nftl->numfreeEUNs++;
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200475 }
476 thisEUN = EUNtmp;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700477 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000478
Linus Torvalds1da177e2005-04-16 15:20:36 -0700479 /* Make this the new start of chain for thisVUC */
480 nftl->ReplUnitTable[targetEUN] = BLOCK_NIL;
481 nftl->EUNtable[thisVUC] = targetEUN;
482
483 return targetEUN;
484}
485
486static u16 NFTL_makefreeblock( struct NFTLrecord *nftl , unsigned pendingblock)
487{
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000488 /* This is the part that needs some cleverness applied.
Linus Torvalds1da177e2005-04-16 15:20:36 -0700489 For now, I'm doing the minimum applicable to actually
490 get the thing to work.
491 Wear-levelling and other clever stuff needs to be implemented
492 and we also need to do some assessment of the results when
493 the system loses power half-way through the routine.
494 */
495 u16 LongestChain = 0;
496 u16 ChainLength = 0, thislen;
497 u16 chain, EUN;
498
499 for (chain = 0; chain < le32_to_cpu(nftl->MediaHdr.FormattedSize) / nftl->EraseSize; chain++) {
500 EUN = nftl->EUNtable[chain];
501 thislen = 0;
502
503 while (EUN <= nftl->lastEUN) {
504 thislen++;
505 //printk("VUC %d reaches len %d with EUN %d\n", chain, thislen, EUN);
506 EUN = nftl->ReplUnitTable[EUN] & 0x7fff;
507 if (thislen > 0xff00) {
508 printk("Endless loop in Virtual Chain %d: Unit %x\n",
509 chain, EUN);
510 }
511 if (thislen > 0xff10) {
512 /* Actually, don't return failure. Just ignore this chain and
513 get on with it. */
514 thislen = 0;
515 break;
516 }
517 }
518
519 if (thislen > ChainLength) {
520 //printk("New longest chain is %d with length %d\n", chain, thislen);
521 ChainLength = thislen;
522 LongestChain = chain;
523 }
524 }
525
526 if (ChainLength < 2) {
527 printk(KERN_WARNING "No Virtual Unit Chains available for folding. "
528 "Failing request\n");
Julia Lawall70ec3bb2009-06-27 09:55:32 +0200529 return BLOCK_NIL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700530 }
531
532 return NFTL_foldchain (nftl, LongestChain, pendingblock);
533}
534
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000535/* NFTL_findwriteunit: Return the unit number into which we can write
Linus Torvalds1da177e2005-04-16 15:20:36 -0700536 for this block. Make it available if it isn't already
537*/
538static inline u16 NFTL_findwriteunit(struct NFTLrecord *nftl, unsigned block)
539{
540 u16 lastEUN;
541 u16 thisVUC = block / (nftl->EraseSize / 512);
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200542 struct mtd_info *mtd = nftl->mbd.mtd;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700543 unsigned int writeEUN;
544 unsigned long blockofs = (block * 512) & (nftl->EraseSize -1);
545 size_t retlen;
546 int silly, silly2 = 3;
547 struct nftl_oob oob;
548
549 do {
550 /* Scan the media to find a unit in the VUC which has
551 a free space for the block in question.
552 */
553
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000554 /* This condition catches the 0x[7f]fff cases, as well as
Linus Torvalds1da177e2005-04-16 15:20:36 -0700555 being a sanity check for past-end-of-media access
556 */
557 lastEUN = BLOCK_NIL;
558 writeEUN = nftl->EUNtable[thisVUC];
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200559 silly = MAX_LOOPS;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700560 while (writeEUN <= nftl->lastEUN) {
561 struct nftl_bci bci;
562 size_t retlen;
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200563 unsigned int status;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700564
565 lastEUN = writeEUN;
566
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200567 nftl_read_oob(mtd,
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200568 (writeEUN * nftl->EraseSize) + blockofs,
569 8, &retlen, (char *)&bci);
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000570
Linus Torvalds1da177e2005-04-16 15:20:36 -0700571 DEBUG(MTD_DEBUG_LEVEL2, "Status of block %d in EUN %d is %x\n",
572 block , writeEUN, le16_to_cpu(bci.Status));
573
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200574 status = bci.Status | bci.Status1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700575 switch(status) {
576 case SECTOR_FREE:
577 return writeEUN;
578
579 case SECTOR_DELETED:
580 case SECTOR_USED:
581 case SECTOR_IGNORE:
582 break;
583 default:
584 // Invalid block. Don't use it any more. Must implement.
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000585 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700586 }
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000587
588 if (!silly--) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700589 printk(KERN_WARNING
590 "Infinite loop in Virtual Unit Chain 0x%x\n",
591 thisVUC);
Julia Lawall70ec3bb2009-06-27 09:55:32 +0200592 return BLOCK_NIL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700593 }
594
595 /* Skip to next block in chain */
596 writeEUN = nftl->ReplUnitTable[writeEUN];
597 }
598
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000599 /* OK. We didn't find one in the existing chain, or there
Linus Torvalds1da177e2005-04-16 15:20:36 -0700600 is no existing chain. */
601
602 /* Try to find an already-free block */
603 writeEUN = NFTL_findfreeblock(nftl, 0);
604
605 if (writeEUN == BLOCK_NIL) {
606 /* That didn't work - there were no free blocks just
607 waiting to be picked up. We're going to have to fold
608 a chain to make room.
609 */
610
611 /* First remember the start of this chain */
612 //u16 startEUN = nftl->EUNtable[thisVUC];
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000613
Linus Torvalds1da177e2005-04-16 15:20:36 -0700614 //printk("Write to VirtualUnitChain %d, calling makefreeblock()\n", thisVUC);
Julia Lawall70ec3bb2009-06-27 09:55:32 +0200615 writeEUN = NFTL_makefreeblock(nftl, BLOCK_NIL);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700616
617 if (writeEUN == BLOCK_NIL) {
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000618 /* OK, we accept that the above comment is
Linus Torvalds1da177e2005-04-16 15:20:36 -0700619 lying - there may have been free blocks
620 last time we called NFTL_findfreeblock(),
621 but they are reserved for when we're
622 desperate. Well, now we're desperate.
623 */
624 DEBUG(MTD_DEBUG_LEVEL1, "Using desperate==1 to find free EUN to accommodate write to VUC %d\n", thisVUC);
625 writeEUN = NFTL_findfreeblock(nftl, 1);
626 }
627 if (writeEUN == BLOCK_NIL) {
628 /* Ouch. This should never happen - we should
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000629 always be able to make some room somehow.
630 If we get here, we've allocated more storage
Linus Torvalds1da177e2005-04-16 15:20:36 -0700631 space than actual media, or our makefreeblock
632 routine is missing something.
633 */
634 printk(KERN_WARNING "Cannot make free space.\n");
635 return BLOCK_NIL;
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000636 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700637 //printk("Restarting scan\n");
638 lastEUN = BLOCK_NIL;
639 continue;
640 }
641
642 /* We've found a free block. Insert it into the chain. */
Thomas Gleixner97894cd2005-11-07 11:15:26 +0000643
Linus Torvalds1da177e2005-04-16 15:20:36 -0700644 if (lastEUN != BLOCK_NIL) {
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200645 thisVUC |= 0x8000; /* It's a replacement block */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700646 } else {
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200647 /* The first block in a new chain */
648 nftl->EUNtable[thisVUC] = writeEUN;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700649 }
650
651 /* set up the actual EUN we're writing into */
652 /* Both in our cache... */
653 nftl->ReplUnitTable[writeEUN] = BLOCK_NIL;
654
655 /* ... and on the flash itself */
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200656 nftl_read_oob(mtd, writeEUN * nftl->EraseSize + 8, 8,
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200657 &retlen, (char *)&oob.u);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700658
659 oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC);
660
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200661 nftl_write_oob(mtd, writeEUN * nftl->EraseSize + 8, 8,
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200662 &retlen, (char *)&oob.u);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700663
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200664 /* we link the new block to the chain only after the
Linus Torvalds1da177e2005-04-16 15:20:36 -0700665 block is ready. It avoids the case where the chain
666 could point to a free block */
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200667 if (lastEUN != BLOCK_NIL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700668 /* Both in our cache... */
669 nftl->ReplUnitTable[lastEUN] = writeEUN;
670 /* ... and on the flash itself */
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200671 nftl_read_oob(mtd, (lastEUN * nftl->EraseSize) + 8,
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200672 8, &retlen, (char *)&oob.u);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700673
674 oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum
675 = cpu_to_le16(writeEUN);
676
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200677 nftl_write_oob(mtd, (lastEUN * nftl->EraseSize) + 8,
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200678 8, &retlen, (char *)&oob.u);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700679 }
680
681 return writeEUN;
682
683 } while (silly2--);
684
685 printk(KERN_WARNING "Error folding to make room for Virtual Unit Chain 0x%x\n",
686 thisVUC);
Julia Lawall70ec3bb2009-06-27 09:55:32 +0200687 return BLOCK_NIL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700688}
689
690static int nftl_writeblock(struct mtd_blktrans_dev *mbd, unsigned long block,
691 char *buffer)
692{
693 struct NFTLrecord *nftl = (void *)mbd;
694 u16 writeEUN;
695 unsigned long blockofs = (block * 512) & (nftl->EraseSize - 1);
696 size_t retlen;
697 struct nftl_oob oob;
698
699 writeEUN = NFTL_findwriteunit(nftl, block);
700
701 if (writeEUN == BLOCK_NIL) {
702 printk(KERN_WARNING
703 "NFTL_writeblock(): Cannot find block to write to\n");
704 /* If we _still_ haven't got a block to use, we're screwed */
705 return 1;
706 }
707
708 memset(&oob, 0xff, sizeof(struct nftl_oob));
709 oob.b.Status = oob.b.Status1 = SECTOR_USED;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700710
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200711 nftl_write(nftl->mbd.mtd, (writeEUN * nftl->EraseSize) + blockofs,
712 512, &retlen, (char *)buffer, (char *)&oob);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700713 return 0;
714}
715#endif /* CONFIG_NFTL_RW */
716
717static int nftl_readblock(struct mtd_blktrans_dev *mbd, unsigned long block,
718 char *buffer)
719{
720 struct NFTLrecord *nftl = (void *)mbd;
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200721 struct mtd_info *mtd = nftl->mbd.mtd;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700722 u16 lastgoodEUN;
723 u16 thisEUN = nftl->EUNtable[block / (nftl->EraseSize / 512)];
724 unsigned long blockofs = (block * 512) & (nftl->EraseSize - 1);
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200725 unsigned int status;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700726 int silly = MAX_LOOPS;
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200727 size_t retlen;
728 struct nftl_bci bci;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700729
730 lastgoodEUN = BLOCK_NIL;
731
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200732 if (thisEUN != BLOCK_NIL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700733 while (thisEUN < nftl->nb_blocks) {
Thomas Gleixner8593fbc2006-05-29 03:26:58 +0200734 if (nftl_read_oob(mtd, (thisEUN * nftl->EraseSize) +
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200735 blockofs, 8, &retlen,
736 (char *)&bci) < 0)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700737 status = SECTOR_IGNORE;
738 else
739 status = bci.Status | bci.Status1;
740
741 switch (status) {
742 case SECTOR_FREE:
743 /* no modification of a sector should follow a free sector */
744 goto the_end;
745 case SECTOR_DELETED:
746 lastgoodEUN = BLOCK_NIL;
747 break;
748 case SECTOR_USED:
749 lastgoodEUN = thisEUN;
750 break;
751 case SECTOR_IGNORE:
752 break;
753 default:
754 printk("Unknown status for block %ld in EUN %d: %x\n",
755 block, thisEUN, status);
756 break;
757 }
758
759 if (!silly--) {
760 printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%lx\n",
761 block / (nftl->EraseSize / 512));
762 return 1;
763 }
764 thisEUN = nftl->ReplUnitTable[thisEUN];
765 }
Thomas Gleixnerf4a43cf2006-05-28 11:01:53 +0200766 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700767
768 the_end:
769 if (lastgoodEUN == BLOCK_NIL) {
770 /* the requested block is not on the media, return all 0x00 */
771 memset(buffer, 0, 512);
772 } else {
773 loff_t ptr = (lastgoodEUN * nftl->EraseSize) + blockofs;
774 size_t retlen;
Thomas Gleixner9a1fcdf2006-05-29 14:56:39 +0200775 int res = mtd->read(mtd, ptr, 512, &retlen, buffer);
776
777 if (res < 0 && res != -EUCLEAN)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700778 return -EIO;
779 }
780 return 0;
781}
782
783static int nftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
784{
785 struct NFTLrecord *nftl = (void *)dev;
786
787 geo->heads = nftl->heads;
788 geo->sectors = nftl->sectors;
789 geo->cylinders = nftl->cylinders;
790
791 return 0;
792}
793
794/****************************************************************************
795 *
796 * Module stuff
797 *
798 ****************************************************************************/
799
800
801static struct mtd_blktrans_ops nftl_tr = {
802 .name = "nftl",
803 .major = NFTL_MAJOR,
804 .part_bits = NFTL_PARTN_BITS,
Richard Purdie19187672006-10-27 09:09:33 +0100805 .blksize = 512,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700806 .getgeo = nftl_getgeo,
807 .readsect = nftl_readblock,
808#ifdef CONFIG_NFTL_RW
809 .writesect = nftl_writeblock,
810#endif
811 .add_mtd = nftl_add_mtd,
812 .remove_dev = nftl_remove_dev,
813 .owner = THIS_MODULE,
814};
815
Linus Torvalds1da177e2005-04-16 15:20:36 -0700816static int __init init_nftl(void)
817{
Linus Torvalds1da177e2005-04-16 15:20:36 -0700818 return register_mtd_blktrans(&nftl_tr);
819}
820
821static void __exit cleanup_nftl(void)
822{
823 deregister_mtd_blktrans(&nftl_tr);
824}
825
826module_init(init_nftl);
827module_exit(cleanup_nftl);
828
829MODULE_LICENSE("GPL");
830MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>, Fabrice Bellard <fabrice.bellard@netgem.com> et al.");
831MODULE_DESCRIPTION("Support code for NAND Flash Translation Layer, used on M-Systems DiskOnChip 2000 and Millennium");
Scott James Remnante7f52162009-03-02 17:43:04 +0000832MODULE_ALIAS_BLOCKDEV_MAJOR(NFTL_MAJOR);