xref: /openbmc/linux/drivers/mtd/mtdconcat.c (revision 85f2f2a8)
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) {
5591da177e4SLinus Torvalds 			err = subdev->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) {
5991da177e4SLinus Torvalds 			err = subdev->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];
6341da177e4SLinus Torvalds 		if ((rc = subdev->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