diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/mtd/nftlcore.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/mtd/nftlcore.c')
-rw-r--r-- | drivers/mtd/nftlcore.c | 767 |
1 files changed, 767 insertions, 0 deletions
diff --git a/drivers/mtd/nftlcore.c b/drivers/mtd/nftlcore.c new file mode 100644 index 000000000000..b2014043634f --- /dev/null +++ b/drivers/mtd/nftlcore.c | |||
@@ -0,0 +1,767 @@ | |||
1 | /* Linux driver for NAND Flash Translation Layer */ | ||
2 | /* (c) 1999 Machine Vision Holdings, Inc. */ | ||
3 | /* Author: David Woodhouse <dwmw2@infradead.org> */ | ||
4 | /* $Id: nftlcore.c,v 1.97 2004/11/16 18:28:59 dwmw2 Exp $ */ | ||
5 | |||
6 | /* | ||
7 | The contents of this file are distributed under the GNU General | ||
8 | Public License version 2. The author places no additional | ||
9 | restrictions of any kind on it. | ||
10 | */ | ||
11 | |||
12 | #define PRERELEASE | ||
13 | |||
14 | #include <linux/config.h> | ||
15 | #include <linux/kernel.h> | ||
16 | #include <linux/module.h> | ||
17 | #include <asm/errno.h> | ||
18 | #include <asm/io.h> | ||
19 | #include <asm/uaccess.h> | ||
20 | #include <linux/miscdevice.h> | ||
21 | #include <linux/pci.h> | ||
22 | #include <linux/delay.h> | ||
23 | #include <linux/slab.h> | ||
24 | #include <linux/sched.h> | ||
25 | #include <linux/init.h> | ||
26 | #include <linux/hdreg.h> | ||
27 | |||
28 | #include <linux/kmod.h> | ||
29 | #include <linux/mtd/mtd.h> | ||
30 | #include <linux/mtd/nand.h> | ||
31 | #include <linux/mtd/nftl.h> | ||
32 | #include <linux/mtd/blktrans.h> | ||
33 | |||
34 | /* maximum number of loops while examining next block, to have a | ||
35 | chance to detect consistency problems (they should never happen | ||
36 | because of the checks done in the mounting */ | ||
37 | |||
38 | #define MAX_LOOPS 10000 | ||
39 | |||
40 | |||
41 | static void nftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) | ||
42 | { | ||
43 | struct NFTLrecord *nftl; | ||
44 | unsigned long temp; | ||
45 | |||
46 | if (mtd->type != MTD_NANDFLASH) | ||
47 | return; | ||
48 | /* OK, this is moderately ugly. But probably safe. Alternatives? */ | ||
49 | if (memcmp(mtd->name, "DiskOnChip", 10)) | ||
50 | return; | ||
51 | |||
52 | if (!mtd->block_isbad) { | ||
53 | printk(KERN_ERR | ||
54 | "NFTL no longer supports the old DiskOnChip drivers loaded via docprobe.\n" | ||
55 | "Please use the new diskonchip driver under the NAND subsystem.\n"); | ||
56 | return; | ||
57 | } | ||
58 | |||
59 | DEBUG(MTD_DEBUG_LEVEL1, "NFTL: add_mtd for %s\n", mtd->name); | ||
60 | |||
61 | nftl = kmalloc(sizeof(struct NFTLrecord), GFP_KERNEL); | ||
62 | |||
63 | if (!nftl) { | ||
64 | printk(KERN_WARNING "NFTL: out of memory for data structures\n"); | ||
65 | return; | ||
66 | } | ||
67 | memset(nftl, 0, sizeof(*nftl)); | ||
68 | |||
69 | nftl->mbd.mtd = mtd; | ||
70 | nftl->mbd.devnum = -1; | ||
71 | nftl->mbd.blksize = 512; | ||
72 | nftl->mbd.tr = tr; | ||
73 | memcpy(&nftl->oobinfo, &mtd->oobinfo, sizeof(struct nand_oobinfo)); | ||
74 | nftl->oobinfo.useecc = MTD_NANDECC_PLACEONLY; | ||
75 | |||
76 | if (NFTL_mount(nftl) < 0) { | ||
77 | printk(KERN_WARNING "NFTL: could not mount device\n"); | ||
78 | kfree(nftl); | ||
79 | return; | ||
80 | } | ||
81 | |||
82 | /* OK, it's a new one. Set up all the data structures. */ | ||
83 | |||
84 | /* Calculate geometry */ | ||
85 | nftl->cylinders = 1024; | ||
86 | nftl->heads = 16; | ||
87 | |||
88 | temp = nftl->cylinders * nftl->heads; | ||
89 | nftl->sectors = nftl->mbd.size / temp; | ||
90 | if (nftl->mbd.size % temp) { | ||
91 | nftl->sectors++; | ||
92 | temp = nftl->cylinders * nftl->sectors; | ||
93 | nftl->heads = nftl->mbd.size / temp; | ||
94 | |||
95 | if (nftl->mbd.size % temp) { | ||
96 | nftl->heads++; | ||
97 | temp = nftl->heads * nftl->sectors; | ||
98 | nftl->cylinders = nftl->mbd.size / temp; | ||
99 | } | ||
100 | } | ||
101 | |||
102 | if (nftl->mbd.size != nftl->heads * nftl->cylinders * nftl->sectors) { | ||
103 | /* | ||
104 | Oh no we don't have | ||
105 | mbd.size == heads * cylinders * sectors | ||
106 | */ | ||
107 | printk(KERN_WARNING "NFTL: cannot calculate a geometry to " | ||
108 | "match size of 0x%lx.\n", nftl->mbd.size); | ||
109 | printk(KERN_WARNING "NFTL: using C:%d H:%d S:%d " | ||
110 | "(== 0x%lx sects)\n", | ||
111 | nftl->cylinders, nftl->heads , nftl->sectors, | ||
112 | (long)nftl->cylinders * (long)nftl->heads * | ||
113 | (long)nftl->sectors ); | ||
114 | } | ||
115 | |||
116 | if (add_mtd_blktrans_dev(&nftl->mbd)) { | ||
117 | if (nftl->ReplUnitTable) | ||
118 | kfree(nftl->ReplUnitTable); | ||
119 | if (nftl->EUNtable) | ||
120 | kfree(nftl->EUNtable); | ||
121 | 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 | |||
129 | static 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); | ||
136 | if (nftl->ReplUnitTable) | ||
137 | kfree(nftl->ReplUnitTable); | ||
138 | if (nftl->EUNtable) | ||
139 | kfree(nftl->EUNtable); | ||
140 | kfree(nftl); | ||
141 | } | ||
142 | |||
143 | #ifdef CONFIG_NFTL_RW | ||
144 | |||
145 | /* Actual NFTL access routines */ | ||
146 | /* NFTL_findfreeblock: Find a free Erase Unit on the NFTL partition. This function is used | ||
147 | * when the give Virtual Unit Chain | ||
148 | */ | ||
149 | static u16 NFTL_findfreeblock(struct NFTLrecord *nftl, int desperate ) | ||
150 | { | ||
151 | /* For a given Virtual Unit Chain: find or create a free block and | ||
152 | add it to the chain */ | ||
153 | /* We're passed the number of the last EUN in the chain, to save us from | ||
154 | having to look it up again */ | ||
155 | u16 pot = nftl->LastFreeEUN; | ||
156 | int silly = nftl->nb_blocks; | ||
157 | |||
158 | /* Normally, we force a fold to happen before we run out of free blocks completely */ | ||
159 | if (!desperate && nftl->numfreeEUNs < 2) { | ||
160 | DEBUG(MTD_DEBUG_LEVEL1, "NFTL_findfreeblock: there are too few free EUNs\n"); | ||
161 | return 0xffff; | ||
162 | } | ||
163 | |||
164 | /* Scan for a free block */ | ||
165 | do { | ||
166 | if (nftl->ReplUnitTable[pot] == BLOCK_FREE) { | ||
167 | nftl->LastFreeEUN = pot; | ||
168 | nftl->numfreeEUNs--; | ||
169 | return pot; | ||
170 | } | ||
171 | |||
172 | /* This will probably point to the MediaHdr unit itself, | ||
173 | right at the beginning of the partition. But that unit | ||
174 | (and the backup unit too) should have the UCI set | ||
175 | up so that it's not selected for overwriting */ | ||
176 | if (++pot > nftl->lastEUN) | ||
177 | pot = le16_to_cpu(nftl->MediaHdr.FirstPhysicalEUN); | ||
178 | |||
179 | if (!silly--) { | ||
180 | printk("Argh! No free blocks found! LastFreeEUN = %d, " | ||
181 | "FirstEUN = %d\n", nftl->LastFreeEUN, | ||
182 | le16_to_cpu(nftl->MediaHdr.FirstPhysicalEUN)); | ||
183 | return 0xffff; | ||
184 | } | ||
185 | } while (pot != nftl->LastFreeEUN); | ||
186 | |||
187 | return 0xffff; | ||
188 | } | ||
189 | |||
190 | static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned pendingblock ) | ||
191 | { | ||
192 | u16 BlockMap[MAX_SECTORS_PER_UNIT]; | ||
193 | unsigned char BlockLastState[MAX_SECTORS_PER_UNIT]; | ||
194 | unsigned char BlockFreeFound[MAX_SECTORS_PER_UNIT]; | ||
195 | unsigned int thisEUN; | ||
196 | int block; | ||
197 | int silly; | ||
198 | unsigned int targetEUN; | ||
199 | struct nftl_oob oob; | ||
200 | int inplace = 1; | ||
201 | size_t retlen; | ||
202 | |||
203 | memset(BlockMap, 0xff, sizeof(BlockMap)); | ||
204 | memset(BlockFreeFound, 0, sizeof(BlockFreeFound)); | ||
205 | |||
206 | thisEUN = nftl->EUNtable[thisVUC]; | ||
207 | |||
208 | if (thisEUN == BLOCK_NIL) { | ||
209 | printk(KERN_WARNING "Trying to fold non-existent " | ||
210 | "Virtual Unit Chain %d!\n", thisVUC); | ||
211 | return BLOCK_NIL; | ||
212 | } | ||
213 | |||
214 | /* Scan to find the Erase Unit which holds the actual data for each | ||
215 | 512-byte block within the Chain. | ||
216 | */ | ||
217 | silly = MAX_LOOPS; | ||
218 | targetEUN = BLOCK_NIL; | ||
219 | while (thisEUN <= nftl->lastEUN ) { | ||
220 | unsigned int status, foldmark; | ||
221 | |||
222 | targetEUN = thisEUN; | ||
223 | for (block = 0; block < nftl->EraseSize / 512; block ++) { | ||
224 | MTD_READOOB(nftl->mbd.mtd, | ||
225 | (thisEUN * nftl->EraseSize) + (block * 512), | ||
226 | 16 , &retlen, (char *)&oob); | ||
227 | if (block == 2) { | ||
228 | foldmark = oob.u.c.FoldMark | oob.u.c.FoldMark1; | ||
229 | if (foldmark == FOLD_MARK_IN_PROGRESS) { | ||
230 | DEBUG(MTD_DEBUG_LEVEL1, | ||
231 | "Write Inhibited on EUN %d\n", thisEUN); | ||
232 | inplace = 0; | ||
233 | } else { | ||
234 | /* There's no other reason not to do inplace, | ||
235 | except ones that come later. So we don't need | ||
236 | to preserve inplace */ | ||
237 | inplace = 1; | ||
238 | } | ||
239 | } | ||
240 | status = oob.b.Status | oob.b.Status1; | ||
241 | BlockLastState[block] = status; | ||
242 | |||
243 | switch(status) { | ||
244 | case SECTOR_FREE: | ||
245 | BlockFreeFound[block] = 1; | ||
246 | break; | ||
247 | |||
248 | case SECTOR_USED: | ||
249 | if (!BlockFreeFound[block]) | ||
250 | BlockMap[block] = thisEUN; | ||
251 | else | ||
252 | printk(KERN_WARNING | ||
253 | "SECTOR_USED found after SECTOR_FREE " | ||
254 | "in Virtual Unit Chain %d for block %d\n", | ||
255 | thisVUC, block); | ||
256 | break; | ||
257 | case SECTOR_DELETED: | ||
258 | if (!BlockFreeFound[block]) | ||
259 | BlockMap[block] = BLOCK_NIL; | ||
260 | else | ||
261 | printk(KERN_WARNING | ||
262 | "SECTOR_DELETED found after SECTOR_FREE " | ||
263 | "in Virtual Unit Chain %d for block %d\n", | ||
264 | thisVUC, block); | ||
265 | break; | ||
266 | |||
267 | case SECTOR_IGNORE: | ||
268 | break; | ||
269 | default: | ||
270 | printk("Unknown status for block %d in EUN %d: %x\n", | ||
271 | block, thisEUN, status); | ||
272 | } | ||
273 | } | ||
274 | |||
275 | if (!silly--) { | ||
276 | printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%x\n", | ||
277 | thisVUC); | ||
278 | return BLOCK_NIL; | ||
279 | } | ||
280 | |||
281 | thisEUN = nftl->ReplUnitTable[thisEUN]; | ||
282 | } | ||
283 | |||
284 | if (inplace) { | ||
285 | /* We're being asked to be a fold-in-place. Check | ||
286 | that all blocks which actually have data associated | ||
287 | with them (i.e. BlockMap[block] != BLOCK_NIL) are | ||
288 | either already present or SECTOR_FREE in the target | ||
289 | block. If not, we're going to have to fold out-of-place | ||
290 | anyway. | ||
291 | */ | ||
292 | for (block = 0; block < nftl->EraseSize / 512 ; block++) { | ||
293 | if (BlockLastState[block] != SECTOR_FREE && | ||
294 | BlockMap[block] != BLOCK_NIL && | ||
295 | BlockMap[block] != targetEUN) { | ||
296 | DEBUG(MTD_DEBUG_LEVEL1, "Setting inplace to 0. VUC %d, " | ||
297 | "block %d was %x lastEUN, " | ||
298 | "and is in EUN %d (%s) %d\n", | ||
299 | thisVUC, block, BlockLastState[block], | ||
300 | BlockMap[block], | ||
301 | BlockMap[block]== targetEUN ? "==" : "!=", | ||
302 | targetEUN); | ||
303 | inplace = 0; | ||
304 | break; | ||
305 | } | ||
306 | } | ||
307 | |||
308 | if (pendingblock >= (thisVUC * (nftl->EraseSize / 512)) && | ||
309 | pendingblock < ((thisVUC + 1)* (nftl->EraseSize / 512)) && | ||
310 | BlockLastState[pendingblock - (thisVUC * (nftl->EraseSize / 512))] != | ||
311 | SECTOR_FREE) { | ||
312 | DEBUG(MTD_DEBUG_LEVEL1, "Pending write not free in EUN %d. " | ||
313 | "Folding out of place.\n", targetEUN); | ||
314 | inplace = 0; | ||
315 | } | ||
316 | } | ||
317 | |||
318 | if (!inplace) { | ||
319 | DEBUG(MTD_DEBUG_LEVEL1, "Cannot fold Virtual Unit Chain %d in place. " | ||
320 | "Trying out-of-place\n", thisVUC); | ||
321 | /* We need to find a targetEUN to fold into. */ | ||
322 | targetEUN = NFTL_findfreeblock(nftl, 1); | ||
323 | if (targetEUN == BLOCK_NIL) { | ||
324 | /* Ouch. Now we're screwed. We need to do a | ||
325 | fold-in-place of another chain to make room | ||
326 | for this one. We need a better way of selecting | ||
327 | which chain to fold, because makefreeblock will | ||
328 | only ask us to fold the same one again. | ||
329 | */ | ||
330 | printk(KERN_WARNING | ||
331 | "NFTL_findfreeblock(desperate) returns 0xffff.\n"); | ||
332 | return BLOCK_NIL; | ||
333 | } | ||
334 | } else { | ||
335 | /* We put a fold mark in the chain we are folding only if | ||
336 | we fold in place to help the mount check code. If we do | ||
337 | not fold in place, it is possible to find the valid | ||
338 | chain by selecting the longer one */ | ||
339 | oob.u.c.FoldMark = oob.u.c.FoldMark1 = cpu_to_le16(FOLD_MARK_IN_PROGRESS); | ||
340 | oob.u.c.unused = 0xffffffff; | ||
341 | MTD_WRITEOOB(nftl->mbd.mtd, (nftl->EraseSize * targetEUN) + 2 * 512 + 8, | ||
342 | 8, &retlen, (char *)&oob.u); | ||
343 | } | ||
344 | |||
345 | /* OK. We now know the location of every block in the Virtual Unit Chain, | ||
346 | and the Erase Unit into which we are supposed to be copying. | ||
347 | Go for it. | ||
348 | */ | ||
349 | DEBUG(MTD_DEBUG_LEVEL1,"Folding chain %d into unit %d\n", thisVUC, targetEUN); | ||
350 | for (block = 0; block < nftl->EraseSize / 512 ; block++) { | ||
351 | unsigned char movebuf[512]; | ||
352 | int ret; | ||
353 | |||
354 | /* If it's in the target EUN already, or if it's pending write, do nothing */ | ||
355 | if (BlockMap[block] == targetEUN || | ||
356 | (pendingblock == (thisVUC * (nftl->EraseSize / 512) + block))) { | ||
357 | continue; | ||
358 | } | ||
359 | |||
360 | /* copy only in non free block (free blocks can only | ||
361 | happen in case of media errors or deleted blocks) */ | ||
362 | if (BlockMap[block] == BLOCK_NIL) | ||
363 | continue; | ||
364 | |||
365 | ret = MTD_READ(nftl->mbd.mtd, (nftl->EraseSize * BlockMap[block]) + (block * 512), | ||
366 | 512, &retlen, movebuf); | ||
367 | if (ret < 0) { | ||
368 | ret = MTD_READ(nftl->mbd.mtd, (nftl->EraseSize * BlockMap[block]) | ||
369 | + (block * 512), 512, &retlen, | ||
370 | movebuf); | ||
371 | if (ret != -EIO) | ||
372 | printk("Error went away on retry.\n"); | ||
373 | } | ||
374 | memset(&oob, 0xff, sizeof(struct nftl_oob)); | ||
375 | oob.b.Status = oob.b.Status1 = SECTOR_USED; | ||
376 | MTD_WRITEECC(nftl->mbd.mtd, (nftl->EraseSize * targetEUN) + (block * 512), | ||
377 | 512, &retlen, movebuf, (char *)&oob, &nftl->oobinfo); | ||
378 | } | ||
379 | |||
380 | /* add the header so that it is now a valid chain */ | ||
381 | oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum | ||
382 | = cpu_to_le16(thisVUC); | ||
383 | oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum = 0xffff; | ||
384 | |||
385 | MTD_WRITEOOB(nftl->mbd.mtd, (nftl->EraseSize * targetEUN) + 8, | ||
386 | 8, &retlen, (char *)&oob.u); | ||
387 | |||
388 | /* OK. We've moved the whole lot into the new block. Now we have to free the original blocks. */ | ||
389 | |||
390 | /* At this point, we have two different chains for this Virtual Unit, and no way to tell | ||
391 | them apart. If we crash now, we get confused. However, both contain the same data, so we | ||
392 | shouldn't actually lose data in this case. It's just that when we load up on a medium which | ||
393 | has duplicate chains, we need to free one of the chains because it's not necessary any more. | ||
394 | */ | ||
395 | thisEUN = nftl->EUNtable[thisVUC]; | ||
396 | DEBUG(MTD_DEBUG_LEVEL1,"Want to erase\n"); | ||
397 | |||
398 | /* For each block in the old chain (except the targetEUN of course), | ||
399 | free it and make it available for future use */ | ||
400 | while (thisEUN <= nftl->lastEUN && thisEUN != targetEUN) { | ||
401 | unsigned int EUNtmp; | ||
402 | |||
403 | EUNtmp = nftl->ReplUnitTable[thisEUN]; | ||
404 | |||
405 | if (NFTL_formatblock(nftl, thisEUN) < 0) { | ||
406 | /* could not erase : mark block as reserved | ||
407 | */ | ||
408 | nftl->ReplUnitTable[thisEUN] = BLOCK_RESERVED; | ||
409 | } else { | ||
410 | /* correctly erased : mark it as free */ | ||
411 | nftl->ReplUnitTable[thisEUN] = BLOCK_FREE; | ||
412 | nftl->numfreeEUNs++; | ||
413 | } | ||
414 | thisEUN = EUNtmp; | ||
415 | } | ||
416 | |||
417 | /* Make this the new start of chain for thisVUC */ | ||
418 | nftl->ReplUnitTable[targetEUN] = BLOCK_NIL; | ||
419 | nftl->EUNtable[thisVUC] = targetEUN; | ||
420 | |||
421 | return targetEUN; | ||
422 | } | ||
423 | |||
424 | static u16 NFTL_makefreeblock( struct NFTLrecord *nftl , unsigned pendingblock) | ||
425 | { | ||
426 | /* This is the part that needs some cleverness applied. | ||
427 | For now, I'm doing the minimum applicable to actually | ||
428 | get the thing to work. | ||
429 | Wear-levelling and other clever stuff needs to be implemented | ||
430 | and we also need to do some assessment of the results when | ||
431 | the system loses power half-way through the routine. | ||
432 | */ | ||
433 | u16 LongestChain = 0; | ||
434 | u16 ChainLength = 0, thislen; | ||
435 | u16 chain, EUN; | ||
436 | |||
437 | for (chain = 0; chain < le32_to_cpu(nftl->MediaHdr.FormattedSize) / nftl->EraseSize; chain++) { | ||
438 | EUN = nftl->EUNtable[chain]; | ||
439 | thislen = 0; | ||
440 | |||
441 | while (EUN <= nftl->lastEUN) { | ||
442 | thislen++; | ||
443 | //printk("VUC %d reaches len %d with EUN %d\n", chain, thislen, EUN); | ||
444 | EUN = nftl->ReplUnitTable[EUN] & 0x7fff; | ||
445 | if (thislen > 0xff00) { | ||
446 | printk("Endless loop in Virtual Chain %d: Unit %x\n", | ||
447 | chain, EUN); | ||
448 | } | ||
449 | if (thislen > 0xff10) { | ||
450 | /* Actually, don't return failure. Just ignore this chain and | ||
451 | get on with it. */ | ||
452 | thislen = 0; | ||
453 | break; | ||
454 | } | ||
455 | } | ||
456 | |||
457 | if (thislen > ChainLength) { | ||
458 | //printk("New longest chain is %d with length %d\n", chain, thislen); | ||
459 | ChainLength = thislen; | ||
460 | LongestChain = chain; | ||
461 | } | ||
462 | } | ||
463 | |||
464 | if (ChainLength < 2) { | ||
465 | printk(KERN_WARNING "No Virtual Unit Chains available for folding. " | ||
466 | "Failing request\n"); | ||
467 | return 0xffff; | ||
468 | } | ||
469 | |||
470 | return NFTL_foldchain (nftl, LongestChain, pendingblock); | ||
471 | } | ||
472 | |||
473 | /* NFTL_findwriteunit: Return the unit number into which we can write | ||
474 | for this block. Make it available if it isn't already | ||
475 | */ | ||
476 | static inline u16 NFTL_findwriteunit(struct NFTLrecord *nftl, unsigned block) | ||
477 | { | ||
478 | u16 lastEUN; | ||
479 | u16 thisVUC = block / (nftl->EraseSize / 512); | ||
480 | unsigned int writeEUN; | ||
481 | unsigned long blockofs = (block * 512) & (nftl->EraseSize -1); | ||
482 | size_t retlen; | ||
483 | int silly, silly2 = 3; | ||
484 | struct nftl_oob oob; | ||
485 | |||
486 | do { | ||
487 | /* Scan the media to find a unit in the VUC which has | ||
488 | a free space for the block in question. | ||
489 | */ | ||
490 | |||
491 | /* This condition catches the 0x[7f]fff cases, as well as | ||
492 | being a sanity check for past-end-of-media access | ||
493 | */ | ||
494 | lastEUN = BLOCK_NIL; | ||
495 | writeEUN = nftl->EUNtable[thisVUC]; | ||
496 | silly = MAX_LOOPS; | ||
497 | while (writeEUN <= nftl->lastEUN) { | ||
498 | struct nftl_bci bci; | ||
499 | size_t retlen; | ||
500 | unsigned int status; | ||
501 | |||
502 | lastEUN = writeEUN; | ||
503 | |||
504 | MTD_READOOB(nftl->mbd.mtd, (writeEUN * nftl->EraseSize) + blockofs, | ||
505 | 8, &retlen, (char *)&bci); | ||
506 | |||
507 | DEBUG(MTD_DEBUG_LEVEL2, "Status of block %d in EUN %d is %x\n", | ||
508 | block , writeEUN, le16_to_cpu(bci.Status)); | ||
509 | |||
510 | status = bci.Status | bci.Status1; | ||
511 | switch(status) { | ||
512 | case SECTOR_FREE: | ||
513 | return writeEUN; | ||
514 | |||
515 | case SECTOR_DELETED: | ||
516 | case SECTOR_USED: | ||
517 | case SECTOR_IGNORE: | ||
518 | break; | ||
519 | default: | ||
520 | // Invalid block. Don't use it any more. Must implement. | ||
521 | break; | ||
522 | } | ||
523 | |||
524 | if (!silly--) { | ||
525 | printk(KERN_WARNING | ||
526 | "Infinite loop in Virtual Unit Chain 0x%x\n", | ||
527 | thisVUC); | ||
528 | return 0xffff; | ||
529 | } | ||
530 | |||
531 | /* Skip to next block in chain */ | ||
532 | writeEUN = nftl->ReplUnitTable[writeEUN]; | ||
533 | } | ||
534 | |||
535 | /* OK. We didn't find one in the existing chain, or there | ||
536 | is no existing chain. */ | ||
537 | |||
538 | /* Try to find an already-free block */ | ||
539 | writeEUN = NFTL_findfreeblock(nftl, 0); | ||
540 | |||
541 | if (writeEUN == BLOCK_NIL) { | ||
542 | /* That didn't work - there were no free blocks just | ||
543 | waiting to be picked up. We're going to have to fold | ||
544 | a chain to make room. | ||
545 | */ | ||
546 | |||
547 | /* First remember the start of this chain */ | ||
548 | //u16 startEUN = nftl->EUNtable[thisVUC]; | ||
549 | |||
550 | //printk("Write to VirtualUnitChain %d, calling makefreeblock()\n", thisVUC); | ||
551 | writeEUN = NFTL_makefreeblock(nftl, 0xffff); | ||
552 | |||
553 | if (writeEUN == BLOCK_NIL) { | ||
554 | /* OK, we accept that the above comment is | ||
555 | lying - there may have been free blocks | ||
556 | last time we called NFTL_findfreeblock(), | ||
557 | but they are reserved for when we're | ||
558 | desperate. Well, now we're desperate. | ||
559 | */ | ||
560 | DEBUG(MTD_DEBUG_LEVEL1, "Using desperate==1 to find free EUN to accommodate write to VUC %d\n", thisVUC); | ||
561 | writeEUN = NFTL_findfreeblock(nftl, 1); | ||
562 | } | ||
563 | if (writeEUN == BLOCK_NIL) { | ||
564 | /* Ouch. This should never happen - we should | ||
565 | always be able to make some room somehow. | ||
566 | If we get here, we've allocated more storage | ||
567 | space than actual media, or our makefreeblock | ||
568 | routine is missing something. | ||
569 | */ | ||
570 | printk(KERN_WARNING "Cannot make free space.\n"); | ||
571 | return BLOCK_NIL; | ||
572 | } | ||
573 | //printk("Restarting scan\n"); | ||
574 | lastEUN = BLOCK_NIL; | ||
575 | continue; | ||
576 | } | ||
577 | |||
578 | /* We've found a free block. Insert it into the chain. */ | ||
579 | |||
580 | if (lastEUN != BLOCK_NIL) { | ||
581 | thisVUC |= 0x8000; /* It's a replacement block */ | ||
582 | } else { | ||
583 | /* The first block in a new chain */ | ||
584 | nftl->EUNtable[thisVUC] = writeEUN; | ||
585 | } | ||
586 | |||
587 | /* set up the actual EUN we're writing into */ | ||
588 | /* Both in our cache... */ | ||
589 | nftl->ReplUnitTable[writeEUN] = BLOCK_NIL; | ||
590 | |||
591 | /* ... and on the flash itself */ | ||
592 | MTD_READOOB(nftl->mbd.mtd, writeEUN * nftl->EraseSize + 8, 8, | ||
593 | &retlen, (char *)&oob.u); | ||
594 | |||
595 | oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC); | ||
596 | |||
597 | MTD_WRITEOOB(nftl->mbd.mtd, writeEUN * nftl->EraseSize + 8, 8, | ||
598 | &retlen, (char *)&oob.u); | ||
599 | |||
600 | /* we link the new block to the chain only after the | ||
601 | block is ready. It avoids the case where the chain | ||
602 | could point to a free block */ | ||
603 | if (lastEUN != BLOCK_NIL) { | ||
604 | /* Both in our cache... */ | ||
605 | nftl->ReplUnitTable[lastEUN] = writeEUN; | ||
606 | /* ... and on the flash itself */ | ||
607 | MTD_READOOB(nftl->mbd.mtd, (lastEUN * nftl->EraseSize) + 8, | ||
608 | 8, &retlen, (char *)&oob.u); | ||
609 | |||
610 | oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum | ||
611 | = cpu_to_le16(writeEUN); | ||
612 | |||
613 | MTD_WRITEOOB(nftl->mbd.mtd, (lastEUN * nftl->EraseSize) + 8, | ||
614 | 8, &retlen, (char *)&oob.u); | ||
615 | } | ||
616 | |||
617 | return writeEUN; | ||
618 | |||
619 | } while (silly2--); | ||
620 | |||
621 | printk(KERN_WARNING "Error folding to make room for Virtual Unit Chain 0x%x\n", | ||
622 | thisVUC); | ||
623 | return 0xffff; | ||
624 | } | ||
625 | |||
626 | static int nftl_writeblock(struct mtd_blktrans_dev *mbd, unsigned long block, | ||
627 | char *buffer) | ||
628 | { | ||
629 | struct NFTLrecord *nftl = (void *)mbd; | ||
630 | u16 writeEUN; | ||
631 | unsigned long blockofs = (block * 512) & (nftl->EraseSize - 1); | ||
632 | size_t retlen; | ||
633 | struct nftl_oob oob; | ||
634 | |||
635 | writeEUN = NFTL_findwriteunit(nftl, block); | ||
636 | |||
637 | if (writeEUN == BLOCK_NIL) { | ||
638 | printk(KERN_WARNING | ||
639 | "NFTL_writeblock(): Cannot find block to write to\n"); | ||
640 | /* If we _still_ haven't got a block to use, we're screwed */ | ||
641 | return 1; | ||
642 | } | ||
643 | |||
644 | memset(&oob, 0xff, sizeof(struct nftl_oob)); | ||
645 | oob.b.Status = oob.b.Status1 = SECTOR_USED; | ||
646 | MTD_WRITEECC(nftl->mbd.mtd, (writeEUN * nftl->EraseSize) + blockofs, | ||
647 | 512, &retlen, (char *)buffer, (char *)&oob, &nftl->oobinfo); | ||
648 | /* need to write SECTOR_USED flags since they are not written in mtd_writeecc */ | ||
649 | |||
650 | return 0; | ||
651 | } | ||
652 | #endif /* CONFIG_NFTL_RW */ | ||
653 | |||
654 | static int nftl_readblock(struct mtd_blktrans_dev *mbd, unsigned long block, | ||
655 | char *buffer) | ||
656 | { | ||
657 | struct NFTLrecord *nftl = (void *)mbd; | ||
658 | u16 lastgoodEUN; | ||
659 | u16 thisEUN = nftl->EUNtable[block / (nftl->EraseSize / 512)]; | ||
660 | unsigned long blockofs = (block * 512) & (nftl->EraseSize - 1); | ||
661 | unsigned int status; | ||
662 | int silly = MAX_LOOPS; | ||
663 | size_t retlen; | ||
664 | struct nftl_bci bci; | ||
665 | |||
666 | lastgoodEUN = BLOCK_NIL; | ||
667 | |||
668 | if (thisEUN != BLOCK_NIL) { | ||
669 | while (thisEUN < nftl->nb_blocks) { | ||
670 | if (MTD_READOOB(nftl->mbd.mtd, (thisEUN * nftl->EraseSize) + blockofs, | ||
671 | 8, &retlen, (char *)&bci) < 0) | ||
672 | status = SECTOR_IGNORE; | ||
673 | else | ||
674 | status = bci.Status | bci.Status1; | ||
675 | |||
676 | switch (status) { | ||
677 | case SECTOR_FREE: | ||
678 | /* no modification of a sector should follow a free sector */ | ||
679 | goto the_end; | ||
680 | case SECTOR_DELETED: | ||
681 | lastgoodEUN = BLOCK_NIL; | ||
682 | break; | ||
683 | case SECTOR_USED: | ||
684 | lastgoodEUN = thisEUN; | ||
685 | break; | ||
686 | case SECTOR_IGNORE: | ||
687 | break; | ||
688 | default: | ||
689 | printk("Unknown status for block %ld in EUN %d: %x\n", | ||
690 | block, thisEUN, status); | ||
691 | break; | ||
692 | } | ||
693 | |||
694 | if (!silly--) { | ||
695 | printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%lx\n", | ||
696 | block / (nftl->EraseSize / 512)); | ||
697 | return 1; | ||
698 | } | ||
699 | thisEUN = nftl->ReplUnitTable[thisEUN]; | ||
700 | } | ||
701 | } | ||
702 | |||
703 | the_end: | ||
704 | if (lastgoodEUN == BLOCK_NIL) { | ||
705 | /* the requested block is not on the media, return all 0x00 */ | ||
706 | memset(buffer, 0, 512); | ||
707 | } else { | ||
708 | loff_t ptr = (lastgoodEUN * nftl->EraseSize) + blockofs; | ||
709 | size_t retlen; | ||
710 | if (MTD_READ(nftl->mbd.mtd, ptr, 512, &retlen, buffer)) | ||
711 | return -EIO; | ||
712 | } | ||
713 | return 0; | ||
714 | } | ||
715 | |||
716 | static int nftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo) | ||
717 | { | ||
718 | struct NFTLrecord *nftl = (void *)dev; | ||
719 | |||
720 | geo->heads = nftl->heads; | ||
721 | geo->sectors = nftl->sectors; | ||
722 | geo->cylinders = nftl->cylinders; | ||
723 | |||
724 | return 0; | ||
725 | } | ||
726 | |||
727 | /**************************************************************************** | ||
728 | * | ||
729 | * Module stuff | ||
730 | * | ||
731 | ****************************************************************************/ | ||
732 | |||
733 | |||
734 | static struct mtd_blktrans_ops nftl_tr = { | ||
735 | .name = "nftl", | ||
736 | .major = NFTL_MAJOR, | ||
737 | .part_bits = NFTL_PARTN_BITS, | ||
738 | .getgeo = nftl_getgeo, | ||
739 | .readsect = nftl_readblock, | ||
740 | #ifdef CONFIG_NFTL_RW | ||
741 | .writesect = nftl_writeblock, | ||
742 | #endif | ||
743 | .add_mtd = nftl_add_mtd, | ||
744 | .remove_dev = nftl_remove_dev, | ||
745 | .owner = THIS_MODULE, | ||
746 | }; | ||
747 | |||
748 | extern char nftlmountrev[]; | ||
749 | |||
750 | static int __init init_nftl(void) | ||
751 | { | ||
752 | printk(KERN_INFO "NFTL driver: nftlcore.c $Revision: 1.97 $, nftlmount.c %s\n", nftlmountrev); | ||
753 | |||
754 | return register_mtd_blktrans(&nftl_tr); | ||
755 | } | ||
756 | |||
757 | static void __exit cleanup_nftl(void) | ||
758 | { | ||
759 | deregister_mtd_blktrans(&nftl_tr); | ||
760 | } | ||
761 | |||
762 | module_init(init_nftl); | ||
763 | module_exit(cleanup_nftl); | ||
764 | |||
765 | MODULE_LICENSE("GPL"); | ||
766 | MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>, Fabrice Bellard <fabrice.bellard@netgem.com> et al."); | ||
767 | MODULE_DESCRIPTION("Support code for NAND Flash Translation Layer, used on M-Systems DiskOnChip 2000 and Millennium"); | ||