1426 lines · 30.5 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * Copyright © 1999-2010 David Woodhouse <dwmw2@infradead.org> */ #include <linux/device.h>#include <linux/fs.h>#include <linux/mm.h>#include <linux/err.h>#include <linux/init.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/sched.h>#include <linux/mutex.h>#include <linux/backing-dev.h>#include <linux/compat.h>#include <linux/mount.h>#include <linux/blkpg.h>#include <linux/magic.h>#include <linux/major.h>#include <linux/mtd/mtd.h>#include <linux/mtd/partitions.h>#include <linux/mtd/map.h> #include <linux/uaccess.h> #include "mtdcore.h" /* * Data structure to hold the pointer to the mtd device as well * as mode information of various use cases. */struct mtd_file_info {	struct mtd_info *mtd;	enum mtd_file_modes mode;}; static loff_t mtdchar_lseek(struct file *file, loff_t offset, int orig){	struct mtd_file_info *mfi = file->private_data;	return fixed_size_llseek(file, offset, orig, mfi->mtd->size);} static int mtdchar_open(struct inode *inode, struct file *file){	int minor = iminor(inode);	int devnum = minor >> 1;	int ret = 0;	struct mtd_info *mtd;	struct mtd_file_info *mfi; 	pr_debug("MTD_open\n"); 	/* You can't open the RO devices RW */	if ((file->f_mode & FMODE_WRITE) && (minor & 1))		return -EACCES; 	mtd = get_mtd_device(NULL, devnum); 	if (IS_ERR(mtd))		return PTR_ERR(mtd); 	if (mtd->type == MTD_ABSENT) {		ret = -ENODEV;		goto out1;	} 	/* You can't open it RW if it's not a writeable device */	if ((file->f_mode & FMODE_WRITE) && !(mtd->flags & MTD_WRITEABLE)) {		ret = -EACCES;		goto out1;	} 	mfi = kzalloc_obj(*mfi);	if (!mfi) {		ret = -ENOMEM;		goto out1;	}	mfi->mtd = mtd;	file->private_data = mfi;	return 0; out1:	put_mtd_device(mtd);	return ret;} /* mtdchar_open */ /*====================================================================*/ static int mtdchar_close(struct inode *inode, struct file *file){	struct mtd_file_info *mfi = file->private_data;	struct mtd_info *mtd = mfi->mtd; 	pr_debug("MTD_close\n"); 	/* Only sync if opened RW */	if ((file->f_mode & FMODE_WRITE))		mtd_sync(mtd); 	put_mtd_device(mtd);	file->private_data = NULL;	kfree(mfi); 	return 0;} /* mtdchar_close */ /* Back in June 2001, dwmw2 wrote: * *   FIXME: This _really_ needs to die. In 2.5, we should lock the *   userspace buffer down and use it directly with readv/writev. * * The implementation below, using mtd_kmalloc_up_to, mitigates * allocation failures when the system is under low-memory situations * or if memory is highly fragmented at the cost of reducing the * performance of the requested transfer due to a smaller buffer size. * * A more complex but more memory-efficient implementation based on * get_user_pages and iovecs to cover extents of those pages is a * longer-term goal, as intimated by dwmw2 above. However, for the * write case, this requires yet more complex head and tail transfer * handling when those head and tail offsets and sizes are such that * alignment requirements are not met in the NAND subdriver. */ static ssize_t mtdchar_read(struct file *file, char __user *buf, size_t count,			loff_t *ppos){	struct mtd_file_info *mfi = file->private_data;	struct mtd_info *mtd = mfi->mtd;	size_t retlen;	size_t total_retlen=0;	int ret=0;	int len;	size_t size = count;	char *kbuf; 	pr_debug("MTD_read\n"); 	if (*ppos + count > mtd->size) {		if (*ppos < mtd->size)			count = mtd->size - *ppos;		else			count = 0;	} 	if (!count)		return 0; 	kbuf = mtd_kmalloc_up_to(mtd, &size);	if (!kbuf)		return -ENOMEM; 	while (count) {		len = min_t(size_t, count, size); 		switch (mfi->mode) {		case MTD_FILE_MODE_OTP_FACTORY:			ret = mtd_read_fact_prot_reg(mtd, *ppos, len,						     &retlen, kbuf);			break;		case MTD_FILE_MODE_OTP_USER:			ret = mtd_read_user_prot_reg(mtd, *ppos, len,						     &retlen, kbuf);			break;		case MTD_FILE_MODE_RAW:		{			struct mtd_oob_ops ops = {}; 			ops.mode = MTD_OPS_RAW;			ops.datbuf = kbuf;			ops.oobbuf = NULL;			ops.len = len; 			ret = mtd_read_oob(mtd, *ppos, &ops);			retlen = ops.retlen;			break;		}		default:			ret = mtd_read(mtd, *ppos, len, &retlen, kbuf);		}		/* Nand returns -EBADMSG on ECC errors, but it returns		 * the data. For our userspace tools it is important		 * to dump areas with ECC errors!		 * For kernel internal usage it also might return -EUCLEAN		 * to signal the caller that a bitflip has occurred and has		 * been corrected by the ECC algorithm.		 * Userspace software which accesses NAND this way		 * must be aware of the fact that it deals with NAND		 */		if (!ret || mtd_is_bitflip_or_eccerr(ret)) {			*ppos += retlen;			if (copy_to_user(buf, kbuf, retlen)) {				kfree(kbuf);				return -EFAULT;			}			else				total_retlen += retlen; 			count -= retlen;			buf += retlen;			if (retlen == 0)				count = 0;		}		else {			kfree(kbuf);			return ret;		} 	} 	kfree(kbuf);	return total_retlen;} /* mtdchar_read */ static ssize_t mtdchar_write(struct file *file, const char __user *buf, size_t count,			loff_t *ppos){	struct mtd_file_info *mfi = file->private_data;	struct mtd_info *mtd = mfi->mtd;	size_t size = count;	char *kbuf;	size_t retlen;	size_t total_retlen=0;	int ret=0;	int len; 	pr_debug("MTD_write\n"); 	if (*ppos >= mtd->size)		return -ENOSPC; 	if (*ppos + count > mtd->size)		count = mtd->size - *ppos; 	if (!count)		return 0; 	kbuf = mtd_kmalloc_up_to(mtd, &size);	if (!kbuf)		return -ENOMEM; 	while (count) {		len = min_t(size_t, count, size); 		if (copy_from_user(kbuf, buf, len)) {			kfree(kbuf);			return -EFAULT;		} 		switch (mfi->mode) {		case MTD_FILE_MODE_OTP_FACTORY:			ret = -EROFS;			break;		case MTD_FILE_MODE_OTP_USER:			ret = mtd_write_user_prot_reg(mtd, *ppos, len,						      &retlen, kbuf);			break; 		case MTD_FILE_MODE_RAW:		{			struct mtd_oob_ops ops = {}; 			ops.mode = MTD_OPS_RAW;			ops.datbuf = kbuf;			ops.oobbuf = NULL;			ops.ooboffs = 0;			ops.len = len; 			ret = mtd_write_oob(mtd, *ppos, &ops);			retlen = ops.retlen;			break;		} 		default:			ret = mtd_write(mtd, *ppos, len, &retlen, kbuf);		} 		/*		 * Return -ENOSPC only if no data could be written at all.		 * Otherwise just return the number of bytes that actually		 * have been written.		 */		if ((ret == -ENOSPC) && (total_retlen))			break; 		if (!ret) {			*ppos += retlen;			total_retlen += retlen;			count -= retlen;			buf += retlen;		}		else {			kfree(kbuf);			return ret;		}	} 	kfree(kbuf);	return total_retlen;} /* mtdchar_write */ /*======================================================================     IOCTL calls for getting device parameters. ======================================================================*/ static int otp_select_filemode(struct mtd_file_info *mfi, int mode){	struct mtd_info *mtd = mfi->mtd;	size_t retlen; 	switch (mode) {	case MTD_OTP_FACTORY:		if (mtd_read_fact_prot_reg(mtd, -1, 0, &retlen, NULL) ==				-EOPNOTSUPP)			return -EOPNOTSUPP; 		mfi->mode = MTD_FILE_MODE_OTP_FACTORY;		break;	case MTD_OTP_USER:		if (mtd_read_user_prot_reg(mtd, -1, 0, &retlen, NULL) ==				-EOPNOTSUPP)			return -EOPNOTSUPP; 		mfi->mode = MTD_FILE_MODE_OTP_USER;		break;	case MTD_OTP_OFF:		mfi->mode = MTD_FILE_MODE_NORMAL;		break;	default:		return -EINVAL;	} 	return 0;} static int mtdchar_writeoob(struct file *file, struct mtd_info *mtd,	uint64_t start, uint32_t length, void __user *ptr,	uint32_t __user *retp){	struct mtd_info *master  = mtd_get_master(mtd);	struct mtd_file_info *mfi = file->private_data;	struct mtd_oob_ops ops = {};	uint32_t retlen;	int ret = 0; 	if (length > 4096)		return -EINVAL; 	if (!master->_write_oob)		return -EOPNOTSUPP; 	ops.ooblen = length;	ops.ooboffs = start & (mtd->writesize - 1);	ops.datbuf = NULL;	ops.mode = (mfi->mode == MTD_FILE_MODE_RAW) ? MTD_OPS_RAW :		MTD_OPS_PLACE_OOB; 	if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs))		return -EINVAL; 	ops.oobbuf = memdup_user(ptr, length);	if (IS_ERR(ops.oobbuf))		return PTR_ERR(ops.oobbuf); 	start &= ~((uint64_t)mtd->writesize - 1);	ret = mtd_write_oob(mtd, start, &ops); 	if (ops.oobretlen > 0xFFFFFFFFU)		ret = -EOVERFLOW;	retlen = ops.oobretlen;	if (copy_to_user(retp, &retlen, sizeof(length)))		ret = -EFAULT; 	kfree(ops.oobbuf);	return ret;} static int mtdchar_readoob(struct file *file, struct mtd_info *mtd,	uint64_t start, uint32_t length, void __user *ptr,	uint32_t __user *retp){	struct mtd_file_info *mfi = file->private_data;	struct mtd_oob_ops ops = {};	int ret = 0; 	if (length > 4096)		return -EINVAL; 	ops.ooblen = length;	ops.ooboffs = start & (mtd->writesize - 1);	ops.datbuf = NULL;	ops.mode = (mfi->mode == MTD_FILE_MODE_RAW) ? MTD_OPS_RAW :		MTD_OPS_PLACE_OOB; 	if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs))		return -EINVAL; 	ops.oobbuf = kmalloc(length, GFP_KERNEL);	if (!ops.oobbuf)		return -ENOMEM; 	start &= ~((uint64_t)mtd->writesize - 1);	ret = mtd_read_oob(mtd, start, &ops); 	if (put_user(ops.oobretlen, retp))		ret = -EFAULT;	else if (ops.oobretlen && copy_to_user(ptr, ops.oobbuf,					    ops.oobretlen))		ret = -EFAULT; 	kfree(ops.oobbuf); 	/*	 * NAND returns -EBADMSG on ECC errors, but it returns the OOB	 * data. For our userspace tools it is important to dump areas	 * with ECC errors!	 * For kernel internal usage it also might return -EUCLEAN	 * to signal the caller that a bitflip has occurred and has	 * been corrected by the ECC algorithm.	 *	 * Note: currently the standard NAND function, nand_read_oob_std,	 * does not calculate ECC for the OOB area, so do not rely on	 * this behavior unless you have replaced it with your own.	 */	if (mtd_is_bitflip_or_eccerr(ret))		return 0; 	return ret;} /* * Copies (and truncates, if necessary) OOB layout information to the * deprecated layout struct, nand_ecclayout_user. This is necessary only to * support the deprecated API ioctl ECCGETLAYOUT while allowing all new * functionality to use mtd_ooblayout_ops flexibly (i.e. mtd_ooblayout_ops * can describe any kind of OOB layout with almost zero overhead from a * memory usage point of view). */static int shrink_ecclayout(struct mtd_info *mtd,			    struct nand_ecclayout_user *to){	struct mtd_oob_region oobregion;	int i, section = 0, ret; 	if (!mtd || !to)		return -EINVAL; 	memset(to, 0, sizeof(*to)); 	to->eccbytes = 0;	for (i = 0; i < MTD_MAX_ECCPOS_ENTRIES;) {		u32 eccpos; 		ret = mtd_ooblayout_ecc(mtd, section++, &oobregion);		if (ret < 0) {			if (ret != -ERANGE)				return ret; 			break;		} 		eccpos = oobregion.offset;		for (; i < MTD_MAX_ECCPOS_ENTRIES &&		       eccpos < oobregion.offset + oobregion.length; i++) {			to->eccpos[i] = eccpos++;			to->eccbytes++;		}	} 	for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES; i++) {		ret = mtd_ooblayout_free(mtd, i, &oobregion);		if (ret < 0) {			if (ret != -ERANGE)				return ret; 			break;		} 		to->oobfree[i].offset = oobregion.offset;		to->oobfree[i].length = oobregion.length;		to->oobavail += to->oobfree[i].length;	} 	return 0;} static int get_oobinfo(struct mtd_info *mtd, struct nand_oobinfo *to){	struct mtd_oob_region oobregion;	int i, section = 0, ret; 	if (!mtd || !to)		return -EINVAL; 	memset(to, 0, sizeof(*to)); 

Lines 1–500 of 1426.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t