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