xref: /openbmc/linux/drivers/mtd/mtdchar.c (revision aab616e31d1c7ec3726f7f5cbdaaec98759ebe93)
11da177e4SLinus Torvalds /*
2a1452a37SDavid Woodhouse  * Copyright © 1999-2010 David Woodhouse <dwmw2@infradead.org>
3a1452a37SDavid Woodhouse  *
4a1452a37SDavid Woodhouse  * This program is free software; you can redistribute it and/or modify
5a1452a37SDavid Woodhouse  * it under the terms of the GNU General Public License as published by
6a1452a37SDavid Woodhouse  * the Free Software Foundation; either version 2 of the License, or
7a1452a37SDavid Woodhouse  * (at your option) any later version.
8a1452a37SDavid Woodhouse  *
9a1452a37SDavid Woodhouse  * This program is distributed in the hope that it will be useful,
10a1452a37SDavid Woodhouse  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11a1452a37SDavid Woodhouse  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12a1452a37SDavid Woodhouse  * GNU General Public License for more details.
13a1452a37SDavid Woodhouse  *
14a1452a37SDavid Woodhouse  * You should have received a copy of the GNU General Public License
15a1452a37SDavid Woodhouse  * along with this program; if not, write to the Free Software
16a1452a37SDavid Woodhouse  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
171da177e4SLinus Torvalds  *
181da177e4SLinus Torvalds  */
191da177e4SLinus Torvalds 
2015fdc52fSThomas Gleixner #include <linux/device.h>
2115fdc52fSThomas Gleixner #include <linux/fs.h>
220c1eafdbSAndrew Morton #include <linux/mm.h>
239c74034fSArtem Bityutskiy #include <linux/err.h>
2415fdc52fSThomas Gleixner #include <linux/init.h>
251da177e4SLinus Torvalds #include <linux/kernel.h>
261da177e4SLinus Torvalds #include <linux/module.h>
2715fdc52fSThomas Gleixner #include <linux/slab.h>
2815fdc52fSThomas Gleixner #include <linux/sched.h>
295aa82940SArnd Bergmann #include <linux/mutex.h>
30402d3265SDavid Howells #include <linux/backing-dev.h>
3197718540SKevin Cernekee #include <linux/compat.h>
32cd874237SKirill A. Shutemov #include <linux/mount.h>
33d0f7959eSRoman Tereshonkov #include <linux/blkpg.h>
34b502bd11SMuthu Kumar #include <linux/magic.h>
35f83c3838SEzequiel Garcia #include <linux/major.h>
361da177e4SLinus Torvalds #include <linux/mtd/mtd.h>
37d0f7959eSRoman Tereshonkov #include <linux/mtd/partitions.h>
38dd02b67dSAnatolij Gustschin #include <linux/mtd/map.h>
391da177e4SLinus Torvalds 
4015fdc52fSThomas Gleixner #include <asm/uaccess.h>
419bc7b387STodd Poynor 
42660685d9SArtem Bityutskiy #include "mtdcore.h"
43660685d9SArtem Bityutskiy 
445aa82940SArnd Bergmann static DEFINE_MUTEX(mtd_mutex);
451da177e4SLinus Torvalds 
46045e9a5dSNicolas Pitre /*
47f1a28c02SThomas Gleixner  * Data structure to hold the pointer to the mtd device as well
4892394b5cSBrian Norris  * as mode information of various use cases.
49045e9a5dSNicolas Pitre  */
50f1a28c02SThomas Gleixner struct mtd_file_info {
51f1a28c02SThomas Gleixner 	struct mtd_info *mtd;
52f1a28c02SThomas Gleixner 	enum mtd_file_modes mode;
53f1a28c02SThomas Gleixner };
5431f4233bSNicolas Pitre 
55969e57adSArtem Bityutskiy static loff_t mtdchar_lseek(struct file *file, loff_t offset, int orig)
561da177e4SLinus Torvalds {
57f1a28c02SThomas Gleixner 	struct mtd_file_info *mfi = file->private_data;
58b959957fSAl Viro 	return fixed_size_llseek(file, offset, orig, mfi->mtd->size);
591da177e4SLinus Torvalds }
601da177e4SLinus Torvalds 
61969e57adSArtem Bityutskiy static int mtdchar_open(struct inode *inode, struct file *file)
621da177e4SLinus Torvalds {
631da177e4SLinus Torvalds 	int minor = iminor(inode);
641da177e4SLinus Torvalds 	int devnum = minor >> 1;
656071239eSJonathan Corbet 	int ret = 0;
661da177e4SLinus Torvalds 	struct mtd_info *mtd;
67f1a28c02SThomas Gleixner 	struct mtd_file_info *mfi;
681da177e4SLinus Torvalds 
69289c0522SBrian Norris 	pr_debug("MTD_open\n");
701da177e4SLinus Torvalds 
711da177e4SLinus Torvalds 	/* You can't open the RO devices RW */
72aeb5d727SAl Viro 	if ((file->f_mode & FMODE_WRITE) && (minor & 1))
731da177e4SLinus Torvalds 		return -EACCES;
741da177e4SLinus Torvalds 
755aa82940SArnd Bergmann 	mutex_lock(&mtd_mutex);
761da177e4SLinus Torvalds 	mtd = get_mtd_device(NULL, devnum);
771da177e4SLinus Torvalds 
786071239eSJonathan Corbet 	if (IS_ERR(mtd)) {
796071239eSJonathan Corbet 		ret = PTR_ERR(mtd);
806071239eSJonathan Corbet 		goto out;
816071239eSJonathan Corbet 	}
821da177e4SLinus Torvalds 
83402d3265SDavid Howells 	if (mtd->type == MTD_ABSENT) {
846071239eSJonathan Corbet 		ret = -ENODEV;
85c65390f4SAl Viro 		goto out1;
861da177e4SLinus Torvalds 	}
871da177e4SLinus Torvalds 
881da177e4SLinus Torvalds 	/* You can't open it RW if it's not a writeable device */
89aeb5d727SAl Viro 	if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) {
906071239eSJonathan Corbet 		ret = -EACCES;
91b4caecd4SChristoph Hellwig 		goto out1;
921da177e4SLinus Torvalds 	}
931da177e4SLinus Torvalds 
94f1a28c02SThomas Gleixner 	mfi = kzalloc(sizeof(*mfi), GFP_KERNEL);
95f1a28c02SThomas Gleixner 	if (!mfi) {
966071239eSJonathan Corbet 		ret = -ENOMEM;
97b4caecd4SChristoph Hellwig 		goto out1;
98f1a28c02SThomas Gleixner 	}
99f1a28c02SThomas Gleixner 	mfi->mtd = mtd;
100f1a28c02SThomas Gleixner 	file->private_data = mfi;
101c65390f4SAl Viro 	mutex_unlock(&mtd_mutex);
102c65390f4SAl Viro 	return 0;
103f1a28c02SThomas Gleixner 
104c65390f4SAl Viro out1:
105c65390f4SAl Viro 	put_mtd_device(mtd);
1066071239eSJonathan Corbet out:
1075aa82940SArnd Bergmann 	mutex_unlock(&mtd_mutex);
1086071239eSJonathan Corbet 	return ret;
109969e57adSArtem Bityutskiy } /* mtdchar_open */
1101da177e4SLinus Torvalds 
1111da177e4SLinus Torvalds /*====================================================================*/
1121da177e4SLinus Torvalds 
113969e57adSArtem Bityutskiy static int mtdchar_close(struct inode *inode, struct file *file)
1141da177e4SLinus Torvalds {
115f1a28c02SThomas Gleixner 	struct mtd_file_info *mfi = file->private_data;
116f1a28c02SThomas Gleixner 	struct mtd_info *mtd = mfi->mtd;
1171da177e4SLinus Torvalds 
118289c0522SBrian Norris 	pr_debug("MTD_close\n");
1191da177e4SLinus Torvalds 
1207eafaed5SJoakim Tjernlund 	/* Only sync if opened RW */
121327cf292SArtem Bityutskiy 	if ((file->f_mode & FMODE_WRITE))
12285f2f2a8SArtem Bityutskiy 		mtd_sync(mtd);
1231da177e4SLinus Torvalds 
1241da177e4SLinus Torvalds 	put_mtd_device(mtd);
125f1a28c02SThomas Gleixner 	file->private_data = NULL;
126f1a28c02SThomas Gleixner 	kfree(mfi);
1271da177e4SLinus Torvalds 
1281da177e4SLinus Torvalds 	return 0;
129969e57adSArtem Bityutskiy } /* mtdchar_close */
1301da177e4SLinus Torvalds 
1313e45cf5eSGrant Erickson /* Back in June 2001, dwmw2 wrote:
1323e45cf5eSGrant Erickson  *
1333e45cf5eSGrant Erickson  *   FIXME: This _really_ needs to die. In 2.5, we should lock the
1343e45cf5eSGrant Erickson  *   userspace buffer down and use it directly with readv/writev.
1353e45cf5eSGrant Erickson  *
1363e45cf5eSGrant Erickson  * The implementation below, using mtd_kmalloc_up_to, mitigates
1373e45cf5eSGrant Erickson  * allocation failures when the system is under low-memory situations
1383e45cf5eSGrant Erickson  * or if memory is highly fragmented at the cost of reducing the
1393e45cf5eSGrant Erickson  * performance of the requested transfer due to a smaller buffer size.
1403e45cf5eSGrant Erickson  *
1413e45cf5eSGrant Erickson  * A more complex but more memory-efficient implementation based on
1423e45cf5eSGrant Erickson  * get_user_pages and iovecs to cover extents of those pages is a
1433e45cf5eSGrant Erickson  * longer-term goal, as intimated by dwmw2 above. However, for the
1443e45cf5eSGrant Erickson  * write case, this requires yet more complex head and tail transfer
1453e45cf5eSGrant Erickson  * handling when those head and tail offsets and sizes are such that
1463e45cf5eSGrant Erickson  * alignment requirements are not met in the NAND subdriver.
1471da177e4SLinus Torvalds  */
1481da177e4SLinus Torvalds 
149969e57adSArtem Bityutskiy static ssize_t mtdchar_read(struct file *file, char __user *buf, size_t count,
150969e57adSArtem Bityutskiy 			loff_t *ppos)
1511da177e4SLinus Torvalds {
152f1a28c02SThomas Gleixner 	struct mtd_file_info *mfi = file->private_data;
153f1a28c02SThomas Gleixner 	struct mtd_info *mtd = mfi->mtd;
15430fa9848SArtem Bityutskiy 	size_t retlen;
1551da177e4SLinus Torvalds 	size_t total_retlen=0;
1561da177e4SLinus Torvalds 	int ret=0;
1571da177e4SLinus Torvalds 	int len;
1583e45cf5eSGrant Erickson 	size_t size = count;
1591da177e4SLinus Torvalds 	char *kbuf;
1601da177e4SLinus Torvalds 
161289c0522SBrian Norris 	pr_debug("MTD_read\n");
1621da177e4SLinus Torvalds 
1631da177e4SLinus Torvalds 	if (*ppos + count > mtd->size)
1641da177e4SLinus Torvalds 		count = mtd->size - *ppos;
1651da177e4SLinus Torvalds 
1661da177e4SLinus Torvalds 	if (!count)
1671da177e4SLinus Torvalds 		return 0;
1681da177e4SLinus Torvalds 
1693e45cf5eSGrant Erickson 	kbuf = mtd_kmalloc_up_to(mtd, &size);
170b802c074SThago Galesi 	if (!kbuf)
171b802c074SThago Galesi 		return -ENOMEM;
172b802c074SThago Galesi 
1731da177e4SLinus Torvalds 	while (count) {
1743e45cf5eSGrant Erickson 		len = min_t(size_t, count, size);
1751da177e4SLinus Torvalds 
176f1a28c02SThomas Gleixner 		switch (mfi->mode) {
177beb133fcSBrian Norris 		case MTD_FILE_MODE_OTP_FACTORY:
178d264f72aSArtem Bityutskiy 			ret = mtd_read_fact_prot_reg(mtd, *ppos, len,
179d264f72aSArtem Bityutskiy 						     &retlen, kbuf);
18031f4233bSNicolas Pitre 			break;
181beb133fcSBrian Norris 		case MTD_FILE_MODE_OTP_USER:
1824ea1cabbSArtem Bityutskiy 			ret = mtd_read_user_prot_reg(mtd, *ppos, len,
1834ea1cabbSArtem Bityutskiy 						     &retlen, kbuf);
18431f4233bSNicolas Pitre 			break;
185beb133fcSBrian Norris 		case MTD_FILE_MODE_RAW:
186f1a28c02SThomas Gleixner 		{
187f1a28c02SThomas Gleixner 			struct mtd_oob_ops ops;
188f1a28c02SThomas Gleixner 
1890612b9ddSBrian Norris 			ops.mode = MTD_OPS_RAW;
190f1a28c02SThomas Gleixner 			ops.datbuf = kbuf;
191f1a28c02SThomas Gleixner 			ops.oobbuf = NULL;
192f1a28c02SThomas Gleixner 			ops.len = len;
193f1a28c02SThomas Gleixner 
194fd2819bbSArtem Bityutskiy 			ret = mtd_read_oob(mtd, *ppos, &ops);
195f1a28c02SThomas Gleixner 			retlen = ops.retlen;
196f1a28c02SThomas Gleixner 			break;
197f1a28c02SThomas Gleixner 		}
19831f4233bSNicolas Pitre 		default:
199329ad399SArtem Bityutskiy 			ret = mtd_read(mtd, *ppos, len, &retlen, kbuf);
20031f4233bSNicolas Pitre 		}
2017854d3f7SBrian Norris 		/* Nand returns -EBADMSG on ECC errors, but it returns
2021da177e4SLinus Torvalds 		 * the data. For our userspace tools it is important
2037854d3f7SBrian Norris 		 * to dump areas with ECC errors!
2049a1fcdfdSThomas Gleixner 		 * For kernel internal usage it also might return -EUCLEAN
20525985edcSLucas De Marchi 		 * to signal the caller that a bitflip has occurred and has
2069a1fcdfdSThomas Gleixner 		 * been corrected by the ECC algorithm.
2071da177e4SLinus Torvalds 		 * Userspace software which accesses NAND this way
2081da177e4SLinus Torvalds 		 * must be aware of the fact that it deals with NAND
2091da177e4SLinus Torvalds 		 */
210d57f4054SBrian Norris 		if (!ret || mtd_is_bitflip_or_eccerr(ret)) {
2111da177e4SLinus Torvalds 			*ppos += retlen;
2121da177e4SLinus Torvalds 			if (copy_to_user(buf, kbuf, retlen)) {
2131da177e4SLinus Torvalds 				kfree(kbuf);
2141da177e4SLinus Torvalds 				return -EFAULT;
2151da177e4SLinus Torvalds 			}
2161da177e4SLinus Torvalds 			else
2171da177e4SLinus Torvalds 				total_retlen += retlen;
2181da177e4SLinus Torvalds 
2191da177e4SLinus Torvalds 			count -= retlen;
2201da177e4SLinus Torvalds 			buf += retlen;
22131f4233bSNicolas Pitre 			if (retlen == 0)
22231f4233bSNicolas Pitre 				count = 0;
2231da177e4SLinus Torvalds 		}
2241da177e4SLinus Torvalds 		else {
2251da177e4SLinus Torvalds 			kfree(kbuf);
2261da177e4SLinus Torvalds 			return ret;
2271da177e4SLinus Torvalds 		}
2281da177e4SLinus Torvalds 
2291da177e4SLinus Torvalds 	}
2301da177e4SLinus Torvalds 
231b802c074SThago Galesi 	kfree(kbuf);
2321da177e4SLinus Torvalds 	return total_retlen;
233969e57adSArtem Bityutskiy } /* mtdchar_read */
2341da177e4SLinus Torvalds 
235969e57adSArtem Bityutskiy static ssize_t mtdchar_write(struct file *file, const char __user *buf, size_t count,
236969e57adSArtem Bityutskiy 			loff_t *ppos)
2371da177e4SLinus Torvalds {
238f1a28c02SThomas Gleixner 	struct mtd_file_info *mfi = file->private_data;
239f1a28c02SThomas Gleixner 	struct mtd_info *mtd = mfi->mtd;
2403e45cf5eSGrant Erickson 	size_t size = count;
2411da177e4SLinus Torvalds 	char *kbuf;
2421da177e4SLinus Torvalds 	size_t retlen;
2431da177e4SLinus Torvalds 	size_t total_retlen=0;
2441da177e4SLinus Torvalds 	int ret=0;
2451da177e4SLinus Torvalds 	int len;
2461da177e4SLinus Torvalds 
247289c0522SBrian Norris 	pr_debug("MTD_write\n");
2481da177e4SLinus Torvalds 
2491da177e4SLinus Torvalds 	if (*ppos == mtd->size)
2501da177e4SLinus Torvalds 		return -ENOSPC;
2511da177e4SLinus Torvalds 
2521da177e4SLinus Torvalds 	if (*ppos + count > mtd->size)
2531da177e4SLinus Torvalds 		count = mtd->size - *ppos;
2541da177e4SLinus Torvalds 
2551da177e4SLinus Torvalds 	if (!count)
2561da177e4SLinus Torvalds 		return 0;
2571da177e4SLinus Torvalds 
2583e45cf5eSGrant Erickson 	kbuf = mtd_kmalloc_up_to(mtd, &size);
259b802c074SThago Galesi 	if (!kbuf)
260b802c074SThago Galesi 		return -ENOMEM;
261b802c074SThago Galesi 
2621da177e4SLinus Torvalds 	while (count) {
2633e45cf5eSGrant Erickson 		len = min_t(size_t, count, size);
2641da177e4SLinus Torvalds 
2651da177e4SLinus Torvalds 		if (copy_from_user(kbuf, buf, len)) {
2661da177e4SLinus Torvalds 			kfree(kbuf);
2671da177e4SLinus Torvalds 			return -EFAULT;
2681da177e4SLinus Torvalds 		}
2691da177e4SLinus Torvalds 
270f1a28c02SThomas Gleixner 		switch (mfi->mode) {
271beb133fcSBrian Norris 		case MTD_FILE_MODE_OTP_FACTORY:
27231f4233bSNicolas Pitre 			ret = -EROFS;
27331f4233bSNicolas Pitre 			break;
274beb133fcSBrian Norris 		case MTD_FILE_MODE_OTP_USER:
275482b43adSArtem Bityutskiy 			ret = mtd_write_user_prot_reg(mtd, *ppos, len,
276482b43adSArtem Bityutskiy 						      &retlen, kbuf);
27731f4233bSNicolas Pitre 			break;
278f1a28c02SThomas Gleixner 
279beb133fcSBrian Norris 		case MTD_FILE_MODE_RAW:
280f1a28c02SThomas Gleixner 		{
281f1a28c02SThomas Gleixner 			struct mtd_oob_ops ops;
282f1a28c02SThomas Gleixner 
2830612b9ddSBrian Norris 			ops.mode = MTD_OPS_RAW;
284f1a28c02SThomas Gleixner 			ops.datbuf = kbuf;
285f1a28c02SThomas Gleixner 			ops.oobbuf = NULL;
286bf514081SPeter Wippich 			ops.ooboffs = 0;
287f1a28c02SThomas Gleixner 			ops.len = len;
288f1a28c02SThomas Gleixner 
289a2cc5ba0SArtem Bityutskiy 			ret = mtd_write_oob(mtd, *ppos, &ops);
290f1a28c02SThomas Gleixner 			retlen = ops.retlen;
291f1a28c02SThomas Gleixner 			break;
292f1a28c02SThomas Gleixner 		}
293f1a28c02SThomas Gleixner 
29431f4233bSNicolas Pitre 		default:
295eda95cbfSArtem Bityutskiy 			ret = mtd_write(mtd, *ppos, len, &retlen, kbuf);
29631f4233bSNicolas Pitre 		}
2979a78bc83SChristian Riesch 
2989a78bc83SChristian Riesch 		/*
2999a78bc83SChristian Riesch 		 * Return -ENOSPC only if no data could be written at all.
3009a78bc83SChristian Riesch 		 * Otherwise just return the number of bytes that actually
3019a78bc83SChristian Riesch 		 * have been written.
3029a78bc83SChristian Riesch 		 */
3039a78bc83SChristian Riesch 		if ((ret == -ENOSPC) && (total_retlen))
3049a78bc83SChristian Riesch 			break;
3059a78bc83SChristian Riesch 
3061da177e4SLinus Torvalds 		if (!ret) {
3071da177e4SLinus Torvalds 			*ppos += retlen;
3081da177e4SLinus Torvalds 			total_retlen += retlen;
3091da177e4SLinus Torvalds 			count -= retlen;
3101da177e4SLinus Torvalds 			buf += retlen;
3111da177e4SLinus Torvalds 		}
3121da177e4SLinus Torvalds 		else {
3131da177e4SLinus Torvalds 			kfree(kbuf);
3141da177e4SLinus Torvalds 			return ret;
3151da177e4SLinus Torvalds 		}
3161da177e4SLinus Torvalds 	}
3171da177e4SLinus Torvalds 
318b802c074SThago Galesi 	kfree(kbuf);
3191da177e4SLinus Torvalds 	return total_retlen;
320969e57adSArtem Bityutskiy } /* mtdchar_write */
3211da177e4SLinus Torvalds 
3221da177e4SLinus Torvalds /*======================================================================
3231da177e4SLinus Torvalds 
3241da177e4SLinus Torvalds     IOCTL calls for getting device parameters.
3251da177e4SLinus Torvalds 
3261da177e4SLinus Torvalds ======================================================================*/
3271da177e4SLinus Torvalds static void mtdchar_erase_callback (struct erase_info *instr)
3281da177e4SLinus Torvalds {
3291da177e4SLinus Torvalds 	wake_up((wait_queue_head_t *)instr->priv);
3301da177e4SLinus Torvalds }
3311da177e4SLinus Torvalds 
332f1a28c02SThomas Gleixner static int otp_select_filemode(struct mtd_file_info *mfi, int mode)
333f1a28c02SThomas Gleixner {
334f1a28c02SThomas Gleixner 	struct mtd_info *mtd = mfi->mtd;
335b6de3d6cSArtem Bityutskiy 	size_t retlen;
336b6de3d6cSArtem Bityutskiy 
337f1a28c02SThomas Gleixner 	switch (mode) {
338f1a28c02SThomas Gleixner 	case MTD_OTP_FACTORY:
3395dc63fa2SUwe Kleine-König 		if (mtd_read_fact_prot_reg(mtd, -1, 0, &retlen, NULL) ==
3405dc63fa2SUwe Kleine-König 				-EOPNOTSUPP)
3415dc63fa2SUwe Kleine-König 			return -EOPNOTSUPP;
3425dc63fa2SUwe Kleine-König 
343beb133fcSBrian Norris 		mfi->mode = MTD_FILE_MODE_OTP_FACTORY;
344f1a28c02SThomas Gleixner 		break;
345f1a28c02SThomas Gleixner 	case MTD_OTP_USER:
3465dc63fa2SUwe Kleine-König 		if (mtd_read_user_prot_reg(mtd, -1, 0, &retlen, NULL) ==
3475dc63fa2SUwe Kleine-König 				-EOPNOTSUPP)
3485dc63fa2SUwe Kleine-König 			return -EOPNOTSUPP;
3495dc63fa2SUwe Kleine-König 
350beb133fcSBrian Norris 		mfi->mode = MTD_FILE_MODE_OTP_USER;
351f1a28c02SThomas Gleixner 		break;
352f1a28c02SThomas Gleixner 	case MTD_OTP_OFF:
3535dc63fa2SUwe Kleine-König 		mfi->mode = MTD_FILE_MODE_NORMAL;
354f1a28c02SThomas Gleixner 		break;
3555dc63fa2SUwe Kleine-König 	default:
3565dc63fa2SUwe Kleine-König 		return -EINVAL;
357f1a28c02SThomas Gleixner 	}
3585dc63fa2SUwe Kleine-König 
3595dc63fa2SUwe Kleine-König 	return 0;
360f1a28c02SThomas Gleixner }
361f1a28c02SThomas Gleixner 
362969e57adSArtem Bityutskiy static int mtdchar_writeoob(struct file *file, struct mtd_info *mtd,
36397718540SKevin Cernekee 	uint64_t start, uint32_t length, void __user *ptr,
36497718540SKevin Cernekee 	uint32_t __user *retp)
36597718540SKevin Cernekee {
3669ce244b3SBrian Norris 	struct mtd_file_info *mfi = file->private_data;
36797718540SKevin Cernekee 	struct mtd_oob_ops ops;
36897718540SKevin Cernekee 	uint32_t retlen;
36997718540SKevin Cernekee 	int ret = 0;
37097718540SKevin Cernekee 
37197718540SKevin Cernekee 	if (!(file->f_mode & FMODE_WRITE))
37297718540SKevin Cernekee 		return -EPERM;
37397718540SKevin Cernekee 
37497718540SKevin Cernekee 	if (length > 4096)
37597718540SKevin Cernekee 		return -EINVAL;
37697718540SKevin Cernekee 
3773c3c10bbSArtem Bityutskiy 	if (!mtd->_write_oob)
37897718540SKevin Cernekee 		ret = -EOPNOTSUPP;
37997718540SKevin Cernekee 	else
3800040476bSRoel Kluin 		ret = access_ok(VERIFY_READ, ptr, length) ? 0 : -EFAULT;
38197718540SKevin Cernekee 
38297718540SKevin Cernekee 	if (ret)
38397718540SKevin Cernekee 		return ret;
38497718540SKevin Cernekee 
38597718540SKevin Cernekee 	ops.ooblen = length;
386305b93f1SBrian Norris 	ops.ooboffs = start & (mtd->writesize - 1);
38797718540SKevin Cernekee 	ops.datbuf = NULL;
388beb133fcSBrian Norris 	ops.mode = (mfi->mode == MTD_FILE_MODE_RAW) ? MTD_OPS_RAW :
3890612b9ddSBrian Norris 		MTD_OPS_PLACE_OOB;
39097718540SKevin Cernekee 
39197718540SKevin Cernekee 	if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs))
39297718540SKevin Cernekee 		return -EINVAL;
39397718540SKevin Cernekee 
394df1f1d1cSJulia Lawall 	ops.oobbuf = memdup_user(ptr, length);
395df1f1d1cSJulia Lawall 	if (IS_ERR(ops.oobbuf))
396df1f1d1cSJulia Lawall 		return PTR_ERR(ops.oobbuf);
39797718540SKevin Cernekee 
398305b93f1SBrian Norris 	start &= ~((uint64_t)mtd->writesize - 1);
399a2cc5ba0SArtem Bityutskiy 	ret = mtd_write_oob(mtd, start, &ops);
40097718540SKevin Cernekee 
40197718540SKevin Cernekee 	if (ops.oobretlen > 0xFFFFFFFFU)
40297718540SKevin Cernekee 		ret = -EOVERFLOW;
40397718540SKevin Cernekee 	retlen = ops.oobretlen;
40497718540SKevin Cernekee 	if (copy_to_user(retp, &retlen, sizeof(length)))
40597718540SKevin Cernekee 		ret = -EFAULT;
40697718540SKevin Cernekee 
40797718540SKevin Cernekee 	kfree(ops.oobbuf);
40897718540SKevin Cernekee 	return ret;
40997718540SKevin Cernekee }
41097718540SKevin Cernekee 
411969e57adSArtem Bityutskiy static int mtdchar_readoob(struct file *file, struct mtd_info *mtd,
412c46f6483SBrian Norris 	uint64_t start, uint32_t length, void __user *ptr,
413c46f6483SBrian Norris 	uint32_t __user *retp)
41497718540SKevin Cernekee {
415c46f6483SBrian Norris 	struct mtd_file_info *mfi = file->private_data;
41697718540SKevin Cernekee 	struct mtd_oob_ops ops;
41797718540SKevin Cernekee 	int ret = 0;
41897718540SKevin Cernekee 
41997718540SKevin Cernekee 	if (length > 4096)
42097718540SKevin Cernekee 		return -EINVAL;
42197718540SKevin Cernekee 
422dac2639fSArtem Bityutskiy 	if (!access_ok(VERIFY_WRITE, ptr, length))
423dac2639fSArtem Bityutskiy 		return -EFAULT;
42497718540SKevin Cernekee 
42597718540SKevin Cernekee 	ops.ooblen = length;
426305b93f1SBrian Norris 	ops.ooboffs = start & (mtd->writesize - 1);
42797718540SKevin Cernekee 	ops.datbuf = NULL;
428beb133fcSBrian Norris 	ops.mode = (mfi->mode == MTD_FILE_MODE_RAW) ? MTD_OPS_RAW :
4290612b9ddSBrian Norris 		MTD_OPS_PLACE_OOB;
43097718540SKevin Cernekee 
43197718540SKevin Cernekee 	if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs))
43297718540SKevin Cernekee 		return -EINVAL;
43397718540SKevin Cernekee 
43497718540SKevin Cernekee 	ops.oobbuf = kmalloc(length, GFP_KERNEL);
43597718540SKevin Cernekee 	if (!ops.oobbuf)
43697718540SKevin Cernekee 		return -ENOMEM;
43797718540SKevin Cernekee 
438305b93f1SBrian Norris 	start &= ~((uint64_t)mtd->writesize - 1);
439fd2819bbSArtem Bityutskiy 	ret = mtd_read_oob(mtd, start, &ops);
44097718540SKevin Cernekee 
44197718540SKevin Cernekee 	if (put_user(ops.oobretlen, retp))
44297718540SKevin Cernekee 		ret = -EFAULT;
44397718540SKevin Cernekee 	else if (ops.oobretlen && copy_to_user(ptr, ops.oobbuf,
44497718540SKevin Cernekee 					    ops.oobretlen))
44597718540SKevin Cernekee 		ret = -EFAULT;
44697718540SKevin Cernekee 
44797718540SKevin Cernekee 	kfree(ops.oobbuf);
448041e4575SBrian Norris 
449041e4575SBrian Norris 	/*
450041e4575SBrian Norris 	 * NAND returns -EBADMSG on ECC errors, but it returns the OOB
451041e4575SBrian Norris 	 * data. For our userspace tools it is important to dump areas
452041e4575SBrian Norris 	 * with ECC errors!
453041e4575SBrian Norris 	 * For kernel internal usage it also might return -EUCLEAN
454041e4575SBrian Norris 	 * to signal the caller that a bitflip has occured and has
455041e4575SBrian Norris 	 * been corrected by the ECC algorithm.
456041e4575SBrian Norris 	 *
457c478d7e4SBrian Norris 	 * Note: currently the standard NAND function, nand_read_oob_std,
458c478d7e4SBrian Norris 	 * does not calculate ECC for the OOB area, so do not rely on
459c478d7e4SBrian Norris 	 * this behavior unless you have replaced it with your own.
460041e4575SBrian Norris 	 */
461d57f4054SBrian Norris 	if (mtd_is_bitflip_or_eccerr(ret))
462041e4575SBrian Norris 		return 0;
463041e4575SBrian Norris 
46497718540SKevin Cernekee 	return ret;
46597718540SKevin Cernekee }
46697718540SKevin Cernekee 
467cc26c3cdSBrian Norris /*
468*aab616e3SBoris Brezillon  * Copies (and truncates, if necessary) OOB layout information to the
469*aab616e3SBoris Brezillon  * deprecated layout struct, nand_ecclayout_user. This is necessary only to
470*aab616e3SBoris Brezillon  * support the deprecated API ioctl ECCGETLAYOUT while allowing all new
471*aab616e3SBoris Brezillon  * functionality to use mtd_ooblayout_ops flexibly (i.e. mtd_ooblayout_ops
472*aab616e3SBoris Brezillon  * can describe any kind of OOB layout with almost zero overhead from a
473*aab616e3SBoris Brezillon  * memory usage point of view).
474cc26c3cdSBrian Norris  */
475c2b78452SBoris Brezillon static int shrink_ecclayout(struct mtd_info *mtd,
476cc26c3cdSBrian Norris 			    struct nand_ecclayout_user *to)
477cc26c3cdSBrian Norris {
478c2b78452SBoris Brezillon 	struct mtd_oob_region oobregion;
479c2b78452SBoris Brezillon 	int i, section = 0, ret;
480cc26c3cdSBrian Norris 
481c2b78452SBoris Brezillon 	if (!mtd || !to)
482cc26c3cdSBrian Norris 		return -EINVAL;
483cc26c3cdSBrian Norris 
484cc26c3cdSBrian Norris 	memset(to, 0, sizeof(*to));
485cc26c3cdSBrian Norris 
486c2b78452SBoris Brezillon 	to->eccbytes = 0;
487c2b78452SBoris Brezillon 	for (i = 0; i < MTD_MAX_ECCPOS_ENTRIES;) {
488c2b78452SBoris Brezillon 		u32 eccpos;
489c2b78452SBoris Brezillon 
490c2b78452SBoris Brezillon 		ret = mtd_ooblayout_ecc(mtd, section, &oobregion);
491c2b78452SBoris Brezillon 		if (ret < 0) {
492c2b78452SBoris Brezillon 			if (ret != -ERANGE)
493c2b78452SBoris Brezillon 				return ret;
494c2b78452SBoris Brezillon 
495c2b78452SBoris Brezillon 			break;
496c2b78452SBoris Brezillon 		}
497c2b78452SBoris Brezillon 
498c2b78452SBoris Brezillon 		eccpos = oobregion.offset;
499c2b78452SBoris Brezillon 		for (; i < MTD_MAX_ECCPOS_ENTRIES &&
500c2b78452SBoris Brezillon 		       eccpos < oobregion.offset + oobregion.length; i++) {
501c2b78452SBoris Brezillon 			to->eccpos[i] = eccpos++;
502c2b78452SBoris Brezillon 			to->eccbytes++;
503c2b78452SBoris Brezillon 		}
504c2b78452SBoris Brezillon 	}
505cc26c3cdSBrian Norris 
506cc26c3cdSBrian Norris 	for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES; i++) {
507c2b78452SBoris Brezillon 		ret = mtd_ooblayout_free(mtd, i, &oobregion);
508c2b78452SBoris Brezillon 		if (ret < 0) {
509c2b78452SBoris Brezillon 			if (ret != -ERANGE)
510c2b78452SBoris Brezillon 				return ret;
511c2b78452SBoris Brezillon 
512cc26c3cdSBrian Norris 			break;
513cc26c3cdSBrian Norris 		}
514cc26c3cdSBrian Norris 
515c2b78452SBoris Brezillon 		to->oobfree[i].offset = oobregion.offset;
516c2b78452SBoris Brezillon 		to->oobfree[i].length = oobregion.length;
517c2b78452SBoris Brezillon 		to->oobavail += to->oobfree[i].length;
518c2b78452SBoris Brezillon 	}
519c2b78452SBoris Brezillon 
520c2b78452SBoris Brezillon 	return 0;
521c2b78452SBoris Brezillon }
522c2b78452SBoris Brezillon 
523c2b78452SBoris Brezillon static int get_oobinfo(struct mtd_info *mtd, struct nand_oobinfo *to)
524c2b78452SBoris Brezillon {
525c2b78452SBoris Brezillon 	struct mtd_oob_region oobregion;
526c2b78452SBoris Brezillon 	int i, section = 0, ret;
527c2b78452SBoris Brezillon 
528c2b78452SBoris Brezillon 	if (!mtd || !to)
529c2b78452SBoris Brezillon 		return -EINVAL;
530c2b78452SBoris Brezillon 
531c2b78452SBoris Brezillon 	memset(to, 0, sizeof(*to));
532c2b78452SBoris Brezillon 
533c2b78452SBoris Brezillon 	to->eccbytes = 0;
534c2b78452SBoris Brezillon 	for (i = 0; i < ARRAY_SIZE(to->eccpos);) {
535c2b78452SBoris Brezillon 		u32 eccpos;
536c2b78452SBoris Brezillon 
537c2b78452SBoris Brezillon 		ret = mtd_ooblayout_ecc(mtd, section, &oobregion);
538c2b78452SBoris Brezillon 		if (ret < 0) {
539c2b78452SBoris Brezillon 			if (ret != -ERANGE)
540c2b78452SBoris Brezillon 				return ret;
541c2b78452SBoris Brezillon 
542c2b78452SBoris Brezillon 			break;
543c2b78452SBoris Brezillon 		}
544c2b78452SBoris Brezillon 
545c2b78452SBoris Brezillon 		if (oobregion.length + i > ARRAY_SIZE(to->eccpos))
546c2b78452SBoris Brezillon 			return -EINVAL;
547c2b78452SBoris Brezillon 
548c2b78452SBoris Brezillon 		eccpos = oobregion.offset;
549c2b78452SBoris Brezillon 		for (; eccpos < oobregion.offset + oobregion.length; i++) {
550c2b78452SBoris Brezillon 			to->eccpos[i] = eccpos++;
551c2b78452SBoris Brezillon 			to->eccbytes++;
552c2b78452SBoris Brezillon 		}
553c2b78452SBoris Brezillon 	}
554c2b78452SBoris Brezillon 
555c2b78452SBoris Brezillon 	for (i = 0; i < 8; i++) {
556c2b78452SBoris Brezillon 		ret = mtd_ooblayout_free(mtd, i, &oobregion);
557c2b78452SBoris Brezillon 		if (ret < 0) {
558c2b78452SBoris Brezillon 			if (ret != -ERANGE)
559c2b78452SBoris Brezillon 				return ret;
560c2b78452SBoris Brezillon 
561c2b78452SBoris Brezillon 			break;
562c2b78452SBoris Brezillon 		}
563c2b78452SBoris Brezillon 
564c2b78452SBoris Brezillon 		to->oobfree[i][0] = oobregion.offset;
565c2b78452SBoris Brezillon 		to->oobfree[i][1] = oobregion.length;
566c2b78452SBoris Brezillon 	}
567c2b78452SBoris Brezillon 
568c2b78452SBoris Brezillon 	to->useecc = MTD_NANDECC_AUTOPLACE;
569c2b78452SBoris Brezillon 
570cc26c3cdSBrian Norris 	return 0;
571cc26c3cdSBrian Norris }
572cc26c3cdSBrian Norris 
573969e57adSArtem Bityutskiy static int mtdchar_blkpg_ioctl(struct mtd_info *mtd,
57453bb724fSBrian Norris 			       struct blkpg_ioctl_arg *arg)
575d0f7959eSRoman Tereshonkov {
576d0f7959eSRoman Tereshonkov 	struct blkpg_partition p;
577d0f7959eSRoman Tereshonkov 
578d0f7959eSRoman Tereshonkov 	if (!capable(CAP_SYS_ADMIN))
579d0f7959eSRoman Tereshonkov 		return -EPERM;
580d0f7959eSRoman Tereshonkov 
58153bb724fSBrian Norris 	if (copy_from_user(&p, arg->data, sizeof(p)))
582d0f7959eSRoman Tereshonkov 		return -EFAULT;
583d0f7959eSRoman Tereshonkov 
58453bb724fSBrian Norris 	switch (arg->op) {
585d0f7959eSRoman Tereshonkov 	case BLKPG_ADD_PARTITION:
586d0f7959eSRoman Tereshonkov 
587a7e93dcdSRoman Tereshonkov 		/* Only master mtd device must be used to add partitions */
588a7e93dcdSRoman Tereshonkov 		if (mtd_is_partition(mtd))
589a7e93dcdSRoman Tereshonkov 			return -EINVAL;
590a7e93dcdSRoman Tereshonkov 
5911cc8d841SBrian Norris 		/* Sanitize user input */
5921cc8d841SBrian Norris 		p.devname[BLKPG_DEVNAMELTH - 1] = '\0';
5931cc8d841SBrian Norris 
594d0f7959eSRoman Tereshonkov 		return mtd_add_partition(mtd, p.devname, p.start, p.length);
595d0f7959eSRoman Tereshonkov 
596d0f7959eSRoman Tereshonkov 	case BLKPG_DEL_PARTITION:
597d0f7959eSRoman Tereshonkov 
598d0f7959eSRoman Tereshonkov 		if (p.pno < 0)
599d0f7959eSRoman Tereshonkov 			return -EINVAL;
600d0f7959eSRoman Tereshonkov 
601d0f7959eSRoman Tereshonkov 		return mtd_del_partition(mtd, p.pno);
602d0f7959eSRoman Tereshonkov 
603d0f7959eSRoman Tereshonkov 	default:
604d0f7959eSRoman Tereshonkov 		return -EINVAL;
605d0f7959eSRoman Tereshonkov 	}
606d0f7959eSRoman Tereshonkov }
607d0f7959eSRoman Tereshonkov 
608969e57adSArtem Bityutskiy static int mtdchar_write_ioctl(struct mtd_info *mtd,
609e99d8b08SBrian Norris 		struct mtd_write_req __user *argp)
610e99d8b08SBrian Norris {
611e99d8b08SBrian Norris 	struct mtd_write_req req;
612e99d8b08SBrian Norris 	struct mtd_oob_ops ops;
613f62cde49SGeert Uytterhoeven 	const void __user *usr_data, *usr_oob;
614e99d8b08SBrian Norris 	int ret;
615e99d8b08SBrian Norris 
616f62cde49SGeert Uytterhoeven 	if (copy_from_user(&req, argp, sizeof(req)))
617e99d8b08SBrian Norris 		return -EFAULT;
618f62cde49SGeert Uytterhoeven 
619f62cde49SGeert Uytterhoeven 	usr_data = (const void __user *)(uintptr_t)req.usr_data;
620f62cde49SGeert Uytterhoeven 	usr_oob = (const void __user *)(uintptr_t)req.usr_oob;
621f62cde49SGeert Uytterhoeven 	if (!access_ok(VERIFY_READ, usr_data, req.len) ||
622f62cde49SGeert Uytterhoeven 	    !access_ok(VERIFY_READ, usr_oob, req.ooblen))
623f62cde49SGeert Uytterhoeven 		return -EFAULT;
624f62cde49SGeert Uytterhoeven 
6253c3c10bbSArtem Bityutskiy 	if (!mtd->_write_oob)
626e99d8b08SBrian Norris 		return -EOPNOTSUPP;
627e99d8b08SBrian Norris 
628e99d8b08SBrian Norris 	ops.mode = req.mode;
629e99d8b08SBrian Norris 	ops.len = (size_t)req.len;
630e99d8b08SBrian Norris 	ops.ooblen = (size_t)req.ooblen;
631e99d8b08SBrian Norris 	ops.ooboffs = 0;
632e99d8b08SBrian Norris 
633f62cde49SGeert Uytterhoeven 	if (usr_data) {
634e99d8b08SBrian Norris 		ops.datbuf = memdup_user(usr_data, ops.len);
635e99d8b08SBrian Norris 		if (IS_ERR(ops.datbuf))
636e99d8b08SBrian Norris 			return PTR_ERR(ops.datbuf);
637e99d8b08SBrian Norris 	} else {
638e99d8b08SBrian Norris 		ops.datbuf = NULL;
639e99d8b08SBrian Norris 	}
640e99d8b08SBrian Norris 
641f62cde49SGeert Uytterhoeven 	if (usr_oob) {
642e99d8b08SBrian Norris 		ops.oobbuf = memdup_user(usr_oob, ops.ooblen);
643e99d8b08SBrian Norris 		if (IS_ERR(ops.oobbuf)) {
644e99d8b08SBrian Norris 			kfree(ops.datbuf);
645e99d8b08SBrian Norris 			return PTR_ERR(ops.oobbuf);
646e99d8b08SBrian Norris 		}
647e99d8b08SBrian Norris 	} else {
648e99d8b08SBrian Norris 		ops.oobbuf = NULL;
649e99d8b08SBrian Norris 	}
650e99d8b08SBrian Norris 
651a2cc5ba0SArtem Bityutskiy 	ret = mtd_write_oob(mtd, (loff_t)req.start, &ops);
652e99d8b08SBrian Norris 
653e99d8b08SBrian Norris 	kfree(ops.datbuf);
654e99d8b08SBrian Norris 	kfree(ops.oobbuf);
655e99d8b08SBrian Norris 
656e99d8b08SBrian Norris 	return ret;
657e99d8b08SBrian Norris }
658e99d8b08SBrian Norris 
659969e57adSArtem Bityutskiy static int mtdchar_ioctl(struct file *file, u_int cmd, u_long arg)
6601da177e4SLinus Torvalds {
661f1a28c02SThomas Gleixner 	struct mtd_file_info *mfi = file->private_data;
662f1a28c02SThomas Gleixner 	struct mtd_info *mtd = mfi->mtd;
6631da177e4SLinus Torvalds 	void __user *argp = (void __user *)arg;
6641da177e4SLinus Torvalds 	int ret = 0;
6651da177e4SLinus Torvalds 	u_long size;
66673c619eaSJoern Engel 	struct mtd_info_user info;
6671da177e4SLinus Torvalds 
668289c0522SBrian Norris 	pr_debug("MTD_ioctl\n");
6691da177e4SLinus Torvalds 
6701da177e4SLinus Torvalds 	size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT;
6711da177e4SLinus Torvalds 	if (cmd & IOC_IN) {
6721da177e4SLinus Torvalds 		if (!access_ok(VERIFY_READ, argp, size))
6731da177e4SLinus Torvalds 			return -EFAULT;
6741da177e4SLinus Torvalds 	}
6751da177e4SLinus Torvalds 	if (cmd & IOC_OUT) {
6761da177e4SLinus Torvalds 		if (!access_ok(VERIFY_WRITE, argp, size))
6771da177e4SLinus Torvalds 			return -EFAULT;
6781da177e4SLinus Torvalds 	}
6791da177e4SLinus Torvalds 
6801da177e4SLinus Torvalds 	switch (cmd) {
6811da177e4SLinus Torvalds 	case MEMGETREGIONCOUNT:
6821da177e4SLinus Torvalds 		if (copy_to_user(argp, &(mtd->numeraseregions), sizeof(int)))
6831da177e4SLinus Torvalds 			return -EFAULT;
6841da177e4SLinus Torvalds 		break;
6851da177e4SLinus Torvalds 
6861da177e4SLinus Torvalds 	case MEMGETREGIONINFO:
6871da177e4SLinus Torvalds 	{
688b67c5f87SZev Weiss 		uint32_t ur_idx;
689b67c5f87SZev Weiss 		struct mtd_erase_region_info *kr;
690bcc98a46SH Hartley Sweeten 		struct region_info_user __user *ur = argp;
6911da177e4SLinus Torvalds 
692b67c5f87SZev Weiss 		if (get_user(ur_idx, &(ur->regionindex)))
6931da177e4SLinus Torvalds 			return -EFAULT;
6941da177e4SLinus Torvalds 
6955e59be1fSDan Carpenter 		if (ur_idx >= mtd->numeraseregions)
6965e59be1fSDan Carpenter 			return -EINVAL;
6975e59be1fSDan Carpenter 
698b67c5f87SZev Weiss 		kr = &(mtd->eraseregions[ur_idx]);
699b67c5f87SZev Weiss 
700b67c5f87SZev Weiss 		if (put_user(kr->offset, &(ur->offset))
701b67c5f87SZev Weiss 		    || put_user(kr->erasesize, &(ur->erasesize))
702b67c5f87SZev Weiss 		    || put_user(kr->numblocks, &(ur->numblocks)))
7031da177e4SLinus Torvalds 			return -EFAULT;
704b67c5f87SZev Weiss 
7051da177e4SLinus Torvalds 		break;
7061da177e4SLinus Torvalds 	}
7071da177e4SLinus Torvalds 
7081da177e4SLinus Torvalds 	case MEMGETINFO:
709a0c5a394SVasiliy Kulikov 		memset(&info, 0, sizeof(info));
71073c619eaSJoern Engel 		info.type	= mtd->type;
71173c619eaSJoern Engel 		info.flags	= mtd->flags;
71273c619eaSJoern Engel 		info.size	= mtd->size;
71373c619eaSJoern Engel 		info.erasesize	= mtd->erasesize;
71473c619eaSJoern Engel 		info.writesize	= mtd->writesize;
71573c619eaSJoern Engel 		info.oobsize	= mtd->oobsize;
71619fb4341SBrian Norris 		/* The below field is obsolete */
71719fb4341SBrian Norris 		info.padding	= 0;
71873c619eaSJoern Engel 		if (copy_to_user(argp, &info, sizeof(struct mtd_info_user)))
7191da177e4SLinus Torvalds 			return -EFAULT;
7201da177e4SLinus Torvalds 		break;
7211da177e4SLinus Torvalds 
7221da177e4SLinus Torvalds 	case MEMERASE:
7230dc54e9fSKevin Cernekee 	case MEMERASE64:
7241da177e4SLinus Torvalds 	{
7251da177e4SLinus Torvalds 		struct erase_info *erase;
7261da177e4SLinus Torvalds 
727aeb5d727SAl Viro 		if(!(file->f_mode & FMODE_WRITE))
7281da177e4SLinus Torvalds 			return -EPERM;
7291da177e4SLinus Torvalds 
73095b93a0cSBurman Yan 		erase=kzalloc(sizeof(struct erase_info),GFP_KERNEL);
7311da177e4SLinus Torvalds 		if (!erase)
7321da177e4SLinus Torvalds 			ret = -ENOMEM;
7331da177e4SLinus Torvalds 		else {
7341da177e4SLinus Torvalds 			wait_queue_head_t waitq;
7351da177e4SLinus Torvalds 			DECLARE_WAITQUEUE(wait, current);
7361da177e4SLinus Torvalds 
7371da177e4SLinus Torvalds 			init_waitqueue_head(&waitq);
7381da177e4SLinus Torvalds 
7390dc54e9fSKevin Cernekee 			if (cmd == MEMERASE64) {
7400dc54e9fSKevin Cernekee 				struct erase_info_user64 einfo64;
7410dc54e9fSKevin Cernekee 
7420dc54e9fSKevin Cernekee 				if (copy_from_user(&einfo64, argp,
7430dc54e9fSKevin Cernekee 					    sizeof(struct erase_info_user64))) {
7440dc54e9fSKevin Cernekee 					kfree(erase);
7450dc54e9fSKevin Cernekee 					return -EFAULT;
7460dc54e9fSKevin Cernekee 				}
7470dc54e9fSKevin Cernekee 				erase->addr = einfo64.start;
7480dc54e9fSKevin Cernekee 				erase->len = einfo64.length;
7490dc54e9fSKevin Cernekee 			} else {
7500dc54e9fSKevin Cernekee 				struct erase_info_user einfo32;
7510dc54e9fSKevin Cernekee 
7520dc54e9fSKevin Cernekee 				if (copy_from_user(&einfo32, argp,
7531da177e4SLinus Torvalds 					    sizeof(struct erase_info_user))) {
7541da177e4SLinus Torvalds 					kfree(erase);
7551da177e4SLinus Torvalds 					return -EFAULT;
7561da177e4SLinus Torvalds 				}
7570dc54e9fSKevin Cernekee 				erase->addr = einfo32.start;
7580dc54e9fSKevin Cernekee 				erase->len = einfo32.length;
7590dc54e9fSKevin Cernekee 			}
7601da177e4SLinus Torvalds 			erase->mtd = mtd;
7611da177e4SLinus Torvalds 			erase->callback = mtdchar_erase_callback;
7621da177e4SLinus Torvalds 			erase->priv = (unsigned long)&waitq;
7631da177e4SLinus Torvalds 
7641da177e4SLinus Torvalds 			/*
7651da177e4SLinus Torvalds 			  FIXME: Allow INTERRUPTIBLE. Which means
7661da177e4SLinus Torvalds 			  not having the wait_queue head on the stack.
7671da177e4SLinus Torvalds 
7681da177e4SLinus Torvalds 			  If the wq_head is on the stack, and we
7691da177e4SLinus Torvalds 			  leave because we got interrupted, then the
7701da177e4SLinus Torvalds 			  wq_head is no longer there when the
7711da177e4SLinus Torvalds 			  callback routine tries to wake us up.
7721da177e4SLinus Torvalds 			*/
7737e1f0dc0SArtem Bityutskiy 			ret = mtd_erase(mtd, erase);
7741da177e4SLinus Torvalds 			if (!ret) {
7751da177e4SLinus Torvalds 				set_current_state(TASK_UNINTERRUPTIBLE);
7761da177e4SLinus Torvalds 				add_wait_queue(&waitq, &wait);
7771da177e4SLinus Torvalds 				if (erase->state != MTD_ERASE_DONE &&
7781da177e4SLinus Torvalds 				    erase->state != MTD_ERASE_FAILED)
7791da177e4SLinus Torvalds 					schedule();
7801da177e4SLinus Torvalds 				remove_wait_queue(&waitq, &wait);
7811da177e4SLinus Torvalds 				set_current_state(TASK_RUNNING);
7821da177e4SLinus Torvalds 
7831da177e4SLinus Torvalds 				ret = (erase->state == MTD_ERASE_FAILED)?-EIO:0;
7841da177e4SLinus Torvalds 			}
7851da177e4SLinus Torvalds 			kfree(erase);
7861da177e4SLinus Torvalds 		}
7871da177e4SLinus Torvalds 		break;
7881da177e4SLinus Torvalds 	}
7891da177e4SLinus Torvalds 
7901da177e4SLinus Torvalds 	case MEMWRITEOOB:
7911da177e4SLinus Torvalds 	{
7921da177e4SLinus Torvalds 		struct mtd_oob_buf buf;
79397718540SKevin Cernekee 		struct mtd_oob_buf __user *buf_user = argp;
7941da177e4SLinus Torvalds 
79597718540SKevin Cernekee 		/* NOTE: writes return length to buf_user->length */
79697718540SKevin Cernekee 		if (copy_from_user(&buf, argp, sizeof(buf)))
7971da177e4SLinus Torvalds 			ret = -EFAULT;
79897718540SKevin Cernekee 		else
799969e57adSArtem Bityutskiy 			ret = mtdchar_writeoob(file, mtd, buf.start, buf.length,
80097718540SKevin Cernekee 				buf.ptr, &buf_user->length);
8011da177e4SLinus Torvalds 		break;
8021da177e4SLinus Torvalds 	}
8031da177e4SLinus Torvalds 
8041da177e4SLinus Torvalds 	case MEMREADOOB:
8051da177e4SLinus Torvalds 	{
8061da177e4SLinus Torvalds 		struct mtd_oob_buf buf;
80797718540SKevin Cernekee 		struct mtd_oob_buf __user *buf_user = argp;
8081da177e4SLinus Torvalds 
80997718540SKevin Cernekee 		/* NOTE: writes return length to buf_user->start */
81097718540SKevin Cernekee 		if (copy_from_user(&buf, argp, sizeof(buf)))
81197718540SKevin Cernekee 			ret = -EFAULT;
8121da177e4SLinus Torvalds 		else
813969e57adSArtem Bityutskiy 			ret = mtdchar_readoob(file, mtd, buf.start, buf.length,
81497718540SKevin Cernekee 				buf.ptr, &buf_user->start);
8151da177e4SLinus Torvalds 		break;
8161da177e4SLinus Torvalds 	}
8171da177e4SLinus Torvalds 
818aea7cea9SKevin Cernekee 	case MEMWRITEOOB64:
819aea7cea9SKevin Cernekee 	{
820aea7cea9SKevin Cernekee 		struct mtd_oob_buf64 buf;
821aea7cea9SKevin Cernekee 		struct mtd_oob_buf64 __user *buf_user = argp;
822aea7cea9SKevin Cernekee 
823aea7cea9SKevin Cernekee 		if (copy_from_user(&buf, argp, sizeof(buf)))
824aea7cea9SKevin Cernekee 			ret = -EFAULT;
825aea7cea9SKevin Cernekee 		else
826969e57adSArtem Bityutskiy 			ret = mtdchar_writeoob(file, mtd, buf.start, buf.length,
827aea7cea9SKevin Cernekee 				(void __user *)(uintptr_t)buf.usr_ptr,
828aea7cea9SKevin Cernekee 				&buf_user->length);
829aea7cea9SKevin Cernekee 		break;
830aea7cea9SKevin Cernekee 	}
831aea7cea9SKevin Cernekee 
832aea7cea9SKevin Cernekee 	case MEMREADOOB64:
833aea7cea9SKevin Cernekee 	{
834aea7cea9SKevin Cernekee 		struct mtd_oob_buf64 buf;
835aea7cea9SKevin Cernekee 		struct mtd_oob_buf64 __user *buf_user = argp;
836aea7cea9SKevin Cernekee 
837aea7cea9SKevin Cernekee 		if (copy_from_user(&buf, argp, sizeof(buf)))
838aea7cea9SKevin Cernekee 			ret = -EFAULT;
839aea7cea9SKevin Cernekee 		else
840969e57adSArtem Bityutskiy 			ret = mtdchar_readoob(file, mtd, buf.start, buf.length,
841aea7cea9SKevin Cernekee 				(void __user *)(uintptr_t)buf.usr_ptr,
842aea7cea9SKevin Cernekee 				&buf_user->length);
843aea7cea9SKevin Cernekee 		break;
844aea7cea9SKevin Cernekee 	}
845aea7cea9SKevin Cernekee 
846e99d8b08SBrian Norris 	case MEMWRITE:
847e99d8b08SBrian Norris 	{
848969e57adSArtem Bityutskiy 		ret = mtdchar_write_ioctl(mtd,
849e99d8b08SBrian Norris 		      (struct mtd_write_req __user *)arg);
850e99d8b08SBrian Norris 		break;
851e99d8b08SBrian Norris 	}
852e99d8b08SBrian Norris 
8531da177e4SLinus Torvalds 	case MEMLOCK:
8541da177e4SLinus Torvalds 	{
855175428b2SHarvey Harrison 		struct erase_info_user einfo;
8561da177e4SLinus Torvalds 
857175428b2SHarvey Harrison 		if (copy_from_user(&einfo, argp, sizeof(einfo)))
8581da177e4SLinus Torvalds 			return -EFAULT;
8591da177e4SLinus Torvalds 
8607799f9acSArtem Bityutskiy 		ret = mtd_lock(mtd, einfo.start, einfo.length);
8611da177e4SLinus Torvalds 		break;
8621da177e4SLinus Torvalds 	}
8631da177e4SLinus Torvalds 
8641da177e4SLinus Torvalds 	case MEMUNLOCK:
8651da177e4SLinus Torvalds 	{
866175428b2SHarvey Harrison 		struct erase_info_user einfo;
8671da177e4SLinus Torvalds 
868175428b2SHarvey Harrison 		if (copy_from_user(&einfo, argp, sizeof(einfo)))
8691da177e4SLinus Torvalds 			return -EFAULT;
8701da177e4SLinus Torvalds 
871b66005cdSArtem Bityutskiy 		ret = mtd_unlock(mtd, einfo.start, einfo.length);
8721da177e4SLinus Torvalds 		break;
8731da177e4SLinus Torvalds 	}
8741da177e4SLinus Torvalds 
8759938424fSRichard Cochran 	case MEMISLOCKED:
8769938424fSRichard Cochran 	{
8779938424fSRichard Cochran 		struct erase_info_user einfo;
8789938424fSRichard Cochran 
8799938424fSRichard Cochran 		if (copy_from_user(&einfo, argp, sizeof(einfo)))
8809938424fSRichard Cochran 			return -EFAULT;
8819938424fSRichard Cochran 
882e95e9786SArtem Bityutskiy 		ret = mtd_is_locked(mtd, einfo.start, einfo.length);
8839938424fSRichard Cochran 		break;
8849938424fSRichard Cochran 	}
8859938424fSRichard Cochran 
8865bd34c09SThomas Gleixner 	/* Legacy interface */
8871da177e4SLinus Torvalds 	case MEMGETOOBSEL:
8881da177e4SLinus Torvalds 	{
8895bd34c09SThomas Gleixner 		struct nand_oobinfo oi;
8905bd34c09SThomas Gleixner 
891adbbc3bcSBoris Brezillon 		if (!mtd->ooblayout)
8925bd34c09SThomas Gleixner 			return -EOPNOTSUPP;
8935bd34c09SThomas Gleixner 
894c2b78452SBoris Brezillon 		ret = get_oobinfo(mtd, &oi);
895c2b78452SBoris Brezillon 		if (ret)
896c2b78452SBoris Brezillon 			return ret;
8975bd34c09SThomas Gleixner 
8985bd34c09SThomas Gleixner 		if (copy_to_user(argp, &oi, sizeof(struct nand_oobinfo)))
8991da177e4SLinus Torvalds 			return -EFAULT;
9001da177e4SLinus Torvalds 		break;
9011da177e4SLinus Torvalds 	}
9021da177e4SLinus Torvalds 
9031da177e4SLinus Torvalds 	case MEMGETBADBLOCK:
9041da177e4SLinus Torvalds 	{
9051da177e4SLinus Torvalds 		loff_t offs;
9061da177e4SLinus Torvalds 
9071da177e4SLinus Torvalds 		if (copy_from_user(&offs, argp, sizeof(loff_t)))
9081da177e4SLinus Torvalds 			return -EFAULT;
9097086c19dSArtem Bityutskiy 		return mtd_block_isbad(mtd, offs);
9101da177e4SLinus Torvalds 		break;
9111da177e4SLinus Torvalds 	}
9121da177e4SLinus Torvalds 
9131da177e4SLinus Torvalds 	case MEMSETBADBLOCK:
9141da177e4SLinus Torvalds 	{
9151da177e4SLinus Torvalds 		loff_t offs;
9161da177e4SLinus Torvalds 
9171da177e4SLinus Torvalds 		if (copy_from_user(&offs, argp, sizeof(loff_t)))
9181da177e4SLinus Torvalds 			return -EFAULT;
9195942ddbcSArtem Bityutskiy 		return mtd_block_markbad(mtd, offs);
9201da177e4SLinus Torvalds 		break;
9211da177e4SLinus Torvalds 	}
9221da177e4SLinus Torvalds 
92331f4233bSNicolas Pitre 	case OTPSELECT:
92431f4233bSNicolas Pitre 	{
92531f4233bSNicolas Pitre 		int mode;
92631f4233bSNicolas Pitre 		if (copy_from_user(&mode, argp, sizeof(int)))
92731f4233bSNicolas Pitre 			return -EFAULT;
928f1a28c02SThomas Gleixner 
929beb133fcSBrian Norris 		mfi->mode = MTD_FILE_MODE_NORMAL;
930f1a28c02SThomas Gleixner 
931f1a28c02SThomas Gleixner 		ret = otp_select_filemode(mfi, mode);
932f1a28c02SThomas Gleixner 
93381dba488SNicolas Pitre 		file->f_pos = 0;
93431f4233bSNicolas Pitre 		break;
93531f4233bSNicolas Pitre 	}
93631f4233bSNicolas Pitre 
93731f4233bSNicolas Pitre 	case OTPGETREGIONCOUNT:
93831f4233bSNicolas Pitre 	case OTPGETREGIONINFO:
93931f4233bSNicolas Pitre 	{
94031f4233bSNicolas Pitre 		struct otp_info *buf = kmalloc(4096, GFP_KERNEL);
9414b78fc42SChristian Riesch 		size_t retlen;
94231f4233bSNicolas Pitre 		if (!buf)
94331f4233bSNicolas Pitre 			return -ENOMEM;
944f1a28c02SThomas Gleixner 		switch (mfi->mode) {
945beb133fcSBrian Norris 		case MTD_FILE_MODE_OTP_FACTORY:
9464b78fc42SChristian Riesch 			ret = mtd_get_fact_prot_info(mtd, 4096, &retlen, buf);
94731f4233bSNicolas Pitre 			break;
948beb133fcSBrian Norris 		case MTD_FILE_MODE_OTP_USER:
9494b78fc42SChristian Riesch 			ret = mtd_get_user_prot_info(mtd, 4096, &retlen, buf);
95031f4233bSNicolas Pitre 			break;
951f1a28c02SThomas Gleixner 		default:
95287e858a9SArtem Bityutskiy 			ret = -EINVAL;
953f1a28c02SThomas Gleixner 			break;
95431f4233bSNicolas Pitre 		}
9554b78fc42SChristian Riesch 		if (!ret) {
95631f4233bSNicolas Pitre 			if (cmd == OTPGETREGIONCOUNT) {
9574b78fc42SChristian Riesch 				int nbr = retlen / sizeof(struct otp_info);
95831f4233bSNicolas Pitre 				ret = copy_to_user(argp, &nbr, sizeof(int));
95931f4233bSNicolas Pitre 			} else
9604b78fc42SChristian Riesch 				ret = copy_to_user(argp, buf, retlen);
96131f4233bSNicolas Pitre 			if (ret)
96231f4233bSNicolas Pitre 				ret = -EFAULT;
96331f4233bSNicolas Pitre 		}
96431f4233bSNicolas Pitre 		kfree(buf);
96531f4233bSNicolas Pitre 		break;
96631f4233bSNicolas Pitre 	}
96731f4233bSNicolas Pitre 
96831f4233bSNicolas Pitre 	case OTPLOCK:
96931f4233bSNicolas Pitre 	{
970175428b2SHarvey Harrison 		struct otp_info oinfo;
97131f4233bSNicolas Pitre 
972beb133fcSBrian Norris 		if (mfi->mode != MTD_FILE_MODE_OTP_USER)
97331f4233bSNicolas Pitre 			return -EINVAL;
974175428b2SHarvey Harrison 		if (copy_from_user(&oinfo, argp, sizeof(oinfo)))
97531f4233bSNicolas Pitre 			return -EFAULT;
9764403dbfbSArtem Bityutskiy 		ret = mtd_lock_user_prot_reg(mtd, oinfo.start, oinfo.length);
97731f4233bSNicolas Pitre 		break;
97831f4233bSNicolas Pitre 	}
97931f4233bSNicolas Pitre 
9807854d3f7SBrian Norris 	/* This ioctl is being deprecated - it truncates the ECC layout */
981f1a28c02SThomas Gleixner 	case ECCGETLAYOUT:
982f1a28c02SThomas Gleixner 	{
983cc26c3cdSBrian Norris 		struct nand_ecclayout_user *usrlay;
984cc26c3cdSBrian Norris 
985adbbc3bcSBoris Brezillon 		if (!mtd->ooblayout)
986f1a28c02SThomas Gleixner 			return -EOPNOTSUPP;
987f1a28c02SThomas Gleixner 
988cc26c3cdSBrian Norris 		usrlay = kmalloc(sizeof(*usrlay), GFP_KERNEL);
989cc26c3cdSBrian Norris 		if (!usrlay)
990cc26c3cdSBrian Norris 			return -ENOMEM;
991cc26c3cdSBrian Norris 
992c2b78452SBoris Brezillon 		shrink_ecclayout(mtd, usrlay);
993cc26c3cdSBrian Norris 
994cc26c3cdSBrian Norris 		if (copy_to_user(argp, usrlay, sizeof(*usrlay)))
995cc26c3cdSBrian Norris 			ret = -EFAULT;
996cc26c3cdSBrian Norris 		kfree(usrlay);
997f1a28c02SThomas Gleixner 		break;
998f1a28c02SThomas Gleixner 	}
999f1a28c02SThomas Gleixner 
1000f1a28c02SThomas Gleixner 	case ECCGETSTATS:
1001f1a28c02SThomas Gleixner 	{
1002f1a28c02SThomas Gleixner 		if (copy_to_user(argp, &mtd->ecc_stats,
1003f1a28c02SThomas Gleixner 				 sizeof(struct mtd_ecc_stats)))
1004f1a28c02SThomas Gleixner 			return -EFAULT;
1005f1a28c02SThomas Gleixner 		break;
1006f1a28c02SThomas Gleixner 	}
1007f1a28c02SThomas Gleixner 
1008f1a28c02SThomas Gleixner 	case MTDFILEMODE:
1009f1a28c02SThomas Gleixner 	{
1010f1a28c02SThomas Gleixner 		mfi->mode = 0;
1011f1a28c02SThomas Gleixner 
1012f1a28c02SThomas Gleixner 		switch(arg) {
1013beb133fcSBrian Norris 		case MTD_FILE_MODE_OTP_FACTORY:
1014beb133fcSBrian Norris 		case MTD_FILE_MODE_OTP_USER:
1015f1a28c02SThomas Gleixner 			ret = otp_select_filemode(mfi, arg);
1016f1a28c02SThomas Gleixner 			break;
1017f1a28c02SThomas Gleixner 
1018beb133fcSBrian Norris 		case MTD_FILE_MODE_RAW:
1019fc002e3cSArtem Bityutskiy 			if (!mtd_has_oob(mtd))
1020f1a28c02SThomas Gleixner 				return -EOPNOTSUPP;
1021f1a28c02SThomas Gleixner 			mfi->mode = arg;
1022f1a28c02SThomas Gleixner 
1023beb133fcSBrian Norris 		case MTD_FILE_MODE_NORMAL:
1024f1a28c02SThomas Gleixner 			break;
1025f1a28c02SThomas Gleixner 		default:
1026f1a28c02SThomas Gleixner 			ret = -EINVAL;
1027f1a28c02SThomas Gleixner 		}
1028f1a28c02SThomas Gleixner 		file->f_pos = 0;
1029f1a28c02SThomas Gleixner 		break;
1030f1a28c02SThomas Gleixner 	}
1031f1a28c02SThomas Gleixner 
1032d0f7959eSRoman Tereshonkov 	case BLKPG:
1033d0f7959eSRoman Tereshonkov 	{
103453bb724fSBrian Norris 		struct blkpg_ioctl_arg __user *blk_arg = argp;
103553bb724fSBrian Norris 		struct blkpg_ioctl_arg a;
103653bb724fSBrian Norris 
103753bb724fSBrian Norris 		if (copy_from_user(&a, blk_arg, sizeof(a)))
103853bb724fSBrian Norris 			ret = -EFAULT;
103953bb724fSBrian Norris 		else
104053bb724fSBrian Norris 			ret = mtdchar_blkpg_ioctl(mtd, &a);
1041d0f7959eSRoman Tereshonkov 		break;
1042d0f7959eSRoman Tereshonkov 	}
1043d0f7959eSRoman Tereshonkov 
1044d0f7959eSRoman Tereshonkov 	case BLKRRPART:
1045d0f7959eSRoman Tereshonkov 	{
1046d0f7959eSRoman Tereshonkov 		/* No reread partition feature. Just return ok */
1047d0f7959eSRoman Tereshonkov 		ret = 0;
1048d0f7959eSRoman Tereshonkov 		break;
1049d0f7959eSRoman Tereshonkov 	}
1050d0f7959eSRoman Tereshonkov 
10511da177e4SLinus Torvalds 	default:
10521da177e4SLinus Torvalds 		ret = -ENOTTY;
10531da177e4SLinus Torvalds 	}
10541da177e4SLinus Torvalds 
10551da177e4SLinus Torvalds 	return ret;
10561da177e4SLinus Torvalds } /* memory_ioctl */
10571da177e4SLinus Torvalds 
1058969e57adSArtem Bityutskiy static long mtdchar_unlocked_ioctl(struct file *file, u_int cmd, u_long arg)
105955929332SArnd Bergmann {
106055929332SArnd Bergmann 	int ret;
106155929332SArnd Bergmann 
10625aa82940SArnd Bergmann 	mutex_lock(&mtd_mutex);
1063969e57adSArtem Bityutskiy 	ret = mtdchar_ioctl(file, cmd, arg);
10645aa82940SArnd Bergmann 	mutex_unlock(&mtd_mutex);
106555929332SArnd Bergmann 
106655929332SArnd Bergmann 	return ret;
106755929332SArnd Bergmann }
106855929332SArnd Bergmann 
106997718540SKevin Cernekee #ifdef CONFIG_COMPAT
107097718540SKevin Cernekee 
107197718540SKevin Cernekee struct mtd_oob_buf32 {
107297718540SKevin Cernekee 	u_int32_t start;
107397718540SKevin Cernekee 	u_int32_t length;
107497718540SKevin Cernekee 	compat_caddr_t ptr;	/* unsigned char* */
107597718540SKevin Cernekee };
107697718540SKevin Cernekee 
107797718540SKevin Cernekee #define MEMWRITEOOB32		_IOWR('M', 3, struct mtd_oob_buf32)
107897718540SKevin Cernekee #define MEMREADOOB32		_IOWR('M', 4, struct mtd_oob_buf32)
107997718540SKevin Cernekee 
1080969e57adSArtem Bityutskiy static long mtdchar_compat_ioctl(struct file *file, unsigned int cmd,
108197718540SKevin Cernekee 	unsigned long arg)
108297718540SKevin Cernekee {
108397718540SKevin Cernekee 	struct mtd_file_info *mfi = file->private_data;
108497718540SKevin Cernekee 	struct mtd_info *mtd = mfi->mtd;
10850b6585ceSDavid Woodhouse 	void __user *argp = compat_ptr(arg);
108697718540SKevin Cernekee 	int ret = 0;
108797718540SKevin Cernekee 
10885aa82940SArnd Bergmann 	mutex_lock(&mtd_mutex);
108997718540SKevin Cernekee 
109097718540SKevin Cernekee 	switch (cmd) {
109197718540SKevin Cernekee 	case MEMWRITEOOB32:
109297718540SKevin Cernekee 	{
109397718540SKevin Cernekee 		struct mtd_oob_buf32 buf;
109497718540SKevin Cernekee 		struct mtd_oob_buf32 __user *buf_user = argp;
109597718540SKevin Cernekee 
109697718540SKevin Cernekee 		if (copy_from_user(&buf, argp, sizeof(buf)))
109797718540SKevin Cernekee 			ret = -EFAULT;
109897718540SKevin Cernekee 		else
1099969e57adSArtem Bityutskiy 			ret = mtdchar_writeoob(file, mtd, buf.start,
110097718540SKevin Cernekee 				buf.length, compat_ptr(buf.ptr),
110197718540SKevin Cernekee 				&buf_user->length);
110297718540SKevin Cernekee 		break;
110397718540SKevin Cernekee 	}
110497718540SKevin Cernekee 
110597718540SKevin Cernekee 	case MEMREADOOB32:
110697718540SKevin Cernekee 	{
110797718540SKevin Cernekee 		struct mtd_oob_buf32 buf;
110897718540SKevin Cernekee 		struct mtd_oob_buf32 __user *buf_user = argp;
110997718540SKevin Cernekee 
111097718540SKevin Cernekee 		/* NOTE: writes return length to buf->start */
111197718540SKevin Cernekee 		if (copy_from_user(&buf, argp, sizeof(buf)))
111297718540SKevin Cernekee 			ret = -EFAULT;
111397718540SKevin Cernekee 		else
1114969e57adSArtem Bityutskiy 			ret = mtdchar_readoob(file, mtd, buf.start,
111597718540SKevin Cernekee 				buf.length, compat_ptr(buf.ptr),
111697718540SKevin Cernekee 				&buf_user->start);
111797718540SKevin Cernekee 		break;
111897718540SKevin Cernekee 	}
111953bb724fSBrian Norris 
112053bb724fSBrian Norris 	case BLKPG:
112153bb724fSBrian Norris 	{
112253bb724fSBrian Norris 		/* Convert from blkpg_compat_ioctl_arg to blkpg_ioctl_arg */
112353bb724fSBrian Norris 		struct blkpg_compat_ioctl_arg __user *uarg = argp;
112453bb724fSBrian Norris 		struct blkpg_compat_ioctl_arg compat_arg;
112553bb724fSBrian Norris 		struct blkpg_ioctl_arg a;
112653bb724fSBrian Norris 
112753bb724fSBrian Norris 		if (copy_from_user(&compat_arg, uarg, sizeof(compat_arg))) {
112853bb724fSBrian Norris 			ret = -EFAULT;
112953bb724fSBrian Norris 			break;
113053bb724fSBrian Norris 		}
113153bb724fSBrian Norris 
113253bb724fSBrian Norris 		memset(&a, 0, sizeof(a));
113353bb724fSBrian Norris 		a.op = compat_arg.op;
113453bb724fSBrian Norris 		a.flags = compat_arg.flags;
113553bb724fSBrian Norris 		a.datalen = compat_arg.datalen;
113653bb724fSBrian Norris 		a.data = compat_ptr(compat_arg.data);
113753bb724fSBrian Norris 
113853bb724fSBrian Norris 		ret = mtdchar_blkpg_ioctl(mtd, &a);
113953bb724fSBrian Norris 		break;
114053bb724fSBrian Norris 	}
114153bb724fSBrian Norris 
114297718540SKevin Cernekee 	default:
1143969e57adSArtem Bityutskiy 		ret = mtdchar_ioctl(file, cmd, (unsigned long)argp);
114497718540SKevin Cernekee 	}
114597718540SKevin Cernekee 
11465aa82940SArnd Bergmann 	mutex_unlock(&mtd_mutex);
114797718540SKevin Cernekee 
114897718540SKevin Cernekee 	return ret;
114997718540SKevin Cernekee }
115097718540SKevin Cernekee 
115197718540SKevin Cernekee #endif /* CONFIG_COMPAT */
115297718540SKevin Cernekee 
1153402d3265SDavid Howells /*
1154402d3265SDavid Howells  * try to determine where a shared mapping can be made
1155402d3265SDavid Howells  * - only supported for NOMMU at the moment (MMU can't doesn't copy private
1156402d3265SDavid Howells  *   mappings)
1157402d3265SDavid Howells  */
1158402d3265SDavid Howells #ifndef CONFIG_MMU
1159969e57adSArtem Bityutskiy static unsigned long mtdchar_get_unmapped_area(struct file *file,
1160402d3265SDavid Howells 					   unsigned long addr,
1161402d3265SDavid Howells 					   unsigned long len,
1162402d3265SDavid Howells 					   unsigned long pgoff,
1163402d3265SDavid Howells 					   unsigned long flags)
1164402d3265SDavid Howells {
1165402d3265SDavid Howells 	struct mtd_file_info *mfi = file->private_data;
1166402d3265SDavid Howells 	struct mtd_info *mtd = mfi->mtd;
1167402d3265SDavid Howells 	unsigned long offset;
1168cd621274SArtem Bityutskiy 	int ret;
1169402d3265SDavid Howells 
1170402d3265SDavid Howells 	if (addr != 0)
1171402d3265SDavid Howells 		return (unsigned long) -EINVAL;
1172402d3265SDavid Howells 
1173402d3265SDavid Howells 	if (len > mtd->size || pgoff >= (mtd->size >> PAGE_SHIFT))
1174402d3265SDavid Howells 		return (unsigned long) -EINVAL;
1175402d3265SDavid Howells 
1176402d3265SDavid Howells 	offset = pgoff << PAGE_SHIFT;
1177402d3265SDavid Howells 	if (offset > mtd->size - len)
1178402d3265SDavid Howells 		return (unsigned long) -EINVAL;
1179402d3265SDavid Howells 
1180cd621274SArtem Bityutskiy 	ret = mtd_get_unmapped_area(mtd, len, offset, flags);
1181b9995932SVladimir Zapolskiy 	return ret == -EOPNOTSUPP ? -ENODEV : ret;
1182402d3265SDavid Howells }
1183b4caecd4SChristoph Hellwig 
1184b4caecd4SChristoph Hellwig static unsigned mtdchar_mmap_capabilities(struct file *file)
1185b4caecd4SChristoph Hellwig {
1186b4caecd4SChristoph Hellwig 	struct mtd_file_info *mfi = file->private_data;
1187b4caecd4SChristoph Hellwig 
1188b4caecd4SChristoph Hellwig 	return mtd_mmap_capabilities(mfi->mtd);
1189b4caecd4SChristoph Hellwig }
1190402d3265SDavid Howells #endif
1191402d3265SDavid Howells 
1192402d3265SDavid Howells /*
1193402d3265SDavid Howells  * set up a mapping for shared memory segments
1194402d3265SDavid Howells  */
1195969e57adSArtem Bityutskiy static int mtdchar_mmap(struct file *file, struct vm_area_struct *vma)
1196402d3265SDavid Howells {
1197402d3265SDavid Howells #ifdef CONFIG_MMU
1198402d3265SDavid Howells 	struct mtd_file_info *mfi = file->private_data;
1199402d3265SDavid Howells 	struct mtd_info *mtd = mfi->mtd;
1200dd02b67dSAnatolij Gustschin 	struct map_info *map = mtd->priv;
1201402d3265SDavid Howells 
1202f5cf8f07SDavid Woodhouse         /* This is broken because it assumes the MTD device is map-based
1203f5cf8f07SDavid Woodhouse 	   and that mtd->priv is a valid struct map_info.  It should be
1204f5cf8f07SDavid Woodhouse 	   replaced with something that uses the mtd_get_unmapped_area()
1205f5cf8f07SDavid Woodhouse 	   operation properly. */
1206f5cf8f07SDavid Woodhouse 	if (0 /*mtd->type == MTD_RAM || mtd->type == MTD_ROM*/) {
1207dd02b67dSAnatolij Gustschin #ifdef pgprot_noncached
12088558e4a2SLinus Torvalds 		if (file->f_flags & O_DSYNC || map->phys >= __pa(high_memory))
1209dd02b67dSAnatolij Gustschin 			vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
1210dd02b67dSAnatolij Gustschin #endif
12118558e4a2SLinus Torvalds 		return vm_iomap_memory(vma, map->phys, map->size);
1212dd02b67dSAnatolij Gustschin 	}
1213b9995932SVladimir Zapolskiy 	return -ENODEV;
1214402d3265SDavid Howells #else
1215b9995932SVladimir Zapolskiy 	return vma->vm_flags & VM_SHARED ? 0 : -EACCES;
1216402d3265SDavid Howells #endif
1217402d3265SDavid Howells }
1218402d3265SDavid Howells 
1219d54b1fdbSArjan van de Ven static const struct file_operations mtd_fops = {
12201da177e4SLinus Torvalds 	.owner		= THIS_MODULE,
1221969e57adSArtem Bityutskiy 	.llseek		= mtdchar_lseek,
1222969e57adSArtem Bityutskiy 	.read		= mtdchar_read,
1223969e57adSArtem Bityutskiy 	.write		= mtdchar_write,
1224969e57adSArtem Bityutskiy 	.unlocked_ioctl	= mtdchar_unlocked_ioctl,
122597718540SKevin Cernekee #ifdef CONFIG_COMPAT
1226969e57adSArtem Bityutskiy 	.compat_ioctl	= mtdchar_compat_ioctl,
122797718540SKevin Cernekee #endif
1228969e57adSArtem Bityutskiy 	.open		= mtdchar_open,
1229969e57adSArtem Bityutskiy 	.release	= mtdchar_close,
1230969e57adSArtem Bityutskiy 	.mmap		= mtdchar_mmap,
1231402d3265SDavid Howells #ifndef CONFIG_MMU
1232969e57adSArtem Bityutskiy 	.get_unmapped_area = mtdchar_get_unmapped_area,
1233b4caecd4SChristoph Hellwig 	.mmap_capabilities = mtdchar_mmap_capabilities,
1234402d3265SDavid Howells #endif
12351da177e4SLinus Torvalds };
12361da177e4SLinus Torvalds 
1237660685d9SArtem Bityutskiy int __init init_mtdchar(void)
1238cd874237SKirill A. Shutemov {
1239cd874237SKirill A. Shutemov 	int ret;
1240cd874237SKirill A. Shutemov 
1241cd874237SKirill A. Shutemov 	ret = __register_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS,
1242cd874237SKirill A. Shutemov 				   "mtd", &mtd_fops);
1243cd874237SKirill A. Shutemov 	if (ret < 0) {
124457ae2b60SArtem Bityutskiy 		pr_err("Can't allocate major number %d for MTD\n",
124557ae2b60SArtem Bityutskiy 		       MTD_CHAR_MAJOR);
1246cd874237SKirill A. Shutemov 		return ret;
1247cd874237SKirill A. Shutemov 	}
1248cd874237SKirill A. Shutemov 
1249cd874237SKirill A. Shutemov 	return ret;
12501da177e4SLinus Torvalds }
12511da177e4SLinus Torvalds 
1252660685d9SArtem Bityutskiy void __exit cleanup_mtdchar(void)
12531da177e4SLinus Torvalds {
1254dad0db31SBen Hutchings 	__unregister_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS, "mtd");
12551da177e4SLinus Torvalds }
12561da177e4SLinus Torvalds 
125790160e13SScott James Remnant MODULE_ALIAS_CHARDEV_MAJOR(MTD_CHAR_MAJOR);
1258