xref: /openbmc/linux/drivers/mtd/mtdchar.c (revision 0ea923f4)
1fd534e9bSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
21da177e4SLinus Torvalds /*
3a1452a37SDavid Woodhouse  * Copyright © 1999-2010 David Woodhouse <dwmw2@infradead.org>
41da177e4SLinus Torvalds  */
51da177e4SLinus Torvalds 
615fdc52fSThomas Gleixner #include <linux/device.h>
715fdc52fSThomas Gleixner #include <linux/fs.h>
80c1eafdbSAndrew Morton #include <linux/mm.h>
99c74034fSArtem Bityutskiy #include <linux/err.h>
1015fdc52fSThomas Gleixner #include <linux/init.h>
111da177e4SLinus Torvalds #include <linux/kernel.h>
121da177e4SLinus Torvalds #include <linux/module.h>
1315fdc52fSThomas Gleixner #include <linux/slab.h>
1415fdc52fSThomas Gleixner #include <linux/sched.h>
155aa82940SArnd Bergmann #include <linux/mutex.h>
16402d3265SDavid Howells #include <linux/backing-dev.h>
1797718540SKevin Cernekee #include <linux/compat.h>
18cd874237SKirill A. Shutemov #include <linux/mount.h>
19d0f7959eSRoman Tereshonkov #include <linux/blkpg.h>
20b502bd11SMuthu Kumar #include <linux/magic.h>
21f83c3838SEzequiel Garcia #include <linux/major.h>
221da177e4SLinus Torvalds #include <linux/mtd/mtd.h>
23d0f7959eSRoman Tereshonkov #include <linux/mtd/partitions.h>
24dd02b67dSAnatolij Gustschin #include <linux/mtd/map.h>
251da177e4SLinus Torvalds 
267c0f6ba6SLinus Torvalds #include <linux/uaccess.h>
279bc7b387STodd Poynor 
28660685d9SArtem Bityutskiy #include "mtdcore.h"
29660685d9SArtem Bityutskiy 
30045e9a5dSNicolas Pitre /*
31f1a28c02SThomas Gleixner  * Data structure to hold the pointer to the mtd device as well
3292394b5cSBrian Norris  * as mode information of various use cases.
33045e9a5dSNicolas Pitre  */
34f1a28c02SThomas Gleixner struct mtd_file_info {
35f1a28c02SThomas Gleixner 	struct mtd_info *mtd;
36f1a28c02SThomas Gleixner 	enum mtd_file_modes mode;
37f1a28c02SThomas Gleixner };
3831f4233bSNicolas Pitre 
mtdchar_lseek(struct file * file,loff_t offset,int orig)39969e57adSArtem Bityutskiy static loff_t mtdchar_lseek(struct file *file, loff_t offset, int orig)
401da177e4SLinus Torvalds {
41f1a28c02SThomas Gleixner 	struct mtd_file_info *mfi = file->private_data;
42b959957fSAl Viro 	return fixed_size_llseek(file, offset, orig, mfi->mtd->size);
431da177e4SLinus Torvalds }
441da177e4SLinus Torvalds 
mtdchar_open(struct inode * inode,struct file * file)45969e57adSArtem Bityutskiy static int mtdchar_open(struct inode *inode, struct file *file)
461da177e4SLinus Torvalds {
471da177e4SLinus Torvalds 	int minor = iminor(inode);
481da177e4SLinus Torvalds 	int devnum = minor >> 1;
496071239eSJonathan Corbet 	int ret = 0;
501da177e4SLinus Torvalds 	struct mtd_info *mtd;
51f1a28c02SThomas Gleixner 	struct mtd_file_info *mfi;
521da177e4SLinus Torvalds 
53289c0522SBrian Norris 	pr_debug("MTD_open\n");
541da177e4SLinus Torvalds 
551da177e4SLinus Torvalds 	/* You can't open the RO devices RW */
56aeb5d727SAl Viro 	if ((file->f_mode & FMODE_WRITE) && (minor & 1))
571da177e4SLinus Torvalds 		return -EACCES;
581da177e4SLinus Torvalds 
591da177e4SLinus Torvalds 	mtd = get_mtd_device(NULL, devnum);
601da177e4SLinus Torvalds 
61ecd400ceSAlexander Sverdlin 	if (IS_ERR(mtd))
62ecd400ceSAlexander Sverdlin 		return PTR_ERR(mtd);
631da177e4SLinus Torvalds 
64402d3265SDavid Howells 	if (mtd->type == MTD_ABSENT) {
656071239eSJonathan Corbet 		ret = -ENODEV;
66c65390f4SAl Viro 		goto out1;
671da177e4SLinus Torvalds 	}
681da177e4SLinus Torvalds 
691da177e4SLinus Torvalds 	/* You can't open it RW if it's not a writeable device */
70aeb5d727SAl Viro 	if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) {
716071239eSJonathan Corbet 		ret = -EACCES;
72b4caecd4SChristoph Hellwig 		goto out1;
731da177e4SLinus Torvalds 	}
741da177e4SLinus Torvalds 
75f1a28c02SThomas Gleixner 	mfi = kzalloc(sizeof(*mfi), GFP_KERNEL);
76f1a28c02SThomas Gleixner 	if (!mfi) {
776071239eSJonathan Corbet 		ret = -ENOMEM;
78b4caecd4SChristoph Hellwig 		goto out1;
79f1a28c02SThomas Gleixner 	}
80f1a28c02SThomas Gleixner 	mfi->mtd = mtd;
81f1a28c02SThomas Gleixner 	file->private_data = mfi;
82c65390f4SAl Viro 	return 0;
83f1a28c02SThomas Gleixner 
84c65390f4SAl Viro out1:
85c65390f4SAl Viro 	put_mtd_device(mtd);
866071239eSJonathan Corbet 	return ret;
87969e57adSArtem Bityutskiy } /* mtdchar_open */
881da177e4SLinus Torvalds 
891da177e4SLinus Torvalds /*====================================================================*/
901da177e4SLinus Torvalds 
mtdchar_close(struct inode * inode,struct file * file)91969e57adSArtem Bityutskiy static int mtdchar_close(struct inode *inode, struct file *file)
921da177e4SLinus Torvalds {
93f1a28c02SThomas Gleixner 	struct mtd_file_info *mfi = file->private_data;
94f1a28c02SThomas Gleixner 	struct mtd_info *mtd = mfi->mtd;
951da177e4SLinus Torvalds 
96289c0522SBrian Norris 	pr_debug("MTD_close\n");
971da177e4SLinus Torvalds 
987eafaed5SJoakim Tjernlund 	/* Only sync if opened RW */
99327cf292SArtem Bityutskiy 	if ((file->f_mode & FMODE_WRITE))
10085f2f2a8SArtem Bityutskiy 		mtd_sync(mtd);
1011da177e4SLinus Torvalds 
1021da177e4SLinus Torvalds 	put_mtd_device(mtd);
103f1a28c02SThomas Gleixner 	file->private_data = NULL;
104f1a28c02SThomas Gleixner 	kfree(mfi);
1051da177e4SLinus Torvalds 
1061da177e4SLinus Torvalds 	return 0;
107969e57adSArtem Bityutskiy } /* mtdchar_close */
1081da177e4SLinus Torvalds 
1093e45cf5eSGrant Erickson /* Back in June 2001, dwmw2 wrote:
1103e45cf5eSGrant Erickson  *
1113e45cf5eSGrant Erickson  *   FIXME: This _really_ needs to die. In 2.5, we should lock the
1123e45cf5eSGrant Erickson  *   userspace buffer down and use it directly with readv/writev.
1133e45cf5eSGrant Erickson  *
1143e45cf5eSGrant Erickson  * The implementation below, using mtd_kmalloc_up_to, mitigates
1153e45cf5eSGrant Erickson  * allocation failures when the system is under low-memory situations
1163e45cf5eSGrant Erickson  * or if memory is highly fragmented at the cost of reducing the
1173e45cf5eSGrant Erickson  * performance of the requested transfer due to a smaller buffer size.
1183e45cf5eSGrant Erickson  *
1193e45cf5eSGrant Erickson  * A more complex but more memory-efficient implementation based on
1203e45cf5eSGrant Erickson  * get_user_pages and iovecs to cover extents of those pages is a
1213e45cf5eSGrant Erickson  * longer-term goal, as intimated by dwmw2 above. However, for the
1223e45cf5eSGrant Erickson  * write case, this requires yet more complex head and tail transfer
1233e45cf5eSGrant Erickson  * handling when those head and tail offsets and sizes are such that
1243e45cf5eSGrant Erickson  * alignment requirements are not met in the NAND subdriver.
1251da177e4SLinus Torvalds  */
1261da177e4SLinus Torvalds 
mtdchar_read(struct file * file,char __user * buf,size_t count,loff_t * ppos)127969e57adSArtem Bityutskiy static ssize_t mtdchar_read(struct file *file, char __user *buf, size_t count,
128969e57adSArtem Bityutskiy 			loff_t *ppos)
1291da177e4SLinus Torvalds {
130f1a28c02SThomas Gleixner 	struct mtd_file_info *mfi = file->private_data;
131f1a28c02SThomas Gleixner 	struct mtd_info *mtd = mfi->mtd;
13230fa9848SArtem Bityutskiy 	size_t retlen;
1331da177e4SLinus Torvalds 	size_t total_retlen=0;
1341da177e4SLinus Torvalds 	int ret=0;
1351da177e4SLinus Torvalds 	int len;
1363e45cf5eSGrant Erickson 	size_t size = count;
1371da177e4SLinus Torvalds 	char *kbuf;
1381da177e4SLinus Torvalds 
139289c0522SBrian Norris 	pr_debug("MTD_read\n");
1401da177e4SLinus Torvalds 
1416c6bc9eaSJann Horn 	if (*ppos + count > mtd->size) {
1426c6bc9eaSJann Horn 		if (*ppos < mtd->size)
1431da177e4SLinus Torvalds 			count = mtd->size - *ppos;
1446c6bc9eaSJann Horn 		else
1456c6bc9eaSJann Horn 			count = 0;
1466c6bc9eaSJann Horn 	}
1471da177e4SLinus Torvalds 
1481da177e4SLinus Torvalds 	if (!count)
1491da177e4SLinus Torvalds 		return 0;
1501da177e4SLinus Torvalds 
1513e45cf5eSGrant Erickson 	kbuf = mtd_kmalloc_up_to(mtd, &size);
152b802c074SThago Galesi 	if (!kbuf)
153b802c074SThago Galesi 		return -ENOMEM;
154b802c074SThago Galesi 
1551da177e4SLinus Torvalds 	while (count) {
1563e45cf5eSGrant Erickson 		len = min_t(size_t, count, size);
1571da177e4SLinus Torvalds 
158f1a28c02SThomas Gleixner 		switch (mfi->mode) {
159beb133fcSBrian Norris 		case MTD_FILE_MODE_OTP_FACTORY:
160d264f72aSArtem Bityutskiy 			ret = mtd_read_fact_prot_reg(mtd, *ppos, len,
161d264f72aSArtem Bityutskiy 						     &retlen, kbuf);
16231f4233bSNicolas Pitre 			break;
163beb133fcSBrian Norris 		case MTD_FILE_MODE_OTP_USER:
1644ea1cabbSArtem Bityutskiy 			ret = mtd_read_user_prot_reg(mtd, *ppos, len,
1654ea1cabbSArtem Bityutskiy 						     &retlen, kbuf);
16631f4233bSNicolas Pitre 			break;
167beb133fcSBrian Norris 		case MTD_FILE_MODE_RAW:
168f1a28c02SThomas Gleixner 		{
169717bc8a6SMiquel Raynal 			struct mtd_oob_ops ops = {};
170f1a28c02SThomas Gleixner 
1710612b9ddSBrian Norris 			ops.mode = MTD_OPS_RAW;
172f1a28c02SThomas Gleixner 			ops.datbuf = kbuf;
173f1a28c02SThomas Gleixner 			ops.oobbuf = NULL;
174f1a28c02SThomas Gleixner 			ops.len = len;
175f1a28c02SThomas Gleixner 
176fd2819bbSArtem Bityutskiy 			ret = mtd_read_oob(mtd, *ppos, &ops);
177f1a28c02SThomas Gleixner 			retlen = ops.retlen;
178f1a28c02SThomas Gleixner 			break;
179f1a28c02SThomas Gleixner 		}
18031f4233bSNicolas Pitre 		default:
181329ad399SArtem Bityutskiy 			ret = mtd_read(mtd, *ppos, len, &retlen, kbuf);
18231f4233bSNicolas Pitre 		}
1837854d3f7SBrian Norris 		/* Nand returns -EBADMSG on ECC errors, but it returns
1841da177e4SLinus Torvalds 		 * the data. For our userspace tools it is important
1857854d3f7SBrian Norris 		 * to dump areas with ECC errors!
1869a1fcdfdSThomas Gleixner 		 * For kernel internal usage it also might return -EUCLEAN
18725985edcSLucas De Marchi 		 * to signal the caller that a bitflip has occurred and has
1889a1fcdfdSThomas Gleixner 		 * been corrected by the ECC algorithm.
1891da177e4SLinus Torvalds 		 * Userspace software which accesses NAND this way
1901da177e4SLinus Torvalds 		 * must be aware of the fact that it deals with NAND
1911da177e4SLinus Torvalds 		 */
192d57f4054SBrian Norris 		if (!ret || mtd_is_bitflip_or_eccerr(ret)) {
1931da177e4SLinus Torvalds 			*ppos += retlen;
1941da177e4SLinus Torvalds 			if (copy_to_user(buf, kbuf, retlen)) {
1951da177e4SLinus Torvalds 				kfree(kbuf);
1961da177e4SLinus Torvalds 				return -EFAULT;
1971da177e4SLinus Torvalds 			}
1981da177e4SLinus Torvalds 			else
1991da177e4SLinus Torvalds 				total_retlen += retlen;
2001da177e4SLinus Torvalds 
2011da177e4SLinus Torvalds 			count -= retlen;
2021da177e4SLinus Torvalds 			buf += retlen;
20331f4233bSNicolas Pitre 			if (retlen == 0)
20431f4233bSNicolas Pitre 				count = 0;
2051da177e4SLinus Torvalds 		}
2061da177e4SLinus Torvalds 		else {
2071da177e4SLinus Torvalds 			kfree(kbuf);
2081da177e4SLinus Torvalds 			return ret;
2091da177e4SLinus Torvalds 		}
2101da177e4SLinus Torvalds 
2111da177e4SLinus Torvalds 	}
2121da177e4SLinus Torvalds 
213b802c074SThago Galesi 	kfree(kbuf);
2141da177e4SLinus Torvalds 	return total_retlen;
215969e57adSArtem Bityutskiy } /* mtdchar_read */
2161da177e4SLinus Torvalds 
mtdchar_write(struct file * file,const char __user * buf,size_t count,loff_t * ppos)217969e57adSArtem Bityutskiy static ssize_t mtdchar_write(struct file *file, const char __user *buf, size_t count,
218969e57adSArtem Bityutskiy 			loff_t *ppos)
2191da177e4SLinus Torvalds {
220f1a28c02SThomas Gleixner 	struct mtd_file_info *mfi = file->private_data;
221f1a28c02SThomas Gleixner 	struct mtd_info *mtd = mfi->mtd;
2223e45cf5eSGrant Erickson 	size_t size = count;
2231da177e4SLinus Torvalds 	char *kbuf;
2241da177e4SLinus Torvalds 	size_t retlen;
2251da177e4SLinus Torvalds 	size_t total_retlen=0;
2261da177e4SLinus Torvalds 	int ret=0;
2271da177e4SLinus Torvalds 	int len;
2281da177e4SLinus Torvalds 
229289c0522SBrian Norris 	pr_debug("MTD_write\n");
2301da177e4SLinus Torvalds 
2316c6bc9eaSJann Horn 	if (*ppos >= mtd->size)
2321da177e4SLinus Torvalds 		return -ENOSPC;
2331da177e4SLinus Torvalds 
2341da177e4SLinus Torvalds 	if (*ppos + count > mtd->size)
2351da177e4SLinus Torvalds 		count = mtd->size - *ppos;
2361da177e4SLinus Torvalds 
2371da177e4SLinus Torvalds 	if (!count)
2381da177e4SLinus Torvalds 		return 0;
2391da177e4SLinus Torvalds 
2403e45cf5eSGrant Erickson 	kbuf = mtd_kmalloc_up_to(mtd, &size);
241b802c074SThago Galesi 	if (!kbuf)
242b802c074SThago Galesi 		return -ENOMEM;
243b802c074SThago Galesi 
2441da177e4SLinus Torvalds 	while (count) {
2453e45cf5eSGrant Erickson 		len = min_t(size_t, count, size);
2461da177e4SLinus Torvalds 
2471da177e4SLinus Torvalds 		if (copy_from_user(kbuf, buf, len)) {
2481da177e4SLinus Torvalds 			kfree(kbuf);
2491da177e4SLinus Torvalds 			return -EFAULT;
2501da177e4SLinus Torvalds 		}
2511da177e4SLinus Torvalds 
252f1a28c02SThomas Gleixner 		switch (mfi->mode) {
253beb133fcSBrian Norris 		case MTD_FILE_MODE_OTP_FACTORY:
25431f4233bSNicolas Pitre 			ret = -EROFS;
25531f4233bSNicolas Pitre 			break;
256beb133fcSBrian Norris 		case MTD_FILE_MODE_OTP_USER:
257482b43adSArtem Bityutskiy 			ret = mtd_write_user_prot_reg(mtd, *ppos, len,
258482b43adSArtem Bityutskiy 						      &retlen, kbuf);
25931f4233bSNicolas Pitre 			break;
260f1a28c02SThomas Gleixner 
261beb133fcSBrian Norris 		case MTD_FILE_MODE_RAW:
262f1a28c02SThomas Gleixner 		{
263717bc8a6SMiquel Raynal 			struct mtd_oob_ops ops = {};
264f1a28c02SThomas Gleixner 
2650612b9ddSBrian Norris 			ops.mode = MTD_OPS_RAW;
266f1a28c02SThomas Gleixner 			ops.datbuf = kbuf;
267f1a28c02SThomas Gleixner 			ops.oobbuf = NULL;
268bf514081SPeter Wippich 			ops.ooboffs = 0;
269f1a28c02SThomas Gleixner 			ops.len = len;
270f1a28c02SThomas Gleixner 
271a2cc5ba0SArtem Bityutskiy 			ret = mtd_write_oob(mtd, *ppos, &ops);
272f1a28c02SThomas Gleixner 			retlen = ops.retlen;
273f1a28c02SThomas Gleixner 			break;
274f1a28c02SThomas Gleixner 		}
275f1a28c02SThomas Gleixner 
27631f4233bSNicolas Pitre 		default:
277eda95cbfSArtem Bityutskiy 			ret = mtd_write(mtd, *ppos, len, &retlen, kbuf);
27831f4233bSNicolas Pitre 		}
2799a78bc83SChristian Riesch 
2809a78bc83SChristian Riesch 		/*
2819a78bc83SChristian Riesch 		 * Return -ENOSPC only if no data could be written at all.
2829a78bc83SChristian Riesch 		 * Otherwise just return the number of bytes that actually
2839a78bc83SChristian Riesch 		 * have been written.
2849a78bc83SChristian Riesch 		 */
2859a78bc83SChristian Riesch 		if ((ret == -ENOSPC) && (total_retlen))
2869a78bc83SChristian Riesch 			break;
2879a78bc83SChristian Riesch 
2881da177e4SLinus Torvalds 		if (!ret) {
2891da177e4SLinus Torvalds 			*ppos += retlen;
2901da177e4SLinus Torvalds 			total_retlen += retlen;
2911da177e4SLinus Torvalds 			count -= retlen;
2921da177e4SLinus Torvalds 			buf += retlen;
2931da177e4SLinus Torvalds 		}
2941da177e4SLinus Torvalds 		else {
2951da177e4SLinus Torvalds 			kfree(kbuf);
2961da177e4SLinus Torvalds 			return ret;
2971da177e4SLinus Torvalds 		}
2981da177e4SLinus Torvalds 	}
2991da177e4SLinus Torvalds 
300b802c074SThago Galesi 	kfree(kbuf);
3011da177e4SLinus Torvalds 	return total_retlen;
302969e57adSArtem Bityutskiy } /* mtdchar_write */
3031da177e4SLinus Torvalds 
3041da177e4SLinus Torvalds /*======================================================================
3051da177e4SLinus Torvalds 
3061da177e4SLinus Torvalds     IOCTL calls for getting device parameters.
3071da177e4SLinus Torvalds 
3081da177e4SLinus Torvalds ======================================================================*/
3091da177e4SLinus Torvalds 
otp_select_filemode(struct mtd_file_info * mfi,int mode)310f1a28c02SThomas Gleixner static int otp_select_filemode(struct mtd_file_info *mfi, int mode)
311f1a28c02SThomas Gleixner {
312f1a28c02SThomas Gleixner 	struct mtd_info *mtd = mfi->mtd;
313b6de3d6cSArtem Bityutskiy 	size_t retlen;
314b6de3d6cSArtem Bityutskiy 
315f1a28c02SThomas Gleixner 	switch (mode) {
316f1a28c02SThomas Gleixner 	case MTD_OTP_FACTORY:
3175dc63fa2SUwe Kleine-König 		if (mtd_read_fact_prot_reg(mtd, -1, 0, &retlen, NULL) ==
3185dc63fa2SUwe Kleine-König 				-EOPNOTSUPP)
3195dc63fa2SUwe Kleine-König 			return -EOPNOTSUPP;
3205dc63fa2SUwe Kleine-König 
321beb133fcSBrian Norris 		mfi->mode = MTD_FILE_MODE_OTP_FACTORY;
322f1a28c02SThomas Gleixner 		break;
323f1a28c02SThomas Gleixner 	case MTD_OTP_USER:
3245dc63fa2SUwe Kleine-König 		if (mtd_read_user_prot_reg(mtd, -1, 0, &retlen, NULL) ==
3255dc63fa2SUwe Kleine-König 				-EOPNOTSUPP)
3265dc63fa2SUwe Kleine-König 			return -EOPNOTSUPP;
3275dc63fa2SUwe Kleine-König 
328beb133fcSBrian Norris 		mfi->mode = MTD_FILE_MODE_OTP_USER;
329f1a28c02SThomas Gleixner 		break;
330f1a28c02SThomas Gleixner 	case MTD_OTP_OFF:
3315dc63fa2SUwe Kleine-König 		mfi->mode = MTD_FILE_MODE_NORMAL;
332f1a28c02SThomas Gleixner 		break;
3335dc63fa2SUwe Kleine-König 	default:
3345dc63fa2SUwe Kleine-König 		return -EINVAL;
335f1a28c02SThomas Gleixner 	}
3365dc63fa2SUwe Kleine-König 
3375dc63fa2SUwe Kleine-König 	return 0;
338f1a28c02SThomas Gleixner }
339f1a28c02SThomas Gleixner 
mtdchar_writeoob(struct file * file,struct mtd_info * mtd,uint64_t start,uint32_t length,void __user * ptr,uint32_t __user * retp)340969e57adSArtem Bityutskiy static int mtdchar_writeoob(struct file *file, struct mtd_info *mtd,
34197718540SKevin Cernekee 	uint64_t start, uint32_t length, void __user *ptr,
34297718540SKevin Cernekee 	uint32_t __user *retp)
34397718540SKevin Cernekee {
34446b5889cSMiquel Raynal 	struct mtd_info *master  = mtd_get_master(mtd);
3459ce244b3SBrian Norris 	struct mtd_file_info *mfi = file->private_data;
346717bc8a6SMiquel Raynal 	struct mtd_oob_ops ops = {};
34797718540SKevin Cernekee 	uint32_t retlen;
34897718540SKevin Cernekee 	int ret = 0;
34997718540SKevin Cernekee 
35097718540SKevin Cernekee 	if (length > 4096)
35197718540SKevin Cernekee 		return -EINVAL;
35297718540SKevin Cernekee 
35346b5889cSMiquel Raynal 	if (!master->_write_oob)
3540db188f9SAl Viro 		return -EOPNOTSUPP;
35597718540SKevin Cernekee 
35697718540SKevin Cernekee 	ops.ooblen = length;
357305b93f1SBrian Norris 	ops.ooboffs = start & (mtd->writesize - 1);
35897718540SKevin Cernekee 	ops.datbuf = NULL;
359beb133fcSBrian Norris 	ops.mode = (mfi->mode == MTD_FILE_MODE_RAW) ? MTD_OPS_RAW :
3600612b9ddSBrian Norris 		MTD_OPS_PLACE_OOB;
36197718540SKevin Cernekee 
36297718540SKevin Cernekee 	if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs))
36397718540SKevin Cernekee 		return -EINVAL;
36497718540SKevin Cernekee 
365df1f1d1cSJulia Lawall 	ops.oobbuf = memdup_user(ptr, length);
366df1f1d1cSJulia Lawall 	if (IS_ERR(ops.oobbuf))
367df1f1d1cSJulia Lawall 		return PTR_ERR(ops.oobbuf);
36897718540SKevin Cernekee 
369305b93f1SBrian Norris 	start &= ~((uint64_t)mtd->writesize - 1);
370a2cc5ba0SArtem Bityutskiy 	ret = mtd_write_oob(mtd, start, &ops);
37197718540SKevin Cernekee 
37297718540SKevin Cernekee 	if (ops.oobretlen > 0xFFFFFFFFU)
37397718540SKevin Cernekee 		ret = -EOVERFLOW;
37497718540SKevin Cernekee 	retlen = ops.oobretlen;
37597718540SKevin Cernekee 	if (copy_to_user(retp, &retlen, sizeof(length)))
37697718540SKevin Cernekee 		ret = -EFAULT;
37797718540SKevin Cernekee 
37897718540SKevin Cernekee 	kfree(ops.oobbuf);
37997718540SKevin Cernekee 	return ret;
38097718540SKevin Cernekee }
38197718540SKevin Cernekee 
mtdchar_readoob(struct file * file,struct mtd_info * mtd,uint64_t start,uint32_t length,void __user * ptr,uint32_t __user * retp)382969e57adSArtem Bityutskiy static int mtdchar_readoob(struct file *file, struct mtd_info *mtd,
383c46f6483SBrian Norris 	uint64_t start, uint32_t length, void __user *ptr,
384c46f6483SBrian Norris 	uint32_t __user *retp)
38597718540SKevin Cernekee {
386c46f6483SBrian Norris 	struct mtd_file_info *mfi = file->private_data;
387717bc8a6SMiquel Raynal 	struct mtd_oob_ops ops = {};
38897718540SKevin Cernekee 	int ret = 0;
38997718540SKevin Cernekee 
39097718540SKevin Cernekee 	if (length > 4096)
39197718540SKevin Cernekee 		return -EINVAL;
39297718540SKevin Cernekee 
39397718540SKevin Cernekee 	ops.ooblen = length;
394305b93f1SBrian Norris 	ops.ooboffs = start & (mtd->writesize - 1);
39597718540SKevin Cernekee 	ops.datbuf = NULL;
396beb133fcSBrian Norris 	ops.mode = (mfi->mode == MTD_FILE_MODE_RAW) ? MTD_OPS_RAW :
3970612b9ddSBrian Norris 		MTD_OPS_PLACE_OOB;
39897718540SKevin Cernekee 
39997718540SKevin Cernekee 	if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs))
40097718540SKevin Cernekee 		return -EINVAL;
40197718540SKevin Cernekee 
40297718540SKevin Cernekee 	ops.oobbuf = kmalloc(length, GFP_KERNEL);
40397718540SKevin Cernekee 	if (!ops.oobbuf)
40497718540SKevin Cernekee 		return -ENOMEM;
40597718540SKevin Cernekee 
406305b93f1SBrian Norris 	start &= ~((uint64_t)mtd->writesize - 1);
407fd2819bbSArtem Bityutskiy 	ret = mtd_read_oob(mtd, start, &ops);
40897718540SKevin Cernekee 
40997718540SKevin Cernekee 	if (put_user(ops.oobretlen, retp))
41097718540SKevin Cernekee 		ret = -EFAULT;
41197718540SKevin Cernekee 	else if (ops.oobretlen && copy_to_user(ptr, ops.oobbuf,
41297718540SKevin Cernekee 					    ops.oobretlen))
41397718540SKevin Cernekee 		ret = -EFAULT;
41497718540SKevin Cernekee 
41597718540SKevin Cernekee 	kfree(ops.oobbuf);
416041e4575SBrian Norris 
417041e4575SBrian Norris 	/*
418041e4575SBrian Norris 	 * NAND returns -EBADMSG on ECC errors, but it returns the OOB
419041e4575SBrian Norris 	 * data. For our userspace tools it is important to dump areas
420041e4575SBrian Norris 	 * with ECC errors!
421041e4575SBrian Norris 	 * For kernel internal usage it also might return -EUCLEAN
4225d708eccSNobuhiro Iwamatsu 	 * to signal the caller that a bitflip has occurred and has
423041e4575SBrian Norris 	 * been corrected by the ECC algorithm.
424041e4575SBrian Norris 	 *
425c478d7e4SBrian Norris 	 * Note: currently the standard NAND function, nand_read_oob_std,
426c478d7e4SBrian Norris 	 * does not calculate ECC for the OOB area, so do not rely on
427c478d7e4SBrian Norris 	 * this behavior unless you have replaced it with your own.
428041e4575SBrian Norris 	 */
429d57f4054SBrian Norris 	if (mtd_is_bitflip_or_eccerr(ret))
430041e4575SBrian Norris 		return 0;
431041e4575SBrian Norris 
43297718540SKevin Cernekee 	return ret;
43397718540SKevin Cernekee }
43497718540SKevin Cernekee 
435cc26c3cdSBrian Norris /*
436aab616e3SBoris Brezillon  * Copies (and truncates, if necessary) OOB layout information to the
437aab616e3SBoris Brezillon  * deprecated layout struct, nand_ecclayout_user. This is necessary only to
438aab616e3SBoris Brezillon  * support the deprecated API ioctl ECCGETLAYOUT while allowing all new
439aab616e3SBoris Brezillon  * functionality to use mtd_ooblayout_ops flexibly (i.e. mtd_ooblayout_ops
440aab616e3SBoris Brezillon  * can describe any kind of OOB layout with almost zero overhead from a
441aab616e3SBoris Brezillon  * memory usage point of view).
442cc26c3cdSBrian Norris  */
shrink_ecclayout(struct mtd_info * mtd,struct nand_ecclayout_user * to)443c2b78452SBoris Brezillon static int shrink_ecclayout(struct mtd_info *mtd,
444cc26c3cdSBrian Norris 			    struct nand_ecclayout_user *to)
445cc26c3cdSBrian Norris {
446c2b78452SBoris Brezillon 	struct mtd_oob_region oobregion;
447c2b78452SBoris Brezillon 	int i, section = 0, ret;
448cc26c3cdSBrian Norris 
449c2b78452SBoris Brezillon 	if (!mtd || !to)
450cc26c3cdSBrian Norris 		return -EINVAL;
451cc26c3cdSBrian Norris 
452cc26c3cdSBrian Norris 	memset(to, 0, sizeof(*to));
453cc26c3cdSBrian Norris 
454c2b78452SBoris Brezillon 	to->eccbytes = 0;
455c2b78452SBoris Brezillon 	for (i = 0; i < MTD_MAX_ECCPOS_ENTRIES;) {
456c2b78452SBoris Brezillon 		u32 eccpos;
457c2b78452SBoris Brezillon 
4586de56493SOuYang ZhiZhong 		ret = mtd_ooblayout_ecc(mtd, section++, &oobregion);
459c2b78452SBoris Brezillon 		if (ret < 0) {
460c2b78452SBoris Brezillon 			if (ret != -ERANGE)
461c2b78452SBoris Brezillon 				return ret;
462c2b78452SBoris Brezillon 
463c2b78452SBoris Brezillon 			break;
464c2b78452SBoris Brezillon 		}
465c2b78452SBoris Brezillon 
466c2b78452SBoris Brezillon 		eccpos = oobregion.offset;
467c2b78452SBoris Brezillon 		for (; i < MTD_MAX_ECCPOS_ENTRIES &&
468c2b78452SBoris Brezillon 		       eccpos < oobregion.offset + oobregion.length; i++) {
469c2b78452SBoris Brezillon 			to->eccpos[i] = eccpos++;
470c2b78452SBoris Brezillon 			to->eccbytes++;
471c2b78452SBoris Brezillon 		}
472c2b78452SBoris Brezillon 	}
473cc26c3cdSBrian Norris 
474cc26c3cdSBrian Norris 	for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES; i++) {
475c2b78452SBoris Brezillon 		ret = mtd_ooblayout_free(mtd, i, &oobregion);
476c2b78452SBoris Brezillon 		if (ret < 0) {
477c2b78452SBoris Brezillon 			if (ret != -ERANGE)
478c2b78452SBoris Brezillon 				return ret;
479c2b78452SBoris Brezillon 
480cc26c3cdSBrian Norris 			break;
481cc26c3cdSBrian Norris 		}
482cc26c3cdSBrian Norris 
483c2b78452SBoris Brezillon 		to->oobfree[i].offset = oobregion.offset;
484c2b78452SBoris Brezillon 		to->oobfree[i].length = oobregion.length;
485c2b78452SBoris Brezillon 		to->oobavail += to->oobfree[i].length;
486c2b78452SBoris Brezillon 	}
487c2b78452SBoris Brezillon 
488c2b78452SBoris Brezillon 	return 0;
489c2b78452SBoris Brezillon }
490c2b78452SBoris Brezillon 
get_oobinfo(struct mtd_info * mtd,struct nand_oobinfo * to)491c2b78452SBoris Brezillon static int get_oobinfo(struct mtd_info *mtd, struct nand_oobinfo *to)
492c2b78452SBoris Brezillon {
493c2b78452SBoris Brezillon 	struct mtd_oob_region oobregion;
494c2b78452SBoris Brezillon 	int i, section = 0, ret;
495c2b78452SBoris Brezillon 
496c2b78452SBoris Brezillon 	if (!mtd || !to)
497c2b78452SBoris Brezillon 		return -EINVAL;
498c2b78452SBoris Brezillon 
499c2b78452SBoris Brezillon 	memset(to, 0, sizeof(*to));
500c2b78452SBoris Brezillon 
501c2b78452SBoris Brezillon 	to->eccbytes = 0;
502c2b78452SBoris Brezillon 	for (i = 0; i < ARRAY_SIZE(to->eccpos);) {
503c2b78452SBoris Brezillon 		u32 eccpos;
504c2b78452SBoris Brezillon 
5056de56493SOuYang ZhiZhong 		ret = mtd_ooblayout_ecc(mtd, section++, &oobregion);
506c2b78452SBoris Brezillon 		if (ret < 0) {
507c2b78452SBoris Brezillon 			if (ret != -ERANGE)
508c2b78452SBoris Brezillon 				return ret;
509c2b78452SBoris Brezillon 
510c2b78452SBoris Brezillon 			break;
511c2b78452SBoris Brezillon 		}
512c2b78452SBoris Brezillon 
513c2b78452SBoris Brezillon 		if (oobregion.length + i > ARRAY_SIZE(to->eccpos))
514c2b78452SBoris Brezillon 			return -EINVAL;
515c2b78452SBoris Brezillon 
516c2b78452SBoris Brezillon 		eccpos = oobregion.offset;
517c2b78452SBoris Brezillon 		for (; eccpos < oobregion.offset + oobregion.length; i++) {
518c2b78452SBoris Brezillon 			to->eccpos[i] = eccpos++;
519c2b78452SBoris Brezillon 			to->eccbytes++;
520c2b78452SBoris Brezillon 		}
521c2b78452SBoris Brezillon 	}
522c2b78452SBoris Brezillon 
523c2b78452SBoris Brezillon 	for (i = 0; i < 8; i++) {
524c2b78452SBoris Brezillon 		ret = mtd_ooblayout_free(mtd, i, &oobregion);
525c2b78452SBoris Brezillon 		if (ret < 0) {
526c2b78452SBoris Brezillon 			if (ret != -ERANGE)
527c2b78452SBoris Brezillon 				return ret;
528c2b78452SBoris Brezillon 
529c2b78452SBoris Brezillon 			break;
530c2b78452SBoris Brezillon 		}
531c2b78452SBoris Brezillon 
532c2b78452SBoris Brezillon 		to->oobfree[i][0] = oobregion.offset;
533c2b78452SBoris Brezillon 		to->oobfree[i][1] = oobregion.length;
534c2b78452SBoris Brezillon 	}
535c2b78452SBoris Brezillon 
536c2b78452SBoris Brezillon 	to->useecc = MTD_NANDECC_AUTOPLACE;
537c2b78452SBoris Brezillon 
538cc26c3cdSBrian Norris 	return 0;
539cc26c3cdSBrian Norris }
540cc26c3cdSBrian Norris 
mtdchar_blkpg_ioctl(struct mtd_info * mtd,struct blkpg_ioctl_arg * arg)541969e57adSArtem Bityutskiy static int mtdchar_blkpg_ioctl(struct mtd_info *mtd,
54253bb724fSBrian Norris 			       struct blkpg_ioctl_arg *arg)
543d0f7959eSRoman Tereshonkov {
544d0f7959eSRoman Tereshonkov 	struct blkpg_partition p;
545d0f7959eSRoman Tereshonkov 
546d0f7959eSRoman Tereshonkov 	if (!capable(CAP_SYS_ADMIN))
547d0f7959eSRoman Tereshonkov 		return -EPERM;
548d0f7959eSRoman Tereshonkov 
54953bb724fSBrian Norris 	if (copy_from_user(&p, arg->data, sizeof(p)))
550d0f7959eSRoman Tereshonkov 		return -EFAULT;
551d0f7959eSRoman Tereshonkov 
55253bb724fSBrian Norris 	switch (arg->op) {
553d0f7959eSRoman Tereshonkov 	case BLKPG_ADD_PARTITION:
554d0f7959eSRoman Tereshonkov 
555a7e93dcdSRoman Tereshonkov 		/* Only master mtd device must be used to add partitions */
556a7e93dcdSRoman Tereshonkov 		if (mtd_is_partition(mtd))
557a7e93dcdSRoman Tereshonkov 			return -EINVAL;
558a7e93dcdSRoman Tereshonkov 
5591cc8d841SBrian Norris 		/* Sanitize user input */
5601cc8d841SBrian Norris 		p.devname[BLKPG_DEVNAMELTH - 1] = '\0';
5611cc8d841SBrian Norris 
562d0f7959eSRoman Tereshonkov 		return mtd_add_partition(mtd, p.devname, p.start, p.length);
563d0f7959eSRoman Tereshonkov 
564d0f7959eSRoman Tereshonkov 	case BLKPG_DEL_PARTITION:
565d0f7959eSRoman Tereshonkov 
566d0f7959eSRoman Tereshonkov 		if (p.pno < 0)
567d0f7959eSRoman Tereshonkov 			return -EINVAL;
568d0f7959eSRoman Tereshonkov 
569d0f7959eSRoman Tereshonkov 		return mtd_del_partition(mtd, p.pno);
570d0f7959eSRoman Tereshonkov 
571d0f7959eSRoman Tereshonkov 	default:
572d0f7959eSRoman Tereshonkov 		return -EINVAL;
573d0f7959eSRoman Tereshonkov 	}
574d0f7959eSRoman Tereshonkov }
575d0f7959eSRoman Tereshonkov 
adjust_oob_length(struct mtd_info * mtd,uint64_t start,struct mtd_oob_ops * ops)5766420ac0aSMichał Kępień static void adjust_oob_length(struct mtd_info *mtd, uint64_t start,
5776420ac0aSMichał Kępień 			      struct mtd_oob_ops *ops)
5786420ac0aSMichał Kępień {
5796420ac0aSMichał Kępień 	uint32_t start_page, end_page;
5806420ac0aSMichał Kępień 	u32 oob_per_page;
5816420ac0aSMichał Kępień 
5826420ac0aSMichał Kępień 	if (ops->len == 0 || ops->ooblen == 0)
5836420ac0aSMichał Kępień 		return;
5846420ac0aSMichał Kępień 
5856420ac0aSMichał Kępień 	start_page = mtd_div_by_ws(start, mtd);
5866420ac0aSMichał Kępień 	end_page = mtd_div_by_ws(start + ops->len - 1, mtd);
5876420ac0aSMichał Kępień 	oob_per_page = mtd_oobavail(mtd, ops);
5886420ac0aSMichał Kępień 
5896420ac0aSMichał Kępień 	ops->ooblen = min_t(size_t, ops->ooblen,
5906420ac0aSMichał Kępień 			    (end_page - start_page + 1) * oob_per_page);
5916420ac0aSMichał Kępień }
5926420ac0aSMichał Kępień 
593*0ea923f4SArnd Bergmann static noinline_for_stack int
mtdchar_write_ioctl(struct mtd_info * mtd,struct mtd_write_req __user * argp)594*0ea923f4SArnd Bergmann mtdchar_write_ioctl(struct mtd_info *mtd, struct mtd_write_req __user *argp)
595e99d8b08SBrian Norris {
59646b5889cSMiquel Raynal 	struct mtd_info *master = mtd_get_master(mtd);
597e99d8b08SBrian Norris 	struct mtd_write_req req;
598f62cde49SGeert Uytterhoeven 	const void __user *usr_data, *usr_oob;
5996420ac0aSMichał Kępień 	uint8_t *datbuf = NULL, *oobbuf = NULL;
6006420ac0aSMichał Kępień 	size_t datbuf_len, oobbuf_len;
6016420ac0aSMichał Kępień 	int ret = 0;
602e99d8b08SBrian Norris 
603f62cde49SGeert Uytterhoeven 	if (copy_from_user(&req, argp, sizeof(req)))
604e99d8b08SBrian Norris 		return -EFAULT;
605f62cde49SGeert Uytterhoeven 
606f62cde49SGeert Uytterhoeven 	usr_data = (const void __user *)(uintptr_t)req.usr_data;
607f62cde49SGeert Uytterhoeven 	usr_oob = (const void __user *)(uintptr_t)req.usr_oob;
608f62cde49SGeert Uytterhoeven 
60946b5889cSMiquel Raynal 	if (!master->_write_oob)
610e99d8b08SBrian Norris 		return -EOPNOTSUPP;
611e99d8b08SBrian Norris 
6126420ac0aSMichał Kępień 	if (!usr_data)
6136420ac0aSMichał Kępień 		req.len = 0;
6146420ac0aSMichał Kępień 
6156420ac0aSMichał Kępień 	if (!usr_oob)
6166420ac0aSMichał Kępień 		req.ooblen = 0;
6176420ac0aSMichał Kępień 
618a1eda864SMichał Kępień 	req.len &= 0xffffffff;
619a1eda864SMichał Kępień 	req.ooblen &= 0xffffffff;
620a1eda864SMichał Kępień 
6216420ac0aSMichał Kępień 	if (req.start + req.len > mtd->size)
6226420ac0aSMichał Kępień 		return -EINVAL;
6236420ac0aSMichał Kępień 
6246420ac0aSMichał Kępień 	datbuf_len = min_t(size_t, req.len, mtd->erasesize);
6256420ac0aSMichał Kępień 	if (datbuf_len > 0) {
62683208e10SMichał Kępień 		datbuf = kvmalloc(datbuf_len, GFP_KERNEL);
6276420ac0aSMichał Kępień 		if (!datbuf)
6286420ac0aSMichał Kępień 			return -ENOMEM;
629e99d8b08SBrian Norris 	}
630e99d8b08SBrian Norris 
6316420ac0aSMichał Kępień 	oobbuf_len = min_t(size_t, req.ooblen, mtd->erasesize);
6326420ac0aSMichał Kępień 	if (oobbuf_len > 0) {
63383208e10SMichał Kępień 		oobbuf = kvmalloc(oobbuf_len, GFP_KERNEL);
6346420ac0aSMichał Kępień 		if (!oobbuf) {
63583208e10SMichał Kępień 			kvfree(datbuf);
6366420ac0aSMichał Kępień 			return -ENOMEM;
637e99d8b08SBrian Norris 		}
638e99d8b08SBrian Norris 	}
639e99d8b08SBrian Norris 
6406420ac0aSMichał Kępień 	while (req.len > 0 || (!usr_data && req.ooblen > 0)) {
6416420ac0aSMichał Kępień 		struct mtd_oob_ops ops = {
6426420ac0aSMichał Kępień 			.mode = req.mode,
6436420ac0aSMichał Kępień 			.len = min_t(size_t, req.len, datbuf_len),
6446420ac0aSMichał Kępień 			.ooblen = min_t(size_t, req.ooblen, oobbuf_len),
6456420ac0aSMichał Kępień 			.datbuf = datbuf,
6466420ac0aSMichał Kępień 			.oobbuf = oobbuf,
6476420ac0aSMichał Kępień 		};
648e99d8b08SBrian Norris 
6496420ac0aSMichał Kępień 		/*
6506420ac0aSMichał Kępień 		 * Shorten non-page-aligned, eraseblock-sized writes so that
6516420ac0aSMichał Kępień 		 * the write ends on an eraseblock boundary.  This is necessary
6526420ac0aSMichał Kępień 		 * for adjust_oob_length() to properly handle non-page-aligned
6536420ac0aSMichał Kępień 		 * writes.
6546420ac0aSMichał Kępień 		 */
6556420ac0aSMichał Kępień 		if (ops.len == mtd->erasesize)
6566420ac0aSMichał Kępień 			ops.len -= mtd_mod_by_ws(req.start + ops.len, mtd);
6576420ac0aSMichał Kępień 
6586420ac0aSMichał Kępień 		/*
6596420ac0aSMichał Kępień 		 * For writes which are not OOB-only, adjust the amount of OOB
6606420ac0aSMichał Kępień 		 * data written according to the number of data pages written.
6616420ac0aSMichał Kępień 		 * This is necessary to prevent OOB data from being skipped
6626420ac0aSMichał Kępień 		 * over in data+OOB writes requiring multiple mtd_write_oob()
6636420ac0aSMichał Kępień 		 * calls to be completed.
6646420ac0aSMichał Kępień 		 */
6656420ac0aSMichał Kępień 		adjust_oob_length(mtd, req.start, &ops);
6666420ac0aSMichał Kępień 
6676420ac0aSMichał Kępień 		if (copy_from_user(datbuf, usr_data, ops.len) ||
6686420ac0aSMichał Kępień 		    copy_from_user(oobbuf, usr_oob, ops.ooblen)) {
6696420ac0aSMichał Kępień 			ret = -EFAULT;
6706420ac0aSMichał Kępień 			break;
6716420ac0aSMichał Kępień 		}
6726420ac0aSMichał Kępień 
6736420ac0aSMichał Kępień 		ret = mtd_write_oob(mtd, req.start, &ops);
6746420ac0aSMichał Kępień 		if (ret)
6756420ac0aSMichał Kępień 			break;
6766420ac0aSMichał Kępień 
6776420ac0aSMichał Kępień 		req.start += ops.retlen;
6786420ac0aSMichał Kępień 		req.len -= ops.retlen;
6796420ac0aSMichał Kępień 		usr_data += ops.retlen;
6806420ac0aSMichał Kępień 
6816420ac0aSMichał Kępień 		req.ooblen -= ops.oobretlen;
6826420ac0aSMichał Kępień 		usr_oob += ops.oobretlen;
6836420ac0aSMichał Kępień 	}
6846420ac0aSMichał Kępień 
68583208e10SMichał Kępień 	kvfree(datbuf);
68683208e10SMichał Kępień 	kvfree(oobbuf);
687e99d8b08SBrian Norris 
688e99d8b08SBrian Norris 	return ret;
689e99d8b08SBrian Norris }
690e99d8b08SBrian Norris 
691*0ea923f4SArnd Bergmann static noinline_for_stack int
mtdchar_read_ioctl(struct mtd_info * mtd,struct mtd_read_req __user * argp)692*0ea923f4SArnd Bergmann mtdchar_read_ioctl(struct mtd_info *mtd, struct mtd_read_req __user *argp)
693095bb6e4SMichał Kępień {
694095bb6e4SMichał Kępień 	struct mtd_info *master = mtd_get_master(mtd);
695095bb6e4SMichał Kępień 	struct mtd_read_req req;
696095bb6e4SMichał Kępień 	void __user *usr_data, *usr_oob;
697095bb6e4SMichał Kępień 	uint8_t *datbuf = NULL, *oobbuf = NULL;
698095bb6e4SMichał Kępień 	size_t datbuf_len, oobbuf_len;
699095bb6e4SMichał Kępień 	size_t orig_len, orig_ooblen;
700095bb6e4SMichał Kępień 	int ret = 0;
701095bb6e4SMichał Kępień 
702095bb6e4SMichał Kępień 	if (copy_from_user(&req, argp, sizeof(req)))
703095bb6e4SMichał Kępień 		return -EFAULT;
704095bb6e4SMichał Kępień 
705095bb6e4SMichał Kępień 	orig_len = req.len;
706095bb6e4SMichał Kępień 	orig_ooblen = req.ooblen;
707095bb6e4SMichał Kępień 
708095bb6e4SMichał Kępień 	usr_data = (void __user *)(uintptr_t)req.usr_data;
709095bb6e4SMichał Kępień 	usr_oob = (void __user *)(uintptr_t)req.usr_oob;
710095bb6e4SMichał Kępień 
711095bb6e4SMichał Kępień 	if (!master->_read_oob)
712095bb6e4SMichał Kępień 		return -EOPNOTSUPP;
713095bb6e4SMichał Kępień 
714095bb6e4SMichał Kępień 	if (!usr_data)
715095bb6e4SMichał Kępień 		req.len = 0;
716095bb6e4SMichał Kępień 
717095bb6e4SMichał Kępień 	if (!usr_oob)
718095bb6e4SMichał Kępień 		req.ooblen = 0;
719095bb6e4SMichał Kępień 
720095bb6e4SMichał Kępień 	req.ecc_stats.uncorrectable_errors = 0;
721095bb6e4SMichał Kępień 	req.ecc_stats.corrected_bitflips = 0;
722095bb6e4SMichał Kępień 	req.ecc_stats.max_bitflips = 0;
723095bb6e4SMichał Kępień 
724095bb6e4SMichał Kępień 	req.len &= 0xffffffff;
725095bb6e4SMichał Kępień 	req.ooblen &= 0xffffffff;
726095bb6e4SMichał Kępień 
727095bb6e4SMichał Kępień 	if (req.start + req.len > mtd->size) {
728095bb6e4SMichał Kępień 		ret = -EINVAL;
729095bb6e4SMichał Kępień 		goto out;
730095bb6e4SMichał Kępień 	}
731095bb6e4SMichał Kępień 
732095bb6e4SMichał Kępień 	datbuf_len = min_t(size_t, req.len, mtd->erasesize);
733095bb6e4SMichał Kępień 	if (datbuf_len > 0) {
734095bb6e4SMichał Kępień 		datbuf = kvmalloc(datbuf_len, GFP_KERNEL);
735095bb6e4SMichał Kępień 		if (!datbuf) {
736095bb6e4SMichał Kępień 			ret = -ENOMEM;
737095bb6e4SMichał Kępień 			goto out;
738095bb6e4SMichał Kępień 		}
739095bb6e4SMichał Kępień 	}
740095bb6e4SMichał Kępień 
741095bb6e4SMichał Kępień 	oobbuf_len = min_t(size_t, req.ooblen, mtd->erasesize);
742095bb6e4SMichał Kępień 	if (oobbuf_len > 0) {
743095bb6e4SMichał Kępień 		oobbuf = kvmalloc(oobbuf_len, GFP_KERNEL);
744095bb6e4SMichał Kępień 		if (!oobbuf) {
745095bb6e4SMichał Kępień 			ret = -ENOMEM;
746095bb6e4SMichał Kępień 			goto out;
747095bb6e4SMichał Kępień 		}
748095bb6e4SMichał Kępień 	}
749095bb6e4SMichał Kępień 
750095bb6e4SMichał Kępień 	while (req.len > 0 || (!usr_data && req.ooblen > 0)) {
751095bb6e4SMichał Kępień 		struct mtd_req_stats stats;
752095bb6e4SMichał Kępień 		struct mtd_oob_ops ops = {
753095bb6e4SMichał Kępień 			.mode = req.mode,
754095bb6e4SMichał Kępień 			.len = min_t(size_t, req.len, datbuf_len),
755095bb6e4SMichał Kępień 			.ooblen = min_t(size_t, req.ooblen, oobbuf_len),
756095bb6e4SMichał Kępień 			.datbuf = datbuf,
757095bb6e4SMichał Kępień 			.oobbuf = oobbuf,
758095bb6e4SMichał Kępień 			.stats = &stats,
759095bb6e4SMichał Kępień 		};
760095bb6e4SMichał Kępień 
761095bb6e4SMichał Kępień 		/*
762095bb6e4SMichał Kępień 		 * Shorten non-page-aligned, eraseblock-sized reads so that the
763095bb6e4SMichał Kępień 		 * read ends on an eraseblock boundary.  This is necessary in
764095bb6e4SMichał Kępień 		 * order to prevent OOB data for some pages from being
765095bb6e4SMichał Kępień 		 * duplicated in the output of non-page-aligned reads requiring
766095bb6e4SMichał Kępień 		 * multiple mtd_read_oob() calls to be completed.
767095bb6e4SMichał Kępień 		 */
768095bb6e4SMichał Kępień 		if (ops.len == mtd->erasesize)
769095bb6e4SMichał Kępień 			ops.len -= mtd_mod_by_ws(req.start + ops.len, mtd);
770095bb6e4SMichał Kępień 
771095bb6e4SMichał Kępień 		ret = mtd_read_oob(mtd, (loff_t)req.start, &ops);
772095bb6e4SMichał Kępień 
773095bb6e4SMichał Kępień 		req.ecc_stats.uncorrectable_errors +=
774095bb6e4SMichał Kępień 			stats.uncorrectable_errors;
775095bb6e4SMichał Kępień 		req.ecc_stats.corrected_bitflips += stats.corrected_bitflips;
776095bb6e4SMichał Kępień 		req.ecc_stats.max_bitflips =
777095bb6e4SMichał Kępień 			max(req.ecc_stats.max_bitflips, stats.max_bitflips);
778095bb6e4SMichał Kępień 
779095bb6e4SMichał Kępień 		if (ret && !mtd_is_bitflip_or_eccerr(ret))
780095bb6e4SMichał Kępień 			break;
781095bb6e4SMichał Kępień 
782095bb6e4SMichał Kępień 		if (copy_to_user(usr_data, ops.datbuf, ops.retlen) ||
783095bb6e4SMichał Kępień 		    copy_to_user(usr_oob, ops.oobbuf, ops.oobretlen)) {
784095bb6e4SMichał Kępień 			ret = -EFAULT;
785095bb6e4SMichał Kępień 			break;
786095bb6e4SMichał Kępień 		}
787095bb6e4SMichał Kępień 
788095bb6e4SMichał Kępień 		req.start += ops.retlen;
789095bb6e4SMichał Kępień 		req.len -= ops.retlen;
790095bb6e4SMichał Kępień 		usr_data += ops.retlen;
791095bb6e4SMichał Kępień 
792095bb6e4SMichał Kępień 		req.ooblen -= ops.oobretlen;
793095bb6e4SMichał Kępień 		usr_oob += ops.oobretlen;
794095bb6e4SMichał Kępień 	}
795095bb6e4SMichał Kępień 
796095bb6e4SMichał Kępień 	/*
797095bb6e4SMichał Kępień 	 * As multiple iterations of the above loop (and therefore multiple
798095bb6e4SMichał Kępień 	 * mtd_read_oob() calls) may be necessary to complete the read request,
799095bb6e4SMichał Kępień 	 * adjust the final return code to ensure it accounts for all detected
800095bb6e4SMichał Kępień 	 * ECC errors.
801095bb6e4SMichał Kępień 	 */
802095bb6e4SMichał Kępień 	if (!ret || mtd_is_bitflip(ret)) {
803095bb6e4SMichał Kępień 		if (req.ecc_stats.uncorrectable_errors > 0)
804095bb6e4SMichał Kępień 			ret = -EBADMSG;
805095bb6e4SMichał Kępień 		else if (req.ecc_stats.corrected_bitflips > 0)
806095bb6e4SMichał Kępień 			ret = -EUCLEAN;
807095bb6e4SMichał Kępień 	}
808095bb6e4SMichał Kępień 
809095bb6e4SMichał Kępień out:
810095bb6e4SMichał Kępień 	req.len = orig_len - req.len;
811095bb6e4SMichał Kępień 	req.ooblen = orig_ooblen - req.ooblen;
812095bb6e4SMichał Kępień 
813095bb6e4SMichał Kępień 	if (copy_to_user(argp, &req, sizeof(req)))
814095bb6e4SMichał Kępień 		ret = -EFAULT;
815095bb6e4SMichał Kępień 
816095bb6e4SMichał Kępień 	kvfree(datbuf);
817095bb6e4SMichał Kępień 	kvfree(oobbuf);
818095bb6e4SMichał Kępień 
819095bb6e4SMichał Kępień 	return ret;
820095bb6e4SMichał Kępień }
821095bb6e4SMichał Kępień 
mtdchar_ioctl(struct file * file,u_int cmd,u_long arg)822969e57adSArtem Bityutskiy static int mtdchar_ioctl(struct file *file, u_int cmd, u_long arg)
8231da177e4SLinus Torvalds {
824f1a28c02SThomas Gleixner 	struct mtd_file_info *mfi = file->private_data;
825f1a28c02SThomas Gleixner 	struct mtd_info *mtd = mfi->mtd;
82646b5889cSMiquel Raynal 	struct mtd_info *master = mtd_get_master(mtd);
8271da177e4SLinus Torvalds 	void __user *argp = (void __user *)arg;
8281da177e4SLinus Torvalds 	int ret = 0;
82973c619eaSJoern Engel 	struct mtd_info_user info;
8301da177e4SLinus Torvalds 
831289c0522SBrian Norris 	pr_debug("MTD_ioctl\n");
8321da177e4SLinus Torvalds 
833f7e6b19bSGreg Kroah-Hartman 	/*
834f7e6b19bSGreg Kroah-Hartman 	 * Check the file mode to require "dangerous" commands to have write
835f7e6b19bSGreg Kroah-Hartman 	 * permissions.
836f7e6b19bSGreg Kroah-Hartman 	 */
837f7e6b19bSGreg Kroah-Hartman 	switch (cmd) {
838f7e6b19bSGreg Kroah-Hartman 	/* "safe" commands */
839f7e6b19bSGreg Kroah-Hartman 	case MEMGETREGIONCOUNT:
840f7e6b19bSGreg Kroah-Hartman 	case MEMGETREGIONINFO:
841f7e6b19bSGreg Kroah-Hartman 	case MEMGETINFO:
842f7e6b19bSGreg Kroah-Hartman 	case MEMREADOOB:
843f7e6b19bSGreg Kroah-Hartman 	case MEMREADOOB64:
844095bb6e4SMichał Kępień 	case MEMREAD:
845f7e6b19bSGreg Kroah-Hartman 	case MEMISLOCKED:
846f7e6b19bSGreg Kroah-Hartman 	case MEMGETOOBSEL:
847f7e6b19bSGreg Kroah-Hartman 	case MEMGETBADBLOCK:
848f7e6b19bSGreg Kroah-Hartman 	case OTPSELECT:
849f7e6b19bSGreg Kroah-Hartman 	case OTPGETREGIONCOUNT:
850f7e6b19bSGreg Kroah-Hartman 	case OTPGETREGIONINFO:
851f7e6b19bSGreg Kroah-Hartman 	case ECCGETLAYOUT:
852f7e6b19bSGreg Kroah-Hartman 	case ECCGETSTATS:
853f7e6b19bSGreg Kroah-Hartman 	case MTDFILEMODE:
854f7e6b19bSGreg Kroah-Hartman 	case BLKPG:
855f7e6b19bSGreg Kroah-Hartman 	case BLKRRPART:
856f7e6b19bSGreg Kroah-Hartman 		break;
857f7e6b19bSGreg Kroah-Hartman 
858f7e6b19bSGreg Kroah-Hartman 	/* "dangerous" commands */
859f7e6b19bSGreg Kroah-Hartman 	case MEMERASE:
860f7e6b19bSGreg Kroah-Hartman 	case MEMERASE64:
8611e97743fSMichael Walle 	case MEMLOCK:
8621e97743fSMichael Walle 	case MEMUNLOCK:
8631e97743fSMichael Walle 	case MEMSETBADBLOCK:
864f7e6b19bSGreg Kroah-Hartman 	case MEMWRITEOOB:
865f7e6b19bSGreg Kroah-Hartman 	case MEMWRITEOOB64:
866f7e6b19bSGreg Kroah-Hartman 	case MEMWRITE:
8671e97743fSMichael Walle 	case OTPLOCK:
868e3c1f1c9SMichael Walle 	case OTPERASE:
869f7e6b19bSGreg Kroah-Hartman 		if (!(file->f_mode & FMODE_WRITE))
870f7e6b19bSGreg Kroah-Hartman 			return -EPERM;
871f7e6b19bSGreg Kroah-Hartman 		break;
872f7e6b19bSGreg Kroah-Hartman 
873f7e6b19bSGreg Kroah-Hartman 	default:
874f7e6b19bSGreg Kroah-Hartman 		return -ENOTTY;
875f7e6b19bSGreg Kroah-Hartman 	}
876f7e6b19bSGreg Kroah-Hartman 
8771da177e4SLinus Torvalds 	switch (cmd) {
8781da177e4SLinus Torvalds 	case MEMGETREGIONCOUNT:
8791da177e4SLinus Torvalds 		if (copy_to_user(argp, &(mtd->numeraseregions), sizeof(int)))
8801da177e4SLinus Torvalds 			return -EFAULT;
8811da177e4SLinus Torvalds 		break;
8821da177e4SLinus Torvalds 
8831da177e4SLinus Torvalds 	case MEMGETREGIONINFO:
8841da177e4SLinus Torvalds 	{
885b67c5f87SZev Weiss 		uint32_t ur_idx;
886b67c5f87SZev Weiss 		struct mtd_erase_region_info *kr;
887bcc98a46SH Hartley Sweeten 		struct region_info_user __user *ur = argp;
8881da177e4SLinus Torvalds 
889b67c5f87SZev Weiss 		if (get_user(ur_idx, &(ur->regionindex)))
8901da177e4SLinus Torvalds 			return -EFAULT;
8911da177e4SLinus Torvalds 
8925e59be1fSDan Carpenter 		if (ur_idx >= mtd->numeraseregions)
8935e59be1fSDan Carpenter 			return -EINVAL;
8945e59be1fSDan Carpenter 
895b67c5f87SZev Weiss 		kr = &(mtd->eraseregions[ur_idx]);
896b67c5f87SZev Weiss 
897b67c5f87SZev Weiss 		if (put_user(kr->offset, &(ur->offset))
898b67c5f87SZev Weiss 		    || put_user(kr->erasesize, &(ur->erasesize))
899b67c5f87SZev Weiss 		    || put_user(kr->numblocks, &(ur->numblocks)))
9001da177e4SLinus Torvalds 			return -EFAULT;
901b67c5f87SZev Weiss 
9021da177e4SLinus Torvalds 		break;
9031da177e4SLinus Torvalds 	}
9041da177e4SLinus Torvalds 
9051da177e4SLinus Torvalds 	case MEMGETINFO:
906a0c5a394SVasiliy Kulikov 		memset(&info, 0, sizeof(info));
90773c619eaSJoern Engel 		info.type	= mtd->type;
90873c619eaSJoern Engel 		info.flags	= mtd->flags;
90973c619eaSJoern Engel 		info.size	= mtd->size;
91073c619eaSJoern Engel 		info.erasesize	= mtd->erasesize;
91173c619eaSJoern Engel 		info.writesize	= mtd->writesize;
91273c619eaSJoern Engel 		info.oobsize	= mtd->oobsize;
91319fb4341SBrian Norris 		/* The below field is obsolete */
91419fb4341SBrian Norris 		info.padding	= 0;
91573c619eaSJoern Engel 		if (copy_to_user(argp, &info, sizeof(struct mtd_info_user)))
9161da177e4SLinus Torvalds 			return -EFAULT;
9171da177e4SLinus Torvalds 		break;
9181da177e4SLinus Torvalds 
9191da177e4SLinus Torvalds 	case MEMERASE:
9200dc54e9fSKevin Cernekee 	case MEMERASE64:
9211da177e4SLinus Torvalds 	{
9221da177e4SLinus Torvalds 		struct erase_info *erase;
9231da177e4SLinus Torvalds 
92495b93a0cSBurman Yan 		erase=kzalloc(sizeof(struct erase_info),GFP_KERNEL);
9251da177e4SLinus Torvalds 		if (!erase)
9261da177e4SLinus Torvalds 			ret = -ENOMEM;
9271da177e4SLinus Torvalds 		else {
9280dc54e9fSKevin Cernekee 			if (cmd == MEMERASE64) {
9290dc54e9fSKevin Cernekee 				struct erase_info_user64 einfo64;
9300dc54e9fSKevin Cernekee 
9310dc54e9fSKevin Cernekee 				if (copy_from_user(&einfo64, argp,
9320dc54e9fSKevin Cernekee 					    sizeof(struct erase_info_user64))) {
9330dc54e9fSKevin Cernekee 					kfree(erase);
9340dc54e9fSKevin Cernekee 					return -EFAULT;
9350dc54e9fSKevin Cernekee 				}
9360dc54e9fSKevin Cernekee 				erase->addr = einfo64.start;
9370dc54e9fSKevin Cernekee 				erase->len = einfo64.length;
9380dc54e9fSKevin Cernekee 			} else {
9390dc54e9fSKevin Cernekee 				struct erase_info_user einfo32;
9400dc54e9fSKevin Cernekee 
9410dc54e9fSKevin Cernekee 				if (copy_from_user(&einfo32, argp,
9421da177e4SLinus Torvalds 					    sizeof(struct erase_info_user))) {
9431da177e4SLinus Torvalds 					kfree(erase);
9441da177e4SLinus Torvalds 					return -EFAULT;
9451da177e4SLinus Torvalds 				}
9460dc54e9fSKevin Cernekee 				erase->addr = einfo32.start;
9470dc54e9fSKevin Cernekee 				erase->len = einfo32.length;
9480dc54e9fSKevin Cernekee 			}
9491da177e4SLinus Torvalds 
9507e1f0dc0SArtem Bityutskiy 			ret = mtd_erase(mtd, erase);
9511da177e4SLinus Torvalds 			kfree(erase);
9521da177e4SLinus Torvalds 		}
9531da177e4SLinus Torvalds 		break;
9541da177e4SLinus Torvalds 	}
9551da177e4SLinus Torvalds 
9561da177e4SLinus Torvalds 	case MEMWRITEOOB:
9571da177e4SLinus Torvalds 	{
9581da177e4SLinus Torvalds 		struct mtd_oob_buf buf;
95997718540SKevin Cernekee 		struct mtd_oob_buf __user *buf_user = argp;
9601da177e4SLinus Torvalds 
96197718540SKevin Cernekee 		/* NOTE: writes return length to buf_user->length */
96297718540SKevin Cernekee 		if (copy_from_user(&buf, argp, sizeof(buf)))
9631da177e4SLinus Torvalds 			ret = -EFAULT;
96497718540SKevin Cernekee 		else
965969e57adSArtem Bityutskiy 			ret = mtdchar_writeoob(file, mtd, buf.start, buf.length,
96697718540SKevin Cernekee 				buf.ptr, &buf_user->length);
9671da177e4SLinus Torvalds 		break;
9681da177e4SLinus Torvalds 	}
9691da177e4SLinus Torvalds 
9701da177e4SLinus Torvalds 	case MEMREADOOB:
9711da177e4SLinus Torvalds 	{
9721da177e4SLinus Torvalds 		struct mtd_oob_buf buf;
97397718540SKevin Cernekee 		struct mtd_oob_buf __user *buf_user = argp;
9741da177e4SLinus Torvalds 
97597718540SKevin Cernekee 		/* NOTE: writes return length to buf_user->start */
97697718540SKevin Cernekee 		if (copy_from_user(&buf, argp, sizeof(buf)))
97797718540SKevin Cernekee 			ret = -EFAULT;
9781da177e4SLinus Torvalds 		else
979969e57adSArtem Bityutskiy 			ret = mtdchar_readoob(file, mtd, buf.start, buf.length,
98097718540SKevin Cernekee 				buf.ptr, &buf_user->start);
9811da177e4SLinus Torvalds 		break;
9821da177e4SLinus Torvalds 	}
9831da177e4SLinus Torvalds 
984aea7cea9SKevin Cernekee 	case MEMWRITEOOB64:
985aea7cea9SKevin Cernekee 	{
986aea7cea9SKevin Cernekee 		struct mtd_oob_buf64 buf;
987aea7cea9SKevin Cernekee 		struct mtd_oob_buf64 __user *buf_user = argp;
988aea7cea9SKevin Cernekee 
989aea7cea9SKevin Cernekee 		if (copy_from_user(&buf, argp, sizeof(buf)))
990aea7cea9SKevin Cernekee 			ret = -EFAULT;
991aea7cea9SKevin Cernekee 		else
992969e57adSArtem Bityutskiy 			ret = mtdchar_writeoob(file, mtd, buf.start, buf.length,
993aea7cea9SKevin Cernekee 				(void __user *)(uintptr_t)buf.usr_ptr,
994aea7cea9SKevin Cernekee 				&buf_user->length);
995aea7cea9SKevin Cernekee 		break;
996aea7cea9SKevin Cernekee 	}
997aea7cea9SKevin Cernekee 
998aea7cea9SKevin Cernekee 	case MEMREADOOB64:
999aea7cea9SKevin Cernekee 	{
1000aea7cea9SKevin Cernekee 		struct mtd_oob_buf64 buf;
1001aea7cea9SKevin Cernekee 		struct mtd_oob_buf64 __user *buf_user = argp;
1002aea7cea9SKevin Cernekee 
1003aea7cea9SKevin Cernekee 		if (copy_from_user(&buf, argp, sizeof(buf)))
1004aea7cea9SKevin Cernekee 			ret = -EFAULT;
1005aea7cea9SKevin Cernekee 		else
1006969e57adSArtem Bityutskiy 			ret = mtdchar_readoob(file, mtd, buf.start, buf.length,
1007aea7cea9SKevin Cernekee 				(void __user *)(uintptr_t)buf.usr_ptr,
1008aea7cea9SKevin Cernekee 				&buf_user->length);
1009aea7cea9SKevin Cernekee 		break;
1010aea7cea9SKevin Cernekee 	}
1011aea7cea9SKevin Cernekee 
1012e99d8b08SBrian Norris 	case MEMWRITE:
1013e99d8b08SBrian Norris 	{
1014969e57adSArtem Bityutskiy 		ret = mtdchar_write_ioctl(mtd,
1015e99d8b08SBrian Norris 		      (struct mtd_write_req __user *)arg);
1016e99d8b08SBrian Norris 		break;
1017e99d8b08SBrian Norris 	}
1018e99d8b08SBrian Norris 
1019095bb6e4SMichał Kępień 	case MEMREAD:
1020095bb6e4SMichał Kępień 	{
1021095bb6e4SMichał Kępień 		ret = mtdchar_read_ioctl(mtd,
1022095bb6e4SMichał Kępień 		      (struct mtd_read_req __user *)arg);
1023095bb6e4SMichał Kępień 		break;
1024095bb6e4SMichał Kępień 	}
1025095bb6e4SMichał Kępień 
10261da177e4SLinus Torvalds 	case MEMLOCK:
10271da177e4SLinus Torvalds 	{
1028175428b2SHarvey Harrison 		struct erase_info_user einfo;
10291da177e4SLinus Torvalds 
1030175428b2SHarvey Harrison 		if (copy_from_user(&einfo, argp, sizeof(einfo)))
10311da177e4SLinus Torvalds 			return -EFAULT;
10321da177e4SLinus Torvalds 
10337799f9acSArtem Bityutskiy 		ret = mtd_lock(mtd, einfo.start, einfo.length);
10341da177e4SLinus Torvalds 		break;
10351da177e4SLinus Torvalds 	}
10361da177e4SLinus Torvalds 
10371da177e4SLinus Torvalds 	case MEMUNLOCK:
10381da177e4SLinus Torvalds 	{
1039175428b2SHarvey Harrison 		struct erase_info_user einfo;
10401da177e4SLinus Torvalds 
1041175428b2SHarvey Harrison 		if (copy_from_user(&einfo, argp, sizeof(einfo)))
10421da177e4SLinus Torvalds 			return -EFAULT;
10431da177e4SLinus Torvalds 
1044b66005cdSArtem Bityutskiy 		ret = mtd_unlock(mtd, einfo.start, einfo.length);
10451da177e4SLinus Torvalds 		break;
10461da177e4SLinus Torvalds 	}
10471da177e4SLinus Torvalds 
10489938424fSRichard Cochran 	case MEMISLOCKED:
10499938424fSRichard Cochran 	{
10509938424fSRichard Cochran 		struct erase_info_user einfo;
10519938424fSRichard Cochran 
10529938424fSRichard Cochran 		if (copy_from_user(&einfo, argp, sizeof(einfo)))
10539938424fSRichard Cochran 			return -EFAULT;
10549938424fSRichard Cochran 
1055e95e9786SArtem Bityutskiy 		ret = mtd_is_locked(mtd, einfo.start, einfo.length);
10569938424fSRichard Cochran 		break;
10579938424fSRichard Cochran 	}
10589938424fSRichard Cochran 
10595bd34c09SThomas Gleixner 	/* Legacy interface */
10601da177e4SLinus Torvalds 	case MEMGETOOBSEL:
10611da177e4SLinus Torvalds 	{
10625bd34c09SThomas Gleixner 		struct nand_oobinfo oi;
10635bd34c09SThomas Gleixner 
106446b5889cSMiquel Raynal 		if (!master->ooblayout)
10655bd34c09SThomas Gleixner 			return -EOPNOTSUPP;
10665bd34c09SThomas Gleixner 
1067c2b78452SBoris Brezillon 		ret = get_oobinfo(mtd, &oi);
1068c2b78452SBoris Brezillon 		if (ret)
1069c2b78452SBoris Brezillon 			return ret;
10705bd34c09SThomas Gleixner 
10715bd34c09SThomas Gleixner 		if (copy_to_user(argp, &oi, sizeof(struct nand_oobinfo)))
10721da177e4SLinus Torvalds 			return -EFAULT;
10731da177e4SLinus Torvalds 		break;
10741da177e4SLinus Torvalds 	}
10751da177e4SLinus Torvalds 
10761da177e4SLinus Torvalds 	case MEMGETBADBLOCK:
10771da177e4SLinus Torvalds 	{
10781da177e4SLinus Torvalds 		loff_t offs;
10791da177e4SLinus Torvalds 
10801da177e4SLinus Torvalds 		if (copy_from_user(&offs, argp, sizeof(loff_t)))
10811da177e4SLinus Torvalds 			return -EFAULT;
10827086c19dSArtem Bityutskiy 		return mtd_block_isbad(mtd, offs);
10831da177e4SLinus Torvalds 	}
10841da177e4SLinus Torvalds 
10851da177e4SLinus Torvalds 	case MEMSETBADBLOCK:
10861da177e4SLinus Torvalds 	{
10871da177e4SLinus Torvalds 		loff_t offs;
10881da177e4SLinus Torvalds 
10891da177e4SLinus Torvalds 		if (copy_from_user(&offs, argp, sizeof(loff_t)))
10901da177e4SLinus Torvalds 			return -EFAULT;
10915942ddbcSArtem Bityutskiy 		return mtd_block_markbad(mtd, offs);
10921da177e4SLinus Torvalds 	}
10931da177e4SLinus Torvalds 
109431f4233bSNicolas Pitre 	case OTPSELECT:
109531f4233bSNicolas Pitre 	{
109631f4233bSNicolas Pitre 		int mode;
109731f4233bSNicolas Pitre 		if (copy_from_user(&mode, argp, sizeof(int)))
109831f4233bSNicolas Pitre 			return -EFAULT;
1099f1a28c02SThomas Gleixner 
1100beb133fcSBrian Norris 		mfi->mode = MTD_FILE_MODE_NORMAL;
1101f1a28c02SThomas Gleixner 
1102f1a28c02SThomas Gleixner 		ret = otp_select_filemode(mfi, mode);
1103f1a28c02SThomas Gleixner 
110481dba488SNicolas Pitre 		file->f_pos = 0;
110531f4233bSNicolas Pitre 		break;
110631f4233bSNicolas Pitre 	}
110731f4233bSNicolas Pitre 
110831f4233bSNicolas Pitre 	case OTPGETREGIONCOUNT:
110931f4233bSNicolas Pitre 	case OTPGETREGIONINFO:
111031f4233bSNicolas Pitre 	{
111131f4233bSNicolas Pitre 		struct otp_info *buf = kmalloc(4096, GFP_KERNEL);
11124b78fc42SChristian Riesch 		size_t retlen;
111331f4233bSNicolas Pitre 		if (!buf)
111431f4233bSNicolas Pitre 			return -ENOMEM;
1115f1a28c02SThomas Gleixner 		switch (mfi->mode) {
1116beb133fcSBrian Norris 		case MTD_FILE_MODE_OTP_FACTORY:
11174b78fc42SChristian Riesch 			ret = mtd_get_fact_prot_info(mtd, 4096, &retlen, buf);
111831f4233bSNicolas Pitre 			break;
1119beb133fcSBrian Norris 		case MTD_FILE_MODE_OTP_USER:
11204b78fc42SChristian Riesch 			ret = mtd_get_user_prot_info(mtd, 4096, &retlen, buf);
112131f4233bSNicolas Pitre 			break;
1122f1a28c02SThomas Gleixner 		default:
112387e858a9SArtem Bityutskiy 			ret = -EINVAL;
1124f1a28c02SThomas Gleixner 			break;
112531f4233bSNicolas Pitre 		}
11264b78fc42SChristian Riesch 		if (!ret) {
112731f4233bSNicolas Pitre 			if (cmd == OTPGETREGIONCOUNT) {
11284b78fc42SChristian Riesch 				int nbr = retlen / sizeof(struct otp_info);
112931f4233bSNicolas Pitre 				ret = copy_to_user(argp, &nbr, sizeof(int));
113031f4233bSNicolas Pitre 			} else
11314b78fc42SChristian Riesch 				ret = copy_to_user(argp, buf, retlen);
113231f4233bSNicolas Pitre 			if (ret)
113331f4233bSNicolas Pitre 				ret = -EFAULT;
113431f4233bSNicolas Pitre 		}
113531f4233bSNicolas Pitre 		kfree(buf);
113631f4233bSNicolas Pitre 		break;
113731f4233bSNicolas Pitre 	}
113831f4233bSNicolas Pitre 
113931f4233bSNicolas Pitre 	case OTPLOCK:
1140e3c1f1c9SMichael Walle 	case OTPERASE:
114131f4233bSNicolas Pitre 	{
1142175428b2SHarvey Harrison 		struct otp_info oinfo;
114331f4233bSNicolas Pitre 
1144beb133fcSBrian Norris 		if (mfi->mode != MTD_FILE_MODE_OTP_USER)
114531f4233bSNicolas Pitre 			return -EINVAL;
1146175428b2SHarvey Harrison 		if (copy_from_user(&oinfo, argp, sizeof(oinfo)))
114731f4233bSNicolas Pitre 			return -EFAULT;
1148e3c1f1c9SMichael Walle 		if (cmd == OTPLOCK)
11494403dbfbSArtem Bityutskiy 			ret = mtd_lock_user_prot_reg(mtd, oinfo.start, oinfo.length);
1150e3c1f1c9SMichael Walle 		else
1151e3c1f1c9SMichael Walle 			ret = mtd_erase_user_prot_reg(mtd, oinfo.start, oinfo.length);
115231f4233bSNicolas Pitre 		break;
115331f4233bSNicolas Pitre 	}
115431f4233bSNicolas Pitre 
11557854d3f7SBrian Norris 	/* This ioctl is being deprecated - it truncates the ECC layout */
1156f1a28c02SThomas Gleixner 	case ECCGETLAYOUT:
1157f1a28c02SThomas Gleixner 	{
1158cc26c3cdSBrian Norris 		struct nand_ecclayout_user *usrlay;
1159cc26c3cdSBrian Norris 
116046b5889cSMiquel Raynal 		if (!master->ooblayout)
1161f1a28c02SThomas Gleixner 			return -EOPNOTSUPP;
1162f1a28c02SThomas Gleixner 
1163cc26c3cdSBrian Norris 		usrlay = kmalloc(sizeof(*usrlay), GFP_KERNEL);
1164cc26c3cdSBrian Norris 		if (!usrlay)
1165cc26c3cdSBrian Norris 			return -ENOMEM;
1166cc26c3cdSBrian Norris 
1167c2b78452SBoris Brezillon 		shrink_ecclayout(mtd, usrlay);
1168cc26c3cdSBrian Norris 
1169cc26c3cdSBrian Norris 		if (copy_to_user(argp, usrlay, sizeof(*usrlay)))
1170cc26c3cdSBrian Norris 			ret = -EFAULT;
1171cc26c3cdSBrian Norris 		kfree(usrlay);
1172f1a28c02SThomas Gleixner 		break;
1173f1a28c02SThomas Gleixner 	}
1174f1a28c02SThomas Gleixner 
1175f1a28c02SThomas Gleixner 	case ECCGETSTATS:
1176f1a28c02SThomas Gleixner 	{
1177f1a28c02SThomas Gleixner 		if (copy_to_user(argp, &mtd->ecc_stats,
1178f1a28c02SThomas Gleixner 				 sizeof(struct mtd_ecc_stats)))
1179f1a28c02SThomas Gleixner 			return -EFAULT;
1180f1a28c02SThomas Gleixner 		break;
1181f1a28c02SThomas Gleixner 	}
1182f1a28c02SThomas Gleixner 
1183f1a28c02SThomas Gleixner 	case MTDFILEMODE:
1184f1a28c02SThomas Gleixner 	{
1185f1a28c02SThomas Gleixner 		mfi->mode = 0;
1186f1a28c02SThomas Gleixner 
1187f1a28c02SThomas Gleixner 		switch(arg) {
1188beb133fcSBrian Norris 		case MTD_FILE_MODE_OTP_FACTORY:
1189beb133fcSBrian Norris 		case MTD_FILE_MODE_OTP_USER:
1190f1a28c02SThomas Gleixner 			ret = otp_select_filemode(mfi, arg);
1191f1a28c02SThomas Gleixner 			break;
1192f1a28c02SThomas Gleixner 
1193beb133fcSBrian Norris 		case MTD_FILE_MODE_RAW:
1194fc002e3cSArtem Bityutskiy 			if (!mtd_has_oob(mtd))
1195f1a28c02SThomas Gleixner 				return -EOPNOTSUPP;
1196f1a28c02SThomas Gleixner 			mfi->mode = arg;
11970975b633SGustavo A. R. Silva 			break;
1198f1a28c02SThomas Gleixner 
1199beb133fcSBrian Norris 		case MTD_FILE_MODE_NORMAL:
1200f1a28c02SThomas Gleixner 			break;
1201f1a28c02SThomas Gleixner 		default:
1202f1a28c02SThomas Gleixner 			ret = -EINVAL;
1203f1a28c02SThomas Gleixner 		}
1204f1a28c02SThomas Gleixner 		file->f_pos = 0;
1205f1a28c02SThomas Gleixner 		break;
1206f1a28c02SThomas Gleixner 	}
1207f1a28c02SThomas Gleixner 
1208d0f7959eSRoman Tereshonkov 	case BLKPG:
1209d0f7959eSRoman Tereshonkov 	{
121053bb724fSBrian Norris 		struct blkpg_ioctl_arg __user *blk_arg = argp;
121153bb724fSBrian Norris 		struct blkpg_ioctl_arg a;
121253bb724fSBrian Norris 
121353bb724fSBrian Norris 		if (copy_from_user(&a, blk_arg, sizeof(a)))
121453bb724fSBrian Norris 			ret = -EFAULT;
121553bb724fSBrian Norris 		else
121653bb724fSBrian Norris 			ret = mtdchar_blkpg_ioctl(mtd, &a);
1217d0f7959eSRoman Tereshonkov 		break;
1218d0f7959eSRoman Tereshonkov 	}
1219d0f7959eSRoman Tereshonkov 
1220d0f7959eSRoman Tereshonkov 	case BLKRRPART:
1221d0f7959eSRoman Tereshonkov 	{
1222d0f7959eSRoman Tereshonkov 		/* No reread partition feature. Just return ok */
1223d0f7959eSRoman Tereshonkov 		ret = 0;
1224d0f7959eSRoman Tereshonkov 		break;
1225d0f7959eSRoman Tereshonkov 	}
12261da177e4SLinus Torvalds 	}
12271da177e4SLinus Torvalds 
12281da177e4SLinus Torvalds 	return ret;
12291da177e4SLinus Torvalds } /* memory_ioctl */
12301da177e4SLinus Torvalds 
mtdchar_unlocked_ioctl(struct file * file,u_int cmd,u_long arg)1231969e57adSArtem Bityutskiy static long mtdchar_unlocked_ioctl(struct file *file, u_int cmd, u_long arg)
123255929332SArnd Bergmann {
12331ad55288SAlexander Sverdlin 	struct mtd_file_info *mfi = file->private_data;
12341ad55288SAlexander Sverdlin 	struct mtd_info *mtd = mfi->mtd;
12351ad55288SAlexander Sverdlin 	struct mtd_info *master = mtd_get_master(mtd);
123655929332SArnd Bergmann 	int ret;
123755929332SArnd Bergmann 
12381ad55288SAlexander Sverdlin 	mutex_lock(&master->master.chrdev_lock);
1239969e57adSArtem Bityutskiy 	ret = mtdchar_ioctl(file, cmd, arg);
12401ad55288SAlexander Sverdlin 	mutex_unlock(&master->master.chrdev_lock);
124155929332SArnd Bergmann 
124255929332SArnd Bergmann 	return ret;
124355929332SArnd Bergmann }
124455929332SArnd Bergmann 
124597718540SKevin Cernekee #ifdef CONFIG_COMPAT
124697718540SKevin Cernekee 
124797718540SKevin Cernekee struct mtd_oob_buf32 {
124897718540SKevin Cernekee 	u_int32_t start;
124997718540SKevin Cernekee 	u_int32_t length;
125097718540SKevin Cernekee 	compat_caddr_t ptr;	/* unsigned char* */
125197718540SKevin Cernekee };
125297718540SKevin Cernekee 
125397718540SKevin Cernekee #define MEMWRITEOOB32		_IOWR('M', 3, struct mtd_oob_buf32)
125497718540SKevin Cernekee #define MEMREADOOB32		_IOWR('M', 4, struct mtd_oob_buf32)
125597718540SKevin Cernekee 
mtdchar_compat_ioctl(struct file * file,unsigned int cmd,unsigned long arg)1256969e57adSArtem Bityutskiy static long mtdchar_compat_ioctl(struct file *file, unsigned int cmd,
125797718540SKevin Cernekee 	unsigned long arg)
125897718540SKevin Cernekee {
125997718540SKevin Cernekee 	struct mtd_file_info *mfi = file->private_data;
126097718540SKevin Cernekee 	struct mtd_info *mtd = mfi->mtd;
12611ad55288SAlexander Sverdlin 	struct mtd_info *master = mtd_get_master(mtd);
12620b6585ceSDavid Woodhouse 	void __user *argp = compat_ptr(arg);
126397718540SKevin Cernekee 	int ret = 0;
126497718540SKevin Cernekee 
12651ad55288SAlexander Sverdlin 	mutex_lock(&master->master.chrdev_lock);
126697718540SKevin Cernekee 
126797718540SKevin Cernekee 	switch (cmd) {
126897718540SKevin Cernekee 	case MEMWRITEOOB32:
126997718540SKevin Cernekee 	{
127097718540SKevin Cernekee 		struct mtd_oob_buf32 buf;
127197718540SKevin Cernekee 		struct mtd_oob_buf32 __user *buf_user = argp;
127297718540SKevin Cernekee 
1273f7e6b19bSGreg Kroah-Hartman 		if (!(file->f_mode & FMODE_WRITE)) {
1274f7e6b19bSGreg Kroah-Hartman 			ret = -EPERM;
1275f7e6b19bSGreg Kroah-Hartman 			break;
1276f7e6b19bSGreg Kroah-Hartman 		}
1277f7e6b19bSGreg Kroah-Hartman 
127897718540SKevin Cernekee 		if (copy_from_user(&buf, argp, sizeof(buf)))
127997718540SKevin Cernekee 			ret = -EFAULT;
128097718540SKevin Cernekee 		else
1281969e57adSArtem Bityutskiy 			ret = mtdchar_writeoob(file, mtd, buf.start,
128297718540SKevin Cernekee 				buf.length, compat_ptr(buf.ptr),
128397718540SKevin Cernekee 				&buf_user->length);
128497718540SKevin Cernekee 		break;
128597718540SKevin Cernekee 	}
128697718540SKevin Cernekee 
128797718540SKevin Cernekee 	case MEMREADOOB32:
128897718540SKevin Cernekee 	{
128997718540SKevin Cernekee 		struct mtd_oob_buf32 buf;
129097718540SKevin Cernekee 		struct mtd_oob_buf32 __user *buf_user = argp;
129197718540SKevin Cernekee 
129297718540SKevin Cernekee 		/* NOTE: writes return length to buf->start */
129397718540SKevin Cernekee 		if (copy_from_user(&buf, argp, sizeof(buf)))
129497718540SKevin Cernekee 			ret = -EFAULT;
129597718540SKevin Cernekee 		else
1296969e57adSArtem Bityutskiy 			ret = mtdchar_readoob(file, mtd, buf.start,
129797718540SKevin Cernekee 				buf.length, compat_ptr(buf.ptr),
129897718540SKevin Cernekee 				&buf_user->start);
129997718540SKevin Cernekee 		break;
130097718540SKevin Cernekee 	}
130153bb724fSBrian Norris 
130253bb724fSBrian Norris 	case BLKPG:
130353bb724fSBrian Norris 	{
130453bb724fSBrian Norris 		/* Convert from blkpg_compat_ioctl_arg to blkpg_ioctl_arg */
130553bb724fSBrian Norris 		struct blkpg_compat_ioctl_arg __user *uarg = argp;
130653bb724fSBrian Norris 		struct blkpg_compat_ioctl_arg compat_arg;
130753bb724fSBrian Norris 		struct blkpg_ioctl_arg a;
130853bb724fSBrian Norris 
130953bb724fSBrian Norris 		if (copy_from_user(&compat_arg, uarg, sizeof(compat_arg))) {
131053bb724fSBrian Norris 			ret = -EFAULT;
131153bb724fSBrian Norris 			break;
131253bb724fSBrian Norris 		}
131353bb724fSBrian Norris 
131453bb724fSBrian Norris 		memset(&a, 0, sizeof(a));
131553bb724fSBrian Norris 		a.op = compat_arg.op;
131653bb724fSBrian Norris 		a.flags = compat_arg.flags;
131753bb724fSBrian Norris 		a.datalen = compat_arg.datalen;
131853bb724fSBrian Norris 		a.data = compat_ptr(compat_arg.data);
131953bb724fSBrian Norris 
132053bb724fSBrian Norris 		ret = mtdchar_blkpg_ioctl(mtd, &a);
132153bb724fSBrian Norris 		break;
132253bb724fSBrian Norris 	}
132353bb724fSBrian Norris 
132497718540SKevin Cernekee 	default:
1325969e57adSArtem Bityutskiy 		ret = mtdchar_ioctl(file, cmd, (unsigned long)argp);
132697718540SKevin Cernekee 	}
132797718540SKevin Cernekee 
13281ad55288SAlexander Sverdlin 	mutex_unlock(&master->master.chrdev_lock);
132997718540SKevin Cernekee 
133097718540SKevin Cernekee 	return ret;
133197718540SKevin Cernekee }
133297718540SKevin Cernekee 
133397718540SKevin Cernekee #endif /* CONFIG_COMPAT */
133497718540SKevin Cernekee 
1335402d3265SDavid Howells /*
1336402d3265SDavid Howells  * try to determine where a shared mapping can be made
1337402d3265SDavid Howells  * - only supported for NOMMU at the moment (MMU can't doesn't copy private
1338402d3265SDavid Howells  *   mappings)
1339402d3265SDavid Howells  */
1340402d3265SDavid Howells #ifndef CONFIG_MMU
mtdchar_get_unmapped_area(struct file * file,unsigned long addr,unsigned long len,unsigned long pgoff,unsigned long flags)1341969e57adSArtem Bityutskiy static unsigned long mtdchar_get_unmapped_area(struct file *file,
1342402d3265SDavid Howells 					   unsigned long addr,
1343402d3265SDavid Howells 					   unsigned long len,
1344402d3265SDavid Howells 					   unsigned long pgoff,
1345402d3265SDavid Howells 					   unsigned long flags)
1346402d3265SDavid Howells {
1347402d3265SDavid Howells 	struct mtd_file_info *mfi = file->private_data;
1348402d3265SDavid Howells 	struct mtd_info *mtd = mfi->mtd;
1349402d3265SDavid Howells 	unsigned long offset;
1350cd621274SArtem Bityutskiy 	int ret;
1351402d3265SDavid Howells 
1352402d3265SDavid Howells 	if (addr != 0)
1353402d3265SDavid Howells 		return (unsigned long) -EINVAL;
1354402d3265SDavid Howells 
1355402d3265SDavid Howells 	if (len > mtd->size || pgoff >= (mtd->size >> PAGE_SHIFT))
1356402d3265SDavid Howells 		return (unsigned long) -EINVAL;
1357402d3265SDavid Howells 
1358402d3265SDavid Howells 	offset = pgoff << PAGE_SHIFT;
1359402d3265SDavid Howells 	if (offset > mtd->size - len)
1360402d3265SDavid Howells 		return (unsigned long) -EINVAL;
1361402d3265SDavid Howells 
1362cd621274SArtem Bityutskiy 	ret = mtd_get_unmapped_area(mtd, len, offset, flags);
1363b9995932SVladimir Zapolskiy 	return ret == -EOPNOTSUPP ? -ENODEV : ret;
1364402d3265SDavid Howells }
1365b4caecd4SChristoph Hellwig 
mtdchar_mmap_capabilities(struct file * file)1366b4caecd4SChristoph Hellwig static unsigned mtdchar_mmap_capabilities(struct file *file)
1367b4caecd4SChristoph Hellwig {
1368b4caecd4SChristoph Hellwig 	struct mtd_file_info *mfi = file->private_data;
1369b4caecd4SChristoph Hellwig 
1370b4caecd4SChristoph Hellwig 	return mtd_mmap_capabilities(mfi->mtd);
1371b4caecd4SChristoph Hellwig }
1372402d3265SDavid Howells #endif
1373402d3265SDavid Howells 
1374402d3265SDavid Howells /*
1375402d3265SDavid Howells  * set up a mapping for shared memory segments
1376402d3265SDavid Howells  */
mtdchar_mmap(struct file * file,struct vm_area_struct * vma)1377969e57adSArtem Bityutskiy static int mtdchar_mmap(struct file *file, struct vm_area_struct *vma)
1378402d3265SDavid Howells {
1379402d3265SDavid Howells #ifdef CONFIG_MMU
1380402d3265SDavid Howells 	struct mtd_file_info *mfi = file->private_data;
1381402d3265SDavid Howells 	struct mtd_info *mtd = mfi->mtd;
1382dd02b67dSAnatolij Gustschin 	struct map_info *map = mtd->priv;
1383402d3265SDavid Howells 
1384f5cf8f07SDavid Woodhouse         /* This is broken because it assumes the MTD device is map-based
1385f5cf8f07SDavid Woodhouse 	   and that mtd->priv is a valid struct map_info.  It should be
1386f5cf8f07SDavid Woodhouse 	   replaced with something that uses the mtd_get_unmapped_area()
1387f5cf8f07SDavid Woodhouse 	   operation properly. */
1388f5cf8f07SDavid Woodhouse 	if (0 /*mtd->type == MTD_RAM || mtd->type == MTD_ROM*/) {
1389dd02b67dSAnatolij Gustschin #ifdef pgprot_noncached
13908558e4a2SLinus Torvalds 		if (file->f_flags & O_DSYNC || map->phys >= __pa(high_memory))
1391dd02b67dSAnatolij Gustschin 			vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
1392dd02b67dSAnatolij Gustschin #endif
13938558e4a2SLinus Torvalds 		return vm_iomap_memory(vma, map->phys, map->size);
1394dd02b67dSAnatolij Gustschin 	}
1395b9995932SVladimir Zapolskiy 	return -ENODEV;
1396402d3265SDavid Howells #else
1397b9995932SVladimir Zapolskiy 	return vma->vm_flags & VM_SHARED ? 0 : -EACCES;
1398402d3265SDavid Howells #endif
1399402d3265SDavid Howells }
1400402d3265SDavid Howells 
1401d54b1fdbSArjan van de Ven static const struct file_operations mtd_fops = {
14021da177e4SLinus Torvalds 	.owner		= THIS_MODULE,
1403969e57adSArtem Bityutskiy 	.llseek		= mtdchar_lseek,
1404969e57adSArtem Bityutskiy 	.read		= mtdchar_read,
1405969e57adSArtem Bityutskiy 	.write		= mtdchar_write,
1406969e57adSArtem Bityutskiy 	.unlocked_ioctl	= mtdchar_unlocked_ioctl,
140797718540SKevin Cernekee #ifdef CONFIG_COMPAT
1408969e57adSArtem Bityutskiy 	.compat_ioctl	= mtdchar_compat_ioctl,
140997718540SKevin Cernekee #endif
1410969e57adSArtem Bityutskiy 	.open		= mtdchar_open,
1411969e57adSArtem Bityutskiy 	.release	= mtdchar_close,
1412969e57adSArtem Bityutskiy 	.mmap		= mtdchar_mmap,
1413402d3265SDavid Howells #ifndef CONFIG_MMU
1414969e57adSArtem Bityutskiy 	.get_unmapped_area = mtdchar_get_unmapped_area,
1415b4caecd4SChristoph Hellwig 	.mmap_capabilities = mtdchar_mmap_capabilities,
1416402d3265SDavid Howells #endif
14171da177e4SLinus Torvalds };
14181da177e4SLinus Torvalds 
init_mtdchar(void)1419660685d9SArtem Bityutskiy int __init init_mtdchar(void)
1420cd874237SKirill A. Shutemov {
1421cd874237SKirill A. Shutemov 	int ret;
1422cd874237SKirill A. Shutemov 
1423cd874237SKirill A. Shutemov 	ret = __register_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS,
1424cd874237SKirill A. Shutemov 				   "mtd", &mtd_fops);
1425cd874237SKirill A. Shutemov 	if (ret < 0) {
142657ae2b60SArtem Bityutskiy 		pr_err("Can't allocate major number %d for MTD\n",
142757ae2b60SArtem Bityutskiy 		       MTD_CHAR_MAJOR);
1428cd874237SKirill A. Shutemov 		return ret;
1429cd874237SKirill A. Shutemov 	}
1430cd874237SKirill A. Shutemov 
1431cd874237SKirill A. Shutemov 	return ret;
14321da177e4SLinus Torvalds }
14331da177e4SLinus Torvalds 
cleanup_mtdchar(void)1434660685d9SArtem Bityutskiy void __exit cleanup_mtdchar(void)
14351da177e4SLinus Torvalds {
1436dad0db31SBen Hutchings 	__unregister_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS, "mtd");
14371da177e4SLinus Torvalds }
14381da177e4SLinus Torvalds 
143990160e13SScott James Remnant MODULE_ALIAS_CHARDEV_MAJOR(MTD_CHAR_MAJOR);
1440