/*
* Copyright © 1999-2010 David Woodhouse <dwmw2@infradead.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#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 <asm/uaccess.h>
#include "mtdcore.h"
static DEFINE_MUTEX(mtd_mutex);
/*
* 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;
mutex_lock(&mtd_mutex);
mtd = get_mtd_device(NULL, devnum);
if (IS_ERR(mtd)) {
ret = PTR_ERR(mtd);
goto out;
}
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(sizeof(*mfi), GFP_KERNEL);
if (!mfi) {
ret = -ENOMEM;
goto out1;
}
mfi->mtd = mtd;
file->private_data = mfi;
mutex_unlock(&mtd_mutex);
return 0;
out1:
put_mtd_device(mtd);
out:
mutex_unlock(&mtd_mutex);
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;