Linux-2.6.12-rc2
[linux-flexiantxendom0-natty.git] / drivers / mtd / mtdconcat.c
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");