/*
* Character-device access to raw MTD devices.
*
*/
#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/smp_lock.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/compatmac.h>
#include <asm/uaccess.h>
static struct class *mtd_class;
static void mtd_notify_add(struct mtd_info* mtd)
{
if (!mtd)
return;
device_create_drvdata(mtd_class, NULL,
MKDEV(MTD_CHAR_MAJOR, mtd->index*2),
NULL, "mtd%d", mtd->index);
device_create_drvdata(mtd_class, NULL,
MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1),
NULL, "mtd%dro", mtd->index);
}
static void mtd_notify_remove(struct mtd_info* mtd)
{
if (!mtd)
return;
device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2));
device_destroy(mtd_class, MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1));
}
static struct mtd_notifier notifier = {
.add = mtd_notify_add,
.remove = mtd_notify_remove,
};
/*
* Data structure to hold the pointer to the mtd device as well
* as mode information ofr various use cases.
*/
struct mtd_file_info {
struct mtd_info *mtd;
enum mtd_file_modes mode;
};
static loff_t mtd_lseek (struct file *file, loff_t offset, int orig)
{
struct mtd_file_info *mfi = file->private_data;
struct mtd_info *mtd = mfi->mtd;
switch (orig) {
case SEEK_SET:
break;
case SEEK_CUR:
offset += file->f_pos;
break;
case SEEK_END:
offset += mtd->size;
break;
default:
return -EINVAL;
}
if (offset >= 0 && offset <= mtd->size)
return file->f_pos = offset;
return -EINVAL;
}
static int mtd_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;
DEBUG(MTD_DEBUG_LEVEL0, "MTD_open\n");
if (devnum >= MAX_MTD_DEVICES)
return -ENODEV;
/* You can't open the RO devices RW */
if ((file->f_mode & 2) && (minor & 1))
return -EACCES;
lock_kernel();
mtd = get_mtd_device(NULL, devnum);
if (IS_ERR(mtd)) {
ret = PTR_ERR(mtd);
goto out;
}
if (MTD_ABSENT == mtd->type) {
put_mtd_device(mtd);
ret = -ENODEV;
goto out;
}
/* You can't open it RW if it's not a writeable device */
if ((file->f_mode & 2) && !(mtd->flags & MTD_WRITEABLE)) {
put_mtd_device(mtd);
ret = -EACCES;
goto out;
}
mfi = kzalloc(sizeof(*mfi), GFP_KERNEL);
if (!mfi) {
put_mtd_device(mtd);
ret = -ENOMEM;
goto out;
}
mfi->mtd = mtd;
file->private_data = mfi;
out:
unlock_kernel();
return ret;
} /* mtd_open */
/*====================================================================*/
static int mtd_close(struct inode *inode, struct file *file)
{
struct mtd_file_info *mfi = file->private_data;
struct mtd_info *mtd = mfi->mtd;
DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n");
/* Only sync if opened RW */
if ((file->f_mode & 2) && mtd->sync)
mtd->sync(mtd);
put_mtd_device(mtd);
file->private_data = NULL;
kfree(mfi);
return 0;
} /* mtd_close */
/* FIXME: This _really_ needs to die. In 2.5, we should lock the
userspace buffer down and use it directly with readv/writev.
*/
#define MAX_KMALLOC_SIZE 0x20000
static ssize_t mtd_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=0;
size_t total_retlen=0;
int ret=0;
int len;
char *kbuf;
DEBUG(MTD_DEBUG_LEVEL0,"MTD_read\n");
if (*ppos + count > mtd->size)
count = mtd->size - *ppos;
if (!count)
return 0;
/* FIXME: Use kiovec in 2.5 to lock down the user's buffers
and pass them directly to the MTD functions */
if (count > MAX_KMALLOC_SIZE)
kbuf=kmalloc(MAX_KMALLOC_SIZE, GFP_KERNEL);
else
kbuf=kmalloc(count, GFP_KERNEL);
if (!kbuf)
return -ENOMEM;
while (count) {
if (count > MAX_KMALLOC_SIZE)
len = MAX_KMALLOC_SIZE;