11da177e4SLinus Torvalds /* 21da177e4SLinus Torvalds * MTD device concatenation layer 31da177e4SLinus Torvalds * 41da177e4SLinus Torvalds * (C) 2002 Robert Kaiser <rkaiser@sysgo.de> 51da177e4SLinus Torvalds * 61da177e4SLinus Torvalds * NAND support by Christian Gan <cgan@iders.ca> 71da177e4SLinus Torvalds * 81da177e4SLinus Torvalds * This code is GPL 91da177e4SLinus Torvalds */ 101da177e4SLinus Torvalds 111da177e4SLinus Torvalds #include <linux/kernel.h> 1215fdc52fSThomas Gleixner #include <linux/module.h> 131da177e4SLinus Torvalds #include <linux/slab.h> 1415fdc52fSThomas Gleixner #include <linux/sched.h> 1515fdc52fSThomas Gleixner #include <linux/types.h> 1615fdc52fSThomas Gleixner 171da177e4SLinus Torvalds #include <linux/mtd/mtd.h> 181da177e4SLinus Torvalds #include <linux/mtd/concat.h> 191da177e4SLinus Torvalds 206c8b44abSAndrew Morton #include <asm/div64.h> 216c8b44abSAndrew Morton 221da177e4SLinus Torvalds /* 231da177e4SLinus Torvalds * Our storage structure: 241da177e4SLinus Torvalds * Subdev points to an array of pointers to struct mtd_info objects 251da177e4SLinus Torvalds * which is allocated along with this structure 261da177e4SLinus Torvalds * 271da177e4SLinus Torvalds */ 281da177e4SLinus Torvalds struct mtd_concat { 291da177e4SLinus Torvalds struct mtd_info mtd; 301da177e4SLinus Torvalds int num_subdev; 311da177e4SLinus Torvalds struct mtd_info **subdev; 321da177e4SLinus Torvalds }; 331da177e4SLinus Torvalds 341da177e4SLinus Torvalds /* 351da177e4SLinus Torvalds * how to calculate the size required for the above structure, 361da177e4SLinus Torvalds * including the pointer array subdev points to: 371da177e4SLinus Torvalds */ 381da177e4SLinus Torvalds #define SIZEOF_STRUCT_MTD_CONCAT(num_subdev) \ 391da177e4SLinus Torvalds ((sizeof(struct mtd_concat) + (num_subdev) * sizeof(struct mtd_info *))) 401da177e4SLinus Torvalds 411da177e4SLinus Torvalds /* 421da177e4SLinus Torvalds * Given a pointer to the MTD object in the mtd_concat structure, 431da177e4SLinus Torvalds * we can retrieve the pointer to that structure with this macro. 441da177e4SLinus Torvalds */ 451da177e4SLinus Torvalds #define CONCAT(x) ((struct mtd_concat *)(x)) 461da177e4SLinus Torvalds 471da177e4SLinus Torvalds /* 481da177e4SLinus Torvalds * MTD methods which look up the relevant subdevice, translate the 491da177e4SLinus Torvalds * effective address and pass through to the subdevice. 501da177e4SLinus Torvalds */ 511da177e4SLinus Torvalds 521da177e4SLinus Torvalds static int 531da177e4SLinus Torvalds concat_read(struct mtd_info *mtd, loff_t from, size_t len, 541da177e4SLinus Torvalds size_t * retlen, u_char * buf) 551da177e4SLinus Torvalds { 561da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 57f1a28c02SThomas Gleixner int ret = 0, err; 581da177e4SLinus Torvalds int i; 591da177e4SLinus Torvalds 601da177e4SLinus Torvalds *retlen = 0; 611da177e4SLinus Torvalds 621da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 631da177e4SLinus Torvalds struct mtd_info *subdev = concat->subdev[i]; 641da177e4SLinus Torvalds size_t size, retsize; 651da177e4SLinus Torvalds 661da177e4SLinus Torvalds if (from >= subdev->size) { 671da177e4SLinus Torvalds /* Not destined for this subdev */ 681da177e4SLinus Torvalds size = 0; 691da177e4SLinus Torvalds from -= subdev->size; 701da177e4SLinus Torvalds continue; 711da177e4SLinus Torvalds } 721da177e4SLinus Torvalds if (from + len > subdev->size) 731da177e4SLinus Torvalds /* First part goes into this subdev */ 741da177e4SLinus Torvalds size = subdev->size - from; 751da177e4SLinus Torvalds else 761da177e4SLinus Torvalds /* Entire transaction goes into this subdev */ 771da177e4SLinus Torvalds size = len; 781da177e4SLinus Torvalds 791da177e4SLinus Torvalds err = subdev->read(subdev, from, size, &retsize, buf); 801da177e4SLinus Torvalds 819a1fcdfdSThomas Gleixner /* Save information about bitflips! */ 82f1a28c02SThomas Gleixner if (unlikely(err)) { 83f1a28c02SThomas Gleixner if (err == -EBADMSG) { 84f1a28c02SThomas Gleixner mtd->ecc_stats.failed++; 859a1fcdfdSThomas Gleixner ret = err; 86f1a28c02SThomas Gleixner } else if (err == -EUCLEAN) { 87f1a28c02SThomas Gleixner mtd->ecc_stats.corrected++; 88f1a28c02SThomas Gleixner /* Do not overwrite -EBADMSG !! */ 89f1a28c02SThomas Gleixner if (!ret) 909a1fcdfdSThomas Gleixner ret = err; 91f1a28c02SThomas Gleixner } else 92f1a28c02SThomas Gleixner return err; 939a1fcdfdSThomas Gleixner } 949a1fcdfdSThomas Gleixner 951da177e4SLinus Torvalds *retlen += retsize; 961da177e4SLinus Torvalds len -= size; 971da177e4SLinus Torvalds if (len == 0) 98f1a28c02SThomas Gleixner return ret; 991da177e4SLinus Torvalds 1001da177e4SLinus Torvalds buf += size; 1011da177e4SLinus Torvalds from = 0; 1021da177e4SLinus Torvalds } 103f1a28c02SThomas Gleixner return -EINVAL; 1041da177e4SLinus Torvalds } 1051da177e4SLinus Torvalds 1061da177e4SLinus Torvalds static int 1071da177e4SLinus Torvalds concat_write(struct mtd_info *mtd, loff_t to, size_t len, 1081da177e4SLinus Torvalds size_t * retlen, const u_char * buf) 1091da177e4SLinus Torvalds { 1101da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 1111da177e4SLinus Torvalds int err = -EINVAL; 1121da177e4SLinus Torvalds int i; 1131da177e4SLinus Torvalds 1141da177e4SLinus Torvalds if (!(mtd->flags & MTD_WRITEABLE)) 1151da177e4SLinus Torvalds return -EROFS; 1161da177e4SLinus Torvalds 1171da177e4SLinus Torvalds *retlen = 0; 1181da177e4SLinus Torvalds 1191da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 1201da177e4SLinus Torvalds struct mtd_info *subdev = concat->subdev[i]; 1211da177e4SLinus Torvalds size_t size, retsize; 1221da177e4SLinus Torvalds 1231da177e4SLinus Torvalds if (to >= subdev->size) { 1241da177e4SLinus Torvalds size = 0; 1251da177e4SLinus Torvalds to -= subdev->size; 1261da177e4SLinus Torvalds continue; 1271da177e4SLinus Torvalds } 1281da177e4SLinus Torvalds if (to + len > subdev->size) 1291da177e4SLinus Torvalds size = subdev->size - to; 1301da177e4SLinus Torvalds else 1311da177e4SLinus Torvalds size = len; 1321da177e4SLinus Torvalds 1331da177e4SLinus Torvalds if (!(subdev->flags & MTD_WRITEABLE)) 1341da177e4SLinus Torvalds err = -EROFS; 1351da177e4SLinus Torvalds else 1361da177e4SLinus Torvalds err = subdev->write(subdev, to, size, &retsize, buf); 1371da177e4SLinus Torvalds 1381da177e4SLinus Torvalds if (err) 1391da177e4SLinus Torvalds break; 1401da177e4SLinus Torvalds 1411da177e4SLinus Torvalds *retlen += retsize; 1421da177e4SLinus Torvalds len -= size; 1431da177e4SLinus Torvalds if (len == 0) 1441da177e4SLinus Torvalds break; 1451da177e4SLinus Torvalds 1461da177e4SLinus Torvalds err = -EINVAL; 1471da177e4SLinus Torvalds buf += size; 1481da177e4SLinus Torvalds to = 0; 1491da177e4SLinus Torvalds } 1501da177e4SLinus Torvalds return err; 1511da177e4SLinus Torvalds } 1521da177e4SLinus Torvalds 1531da177e4SLinus Torvalds static int 1549d8522dfSThomas Gleixner concat_writev(struct mtd_info *mtd, const struct kvec *vecs, 1559d8522dfSThomas Gleixner unsigned long count, loff_t to, size_t * retlen) 156e8d32937SAlexander Belyakov { 157e8d32937SAlexander Belyakov struct mtd_concat *concat = CONCAT(mtd); 158e8d32937SAlexander Belyakov struct kvec *vecs_copy; 159e8d32937SAlexander Belyakov unsigned long entry_low, entry_high; 160e8d32937SAlexander Belyakov size_t total_len = 0; 161e8d32937SAlexander Belyakov int i; 162e8d32937SAlexander Belyakov int err = -EINVAL; 163e8d32937SAlexander Belyakov 164e8d32937SAlexander Belyakov if (!(mtd->flags & MTD_WRITEABLE)) 165e8d32937SAlexander Belyakov return -EROFS; 166e8d32937SAlexander Belyakov 167e8d32937SAlexander Belyakov *retlen = 0; 168e8d32937SAlexander Belyakov 169e8d32937SAlexander Belyakov /* Calculate total length of data */ 170e8d32937SAlexander Belyakov for (i = 0; i < count; i++) 171e8d32937SAlexander Belyakov total_len += vecs[i].iov_len; 172e8d32937SAlexander Belyakov 173e8d32937SAlexander Belyakov /* Do not allow write past end of device */ 174e8d32937SAlexander Belyakov if ((to + total_len) > mtd->size) 175e8d32937SAlexander Belyakov return -EINVAL; 176e8d32937SAlexander Belyakov 177e8d32937SAlexander Belyakov /* Check alignment */ 17828318776SJoern Engel if (mtd->writesize > 1) { 1790bf9733dSDavid Woodhouse uint64_t __to = to; 18028318776SJoern Engel if (do_div(__to, mtd->writesize) || (total_len % mtd->writesize)) 181e8d32937SAlexander Belyakov return -EINVAL; 1826c8b44abSAndrew Morton } 183e8d32937SAlexander Belyakov 184e8d32937SAlexander Belyakov /* make a copy of vecs */ 185e8d32937SAlexander Belyakov vecs_copy = kmalloc(sizeof(struct kvec) * count, GFP_KERNEL); 186e8d32937SAlexander Belyakov if (!vecs_copy) 187e8d32937SAlexander Belyakov return -ENOMEM; 188e8d32937SAlexander Belyakov memcpy(vecs_copy, vecs, sizeof(struct kvec) * count); 189e8d32937SAlexander Belyakov 190e8d32937SAlexander Belyakov entry_low = 0; 191e8d32937SAlexander Belyakov for (i = 0; i < concat->num_subdev; i++) { 192e8d32937SAlexander Belyakov struct mtd_info *subdev = concat->subdev[i]; 193e8d32937SAlexander Belyakov size_t size, wsize, retsize, old_iov_len; 194e8d32937SAlexander Belyakov 195e8d32937SAlexander Belyakov if (to >= subdev->size) { 196e8d32937SAlexander Belyakov to -= subdev->size; 197e8d32937SAlexander Belyakov continue; 198e8d32937SAlexander Belyakov } 199e8d32937SAlexander Belyakov 200e8d32937SAlexander Belyakov size = min(total_len, (size_t)(subdev->size - to)); 201e8d32937SAlexander Belyakov wsize = size; /* store for future use */ 202e8d32937SAlexander Belyakov 203e8d32937SAlexander Belyakov entry_high = entry_low; 204e8d32937SAlexander Belyakov while (entry_high < count) { 205e8d32937SAlexander Belyakov if (size <= vecs_copy[entry_high].iov_len) 206e8d32937SAlexander Belyakov break; 207e8d32937SAlexander Belyakov size -= vecs_copy[entry_high++].iov_len; 208e8d32937SAlexander Belyakov } 209e8d32937SAlexander Belyakov 210e8d32937SAlexander Belyakov old_iov_len = vecs_copy[entry_high].iov_len; 211e8d32937SAlexander Belyakov vecs_copy[entry_high].iov_len = size; 212e8d32937SAlexander Belyakov 213e8d32937SAlexander Belyakov if (!(subdev->flags & MTD_WRITEABLE)) 214e8d32937SAlexander Belyakov err = -EROFS; 215e8d32937SAlexander Belyakov else 216e8d32937SAlexander Belyakov err = subdev->writev(subdev, &vecs_copy[entry_low], 217e8d32937SAlexander Belyakov entry_high - entry_low + 1, to, &retsize); 218e8d32937SAlexander Belyakov 219e8d32937SAlexander Belyakov vecs_copy[entry_high].iov_len = old_iov_len - size; 220e8d32937SAlexander Belyakov vecs_copy[entry_high].iov_base += size; 221e8d32937SAlexander Belyakov 222e8d32937SAlexander Belyakov entry_low = entry_high; 223e8d32937SAlexander Belyakov 224e8d32937SAlexander Belyakov if (err) 225e8d32937SAlexander Belyakov break; 226e8d32937SAlexander Belyakov 227e8d32937SAlexander Belyakov *retlen += retsize; 228e8d32937SAlexander Belyakov total_len -= wsize; 229e8d32937SAlexander Belyakov 230e8d32937SAlexander Belyakov if (total_len == 0) 231e8d32937SAlexander Belyakov break; 232e8d32937SAlexander Belyakov 233e8d32937SAlexander Belyakov err = -EINVAL; 234e8d32937SAlexander Belyakov to = 0; 235e8d32937SAlexander Belyakov } 236e8d32937SAlexander Belyakov 237e8d32937SAlexander Belyakov kfree(vecs_copy); 238e8d32937SAlexander Belyakov return err; 239e8d32937SAlexander Belyakov } 240e8d32937SAlexander Belyakov 241e8d32937SAlexander Belyakov static int 2428593fbc6SThomas Gleixner concat_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) 2431da177e4SLinus Torvalds { 2441da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 2458593fbc6SThomas Gleixner struct mtd_oob_ops devops = *ops; 246f1a28c02SThomas Gleixner int i, err, ret = 0; 2471da177e4SLinus Torvalds 2487014568bSVitaly Wool ops->retlen = ops->oobretlen = 0; 2491da177e4SLinus Torvalds 2501da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 2511da177e4SLinus Torvalds struct mtd_info *subdev = concat->subdev[i]; 2521da177e4SLinus Torvalds 2531da177e4SLinus Torvalds if (from >= subdev->size) { 2541da177e4SLinus Torvalds from -= subdev->size; 2551da177e4SLinus Torvalds continue; 2561da177e4SLinus Torvalds } 2571da177e4SLinus Torvalds 2588593fbc6SThomas Gleixner /* partial read ? */ 2598593fbc6SThomas Gleixner if (from + devops.len > subdev->size) 2608593fbc6SThomas Gleixner devops.len = subdev->size - from; 2611da177e4SLinus Torvalds 2628593fbc6SThomas Gleixner err = subdev->read_oob(subdev, from, &devops); 2638593fbc6SThomas Gleixner ops->retlen += devops.retlen; 2647014568bSVitaly Wool ops->oobretlen += devops.oobretlen; 265f1a28c02SThomas Gleixner 266f1a28c02SThomas Gleixner /* Save information about bitflips! */ 267f1a28c02SThomas Gleixner if (unlikely(err)) { 268f1a28c02SThomas Gleixner if (err == -EBADMSG) { 269f1a28c02SThomas Gleixner mtd->ecc_stats.failed++; 270f1a28c02SThomas Gleixner ret = err; 271f1a28c02SThomas Gleixner } else if (err == -EUCLEAN) { 272f1a28c02SThomas Gleixner mtd->ecc_stats.corrected++; 273f1a28c02SThomas Gleixner /* Do not overwrite -EBADMSG !! */ 274f1a28c02SThomas Gleixner if (!ret) 275f1a28c02SThomas Gleixner ret = err; 276f1a28c02SThomas Gleixner } else 2778593fbc6SThomas Gleixner return err; 278f1a28c02SThomas Gleixner } 2791da177e4SLinus Torvalds 2807014568bSVitaly Wool if (devops.datbuf) { 2818593fbc6SThomas Gleixner devops.len = ops->len - ops->retlen; 2828593fbc6SThomas Gleixner if (!devops.len) 283f1a28c02SThomas Gleixner return ret; 2848593fbc6SThomas Gleixner devops.datbuf += devops.retlen; 2857014568bSVitaly Wool } 2867014568bSVitaly Wool if (devops.oobbuf) { 2877014568bSVitaly Wool devops.ooblen = ops->ooblen - ops->oobretlen; 2887014568bSVitaly Wool if (!devops.ooblen) 2897014568bSVitaly Wool return ret; 2907014568bSVitaly Wool devops.oobbuf += ops->oobretlen; 2917014568bSVitaly Wool } 2928593fbc6SThomas Gleixner 2931da177e4SLinus Torvalds from = 0; 2941da177e4SLinus Torvalds } 2958593fbc6SThomas Gleixner return -EINVAL; 2961da177e4SLinus Torvalds } 2971da177e4SLinus Torvalds 2981da177e4SLinus Torvalds static int 2998593fbc6SThomas Gleixner concat_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops) 3001da177e4SLinus Torvalds { 3011da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 3028593fbc6SThomas Gleixner struct mtd_oob_ops devops = *ops; 3038593fbc6SThomas Gleixner int i, err; 3041da177e4SLinus Torvalds 3051da177e4SLinus Torvalds if (!(mtd->flags & MTD_WRITEABLE)) 3061da177e4SLinus Torvalds return -EROFS; 3071da177e4SLinus Torvalds 3088593fbc6SThomas Gleixner ops->retlen = 0; 3091da177e4SLinus Torvalds 3101da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 3111da177e4SLinus Torvalds struct mtd_info *subdev = concat->subdev[i]; 3121da177e4SLinus Torvalds 3131da177e4SLinus Torvalds if (to >= subdev->size) { 3141da177e4SLinus Torvalds to -= subdev->size; 3151da177e4SLinus Torvalds continue; 3161da177e4SLinus Torvalds } 3171da177e4SLinus Torvalds 3188593fbc6SThomas Gleixner /* partial write ? */ 3198593fbc6SThomas Gleixner if (to + devops.len > subdev->size) 3208593fbc6SThomas Gleixner devops.len = subdev->size - to; 3211da177e4SLinus Torvalds 3228593fbc6SThomas Gleixner err = subdev->write_oob(subdev, to, &devops); 3238593fbc6SThomas Gleixner ops->retlen += devops.retlen; 3241da177e4SLinus Torvalds if (err) 3258593fbc6SThomas Gleixner return err; 3261da177e4SLinus Torvalds 3277014568bSVitaly Wool if (devops.datbuf) { 3288593fbc6SThomas Gleixner devops.len = ops->len - ops->retlen; 3298593fbc6SThomas Gleixner if (!devops.len) 3308593fbc6SThomas Gleixner return 0; 3318593fbc6SThomas Gleixner devops.datbuf += devops.retlen; 3327014568bSVitaly Wool } 3337014568bSVitaly Wool if (devops.oobbuf) { 3347014568bSVitaly Wool devops.ooblen = ops->ooblen - ops->oobretlen; 3357014568bSVitaly Wool if (!devops.ooblen) 3367014568bSVitaly Wool return 0; 3377014568bSVitaly Wool devops.oobbuf += devops.oobretlen; 3387014568bSVitaly Wool } 3391da177e4SLinus Torvalds to = 0; 3401da177e4SLinus Torvalds } 3418593fbc6SThomas Gleixner return -EINVAL; 3421da177e4SLinus Torvalds } 3431da177e4SLinus Torvalds 3441da177e4SLinus Torvalds static void concat_erase_callback(struct erase_info *instr) 3451da177e4SLinus Torvalds { 3461da177e4SLinus Torvalds wake_up((wait_queue_head_t *) instr->priv); 3471da177e4SLinus Torvalds } 3481da177e4SLinus Torvalds 3491da177e4SLinus Torvalds static int concat_dev_erase(struct mtd_info *mtd, struct erase_info *erase) 3501da177e4SLinus Torvalds { 3511da177e4SLinus Torvalds int err; 3521da177e4SLinus Torvalds wait_queue_head_t waitq; 3531da177e4SLinus Torvalds DECLARE_WAITQUEUE(wait, current); 3541da177e4SLinus Torvalds 3551da177e4SLinus Torvalds /* 3561da177e4SLinus Torvalds * This code was stol^H^H^H^Hinspired by mtdchar.c 3571da177e4SLinus Torvalds */ 3581da177e4SLinus Torvalds init_waitqueue_head(&waitq); 3591da177e4SLinus Torvalds 3601da177e4SLinus Torvalds erase->mtd = mtd; 3611da177e4SLinus Torvalds erase->callback = concat_erase_callback; 3621da177e4SLinus Torvalds erase->priv = (unsigned long) &waitq; 3631da177e4SLinus Torvalds 3641da177e4SLinus Torvalds /* 3651da177e4SLinus Torvalds * FIXME: Allow INTERRUPTIBLE. Which means 3661da177e4SLinus Torvalds * not having the wait_queue head on the stack. 3671da177e4SLinus Torvalds */ 3681da177e4SLinus Torvalds err = mtd->erase(mtd, erase); 3691da177e4SLinus Torvalds if (!err) { 3701da177e4SLinus Torvalds set_current_state(TASK_UNINTERRUPTIBLE); 3711da177e4SLinus Torvalds add_wait_queue(&waitq, &wait); 3721da177e4SLinus Torvalds if (erase->state != MTD_ERASE_DONE 3731da177e4SLinus Torvalds && erase->state != MTD_ERASE_FAILED) 3741da177e4SLinus Torvalds schedule(); 3751da177e4SLinus Torvalds remove_wait_queue(&waitq, &wait); 3761da177e4SLinus Torvalds set_current_state(TASK_RUNNING); 3771da177e4SLinus Torvalds 3781da177e4SLinus Torvalds err = (erase->state == MTD_ERASE_FAILED) ? -EIO : 0; 3791da177e4SLinus Torvalds } 3801da177e4SLinus Torvalds return err; 3811da177e4SLinus Torvalds } 3821da177e4SLinus Torvalds 3831da177e4SLinus Torvalds static int concat_erase(struct mtd_info *mtd, struct erase_info *instr) 3841da177e4SLinus Torvalds { 3851da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 3861da177e4SLinus Torvalds struct mtd_info *subdev; 3871da177e4SLinus Torvalds int i, err; 3881da177e4SLinus Torvalds u_int32_t length, offset = 0; 3891da177e4SLinus Torvalds struct erase_info *erase; 3901da177e4SLinus Torvalds 3911da177e4SLinus Torvalds if (!(mtd->flags & MTD_WRITEABLE)) 3921da177e4SLinus Torvalds return -EROFS; 3931da177e4SLinus Torvalds 3941da177e4SLinus Torvalds if (instr->addr > concat->mtd.size) 3951da177e4SLinus Torvalds return -EINVAL; 3961da177e4SLinus Torvalds 3971da177e4SLinus Torvalds if (instr->len + instr->addr > concat->mtd.size) 3981da177e4SLinus Torvalds return -EINVAL; 3991da177e4SLinus Torvalds 4001da177e4SLinus Torvalds /* 4011da177e4SLinus Torvalds * Check for proper erase block alignment of the to-be-erased area. 4021da177e4SLinus Torvalds * It is easier to do this based on the super device's erase 4031da177e4SLinus Torvalds * region info rather than looking at each particular sub-device 4041da177e4SLinus Torvalds * in turn. 4051da177e4SLinus Torvalds */ 4061da177e4SLinus Torvalds if (!concat->mtd.numeraseregions) { 4071da177e4SLinus Torvalds /* the easy case: device has uniform erase block size */ 4081da177e4SLinus Torvalds if (instr->addr & (concat->mtd.erasesize - 1)) 4091da177e4SLinus Torvalds return -EINVAL; 4101da177e4SLinus Torvalds if (instr->len & (concat->mtd.erasesize - 1)) 4111da177e4SLinus Torvalds return -EINVAL; 4121da177e4SLinus Torvalds } else { 4131da177e4SLinus Torvalds /* device has variable erase size */ 4141da177e4SLinus Torvalds struct mtd_erase_region_info *erase_regions = 4151da177e4SLinus Torvalds concat->mtd.eraseregions; 4161da177e4SLinus Torvalds 4171da177e4SLinus Torvalds /* 4181da177e4SLinus Torvalds * Find the erase region where the to-be-erased area begins: 4191da177e4SLinus Torvalds */ 4201da177e4SLinus Torvalds for (i = 0; i < concat->mtd.numeraseregions && 4211da177e4SLinus Torvalds instr->addr >= erase_regions[i].offset; i++) ; 4221da177e4SLinus Torvalds --i; 4231da177e4SLinus Torvalds 4241da177e4SLinus Torvalds /* 4251da177e4SLinus Torvalds * Now erase_regions[i] is the region in which the 4261da177e4SLinus Torvalds * to-be-erased area begins. Verify that the starting 4271da177e4SLinus Torvalds * offset is aligned to this region's erase size: 4281da177e4SLinus Torvalds */ 4291da177e4SLinus Torvalds if (instr->addr & (erase_regions[i].erasesize - 1)) 4301da177e4SLinus Torvalds return -EINVAL; 4311da177e4SLinus Torvalds 4321da177e4SLinus Torvalds /* 4331da177e4SLinus Torvalds * now find the erase region where the to-be-erased area ends: 4341da177e4SLinus Torvalds */ 4351da177e4SLinus Torvalds for (; i < concat->mtd.numeraseregions && 4361da177e4SLinus Torvalds (instr->addr + instr->len) >= erase_regions[i].offset; 4371da177e4SLinus Torvalds ++i) ; 4381da177e4SLinus Torvalds --i; 4391da177e4SLinus Torvalds /* 4401da177e4SLinus Torvalds * check if the ending offset is aligned to this region's erase size 4411da177e4SLinus Torvalds */ 4421da177e4SLinus Torvalds if ((instr->addr + instr->len) & (erase_regions[i].erasesize - 4431da177e4SLinus Torvalds 1)) 4441da177e4SLinus Torvalds return -EINVAL; 4451da177e4SLinus Torvalds } 4461da177e4SLinus Torvalds 447bb0eb217SAdrian Hunter instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN; 4481da177e4SLinus Torvalds 4491da177e4SLinus Torvalds /* make a local copy of instr to avoid modifying the caller's struct */ 4501da177e4SLinus Torvalds erase = kmalloc(sizeof (struct erase_info), GFP_KERNEL); 4511da177e4SLinus Torvalds 4521da177e4SLinus Torvalds if (!erase) 4531da177e4SLinus Torvalds return -ENOMEM; 4541da177e4SLinus Torvalds 4551da177e4SLinus Torvalds *erase = *instr; 4561da177e4SLinus Torvalds length = instr->len; 4571da177e4SLinus Torvalds 4581da177e4SLinus Torvalds /* 4591da177e4SLinus Torvalds * find the subdevice where the to-be-erased area begins, adjust 4601da177e4SLinus Torvalds * starting offset to be relative to the subdevice start 4611da177e4SLinus Torvalds */ 4621da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 4631da177e4SLinus Torvalds subdev = concat->subdev[i]; 4641da177e4SLinus Torvalds if (subdev->size <= erase->addr) { 4651da177e4SLinus Torvalds erase->addr -= subdev->size; 4661da177e4SLinus Torvalds offset += subdev->size; 4671da177e4SLinus Torvalds } else { 4681da177e4SLinus Torvalds break; 4691da177e4SLinus Torvalds } 4701da177e4SLinus Torvalds } 4711da177e4SLinus Torvalds 4721da177e4SLinus Torvalds /* must never happen since size limit has been verified above */ 473373ebfbfSEric Sesterhenn BUG_ON(i >= concat->num_subdev); 4741da177e4SLinus Torvalds 4751da177e4SLinus Torvalds /* now do the erase: */ 4761da177e4SLinus Torvalds err = 0; 4771da177e4SLinus Torvalds for (; length > 0; i++) { 4781da177e4SLinus Torvalds /* loop for all subdevices affected by this request */ 4791da177e4SLinus Torvalds subdev = concat->subdev[i]; /* get current subdevice */ 4801da177e4SLinus Torvalds 4811da177e4SLinus Torvalds /* limit length to subdevice's size: */ 4821da177e4SLinus Torvalds if (erase->addr + length > subdev->size) 4831da177e4SLinus Torvalds erase->len = subdev->size - erase->addr; 4841da177e4SLinus Torvalds else 4851da177e4SLinus Torvalds erase->len = length; 4861da177e4SLinus Torvalds 4871da177e4SLinus Torvalds if (!(subdev->flags & MTD_WRITEABLE)) { 4881da177e4SLinus Torvalds err = -EROFS; 4891da177e4SLinus Torvalds break; 4901da177e4SLinus Torvalds } 4911da177e4SLinus Torvalds length -= erase->len; 4921da177e4SLinus Torvalds if ((err = concat_dev_erase(subdev, erase))) { 4931da177e4SLinus Torvalds /* sanity check: should never happen since 4941da177e4SLinus Torvalds * block alignment has been checked above */ 495373ebfbfSEric Sesterhenn BUG_ON(err == -EINVAL); 496bb0eb217SAdrian Hunter if (erase->fail_addr != MTD_FAIL_ADDR_UNKNOWN) 4971da177e4SLinus Torvalds instr->fail_addr = erase->fail_addr + offset; 4981da177e4SLinus Torvalds break; 4991da177e4SLinus Torvalds } 5001da177e4SLinus Torvalds /* 5011da177e4SLinus Torvalds * erase->addr specifies the offset of the area to be 5021da177e4SLinus Torvalds * erased *within the current subdevice*. It can be 5031da177e4SLinus Torvalds * non-zero only the first time through this loop, i.e. 5041da177e4SLinus Torvalds * for the first subdevice where blocks need to be erased. 5051da177e4SLinus Torvalds * All the following erases must begin at the start of the 5061da177e4SLinus Torvalds * current subdevice, i.e. at offset zero. 5071da177e4SLinus Torvalds */ 5081da177e4SLinus Torvalds erase->addr = 0; 5091da177e4SLinus Torvalds offset += subdev->size; 5101da177e4SLinus Torvalds } 5111da177e4SLinus Torvalds instr->state = erase->state; 5121da177e4SLinus Torvalds kfree(erase); 5131da177e4SLinus Torvalds if (err) 5141da177e4SLinus Torvalds return err; 5151da177e4SLinus Torvalds 5161da177e4SLinus Torvalds if (instr->callback) 5171da177e4SLinus Torvalds instr->callback(instr); 5181da177e4SLinus Torvalds return 0; 5191da177e4SLinus Torvalds } 5201da177e4SLinus Torvalds 5211da177e4SLinus Torvalds static int concat_lock(struct mtd_info *mtd, loff_t ofs, size_t len) 5221da177e4SLinus Torvalds { 5231da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 5241da177e4SLinus Torvalds int i, err = -EINVAL; 5251da177e4SLinus Torvalds 5261da177e4SLinus Torvalds if ((len + ofs) > mtd->size) 5271da177e4SLinus Torvalds return -EINVAL; 5281da177e4SLinus Torvalds 5291da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 5301da177e4SLinus Torvalds struct mtd_info *subdev = concat->subdev[i]; 5311da177e4SLinus Torvalds size_t size; 5321da177e4SLinus Torvalds 5331da177e4SLinus Torvalds if (ofs >= subdev->size) { 5341da177e4SLinus Torvalds size = 0; 5351da177e4SLinus Torvalds ofs -= subdev->size; 5361da177e4SLinus Torvalds continue; 5371da177e4SLinus Torvalds } 5381da177e4SLinus Torvalds if (ofs + len > subdev->size) 5391da177e4SLinus Torvalds size = subdev->size - ofs; 5401da177e4SLinus Torvalds else 5411da177e4SLinus Torvalds size = len; 5421da177e4SLinus Torvalds 5431da177e4SLinus Torvalds err = subdev->lock(subdev, ofs, size); 5441da177e4SLinus Torvalds 5451da177e4SLinus Torvalds if (err) 5461da177e4SLinus Torvalds break; 5471da177e4SLinus Torvalds 5481da177e4SLinus Torvalds len -= size; 5491da177e4SLinus Torvalds if (len == 0) 5501da177e4SLinus Torvalds break; 5511da177e4SLinus Torvalds 5521da177e4SLinus Torvalds err = -EINVAL; 5531da177e4SLinus Torvalds ofs = 0; 5541da177e4SLinus Torvalds } 5551da177e4SLinus Torvalds 5561da177e4SLinus Torvalds return err; 5571da177e4SLinus Torvalds } 5581da177e4SLinus Torvalds 5591da177e4SLinus Torvalds static int concat_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) 5601da177e4SLinus Torvalds { 5611da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 5621da177e4SLinus Torvalds int i, err = 0; 5631da177e4SLinus Torvalds 5641da177e4SLinus Torvalds if ((len + ofs) > mtd->size) 5651da177e4SLinus Torvalds return -EINVAL; 5661da177e4SLinus Torvalds 5671da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 5681da177e4SLinus Torvalds struct mtd_info *subdev = concat->subdev[i]; 5691da177e4SLinus Torvalds size_t size; 5701da177e4SLinus Torvalds 5711da177e4SLinus Torvalds if (ofs >= subdev->size) { 5721da177e4SLinus Torvalds size = 0; 5731da177e4SLinus Torvalds ofs -= subdev->size; 5741da177e4SLinus Torvalds continue; 5751da177e4SLinus Torvalds } 5761da177e4SLinus Torvalds if (ofs + len > subdev->size) 5771da177e4SLinus Torvalds size = subdev->size - ofs; 5781da177e4SLinus Torvalds else 5791da177e4SLinus Torvalds size = len; 5801da177e4SLinus Torvalds 5811da177e4SLinus Torvalds err = subdev->unlock(subdev, ofs, size); 5821da177e4SLinus Torvalds 5831da177e4SLinus Torvalds if (err) 5841da177e4SLinus Torvalds break; 5851da177e4SLinus Torvalds 5861da177e4SLinus Torvalds len -= size; 5871da177e4SLinus Torvalds if (len == 0) 5881da177e4SLinus Torvalds break; 5891da177e4SLinus Torvalds 5901da177e4SLinus Torvalds err = -EINVAL; 5911da177e4SLinus Torvalds ofs = 0; 5921da177e4SLinus Torvalds } 5931da177e4SLinus Torvalds 5941da177e4SLinus Torvalds return err; 5951da177e4SLinus Torvalds } 5961da177e4SLinus Torvalds 5971da177e4SLinus Torvalds static void concat_sync(struct mtd_info *mtd) 5981da177e4SLinus Torvalds { 5991da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 6001da177e4SLinus Torvalds int i; 6011da177e4SLinus Torvalds 6021da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 6031da177e4SLinus Torvalds struct mtd_info *subdev = concat->subdev[i]; 6041da177e4SLinus Torvalds subdev->sync(subdev); 6051da177e4SLinus Torvalds } 6061da177e4SLinus Torvalds } 6071da177e4SLinus Torvalds 6081da177e4SLinus Torvalds static int concat_suspend(struct mtd_info *mtd) 6091da177e4SLinus Torvalds { 6101da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 6111da177e4SLinus Torvalds int i, rc = 0; 6121da177e4SLinus Torvalds 6131da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 6141da177e4SLinus Torvalds struct mtd_info *subdev = concat->subdev[i]; 6151da177e4SLinus Torvalds if ((rc = subdev->suspend(subdev)) < 0) 6161da177e4SLinus Torvalds return rc; 6171da177e4SLinus Torvalds } 6181da177e4SLinus Torvalds return rc; 6191da177e4SLinus Torvalds } 6201da177e4SLinus Torvalds 6211da177e4SLinus Torvalds static void concat_resume(struct mtd_info *mtd) 6221da177e4SLinus Torvalds { 6231da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 6241da177e4SLinus Torvalds int i; 6251da177e4SLinus Torvalds 6261da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 6271da177e4SLinus Torvalds struct mtd_info *subdev = concat->subdev[i]; 6281da177e4SLinus Torvalds subdev->resume(subdev); 6291da177e4SLinus Torvalds } 6301da177e4SLinus Torvalds } 6311da177e4SLinus Torvalds 632e8d32937SAlexander Belyakov static int concat_block_isbad(struct mtd_info *mtd, loff_t ofs) 633e8d32937SAlexander Belyakov { 634e8d32937SAlexander Belyakov struct mtd_concat *concat = CONCAT(mtd); 635e8d32937SAlexander Belyakov int i, res = 0; 636e8d32937SAlexander Belyakov 637e8d32937SAlexander Belyakov if (!concat->subdev[0]->block_isbad) 638e8d32937SAlexander Belyakov return res; 639e8d32937SAlexander Belyakov 640e8d32937SAlexander Belyakov if (ofs > mtd->size) 641e8d32937SAlexander Belyakov return -EINVAL; 642e8d32937SAlexander Belyakov 643e8d32937SAlexander Belyakov for (i = 0; i < concat->num_subdev; i++) { 644e8d32937SAlexander Belyakov struct mtd_info *subdev = concat->subdev[i]; 645e8d32937SAlexander Belyakov 646e8d32937SAlexander Belyakov if (ofs >= subdev->size) { 647e8d32937SAlexander Belyakov ofs -= subdev->size; 648e8d32937SAlexander Belyakov continue; 649e8d32937SAlexander Belyakov } 650e8d32937SAlexander Belyakov 651e8d32937SAlexander Belyakov res = subdev->block_isbad(subdev, ofs); 652e8d32937SAlexander Belyakov break; 653e8d32937SAlexander Belyakov } 654e8d32937SAlexander Belyakov 655e8d32937SAlexander Belyakov return res; 656e8d32937SAlexander Belyakov } 657e8d32937SAlexander Belyakov 658e8d32937SAlexander Belyakov static int concat_block_markbad(struct mtd_info *mtd, loff_t ofs) 659e8d32937SAlexander Belyakov { 660e8d32937SAlexander Belyakov struct mtd_concat *concat = CONCAT(mtd); 661e8d32937SAlexander Belyakov int i, err = -EINVAL; 662e8d32937SAlexander Belyakov 663e8d32937SAlexander Belyakov if (!concat->subdev[0]->block_markbad) 664e8d32937SAlexander Belyakov return 0; 665e8d32937SAlexander Belyakov 666e8d32937SAlexander Belyakov if (ofs > mtd->size) 667e8d32937SAlexander Belyakov return -EINVAL; 668e8d32937SAlexander Belyakov 669e8d32937SAlexander Belyakov for (i = 0; i < concat->num_subdev; i++) { 670e8d32937SAlexander Belyakov struct mtd_info *subdev = concat->subdev[i]; 671e8d32937SAlexander Belyakov 672e8d32937SAlexander Belyakov if (ofs >= subdev->size) { 673e8d32937SAlexander Belyakov ofs -= subdev->size; 674e8d32937SAlexander Belyakov continue; 675e8d32937SAlexander Belyakov } 676e8d32937SAlexander Belyakov 677e8d32937SAlexander Belyakov err = subdev->block_markbad(subdev, ofs); 678f1a28c02SThomas Gleixner if (!err) 679f1a28c02SThomas Gleixner mtd->ecc_stats.badblocks++; 680e8d32937SAlexander Belyakov break; 681e8d32937SAlexander Belyakov } 682e8d32937SAlexander Belyakov 683e8d32937SAlexander Belyakov return err; 684e8d32937SAlexander Belyakov } 685e8d32937SAlexander Belyakov 6861da177e4SLinus Torvalds /* 6871da177e4SLinus Torvalds * This function constructs a virtual MTD device by concatenating 6881da177e4SLinus Torvalds * num_devs MTD devices. A pointer to the new device object is 6891da177e4SLinus Torvalds * stored to *new_dev upon success. This function does _not_ 6901da177e4SLinus Torvalds * register any devices: this is the caller's responsibility. 6911da177e4SLinus Torvalds */ 6921da177e4SLinus Torvalds struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to concatenate */ 6931da177e4SLinus Torvalds int num_devs, /* number of subdevices */ 6941da177e4SLinus Torvalds char *name) 6951da177e4SLinus Torvalds { /* name for the new device */ 6961da177e4SLinus Torvalds int i; 6971da177e4SLinus Torvalds size_t size; 6981da177e4SLinus Torvalds struct mtd_concat *concat; 6991da177e4SLinus Torvalds u_int32_t max_erasesize, curr_erasesize; 7001da177e4SLinus Torvalds int num_erase_region; 7011da177e4SLinus Torvalds 7021da177e4SLinus Torvalds printk(KERN_NOTICE "Concatenating MTD devices:\n"); 7031da177e4SLinus Torvalds for (i = 0; i < num_devs; i++) 7041da177e4SLinus Torvalds printk(KERN_NOTICE "(%d): \"%s\"\n", i, subdev[i]->name); 7051da177e4SLinus Torvalds printk(KERN_NOTICE "into device \"%s\"\n", name); 7061da177e4SLinus Torvalds 7071da177e4SLinus Torvalds /* allocate the device structure */ 7081da177e4SLinus Torvalds size = SIZEOF_STRUCT_MTD_CONCAT(num_devs); 70995b93a0cSBurman Yan concat = kzalloc(size, GFP_KERNEL); 7101da177e4SLinus Torvalds if (!concat) { 7111da177e4SLinus Torvalds printk 7121da177e4SLinus Torvalds ("memory allocation error while creating concatenated device \"%s\"\n", 7131da177e4SLinus Torvalds name); 7141da177e4SLinus Torvalds return NULL; 7151da177e4SLinus Torvalds } 7161da177e4SLinus Torvalds concat->subdev = (struct mtd_info **) (concat + 1); 7171da177e4SLinus Torvalds 7181da177e4SLinus Torvalds /* 7191da177e4SLinus Torvalds * Set up the new "super" device's MTD object structure, check for 7201da177e4SLinus Torvalds * incompatibilites between the subdevices. 7211da177e4SLinus Torvalds */ 7221da177e4SLinus Torvalds concat->mtd.type = subdev[0]->type; 7231da177e4SLinus Torvalds concat->mtd.flags = subdev[0]->flags; 7241da177e4SLinus Torvalds concat->mtd.size = subdev[0]->size; 7251da177e4SLinus Torvalds concat->mtd.erasesize = subdev[0]->erasesize; 72628318776SJoern Engel concat->mtd.writesize = subdev[0]->writesize; 727a2e1b833SChris Paulson-Ellis concat->mtd.subpage_sft = subdev[0]->subpage_sft; 7281da177e4SLinus Torvalds concat->mtd.oobsize = subdev[0]->oobsize; 7291f92267cSVitaly Wool concat->mtd.oobavail = subdev[0]->oobavail; 730e8d32937SAlexander Belyakov if (subdev[0]->writev) 731e8d32937SAlexander Belyakov concat->mtd.writev = concat_writev; 7321da177e4SLinus Torvalds if (subdev[0]->read_oob) 7331da177e4SLinus Torvalds concat->mtd.read_oob = concat_read_oob; 7341da177e4SLinus Torvalds if (subdev[0]->write_oob) 7351da177e4SLinus Torvalds concat->mtd.write_oob = concat_write_oob; 736e8d32937SAlexander Belyakov if (subdev[0]->block_isbad) 737e8d32937SAlexander Belyakov concat->mtd.block_isbad = concat_block_isbad; 738e8d32937SAlexander Belyakov if (subdev[0]->block_markbad) 739e8d32937SAlexander Belyakov concat->mtd.block_markbad = concat_block_markbad; 7401da177e4SLinus Torvalds 741f1a28c02SThomas Gleixner concat->mtd.ecc_stats.badblocks = subdev[0]->ecc_stats.badblocks; 742f1a28c02SThomas Gleixner 7431da177e4SLinus Torvalds concat->subdev[0] = subdev[0]; 7441da177e4SLinus Torvalds 7451da177e4SLinus Torvalds for (i = 1; i < num_devs; i++) { 7461da177e4SLinus Torvalds if (concat->mtd.type != subdev[i]->type) { 7471da177e4SLinus Torvalds kfree(concat); 7481da177e4SLinus Torvalds printk("Incompatible device type on \"%s\"\n", 7491da177e4SLinus Torvalds subdev[i]->name); 7501da177e4SLinus Torvalds return NULL; 7511da177e4SLinus Torvalds } 7521da177e4SLinus Torvalds if (concat->mtd.flags != subdev[i]->flags) { 7531da177e4SLinus Torvalds /* 7541da177e4SLinus Torvalds * Expect all flags except MTD_WRITEABLE to be 7551da177e4SLinus Torvalds * equal on all subdevices. 7561da177e4SLinus Torvalds */ 7571da177e4SLinus Torvalds if ((concat->mtd.flags ^ subdev[i]-> 7581da177e4SLinus Torvalds flags) & ~MTD_WRITEABLE) { 7591da177e4SLinus Torvalds kfree(concat); 7601da177e4SLinus Torvalds printk("Incompatible device flags on \"%s\"\n", 7611da177e4SLinus Torvalds subdev[i]->name); 7621da177e4SLinus Torvalds return NULL; 7631da177e4SLinus Torvalds } else 7641da177e4SLinus Torvalds /* if writeable attribute differs, 7651da177e4SLinus Torvalds make super device writeable */ 7661da177e4SLinus Torvalds concat->mtd.flags |= 7671da177e4SLinus Torvalds subdev[i]->flags & MTD_WRITEABLE; 7681da177e4SLinus Torvalds } 7691da177e4SLinus Torvalds concat->mtd.size += subdev[i]->size; 770f1a28c02SThomas Gleixner concat->mtd.ecc_stats.badblocks += 771f1a28c02SThomas Gleixner subdev[i]->ecc_stats.badblocks; 77228318776SJoern Engel if (concat->mtd.writesize != subdev[i]->writesize || 77329072b96SThomas Gleixner concat->mtd.subpage_sft != subdev[i]->subpage_sft || 7741da177e4SLinus Torvalds concat->mtd.oobsize != subdev[i]->oobsize || 7751da177e4SLinus Torvalds !concat->mtd.read_oob != !subdev[i]->read_oob || 7761da177e4SLinus Torvalds !concat->mtd.write_oob != !subdev[i]->write_oob) { 7771da177e4SLinus Torvalds kfree(concat); 7781da177e4SLinus Torvalds printk("Incompatible OOB or ECC data on \"%s\"\n", 7791da177e4SLinus Torvalds subdev[i]->name); 7801da177e4SLinus Torvalds return NULL; 7811da177e4SLinus Torvalds } 7821da177e4SLinus Torvalds concat->subdev[i] = subdev[i]; 7831da177e4SLinus Torvalds 7841da177e4SLinus Torvalds } 7851da177e4SLinus Torvalds 7865bd34c09SThomas Gleixner concat->mtd.ecclayout = subdev[0]->ecclayout; 787e8d32937SAlexander Belyakov 7881da177e4SLinus Torvalds concat->num_subdev = num_devs; 7891da177e4SLinus Torvalds concat->mtd.name = name; 7901da177e4SLinus Torvalds 7911da177e4SLinus Torvalds concat->mtd.erase = concat_erase; 7921da177e4SLinus Torvalds concat->mtd.read = concat_read; 7931da177e4SLinus Torvalds concat->mtd.write = concat_write; 7941da177e4SLinus Torvalds concat->mtd.sync = concat_sync; 7951da177e4SLinus Torvalds concat->mtd.lock = concat_lock; 7961da177e4SLinus Torvalds concat->mtd.unlock = concat_unlock; 7971da177e4SLinus Torvalds concat->mtd.suspend = concat_suspend; 7981da177e4SLinus Torvalds concat->mtd.resume = concat_resume; 7991da177e4SLinus Torvalds 8001da177e4SLinus Torvalds /* 8011da177e4SLinus Torvalds * Combine the erase block size info of the subdevices: 8021da177e4SLinus Torvalds * 8031da177e4SLinus Torvalds * first, walk the map of the new device and see how 8041da177e4SLinus Torvalds * many changes in erase size we have 8051da177e4SLinus Torvalds */ 8061da177e4SLinus Torvalds max_erasesize = curr_erasesize = subdev[0]->erasesize; 8071da177e4SLinus Torvalds num_erase_region = 1; 8081da177e4SLinus Torvalds for (i = 0; i < num_devs; i++) { 8091da177e4SLinus Torvalds if (subdev[i]->numeraseregions == 0) { 8101da177e4SLinus Torvalds /* current subdevice has uniform erase size */ 8111da177e4SLinus Torvalds if (subdev[i]->erasesize != curr_erasesize) { 8121da177e4SLinus Torvalds /* if it differs from the last subdevice's erase size, count it */ 8131da177e4SLinus Torvalds ++num_erase_region; 8141da177e4SLinus Torvalds curr_erasesize = subdev[i]->erasesize; 8151da177e4SLinus Torvalds if (curr_erasesize > max_erasesize) 8161da177e4SLinus Torvalds max_erasesize = curr_erasesize; 8171da177e4SLinus Torvalds } 8181da177e4SLinus Torvalds } else { 8191da177e4SLinus Torvalds /* current subdevice has variable erase size */ 8201da177e4SLinus Torvalds int j; 8211da177e4SLinus Torvalds for (j = 0; j < subdev[i]->numeraseregions; j++) { 8221da177e4SLinus Torvalds 8231da177e4SLinus Torvalds /* walk the list of erase regions, count any changes */ 8241da177e4SLinus Torvalds if (subdev[i]->eraseregions[j].erasesize != 8251da177e4SLinus Torvalds curr_erasesize) { 8261da177e4SLinus Torvalds ++num_erase_region; 8271da177e4SLinus Torvalds curr_erasesize = 8281da177e4SLinus Torvalds subdev[i]->eraseregions[j]. 8291da177e4SLinus Torvalds erasesize; 8301da177e4SLinus Torvalds if (curr_erasesize > max_erasesize) 8311da177e4SLinus Torvalds max_erasesize = curr_erasesize; 8321da177e4SLinus Torvalds } 8331da177e4SLinus Torvalds } 8341da177e4SLinus Torvalds } 8351da177e4SLinus Torvalds } 8361da177e4SLinus Torvalds 8371da177e4SLinus Torvalds if (num_erase_region == 1) { 8381da177e4SLinus Torvalds /* 8391da177e4SLinus Torvalds * All subdevices have the same uniform erase size. 8401da177e4SLinus Torvalds * This is easy: 8411da177e4SLinus Torvalds */ 8421da177e4SLinus Torvalds concat->mtd.erasesize = curr_erasesize; 8431da177e4SLinus Torvalds concat->mtd.numeraseregions = 0; 8441da177e4SLinus Torvalds } else { 8451da177e4SLinus Torvalds /* 8461da177e4SLinus Torvalds * erase block size varies across the subdevices: allocate 8471da177e4SLinus Torvalds * space to store the data describing the variable erase regions 8481da177e4SLinus Torvalds */ 8491da177e4SLinus Torvalds struct mtd_erase_region_info *erase_region_p; 8501da177e4SLinus Torvalds u_int32_t begin, position; 8511da177e4SLinus Torvalds 8521da177e4SLinus Torvalds concat->mtd.erasesize = max_erasesize; 8531da177e4SLinus Torvalds concat->mtd.numeraseregions = num_erase_region; 8541da177e4SLinus Torvalds concat->mtd.eraseregions = erase_region_p = 8551da177e4SLinus Torvalds kmalloc(num_erase_region * 8561da177e4SLinus Torvalds sizeof (struct mtd_erase_region_info), GFP_KERNEL); 8571da177e4SLinus Torvalds if (!erase_region_p) { 8581da177e4SLinus Torvalds kfree(concat); 8591da177e4SLinus Torvalds printk 8601da177e4SLinus Torvalds ("memory allocation error while creating erase region list" 8611da177e4SLinus Torvalds " for device \"%s\"\n", name); 8621da177e4SLinus Torvalds return NULL; 8631da177e4SLinus Torvalds } 8641da177e4SLinus Torvalds 8651da177e4SLinus Torvalds /* 8661da177e4SLinus Torvalds * walk the map of the new device once more and fill in 8671da177e4SLinus Torvalds * in erase region info: 8681da177e4SLinus Torvalds */ 8691da177e4SLinus Torvalds curr_erasesize = subdev[0]->erasesize; 8701da177e4SLinus Torvalds begin = position = 0; 8711da177e4SLinus Torvalds for (i = 0; i < num_devs; i++) { 8721da177e4SLinus Torvalds if (subdev[i]->numeraseregions == 0) { 8731da177e4SLinus Torvalds /* current subdevice has uniform erase size */ 8741da177e4SLinus Torvalds if (subdev[i]->erasesize != curr_erasesize) { 8751da177e4SLinus Torvalds /* 8761da177e4SLinus Torvalds * fill in an mtd_erase_region_info structure for the area 8771da177e4SLinus Torvalds * we have walked so far: 8781da177e4SLinus Torvalds */ 8791da177e4SLinus Torvalds erase_region_p->offset = begin; 8801da177e4SLinus Torvalds erase_region_p->erasesize = 8811da177e4SLinus Torvalds curr_erasesize; 8821da177e4SLinus Torvalds erase_region_p->numblocks = 8831da177e4SLinus Torvalds (position - begin) / curr_erasesize; 8841da177e4SLinus Torvalds begin = position; 8851da177e4SLinus Torvalds 8861da177e4SLinus Torvalds curr_erasesize = subdev[i]->erasesize; 8871da177e4SLinus Torvalds ++erase_region_p; 8881da177e4SLinus Torvalds } 8891da177e4SLinus Torvalds position += subdev[i]->size; 8901da177e4SLinus Torvalds } else { 8911da177e4SLinus Torvalds /* current subdevice has variable erase size */ 8921da177e4SLinus Torvalds int j; 8931da177e4SLinus Torvalds for (j = 0; j < subdev[i]->numeraseregions; j++) { 8941da177e4SLinus Torvalds /* walk the list of erase regions, count any changes */ 8951da177e4SLinus Torvalds if (subdev[i]->eraseregions[j]. 8961da177e4SLinus Torvalds erasesize != curr_erasesize) { 8971da177e4SLinus Torvalds erase_region_p->offset = begin; 8981da177e4SLinus Torvalds erase_region_p->erasesize = 8991da177e4SLinus Torvalds curr_erasesize; 9001da177e4SLinus Torvalds erase_region_p->numblocks = 9011da177e4SLinus Torvalds (position - 9021da177e4SLinus Torvalds begin) / curr_erasesize; 9031da177e4SLinus Torvalds begin = position; 9041da177e4SLinus Torvalds 9051da177e4SLinus Torvalds curr_erasesize = 9061da177e4SLinus Torvalds subdev[i]->eraseregions[j]. 9071da177e4SLinus Torvalds erasesize; 9081da177e4SLinus Torvalds ++erase_region_p; 9091da177e4SLinus Torvalds } 9101da177e4SLinus Torvalds position += 9111da177e4SLinus Torvalds subdev[i]->eraseregions[j]. 9121da177e4SLinus Torvalds numblocks * curr_erasesize; 9131da177e4SLinus Torvalds } 9141da177e4SLinus Torvalds } 9151da177e4SLinus Torvalds } 9161da177e4SLinus Torvalds /* Now write the final entry */ 9171da177e4SLinus Torvalds erase_region_p->offset = begin; 9181da177e4SLinus Torvalds erase_region_p->erasesize = curr_erasesize; 9191da177e4SLinus Torvalds erase_region_p->numblocks = (position - begin) / curr_erasesize; 9201da177e4SLinus Torvalds } 9211da177e4SLinus Torvalds 9221da177e4SLinus Torvalds return &concat->mtd; 9231da177e4SLinus Torvalds } 9241da177e4SLinus Torvalds 9251da177e4SLinus Torvalds /* 9261da177e4SLinus Torvalds * This function destroys an MTD object obtained from concat_mtd_devs() 9271da177e4SLinus Torvalds */ 9281da177e4SLinus Torvalds 9291da177e4SLinus Torvalds void mtd_concat_destroy(struct mtd_info *mtd) 9301da177e4SLinus Torvalds { 9311da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 9321da177e4SLinus Torvalds if (concat->mtd.numeraseregions) 9331da177e4SLinus Torvalds kfree(concat->mtd.eraseregions); 9341da177e4SLinus Torvalds kfree(concat); 9351da177e4SLinus Torvalds } 9361da177e4SLinus Torvalds 9371da177e4SLinus Torvalds EXPORT_SYMBOL(mtd_concat_create); 9381da177e4SLinus Torvalds EXPORT_SYMBOL(mtd_concat_destroy); 9391da177e4SLinus Torvalds 9401da177e4SLinus Torvalds MODULE_LICENSE("GPL"); 9411da177e4SLinus Torvalds MODULE_AUTHOR("Robert Kaiser <rkaiser@sysgo.de>"); 9421da177e4SLinus Torvalds MODULE_DESCRIPTION("Generic support for concatenating of MTD devices"); 943