diff options
Diffstat (limited to 'drivers/mtd/mtdconcat.c')
-rw-r--r-- | drivers/mtd/mtdconcat.c | 897 |
1 files changed, 897 insertions, 0 deletions
diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c new file mode 100644 index 000000000000..8f66d093c80d --- /dev/null +++ b/drivers/mtd/mtdconcat.c | |||
@@ -0,0 +1,897 @@ | |||
1 | /* | ||
2 | * MTD device concatenation layer | ||
3 | * | ||
4 | * (C) 2002 Robert Kaiser <rkaiser@sysgo.de> | ||
5 | * | ||
6 | * NAND support by Christian Gan <cgan@iders.ca> | ||
7 | * | ||
8 | * This code is GPL | ||
9 | * | ||
10 | * $Id: mtdconcat.c,v 1.9 2004/06/30 15:17:41 dbrown Exp $ | ||
11 | */ | ||
12 | |||
13 | #include <linux/module.h> | ||
14 | #include <linux/types.h> | ||
15 | #include <linux/kernel.h> | ||
16 | #include <linux/slab.h> | ||
17 | |||
18 | #include <linux/mtd/mtd.h> | ||
19 | #include <linux/mtd/concat.h> | ||
20 | |||
21 | /* | ||
22 | * Our storage structure: | ||
23 | * Subdev points to an array of pointers to struct mtd_info objects | ||
24 | * which is allocated along with this structure | ||
25 | * | ||
26 | */ | ||
27 | struct mtd_concat { | ||
28 | struct mtd_info mtd; | ||
29 | int num_subdev; | ||
30 | struct mtd_info **subdev; | ||
31 | }; | ||
32 | |||
33 | /* | ||
34 | * how to calculate the size required for the above structure, | ||
35 | * including the pointer array subdev points to: | ||
36 | */ | ||
37 | #define SIZEOF_STRUCT_MTD_CONCAT(num_subdev) \ | ||
38 | ((sizeof(struct mtd_concat) + (num_subdev) * sizeof(struct mtd_info *))) | ||
39 | |||
40 | /* | ||
41 | * Given a pointer to the MTD object in the mtd_concat structure, | ||
42 | * we can retrieve the pointer to that structure with this macro. | ||
43 | */ | ||
44 | #define CONCAT(x) ((struct mtd_concat *)(x)) | ||
45 | |||
46 | /* | ||
47 | * MTD methods which look up the relevant subdevice, translate the | ||
48 | * effective address and pass through to the subdevice. | ||
49 | */ | ||
50 | |||
51 | static int | ||
52 | concat_read(struct mtd_info *mtd, loff_t from, size_t len, | ||
53 | size_t * retlen, u_char * buf) | ||
54 | { | ||
55 | struct mtd_concat *concat = CONCAT(mtd); | ||
56 | int err = -EINVAL; | ||
57 | int i; | ||
58 | |||
59 | *retlen = 0; | ||
60 | |||
61 | for (i = 0; i < concat->num_subdev; i++) { | ||
62 | struct mtd_info *subdev = concat->subdev[i]; | ||
63 | size_t size, retsize; | ||
64 | |||
65 | if (from >= subdev->size) { | ||
66 | /* Not destined for this subdev */ | ||
67 | size = 0; | ||
68 | from -= subdev->size; | ||
69 | continue; | ||
70 | } | ||
71 | if (from + len > subdev->size) | ||
72 | /* First part goes into this subdev */ | ||
73 | size = subdev->size - from; | ||
74 | else | ||
75 | /* Entire transaction goes into this subdev */ | ||
76 | size = len; | ||
77 | |||
78 | err = subdev->read(subdev, from, size, &retsize, buf); | ||
79 | |||
80 | if (err) | ||
81 | break; | ||
82 | |||
83 | *retlen += retsize; | ||
84 | len -= size; | ||
85 | if (len == 0) | ||
86 | break; | ||
87 | |||
88 | err = -EINVAL; | ||
89 | buf += size; | ||
90 | from = 0; | ||
91 | } | ||
92 | return err; | ||
93 | } | ||
94 | |||
95 | static int | ||
96 | concat_write(struct mtd_info *mtd, loff_t to, size_t len, | ||
97 | size_t * retlen, const u_char * buf) | ||
98 | { | ||
99 | struct mtd_concat *concat = CONCAT(mtd); | ||
100 | int err = -EINVAL; | ||
101 | int i; | ||
102 | |||
103 | if (!(mtd->flags & MTD_WRITEABLE)) | ||
104 | return -EROFS; | ||
105 | |||
106 | *retlen = 0; | ||
107 | |||
108 | for (i = 0; i < concat->num_subdev; i++) { | ||
109 | struct mtd_info *subdev = concat->subdev[i]; | ||
110 | size_t size, retsize; | ||
111 | |||
112 | if (to >= subdev->size) { | ||
113 | size = 0; | ||
114 | to -= subdev->size; | ||
115 | continue; | ||
116 | } | ||
117 | if (to + len > subdev->size) | ||
118 | size = subdev->size - to; | ||
119 | else | ||
120 | size = len; | ||
121 | |||
122 | if (!(subdev->flags & MTD_WRITEABLE)) | ||
123 | err = -EROFS; | ||
124 | else | ||
125 | err = subdev->write(subdev, to, size, &retsize, buf); | ||
126 | |||
127 | if (err) | ||
128 | break; | ||
129 | |||
130 | *retlen += retsize; | ||
131 | len -= size; | ||
132 | if (len == 0) | ||
133 | break; | ||
134 | |||
135 | err = -EINVAL; | ||
136 | buf += size; | ||
137 | to = 0; | ||
138 | } | ||
139 | return err; | ||
140 | } | ||
141 | |||
142 | static int | ||
143 | concat_read_ecc(struct mtd_info *mtd, loff_t from, size_t len, | ||
144 | size_t * retlen, u_char * buf, u_char * eccbuf, | ||
145 | struct nand_oobinfo *oobsel) | ||
146 | { | ||
147 | struct mtd_concat *concat = CONCAT(mtd); | ||
148 | int err = -EINVAL; | ||
149 | int i; | ||
150 | |||
151 | *retlen = 0; | ||
152 | |||
153 | for (i = 0; i < concat->num_subdev; i++) { | ||
154 | struct mtd_info *subdev = concat->subdev[i]; | ||
155 | size_t size, retsize; | ||
156 | |||
157 | if (from >= subdev->size) { | ||
158 | /* Not destined for this subdev */ | ||
159 | size = 0; | ||
160 | from -= subdev->size; | ||
161 | continue; | ||
162 | } | ||
163 | |||
164 | if (from + len > subdev->size) | ||
165 | /* First part goes into this subdev */ | ||
166 | size = subdev->size - from; | ||
167 | else | ||
168 | /* Entire transaction goes into this subdev */ | ||
169 | size = len; | ||
170 | |||
171 | if (subdev->read_ecc) | ||
172 | err = subdev->read_ecc(subdev, from, size, | ||
173 | &retsize, buf, eccbuf, oobsel); | ||
174 | else | ||
175 | err = -EINVAL; | ||
176 | |||
177 | if (err) | ||
178 | break; | ||
179 | |||
180 | *retlen += retsize; | ||
181 | len -= size; | ||
182 | if (len == 0) | ||
183 | break; | ||
184 | |||
185 | err = -EINVAL; | ||
186 | buf += size; | ||
187 | if (eccbuf) { | ||
188 | eccbuf += subdev->oobsize; | ||
189 | /* in nand.c at least, eccbufs are | ||
190 | tagged with 2 (int)eccstatus'; we | ||
191 | must account for these */ | ||
192 | eccbuf += 2 * (sizeof (int)); | ||
193 | } | ||
194 | from = 0; | ||
195 | } | ||
196 | return err; | ||
197 | } | ||
198 | |||
199 | static int | ||
200 | concat_write_ecc(struct mtd_info *mtd, loff_t to, size_t len, | ||
201 | size_t * retlen, const u_char * buf, u_char * eccbuf, | ||
202 | struct nand_oobinfo *oobsel) | ||
203 | { | ||
204 | struct mtd_concat *concat = CONCAT(mtd); | ||
205 | int err = -EINVAL; | ||
206 | int i; | ||
207 | |||
208 | if (!(mtd->flags & MTD_WRITEABLE)) | ||
209 | return -EROFS; | ||
210 | |||
211 | *retlen = 0; | ||
212 | |||
213 | for (i = 0; i < concat->num_subdev; i++) { | ||
214 | struct mtd_info *subdev = concat->subdev[i]; | ||
215 | size_t size, retsize; | ||
216 | |||
217 | if (to >= subdev->size) { | ||
218 | size = 0; | ||
219 | to -= subdev->size; | ||
220 | continue; | ||
221 | } | ||
222 | if (to + len > subdev->size) | ||
223 | size = subdev->size - to; | ||
224 | else | ||
225 | size = len; | ||
226 | |||
227 | if (!(subdev->flags & MTD_WRITEABLE)) | ||
228 | err = -EROFS; | ||
229 | else if (subdev->write_ecc) | ||
230 | err = subdev->write_ecc(subdev, to, size, | ||
231 | &retsize, buf, eccbuf, oobsel); | ||
232 | else | ||
233 | err = -EINVAL; | ||
234 | |||
235 | if (err) | ||
236 | break; | ||
237 | |||
238 | *retlen += retsize; | ||
239 | len -= size; | ||
240 | if (len == 0) | ||
241 | break; | ||
242 | |||
243 | err = -EINVAL; | ||
244 | buf += size; | ||
245 | if (eccbuf) | ||
246 | eccbuf += subdev->oobsize; | ||
247 | to = 0; | ||
248 | } | ||
249 | return err; | ||
250 | } | ||
251 | |||
252 | static int | ||
253 | concat_read_oob(struct mtd_info *mtd, loff_t from, size_t len, | ||
254 | size_t * retlen, u_char * buf) | ||
255 | { | ||
256 | struct mtd_concat *concat = CONCAT(mtd); | ||
257 | int err = -EINVAL; | ||
258 | int i; | ||
259 | |||
260 | *retlen = 0; | ||
261 | |||
262 | for (i = 0; i < concat->num_subdev; i++) { | ||
263 | struct mtd_info *subdev = concat->subdev[i]; | ||
264 | size_t size, retsize; | ||
265 | |||
266 | if (from >= subdev->size) { | ||
267 | /* Not destined for this subdev */ | ||
268 | size = 0; | ||
269 | from -= subdev->size; | ||
270 | continue; | ||
271 | } | ||
272 | if (from + len > subdev->size) | ||
273 | /* First part goes into this subdev */ | ||
274 | size = subdev->size - from; | ||
275 | else | ||
276 | /* Entire transaction goes into this subdev */ | ||
277 | size = len; | ||
278 | |||
279 | if (subdev->read_oob) | ||
280 | err = subdev->read_oob(subdev, from, size, | ||
281 | &retsize, buf); | ||
282 | else | ||
283 | err = -EINVAL; | ||
284 | |||
285 | if (err) | ||
286 | break; | ||
287 | |||
288 | *retlen += retsize; | ||
289 | len -= size; | ||
290 | if (len == 0) | ||
291 | break; | ||
292 | |||
293 | err = -EINVAL; | ||
294 | buf += size; | ||
295 | from = 0; | ||
296 | } | ||
297 | return err; | ||
298 | } | ||
299 | |||
300 | static int | ||
301 | concat_write_oob(struct mtd_info *mtd, loff_t to, size_t len, | ||
302 | size_t * retlen, const u_char * buf) | ||
303 | { | ||
304 | struct mtd_concat *concat = CONCAT(mtd); | ||
305 | int err = -EINVAL; | ||
306 | int i; | ||
307 | |||
308 | if (!(mtd->flags & MTD_WRITEABLE)) | ||
309 | return -EROFS; | ||
310 | |||
311 | *retlen = 0; | ||
312 | |||
313 | for (i = 0; i < concat->num_subdev; i++) { | ||
314 | struct mtd_info *subdev = concat->subdev[i]; | ||
315 | size_t size, retsize; | ||
316 | |||
317 | if (to >= subdev->size) { | ||
318 | size = 0; | ||
319 | to -= subdev->size; | ||
320 | continue; | ||
321 | } | ||
322 | if (to + len > subdev->size) | ||
323 | size = subdev->size - to; | ||
324 | else | ||
325 | size = len; | ||
326 | |||
327 | if (!(subdev->flags & MTD_WRITEABLE)) | ||
328 | err = -EROFS; | ||
329 | else if (subdev->write_oob) | ||
330 | err = subdev->write_oob(subdev, to, size, &retsize, | ||
331 | buf); | ||
332 | else | ||
333 | err = -EINVAL; | ||
334 | |||
335 | if (err) | ||
336 | break; | ||
337 | |||
338 | *retlen += retsize; | ||
339 | len -= size; | ||
340 | if (len == 0) | ||
341 | break; | ||
342 | |||
343 | err = -EINVAL; | ||
344 | buf += size; | ||
345 | to = 0; | ||
346 | } | ||
347 | return err; | ||
348 | } | ||
349 | |||
350 | static void concat_erase_callback(struct erase_info *instr) | ||
351 | { | ||
352 | wake_up((wait_queue_head_t *) instr->priv); | ||
353 | } | ||
354 | |||
355 | static int concat_dev_erase(struct mtd_info *mtd, struct erase_info *erase) | ||
356 | { | ||
357 | int err; | ||
358 | wait_queue_head_t waitq; | ||
359 | DECLARE_WAITQUEUE(wait, current); | ||
360 | |||
361 | /* | ||
362 | * This code was stol^H^H^H^Hinspired by mtdchar.c | ||
363 | */ | ||
364 | init_waitqueue_head(&waitq); | ||
365 | |||
366 | erase->mtd = mtd; | ||
367 | erase->callback = concat_erase_callback; | ||
368 | erase->priv = (unsigned long) &waitq; | ||
369 | |||
370 | /* | ||
371 | * FIXME: Allow INTERRUPTIBLE. Which means | ||
372 | * not having the wait_queue head on the stack. | ||
373 | */ | ||
374 | err = mtd->erase(mtd, erase); | ||
375 | if (!err) { | ||
376 | set_current_state(TASK_UNINTERRUPTIBLE); | ||
377 | add_wait_queue(&waitq, &wait); | ||
378 | if (erase->state != MTD_ERASE_DONE | ||
379 | && erase->state != MTD_ERASE_FAILED) | ||
380 | schedule(); | ||
381 | remove_wait_queue(&waitq, &wait); | ||
382 | set_current_state(TASK_RUNNING); | ||
383 | |||
384 | err = (erase->state == MTD_ERASE_FAILED) ? -EIO : 0; | ||
385 | } | ||
386 | return err; | ||
387 | } | ||
388 | |||
389 | static int concat_erase(struct mtd_info *mtd, struct erase_info *instr) | ||
390 | { | ||
391 | struct mtd_concat *concat = CONCAT(mtd); | ||
392 | struct mtd_info *subdev; | ||
393 | int i, err; | ||
394 | u_int32_t length, offset = 0; | ||
395 | struct erase_info *erase; | ||
396 | |||
397 | if (!(mtd->flags & MTD_WRITEABLE)) | ||
398 | return -EROFS; | ||
399 | |||
400 | if (instr->addr > concat->mtd.size) | ||
401 | return -EINVAL; | ||
402 | |||
403 | if (instr->len + instr->addr > concat->mtd.size) | ||
404 | return -EINVAL; | ||
405 | |||
406 | /* | ||
407 | * Check for proper erase block alignment of the to-be-erased area. | ||
408 | * It is easier to do this based on the super device's erase | ||
409 | * region info rather than looking at each particular sub-device | ||
410 | * in turn. | ||
411 | */ | ||
412 | if (!concat->mtd.numeraseregions) { | ||
413 | /* the easy case: device has uniform erase block size */ | ||
414 | if (instr->addr & (concat->mtd.erasesize - 1)) | ||
415 | return -EINVAL; | ||
416 | if (instr->len & (concat->mtd.erasesize - 1)) | ||
417 | return -EINVAL; | ||
418 | } else { | ||
419 | /* device has variable erase size */ | ||
420 | struct mtd_erase_region_info *erase_regions = | ||
421 | concat->mtd.eraseregions; | ||
422 | |||
423 | /* | ||
424 | * Find the erase region where the to-be-erased area begins: | ||
425 | */ | ||
426 | for (i = 0; i < concat->mtd.numeraseregions && | ||
427 | instr->addr >= erase_regions[i].offset; i++) ; | ||
428 | --i; | ||
429 | |||
430 | /* | ||
431 | * Now erase_regions[i] is the region in which the | ||
432 | * to-be-erased area begins. Verify that the starting | ||
433 | * offset is aligned to this region's erase size: | ||
434 | */ | ||
435 | if (instr->addr & (erase_regions[i].erasesize - 1)) | ||
436 | return -EINVAL; | ||
437 | |||
438 | /* | ||
439 | * now find the erase region where the to-be-erased area ends: | ||
440 | */ | ||
441 | for (; i < concat->mtd.numeraseregions && | ||
442 | (instr->addr + instr->len) >= erase_regions[i].offset; | ||
443 | ++i) ; | ||
444 | --i; | ||
445 | /* | ||
446 | * check if the ending offset is aligned to this region's erase size | ||
447 | */ | ||
448 | if ((instr->addr + instr->len) & (erase_regions[i].erasesize - | ||
449 | 1)) | ||
450 | return -EINVAL; | ||
451 | } | ||
452 | |||
453 | instr->fail_addr = 0xffffffff; | ||
454 | |||
455 | /* make a local copy of instr to avoid modifying the caller's struct */ | ||
456 | erase = kmalloc(sizeof (struct erase_info), GFP_KERNEL); | ||
457 | |||
458 | if (!erase) | ||
459 | return -ENOMEM; | ||
460 | |||
461 | *erase = *instr; | ||
462 | length = instr->len; | ||
463 | |||
464 | /* | ||
465 | * find the subdevice where the to-be-erased area begins, adjust | ||
466 | * starting offset to be relative to the subdevice start | ||
467 | */ | ||
468 | for (i = 0; i < concat->num_subdev; i++) { | ||
469 | subdev = concat->subdev[i]; | ||
470 | if (subdev->size <= erase->addr) { | ||
471 | erase->addr -= subdev->size; | ||
472 | offset += subdev->size; | ||
473 | } else { | ||
474 | break; | ||
475 | } | ||
476 | } | ||
477 | |||
478 | /* must never happen since size limit has been verified above */ | ||
479 | if (i >= concat->num_subdev) | ||
480 | BUG(); | ||
481 | |||
482 | /* now do the erase: */ | ||
483 | err = 0; | ||
484 | for (; length > 0; i++) { | ||
485 | /* loop for all subdevices affected by this request */ | ||
486 | subdev = concat->subdev[i]; /* get current subdevice */ | ||
487 | |||
488 | /* limit length to subdevice's size: */ | ||
489 | if (erase->addr + length > subdev->size) | ||
490 | erase->len = subdev->size - erase->addr; | ||
491 | else | ||
492 | erase->len = length; | ||
493 | |||
494 | if (!(subdev->flags & MTD_WRITEABLE)) { | ||
495 | err = -EROFS; | ||
496 | break; | ||
497 | } | ||
498 | length -= erase->len; | ||
499 | if ((err = concat_dev_erase(subdev, erase))) { | ||
500 | /* sanity check: should never happen since | ||
501 | * block alignment has been checked above */ | ||
502 | if (err == -EINVAL) | ||
503 | BUG(); | ||
504 | if (erase->fail_addr != 0xffffffff) | ||
505 | instr->fail_addr = erase->fail_addr + offset; | ||
506 | break; | ||
507 | } | ||
508 | /* | ||
509 | * erase->addr specifies the offset of the area to be | ||
510 | * erased *within the current subdevice*. It can be | ||
511 | * non-zero only the first time through this loop, i.e. | ||
512 | * for the first subdevice where blocks need to be erased. | ||
513 | * All the following erases must begin at the start of the | ||
514 | * current subdevice, i.e. at offset zero. | ||
515 | */ | ||
516 | erase->addr = 0; | ||
517 | offset += subdev->size; | ||
518 | } | ||
519 | instr->state = erase->state; | ||
520 | kfree(erase); | ||
521 | if (err) | ||
522 | return err; | ||
523 | |||
524 | if (instr->callback) | ||
525 | instr->callback(instr); | ||
526 | return 0; | ||
527 | } | ||
528 | |||
529 | static int concat_lock(struct mtd_info *mtd, loff_t ofs, size_t len) | ||
530 | { | ||
531 | struct mtd_concat *concat = CONCAT(mtd); | ||
532 | int i, err = -EINVAL; | ||
533 | |||
534 | if ((len + ofs) > mtd->size) | ||
535 | return -EINVAL; | ||
536 | |||
537 | for (i = 0; i < concat->num_subdev; i++) { | ||
538 | struct mtd_info *subdev = concat->subdev[i]; | ||
539 | size_t size; | ||
540 | |||
541 | if (ofs >= subdev->size) { | ||
542 | size = 0; | ||
543 | ofs -= subdev->size; | ||
544 | continue; | ||
545 | } | ||
546 | if (ofs + len > subdev->size) | ||
547 | size = subdev->size - ofs; | ||
548 | else | ||
549 | size = len; | ||
550 | |||
551 | err = subdev->lock(subdev, ofs, size); | ||
552 | |||
553 | if (err) | ||
554 | break; | ||
555 | |||
556 | len -= size; | ||
557 | if (len == 0) | ||
558 | break; | ||
559 | |||
560 | err = -EINVAL; | ||
561 | ofs = 0; | ||
562 | } | ||
563 | |||
564 | return err; | ||
565 | } | ||
566 | |||
567 | static int concat_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) | ||
568 | { | ||
569 | struct mtd_concat *concat = CONCAT(mtd); | ||
570 | int i, err = 0; | ||
571 | |||
572 | if ((len + ofs) > mtd->size) | ||
573 | return -EINVAL; | ||
574 | |||
575 | for (i = 0; i < concat->num_subdev; i++) { | ||
576 | struct mtd_info *subdev = concat->subdev[i]; | ||
577 | size_t size; | ||
578 | |||
579 | if (ofs >= subdev->size) { | ||
580 | size = 0; | ||
581 | ofs -= subdev->size; | ||
582 | continue; | ||
583 | } | ||
584 | if (ofs + len > subdev->size) | ||
585 | size = subdev->size - ofs; | ||
586 | else | ||
587 | size = len; | ||
588 | |||
589 | err = subdev->unlock(subdev, ofs, size); | ||
590 | |||
591 | if (err) | ||
592 | break; | ||
593 | |||
594 | len -= size; | ||
595 | if (len == 0) | ||
596 | break; | ||
597 | |||
598 | err = -EINVAL; | ||
599 | ofs = 0; | ||
600 | } | ||
601 | |||
602 | return err; | ||
603 | } | ||
604 | |||
605 | static void concat_sync(struct mtd_info *mtd) | ||
606 | { | ||
607 | struct mtd_concat *concat = CONCAT(mtd); | ||
608 | int i; | ||
609 | |||
610 | for (i = 0; i < concat->num_subdev; i++) { | ||
611 | struct mtd_info *subdev = concat->subdev[i]; | ||
612 | subdev->sync(subdev); | ||
613 | } | ||
614 | } | ||
615 | |||
616 | static int concat_suspend(struct mtd_info *mtd) | ||
617 | { | ||
618 | struct mtd_concat *concat = CONCAT(mtd); | ||
619 | int i, rc = 0; | ||
620 | |||
621 | for (i = 0; i < concat->num_subdev; i++) { | ||
622 | struct mtd_info *subdev = concat->subdev[i]; | ||
623 | if ((rc = subdev->suspend(subdev)) < 0) | ||
624 | return rc; | ||
625 | } | ||
626 | return rc; | ||
627 | } | ||
628 | |||
629 | static void concat_resume(struct mtd_info *mtd) | ||
630 | { | ||
631 | struct mtd_concat *concat = CONCAT(mtd); | ||
632 | int i; | ||
633 | |||
634 | for (i = 0; i < concat->num_subdev; i++) { | ||
635 | struct mtd_info *subdev = concat->subdev[i]; | ||
636 | subdev->resume(subdev); | ||
637 | } | ||
638 | } | ||
639 | |||
640 | /* | ||
641 | * This function constructs a virtual MTD device by concatenating | ||
642 | * num_devs MTD devices. A pointer to the new device object is | ||
643 | * stored to *new_dev upon success. This function does _not_ | ||
644 | * register any devices: this is the caller's responsibility. | ||
645 | */ | ||
646 | struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to concatenate */ | ||
647 | int num_devs, /* number of subdevices */ | ||
648 | char *name) | ||
649 | { /* name for the new device */ | ||
650 | int i; | ||
651 | size_t size; | ||
652 | struct mtd_concat *concat; | ||
653 | u_int32_t max_erasesize, curr_erasesize; | ||
654 | int num_erase_region; | ||
655 | |||
656 | printk(KERN_NOTICE "Concatenating MTD devices:\n"); | ||
657 | for (i = 0; i < num_devs; i++) | ||
658 | printk(KERN_NOTICE "(%d): \"%s\"\n", i, subdev[i]->name); | ||
659 | printk(KERN_NOTICE "into device \"%s\"\n", name); | ||
660 | |||
661 | /* allocate the device structure */ | ||
662 | size = SIZEOF_STRUCT_MTD_CONCAT(num_devs); | ||
663 | concat = kmalloc(size, GFP_KERNEL); | ||
664 | if (!concat) { | ||
665 | printk | ||
666 | ("memory allocation error while creating concatenated device \"%s\"\n", | ||
667 | name); | ||
668 | return NULL; | ||
669 | } | ||
670 | memset(concat, 0, size); | ||
671 | concat->subdev = (struct mtd_info **) (concat + 1); | ||
672 | |||
673 | /* | ||
674 | * Set up the new "super" device's MTD object structure, check for | ||
675 | * incompatibilites between the subdevices. | ||
676 | */ | ||
677 | concat->mtd.type = subdev[0]->type; | ||
678 | concat->mtd.flags = subdev[0]->flags; | ||
679 | concat->mtd.size = subdev[0]->size; | ||
680 | concat->mtd.erasesize = subdev[0]->erasesize; | ||
681 | concat->mtd.oobblock = subdev[0]->oobblock; | ||
682 | concat->mtd.oobsize = subdev[0]->oobsize; | ||
683 | concat->mtd.ecctype = subdev[0]->ecctype; | ||
684 | concat->mtd.eccsize = subdev[0]->eccsize; | ||
685 | if (subdev[0]->read_ecc) | ||
686 | concat->mtd.read_ecc = concat_read_ecc; | ||
687 | if (subdev[0]->write_ecc) | ||
688 | concat->mtd.write_ecc = concat_write_ecc; | ||
689 | if (subdev[0]->read_oob) | ||
690 | concat->mtd.read_oob = concat_read_oob; | ||
691 | if (subdev[0]->write_oob) | ||
692 | concat->mtd.write_oob = concat_write_oob; | ||
693 | |||
694 | concat->subdev[0] = subdev[0]; | ||
695 | |||
696 | for (i = 1; i < num_devs; i++) { | ||
697 | if (concat->mtd.type != subdev[i]->type) { | ||
698 | kfree(concat); | ||
699 | printk("Incompatible device type on \"%s\"\n", | ||
700 | subdev[i]->name); | ||
701 | return NULL; | ||
702 | } | ||
703 | if (concat->mtd.flags != subdev[i]->flags) { | ||
704 | /* | ||
705 | * Expect all flags except MTD_WRITEABLE to be | ||
706 | * equal on all subdevices. | ||
707 | */ | ||
708 | if ((concat->mtd.flags ^ subdev[i]-> | ||
709 | flags) & ~MTD_WRITEABLE) { | ||
710 | kfree(concat); | ||
711 | printk("Incompatible device flags on \"%s\"\n", | ||
712 | subdev[i]->name); | ||
713 | return NULL; | ||
714 | } else | ||
715 | /* if writeable attribute differs, | ||
716 | make super device writeable */ | ||
717 | concat->mtd.flags |= | ||
718 | subdev[i]->flags & MTD_WRITEABLE; | ||
719 | } | ||
720 | concat->mtd.size += subdev[i]->size; | ||
721 | if (concat->mtd.oobblock != subdev[i]->oobblock || | ||
722 | concat->mtd.oobsize != subdev[i]->oobsize || | ||
723 | concat->mtd.ecctype != subdev[i]->ecctype || | ||
724 | concat->mtd.eccsize != subdev[i]->eccsize || | ||
725 | !concat->mtd.read_ecc != !subdev[i]->read_ecc || | ||
726 | !concat->mtd.write_ecc != !subdev[i]->write_ecc || | ||
727 | !concat->mtd.read_oob != !subdev[i]->read_oob || | ||
728 | !concat->mtd.write_oob != !subdev[i]->write_oob) { | ||
729 | kfree(concat); | ||
730 | printk("Incompatible OOB or ECC data on \"%s\"\n", | ||
731 | subdev[i]->name); | ||
732 | return NULL; | ||
733 | } | ||
734 | concat->subdev[i] = subdev[i]; | ||
735 | |||
736 | } | ||
737 | |||
738 | concat->num_subdev = num_devs; | ||
739 | concat->mtd.name = name; | ||
740 | |||
741 | /* | ||
742 | * NOTE: for now, we do not provide any readv()/writev() methods | ||
743 | * because they are messy to implement and they are not | ||
744 | * used to a great extent anyway. | ||
745 | */ | ||
746 | concat->mtd.erase = concat_erase; | ||
747 | concat->mtd.read = concat_read; | ||
748 | concat->mtd.write = concat_write; | ||
749 | concat->mtd.sync = concat_sync; | ||
750 | concat->mtd.lock = concat_lock; | ||
751 | concat->mtd.unlock = concat_unlock; | ||
752 | concat->mtd.suspend = concat_suspend; | ||
753 | concat->mtd.resume = concat_resume; | ||
754 | |||
755 | /* | ||
756 | * Combine the erase block size info of the subdevices: | ||
757 | * | ||
758 | * first, walk the map of the new device and see how | ||
759 | * many changes in erase size we have | ||
760 | */ | ||
761 | max_erasesize = curr_erasesize = subdev[0]->erasesize; | ||
762 | num_erase_region = 1; | ||
763 | for (i = 0; i < num_devs; i++) { | ||
764 | if (subdev[i]->numeraseregions == 0) { | ||
765 | /* current subdevice has uniform erase size */ | ||
766 | if (subdev[i]->erasesize != curr_erasesize) { | ||
767 | /* if it differs from the last subdevice's erase size, count it */ | ||
768 | ++num_erase_region; | ||
769 | curr_erasesize = subdev[i]->erasesize; | ||
770 | if (curr_erasesize > max_erasesize) | ||
771 | max_erasesize = curr_erasesize; | ||
772 | } | ||
773 | } else { | ||
774 | /* current subdevice has variable erase size */ | ||
775 | int j; | ||
776 | for (j = 0; j < subdev[i]->numeraseregions; j++) { | ||
777 | |||
778 | /* walk the list of erase regions, count any changes */ | ||
779 | if (subdev[i]->eraseregions[j].erasesize != | ||
780 | curr_erasesize) { | ||
781 | ++num_erase_region; | ||
782 | curr_erasesize = | ||
783 | subdev[i]->eraseregions[j]. | ||
784 | erasesize; | ||
785 | if (curr_erasesize > max_erasesize) | ||
786 | max_erasesize = curr_erasesize; | ||
787 | } | ||
788 | } | ||
789 | } | ||
790 | } | ||
791 | |||
792 | if (num_erase_region == 1) { | ||
793 | /* | ||
794 | * All subdevices have the same uniform erase size. | ||
795 | * This is easy: | ||
796 | */ | ||
797 | concat->mtd.erasesize = curr_erasesize; | ||
798 | concat->mtd.numeraseregions = 0; | ||
799 | } else { | ||
800 | /* | ||
801 | * erase block size varies across the subdevices: allocate | ||
802 | * space to store the data describing the variable erase regions | ||
803 | */ | ||
804 | struct mtd_erase_region_info *erase_region_p; | ||
805 | u_int32_t begin, position; | ||
806 | |||
807 | concat->mtd.erasesize = max_erasesize; | ||
808 | concat->mtd.numeraseregions = num_erase_region; | ||
809 | concat->mtd.eraseregions = erase_region_p = | ||
810 | kmalloc(num_erase_region * | ||
811 | sizeof (struct mtd_erase_region_info), GFP_KERNEL); | ||
812 | if (!erase_region_p) { | ||
813 | kfree(concat); | ||
814 | printk | ||
815 | ("memory allocation error while creating erase region list" | ||
816 | " for device \"%s\"\n", name); | ||
817 | return NULL; | ||
818 | } | ||
819 | |||
820 | /* | ||
821 | * walk the map of the new device once more and fill in | ||
822 | * in erase region info: | ||
823 | */ | ||
824 | curr_erasesize = subdev[0]->erasesize; | ||
825 | begin = position = 0; | ||
826 | for (i = 0; i < num_devs; i++) { | ||
827 | if (subdev[i]->numeraseregions == 0) { | ||
828 | /* current subdevice has uniform erase size */ | ||
829 | if (subdev[i]->erasesize != curr_erasesize) { | ||
830 | /* | ||
831 | * fill in an mtd_erase_region_info structure for the area | ||
832 | * we have walked so far: | ||
833 | */ | ||
834 | erase_region_p->offset = begin; | ||
835 | erase_region_p->erasesize = | ||
836 | curr_erasesize; | ||
837 | erase_region_p->numblocks = | ||
838 | (position - begin) / curr_erasesize; | ||
839 | begin = position; | ||
840 | |||
841 | curr_erasesize = subdev[i]->erasesize; | ||
842 | ++erase_region_p; | ||
843 | } | ||
844 | position += subdev[i]->size; | ||
845 | } else { | ||
846 | /* current subdevice has variable erase size */ | ||
847 | int j; | ||
848 | for (j = 0; j < subdev[i]->numeraseregions; j++) { | ||
849 | /* walk the list of erase regions, count any changes */ | ||
850 | if (subdev[i]->eraseregions[j]. | ||
851 | erasesize != curr_erasesize) { | ||
852 | erase_region_p->offset = begin; | ||
853 | erase_region_p->erasesize = | ||
854 | curr_erasesize; | ||
855 | erase_region_p->numblocks = | ||
856 | (position - | ||
857 | begin) / curr_erasesize; | ||
858 | begin = position; | ||
859 | |||
860 | curr_erasesize = | ||
861 | subdev[i]->eraseregions[j]. | ||
862 | erasesize; | ||
863 | ++erase_region_p; | ||
864 | } | ||
865 | position += | ||
866 | subdev[i]->eraseregions[j]. | ||
867 | numblocks * curr_erasesize; | ||
868 | } | ||
869 | } | ||
870 | } | ||
871 | /* Now write the final entry */ | ||
872 | erase_region_p->offset = begin; | ||
873 | erase_region_p->erasesize = curr_erasesize; | ||
874 | erase_region_p->numblocks = (position - begin) / curr_erasesize; | ||
875 | } | ||
876 | |||
877 | return &concat->mtd; | ||
878 | } | ||
879 | |||
880 | /* | ||
881 | * This function destroys an MTD object obtained from concat_mtd_devs() | ||
882 | */ | ||
883 | |||
884 | void mtd_concat_destroy(struct mtd_info *mtd) | ||
885 | { | ||
886 | struct mtd_concat *concat = CONCAT(mtd); | ||
887 | if (concat->mtd.numeraseregions) | ||
888 | kfree(concat->mtd.eraseregions); | ||
889 | kfree(concat); | ||
890 | } | ||
891 | |||
892 | EXPORT_SYMBOL(mtd_concat_create); | ||
893 | EXPORT_SYMBOL(mtd_concat_destroy); | ||
894 | |||
895 | MODULE_LICENSE("GPL"); | ||
896 | MODULE_AUTHOR("Robert Kaiser <rkaiser@sysgo.de>"); | ||
897 | MODULE_DESCRIPTION("Generic support for concatenating of MTD devices"); | ||