11da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
21da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * MTD device concatenation layer
31da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
4a1452a3771c4eb85bd779790b040efdc36f4274eDavid Woodhouse * Copyright © 2002 Robert Kaiser <rkaiser@sysgo.de>
5a1452a3771c4eb85bd779790b040efdc36f4274eDavid Woodhouse * Copyright © 2002-2010 David Woodhouse <dwmw2@infradead.org>
61da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
71da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * NAND support by Christian Gan <cgan@iders.ca>
81da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
9a1452a3771c4eb85bd779790b040efdc36f4274eDavid Woodhouse * This program is free software; you can redistribute it and/or modify
10a1452a3771c4eb85bd779790b040efdc36f4274eDavid Woodhouse * it under the terms of the GNU General Public License as published by
11a1452a3771c4eb85bd779790b040efdc36f4274eDavid Woodhouse * the Free Software Foundation; either version 2 of the License, or
12a1452a3771c4eb85bd779790b040efdc36f4274eDavid Woodhouse * (at your option) any later version.
13a1452a3771c4eb85bd779790b040efdc36f4274eDavid Woodhouse *
14a1452a3771c4eb85bd779790b040efdc36f4274eDavid Woodhouse * This program is distributed in the hope that it will be useful,
15a1452a3771c4eb85bd779790b040efdc36f4274eDavid Woodhouse * but WITHOUT ANY WARRANTY; without even the implied warranty of
16a1452a3771c4eb85bd779790b040efdc36f4274eDavid Woodhouse * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17a1452a3771c4eb85bd779790b040efdc36f4274eDavid Woodhouse * GNU General Public License for more details.
18a1452a3771c4eb85bd779790b040efdc36f4274eDavid Woodhouse *
19a1452a3771c4eb85bd779790b040efdc36f4274eDavid Woodhouse * You should have received a copy of the GNU General Public License
20a1452a3771c4eb85bd779790b040efdc36f4274eDavid Woodhouse * along with this program; if not, write to the Free Software
21a1452a3771c4eb85bd779790b040efdc36f4274eDavid Woodhouse * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
22a1452a3771c4eb85bd779790b040efdc36f4274eDavid Woodhouse *
231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/kernel.h>
2615fdc52f35b853e3fa550087987b5ee4ffbd199bThomas Gleixner#include <linux/module.h>
271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/slab.h>
2815fdc52f35b853e3fa550087987b5ee4ffbd199bThomas Gleixner#include <linux/sched.h>
2915fdc52f35b853e3fa550087987b5ee4ffbd199bThomas Gleixner#include <linux/types.h>
306e232cfce35a20a8702d9ac7709d35030c1b3271David Howells#include <linux/backing-dev.h>
3115fdc52f35b853e3fa550087987b5ee4ffbd199bThomas Gleixner
321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/mtd/mtd.h>
331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/mtd/concat.h>
341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
356c8b44abc86a3e23dd1a22c0ee187f06bd7c7f5dAndrew Morton#include <asm/div64.h>
366c8b44abc86a3e23dd1a22c0ee187f06bd7c7f5dAndrew Morton
371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * Our storage structure:
391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * Subdev points to an array of pointers to struct mtd_info objects
401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * which is allocated along with this structure
411da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstruct mtd_concat {
441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct mtd_info mtd;
451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int num_subdev;
461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct mtd_info **subdev;
471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds};
481da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
491da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
501da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * how to calculate the size required for the above structure,
511da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * including the pointer array subdev points to:
521da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#define SIZEOF_STRUCT_MTD_CONCAT(num_subdev)	\
541da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	((sizeof(struct mtd_concat) + (num_subdev) * sizeof(struct mtd_info *)))
551da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
561da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * Given a pointer to the MTD object in the mtd_concat structure,
581da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * we can retrieve the pointer to that structure with this macro.
591da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
601da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#define CONCAT(x)  ((struct mtd_concat *)(x))
611da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
6297894cda5773e59bd13e87b72077751099419a9fThomas Gleixner/*
631da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * MTD methods which look up the relevant subdevice, translate the
641da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * effective address and pass through to the subdevice.
651da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
661da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
671da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int
681da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsconcat_read(struct mtd_info *mtd, loff_t from, size_t len,
691da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	    size_t * retlen, u_char * buf)
701da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
711da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct mtd_concat *concat = CONCAT(mtd);
72f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner	int ret = 0, err;
731da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int i;
741da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
751da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	for (i = 0; i < concat->num_subdev; i++) {
761da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		struct mtd_info *subdev = concat->subdev[i];
771da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		size_t size, retsize;
781da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
791da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (from >= subdev->size) {
801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			/* Not destined for this subdev */
811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			size = 0;
821da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			from -= subdev->size;
831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			continue;
841da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (from + len > subdev->size)
861da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			/* First part goes into this subdev */
871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			size = subdev->size - from;
881da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		else
891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			/* Entire transaction goes into this subdev */
901da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			size = len;
911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
92329ad399a9b3adf52c90637b21ca029fcf7f8795Artem Bityutskiy		err = mtd_read(subdev, from, size, &retsize, buf);
931da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
949a1fcdfd4bee27c418424cac47abf7c049541297Thomas Gleixner		/* Save information about bitflips! */
95f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner		if (unlikely(err)) {
96d57f40544a41fdfe90fd863b6865138c5a82f1ccBrian Norris			if (mtd_is_eccerr(err)) {
97f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner				mtd->ecc_stats.failed++;
989a1fcdfd4bee27c418424cac47abf7c049541297Thomas Gleixner				ret = err;
99d57f40544a41fdfe90fd863b6865138c5a82f1ccBrian Norris			} else if (mtd_is_bitflip(err)) {
100f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner				mtd->ecc_stats.corrected++;
101f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner				/* Do not overwrite -EBADMSG !! */
102f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner				if (!ret)
103f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner					ret = err;
104f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner			} else
105f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner				return err;
1069a1fcdfd4bee27c418424cac47abf7c049541297Thomas Gleixner		}
1079a1fcdfd4bee27c418424cac47abf7c049541297Thomas Gleixner
1081da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		*retlen += retsize;
1091da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		len -= size;
1101da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (len == 0)
111f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner			return ret;
1121da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1131da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		buf += size;
1141da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		from = 0;
1151da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
116f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner	return -EINVAL;
1171da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int
1201da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsconcat_write(struct mtd_info *mtd, loff_t to, size_t len,
1211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	     size_t * retlen, const u_char * buf)
1221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
1231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct mtd_concat *concat = CONCAT(mtd);
1241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int err = -EINVAL;
1251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int i;
1261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	for (i = 0; i < concat->num_subdev; i++) {
1281da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		struct mtd_info *subdev = concat->subdev[i];
1291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		size_t size, retsize;
1301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (to >= subdev->size) {
1321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			size = 0;
1331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			to -= subdev->size;
1341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			continue;
1351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
1361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (to + len > subdev->size)
1371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			size = subdev->size - to;
1381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		else
1391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			size = len;
1401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
141664addc248d2fed68d013d26ff2fc796d7134259Artem Bityutskiy		err = mtd_write(subdev, to, size, &retsize, buf);
1421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (err)
1431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			break;
1441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		*retlen += retsize;
1461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		len -= size;
1471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (len == 0)
1481da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			break;
1491da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1501da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		err = -EINVAL;
1511da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		buf += size;
1521da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		to = 0;
1531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
1541da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return err;
1551da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
1561da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int
1589d8522df37f91621a70c5c0dbbf5bf2220b16798Thomas Gleixnerconcat_writev(struct mtd_info *mtd, const struct kvec *vecs,
1599d8522df37f91621a70c5c0dbbf5bf2220b16798Thomas Gleixner		unsigned long count, loff_t to, size_t * retlen)
160e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov{
161e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	struct mtd_concat *concat = CONCAT(mtd);
162e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	struct kvec *vecs_copy;
163e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	unsigned long entry_low, entry_high;
164e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	size_t total_len = 0;
165e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	int i;
166e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	int err = -EINVAL;
167e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
168e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	/* Calculate total length of data */
169e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	for (i = 0; i < count; i++)
170e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		total_len += vecs[i].iov_len;
171e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
172e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	/* Check alignment */
17328318776a80bc3261f9af91ef79e6e38bb9f5becJoern Engel	if (mtd->writesize > 1) {
1740bf9733d0d65ebb413d62204ad8e328e0a0b9407David Woodhouse		uint64_t __to = to;
17528318776a80bc3261f9af91ef79e6e38bb9f5becJoern Engel		if (do_div(__to, mtd->writesize) || (total_len % mtd->writesize))
176e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov			return -EINVAL;
1776c8b44abc86a3e23dd1a22c0ee187f06bd7c7f5dAndrew Morton	}
178e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
179e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	/* make a copy of vecs */
180d80f2666b5373f195deae57c9f33a5abb8053d37Julia Lawall	vecs_copy = kmemdup(vecs, sizeof(struct kvec) * count, GFP_KERNEL);
181e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	if (!vecs_copy)
182e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		return -ENOMEM;
183e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
184e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	entry_low = 0;
185e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	for (i = 0; i < concat->num_subdev; i++) {
186e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		struct mtd_info *subdev = concat->subdev[i];
187e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		size_t size, wsize, retsize, old_iov_len;
188e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
189e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		if (to >= subdev->size) {
190e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov			to -= subdev->size;
191e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov			continue;
192e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		}
193e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
19469423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunter		size = min_t(uint64_t, total_len, subdev->size - to);
195e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		wsize = size; /* store for future use */
196e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
197e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		entry_high = entry_low;
198e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		while (entry_high < count) {
199e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov			if (size <= vecs_copy[entry_high].iov_len)
200e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov				break;
201e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov			size -= vecs_copy[entry_high++].iov_len;
202e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		}
203e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
204e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		old_iov_len = vecs_copy[entry_high].iov_len;
205e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		vecs_copy[entry_high].iov_len = size;
206e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
207664addc248d2fed68d013d26ff2fc796d7134259Artem Bityutskiy		err = mtd_writev(subdev, &vecs_copy[entry_low],
208664addc248d2fed68d013d26ff2fc796d7134259Artem Bityutskiy				 entry_high - entry_low + 1, to, &retsize);
209e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
210e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		vecs_copy[entry_high].iov_len = old_iov_len - size;
211e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		vecs_copy[entry_high].iov_base += size;
212e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
213e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		entry_low = entry_high;
214e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
215e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		if (err)
216e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov			break;
217e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
218e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		*retlen += retsize;
219e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		total_len -= wsize;
220e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
221e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		if (total_len == 0)
222e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov			break;
223e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
224e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		err = -EINVAL;
225e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		to = 0;
226e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	}
227e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
228e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	kfree(vecs_copy);
229e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	return err;
230e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov}
231e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
232e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakovstatic int
2338593fbc68b0df1168995de76d1af38eb62fd6b62Thomas Gleixnerconcat_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
2341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
2351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct mtd_concat *concat = CONCAT(mtd);
2368593fbc68b0df1168995de76d1af38eb62fd6b62Thomas Gleixner	struct mtd_oob_ops devops = *ops;
237f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner	int i, err, ret = 0;
2381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2397014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool	ops->retlen = ops->oobretlen = 0;
2401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2411da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	for (i = 0; i < concat->num_subdev; i++) {
2421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		struct mtd_info *subdev = concat->subdev[i];
2431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (from >= subdev->size) {
2451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			from -= subdev->size;
2461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			continue;
2471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
2481da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2498593fbc68b0df1168995de76d1af38eb62fd6b62Thomas Gleixner		/* partial read ? */
2508593fbc68b0df1168995de76d1af38eb62fd6b62Thomas Gleixner		if (from + devops.len > subdev->size)
2518593fbc68b0df1168995de76d1af38eb62fd6b62Thomas Gleixner			devops.len = subdev->size - from;
2521da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
253fd2819bbc92fc98bed5d612e4acbe16b6326f6bfArtem Bityutskiy		err = mtd_read_oob(subdev, from, &devops);
2548593fbc68b0df1168995de76d1af38eb62fd6b62Thomas Gleixner		ops->retlen += devops.retlen;
2557014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool		ops->oobretlen += devops.oobretlen;
256f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner
257f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner		/* Save information about bitflips! */
258f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner		if (unlikely(err)) {
259d57f40544a41fdfe90fd863b6865138c5a82f1ccBrian Norris			if (mtd_is_eccerr(err)) {
260f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner				mtd->ecc_stats.failed++;
261f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner				ret = err;
262d57f40544a41fdfe90fd863b6865138c5a82f1ccBrian Norris			} else if (mtd_is_bitflip(err)) {
263f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner				mtd->ecc_stats.corrected++;
264f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner				/* Do not overwrite -EBADMSG !! */
265f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner				if (!ret)
266f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner					ret = err;
267f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner			} else
268f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner				return err;
269f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner		}
2701da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2717014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool		if (devops.datbuf) {
2727014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool			devops.len = ops->len - ops->retlen;
2737014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool			if (!devops.len)
2747014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool				return ret;
2758593fbc68b0df1168995de76d1af38eb62fd6b62Thomas Gleixner			devops.datbuf += devops.retlen;
2767014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool		}
2777014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool		if (devops.oobbuf) {
2787014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool			devops.ooblen = ops->ooblen - ops->oobretlen;
2797014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool			if (!devops.ooblen)
2807014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool				return ret;
2817014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool			devops.oobbuf += ops->oobretlen;
2827014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool		}
2831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2841da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		from = 0;
2851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
2868593fbc68b0df1168995de76d1af38eb62fd6b62Thomas Gleixner	return -EINVAL;
2871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
2881da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int
2908593fbc68b0df1168995de76d1af38eb62fd6b62Thomas Gleixnerconcat_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops)
2911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
2921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct mtd_concat *concat = CONCAT(mtd);
2938593fbc68b0df1168995de76d1af38eb62fd6b62Thomas Gleixner	struct mtd_oob_ops devops = *ops;
2948593fbc68b0df1168995de76d1af38eb62fd6b62Thomas Gleixner	int i, err;
2951da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
2961da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (!(mtd->flags & MTD_WRITEABLE))
2971da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return -EROFS;
2981da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
299431e1ecabddcd7cbba237182ddf431771f98bb4cFelix Radensky	ops->retlen = ops->oobretlen = 0;
3001da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3011da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	for (i = 0; i < concat->num_subdev; i++) {
3021da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		struct mtd_info *subdev = concat->subdev[i];
3031da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3041da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (to >= subdev->size) {
3051da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			to -= subdev->size;
3061da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			continue;
3071da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
3081da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3098593fbc68b0df1168995de76d1af38eb62fd6b62Thomas Gleixner		/* partial write ? */
3108593fbc68b0df1168995de76d1af38eb62fd6b62Thomas Gleixner		if (to + devops.len > subdev->size)
3118593fbc68b0df1168995de76d1af38eb62fd6b62Thomas Gleixner			devops.len = subdev->size - to;
3121da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
313a2cc5ba075f9bc837d0b4d4ec7328dcefc11859dArtem Bityutskiy		err = mtd_write_oob(subdev, to, &devops);
314431e1ecabddcd7cbba237182ddf431771f98bb4cFelix Radensky		ops->retlen += devops.oobretlen;
3151da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (err)
3168593fbc68b0df1168995de76d1af38eb62fd6b62Thomas Gleixner			return err;
3171da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3187014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool		if (devops.datbuf) {
3197014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool			devops.len = ops->len - ops->retlen;
3207014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool			if (!devops.len)
3217014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool				return 0;
3228593fbc68b0df1168995de76d1af38eb62fd6b62Thomas Gleixner			devops.datbuf += devops.retlen;
3237014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool		}
3247014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool		if (devops.oobbuf) {
3257014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool			devops.ooblen = ops->ooblen - ops->oobretlen;
3267014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool			if (!devops.ooblen)
3277014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool				return 0;
3287014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool			devops.oobbuf += devops.oobretlen;
3297014568bad55c20b7ee4f439d78c9e875912d51fVitaly Wool		}
3301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		to = 0;
3311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
3328593fbc68b0df1168995de76d1af38eb62fd6b62Thomas Gleixner	return -EINVAL;
3331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
3341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void concat_erase_callback(struct erase_info *instr)
3361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
3371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	wake_up((wait_queue_head_t *) instr->priv);
3381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
3391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int concat_dev_erase(struct mtd_info *mtd, struct erase_info *erase)
3411da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
3421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int err;
3431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	wait_queue_head_t waitq;
3441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	DECLARE_WAITQUEUE(wait, current);
3451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/*
3471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * This code was stol^H^H^H^Hinspired by mtdchar.c
3481da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 */
3491da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	init_waitqueue_head(&waitq);
3501da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3511da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	erase->mtd = mtd;
3521da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	erase->callback = concat_erase_callback;
3531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	erase->priv = (unsigned long) &waitq;
3541da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3551da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/*
3561da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * FIXME: Allow INTERRUPTIBLE. Which means
3571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * not having the wait_queue head on the stack.
3581da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 */
3597e1f0dc0551b99acb5e8fa161a7ac401994d57d8Artem Bityutskiy	err = mtd_erase(mtd, erase);
3601da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (!err) {
3611da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		set_current_state(TASK_UNINTERRUPTIBLE);
3621da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		add_wait_queue(&waitq, &wait);
3631da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (erase->state != MTD_ERASE_DONE
3641da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		    && erase->state != MTD_ERASE_FAILED)
3651da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			schedule();
3661da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		remove_wait_queue(&waitq, &wait);
3671da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		set_current_state(TASK_RUNNING);
3681da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3691da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		err = (erase->state == MTD_ERASE_FAILED) ? -EIO : 0;
3701da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
3711da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return err;
3721da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
3731da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3741da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int concat_erase(struct mtd_info *mtd, struct erase_info *instr)
3751da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
3761da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct mtd_concat *concat = CONCAT(mtd);
3771da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct mtd_info *subdev;
3781da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int i, err;
37969423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunter	uint64_t length, offset = 0;
3801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct erase_info *erase;
3811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3821da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/*
3831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * Check for proper erase block alignment of the to-be-erased area.
3841da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * It is easier to do this based on the super device's erase
3851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * region info rather than looking at each particular sub-device
3861da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * in turn.
3871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 */
3881da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (!concat->mtd.numeraseregions) {
3891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/* the easy case: device has uniform erase block size */
3901da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (instr->addr & (concat->mtd.erasesize - 1))
3911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			return -EINVAL;
3921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (instr->len & (concat->mtd.erasesize - 1))
3931da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			return -EINVAL;
3941da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	} else {
3951da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/* device has variable erase size */
3961da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		struct mtd_erase_region_info *erase_regions =
3971da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		    concat->mtd.eraseregions;
3981da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
3991da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/*
4001da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 * Find the erase region where the to-be-erased area begins:
4011da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 */
4021da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		for (i = 0; i < concat->mtd.numeraseregions &&
4031da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		     instr->addr >= erase_regions[i].offset; i++) ;
4041da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		--i;
4051da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
4061da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/*
4071da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 * Now erase_regions[i] is the region in which the
4081da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 * to-be-erased area begins. Verify that the starting
4091da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 * offset is aligned to this region's erase size:
4101da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 */
411ebf2e93036907fe2a7ddab942aa63d35f97f3b2bRoel Kluin		if (i < 0 || instr->addr & (erase_regions[i].erasesize - 1))
4121da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			return -EINVAL;
4131da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
4141da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/*
4151da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 * now find the erase region where the to-be-erased area ends:
4161da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 */
4171da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		for (; i < concat->mtd.numeraseregions &&
4181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		     (instr->addr + instr->len) >= erase_regions[i].offset;
4191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		     ++i) ;
4201da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		--i;
4211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/*
4221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 * check if the ending offset is aligned to this region's erase size
4231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 */
424ebf2e93036907fe2a7ddab942aa63d35f97f3b2bRoel Kluin		if (i < 0 || ((instr->addr + instr->len) &
425ebf2e93036907fe2a7ddab942aa63d35f97f3b2bRoel Kluin					(erase_regions[i].erasesize - 1)))
4261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			return -EINVAL;
4271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
4281da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
4291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* make a local copy of instr to avoid modifying the caller's struct */
4301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	erase = kmalloc(sizeof (struct erase_info), GFP_KERNEL);
4311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
4321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (!erase)
4331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return -ENOMEM;
4341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
4351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	*erase = *instr;
4361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	length = instr->len;
4371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
4381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/*
4391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * find the subdevice where the to-be-erased area begins, adjust
4401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * starting offset to be relative to the subdevice start
4411da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 */
4421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	for (i = 0; i < concat->num_subdev; i++) {
4431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		subdev = concat->subdev[i];
4441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (subdev->size <= erase->addr) {
4451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			erase->addr -= subdev->size;
4461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			offset += subdev->size;
4471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		} else {
4481da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			break;
4491da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
4501da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
4511da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
4521da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* must never happen since size limit has been verified above */
453373ebfbf17a8ecad304f65cb92c4d2d10adc0a19Eric Sesterhenn	BUG_ON(i >= concat->num_subdev);
4541da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
4551da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* now do the erase: */
4561da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	err = 0;
4571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	for (; length > 0; i++) {
4581da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/* loop for all subdevices affected by this request */
4591da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		subdev = concat->subdev[i];	/* get current subdevice */
4601da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
4611da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/* limit length to subdevice's size: */
4621da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (erase->addr + length > subdev->size)
4631da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			erase->len = subdev->size - erase->addr;
4641da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		else
4651da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			erase->len = length;
4661da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
4671da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		length -= erase->len;
4681da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if ((err = concat_dev_erase(subdev, erase))) {
4691da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			/* sanity check: should never happen since
4701da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			 * block alignment has been checked above */
471373ebfbf17a8ecad304f65cb92c4d2d10adc0a19Eric Sesterhenn			BUG_ON(err == -EINVAL);
472bb0eb217c980d50c45f3e793b4dcc70ab9ee820dAdrian Hunter			if (erase->fail_addr != MTD_FAIL_ADDR_UNKNOWN)
4731da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				instr->fail_addr = erase->fail_addr + offset;
4741da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			break;
4751da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
4761da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/*
4771da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 * erase->addr specifies the offset of the area to be
4781da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 * erased *within the current subdevice*. It can be
4791da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 * non-zero only the first time through this loop, i.e.
4801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 * for the first subdevice where blocks need to be erased.
4811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 * All the following erases must begin at the start of the
4821da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 * current subdevice, i.e. at offset zero.
4831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 */
4841da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		erase->addr = 0;
4851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		offset += subdev->size;
4861da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
4871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	instr->state = erase->state;
4881da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	kfree(erase);
4891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (err)
4901da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return err;
4911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
4921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (instr->callback)
4931da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		instr->callback(instr);
4941da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return 0;
4951da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
4961da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
49769423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunterstatic int concat_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
4981da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
4991da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct mtd_concat *concat = CONCAT(mtd);
5001da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int i, err = -EINVAL;
5011da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
5021da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	for (i = 0; i < concat->num_subdev; i++) {
5031da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		struct mtd_info *subdev = concat->subdev[i];
50469423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunter		uint64_t size;
5051da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
5061da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (ofs >= subdev->size) {
5071da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			size = 0;
5081da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			ofs -= subdev->size;
5091da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			continue;
5101da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
5111da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (ofs + len > subdev->size)
5121da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			size = subdev->size - ofs;
5131da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		else
5141da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			size = len;
5151da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
516381345652fca688aeaa967c231e5075cf68d05b6Artem Bityutskiy		err = mtd_lock(subdev, ofs, size);
517381345652fca688aeaa967c231e5075cf68d05b6Artem Bityutskiy		if (err)
518381345652fca688aeaa967c231e5075cf68d05b6Artem Bityutskiy			break;
5191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
5201da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		len -= size;
5211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (len == 0)
5221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			break;
5231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
5241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		err = -EINVAL;
5251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		ofs = 0;
5261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
5271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
5281da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return err;
5291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
5301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
53169423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunterstatic int concat_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
5321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
5331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct mtd_concat *concat = CONCAT(mtd);
5341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int i, err = 0;
5351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
5361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	for (i = 0; i < concat->num_subdev; i++) {
5371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		struct mtd_info *subdev = concat->subdev[i];
53869423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunter		uint64_t size;
5391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
5401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (ofs >= subdev->size) {
5411da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			size = 0;
5421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			ofs -= subdev->size;
5431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			continue;
5441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
5451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (ofs + len > subdev->size)
5461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			size = subdev->size - ofs;
5471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		else
5481da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			size = len;
5491da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
550381345652fca688aeaa967c231e5075cf68d05b6Artem Bityutskiy		err = mtd_unlock(subdev, ofs, size);
551381345652fca688aeaa967c231e5075cf68d05b6Artem Bityutskiy		if (err)
552381345652fca688aeaa967c231e5075cf68d05b6Artem Bityutskiy			break;
5531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
5541da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		len -= size;
5551da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (len == 0)
5561da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			break;
5571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
5581da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		err = -EINVAL;
5591da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		ofs = 0;
5601da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
5611da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
5621da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return err;
5631da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
5641da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
5651da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void concat_sync(struct mtd_info *mtd)
5661da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
5671da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct mtd_concat *concat = CONCAT(mtd);
5681da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int i;
5691da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
5701da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	for (i = 0; i < concat->num_subdev; i++) {
5711da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		struct mtd_info *subdev = concat->subdev[i];
57285f2f2a809d658c15b574df02ede92090f45a1f2Artem Bityutskiy		mtd_sync(subdev);
5731da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
5741da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
5751da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
5761da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic int concat_suspend(struct mtd_info *mtd)
5771da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
5781da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct mtd_concat *concat = CONCAT(mtd);
5791da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int i, rc = 0;
5801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
5811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	for (i = 0; i < concat->num_subdev; i++) {
5821da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		struct mtd_info *subdev = concat->subdev[i];
5833fe4bae88460869a8e553397cd9057a4ee7ca341Artem Bityutskiy		if ((rc = mtd_suspend(subdev)) < 0)
5841da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			return rc;
5851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
5861da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return rc;
5871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
5881da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
5891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstatic void concat_resume(struct mtd_info *mtd)
5901da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
5911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct mtd_concat *concat = CONCAT(mtd);
5921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int i;
5931da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
5941da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	for (i = 0; i < concat->num_subdev; i++) {
5951da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		struct mtd_info *subdev = concat->subdev[i];
596ead995f8d4da1e2f1ef40b0e5f4133fee38a3d3dArtem Bityutskiy		mtd_resume(subdev);
5971da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
5981da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
5991da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
600e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakovstatic int concat_block_isbad(struct mtd_info *mtd, loff_t ofs)
601e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov{
602e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	struct mtd_concat *concat = CONCAT(mtd);
603e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	int i, res = 0;
604e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
6058f461a730242c528ca221948edceca49266a3ffbArtem Bityutskiy	if (!mtd_can_have_bb(concat->subdev[0]))
606e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		return res;
607e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
608e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	for (i = 0; i < concat->num_subdev; i++) {
609e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		struct mtd_info *subdev = concat->subdev[i];
610e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
611e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		if (ofs >= subdev->size) {
612e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov			ofs -= subdev->size;
613e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov			continue;
614e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		}
615e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
6167086c19d07429d697057587caf1e5e0345442d16Artem Bityutskiy		res = mtd_block_isbad(subdev, ofs);
617e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		break;
618e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	}
619e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
620e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	return res;
621e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov}
622e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
623e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakovstatic int concat_block_markbad(struct mtd_info *mtd, loff_t ofs)
624e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov{
625e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	struct mtd_concat *concat = CONCAT(mtd);
626e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	int i, err = -EINVAL;
627e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
628e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	for (i = 0; i < concat->num_subdev; i++) {
629e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		struct mtd_info *subdev = concat->subdev[i];
630e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
631e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		if (ofs >= subdev->size) {
632e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov			ofs -= subdev->size;
633e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov			continue;
634e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		}
635e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
6365942ddbc500d1c9b75e571b656be97f65b26adfeArtem Bityutskiy		err = mtd_block_markbad(subdev, ofs);
637f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner		if (!err)
638f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner			mtd->ecc_stats.badblocks++;
639e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov		break;
640e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	}
641e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
642e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov	return err;
643e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov}
644e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
6451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
6466e232cfce35a20a8702d9ac7709d35030c1b3271David Howells * try to support NOMMU mmaps on concatenated devices
6476e232cfce35a20a8702d9ac7709d35030c1b3271David Howells * - we don't support subdev spanning as we can't guarantee it'll work
6486e232cfce35a20a8702d9ac7709d35030c1b3271David Howells */
6496e232cfce35a20a8702d9ac7709d35030c1b3271David Howellsstatic unsigned long concat_get_unmapped_area(struct mtd_info *mtd,
6506e232cfce35a20a8702d9ac7709d35030c1b3271David Howells					      unsigned long len,
6516e232cfce35a20a8702d9ac7709d35030c1b3271David Howells					      unsigned long offset,
6526e232cfce35a20a8702d9ac7709d35030c1b3271David Howells					      unsigned long flags)
6536e232cfce35a20a8702d9ac7709d35030c1b3271David Howells{
6546e232cfce35a20a8702d9ac7709d35030c1b3271David Howells	struct mtd_concat *concat = CONCAT(mtd);
6556e232cfce35a20a8702d9ac7709d35030c1b3271David Howells	int i;
6566e232cfce35a20a8702d9ac7709d35030c1b3271David Howells
6576e232cfce35a20a8702d9ac7709d35030c1b3271David Howells	for (i = 0; i < concat->num_subdev; i++) {
6586e232cfce35a20a8702d9ac7709d35030c1b3271David Howells		struct mtd_info *subdev = concat->subdev[i];
6596e232cfce35a20a8702d9ac7709d35030c1b3271David Howells
6606e232cfce35a20a8702d9ac7709d35030c1b3271David Howells		if (offset >= subdev->size) {
6616e232cfce35a20a8702d9ac7709d35030c1b3271David Howells			offset -= subdev->size;
6626e232cfce35a20a8702d9ac7709d35030c1b3271David Howells			continue;
6636e232cfce35a20a8702d9ac7709d35030c1b3271David Howells		}
6646e232cfce35a20a8702d9ac7709d35030c1b3271David Howells
665cd621274b0ec747db8dedbf857624c067f481976Artem Bityutskiy		return mtd_get_unmapped_area(subdev, len, offset, flags);
6666e232cfce35a20a8702d9ac7709d35030c1b3271David Howells	}
6676e232cfce35a20a8702d9ac7709d35030c1b3271David Howells
6686e232cfce35a20a8702d9ac7709d35030c1b3271David Howells	return (unsigned long) -ENOSYS;
6696e232cfce35a20a8702d9ac7709d35030c1b3271David Howells}
6706e232cfce35a20a8702d9ac7709d35030c1b3271David Howells
6716e232cfce35a20a8702d9ac7709d35030c1b3271David Howells/*
6721da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * This function constructs a virtual MTD device by concatenating
6731da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * num_devs MTD devices. A pointer to the new device object is
6741da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * stored to *new_dev upon success. This function does _not_
6751da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * register any devices: this is the caller's responsibility.
6761da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
6771da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsstruct mtd_info *mtd_concat_create(struct mtd_info *subdev[],	/* subdevices to concatenate */
6781da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				   int num_devs,	/* number of subdevices      */
679160bbab3000dafccbe43688e48208cecf4deb879Kay Sievers				   const char *name)
6801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{				/* name for the new device   */
6811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int i;
6821da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	size_t size;
6831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct mtd_concat *concat;
68426cdb67c74aedc22367e6d0271f7f955220cca65David Woodhouse	uint32_t max_erasesize, curr_erasesize;
6851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int num_erase_region;
686771df61949cf2d6ae9ff07e209c80693cdbc9302Holger Brunck	int max_writebufsize = 0;
6871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
6881da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	printk(KERN_NOTICE "Concatenating MTD devices:\n");
6891da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	for (i = 0; i < num_devs; i++)
6901da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		printk(KERN_NOTICE "(%d): \"%s\"\n", i, subdev[i]->name);
6911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	printk(KERN_NOTICE "into device \"%s\"\n", name);
6921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
6931da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/* allocate the device structure */
6941da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	size = SIZEOF_STRUCT_MTD_CONCAT(num_devs);
69595b93a0cd46682c6d9e8eea803fda510cb6b863aBurman Yan	concat = kzalloc(size, GFP_KERNEL);
6961da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (!concat) {
6971da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		printk
6981da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		    ("memory allocation error while creating concatenated device \"%s\"\n",
6991da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		     name);
7001da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return NULL;
7011da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
7021da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	concat->subdev = (struct mtd_info **) (concat + 1);
7031da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
7041da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/*
7051da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * Set up the new "super" device's MTD object structure, check for
70692394b5c2be774425f255b5c7afbd8b19978fe12Brian Norris	 * incompatibilities between the subdevices.
7071da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 */
7081da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	concat->mtd.type = subdev[0]->type;
7091da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	concat->mtd.flags = subdev[0]->flags;
7101da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	concat->mtd.size = subdev[0]->size;
7111da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	concat->mtd.erasesize = subdev[0]->erasesize;
71228318776a80bc3261f9af91ef79e6e38bb9f5becJoern Engel	concat->mtd.writesize = subdev[0]->writesize;
713771df61949cf2d6ae9ff07e209c80693cdbc9302Holger Brunck
714771df61949cf2d6ae9ff07e209c80693cdbc9302Holger Brunck	for (i = 0; i < num_devs; i++)
715771df61949cf2d6ae9ff07e209c80693cdbc9302Holger Brunck		if (max_writebufsize < subdev[i]->writebufsize)
716771df61949cf2d6ae9ff07e209c80693cdbc9302Holger Brunck			max_writebufsize = subdev[i]->writebufsize;
717771df61949cf2d6ae9ff07e209c80693cdbc9302Holger Brunck	concat->mtd.writebufsize = max_writebufsize;
718771df61949cf2d6ae9ff07e209c80693cdbc9302Holger Brunck
719a2e1b833d9e0231d67e722b7e2f4d79daf919bafChris Paulson-Ellis	concat->mtd.subpage_sft = subdev[0]->subpage_sft;
7201da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	concat->mtd.oobsize = subdev[0]->oobsize;
7211f92267c51a514f35ad5b0fd46cb099c0980b679Vitaly Wool	concat->mtd.oobavail = subdev[0]->oobavail;
7223c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy	if (subdev[0]->_writev)
7233c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy		concat->mtd._writev = concat_writev;
7243c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy	if (subdev[0]->_read_oob)
7253c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy		concat->mtd._read_oob = concat_read_oob;
7263c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy	if (subdev[0]->_write_oob)
7273c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy		concat->mtd._write_oob = concat_write_oob;
7283c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy	if (subdev[0]->_block_isbad)
7293c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy		concat->mtd._block_isbad = concat_block_isbad;
7303c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy	if (subdev[0]->_block_markbad)
7313c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy		concat->mtd._block_markbad = concat_block_markbad;
7321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
733f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner	concat->mtd.ecc_stats.badblocks = subdev[0]->ecc_stats.badblocks;
734f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner
7356e232cfce35a20a8702d9ac7709d35030c1b3271David Howells	concat->mtd.backing_dev_info = subdev[0]->backing_dev_info;
7366e232cfce35a20a8702d9ac7709d35030c1b3271David Howells
7371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	concat->subdev[0] = subdev[0];
7381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
7391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	for (i = 1; i < num_devs; i++) {
7401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (concat->mtd.type != subdev[i]->type) {
7411da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			kfree(concat);
7421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			printk("Incompatible device type on \"%s\"\n",
7431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			       subdev[i]->name);
7441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			return NULL;
7451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
7461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (concat->mtd.flags != subdev[i]->flags) {
7471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			/*
7481da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			 * Expect all flags except MTD_WRITEABLE to be
7491da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			 * equal on all subdevices.
7501da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			 */
7511da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			if ((concat->mtd.flags ^ subdev[i]->
7521da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			     flags) & ~MTD_WRITEABLE) {
7531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				kfree(concat);
7541da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				printk("Incompatible device flags on \"%s\"\n",
7551da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				       subdev[i]->name);
7561da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				return NULL;
7571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			} else
7581da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				/* if writeable attribute differs,
7591da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				   make super device writeable */
7601da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				concat->mtd.flags |=
7611da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				    subdev[i]->flags & MTD_WRITEABLE;
7621da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
7636e232cfce35a20a8702d9ac7709d35030c1b3271David Howells
7646e232cfce35a20a8702d9ac7709d35030c1b3271David Howells		/* only permit direct mapping if the BDIs are all the same
7656e232cfce35a20a8702d9ac7709d35030c1b3271David Howells		 * - copy-mapping is still permitted
7666e232cfce35a20a8702d9ac7709d35030c1b3271David Howells		 */
7676e232cfce35a20a8702d9ac7709d35030c1b3271David Howells		if (concat->mtd.backing_dev_info !=
7686e232cfce35a20a8702d9ac7709d35030c1b3271David Howells		    subdev[i]->backing_dev_info)
7696e232cfce35a20a8702d9ac7709d35030c1b3271David Howells			concat->mtd.backing_dev_info =
7706e232cfce35a20a8702d9ac7709d35030c1b3271David Howells				&default_backing_dev_info;
7716e232cfce35a20a8702d9ac7709d35030c1b3271David Howells
7721da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		concat->mtd.size += subdev[i]->size;
773f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner		concat->mtd.ecc_stats.badblocks +=
774f1a28c02843efcfcc41982149880bac3ac180234Thomas Gleixner			subdev[i]->ecc_stats.badblocks;
77528318776a80bc3261f9af91ef79e6e38bb9f5becJoern Engel		if (concat->mtd.writesize   !=  subdev[i]->writesize ||
77629072b96078ffde36f03d51e6b5d0cff1ba8c7dfThomas Gleixner		    concat->mtd.subpage_sft != subdev[i]->subpage_sft ||
7771da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		    concat->mtd.oobsize    !=  subdev[i]->oobsize ||
7783c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy		    !concat->mtd._read_oob  != !subdev[i]->_read_oob ||
7793c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy		    !concat->mtd._write_oob != !subdev[i]->_write_oob) {
7801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			kfree(concat);
7811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			printk("Incompatible OOB or ECC data on \"%s\"\n",
7821da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			       subdev[i]->name);
7831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			return NULL;
7841da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
7851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		concat->subdev[i] = subdev[i];
7861da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
7871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
7881da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
7895bd34c091a044d130601370c370f84b1c59f1627Thomas Gleixner	concat->mtd.ecclayout = subdev[0]->ecclayout;
790e8d32937d9f2022c31871ef357a4883f78da1b7fAlexander Belyakov
7911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	concat->num_subdev = num_devs;
7921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	concat->mtd.name = name;
7931da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
7943c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy	concat->mtd._erase = concat_erase;
7953c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy	concat->mtd._read = concat_read;
7963c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy	concat->mtd._write = concat_write;
7973c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy	concat->mtd._sync = concat_sync;
7983c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy	concat->mtd._lock = concat_lock;
7993c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy	concat->mtd._unlock = concat_unlock;
8003c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy	concat->mtd._suspend = concat_suspend;
8013c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy	concat->mtd._resume = concat_resume;
8023c3c10bba1e4ccb75b41442e45c1a072f6cded19Artem Bityutskiy	concat->mtd._get_unmapped_area = concat_get_unmapped_area;
8031da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
8041da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	/*
8051da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * Combine the erase block size info of the subdevices:
8061da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 *
8071da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * first, walk the map of the new device and see how
8081da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 * many changes in erase size we have
8091da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	 */
8101da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	max_erasesize = curr_erasesize = subdev[0]->erasesize;
8111da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	num_erase_region = 1;
8121da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	for (i = 0; i < num_devs; i++) {
8131da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (subdev[i]->numeraseregions == 0) {
8141da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			/* current subdevice has uniform erase size */
8151da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			if (subdev[i]->erasesize != curr_erasesize) {
8161da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				/* if it differs from the last subdevice's erase size, count it */
8171da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				++num_erase_region;
8181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				curr_erasesize = subdev[i]->erasesize;
8191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				if (curr_erasesize > max_erasesize)
8201da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					max_erasesize = curr_erasesize;
8211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			}
8221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		} else {
8231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			/* current subdevice has variable erase size */
8241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			int j;
8251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			for (j = 0; j < subdev[i]->numeraseregions; j++) {
8261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
8271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				/* walk the list of erase regions, count any changes */
8281da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				if (subdev[i]->eraseregions[j].erasesize !=
8291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				    curr_erasesize) {
8301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					++num_erase_region;
8311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					curr_erasesize =
8321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					    subdev[i]->eraseregions[j].
8331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					    erasesize;
8341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					if (curr_erasesize > max_erasesize)
8351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds						max_erasesize = curr_erasesize;
8361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				}
8371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			}
8381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
8391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
8401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
8411da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (num_erase_region == 1) {
8421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/*
8431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 * All subdevices have the same uniform erase size.
8441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 * This is easy:
8451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 */
8461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		concat->mtd.erasesize = curr_erasesize;
8471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		concat->mtd.numeraseregions = 0;
8481da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	} else {
84969423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunter		uint64_t tmp64;
85069423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunter
8511da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/*
8521da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 * erase block size varies across the subdevices: allocate
8531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 * space to store the data describing the variable erase regions
8541da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 */
8551da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		struct mtd_erase_region_info *erase_region_p;
85669423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunter		uint64_t begin, position;
8571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
8581da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		concat->mtd.erasesize = max_erasesize;
8591da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		concat->mtd.numeraseregions = num_erase_region;
8601da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		concat->mtd.eraseregions = erase_region_p =
8611da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		    kmalloc(num_erase_region *
8621da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			    sizeof (struct mtd_erase_region_info), GFP_KERNEL);
8631da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		if (!erase_region_p) {
8641da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			kfree(concat);
8651da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			printk
8661da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			    ("memory allocation error while creating erase region list"
8671da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			     " for device \"%s\"\n", name);
8681da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			return NULL;
8691da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
8701da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
8711da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/*
8721da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 * walk the map of the new device once more and fill in
8731da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 * in erase region info:
8741da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		 */
8751da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		curr_erasesize = subdev[0]->erasesize;
8761da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		begin = position = 0;
8771da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		for (i = 0; i < num_devs; i++) {
8781da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			if (subdev[i]->numeraseregions == 0) {
8791da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				/* current subdevice has uniform erase size */
8801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				if (subdev[i]->erasesize != curr_erasesize) {
8811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					/*
8821da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					 *  fill in an mtd_erase_region_info structure for the area
8831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					 *  we have walked so far:
8841da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					 */
8851da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					erase_region_p->offset = begin;
8861da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					erase_region_p->erasesize =
8871da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					    curr_erasesize;
88869423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunter					tmp64 = position - begin;
88969423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunter					do_div(tmp64, curr_erasesize);
89069423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunter					erase_region_p->numblocks = tmp64;
8911da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					begin = position;
8921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
8931da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					curr_erasesize = subdev[i]->erasesize;
8941da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					++erase_region_p;
8951da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				}
8961da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				position += subdev[i]->size;
8971da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			} else {
8981da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				/* current subdevice has variable erase size */
8991da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				int j;
9001da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				for (j = 0; j < subdev[i]->numeraseregions; j++) {
9011da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					/* walk the list of erase regions, count any changes */
9021da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					if (subdev[i]->eraseregions[j].
9031da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					    erasesize != curr_erasesize) {
9041da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds						erase_region_p->offset = begin;
9051da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds						erase_region_p->erasesize =
9061da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds						    curr_erasesize;
90769423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunter						tmp64 = position - begin;
90869423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunter						do_div(tmp64, curr_erasesize);
90969423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunter						erase_region_p->numblocks = tmp64;
9101da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds						begin = position;
9111da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
9121da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds						curr_erasesize =
9131da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds						    subdev[i]->eraseregions[j].
9141da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds						    erasesize;
9151da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds						++erase_region_p;
9161da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					}
9171da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					position +=
9181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds					    subdev[i]->eraseregions[j].
91969423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunter					    numblocks * (uint64_t)curr_erasesize;
9201da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				}
9211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			}
9221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
9231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		/* Now write the final entry */
9241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		erase_region_p->offset = begin;
9251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		erase_region_p->erasesize = curr_erasesize;
92669423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunter		tmp64 = position - begin;
92769423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunter		do_div(tmp64, curr_erasesize);
92869423d99fc182a81f3c5db3eb5c140acc6fc64beAdrian Hunter		erase_region_p->numblocks = tmp64;
9291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
9301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
9311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return &concat->mtd;
9321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
9331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
93497894cda5773e59bd13e87b72077751099419a9fThomas Gleixner/*
9351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * This function destroys an MTD object obtained from concat_mtd_devs()
9361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
9371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
9381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsvoid mtd_concat_destroy(struct mtd_info *mtd)
9391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
9401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct mtd_concat *concat = CONCAT(mtd);
9411da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (concat->mtd.numeraseregions)
9421da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		kfree(concat->mtd.eraseregions);
9431da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	kfree(concat);
9441da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
9451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
9461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsEXPORT_SYMBOL(mtd_concat_create);
9471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsEXPORT_SYMBOL(mtd_concat_destroy);
9481da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
9491da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_LICENSE("GPL");
9501da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_AUTHOR("Robert Kaiser <rkaiser@sysgo.de>");
9511da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus TorvaldsMODULE_DESCRIPTION("Generic support for concatenating of MTD devices");
952