10a572655SStefan Roese /* 20a572655SStefan Roese * MTD device concatenation layer 30a572655SStefan Roese * 40a572655SStefan Roese * (C) 2002 Robert Kaiser <rkaiser@sysgo.de> 50a572655SStefan Roese * 60a572655SStefan Roese * NAND support by Christian Gan <cgan@iders.ca> 70a572655SStefan Roese * 80a572655SStefan Roese * This code is GPL 90a572655SStefan Roese */ 100a572655SStefan Roese 110a572655SStefan Roese #include <linux/mtd/mtd.h> 12*7b15e2bbSMike Frysinger #include <linux/compat.h> 130a572655SStefan Roese #include <linux/mtd/concat.h> 140a572655SStefan Roese #include <ubi_uboot.h> 150a572655SStefan Roese 160a572655SStefan Roese /* 170a572655SStefan Roese * Our storage structure: 180a572655SStefan Roese * Subdev points to an array of pointers to struct mtd_info objects 190a572655SStefan Roese * which is allocated along with this structure 200a572655SStefan Roese * 210a572655SStefan Roese */ 220a572655SStefan Roese struct mtd_concat { 230a572655SStefan Roese struct mtd_info mtd; 240a572655SStefan Roese int num_subdev; 250a572655SStefan Roese struct mtd_info **subdev; 260a572655SStefan Roese }; 270a572655SStefan Roese 280a572655SStefan Roese /* 290a572655SStefan Roese * how to calculate the size required for the above structure, 300a572655SStefan Roese * including the pointer array subdev points to: 310a572655SStefan Roese */ 320a572655SStefan Roese #define SIZEOF_STRUCT_MTD_CONCAT(num_subdev) \ 330a572655SStefan Roese ((sizeof(struct mtd_concat) + (num_subdev) * sizeof(struct mtd_info *))) 340a572655SStefan Roese 350a572655SStefan Roese /* 360a572655SStefan Roese * Given a pointer to the MTD object in the mtd_concat structure, 370a572655SStefan Roese * we can retrieve the pointer to that structure with this macro. 380a572655SStefan Roese */ 390a572655SStefan Roese #define CONCAT(x) ((struct mtd_concat *)(x)) 400a572655SStefan Roese 410a572655SStefan Roese /* 420a572655SStefan Roese * MTD methods which look up the relevant subdevice, translate the 430a572655SStefan Roese * effective address and pass through to the subdevice. 440a572655SStefan Roese */ 450a572655SStefan Roese 460a572655SStefan Roese static int 470a572655SStefan Roese concat_read(struct mtd_info *mtd, loff_t from, size_t len, 480a572655SStefan Roese size_t * retlen, u_char * buf) 490a572655SStefan Roese { 500a572655SStefan Roese struct mtd_concat *concat = CONCAT(mtd); 510a572655SStefan Roese int ret = 0, err; 520a572655SStefan Roese int i; 530a572655SStefan Roese 540a572655SStefan Roese *retlen = 0; 550a572655SStefan Roese 560a572655SStefan Roese for (i = 0; i < concat->num_subdev; i++) { 570a572655SStefan Roese struct mtd_info *subdev = concat->subdev[i]; 580a572655SStefan Roese size_t size, retsize; 590a572655SStefan Roese 600a572655SStefan Roese if (from >= subdev->size) { 610a572655SStefan Roese /* Not destined for this subdev */ 620a572655SStefan Roese size = 0; 630a572655SStefan Roese from -= subdev->size; 640a572655SStefan Roese continue; 650a572655SStefan Roese } 660a572655SStefan Roese if (from + len > subdev->size) 670a572655SStefan Roese /* First part goes into this subdev */ 680a572655SStefan Roese size = subdev->size - from; 690a572655SStefan Roese else 700a572655SStefan Roese /* Entire transaction goes into this subdev */ 710a572655SStefan Roese size = len; 720a572655SStefan Roese 730a572655SStefan Roese err = subdev->read(subdev, from, size, &retsize, buf); 740a572655SStefan Roese 750a572655SStefan Roese /* Save information about bitflips! */ 760a572655SStefan Roese if (unlikely(err)) { 770a572655SStefan Roese if (err == -EBADMSG) { 780a572655SStefan Roese mtd->ecc_stats.failed++; 790a572655SStefan Roese ret = err; 800a572655SStefan Roese } else if (err == -EUCLEAN) { 810a572655SStefan Roese mtd->ecc_stats.corrected++; 820a572655SStefan Roese /* Do not overwrite -EBADMSG !! */ 830a572655SStefan Roese if (!ret) 840a572655SStefan Roese ret = err; 850a572655SStefan Roese } else 860a572655SStefan Roese return err; 870a572655SStefan Roese } 880a572655SStefan Roese 890a572655SStefan Roese *retlen += retsize; 900a572655SStefan Roese len -= size; 910a572655SStefan Roese if (len == 0) 920a572655SStefan Roese return ret; 930a572655SStefan Roese 940a572655SStefan Roese buf += size; 950a572655SStefan Roese from = 0; 960a572655SStefan Roese } 970a572655SStefan Roese return -EINVAL; 980a572655SStefan Roese } 990a572655SStefan Roese 1000a572655SStefan Roese static int 1010a572655SStefan Roese concat_write(struct mtd_info *mtd, loff_t to, size_t len, 1020a572655SStefan Roese size_t * retlen, const u_char * buf) 1030a572655SStefan Roese { 1040a572655SStefan Roese struct mtd_concat *concat = CONCAT(mtd); 1050a572655SStefan Roese int err = -EINVAL; 1060a572655SStefan Roese int i; 1070a572655SStefan Roese 1080a572655SStefan Roese if (!(mtd->flags & MTD_WRITEABLE)) 1090a572655SStefan Roese return -EROFS; 1100a572655SStefan Roese 1110a572655SStefan Roese *retlen = 0; 1120a572655SStefan Roese 1130a572655SStefan Roese for (i = 0; i < concat->num_subdev; i++) { 1140a572655SStefan Roese struct mtd_info *subdev = concat->subdev[i]; 1150a572655SStefan Roese size_t size, retsize; 1160a572655SStefan Roese 1170a572655SStefan Roese if (to >= subdev->size) { 1180a572655SStefan Roese size = 0; 1190a572655SStefan Roese to -= subdev->size; 1200a572655SStefan Roese continue; 1210a572655SStefan Roese } 1220a572655SStefan Roese if (to + len > subdev->size) 1230a572655SStefan Roese size = subdev->size - to; 1240a572655SStefan Roese else 1250a572655SStefan Roese size = len; 1260a572655SStefan Roese 1270a572655SStefan Roese if (!(subdev->flags & MTD_WRITEABLE)) 1280a572655SStefan Roese err = -EROFS; 1290a572655SStefan Roese else 1300a572655SStefan Roese err = subdev->write(subdev, to, size, &retsize, buf); 1310a572655SStefan Roese 1320a572655SStefan Roese if (err) 1330a572655SStefan Roese break; 1340a572655SStefan Roese 1350a572655SStefan Roese *retlen += retsize; 1360a572655SStefan Roese len -= size; 1370a572655SStefan Roese if (len == 0) 1380a572655SStefan Roese break; 1390a572655SStefan Roese 1400a572655SStefan Roese err = -EINVAL; 1410a572655SStefan Roese buf += size; 1420a572655SStefan Roese to = 0; 1430a572655SStefan Roese } 1440a572655SStefan Roese return err; 1450a572655SStefan Roese } 1460a572655SStefan Roese 1470a572655SStefan Roese static int 1480a572655SStefan Roese concat_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops) 1490a572655SStefan Roese { 1500a572655SStefan Roese struct mtd_concat *concat = CONCAT(mtd); 1510a572655SStefan Roese struct mtd_oob_ops devops = *ops; 1520a572655SStefan Roese int i, err, ret = 0; 1530a572655SStefan Roese 1540a572655SStefan Roese ops->retlen = ops->oobretlen = 0; 1550a572655SStefan Roese 1560a572655SStefan Roese for (i = 0; i < concat->num_subdev; i++) { 1570a572655SStefan Roese struct mtd_info *subdev = concat->subdev[i]; 1580a572655SStefan Roese 1590a572655SStefan Roese if (from >= subdev->size) { 1600a572655SStefan Roese from -= subdev->size; 1610a572655SStefan Roese continue; 1620a572655SStefan Roese } 1630a572655SStefan Roese 1640a572655SStefan Roese /* partial read ? */ 1650a572655SStefan Roese if (from + devops.len > subdev->size) 1660a572655SStefan Roese devops.len = subdev->size - from; 1670a572655SStefan Roese 1680a572655SStefan Roese err = subdev->read_oob(subdev, from, &devops); 1690a572655SStefan Roese ops->retlen += devops.retlen; 1700a572655SStefan Roese ops->oobretlen += devops.oobretlen; 1710a572655SStefan Roese 1720a572655SStefan Roese /* Save information about bitflips! */ 1730a572655SStefan Roese if (unlikely(err)) { 1740a572655SStefan Roese if (err == -EBADMSG) { 1750a572655SStefan Roese mtd->ecc_stats.failed++; 1760a572655SStefan Roese ret = err; 1770a572655SStefan Roese } else if (err == -EUCLEAN) { 1780a572655SStefan Roese mtd->ecc_stats.corrected++; 1790a572655SStefan Roese /* Do not overwrite -EBADMSG !! */ 1800a572655SStefan Roese if (!ret) 1810a572655SStefan Roese ret = err; 1820a572655SStefan Roese } else 1830a572655SStefan Roese return err; 1840a572655SStefan Roese } 1850a572655SStefan Roese 1860a572655SStefan Roese if (devops.datbuf) { 1870a572655SStefan Roese devops.len = ops->len - ops->retlen; 1880a572655SStefan Roese if (!devops.len) 1890a572655SStefan Roese return ret; 1900a572655SStefan Roese devops.datbuf += devops.retlen; 1910a572655SStefan Roese } 1920a572655SStefan Roese if (devops.oobbuf) { 1930a572655SStefan Roese devops.ooblen = ops->ooblen - ops->oobretlen; 1940a572655SStefan Roese if (!devops.ooblen) 1950a572655SStefan Roese return ret; 1960a572655SStefan Roese devops.oobbuf += ops->oobretlen; 1970a572655SStefan Roese } 1980a572655SStefan Roese 1990a572655SStefan Roese from = 0; 2000a572655SStefan Roese } 2010a572655SStefan Roese return -EINVAL; 2020a572655SStefan Roese } 2030a572655SStefan Roese 2040a572655SStefan Roese static int 2050a572655SStefan Roese concat_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops) 2060a572655SStefan Roese { 2070a572655SStefan Roese struct mtd_concat *concat = CONCAT(mtd); 2080a572655SStefan Roese struct mtd_oob_ops devops = *ops; 2090a572655SStefan Roese int i, err; 2100a572655SStefan Roese 2110a572655SStefan Roese if (!(mtd->flags & MTD_WRITEABLE)) 2120a572655SStefan Roese return -EROFS; 2130a572655SStefan Roese 2140a572655SStefan Roese ops->retlen = 0; 2150a572655SStefan Roese 2160a572655SStefan Roese for (i = 0; i < concat->num_subdev; i++) { 2170a572655SStefan Roese struct mtd_info *subdev = concat->subdev[i]; 2180a572655SStefan Roese 2190a572655SStefan Roese if (to >= subdev->size) { 2200a572655SStefan Roese to -= subdev->size; 2210a572655SStefan Roese continue; 2220a572655SStefan Roese } 2230a572655SStefan Roese 2240a572655SStefan Roese /* partial write ? */ 2250a572655SStefan Roese if (to + devops.len > subdev->size) 2260a572655SStefan Roese devops.len = subdev->size - to; 2270a572655SStefan Roese 2280a572655SStefan Roese err = subdev->write_oob(subdev, to, &devops); 2290a572655SStefan Roese ops->retlen += devops.retlen; 2300a572655SStefan Roese if (err) 2310a572655SStefan Roese return err; 2320a572655SStefan Roese 2330a572655SStefan Roese if (devops.datbuf) { 2340a572655SStefan Roese devops.len = ops->len - ops->retlen; 2350a572655SStefan Roese if (!devops.len) 2360a572655SStefan Roese return 0; 2370a572655SStefan Roese devops.datbuf += devops.retlen; 2380a572655SStefan Roese } 2390a572655SStefan Roese if (devops.oobbuf) { 2400a572655SStefan Roese devops.ooblen = ops->ooblen - ops->oobretlen; 2410a572655SStefan Roese if (!devops.ooblen) 2420a572655SStefan Roese return 0; 2430a572655SStefan Roese devops.oobbuf += devops.oobretlen; 2440a572655SStefan Roese } 2450a572655SStefan Roese to = 0; 2460a572655SStefan Roese } 2470a572655SStefan Roese return -EINVAL; 2480a572655SStefan Roese } 2490a572655SStefan Roese 2500a572655SStefan Roese static void concat_erase_callback(struct erase_info *instr) 2510a572655SStefan Roese { 2520a572655SStefan Roese /* Nothing to do here in U-Boot */ 2530a572655SStefan Roese } 2540a572655SStefan Roese 2550a572655SStefan Roese static int concat_dev_erase(struct mtd_info *mtd, struct erase_info *erase) 2560a572655SStefan Roese { 2570a572655SStefan Roese int err; 2580a572655SStefan Roese wait_queue_head_t waitq; 2590a572655SStefan Roese DECLARE_WAITQUEUE(wait, current); 2600a572655SStefan Roese 2610a572655SStefan Roese /* 2620a572655SStefan Roese * This code was stol^H^H^H^Hinspired by mtdchar.c 2630a572655SStefan Roese */ 2640a572655SStefan Roese init_waitqueue_head(&waitq); 2650a572655SStefan Roese 2660a572655SStefan Roese erase->mtd = mtd; 2670a572655SStefan Roese erase->callback = concat_erase_callback; 2680a572655SStefan Roese erase->priv = (unsigned long) &waitq; 2690a572655SStefan Roese 2700a572655SStefan Roese /* 2710a572655SStefan Roese * FIXME: Allow INTERRUPTIBLE. Which means 2720a572655SStefan Roese * not having the wait_queue head on the stack. 2730a572655SStefan Roese */ 2740a572655SStefan Roese err = mtd->erase(mtd, erase); 2750a572655SStefan Roese if (!err) { 2760a572655SStefan Roese set_current_state(TASK_UNINTERRUPTIBLE); 2770a572655SStefan Roese add_wait_queue(&waitq, &wait); 2780a572655SStefan Roese if (erase->state != MTD_ERASE_DONE 2790a572655SStefan Roese && erase->state != MTD_ERASE_FAILED) 2800a572655SStefan Roese schedule(); 2810a572655SStefan Roese remove_wait_queue(&waitq, &wait); 2820a572655SStefan Roese set_current_state(TASK_RUNNING); 2830a572655SStefan Roese 2840a572655SStefan Roese err = (erase->state == MTD_ERASE_FAILED) ? -EIO : 0; 2850a572655SStefan Roese } 2860a572655SStefan Roese return err; 2870a572655SStefan Roese } 2880a572655SStefan Roese 2890a572655SStefan Roese static int concat_erase(struct mtd_info *mtd, struct erase_info *instr) 2900a572655SStefan Roese { 2910a572655SStefan Roese struct mtd_concat *concat = CONCAT(mtd); 2920a572655SStefan Roese struct mtd_info *subdev; 2930a572655SStefan Roese int i, err; 2940a572655SStefan Roese uint64_t length, offset = 0; 2950a572655SStefan Roese struct erase_info *erase; 2960a572655SStefan Roese 2970a572655SStefan Roese if (!(mtd->flags & MTD_WRITEABLE)) 2980a572655SStefan Roese return -EROFS; 2990a572655SStefan Roese 3000a572655SStefan Roese if (instr->addr > concat->mtd.size) 3010a572655SStefan Roese return -EINVAL; 3020a572655SStefan Roese 3030a572655SStefan Roese if (instr->len + instr->addr > concat->mtd.size) 3040a572655SStefan Roese return -EINVAL; 3050a572655SStefan Roese 3060a572655SStefan Roese /* 3070a572655SStefan Roese * Check for proper erase block alignment of the to-be-erased area. 3080a572655SStefan Roese * It is easier to do this based on the super device's erase 3090a572655SStefan Roese * region info rather than looking at each particular sub-device 3100a572655SStefan Roese * in turn. 3110a572655SStefan Roese */ 3120a572655SStefan Roese if (!concat->mtd.numeraseregions) { 3130a572655SStefan Roese /* the easy case: device has uniform erase block size */ 3140a572655SStefan Roese if (instr->addr & (concat->mtd.erasesize - 1)) 3150a572655SStefan Roese return -EINVAL; 3160a572655SStefan Roese if (instr->len & (concat->mtd.erasesize - 1)) 3170a572655SStefan Roese return -EINVAL; 3180a572655SStefan Roese } else { 3190a572655SStefan Roese /* device has variable erase size */ 3200a572655SStefan Roese struct mtd_erase_region_info *erase_regions = 3210a572655SStefan Roese concat->mtd.eraseregions; 3220a572655SStefan Roese 3230a572655SStefan Roese /* 3240a572655SStefan Roese * Find the erase region where the to-be-erased area begins: 3250a572655SStefan Roese */ 3260a572655SStefan Roese for (i = 0; i < concat->mtd.numeraseregions && 3270a572655SStefan Roese instr->addr >= erase_regions[i].offset; i++) ; 3280a572655SStefan Roese --i; 3290a572655SStefan Roese 3300a572655SStefan Roese /* 3310a572655SStefan Roese * Now erase_regions[i] is the region in which the 3320a572655SStefan Roese * to-be-erased area begins. Verify that the starting 3330a572655SStefan Roese * offset is aligned to this region's erase size: 3340a572655SStefan Roese */ 3350a572655SStefan Roese if (instr->addr & (erase_regions[i].erasesize - 1)) 3360a572655SStefan Roese return -EINVAL; 3370a572655SStefan Roese 3380a572655SStefan Roese /* 3390a572655SStefan Roese * now find the erase region where the to-be-erased area ends: 3400a572655SStefan Roese */ 3410a572655SStefan Roese for (; i < concat->mtd.numeraseregions && 3420a572655SStefan Roese (instr->addr + instr->len) >= erase_regions[i].offset; 3430a572655SStefan Roese ++i) ; 3440a572655SStefan Roese --i; 3450a572655SStefan Roese /* 3460a572655SStefan Roese * check if the ending offset is aligned to this region's erase size 3470a572655SStefan Roese */ 3480a572655SStefan Roese if ((instr->addr + instr->len) & (erase_regions[i].erasesize - 3490a572655SStefan Roese 1)) 3500a572655SStefan Roese return -EINVAL; 3510a572655SStefan Roese } 3520a572655SStefan Roese 3530a572655SStefan Roese instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN; 3540a572655SStefan Roese 3550a572655SStefan Roese /* make a local copy of instr to avoid modifying the caller's struct */ 3560a572655SStefan Roese erase = kmalloc(sizeof (struct erase_info), GFP_KERNEL); 3570a572655SStefan Roese 3580a572655SStefan Roese if (!erase) 3590a572655SStefan Roese return -ENOMEM; 3600a572655SStefan Roese 3610a572655SStefan Roese *erase = *instr; 3620a572655SStefan Roese length = instr->len; 3630a572655SStefan Roese 3640a572655SStefan Roese /* 3650a572655SStefan Roese * find the subdevice where the to-be-erased area begins, adjust 3660a572655SStefan Roese * starting offset to be relative to the subdevice start 3670a572655SStefan Roese */ 3680a572655SStefan Roese for (i = 0; i < concat->num_subdev; i++) { 3690a572655SStefan Roese subdev = concat->subdev[i]; 3700a572655SStefan Roese if (subdev->size <= erase->addr) { 3710a572655SStefan Roese erase->addr -= subdev->size; 3720a572655SStefan Roese offset += subdev->size; 3730a572655SStefan Roese } else { 3740a572655SStefan Roese break; 3750a572655SStefan Roese } 3760a572655SStefan Roese } 3770a572655SStefan Roese 3780a572655SStefan Roese /* must never happen since size limit has been verified above */ 3790a572655SStefan Roese BUG_ON(i >= concat->num_subdev); 3800a572655SStefan Roese 3810a572655SStefan Roese /* now do the erase: */ 3820a572655SStefan Roese err = 0; 3830a572655SStefan Roese for (; length > 0; i++) { 3840a572655SStefan Roese /* loop for all subdevices affected by this request */ 3850a572655SStefan Roese subdev = concat->subdev[i]; /* get current subdevice */ 3860a572655SStefan Roese 3870a572655SStefan Roese /* limit length to subdevice's size: */ 3880a572655SStefan Roese if (erase->addr + length > subdev->size) 3890a572655SStefan Roese erase->len = subdev->size - erase->addr; 3900a572655SStefan Roese else 3910a572655SStefan Roese erase->len = length; 3920a572655SStefan Roese 3930a572655SStefan Roese if (!(subdev->flags & MTD_WRITEABLE)) { 3940a572655SStefan Roese err = -EROFS; 3950a572655SStefan Roese break; 3960a572655SStefan Roese } 3970a572655SStefan Roese length -= erase->len; 3980a572655SStefan Roese if ((err = concat_dev_erase(subdev, erase))) { 3990a572655SStefan Roese /* sanity check: should never happen since 4000a572655SStefan Roese * block alignment has been checked above */ 4010a572655SStefan Roese BUG_ON(err == -EINVAL); 4020a572655SStefan Roese if (erase->fail_addr != MTD_FAIL_ADDR_UNKNOWN) 4030a572655SStefan Roese instr->fail_addr = erase->fail_addr + offset; 4040a572655SStefan Roese break; 4050a572655SStefan Roese } 4060a572655SStefan Roese /* 4070a572655SStefan Roese * erase->addr specifies the offset of the area to be 4080a572655SStefan Roese * erased *within the current subdevice*. It can be 4090a572655SStefan Roese * non-zero only the first time through this loop, i.e. 4100a572655SStefan Roese * for the first subdevice where blocks need to be erased. 4110a572655SStefan Roese * All the following erases must begin at the start of the 4120a572655SStefan Roese * current subdevice, i.e. at offset zero. 4130a572655SStefan Roese */ 4140a572655SStefan Roese erase->addr = 0; 4150a572655SStefan Roese offset += subdev->size; 4160a572655SStefan Roese } 4170a572655SStefan Roese instr->state = erase->state; 4180a572655SStefan Roese kfree(erase); 4190a572655SStefan Roese if (err) 4200a572655SStefan Roese return err; 4210a572655SStefan Roese 4220a572655SStefan Roese if (instr->callback) 4230a572655SStefan Roese instr->callback(instr); 4240a572655SStefan Roese return 0; 4250a572655SStefan Roese } 4260a572655SStefan Roese 4270a572655SStefan Roese static int concat_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) 4280a572655SStefan Roese { 4290a572655SStefan Roese struct mtd_concat *concat = CONCAT(mtd); 4300a572655SStefan Roese int i, err = -EINVAL; 4310a572655SStefan Roese 4320a572655SStefan Roese if ((len + ofs) > mtd->size) 4330a572655SStefan Roese return -EINVAL; 4340a572655SStefan Roese 4350a572655SStefan Roese for (i = 0; i < concat->num_subdev; i++) { 4360a572655SStefan Roese struct mtd_info *subdev = concat->subdev[i]; 4370a572655SStefan Roese uint64_t size; 4380a572655SStefan Roese 4390a572655SStefan Roese if (ofs >= subdev->size) { 4400a572655SStefan Roese size = 0; 4410a572655SStefan Roese ofs -= subdev->size; 4420a572655SStefan Roese continue; 4430a572655SStefan Roese } 4440a572655SStefan Roese if (ofs + len > subdev->size) 4450a572655SStefan Roese size = subdev->size - ofs; 4460a572655SStefan Roese else 4470a572655SStefan Roese size = len; 4480a572655SStefan Roese 4490a572655SStefan Roese err = subdev->lock(subdev, ofs, size); 4500a572655SStefan Roese 4510a572655SStefan Roese if (err) 4520a572655SStefan Roese break; 4530a572655SStefan Roese 4540a572655SStefan Roese len -= size; 4550a572655SStefan Roese if (len == 0) 4560a572655SStefan Roese break; 4570a572655SStefan Roese 4580a572655SStefan Roese err = -EINVAL; 4590a572655SStefan Roese ofs = 0; 4600a572655SStefan Roese } 4610a572655SStefan Roese 4620a572655SStefan Roese return err; 4630a572655SStefan Roese } 4640a572655SStefan Roese 4650a572655SStefan Roese static int concat_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) 4660a572655SStefan Roese { 4670a572655SStefan Roese struct mtd_concat *concat = CONCAT(mtd); 4680a572655SStefan Roese int i, err = 0; 4690a572655SStefan Roese 4700a572655SStefan Roese if ((len + ofs) > mtd->size) 4710a572655SStefan Roese return -EINVAL; 4720a572655SStefan Roese 4730a572655SStefan Roese for (i = 0; i < concat->num_subdev; i++) { 4740a572655SStefan Roese struct mtd_info *subdev = concat->subdev[i]; 4750a572655SStefan Roese uint64_t size; 4760a572655SStefan Roese 4770a572655SStefan Roese if (ofs >= subdev->size) { 4780a572655SStefan Roese size = 0; 4790a572655SStefan Roese ofs -= subdev->size; 4800a572655SStefan Roese continue; 4810a572655SStefan Roese } 4820a572655SStefan Roese if (ofs + len > subdev->size) 4830a572655SStefan Roese size = subdev->size - ofs; 4840a572655SStefan Roese else 4850a572655SStefan Roese size = len; 4860a572655SStefan Roese 4870a572655SStefan Roese err = subdev->unlock(subdev, ofs, size); 4880a572655SStefan Roese 4890a572655SStefan Roese if (err) 4900a572655SStefan Roese break; 4910a572655SStefan Roese 4920a572655SStefan Roese len -= size; 4930a572655SStefan Roese if (len == 0) 4940a572655SStefan Roese break; 4950a572655SStefan Roese 4960a572655SStefan Roese err = -EINVAL; 4970a572655SStefan Roese ofs = 0; 4980a572655SStefan Roese } 4990a572655SStefan Roese 5000a572655SStefan Roese return err; 5010a572655SStefan Roese } 5020a572655SStefan Roese 5030a572655SStefan Roese static void concat_sync(struct mtd_info *mtd) 5040a572655SStefan Roese { 5050a572655SStefan Roese struct mtd_concat *concat = CONCAT(mtd); 5060a572655SStefan Roese int i; 5070a572655SStefan Roese 5080a572655SStefan Roese for (i = 0; i < concat->num_subdev; i++) { 5090a572655SStefan Roese struct mtd_info *subdev = concat->subdev[i]; 5100a572655SStefan Roese subdev->sync(subdev); 5110a572655SStefan Roese } 5120a572655SStefan Roese } 5130a572655SStefan Roese 5140a572655SStefan Roese static int concat_block_isbad(struct mtd_info *mtd, loff_t ofs) 5150a572655SStefan Roese { 5160a572655SStefan Roese struct mtd_concat *concat = CONCAT(mtd); 5170a572655SStefan Roese int i, res = 0; 5180a572655SStefan Roese 5190a572655SStefan Roese if (!concat->subdev[0]->block_isbad) 5200a572655SStefan Roese return res; 5210a572655SStefan Roese 5220a572655SStefan Roese if (ofs > mtd->size) 5230a572655SStefan Roese return -EINVAL; 5240a572655SStefan Roese 5250a572655SStefan Roese for (i = 0; i < concat->num_subdev; i++) { 5260a572655SStefan Roese struct mtd_info *subdev = concat->subdev[i]; 5270a572655SStefan Roese 5280a572655SStefan Roese if (ofs >= subdev->size) { 5290a572655SStefan Roese ofs -= subdev->size; 5300a572655SStefan Roese continue; 5310a572655SStefan Roese } 5320a572655SStefan Roese 5330a572655SStefan Roese res = subdev->block_isbad(subdev, ofs); 5340a572655SStefan Roese break; 5350a572655SStefan Roese } 5360a572655SStefan Roese 5370a572655SStefan Roese return res; 5380a572655SStefan Roese } 5390a572655SStefan Roese 5400a572655SStefan Roese static int concat_block_markbad(struct mtd_info *mtd, loff_t ofs) 5410a572655SStefan Roese { 5420a572655SStefan Roese struct mtd_concat *concat = CONCAT(mtd); 5430a572655SStefan Roese int i, err = -EINVAL; 5440a572655SStefan Roese 5450a572655SStefan Roese if (!concat->subdev[0]->block_markbad) 5460a572655SStefan Roese return 0; 5470a572655SStefan Roese 5480a572655SStefan Roese if (ofs > mtd->size) 5490a572655SStefan Roese return -EINVAL; 5500a572655SStefan Roese 5510a572655SStefan Roese for (i = 0; i < concat->num_subdev; i++) { 5520a572655SStefan Roese struct mtd_info *subdev = concat->subdev[i]; 5530a572655SStefan Roese 5540a572655SStefan Roese if (ofs >= subdev->size) { 5550a572655SStefan Roese ofs -= subdev->size; 5560a572655SStefan Roese continue; 5570a572655SStefan Roese } 5580a572655SStefan Roese 5590a572655SStefan Roese err = subdev->block_markbad(subdev, ofs); 5600a572655SStefan Roese if (!err) 5610a572655SStefan Roese mtd->ecc_stats.badblocks++; 5620a572655SStefan Roese break; 5630a572655SStefan Roese } 5640a572655SStefan Roese 5650a572655SStefan Roese return err; 5660a572655SStefan Roese } 5670a572655SStefan Roese 5680a572655SStefan Roese /* 5690a572655SStefan Roese * This function constructs a virtual MTD device by concatenating 5700a572655SStefan Roese * num_devs MTD devices. A pointer to the new device object is 5710a572655SStefan Roese * stored to *new_dev upon success. This function does _not_ 5720a572655SStefan Roese * register any devices: this is the caller's responsibility. 5730a572655SStefan Roese */ 5740a572655SStefan Roese struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to concatenate */ 5750a572655SStefan Roese int num_devs, /* number of subdevices */ 5760a572655SStefan Roese const char *name) 5770a572655SStefan Roese { /* name for the new device */ 5780a572655SStefan Roese int i; 5790a572655SStefan Roese size_t size; 5800a572655SStefan Roese struct mtd_concat *concat; 5810a572655SStefan Roese uint32_t max_erasesize, curr_erasesize; 5820a572655SStefan Roese int num_erase_region; 5830a572655SStefan Roese 5840a572655SStefan Roese debug("Concatenating MTD devices:\n"); 5850a572655SStefan Roese for (i = 0; i < num_devs; i++) 5860a572655SStefan Roese debug("(%d): \"%s\"\n", i, subdev[i]->name); 5870a572655SStefan Roese debug("into device \"%s\"\n", name); 5880a572655SStefan Roese 5890a572655SStefan Roese /* allocate the device structure */ 5900a572655SStefan Roese size = SIZEOF_STRUCT_MTD_CONCAT(num_devs); 5910a572655SStefan Roese concat = kzalloc(size, GFP_KERNEL); 5920a572655SStefan Roese if (!concat) { 5930a572655SStefan Roese printk 5940a572655SStefan Roese ("memory allocation error while creating concatenated device \"%s\"\n", 5950a572655SStefan Roese name); 5960a572655SStefan Roese return NULL; 5970a572655SStefan Roese } 5980a572655SStefan Roese concat->subdev = (struct mtd_info **) (concat + 1); 5990a572655SStefan Roese 6000a572655SStefan Roese /* 6010a572655SStefan Roese * Set up the new "super" device's MTD object structure, check for 6020a572655SStefan Roese * incompatibilites between the subdevices. 6030a572655SStefan Roese */ 6040a572655SStefan Roese concat->mtd.type = subdev[0]->type; 6050a572655SStefan Roese concat->mtd.flags = subdev[0]->flags; 6060a572655SStefan Roese concat->mtd.size = subdev[0]->size; 6070a572655SStefan Roese concat->mtd.erasesize = subdev[0]->erasesize; 6080a572655SStefan Roese concat->mtd.writesize = subdev[0]->writesize; 6090a572655SStefan Roese concat->mtd.subpage_sft = subdev[0]->subpage_sft; 6100a572655SStefan Roese concat->mtd.oobsize = subdev[0]->oobsize; 6110a572655SStefan Roese concat->mtd.oobavail = subdev[0]->oobavail; 6120a572655SStefan Roese if (subdev[0]->read_oob) 6130a572655SStefan Roese concat->mtd.read_oob = concat_read_oob; 6140a572655SStefan Roese if (subdev[0]->write_oob) 6150a572655SStefan Roese concat->mtd.write_oob = concat_write_oob; 6160a572655SStefan Roese if (subdev[0]->block_isbad) 6170a572655SStefan Roese concat->mtd.block_isbad = concat_block_isbad; 6180a572655SStefan Roese if (subdev[0]->block_markbad) 6190a572655SStefan Roese concat->mtd.block_markbad = concat_block_markbad; 6200a572655SStefan Roese 6210a572655SStefan Roese concat->mtd.ecc_stats.badblocks = subdev[0]->ecc_stats.badblocks; 6220a572655SStefan Roese 6230a572655SStefan Roese concat->subdev[0] = subdev[0]; 6240a572655SStefan Roese 6250a572655SStefan Roese for (i = 1; i < num_devs; i++) { 6260a572655SStefan Roese if (concat->mtd.type != subdev[i]->type) { 6270a572655SStefan Roese kfree(concat); 6280a572655SStefan Roese printk("Incompatible device type on \"%s\"\n", 6290a572655SStefan Roese subdev[i]->name); 6300a572655SStefan Roese return NULL; 6310a572655SStefan Roese } 6320a572655SStefan Roese if (concat->mtd.flags != subdev[i]->flags) { 6330a572655SStefan Roese /* 6340a572655SStefan Roese * Expect all flags except MTD_WRITEABLE to be 6350a572655SStefan Roese * equal on all subdevices. 6360a572655SStefan Roese */ 6370a572655SStefan Roese if ((concat->mtd.flags ^ subdev[i]-> 6380a572655SStefan Roese flags) & ~MTD_WRITEABLE) { 6390a572655SStefan Roese kfree(concat); 6400a572655SStefan Roese printk("Incompatible device flags on \"%s\"\n", 6410a572655SStefan Roese subdev[i]->name); 6420a572655SStefan Roese return NULL; 6430a572655SStefan Roese } else 6440a572655SStefan Roese /* if writeable attribute differs, 6450a572655SStefan Roese make super device writeable */ 6460a572655SStefan Roese concat->mtd.flags |= 6470a572655SStefan Roese subdev[i]->flags & MTD_WRITEABLE; 6480a572655SStefan Roese } 6490a572655SStefan Roese 6500a572655SStefan Roese concat->mtd.size += subdev[i]->size; 6510a572655SStefan Roese concat->mtd.ecc_stats.badblocks += 6520a572655SStefan Roese subdev[i]->ecc_stats.badblocks; 6530a572655SStefan Roese if (concat->mtd.writesize != subdev[i]->writesize || 6540a572655SStefan Roese concat->mtd.subpage_sft != subdev[i]->subpage_sft || 6550a572655SStefan Roese concat->mtd.oobsize != subdev[i]->oobsize || 6560a572655SStefan Roese !concat->mtd.read_oob != !subdev[i]->read_oob || 6570a572655SStefan Roese !concat->mtd.write_oob != !subdev[i]->write_oob) { 6580a572655SStefan Roese kfree(concat); 6590a572655SStefan Roese printk("Incompatible OOB or ECC data on \"%s\"\n", 6600a572655SStefan Roese subdev[i]->name); 6610a572655SStefan Roese return NULL; 6620a572655SStefan Roese } 6630a572655SStefan Roese concat->subdev[i] = subdev[i]; 6640a572655SStefan Roese 6650a572655SStefan Roese } 6660a572655SStefan Roese 6670a572655SStefan Roese concat->mtd.ecclayout = subdev[0]->ecclayout; 6680a572655SStefan Roese 6690a572655SStefan Roese concat->num_subdev = num_devs; 6700a572655SStefan Roese concat->mtd.name = name; 6710a572655SStefan Roese 6720a572655SStefan Roese concat->mtd.erase = concat_erase; 6730a572655SStefan Roese concat->mtd.read = concat_read; 6740a572655SStefan Roese concat->mtd.write = concat_write; 6750a572655SStefan Roese concat->mtd.sync = concat_sync; 6760a572655SStefan Roese concat->mtd.lock = concat_lock; 6770a572655SStefan Roese concat->mtd.unlock = concat_unlock; 6780a572655SStefan Roese 6790a572655SStefan Roese /* 6800a572655SStefan Roese * Combine the erase block size info of the subdevices: 6810a572655SStefan Roese * 6820a572655SStefan Roese * first, walk the map of the new device and see how 6830a572655SStefan Roese * many changes in erase size we have 6840a572655SStefan Roese */ 6850a572655SStefan Roese max_erasesize = curr_erasesize = subdev[0]->erasesize; 6860a572655SStefan Roese num_erase_region = 1; 6870a572655SStefan Roese for (i = 0; i < num_devs; i++) { 6880a572655SStefan Roese if (subdev[i]->numeraseregions == 0) { 6890a572655SStefan Roese /* current subdevice has uniform erase size */ 6900a572655SStefan Roese if (subdev[i]->erasesize != curr_erasesize) { 6910a572655SStefan Roese /* if it differs from the last subdevice's erase size, count it */ 6920a572655SStefan Roese ++num_erase_region; 6930a572655SStefan Roese curr_erasesize = subdev[i]->erasesize; 6940a572655SStefan Roese if (curr_erasesize > max_erasesize) 6950a572655SStefan Roese max_erasesize = curr_erasesize; 6960a572655SStefan Roese } 6970a572655SStefan Roese } else { 6980a572655SStefan Roese /* current subdevice has variable erase size */ 6990a572655SStefan Roese int j; 7000a572655SStefan Roese for (j = 0; j < subdev[i]->numeraseregions; j++) { 7010a572655SStefan Roese 7020a572655SStefan Roese /* walk the list of erase regions, count any changes */ 7030a572655SStefan Roese if (subdev[i]->eraseregions[j].erasesize != 7040a572655SStefan Roese curr_erasesize) { 7050a572655SStefan Roese ++num_erase_region; 7060a572655SStefan Roese curr_erasesize = 7070a572655SStefan Roese subdev[i]->eraseregions[j]. 7080a572655SStefan Roese erasesize; 7090a572655SStefan Roese if (curr_erasesize > max_erasesize) 7100a572655SStefan Roese max_erasesize = curr_erasesize; 7110a572655SStefan Roese } 7120a572655SStefan Roese } 7130a572655SStefan Roese } 7140a572655SStefan Roese } 7150a572655SStefan Roese 7160a572655SStefan Roese if (num_erase_region == 1) { 7170a572655SStefan Roese /* 7180a572655SStefan Roese * All subdevices have the same uniform erase size. 7190a572655SStefan Roese * This is easy: 7200a572655SStefan Roese */ 7210a572655SStefan Roese concat->mtd.erasesize = curr_erasesize; 7220a572655SStefan Roese concat->mtd.numeraseregions = 0; 7230a572655SStefan Roese } else { 7240a572655SStefan Roese uint64_t tmp64; 7250a572655SStefan Roese 7260a572655SStefan Roese /* 7270a572655SStefan Roese * erase block size varies across the subdevices: allocate 7280a572655SStefan Roese * space to store the data describing the variable erase regions 7290a572655SStefan Roese */ 7300a572655SStefan Roese struct mtd_erase_region_info *erase_region_p; 7310a572655SStefan Roese uint64_t begin, position; 7320a572655SStefan Roese 7330a572655SStefan Roese concat->mtd.erasesize = max_erasesize; 7340a572655SStefan Roese concat->mtd.numeraseregions = num_erase_region; 7350a572655SStefan Roese concat->mtd.eraseregions = erase_region_p = 7360a572655SStefan Roese kmalloc(num_erase_region * 7370a572655SStefan Roese sizeof (struct mtd_erase_region_info), GFP_KERNEL); 7380a572655SStefan Roese if (!erase_region_p) { 7390a572655SStefan Roese kfree(concat); 7400a572655SStefan Roese printk 7410a572655SStefan Roese ("memory allocation error while creating erase region list" 7420a572655SStefan Roese " for device \"%s\"\n", name); 7430a572655SStefan Roese return NULL; 7440a572655SStefan Roese } 7450a572655SStefan Roese 7460a572655SStefan Roese /* 7470a572655SStefan Roese * walk the map of the new device once more and fill in 7480a572655SStefan Roese * in erase region info: 7490a572655SStefan Roese */ 7500a572655SStefan Roese curr_erasesize = subdev[0]->erasesize; 7510a572655SStefan Roese begin = position = 0; 7520a572655SStefan Roese for (i = 0; i < num_devs; i++) { 7530a572655SStefan Roese if (subdev[i]->numeraseregions == 0) { 7540a572655SStefan Roese /* current subdevice has uniform erase size */ 7550a572655SStefan Roese if (subdev[i]->erasesize != curr_erasesize) { 7560a572655SStefan Roese /* 7570a572655SStefan Roese * fill in an mtd_erase_region_info structure for the area 7580a572655SStefan Roese * we have walked so far: 7590a572655SStefan Roese */ 7600a572655SStefan Roese erase_region_p->offset = begin; 7610a572655SStefan Roese erase_region_p->erasesize = 7620a572655SStefan Roese curr_erasesize; 7630a572655SStefan Roese tmp64 = position - begin; 7640a572655SStefan Roese do_div(tmp64, curr_erasesize); 7650a572655SStefan Roese erase_region_p->numblocks = tmp64; 7660a572655SStefan Roese begin = position; 7670a572655SStefan Roese 7680a572655SStefan Roese curr_erasesize = subdev[i]->erasesize; 7690a572655SStefan Roese ++erase_region_p; 7700a572655SStefan Roese } 7710a572655SStefan Roese position += subdev[i]->size; 7720a572655SStefan Roese } else { 7730a572655SStefan Roese /* current subdevice has variable erase size */ 7740a572655SStefan Roese int j; 7750a572655SStefan Roese for (j = 0; j < subdev[i]->numeraseregions; j++) { 7760a572655SStefan Roese /* walk the list of erase regions, count any changes */ 7770a572655SStefan Roese if (subdev[i]->eraseregions[j]. 7780a572655SStefan Roese erasesize != curr_erasesize) { 7790a572655SStefan Roese erase_region_p->offset = begin; 7800a572655SStefan Roese erase_region_p->erasesize = 7810a572655SStefan Roese curr_erasesize; 7820a572655SStefan Roese tmp64 = position - begin; 7830a572655SStefan Roese do_div(tmp64, curr_erasesize); 7840a572655SStefan Roese erase_region_p->numblocks = tmp64; 7850a572655SStefan Roese begin = position; 7860a572655SStefan Roese 7870a572655SStefan Roese curr_erasesize = 7880a572655SStefan Roese subdev[i]->eraseregions[j]. 7890a572655SStefan Roese erasesize; 7900a572655SStefan Roese ++erase_region_p; 7910a572655SStefan Roese } 7920a572655SStefan Roese position += 7930a572655SStefan Roese subdev[i]->eraseregions[j]. 7940a572655SStefan Roese numblocks * (uint64_t)curr_erasesize; 7950a572655SStefan Roese } 7960a572655SStefan Roese } 7970a572655SStefan Roese } 7980a572655SStefan Roese /* Now write the final entry */ 7990a572655SStefan Roese erase_region_p->offset = begin; 8000a572655SStefan Roese erase_region_p->erasesize = curr_erasesize; 8010a572655SStefan Roese tmp64 = position - begin; 8020a572655SStefan Roese do_div(tmp64, curr_erasesize); 8030a572655SStefan Roese erase_region_p->numblocks = tmp64; 8040a572655SStefan Roese } 8050a572655SStefan Roese 8060a572655SStefan Roese return &concat->mtd; 8070a572655SStefan Roese } 808