11da177e4SLinus Torvalds /* 21da177e4SLinus Torvalds * MTD device concatenation layer 31da177e4SLinus Torvalds * 4a1452a37SDavid Woodhouse * Copyright © 2002 Robert Kaiser <rkaiser@sysgo.de> 5a1452a37SDavid Woodhouse * Copyright © 2002-2010 David Woodhouse <dwmw2@infradead.org> 61da177e4SLinus Torvalds * 71da177e4SLinus Torvalds * NAND support by Christian Gan <cgan@iders.ca> 81da177e4SLinus Torvalds * 9a1452a37SDavid Woodhouse * This program is free software; you can redistribute it and/or modify 10a1452a37SDavid Woodhouse * it under the terms of the GNU General Public License as published by 11a1452a37SDavid Woodhouse * the Free Software Foundation; either version 2 of the License, or 12a1452a37SDavid Woodhouse * (at your option) any later version. 13a1452a37SDavid Woodhouse * 14a1452a37SDavid Woodhouse * This program is distributed in the hope that it will be useful, 15a1452a37SDavid Woodhouse * but WITHOUT ANY WARRANTY; without even the implied warranty of 16a1452a37SDavid Woodhouse * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17a1452a37SDavid Woodhouse * GNU General Public License for more details. 18a1452a37SDavid Woodhouse * 19a1452a37SDavid Woodhouse * You should have received a copy of the GNU General Public License 20a1452a37SDavid Woodhouse * along with this program; if not, write to the Free Software 21a1452a37SDavid Woodhouse * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 22a1452a37SDavid Woodhouse * 231da177e4SLinus Torvalds */ 241da177e4SLinus Torvalds 251da177e4SLinus Torvalds #include <linux/kernel.h> 2615fdc52fSThomas Gleixner #include <linux/module.h> 271da177e4SLinus Torvalds #include <linux/slab.h> 2815fdc52fSThomas Gleixner #include <linux/sched.h> 2915fdc52fSThomas Gleixner #include <linux/types.h> 306e232cfcSDavid Howells #include <linux/backing-dev.h> 3115fdc52fSThomas Gleixner 321da177e4SLinus Torvalds #include <linux/mtd/mtd.h> 331da177e4SLinus Torvalds #include <linux/mtd/concat.h> 341da177e4SLinus Torvalds 356c8b44abSAndrew Morton #include <asm/div64.h> 366c8b44abSAndrew Morton 371da177e4SLinus Torvalds /* 381da177e4SLinus Torvalds * Our storage structure: 391da177e4SLinus Torvalds * Subdev points to an array of pointers to struct mtd_info objects 401da177e4SLinus Torvalds * which is allocated along with this structure 411da177e4SLinus Torvalds * 421da177e4SLinus Torvalds */ 431da177e4SLinus Torvalds struct mtd_concat { 441da177e4SLinus Torvalds struct mtd_info mtd; 451da177e4SLinus Torvalds int num_subdev; 461da177e4SLinus Torvalds struct mtd_info **subdev; 471da177e4SLinus Torvalds }; 481da177e4SLinus Torvalds 491da177e4SLinus Torvalds /* 501da177e4SLinus Torvalds * how to calculate the size required for the above structure, 511da177e4SLinus Torvalds * including the pointer array subdev points to: 521da177e4SLinus Torvalds */ 531da177e4SLinus Torvalds #define SIZEOF_STRUCT_MTD_CONCAT(num_subdev) \ 541da177e4SLinus Torvalds ((sizeof(struct mtd_concat) + (num_subdev) * sizeof(struct mtd_info *))) 551da177e4SLinus Torvalds 561da177e4SLinus Torvalds /* 571da177e4SLinus Torvalds * Given a pointer to the MTD object in the mtd_concat structure, 581da177e4SLinus Torvalds * we can retrieve the pointer to that structure with this macro. 591da177e4SLinus Torvalds */ 601da177e4SLinus Torvalds #define CONCAT(x) ((struct mtd_concat *)(x)) 611da177e4SLinus Torvalds 621da177e4SLinus Torvalds /* 631da177e4SLinus Torvalds * MTD methods which look up the relevant subdevice, translate the 641da177e4SLinus Torvalds * effective address and pass through to the subdevice. 651da177e4SLinus Torvalds */ 661da177e4SLinus Torvalds 671da177e4SLinus Torvalds static int 681da177e4SLinus Torvalds concat_read(struct mtd_info *mtd, loff_t from, size_t len, 691da177e4SLinus Torvalds size_t * retlen, u_char * buf) 701da177e4SLinus Torvalds { 711da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 72f1a28c02SThomas Gleixner int ret = 0, err; 731da177e4SLinus Torvalds int i; 741da177e4SLinus Torvalds 751da177e4SLinus Torvalds *retlen = 0; 761da177e4SLinus Torvalds 771da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 781da177e4SLinus Torvalds struct mtd_info *subdev = concat->subdev[i]; 791da177e4SLinus Torvalds size_t size, retsize; 801da177e4SLinus Torvalds 811da177e4SLinus Torvalds if (from >= subdev->size) { 821da177e4SLinus Torvalds /* Not destined for this subdev */ 831da177e4SLinus Torvalds size = 0; 841da177e4SLinus Torvalds from -= subdev->size; 851da177e4SLinus Torvalds continue; 861da177e4SLinus Torvalds } 871da177e4SLinus Torvalds if (from + len > subdev->size) 881da177e4SLinus Torvalds /* First part goes into this subdev */ 891da177e4SLinus Torvalds size = subdev->size - from; 901da177e4SLinus Torvalds else 911da177e4SLinus Torvalds /* Entire transaction goes into this subdev */ 921da177e4SLinus Torvalds size = len; 931da177e4SLinus Torvalds 94329ad399SArtem Bityutskiy err = mtd_read(subdev, from, size, &retsize, buf); 951da177e4SLinus Torvalds 969a1fcdfdSThomas Gleixner /* Save information about bitflips! */ 97f1a28c02SThomas Gleixner if (unlikely(err)) { 98d57f4054SBrian Norris if (mtd_is_eccerr(err)) { 99f1a28c02SThomas Gleixner mtd->ecc_stats.failed++; 1009a1fcdfdSThomas Gleixner ret = err; 101d57f4054SBrian Norris } else if (mtd_is_bitflip(err)) { 102f1a28c02SThomas Gleixner mtd->ecc_stats.corrected++; 103f1a28c02SThomas Gleixner /* Do not overwrite -EBADMSG !! */ 104f1a28c02SThomas Gleixner if (!ret) 1059a1fcdfdSThomas Gleixner ret = err; 106f1a28c02SThomas Gleixner } else 107f1a28c02SThomas Gleixner return err; 1089a1fcdfdSThomas Gleixner } 1099a1fcdfdSThomas Gleixner 1101da177e4SLinus Torvalds *retlen += retsize; 1111da177e4SLinus Torvalds len -= size; 1121da177e4SLinus Torvalds if (len == 0) 113f1a28c02SThomas Gleixner return ret; 1141da177e4SLinus Torvalds 1151da177e4SLinus Torvalds buf += size; 1161da177e4SLinus Torvalds from = 0; 1171da177e4SLinus Torvalds } 118f1a28c02SThomas Gleixner return -EINVAL; 1191da177e4SLinus Torvalds } 1201da177e4SLinus Torvalds 1211da177e4SLinus Torvalds static int 1221da177e4SLinus Torvalds concat_write(struct mtd_info *mtd, loff_t to, size_t len, 1231da177e4SLinus Torvalds size_t * retlen, const u_char * buf) 1241da177e4SLinus Torvalds { 1251da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 1261da177e4SLinus Torvalds int err = -EINVAL; 1271da177e4SLinus Torvalds int i; 1281da177e4SLinus Torvalds 1291da177e4SLinus Torvalds if (!(mtd->flags & MTD_WRITEABLE)) 1301da177e4SLinus Torvalds return -EROFS; 1311da177e4SLinus Torvalds 1321da177e4SLinus Torvalds *retlen = 0; 1331da177e4SLinus Torvalds 1341da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 1351da177e4SLinus Torvalds struct mtd_info *subdev = concat->subdev[i]; 1361da177e4SLinus Torvalds size_t size, retsize; 1371da177e4SLinus Torvalds 1381da177e4SLinus Torvalds if (to >= subdev->size) { 1391da177e4SLinus Torvalds size = 0; 1401da177e4SLinus Torvalds to -= subdev->size; 1411da177e4SLinus Torvalds continue; 1421da177e4SLinus Torvalds } 1431da177e4SLinus Torvalds if (to + len > subdev->size) 1441da177e4SLinus Torvalds size = subdev->size - to; 1451da177e4SLinus Torvalds else 1461da177e4SLinus Torvalds size = len; 1471da177e4SLinus Torvalds 1481da177e4SLinus Torvalds if (!(subdev->flags & MTD_WRITEABLE)) 1491da177e4SLinus Torvalds err = -EROFS; 1501da177e4SLinus Torvalds else 151eda95cbfSArtem Bityutskiy err = mtd_write(subdev, to, size, &retsize, buf); 1521da177e4SLinus Torvalds 1531da177e4SLinus Torvalds if (err) 1541da177e4SLinus Torvalds break; 1551da177e4SLinus Torvalds 1561da177e4SLinus Torvalds *retlen += retsize; 1571da177e4SLinus Torvalds len -= size; 1581da177e4SLinus Torvalds if (len == 0) 1591da177e4SLinus Torvalds break; 1601da177e4SLinus Torvalds 1611da177e4SLinus Torvalds err = -EINVAL; 1621da177e4SLinus Torvalds buf += size; 1631da177e4SLinus Torvalds to = 0; 1641da177e4SLinus Torvalds } 1651da177e4SLinus Torvalds return err; 1661da177e4SLinus Torvalds } 1671da177e4SLinus Torvalds 1681da177e4SLinus Torvalds static int 1699d8522dfSThomas Gleixner concat_writev(struct mtd_info *mtd, const struct kvec *vecs, 1709d8522dfSThomas Gleixner unsigned long count, loff_t to, size_t * retlen) 171e8d32937SAlexander Belyakov { 172e8d32937SAlexander Belyakov struct mtd_concat *concat = CONCAT(mtd); 173e8d32937SAlexander Belyakov struct kvec *vecs_copy; 174e8d32937SAlexander Belyakov unsigned long entry_low, entry_high; 175e8d32937SAlexander Belyakov size_t total_len = 0; 176e8d32937SAlexander Belyakov int i; 177e8d32937SAlexander Belyakov int err = -EINVAL; 178e8d32937SAlexander Belyakov 179e8d32937SAlexander Belyakov if (!(mtd->flags & MTD_WRITEABLE)) 180e8d32937SAlexander Belyakov return -EROFS; 181e8d32937SAlexander Belyakov 182e8d32937SAlexander Belyakov *retlen = 0; 183e8d32937SAlexander Belyakov 184e8d32937SAlexander Belyakov /* Calculate total length of data */ 185e8d32937SAlexander Belyakov for (i = 0; i < count; i++) 186e8d32937SAlexander Belyakov total_len += vecs[i].iov_len; 187e8d32937SAlexander Belyakov 188e8d32937SAlexander Belyakov /* Do not allow write past end of device */ 189e8d32937SAlexander Belyakov if ((to + total_len) > mtd->size) 190e8d32937SAlexander Belyakov return -EINVAL; 191e8d32937SAlexander Belyakov 192e8d32937SAlexander Belyakov /* Check alignment */ 19328318776SJoern Engel if (mtd->writesize > 1) { 1940bf9733dSDavid Woodhouse uint64_t __to = to; 19528318776SJoern Engel if (do_div(__to, mtd->writesize) || (total_len % mtd->writesize)) 196e8d32937SAlexander Belyakov return -EINVAL; 1976c8b44abSAndrew Morton } 198e8d32937SAlexander Belyakov 199e8d32937SAlexander Belyakov /* make a copy of vecs */ 200d80f2666SJulia Lawall vecs_copy = kmemdup(vecs, sizeof(struct kvec) * count, GFP_KERNEL); 201e8d32937SAlexander Belyakov if (!vecs_copy) 202e8d32937SAlexander Belyakov return -ENOMEM; 203e8d32937SAlexander Belyakov 204e8d32937SAlexander Belyakov entry_low = 0; 205e8d32937SAlexander Belyakov for (i = 0; i < concat->num_subdev; i++) { 206e8d32937SAlexander Belyakov struct mtd_info *subdev = concat->subdev[i]; 207e8d32937SAlexander Belyakov size_t size, wsize, retsize, old_iov_len; 208e8d32937SAlexander Belyakov 209e8d32937SAlexander Belyakov if (to >= subdev->size) { 210e8d32937SAlexander Belyakov to -= subdev->size; 211e8d32937SAlexander Belyakov continue; 212e8d32937SAlexander Belyakov } 213e8d32937SAlexander Belyakov 21469423d99SAdrian Hunter size = min_t(uint64_t, total_len, subdev->size - to); 215e8d32937SAlexander Belyakov wsize = size; /* store for future use */ 216e8d32937SAlexander Belyakov 217e8d32937SAlexander Belyakov entry_high = entry_low; 218e8d32937SAlexander Belyakov while (entry_high < count) { 219e8d32937SAlexander Belyakov if (size <= vecs_copy[entry_high].iov_len) 220e8d32937SAlexander Belyakov break; 221e8d32937SAlexander Belyakov size -= vecs_copy[entry_high++].iov_len; 222e8d32937SAlexander Belyakov } 223e8d32937SAlexander Belyakov 224e8d32937SAlexander Belyakov old_iov_len = vecs_copy[entry_high].iov_len; 225e8d32937SAlexander Belyakov vecs_copy[entry_high].iov_len = size; 226e8d32937SAlexander Belyakov 227e8d32937SAlexander Belyakov if (!(subdev->flags & MTD_WRITEABLE)) 228e8d32937SAlexander Belyakov err = -EROFS; 229e8d32937SAlexander Belyakov else 230b0a31f7bSArtem Bityutskiy err = mtd_writev(subdev, &vecs_copy[entry_low], 231b0a31f7bSArtem Bityutskiy entry_high - entry_low + 1, to, 232b0a31f7bSArtem Bityutskiy &retsize); 233e8d32937SAlexander Belyakov 234e8d32937SAlexander Belyakov vecs_copy[entry_high].iov_len = old_iov_len - size; 235e8d32937SAlexander Belyakov vecs_copy[entry_high].iov_base += size; 236e8d32937SAlexander Belyakov 237e8d32937SAlexander Belyakov entry_low = entry_high; 238e8d32937SAlexander Belyakov 239e8d32937SAlexander Belyakov if (err) 240e8d32937SAlexander Belyakov break; 241e8d32937SAlexander Belyakov 242e8d32937SAlexander Belyakov *retlen += retsize; 243e8d32937SAlexander Belyakov total_len -= wsize; 244e8d32937SAlexander Belyakov 245e8d32937SAlexander Belyakov if (total_len == 0) 246e8d32937SAlexander Belyakov break; 247e8d32937SAlexander Belyakov 248e8d32937SAlexander Belyakov err = -EINVAL; 249e8d32937SAlexander Belyakov to = 0; 250e8d32937SAlexander Belyakov } 251e8d32937SAlexander Belyakov 252e8d32937SAlexander Belyakov kfree(vecs_copy); 253e8d32937SAlexander Belyakov return err; 254e8d32937SAlexander Belyakov } 255e8d32937SAlexander Belyakov 256e8d32937SAlexander Belyakov static int 2578593fbc6SThomas Gleixner concat_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) 2581da177e4SLinus Torvalds { 2591da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 2608593fbc6SThomas Gleixner struct mtd_oob_ops devops = *ops; 261f1a28c02SThomas Gleixner int i, err, ret = 0; 2621da177e4SLinus Torvalds 2637014568bSVitaly Wool ops->retlen = ops->oobretlen = 0; 2641da177e4SLinus Torvalds 2651da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 2661da177e4SLinus Torvalds struct mtd_info *subdev = concat->subdev[i]; 2671da177e4SLinus Torvalds 2681da177e4SLinus Torvalds if (from >= subdev->size) { 2691da177e4SLinus Torvalds from -= subdev->size; 2701da177e4SLinus Torvalds continue; 2711da177e4SLinus Torvalds } 2721da177e4SLinus Torvalds 2738593fbc6SThomas Gleixner /* partial read ? */ 2748593fbc6SThomas Gleixner if (from + devops.len > subdev->size) 2758593fbc6SThomas Gleixner devops.len = subdev->size - from; 2761da177e4SLinus Torvalds 277fd2819bbSArtem Bityutskiy err = mtd_read_oob(subdev, from, &devops); 2788593fbc6SThomas Gleixner ops->retlen += devops.retlen; 2797014568bSVitaly Wool ops->oobretlen += devops.oobretlen; 280f1a28c02SThomas Gleixner 281f1a28c02SThomas Gleixner /* Save information about bitflips! */ 282f1a28c02SThomas Gleixner if (unlikely(err)) { 283d57f4054SBrian Norris if (mtd_is_eccerr(err)) { 284f1a28c02SThomas Gleixner mtd->ecc_stats.failed++; 285f1a28c02SThomas Gleixner ret = err; 286d57f4054SBrian Norris } else if (mtd_is_bitflip(err)) { 287f1a28c02SThomas Gleixner mtd->ecc_stats.corrected++; 288f1a28c02SThomas Gleixner /* Do not overwrite -EBADMSG !! */ 289f1a28c02SThomas Gleixner if (!ret) 290f1a28c02SThomas Gleixner ret = err; 291f1a28c02SThomas Gleixner } else 2928593fbc6SThomas Gleixner return err; 293f1a28c02SThomas Gleixner } 2941da177e4SLinus Torvalds 2957014568bSVitaly Wool if (devops.datbuf) { 2968593fbc6SThomas Gleixner devops.len = ops->len - ops->retlen; 2978593fbc6SThomas Gleixner if (!devops.len) 298f1a28c02SThomas Gleixner return ret; 2998593fbc6SThomas Gleixner devops.datbuf += devops.retlen; 3007014568bSVitaly Wool } 3017014568bSVitaly Wool if (devops.oobbuf) { 3027014568bSVitaly Wool devops.ooblen = ops->ooblen - ops->oobretlen; 3037014568bSVitaly Wool if (!devops.ooblen) 3047014568bSVitaly Wool return ret; 3057014568bSVitaly Wool devops.oobbuf += ops->oobretlen; 3067014568bSVitaly Wool } 3078593fbc6SThomas Gleixner 3081da177e4SLinus Torvalds from = 0; 3091da177e4SLinus Torvalds } 3108593fbc6SThomas Gleixner return -EINVAL; 3111da177e4SLinus Torvalds } 3121da177e4SLinus Torvalds 3131da177e4SLinus Torvalds static int 3148593fbc6SThomas Gleixner concat_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops) 3151da177e4SLinus Torvalds { 3161da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 3178593fbc6SThomas Gleixner struct mtd_oob_ops devops = *ops; 3188593fbc6SThomas Gleixner int i, err; 3191da177e4SLinus Torvalds 3201da177e4SLinus Torvalds if (!(mtd->flags & MTD_WRITEABLE)) 3211da177e4SLinus Torvalds return -EROFS; 3221da177e4SLinus Torvalds 323431e1ecaSFelix Radensky ops->retlen = ops->oobretlen = 0; 3241da177e4SLinus Torvalds 3251da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 3261da177e4SLinus Torvalds struct mtd_info *subdev = concat->subdev[i]; 3271da177e4SLinus Torvalds 3281da177e4SLinus Torvalds if (to >= subdev->size) { 3291da177e4SLinus Torvalds to -= subdev->size; 3301da177e4SLinus Torvalds continue; 3311da177e4SLinus Torvalds } 3321da177e4SLinus Torvalds 3338593fbc6SThomas Gleixner /* partial write ? */ 3348593fbc6SThomas Gleixner if (to + devops.len > subdev->size) 3358593fbc6SThomas Gleixner devops.len = subdev->size - to; 3361da177e4SLinus Torvalds 337a2cc5ba0SArtem Bityutskiy err = mtd_write_oob(subdev, to, &devops); 338431e1ecaSFelix Radensky ops->retlen += devops.oobretlen; 3391da177e4SLinus Torvalds if (err) 3408593fbc6SThomas Gleixner return err; 3411da177e4SLinus Torvalds 3427014568bSVitaly Wool if (devops.datbuf) { 3438593fbc6SThomas Gleixner devops.len = ops->len - ops->retlen; 3448593fbc6SThomas Gleixner if (!devops.len) 3458593fbc6SThomas Gleixner return 0; 3468593fbc6SThomas Gleixner devops.datbuf += devops.retlen; 3477014568bSVitaly Wool } 3487014568bSVitaly Wool if (devops.oobbuf) { 3497014568bSVitaly Wool devops.ooblen = ops->ooblen - ops->oobretlen; 3507014568bSVitaly Wool if (!devops.ooblen) 3517014568bSVitaly Wool return 0; 3527014568bSVitaly Wool devops.oobbuf += devops.oobretlen; 3537014568bSVitaly Wool } 3541da177e4SLinus Torvalds to = 0; 3551da177e4SLinus Torvalds } 3568593fbc6SThomas Gleixner return -EINVAL; 3571da177e4SLinus Torvalds } 3581da177e4SLinus Torvalds 3591da177e4SLinus Torvalds static void concat_erase_callback(struct erase_info *instr) 3601da177e4SLinus Torvalds { 3611da177e4SLinus Torvalds wake_up((wait_queue_head_t *) instr->priv); 3621da177e4SLinus Torvalds } 3631da177e4SLinus Torvalds 3641da177e4SLinus Torvalds static int concat_dev_erase(struct mtd_info *mtd, struct erase_info *erase) 3651da177e4SLinus Torvalds { 3661da177e4SLinus Torvalds int err; 3671da177e4SLinus Torvalds wait_queue_head_t waitq; 3681da177e4SLinus Torvalds DECLARE_WAITQUEUE(wait, current); 3691da177e4SLinus Torvalds 3701da177e4SLinus Torvalds /* 3711da177e4SLinus Torvalds * This code was stol^H^H^H^Hinspired by mtdchar.c 3721da177e4SLinus Torvalds */ 3731da177e4SLinus Torvalds init_waitqueue_head(&waitq); 3741da177e4SLinus Torvalds 3751da177e4SLinus Torvalds erase->mtd = mtd; 3761da177e4SLinus Torvalds erase->callback = concat_erase_callback; 3771da177e4SLinus Torvalds erase->priv = (unsigned long) &waitq; 3781da177e4SLinus Torvalds 3791da177e4SLinus Torvalds /* 3801da177e4SLinus Torvalds * FIXME: Allow INTERRUPTIBLE. Which means 3811da177e4SLinus Torvalds * not having the wait_queue head on the stack. 3821da177e4SLinus Torvalds */ 3837e1f0dc0SArtem Bityutskiy err = mtd_erase(mtd, erase); 3841da177e4SLinus Torvalds if (!err) { 3851da177e4SLinus Torvalds set_current_state(TASK_UNINTERRUPTIBLE); 3861da177e4SLinus Torvalds add_wait_queue(&waitq, &wait); 3871da177e4SLinus Torvalds if (erase->state != MTD_ERASE_DONE 3881da177e4SLinus Torvalds && erase->state != MTD_ERASE_FAILED) 3891da177e4SLinus Torvalds schedule(); 3901da177e4SLinus Torvalds remove_wait_queue(&waitq, &wait); 3911da177e4SLinus Torvalds set_current_state(TASK_RUNNING); 3921da177e4SLinus Torvalds 3931da177e4SLinus Torvalds err = (erase->state == MTD_ERASE_FAILED) ? -EIO : 0; 3941da177e4SLinus Torvalds } 3951da177e4SLinus Torvalds return err; 3961da177e4SLinus Torvalds } 3971da177e4SLinus Torvalds 3981da177e4SLinus Torvalds static int concat_erase(struct mtd_info *mtd, struct erase_info *instr) 3991da177e4SLinus Torvalds { 4001da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 4011da177e4SLinus Torvalds struct mtd_info *subdev; 4021da177e4SLinus Torvalds int i, err; 40369423d99SAdrian Hunter uint64_t length, offset = 0; 4041da177e4SLinus Torvalds struct erase_info *erase; 4051da177e4SLinus Torvalds 4061da177e4SLinus Torvalds if (!(mtd->flags & MTD_WRITEABLE)) 4071da177e4SLinus Torvalds return -EROFS; 4081da177e4SLinus Torvalds 4091da177e4SLinus Torvalds if (instr->addr > concat->mtd.size) 4101da177e4SLinus Torvalds return -EINVAL; 4111da177e4SLinus Torvalds 4121da177e4SLinus Torvalds if (instr->len + instr->addr > concat->mtd.size) 4131da177e4SLinus Torvalds return -EINVAL; 4141da177e4SLinus Torvalds 4151da177e4SLinus Torvalds /* 4161da177e4SLinus Torvalds * Check for proper erase block alignment of the to-be-erased area. 4171da177e4SLinus Torvalds * It is easier to do this based on the super device's erase 4181da177e4SLinus Torvalds * region info rather than looking at each particular sub-device 4191da177e4SLinus Torvalds * in turn. 4201da177e4SLinus Torvalds */ 4211da177e4SLinus Torvalds if (!concat->mtd.numeraseregions) { 4221da177e4SLinus Torvalds /* the easy case: device has uniform erase block size */ 4231da177e4SLinus Torvalds if (instr->addr & (concat->mtd.erasesize - 1)) 4241da177e4SLinus Torvalds return -EINVAL; 4251da177e4SLinus Torvalds if (instr->len & (concat->mtd.erasesize - 1)) 4261da177e4SLinus Torvalds return -EINVAL; 4271da177e4SLinus Torvalds } else { 4281da177e4SLinus Torvalds /* device has variable erase size */ 4291da177e4SLinus Torvalds struct mtd_erase_region_info *erase_regions = 4301da177e4SLinus Torvalds concat->mtd.eraseregions; 4311da177e4SLinus Torvalds 4321da177e4SLinus Torvalds /* 4331da177e4SLinus Torvalds * Find the erase region where the to-be-erased area begins: 4341da177e4SLinus Torvalds */ 4351da177e4SLinus Torvalds for (i = 0; i < concat->mtd.numeraseregions && 4361da177e4SLinus Torvalds instr->addr >= erase_regions[i].offset; i++) ; 4371da177e4SLinus Torvalds --i; 4381da177e4SLinus Torvalds 4391da177e4SLinus Torvalds /* 4401da177e4SLinus Torvalds * Now erase_regions[i] is the region in which the 4411da177e4SLinus Torvalds * to-be-erased area begins. Verify that the starting 4421da177e4SLinus Torvalds * offset is aligned to this region's erase size: 4431da177e4SLinus Torvalds */ 444ebf2e930SRoel Kluin if (i < 0 || instr->addr & (erase_regions[i].erasesize - 1)) 4451da177e4SLinus Torvalds return -EINVAL; 4461da177e4SLinus Torvalds 4471da177e4SLinus Torvalds /* 4481da177e4SLinus Torvalds * now find the erase region where the to-be-erased area ends: 4491da177e4SLinus Torvalds */ 4501da177e4SLinus Torvalds for (; i < concat->mtd.numeraseregions && 4511da177e4SLinus Torvalds (instr->addr + instr->len) >= erase_regions[i].offset; 4521da177e4SLinus Torvalds ++i) ; 4531da177e4SLinus Torvalds --i; 4541da177e4SLinus Torvalds /* 4551da177e4SLinus Torvalds * check if the ending offset is aligned to this region's erase size 4561da177e4SLinus Torvalds */ 457ebf2e930SRoel Kluin if (i < 0 || ((instr->addr + instr->len) & 458ebf2e930SRoel Kluin (erase_regions[i].erasesize - 1))) 4591da177e4SLinus Torvalds return -EINVAL; 4601da177e4SLinus Torvalds } 4611da177e4SLinus Torvalds 462bb0eb217SAdrian Hunter instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN; 4631da177e4SLinus Torvalds 4641da177e4SLinus Torvalds /* make a local copy of instr to avoid modifying the caller's struct */ 4651da177e4SLinus Torvalds erase = kmalloc(sizeof (struct erase_info), GFP_KERNEL); 4661da177e4SLinus Torvalds 4671da177e4SLinus Torvalds if (!erase) 4681da177e4SLinus Torvalds return -ENOMEM; 4691da177e4SLinus Torvalds 4701da177e4SLinus Torvalds *erase = *instr; 4711da177e4SLinus Torvalds length = instr->len; 4721da177e4SLinus Torvalds 4731da177e4SLinus Torvalds /* 4741da177e4SLinus Torvalds * find the subdevice where the to-be-erased area begins, adjust 4751da177e4SLinus Torvalds * starting offset to be relative to the subdevice start 4761da177e4SLinus Torvalds */ 4771da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 4781da177e4SLinus Torvalds subdev = concat->subdev[i]; 4791da177e4SLinus Torvalds if (subdev->size <= erase->addr) { 4801da177e4SLinus Torvalds erase->addr -= subdev->size; 4811da177e4SLinus Torvalds offset += subdev->size; 4821da177e4SLinus Torvalds } else { 4831da177e4SLinus Torvalds break; 4841da177e4SLinus Torvalds } 4851da177e4SLinus Torvalds } 4861da177e4SLinus Torvalds 4871da177e4SLinus Torvalds /* must never happen since size limit has been verified above */ 488373ebfbfSEric Sesterhenn BUG_ON(i >= concat->num_subdev); 4891da177e4SLinus Torvalds 4901da177e4SLinus Torvalds /* now do the erase: */ 4911da177e4SLinus Torvalds err = 0; 4921da177e4SLinus Torvalds for (; length > 0; i++) { 4931da177e4SLinus Torvalds /* loop for all subdevices affected by this request */ 4941da177e4SLinus Torvalds subdev = concat->subdev[i]; /* get current subdevice */ 4951da177e4SLinus Torvalds 4961da177e4SLinus Torvalds /* limit length to subdevice's size: */ 4971da177e4SLinus Torvalds if (erase->addr + length > subdev->size) 4981da177e4SLinus Torvalds erase->len = subdev->size - erase->addr; 4991da177e4SLinus Torvalds else 5001da177e4SLinus Torvalds erase->len = length; 5011da177e4SLinus Torvalds 5021da177e4SLinus Torvalds if (!(subdev->flags & MTD_WRITEABLE)) { 5031da177e4SLinus Torvalds err = -EROFS; 5041da177e4SLinus Torvalds break; 5051da177e4SLinus Torvalds } 5061da177e4SLinus Torvalds length -= erase->len; 5071da177e4SLinus Torvalds if ((err = concat_dev_erase(subdev, erase))) { 5081da177e4SLinus Torvalds /* sanity check: should never happen since 5091da177e4SLinus Torvalds * block alignment has been checked above */ 510373ebfbfSEric Sesterhenn BUG_ON(err == -EINVAL); 511bb0eb217SAdrian Hunter if (erase->fail_addr != MTD_FAIL_ADDR_UNKNOWN) 5121da177e4SLinus Torvalds instr->fail_addr = erase->fail_addr + offset; 5131da177e4SLinus Torvalds break; 5141da177e4SLinus Torvalds } 5151da177e4SLinus Torvalds /* 5161da177e4SLinus Torvalds * erase->addr specifies the offset of the area to be 5171da177e4SLinus Torvalds * erased *within the current subdevice*. It can be 5181da177e4SLinus Torvalds * non-zero only the first time through this loop, i.e. 5191da177e4SLinus Torvalds * for the first subdevice where blocks need to be erased. 5201da177e4SLinus Torvalds * All the following erases must begin at the start of the 5211da177e4SLinus Torvalds * current subdevice, i.e. at offset zero. 5221da177e4SLinus Torvalds */ 5231da177e4SLinus Torvalds erase->addr = 0; 5241da177e4SLinus Torvalds offset += subdev->size; 5251da177e4SLinus Torvalds } 5261da177e4SLinus Torvalds instr->state = erase->state; 5271da177e4SLinus Torvalds kfree(erase); 5281da177e4SLinus Torvalds if (err) 5291da177e4SLinus Torvalds return err; 5301da177e4SLinus Torvalds 5311da177e4SLinus Torvalds if (instr->callback) 5321da177e4SLinus Torvalds instr->callback(instr); 5331da177e4SLinus Torvalds return 0; 5341da177e4SLinus Torvalds } 5351da177e4SLinus Torvalds 53669423d99SAdrian Hunter static int concat_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) 5371da177e4SLinus Torvalds { 5381da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 5391da177e4SLinus Torvalds int i, err = -EINVAL; 5401da177e4SLinus Torvalds 5411da177e4SLinus Torvalds if ((len + ofs) > mtd->size) 5421da177e4SLinus Torvalds return -EINVAL; 5431da177e4SLinus Torvalds 5441da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 5451da177e4SLinus Torvalds struct mtd_info *subdev = concat->subdev[i]; 54669423d99SAdrian Hunter uint64_t size; 5471da177e4SLinus Torvalds 5481da177e4SLinus Torvalds if (ofs >= subdev->size) { 5491da177e4SLinus Torvalds size = 0; 5501da177e4SLinus Torvalds ofs -= subdev->size; 5511da177e4SLinus Torvalds continue; 5521da177e4SLinus Torvalds } 5531da177e4SLinus Torvalds if (ofs + len > subdev->size) 5541da177e4SLinus Torvalds size = subdev->size - ofs; 5551da177e4SLinus Torvalds else 5561da177e4SLinus Torvalds size = len; 5571da177e4SLinus Torvalds 558e1d0fe3cSMartin Krause if (subdev->lock) { 5597799f9acSArtem Bityutskiy err = mtd_lock(subdev, ofs, size); 5601da177e4SLinus Torvalds if (err) 5611da177e4SLinus Torvalds break; 562e1d0fe3cSMartin Krause } else 563e1d0fe3cSMartin Krause err = -EOPNOTSUPP; 5641da177e4SLinus Torvalds 5651da177e4SLinus Torvalds len -= size; 5661da177e4SLinus Torvalds if (len == 0) 5671da177e4SLinus Torvalds break; 5681da177e4SLinus Torvalds 5691da177e4SLinus Torvalds err = -EINVAL; 5701da177e4SLinus Torvalds ofs = 0; 5711da177e4SLinus Torvalds } 5721da177e4SLinus Torvalds 5731da177e4SLinus Torvalds return err; 5741da177e4SLinus Torvalds } 5751da177e4SLinus Torvalds 57669423d99SAdrian Hunter static int concat_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) 5771da177e4SLinus Torvalds { 5781da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 5791da177e4SLinus Torvalds int i, err = 0; 5801da177e4SLinus Torvalds 5811da177e4SLinus Torvalds if ((len + ofs) > mtd->size) 5821da177e4SLinus Torvalds return -EINVAL; 5831da177e4SLinus Torvalds 5841da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 5851da177e4SLinus Torvalds struct mtd_info *subdev = concat->subdev[i]; 58669423d99SAdrian Hunter uint64_t size; 5871da177e4SLinus Torvalds 5881da177e4SLinus Torvalds if (ofs >= subdev->size) { 5891da177e4SLinus Torvalds size = 0; 5901da177e4SLinus Torvalds ofs -= subdev->size; 5911da177e4SLinus Torvalds continue; 5921da177e4SLinus Torvalds } 5931da177e4SLinus Torvalds if (ofs + len > subdev->size) 5941da177e4SLinus Torvalds size = subdev->size - ofs; 5951da177e4SLinus Torvalds else 5961da177e4SLinus Torvalds size = len; 5971da177e4SLinus Torvalds 598e1d0fe3cSMartin Krause if (subdev->unlock) { 599b66005cdSArtem Bityutskiy err = mtd_unlock(subdev, ofs, size); 6001da177e4SLinus Torvalds if (err) 6011da177e4SLinus Torvalds break; 602e1d0fe3cSMartin Krause } else 603e1d0fe3cSMartin Krause err = -EOPNOTSUPP; 6041da177e4SLinus Torvalds 6051da177e4SLinus Torvalds len -= size; 6061da177e4SLinus Torvalds if (len == 0) 6071da177e4SLinus Torvalds break; 6081da177e4SLinus Torvalds 6091da177e4SLinus Torvalds err = -EINVAL; 6101da177e4SLinus Torvalds ofs = 0; 6111da177e4SLinus Torvalds } 6121da177e4SLinus Torvalds 6131da177e4SLinus Torvalds return err; 6141da177e4SLinus Torvalds } 6151da177e4SLinus Torvalds 6161da177e4SLinus Torvalds static void concat_sync(struct mtd_info *mtd) 6171da177e4SLinus Torvalds { 6181da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 6191da177e4SLinus Torvalds int i; 6201da177e4SLinus Torvalds 6211da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 6221da177e4SLinus Torvalds struct mtd_info *subdev = concat->subdev[i]; 62385f2f2a8SArtem Bityutskiy mtd_sync(subdev); 6241da177e4SLinus Torvalds } 6251da177e4SLinus Torvalds } 6261da177e4SLinus Torvalds 6271da177e4SLinus Torvalds static int concat_suspend(struct mtd_info *mtd) 6281da177e4SLinus Torvalds { 6291da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 6301da177e4SLinus Torvalds int i, rc = 0; 6311da177e4SLinus Torvalds 6321da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 6331da177e4SLinus Torvalds struct mtd_info *subdev = concat->subdev[i]; 6343fe4bae8SArtem Bityutskiy if ((rc = mtd_suspend(subdev)) < 0) 6351da177e4SLinus Torvalds return rc; 6361da177e4SLinus Torvalds } 6371da177e4SLinus Torvalds return rc; 6381da177e4SLinus Torvalds } 6391da177e4SLinus Torvalds 6401da177e4SLinus Torvalds static void concat_resume(struct mtd_info *mtd) 6411da177e4SLinus Torvalds { 6421da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 6431da177e4SLinus Torvalds int i; 6441da177e4SLinus Torvalds 6451da177e4SLinus Torvalds for (i = 0; i < concat->num_subdev; i++) { 6461da177e4SLinus Torvalds struct mtd_info *subdev = concat->subdev[i]; 6471da177e4SLinus Torvalds subdev->resume(subdev); 6481da177e4SLinus Torvalds } 6491da177e4SLinus Torvalds } 6501da177e4SLinus Torvalds 651e8d32937SAlexander Belyakov static int concat_block_isbad(struct mtd_info *mtd, loff_t ofs) 652e8d32937SAlexander Belyakov { 653e8d32937SAlexander Belyakov struct mtd_concat *concat = CONCAT(mtd); 654e8d32937SAlexander Belyakov int i, res = 0; 655e8d32937SAlexander Belyakov 656e8d32937SAlexander Belyakov if (!concat->subdev[0]->block_isbad) 657e8d32937SAlexander Belyakov return res; 658e8d32937SAlexander Belyakov 659e8d32937SAlexander Belyakov if (ofs > mtd->size) 660e8d32937SAlexander Belyakov return -EINVAL; 661e8d32937SAlexander Belyakov 662e8d32937SAlexander Belyakov for (i = 0; i < concat->num_subdev; i++) { 663e8d32937SAlexander Belyakov struct mtd_info *subdev = concat->subdev[i]; 664e8d32937SAlexander Belyakov 665e8d32937SAlexander Belyakov if (ofs >= subdev->size) { 666e8d32937SAlexander Belyakov ofs -= subdev->size; 667e8d32937SAlexander Belyakov continue; 668e8d32937SAlexander Belyakov } 669e8d32937SAlexander Belyakov 670e8d32937SAlexander Belyakov res = subdev->block_isbad(subdev, ofs); 671e8d32937SAlexander Belyakov break; 672e8d32937SAlexander Belyakov } 673e8d32937SAlexander Belyakov 674e8d32937SAlexander Belyakov return res; 675e8d32937SAlexander Belyakov } 676e8d32937SAlexander Belyakov 677e8d32937SAlexander Belyakov static int concat_block_markbad(struct mtd_info *mtd, loff_t ofs) 678e8d32937SAlexander Belyakov { 679e8d32937SAlexander Belyakov struct mtd_concat *concat = CONCAT(mtd); 680e8d32937SAlexander Belyakov int i, err = -EINVAL; 681e8d32937SAlexander Belyakov 682e8d32937SAlexander Belyakov if (!concat->subdev[0]->block_markbad) 683e8d32937SAlexander Belyakov return 0; 684e8d32937SAlexander Belyakov 685e8d32937SAlexander Belyakov if (ofs > mtd->size) 686e8d32937SAlexander Belyakov return -EINVAL; 687e8d32937SAlexander Belyakov 688e8d32937SAlexander Belyakov for (i = 0; i < concat->num_subdev; i++) { 689e8d32937SAlexander Belyakov struct mtd_info *subdev = concat->subdev[i]; 690e8d32937SAlexander Belyakov 691e8d32937SAlexander Belyakov if (ofs >= subdev->size) { 692e8d32937SAlexander Belyakov ofs -= subdev->size; 693e8d32937SAlexander Belyakov continue; 694e8d32937SAlexander Belyakov } 695e8d32937SAlexander Belyakov 696e8d32937SAlexander Belyakov err = subdev->block_markbad(subdev, ofs); 697f1a28c02SThomas Gleixner if (!err) 698f1a28c02SThomas Gleixner mtd->ecc_stats.badblocks++; 699e8d32937SAlexander Belyakov break; 700e8d32937SAlexander Belyakov } 701e8d32937SAlexander Belyakov 702e8d32937SAlexander Belyakov return err; 703e8d32937SAlexander Belyakov } 704e8d32937SAlexander Belyakov 7051da177e4SLinus Torvalds /* 7066e232cfcSDavid Howells * try to support NOMMU mmaps on concatenated devices 7076e232cfcSDavid Howells * - we don't support subdev spanning as we can't guarantee it'll work 7086e232cfcSDavid Howells */ 7096e232cfcSDavid Howells static unsigned long concat_get_unmapped_area(struct mtd_info *mtd, 7106e232cfcSDavid Howells unsigned long len, 7116e232cfcSDavid Howells unsigned long offset, 7126e232cfcSDavid Howells unsigned long flags) 7136e232cfcSDavid Howells { 7146e232cfcSDavid Howells struct mtd_concat *concat = CONCAT(mtd); 7156e232cfcSDavid Howells int i; 7166e232cfcSDavid Howells 7176e232cfcSDavid Howells for (i = 0; i < concat->num_subdev; i++) { 7186e232cfcSDavid Howells struct mtd_info *subdev = concat->subdev[i]; 7196e232cfcSDavid Howells 7206e232cfcSDavid Howells if (offset >= subdev->size) { 7216e232cfcSDavid Howells offset -= subdev->size; 7226e232cfcSDavid Howells continue; 7236e232cfcSDavid Howells } 7246e232cfcSDavid Howells 7256e232cfcSDavid Howells /* we've found the subdev over which the mapping will reside */ 7266e232cfcSDavid Howells if (offset + len > subdev->size) 7276e232cfcSDavid Howells return (unsigned long) -EINVAL; 7286e232cfcSDavid Howells 7296e232cfcSDavid Howells if (subdev->get_unmapped_area) 73004c601bfSArtem Bityutskiy return mtd_get_unmapped_area(subdev, len, offset, 7316e232cfcSDavid Howells flags); 7326e232cfcSDavid Howells 7336e232cfcSDavid Howells break; 7346e232cfcSDavid Howells } 7356e232cfcSDavid Howells 7366e232cfcSDavid Howells return (unsigned long) -ENOSYS; 7376e232cfcSDavid Howells } 7386e232cfcSDavid Howells 7396e232cfcSDavid Howells /* 7401da177e4SLinus Torvalds * This function constructs a virtual MTD device by concatenating 7411da177e4SLinus Torvalds * num_devs MTD devices. A pointer to the new device object is 7421da177e4SLinus Torvalds * stored to *new_dev upon success. This function does _not_ 7431da177e4SLinus Torvalds * register any devices: this is the caller's responsibility. 7441da177e4SLinus Torvalds */ 7451da177e4SLinus Torvalds struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to concatenate */ 7461da177e4SLinus Torvalds int num_devs, /* number of subdevices */ 747160bbab3SKay Sievers const char *name) 7481da177e4SLinus Torvalds { /* name for the new device */ 7491da177e4SLinus Torvalds int i; 7501da177e4SLinus Torvalds size_t size; 7511da177e4SLinus Torvalds struct mtd_concat *concat; 75226cdb67cSDavid Woodhouse uint32_t max_erasesize, curr_erasesize; 7531da177e4SLinus Torvalds int num_erase_region; 754771df619SHolger Brunck int max_writebufsize = 0; 7551da177e4SLinus Torvalds 7561da177e4SLinus Torvalds printk(KERN_NOTICE "Concatenating MTD devices:\n"); 7571da177e4SLinus Torvalds for (i = 0; i < num_devs; i++) 7581da177e4SLinus Torvalds printk(KERN_NOTICE "(%d): \"%s\"\n", i, subdev[i]->name); 7591da177e4SLinus Torvalds printk(KERN_NOTICE "into device \"%s\"\n", name); 7601da177e4SLinus Torvalds 7611da177e4SLinus Torvalds /* allocate the device structure */ 7621da177e4SLinus Torvalds size = SIZEOF_STRUCT_MTD_CONCAT(num_devs); 76395b93a0cSBurman Yan concat = kzalloc(size, GFP_KERNEL); 7641da177e4SLinus Torvalds if (!concat) { 7651da177e4SLinus Torvalds printk 7661da177e4SLinus Torvalds ("memory allocation error while creating concatenated device \"%s\"\n", 7671da177e4SLinus Torvalds name); 7681da177e4SLinus Torvalds return NULL; 7691da177e4SLinus Torvalds } 7701da177e4SLinus Torvalds concat->subdev = (struct mtd_info **) (concat + 1); 7711da177e4SLinus Torvalds 7721da177e4SLinus Torvalds /* 7731da177e4SLinus Torvalds * Set up the new "super" device's MTD object structure, check for 77492394b5cSBrian Norris * incompatibilities between the subdevices. 7751da177e4SLinus Torvalds */ 7761da177e4SLinus Torvalds concat->mtd.type = subdev[0]->type; 7771da177e4SLinus Torvalds concat->mtd.flags = subdev[0]->flags; 7781da177e4SLinus Torvalds concat->mtd.size = subdev[0]->size; 7791da177e4SLinus Torvalds concat->mtd.erasesize = subdev[0]->erasesize; 78028318776SJoern Engel concat->mtd.writesize = subdev[0]->writesize; 781771df619SHolger Brunck 782771df619SHolger Brunck for (i = 0; i < num_devs; i++) 783771df619SHolger Brunck if (max_writebufsize < subdev[i]->writebufsize) 784771df619SHolger Brunck max_writebufsize = subdev[i]->writebufsize; 785771df619SHolger Brunck concat->mtd.writebufsize = max_writebufsize; 786771df619SHolger Brunck 787a2e1b833SChris Paulson-Ellis concat->mtd.subpage_sft = subdev[0]->subpage_sft; 7881da177e4SLinus Torvalds concat->mtd.oobsize = subdev[0]->oobsize; 7891f92267cSVitaly Wool concat->mtd.oobavail = subdev[0]->oobavail; 790e8d32937SAlexander Belyakov if (subdev[0]->writev) 791e8d32937SAlexander Belyakov concat->mtd.writev = concat_writev; 7921da177e4SLinus Torvalds if (subdev[0]->read_oob) 7931da177e4SLinus Torvalds concat->mtd.read_oob = concat_read_oob; 7941da177e4SLinus Torvalds if (subdev[0]->write_oob) 7951da177e4SLinus Torvalds concat->mtd.write_oob = concat_write_oob; 796e8d32937SAlexander Belyakov if (subdev[0]->block_isbad) 797e8d32937SAlexander Belyakov concat->mtd.block_isbad = concat_block_isbad; 798e8d32937SAlexander Belyakov if (subdev[0]->block_markbad) 799e8d32937SAlexander Belyakov concat->mtd.block_markbad = concat_block_markbad; 8001da177e4SLinus Torvalds 801f1a28c02SThomas Gleixner concat->mtd.ecc_stats.badblocks = subdev[0]->ecc_stats.badblocks; 802f1a28c02SThomas Gleixner 8036e232cfcSDavid Howells concat->mtd.backing_dev_info = subdev[0]->backing_dev_info; 8046e232cfcSDavid Howells 8051da177e4SLinus Torvalds concat->subdev[0] = subdev[0]; 8061da177e4SLinus Torvalds 8071da177e4SLinus Torvalds for (i = 1; i < num_devs; i++) { 8081da177e4SLinus Torvalds if (concat->mtd.type != subdev[i]->type) { 8091da177e4SLinus Torvalds kfree(concat); 8101da177e4SLinus Torvalds printk("Incompatible device type on \"%s\"\n", 8111da177e4SLinus Torvalds subdev[i]->name); 8121da177e4SLinus Torvalds return NULL; 8131da177e4SLinus Torvalds } 8141da177e4SLinus Torvalds if (concat->mtd.flags != subdev[i]->flags) { 8151da177e4SLinus Torvalds /* 8161da177e4SLinus Torvalds * Expect all flags except MTD_WRITEABLE to be 8171da177e4SLinus Torvalds * equal on all subdevices. 8181da177e4SLinus Torvalds */ 8191da177e4SLinus Torvalds if ((concat->mtd.flags ^ subdev[i]-> 8201da177e4SLinus Torvalds flags) & ~MTD_WRITEABLE) { 8211da177e4SLinus Torvalds kfree(concat); 8221da177e4SLinus Torvalds printk("Incompatible device flags on \"%s\"\n", 8231da177e4SLinus Torvalds subdev[i]->name); 8241da177e4SLinus Torvalds return NULL; 8251da177e4SLinus Torvalds } else 8261da177e4SLinus Torvalds /* if writeable attribute differs, 8271da177e4SLinus Torvalds make super device writeable */ 8281da177e4SLinus Torvalds concat->mtd.flags |= 8291da177e4SLinus Torvalds subdev[i]->flags & MTD_WRITEABLE; 8301da177e4SLinus Torvalds } 8316e232cfcSDavid Howells 8326e232cfcSDavid Howells /* only permit direct mapping if the BDIs are all the same 8336e232cfcSDavid Howells * - copy-mapping is still permitted 8346e232cfcSDavid Howells */ 8356e232cfcSDavid Howells if (concat->mtd.backing_dev_info != 8366e232cfcSDavid Howells subdev[i]->backing_dev_info) 8376e232cfcSDavid Howells concat->mtd.backing_dev_info = 8386e232cfcSDavid Howells &default_backing_dev_info; 8396e232cfcSDavid Howells 8401da177e4SLinus Torvalds concat->mtd.size += subdev[i]->size; 841f1a28c02SThomas Gleixner concat->mtd.ecc_stats.badblocks += 842f1a28c02SThomas Gleixner subdev[i]->ecc_stats.badblocks; 84328318776SJoern Engel if (concat->mtd.writesize != subdev[i]->writesize || 84429072b96SThomas Gleixner concat->mtd.subpage_sft != subdev[i]->subpage_sft || 8451da177e4SLinus Torvalds concat->mtd.oobsize != subdev[i]->oobsize || 8461da177e4SLinus Torvalds !concat->mtd.read_oob != !subdev[i]->read_oob || 8471da177e4SLinus Torvalds !concat->mtd.write_oob != !subdev[i]->write_oob) { 8481da177e4SLinus Torvalds kfree(concat); 8491da177e4SLinus Torvalds printk("Incompatible OOB or ECC data on \"%s\"\n", 8501da177e4SLinus Torvalds subdev[i]->name); 8511da177e4SLinus Torvalds return NULL; 8521da177e4SLinus Torvalds } 8531da177e4SLinus Torvalds concat->subdev[i] = subdev[i]; 8541da177e4SLinus Torvalds 8551da177e4SLinus Torvalds } 8561da177e4SLinus Torvalds 8575bd34c09SThomas Gleixner concat->mtd.ecclayout = subdev[0]->ecclayout; 858e8d32937SAlexander Belyakov 8591da177e4SLinus Torvalds concat->num_subdev = num_devs; 8601da177e4SLinus Torvalds concat->mtd.name = name; 8611da177e4SLinus Torvalds 8621da177e4SLinus Torvalds concat->mtd.erase = concat_erase; 8631da177e4SLinus Torvalds concat->mtd.read = concat_read; 8641da177e4SLinus Torvalds concat->mtd.write = concat_write; 8651da177e4SLinus Torvalds concat->mtd.sync = concat_sync; 8661da177e4SLinus Torvalds concat->mtd.lock = concat_lock; 8671da177e4SLinus Torvalds concat->mtd.unlock = concat_unlock; 8681da177e4SLinus Torvalds concat->mtd.suspend = concat_suspend; 8691da177e4SLinus Torvalds concat->mtd.resume = concat_resume; 8706e232cfcSDavid Howells concat->mtd.get_unmapped_area = concat_get_unmapped_area; 8711da177e4SLinus Torvalds 8721da177e4SLinus Torvalds /* 8731da177e4SLinus Torvalds * Combine the erase block size info of the subdevices: 8741da177e4SLinus Torvalds * 8751da177e4SLinus Torvalds * first, walk the map of the new device and see how 8761da177e4SLinus Torvalds * many changes in erase size we have 8771da177e4SLinus Torvalds */ 8781da177e4SLinus Torvalds max_erasesize = curr_erasesize = subdev[0]->erasesize; 8791da177e4SLinus Torvalds num_erase_region = 1; 8801da177e4SLinus Torvalds for (i = 0; i < num_devs; i++) { 8811da177e4SLinus Torvalds if (subdev[i]->numeraseregions == 0) { 8821da177e4SLinus Torvalds /* current subdevice has uniform erase size */ 8831da177e4SLinus Torvalds if (subdev[i]->erasesize != curr_erasesize) { 8841da177e4SLinus Torvalds /* if it differs from the last subdevice's erase size, count it */ 8851da177e4SLinus Torvalds ++num_erase_region; 8861da177e4SLinus Torvalds curr_erasesize = subdev[i]->erasesize; 8871da177e4SLinus Torvalds if (curr_erasesize > max_erasesize) 8881da177e4SLinus Torvalds max_erasesize = curr_erasesize; 8891da177e4SLinus Torvalds } 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 8951da177e4SLinus Torvalds /* walk the list of erase regions, count any changes */ 8961da177e4SLinus Torvalds if (subdev[i]->eraseregions[j].erasesize != 8971da177e4SLinus Torvalds curr_erasesize) { 8981da177e4SLinus Torvalds ++num_erase_region; 8991da177e4SLinus Torvalds curr_erasesize = 9001da177e4SLinus Torvalds subdev[i]->eraseregions[j]. 9011da177e4SLinus Torvalds erasesize; 9021da177e4SLinus Torvalds if (curr_erasesize > max_erasesize) 9031da177e4SLinus Torvalds max_erasesize = curr_erasesize; 9041da177e4SLinus Torvalds } 9051da177e4SLinus Torvalds } 9061da177e4SLinus Torvalds } 9071da177e4SLinus Torvalds } 9081da177e4SLinus Torvalds 9091da177e4SLinus Torvalds if (num_erase_region == 1) { 9101da177e4SLinus Torvalds /* 9111da177e4SLinus Torvalds * All subdevices have the same uniform erase size. 9121da177e4SLinus Torvalds * This is easy: 9131da177e4SLinus Torvalds */ 9141da177e4SLinus Torvalds concat->mtd.erasesize = curr_erasesize; 9151da177e4SLinus Torvalds concat->mtd.numeraseregions = 0; 9161da177e4SLinus Torvalds } else { 91769423d99SAdrian Hunter uint64_t tmp64; 91869423d99SAdrian Hunter 9191da177e4SLinus Torvalds /* 9201da177e4SLinus Torvalds * erase block size varies across the subdevices: allocate 9211da177e4SLinus Torvalds * space to store the data describing the variable erase regions 9221da177e4SLinus Torvalds */ 9231da177e4SLinus Torvalds struct mtd_erase_region_info *erase_region_p; 92469423d99SAdrian Hunter uint64_t begin, position; 9251da177e4SLinus Torvalds 9261da177e4SLinus Torvalds concat->mtd.erasesize = max_erasesize; 9271da177e4SLinus Torvalds concat->mtd.numeraseregions = num_erase_region; 9281da177e4SLinus Torvalds concat->mtd.eraseregions = erase_region_p = 9291da177e4SLinus Torvalds kmalloc(num_erase_region * 9301da177e4SLinus Torvalds sizeof (struct mtd_erase_region_info), GFP_KERNEL); 9311da177e4SLinus Torvalds if (!erase_region_p) { 9321da177e4SLinus Torvalds kfree(concat); 9331da177e4SLinus Torvalds printk 9341da177e4SLinus Torvalds ("memory allocation error while creating erase region list" 9351da177e4SLinus Torvalds " for device \"%s\"\n", name); 9361da177e4SLinus Torvalds return NULL; 9371da177e4SLinus Torvalds } 9381da177e4SLinus Torvalds 9391da177e4SLinus Torvalds /* 9401da177e4SLinus Torvalds * walk the map of the new device once more and fill in 9411da177e4SLinus Torvalds * in erase region info: 9421da177e4SLinus Torvalds */ 9431da177e4SLinus Torvalds curr_erasesize = subdev[0]->erasesize; 9441da177e4SLinus Torvalds begin = position = 0; 9451da177e4SLinus Torvalds for (i = 0; i < num_devs; i++) { 9461da177e4SLinus Torvalds if (subdev[i]->numeraseregions == 0) { 9471da177e4SLinus Torvalds /* current subdevice has uniform erase size */ 9481da177e4SLinus Torvalds if (subdev[i]->erasesize != curr_erasesize) { 9491da177e4SLinus Torvalds /* 9501da177e4SLinus Torvalds * fill in an mtd_erase_region_info structure for the area 9511da177e4SLinus Torvalds * we have walked so far: 9521da177e4SLinus Torvalds */ 9531da177e4SLinus Torvalds erase_region_p->offset = begin; 9541da177e4SLinus Torvalds erase_region_p->erasesize = 9551da177e4SLinus Torvalds curr_erasesize; 95669423d99SAdrian Hunter tmp64 = position - begin; 95769423d99SAdrian Hunter do_div(tmp64, curr_erasesize); 95869423d99SAdrian Hunter erase_region_p->numblocks = tmp64; 9591da177e4SLinus Torvalds begin = position; 9601da177e4SLinus Torvalds 9611da177e4SLinus Torvalds curr_erasesize = subdev[i]->erasesize; 9621da177e4SLinus Torvalds ++erase_region_p; 9631da177e4SLinus Torvalds } 9641da177e4SLinus Torvalds position += subdev[i]->size; 9651da177e4SLinus Torvalds } else { 9661da177e4SLinus Torvalds /* current subdevice has variable erase size */ 9671da177e4SLinus Torvalds int j; 9681da177e4SLinus Torvalds for (j = 0; j < subdev[i]->numeraseregions; j++) { 9691da177e4SLinus Torvalds /* walk the list of erase regions, count any changes */ 9701da177e4SLinus Torvalds if (subdev[i]->eraseregions[j]. 9711da177e4SLinus Torvalds erasesize != curr_erasesize) { 9721da177e4SLinus Torvalds erase_region_p->offset = begin; 9731da177e4SLinus Torvalds erase_region_p->erasesize = 9741da177e4SLinus Torvalds curr_erasesize; 97569423d99SAdrian Hunter tmp64 = position - begin; 97669423d99SAdrian Hunter do_div(tmp64, curr_erasesize); 97769423d99SAdrian Hunter erase_region_p->numblocks = tmp64; 9781da177e4SLinus Torvalds begin = position; 9791da177e4SLinus Torvalds 9801da177e4SLinus Torvalds curr_erasesize = 9811da177e4SLinus Torvalds subdev[i]->eraseregions[j]. 9821da177e4SLinus Torvalds erasesize; 9831da177e4SLinus Torvalds ++erase_region_p; 9841da177e4SLinus Torvalds } 9851da177e4SLinus Torvalds position += 9861da177e4SLinus Torvalds subdev[i]->eraseregions[j]. 98769423d99SAdrian Hunter numblocks * (uint64_t)curr_erasesize; 9881da177e4SLinus Torvalds } 9891da177e4SLinus Torvalds } 9901da177e4SLinus Torvalds } 9911da177e4SLinus Torvalds /* Now write the final entry */ 9921da177e4SLinus Torvalds erase_region_p->offset = begin; 9931da177e4SLinus Torvalds erase_region_p->erasesize = curr_erasesize; 99469423d99SAdrian Hunter tmp64 = position - begin; 99569423d99SAdrian Hunter do_div(tmp64, curr_erasesize); 99669423d99SAdrian Hunter erase_region_p->numblocks = tmp64; 9971da177e4SLinus Torvalds } 9981da177e4SLinus Torvalds 9991da177e4SLinus Torvalds return &concat->mtd; 10001da177e4SLinus Torvalds } 10011da177e4SLinus Torvalds 10021da177e4SLinus Torvalds /* 10031da177e4SLinus Torvalds * This function destroys an MTD object obtained from concat_mtd_devs() 10041da177e4SLinus Torvalds */ 10051da177e4SLinus Torvalds 10061da177e4SLinus Torvalds void mtd_concat_destroy(struct mtd_info *mtd) 10071da177e4SLinus Torvalds { 10081da177e4SLinus Torvalds struct mtd_concat *concat = CONCAT(mtd); 10091da177e4SLinus Torvalds if (concat->mtd.numeraseregions) 10101da177e4SLinus Torvalds kfree(concat->mtd.eraseregions); 10111da177e4SLinus Torvalds kfree(concat); 10121da177e4SLinus Torvalds } 10131da177e4SLinus Torvalds 10141da177e4SLinus Torvalds EXPORT_SYMBOL(mtd_concat_create); 10151da177e4SLinus Torvalds EXPORT_SYMBOL(mtd_concat_destroy); 10161da177e4SLinus Torvalds 10171da177e4SLinus Torvalds MODULE_LICENSE("GPL"); 10181da177e4SLinus Torvalds MODULE_AUTHOR("Robert Kaiser <rkaiser@sysgo.de>"); 10191da177e4SLinus Torvalds MODULE_DESCRIPTION("Generic support for concatenating of MTD devices"); 1020