[PATCH 1/2] [MTD] CORE: New ioctl calls for >4GiB device support (take 2)
Kevin Cernekee
kpc.mtd at gmail.com
Fri Mar 20 01:37:09 EDT 2009
Extend the MTD user ABI to access >4GiB devices using 64-bit offsets.
Add compat_ioctl support to the MTD ABI.
New ioctls: MEMABIINFO MEMGETINFO64 MEMERASE64 MEMWRITEOOB64 MEMREADOOB64
MEMLOCK64 MEMUNLOCK64 MEMGETREGIONINFO64
Signed-off-by: Kevin Cernekee <kpc.mtd at gmail.com>
---
drivers/mtd/compat_ioctl.c | 233 +++++++++++++
drivers/mtd/compat_ioctl.h | 89 +++++
drivers/mtd/mtdchar.c | 817 ++++++++++++++++++++++++++++---------------
include/mtd/mtd-abi.h | 69 ++++-
include/mtd/mtd-user.h | 4 +
5 files changed, 921 insertions(+), 291 deletions(-)
create mode 100644 drivers/mtd/compat_ioctl.c
create mode 100644 drivers/mtd/compat_ioctl.h
diff --git a/drivers/mtd/compat_ioctl.c b/drivers/mtd/compat_ioctl.c
new file mode 100644
index 0000000..ee98f71
--- /dev/null
+++ b/drivers/mtd/compat_ioctl.c
@@ -0,0 +1,233 @@
+/*
+ * MTD compat_ioctl implementation
+ */
+
+#include <linux/compat.h>
+#include <linux/ioctl.h>
+#include <linux/smp_lock.h>
+#include <linux/uaccess.h>
+#include <linux/fs.h>
+#include <linux/mtd/mtd.h>
+#include "compat_ioctl.h"
+
+static long mtd_compat_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct mtd_file_info *mfi = file->private_data;
+ struct mtd_info *mtd = mfi->mtd;
+ void __user *argp = (void __user *)arg;
+ int ret = 0;
+ u_long size;
+
+ DEBUG(MTD_DEBUG_LEVEL0, "MTD_ioctl\n");
+
+ lock_kernel(); /* just because mtd_ioctl() holds the BKL too */
+
+ size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT;
+ if (cmd & IOC_IN) {
+ if (!access_ok(VERIFY_READ, argp, size)) {
+ unlock_kernel();
+ return -EFAULT;
+ }
+ }
+ if (cmd & IOC_OUT) {
+ if (!access_ok(VERIFY_WRITE, argp, size)) {
+ unlock_kernel();
+ return -EFAULT;
+ }
+ }
+
+ switch (cmd) {
+ case MEMGETREGIONCOUNT_32:
+ {
+ compat_int_t numeraseregions = mtd->numeraseregions;
+ if (copy_to_user(argp, &numeraseregions, sizeof(compat_int_t)))
+ ret = -EFAULT;
+ break;
+ }
+
+ case MEMGETREGIONINFO_32:
+ ret = mtd_ioctl_getregioninfo(mtd, argp);
+ break;
+
+ case MEMGETREGIONINFO64_32:
+ ret = mtd_ioctl_getregioninfo64(mtd, argp);
+ break;
+
+ case MEMABIINFO_32:
+ ret = mtd_ioctl_getabiinfo(argp);
+ break;
+
+ case MEMGETINFO_32:
+ ret = mtd_ioctl_getinfo(mtd, argp);
+ break;
+
+ case MEMGETINFO64_32:
+ ret = mtd_ioctl_getinfo64(mtd, argp);
+ break;
+
+ case MEMERASE_32:
+ ret = mtd_ioctl_erase(file, mtd, argp);
+ break;
+
+ case MEMERASE64_32:
+ ret = mtd_ioctl_erase64(file, mtd, argp);
+ break;
+
+ case MEMWRITEOOB_32:
+ {
+ struct mtd_oob_buf_32 buf;
+ struct mtd_oob_buf_32 __user *buf_user = argp;
+
+ if (copy_from_user(&buf, argp, sizeof(buf)))
+ ret = -EFAULT;
+ else
+ ret = mtd_do_writeoob(file, mtd, buf.start, buf.length,
+ compat_ptr(buf.ptr), &buf_user->length);
+ break;
+ }
+
+ case MEMWRITEOOB64_32:
+ {
+ struct mtd_oob_buf64_32 buf;
+ struct mtd_oob_buf64_32 __user *buf_user = argp;
+
+ if (copy_from_user(&buf, argp, sizeof(buf)))
+ ret = -EFAULT;
+ else
+ ret = mtd_do_writeoob(file, mtd, buf.start, buf.length,
+ compat_ptr(buf.ptr), &buf_user->length);
+ break;
+ }
+
+ case MEMREADOOB_32:
+ {
+ struct mtd_oob_buf_32 buf;
+ struct mtd_oob_buf_32 __user *buf_user = argp;
+
+ /* NOTE: old ABI writes return length to buf->start */
+ if (copy_from_user(&buf, argp, sizeof(buf)))
+ ret = -EFAULT;
+ else
+ ret = mtd_do_readoob(mtd, buf.start, buf.length,
+ compat_ptr(buf.ptr), &buf_user->start);
+ break;
+ }
+
+ case MEMREADOOB64_32:
+ {
+ struct mtd_oob_buf64_32 buf;
+ struct mtd_oob_buf64_32 __user *buf_user = argp;
+
+ if (copy_from_user(&buf, argp, sizeof(buf)))
+ ret = -EFAULT;
+ else
+ ret = mtd_do_readoob(mtd, buf.start, buf.length,
+ compat_ptr(buf.ptr), &buf_user->length);
+ break;
+ }
+
+ case MEMLOCK_32:
+ ret = mtd_ioctl_lock(mtd, argp);
+ break;
+
+ case MEMLOCK64_32:
+ ret = mtd_ioctl_lock64(mtd, argp);
+ break;
+
+ case MEMUNLOCK_32:
+ ret = mtd_ioctl_unlock(mtd, argp);
+ break;
+
+ case MEMUNLOCK64_32:
+ ret = mtd_ioctl_unlock64(mtd, argp);
+ break;
+
+ /* Legacy interface */
+ case MEMGETOOBSEL_32:
+ ret = mtd_ioctl_oobsel(mtd, argp);
+ break;
+
+ case MEMGETBADBLOCK_32:
+ {
+ compat_loff_t offs;
+
+ if (copy_from_user(&offs, argp, sizeof(offs)))
+ ret = -EFAULT;
+ else {
+ if (!mtd->block_isbad)
+ ret = -EOPNOTSUPP;
+ else
+ ret = mtd->block_isbad(mtd, offs);
+ }
+ break;
+ }
+
+ case MEMSETBADBLOCK_32:
+ {
+ compat_loff_t offs;
+
+ if (copy_from_user(&offs, argp, sizeof(offs)))
+ ret = -EFAULT;
+ else {
+ if (!mtd->block_markbad)
+ ret = -EOPNOTSUPP;
+ else
+ ret = mtd->block_markbad(mtd, offs);
+ }
+ break;
+ }
+
+#ifdef CONFIG_HAVE_MTD_OTP
+ case OTPSELECT_32:
+ {
+ compat_int_t mode;
+ if (copy_from_user(&mode, argp, sizeof(mode)))
+ ret = -EFAULT;
+ else {
+ mfi->mode = MTD_MODE_NORMAL;
+ ret = otp_select_filemode(mfi, mode);
+ file->f_pos = 0;
+ }
+ break;
+ }
+
+ case OTPGETREGIONCOUNT_32:
+ {
+ int count;
+ compat_int_t __user *ucount = argp;
+
+ ret = mtd_ioctl_otpregioninfo(mfi, mtd, &count, NULL);
+ if(ret == 0 && put_user(count, ucount))
+ ret = -EFAULT;
+ break;
+ }
+
+ case OTPGETREGIONINFO_32:
+ ret = mtd_ioctl_otpregioninfo(mfi, mtd, NULL, argp);
+ break;
+
+ case OTPLOCK_32:
+ ret = mtd_ioctl_otplock(mfi, mtd, argp);
+ break;
+#endif
+
+ case ECCGETLAYOUT_32:
+ ret = mtd_ioctl_ecclayout(mtd, argp);
+ break;
+
+ case ECCGETSTATS_32:
+ ret = mtd_ioctl_eccstats(mtd, argp);
+ break;
+
+ case MTDFILEMODE_32:
+ ret = mtd_ioctl_filemode(file, mfi, mtd, arg);
+ break;
+
+ default:
+ ret = -ENOTTY;
+ }
+
+ unlock_kernel();
+ return(ret);
+}
diff --git a/drivers/mtd/compat_ioctl.h b/drivers/mtd/compat_ioctl.h
new file mode 100644
index 0000000..bdc429f
--- /dev/null
+++ b/drivers/mtd/compat_ioctl.h
@@ -0,0 +1,89 @@
+/*
+ * MTD compat_ioctl definitions
+ */
+
+#ifndef _MTD_COMPAT_IOCTL_H
+#define _MTD_COMPAT_IOCTL_H
+
+#ifdef CONFIG_COMPAT
+
+#include <linux/compat.h>
+#include <linux/ioctl.h>
+#include <linux/compiler.h>
+#include <linux/mtd/mtd.h>
+
+struct erase_info_user64_32 {
+ uint64_t start;
+ uint64_t length;
+ uint32_t res0[8];
+} __aligned(4) __packed; /* prevents tail padding */
+
+struct mtd_oob_buf_32 {
+ uint32_t start;
+ uint32_t length;
+ compat_uptr_t ptr;
+} __aligned(4) __packed;
+
+struct mtd_oob_buf64_32 {
+ uint64_t start;
+ uint32_t res0;
+ uint32_t length;
+ compat_uptr_t ptr;
+ uint32_t res2[8];
+} __aligned(4) __packed;
+
+struct mtd_info_user64_32 {
+ uint32_t type;
+ uint32_t flags;
+ uint64_t size;
+ uint32_t res0;
+ uint32_t erasesize;
+ uint32_t res1;
+ uint32_t writesize;
+ uint32_t res2;
+ uint32_t oobsize;
+ uint32_t res3[32];
+} __aligned(4) __packed;
+
+struct region_info_user64_32 {
+ uint64_t offset;
+ uint32_t res0;
+ uint32_t erasesize;
+ uint32_t res1;
+ uint32_t numblocks;
+ uint32_t res2;
+ uint32_t regionindex;
+ uint32_t res3[16];
+} __aligned(4) __packed;
+
+#define MEMGETINFO_32 _IOR ('M', 1, struct mtd_info_user)
+#define MEMERASE_32 _IOW ('M', 2, struct erase_info_user)
+#define MEMWRITEOOB_32 _IOWR('M', 3, struct mtd_oob_buf_32)
+#define MEMREADOOB_32 _IOWR('M', 4, struct mtd_oob_buf_32)
+#define MEMLOCK_32 _IOW ('M', 5, struct erase_info_user)
+#define MEMUNLOCK_32 _IOW ('M', 6, struct erase_info_user)
+#define MEMGETREGIONCOUNT_32 _IOR ('M', 7, compat_int_t)
+#define MEMGETREGIONINFO_32 _IOWR('M', 8, struct region_info_user)
+#define MEMSETOOBSEL_32 _IOW ('M', 9, struct nand_oobinfo)
+#define MEMGETOOBSEL_32 _IOR ('M', 10, struct nand_oobinfo)
+#define MEMGETBADBLOCK_32 _IOW ('M', 11, compat_loff_t)
+#define MEMSETBADBLOCK_32 _IOW ('M', 12, compat_loff_t)
+#define OTPSELECT_32 _IOR ('M', 13, compat_int_t)
+#define OTPGETREGIONCOUNT_32 _IOW ('M', 14, compat_int_t)
+#define OTPGETREGIONINFO_32 _IOW ('M', 15, struct otp_info)
+#define OTPLOCK_32 _IOR ('M', 16, struct otp_info)
+#define ECCGETLAYOUT_32 _IOR ('M', 17, struct nand_ecclayout)
+#define ECCGETSTATS_32 _IOR ('M', 18, struct mtd_ecc_stats)
+#define MTDFILEMODE_32 _IO ('M', 19)
+#define MEMABIINFO_32 _IOR ('M', 20, struct mtd_abi_info)
+#define MEMGETINFO64_32 _IOR ('M', 21, struct mtd_info_user64_32)
+#define MEMERASE64_32 _IOW ('M', 22, struct erase_info_user64_32)
+#define MEMWRITEOOB64_32 _IOWR('M', 23, struct mtd_oob_buf64_32)
+#define MEMREADOOB64_32 _IOWR('M', 24, struct mtd_oob_buf64_32)
+#define MEMLOCK64_32 _IOW ('M', 25, struct erase_info_user64_32)
+#define MEMUNLOCK64_32 _IOW ('M', 26, struct erase_info_user64_32)
+#define MEMGETREGIONINFO64_32 _IOWR('M', 27, struct region_info_user64_32)
+
+#endif /* CONFIG_COMPAT */
+
+#endif /* !_MTD_COMPAT_IOCTL_H */
diff --git a/drivers/mtd/mtdchar.c b/drivers/mtd/mtdchar.c
index e9ec59e..eb89edd 100644
--- a/drivers/mtd/mtdchar.c
+++ b/drivers/mtd/mtdchar.c
@@ -19,6 +19,8 @@
#include <asm/uaccess.h>
+#include "compat_ioctl.h"
+
static struct class *mtd_class;
static void mtd_notify_add(struct mtd_info* mtd)
@@ -378,6 +380,436 @@ static int otp_select_filemode(struct
mtd_file_info *mfi, int mode)
# define otp_select_filemode(f,m) -EOPNOTSUPP
#endif
+static int mtd_ioctl_getregioninfo(struct mtd_info *mtd, void __user *argp)
+{
+ uint32_t ur_idx;
+ struct mtd_erase_region_info *kr;
+ struct region_info_user *ur = (struct region_info_user *) argp;
+
+ if (get_user(ur_idx, &(ur->regionindex)))
+ return -EFAULT;
+
+ kr = &(mtd->eraseregions[ur_idx]);
+
+ if (put_user(kr->offset, &(ur->offset))
+ || put_user(kr->erasesize, &(ur->erasesize))
+ || put_user(kr->numblocks, &(ur->numblocks)))
+ return -EFAULT;
+
+ return(0);
+}
+
+static int mtd_ioctl_getregioninfo64(struct mtd_info *mtd, void __user *argp)
+{
+ uint32_t ur_idx;
+ struct mtd_erase_region_info *kr;
+ struct region_info_user64 *ur =
+ (struct region_info_user64 *) argp;
+
+ if (get_user(ur_idx, &(ur->regionindex)))
+ return -EFAULT;
+
+ kr = &(mtd->eraseregions[ur_idx]);
+
+ if (put_user(kr->offset, &(ur->offset))
+ || put_user(kr->erasesize, &(ur->erasesize))
+ || put_user(kr->numblocks, &(ur->numblocks)))
+ return -EFAULT;
+
+ return(0);
+}
+
+static int mtd_ioctl_getabiinfo(void __user *argp)
+{
+ struct mtd_abi_info abiinfo;
+
+ abiinfo.major = MTD_MAJOR_VERSION;
+ abiinfo.minor = MTD_MINOR_VERSION;
+ abiinfo.patchlevel = MTD_PATCHLEVEL_VERSION;
+
+ if(copy_to_user(argp, &abiinfo, sizeof(abiinfo)))
+ return(-EFAULT);
+ return(0);
+}
+
+static int mtd_ioctl_getinfo(struct mtd_info *mtd, void __user *argp)
+{
+ struct mtd_info_user info;
+
+ info.type = mtd->type;
+ info.flags = mtd->flags;
+ info.size = mtd->size;
+ info.erasesize = mtd->erasesize;
+ info.writesize = mtd->writesize;
+ info.oobsize = mtd->oobsize;
+ /* The below fields are obsolete */
+ info.ecctype = -1;
+ info.eccsize = 0;
+ if (copy_to_user(argp, &info, sizeof(struct mtd_info_user)))
+ return -EFAULT;
+ return(0);
+}
+
+static int mtd_ioctl_getinfo64(struct mtd_info *mtd, void __user *argp)
+{
+ struct mtd_info_user64 info;
+
+ info.type = mtd->type;
+ info.flags = mtd->flags;
+ info.size = mtd->size;
+ info.erasesize = mtd->erasesize;
+ info.writesize = mtd->writesize;
+ info.oobsize = mtd->oobsize;
+
+ if (copy_to_user(argp, &info, sizeof(struct mtd_info_user64)))
+ return -EFAULT;
+ return(0);
+}
+
+static int mtd_do_erase(struct file *file, struct mtd_info *mtd,
+ uint64_t start, uint64_t length)
+{
+ struct erase_info *erase;
+ int ret = 0;
+
+ if(!(file->f_mode & FMODE_WRITE))
+ return -EPERM;
+
+ erase=kzalloc(sizeof(struct erase_info),GFP_KERNEL);
+ if (!erase)
+ ret = -ENOMEM;
+ else {
+ wait_queue_head_t waitq;
+ DECLARE_WAITQUEUE(wait, current);
+
+ init_waitqueue_head(&waitq);
+
+ erase->addr = start;
+ erase->len = length;
+ erase->mtd = mtd;
+ erase->callback = mtdchar_erase_callback;
+ erase->priv = (unsigned long)&waitq;
+
+ /*
+ FIXME: Allow INTERRUPTIBLE. Which means
+ not having the wait_queue head on the stack.
+
+ If the wq_head is on the stack, and we
+ leave because we got interrupted, then the
+ wq_head is no longer there when the
+ callback routine tries to wake us up.
+ */
+ ret = mtd->erase(mtd, erase);
+ if (!ret) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&waitq, &wait);
+ if (erase->state != MTD_ERASE_DONE &&
+ erase->state != MTD_ERASE_FAILED)
+ schedule();
+ remove_wait_queue(&waitq, &wait);
+ set_current_state(TASK_RUNNING);
+
+ ret = (erase->state == MTD_ERASE_FAILED)?-EIO:0;
+ }
+ kfree(erase);
+ }
+ return(ret);
+}
+
+static int mtd_ioctl_erase(struct file *file, struct mtd_info *mtd,
+ void __user *argp)
+{
+ struct erase_info_user einfo;
+
+ if (copy_from_user(&einfo, argp, sizeof(struct erase_info_user)))
+ return -EFAULT;
+ return(mtd_do_erase(file, mtd, einfo.start, einfo.length));
+}
+
+static int mtd_ioctl_erase64(struct file *file, struct mtd_info *mtd,
+ void __user *argp)
+{
+ struct erase_info_user64 einfo;
+
+ if (copy_from_user(&einfo, argp, sizeof(struct erase_info_user64)))
+ return -EFAULT;
+ return(mtd_do_erase(file, mtd, einfo.start, einfo.length));
+}
+
+static int mtd_do_writeoob(struct file *file, struct mtd_info *mtd,
+ uint64_t start, uint32_t length, void __user *ptr,
+ uint32_t __user *retp)
+{
+ struct mtd_oob_ops ops;
+ uint32_t retlen;
+ int ret = 0;
+
+ if(!(file->f_mode & FMODE_WRITE))
+ return -EPERM;
+
+ if (length > 4096)
+ return -EINVAL;
+
+ if (!mtd->write_oob)
+ ret = -EOPNOTSUPP;
+ else
+ ret = access_ok(VERIFY_READ, ptr, length) ? 0 : EFAULT;
+
+ if (ret)
+ return ret;
+
+ ops.ooblen = length;
+ ops.ooboffs = start & (mtd->oobsize - 1);
+ ops.datbuf = NULL;
+ ops.mode = MTD_OOB_PLACE;
+
+ if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs))
+ return -EINVAL;
+
+ ops.oobbuf = kmalloc(length, GFP_KERNEL);
+ if (!ops.oobbuf)
+ return -ENOMEM;
+
+ if (copy_from_user(ops.oobbuf, ptr, length)) {
+ kfree(ops.oobbuf);
+ return -EFAULT;
+ }
+
+ start &= ~((uint64_t)mtd->oobsize - 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 mtd_do_readoob(struct mtd_info *mtd, uint64_t start,
+ uint32_t length, void __user *ptr, uint32_t __user *retp)
+{
+ struct mtd_oob_ops ops;
+ int ret = 0;
+
+ if (length > 4096)
+ return -EINVAL;
+
+ if (!mtd->read_oob)
+ ret = -EOPNOTSUPP;
+ else
+ ret = access_ok(VERIFY_WRITE, ptr,
+ length) ? 0 : -EFAULT;
+ if (ret)
+ return ret;
+
+ ops.ooblen = length;
+ ops.ooboffs = start & (mtd->oobsize - 1);
+ ops.datbuf = NULL;
+ ops.mode = MTD_OOB_PLACE;
+
+ 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->oobsize - 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);
+ return(ret);
+}
+
+static int mtd_ioctl_lock(struct mtd_info *mtd, void __user *argp)
+{
+ struct erase_info_user einfo;
+ int ret;
+
+ if (copy_from_user(&einfo, argp, sizeof(einfo)))
+ return -EFAULT;
+
+ if (!mtd->lock)
+ ret = -EOPNOTSUPP;
+ else
+ ret = mtd->lock(mtd, einfo.start, einfo.length);
+ return(ret);
+}
+
+static int mtd_ioctl_lock64(struct mtd_info *mtd, void __user *argp)
+{
+ struct erase_info_user64 einfo;
+ int ret;
+
+ if (copy_from_user(&einfo, argp, sizeof(einfo)))
+ return -EFAULT;
+
+ if (!mtd->lock)
+ ret = -EOPNOTSUPP;
+ else
+ ret = mtd->lock(mtd, einfo.start, einfo.length);
+ return(ret);
+}
+
+static int mtd_ioctl_unlock(struct mtd_info *mtd, void __user *argp)
+{
+ struct erase_info_user einfo;
+ int ret;
+
+ if (copy_from_user(&einfo, argp, sizeof(einfo)))
+ return -EFAULT;
+
+ if (!mtd->unlock)
+ ret = -EOPNOTSUPP;
+ else
+ ret = mtd->unlock(mtd, einfo.start, einfo.length);
+ return(ret);
+}
+
+static int mtd_ioctl_unlock64(struct mtd_info *mtd, void __user *argp)
+{
+ struct erase_info_user64 einfo;
+ int ret;
+
+ if (copy_from_user(&einfo, argp, sizeof(einfo)))
+ return -EFAULT;
+
+ if (!mtd->unlock)
+ ret = -EOPNOTSUPP;
+ else
+ ret = mtd->unlock(mtd, einfo.start, einfo.length);
+ return(ret);
+}
+
+static int mtd_ioctl_oobsel(struct mtd_info *mtd, void __user *argp)
+{
+ struct nand_oobinfo oi;
+
+ if (!mtd->ecclayout)
+ return -EOPNOTSUPP;
+ if (mtd->ecclayout->eccbytes > ARRAY_SIZE(oi.eccpos))
+ return -EINVAL;
+
+ oi.useecc = MTD_NANDECC_AUTOPLACE;
+ memcpy(&oi.eccpos, mtd->ecclayout->eccpos, sizeof(oi.eccpos));
+ memcpy(&oi.oobfree, mtd->ecclayout->oobfree,
+ sizeof(oi.oobfree));
+ oi.eccbytes = mtd->ecclayout->eccbytes;
+
+ if (copy_to_user(argp, &oi, sizeof(struct nand_oobinfo)))
+ return -EFAULT;
+ return(0);
+}
+
+#ifdef CONFIG_HAVE_MTD_OTP
+static int mtd_ioctl_otpregioninfo(struct mtd_file_info *mfi,
+ struct mtd_info *mtd, int *count, void __user *argp)
+{
+ struct otp_info *buf = kmalloc(4096, GFP_KERNEL);
+ int ret;
+
+ if (!buf)
+ return -ENOMEM;
+ ret = -EOPNOTSUPP;
+ switch (mfi->mode) {
+ case MTD_MODE_OTP_FACTORY:
+ if (mtd->get_fact_prot_info)
+ ret = mtd->get_fact_prot_info(mtd, buf, 4096);
+ break;
+ case MTD_MODE_OTP_USER:
+ if (mtd->get_user_prot_info)
+ ret = mtd->get_user_prot_info(mtd, buf, 4096);
+ break;
+ default:
+ break;
+ }
+ if (ret >= 0) {
+ if (count) {
+ *count = ret / sizeof(struct otp_info);
+ ret = 0;
+ }
+ if (argp)
+ ret = copy_to_user(argp, buf, ret);
+ if (ret)
+ ret = -EFAULT;
+ }
+ kfree(buf);
+ return(ret);
+}
+
+static int mtd_ioctl_otplock(struct mtd_file_info *mfi, struct mtd_info *mtd,
+ void __user *argp)
+{
+ struct otp_info oinfo;
+ int ret;
+
+ if (mfi->mode != MTD_MODE_OTP_USER)
+ return -EINVAL;
+ if (copy_from_user(&oinfo, argp, sizeof(oinfo)))
+ return -EFAULT;
+ if (!mtd->lock_user_prot_reg)
+ return -EOPNOTSUPP;
+ ret = mtd->lock_user_prot_reg(mtd, oinfo.start, oinfo.length);
+ return(ret);
+}
+#endif /* CONFIG_HAVE_MTD_OTP */
+
+static int mtd_ioctl_ecclayout(struct mtd_info *mtd, void __user *argp)
+{
+ if (!mtd->ecclayout)
+ return -EOPNOTSUPP;
+
+ if (copy_to_user(argp, mtd->ecclayout,
+ sizeof(struct nand_ecclayout)))
+ return -EFAULT;
+ return(0);
+}
+
+static int mtd_ioctl_eccstats(struct mtd_info *mtd, void __user *argp)
+{
+ if (copy_to_user(argp, &mtd->ecc_stats,
+ sizeof(struct mtd_ecc_stats)))
+ return -EFAULT;
+ return(0);
+}
+
+static int mtd_ioctl_filemode(struct file *file, struct mtd_file_info *mfi,
+ struct mtd_info *mtd, unsigned long arg)
+{
+ int ret = 0;
+
+ mfi->mode = 0;
+
+ switch(arg) {
+ case MTD_MODE_OTP_FACTORY:
+ case MTD_MODE_OTP_USER:
+ ret = otp_select_filemode(mfi, arg);
+ break;
+
+ case MTD_MODE_RAW:
+ if (!mtd->read_oob || !mtd->write_oob)
+ return -EOPNOTSUPP;
+ mfi->mode = arg;
+
+ case MTD_MODE_NORMAL:
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ file->f_pos = 0;
+
+ return(ret);
+}
+
static int mtd_ioctl(struct inode *inode, struct file *file,
u_int cmd, u_long arg)
{
@@ -386,7 +818,6 @@ static int mtd_ioctl(struct inode *inode, struct file *file,
void __user *argp = (void __user *)arg;
int ret = 0;
u_long size;
- struct mtd_info_user info;
DEBUG(MTD_DEBUG_LEVEL0, "MTD_ioctl\n");
@@ -403,256 +834,123 @@ static int mtd_ioctl(struct inode *inode,
struct file *file,
switch (cmd) {
case MEMGETREGIONCOUNT:
if (copy_to_user(argp, &(mtd->numeraseregions), sizeof(int)))
- return -EFAULT;
+ ret = -EFAULT;
break;
case MEMGETREGIONINFO:
- {
- uint32_t ur_idx;
- struct mtd_erase_region_info *kr;
- struct region_info_user *ur = (struct region_info_user *) argp;
-
- if (get_user(ur_idx, &(ur->regionindex)))
- return -EFAULT;
-
- kr = &(mtd->eraseregions[ur_idx]);
+ ret = mtd_ioctl_getregioninfo(mtd, argp);
+ break;
- if (put_user(kr->offset, &(ur->offset))
- || put_user(kr->erasesize, &(ur->erasesize))
- || put_user(kr->numblocks, &(ur->numblocks)))
- return -EFAULT;
+ case MEMGETREGIONINFO64:
+ ret = mtd_ioctl_getregioninfo64(mtd, argp);
+ break;
+ case MEMABIINFO:
+ ret = mtd_ioctl_getabiinfo(argp);
break;
- }
case MEMGETINFO:
- info.type = mtd->type;
- info.flags = mtd->flags;
- info.size = mtd->size;
- info.erasesize = mtd->erasesize;
- info.writesize = mtd->writesize;
- info.oobsize = mtd->oobsize;
- /* The below fields are obsolete */
- info.ecctype = -1;
- info.eccsize = 0;
- if (copy_to_user(argp, &info, sizeof(struct mtd_info_user)))
- return -EFAULT;
+ ret = mtd_ioctl_getinfo(mtd, argp);
break;
- case MEMERASE:
- {
- struct erase_info *erase;
-
- if(!(file->f_mode & FMODE_WRITE))
- return -EPERM;
-
- erase=kzalloc(sizeof(struct erase_info),GFP_KERNEL);
- if (!erase)
- ret = -ENOMEM;
- else {
- struct erase_info_user einfo;
-
- wait_queue_head_t waitq;
- DECLARE_WAITQUEUE(wait, current);
+ case MEMGETINFO64:
+ ret = mtd_ioctl_getinfo64(mtd, argp);
+ break;
- init_waitqueue_head(&waitq);
+ case MEMERASE:
+ ret = mtd_ioctl_erase(file, mtd, argp);
+ break;
- if (copy_from_user(&einfo, argp,
- sizeof(struct erase_info_user))) {
- kfree(erase);
- return -EFAULT;
- }
- erase->addr = einfo.start;
- erase->len = einfo.length;
- erase->mtd = mtd;
- erase->callback = mtdchar_erase_callback;
- erase->priv = (unsigned long)&waitq;
-
- /*
- FIXME: Allow INTERRUPTIBLE. Which means
- not having the wait_queue head on the stack.
-
- If the wq_head is on the stack, and we
- leave because we got interrupted, then the
- wq_head is no longer there when the
- callback routine tries to wake us up.
- */
- ret = mtd->erase(mtd, erase);
- if (!ret) {
- set_current_state(TASK_UNINTERRUPTIBLE);
- add_wait_queue(&waitq, &wait);
- if (erase->state != MTD_ERASE_DONE &&
- erase->state != MTD_ERASE_FAILED)
- schedule();
- remove_wait_queue(&waitq, &wait);
- set_current_state(TASK_RUNNING);
-
- ret = (erase->state == MTD_ERASE_FAILED)?-EIO:0;
- }
- kfree(erase);
- }
+ case MEMERASE64:
+ ret = mtd_ioctl_erase64(file, mtd, argp);
break;
- }
case MEMWRITEOOB:
{
struct mtd_oob_buf buf;
- struct mtd_oob_ops ops;
- struct mtd_oob_buf __user *user_buf = argp;
- uint32_t retlen;
+ struct mtd_oob_buf __user *buf_user = argp;
- if(!(file->f_mode & FMODE_WRITE))
- return -EPERM;
-
- if (copy_from_user(&buf, argp, sizeof(struct mtd_oob_buf)))
- return -EFAULT;
-
- if (buf.length > 4096)
- return -EINVAL;
-
- if (!mtd->write_oob)
- ret = -EOPNOTSUPP;
+ if (copy_from_user(&buf, argp, sizeof(buf)))
+ ret = -EFAULT;
else
- ret = access_ok(VERIFY_READ, buf.ptr,
- buf.length) ? 0 : EFAULT;
-
- if (ret)
- return ret;
-
- ops.ooblen = buf.length;
- ops.ooboffs = buf.start & (mtd->oobsize - 1);
- ops.datbuf = NULL;
- ops.mode = MTD_OOB_PLACE;
-
- if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs))
- return -EINVAL;
-
- ops.oobbuf = kmalloc(buf.length, GFP_KERNEL);
- if (!ops.oobbuf)
- return -ENOMEM;
-
- if (copy_from_user(ops.oobbuf, buf.ptr, buf.length)) {
- kfree(ops.oobbuf);
- return -EFAULT;
- }
+ ret = mtd_do_writeoob(file, mtd, buf.start, buf.length,
+ buf.ptr, &buf_user->length);
+ break;
+ }
- buf.start &= ~(mtd->oobsize - 1);
- ret = mtd->write_oob(mtd, buf.start, &ops);
+ case MEMWRITEOOB64:
+ {
+ struct mtd_oob_buf64 buf;
+ struct mtd_oob_buf64 __user *buf_user = argp;
- if (ops.oobretlen > 0xFFFFFFFFU)
- ret = -EOVERFLOW;
- retlen = ops.oobretlen;
- if (copy_to_user(&user_buf->length, &retlen, sizeof(buf.length)))
+ if (copy_from_user(&buf, argp, sizeof(buf)))
ret = -EFAULT;
-
- kfree(ops.oobbuf);
+ else
+ ret = mtd_do_writeoob(file, mtd, buf.start, buf.length,
+ buf.ptr, &buf_user->length);
break;
-
}
case MEMREADOOB:
{
struct mtd_oob_buf buf;
- struct mtd_oob_ops ops;
-
- if (copy_from_user(&buf, argp, sizeof(struct mtd_oob_buf)))
- return -EFAULT;
+ struct mtd_oob_buf __user *buf_user = argp;
- if (buf.length > 4096)
- return -EINVAL;
-
- if (!mtd->read_oob)
- ret = -EOPNOTSUPP;
+ /* NOTE: old ABI writes return length to buf->start */
+ if (copy_from_user(&buf, argp, sizeof(buf)))
+ ret = -EFAULT;
else
- ret = access_ok(VERIFY_WRITE, buf.ptr,
- buf.length) ? 0 : -EFAULT;
- if (ret)
- return ret;
-
- ops.ooblen = buf.length;
- ops.ooboffs = buf.start & (mtd->oobsize - 1);
- ops.datbuf = NULL;
- ops.mode = MTD_OOB_PLACE;
-
- if (ops.ooboffs && ops.ooblen > (mtd->oobsize - ops.ooboffs))
- return -EINVAL;
-
- ops.oobbuf = kmalloc(buf.length, GFP_KERNEL);
- if (!ops.oobbuf)
- return -ENOMEM;
+ ret = mtd_do_readoob(mtd, buf.start, buf.length,
+ buf.ptr, &buf_user->start);
+ break;
+ }
- buf.start &= ~(mtd->oobsize - 1);
- ret = mtd->read_oob(mtd, buf.start, &ops);
+ case MEMREADOOB64:
+ {
+ struct mtd_oob_buf64 buf;
+ struct mtd_oob_buf64 __user *buf_user = argp;
- if (put_user(ops.oobretlen, (uint32_t __user *)argp))
- ret = -EFAULT;
- else if (ops.oobretlen && copy_to_user(buf.ptr, ops.oobbuf,
- ops.oobretlen))
+ if (copy_from_user(&buf, argp, sizeof(buf)))
ret = -EFAULT;
-
- kfree(ops.oobbuf);
+ else
+ ret = mtd_do_readoob(mtd, buf.start, buf.length,
+ buf.ptr, &buf_user->length);
break;
}
case MEMLOCK:
- {
- struct erase_info_user einfo;
-
- if (copy_from_user(&einfo, argp, sizeof(einfo)))
- return -EFAULT;
+ ret = mtd_ioctl_lock(mtd, argp);
+ break;
- if (!mtd->lock)
- ret = -EOPNOTSUPP;
- else
- ret = mtd->lock(mtd, einfo.start, einfo.length);
+ case MEMLOCK64:
+ ret = mtd_ioctl_lock64(mtd, argp);
break;
- }
case MEMUNLOCK:
- {
- struct erase_info_user einfo;
-
- if (copy_from_user(&einfo, argp, sizeof(einfo)))
- return -EFAULT;
+ ret = mtd_ioctl_unlock(mtd, argp);
+ break;
- if (!mtd->unlock)
- ret = -EOPNOTSUPP;
- else
- ret = mtd->unlock(mtd, einfo.start, einfo.length);
+ case MEMUNLOCK64:
+ ret = mtd_ioctl_unlock64(mtd, argp);
break;
- }
/* Legacy interface */
case MEMGETOOBSEL:
- {
- struct nand_oobinfo oi;
-
- if (!mtd->ecclayout)
- return -EOPNOTSUPP;
- if (mtd->ecclayout->eccbytes > ARRAY_SIZE(oi.eccpos))
- return -EINVAL;
-
- oi.useecc = MTD_NANDECC_AUTOPLACE;
- memcpy(&oi.eccpos, mtd->ecclayout->eccpos, sizeof(oi.eccpos));
- memcpy(&oi.oobfree, mtd->ecclayout->oobfree,
- sizeof(oi.oobfree));
- oi.eccbytes = mtd->ecclayout->eccbytes;
-
- if (copy_to_user(argp, &oi, sizeof(struct nand_oobinfo)))
- return -EFAULT;
+ ret = mtd_ioctl_oobsel(mtd, argp);
break;
- }
case MEMGETBADBLOCK:
{
loff_t offs;
- if (copy_from_user(&offs, argp, sizeof(loff_t)))
- return -EFAULT;
- if (!mtd->block_isbad)
- ret = -EOPNOTSUPP;
- else
- return mtd->block_isbad(mtd, offs);
+ if (copy_from_user(&offs, argp, sizeof(offs)))
+ ret = -EFAULT;
+ else {
+ if (!mtd->block_isbad)
+ ret = -EOPNOTSUPP;
+ else
+ ret = mtd->block_isbad(mtd, offs);
+ }
break;
}
@@ -660,12 +958,14 @@ static int mtd_ioctl(struct inode *inode, struct
file *file,
{
loff_t offs;
- if (copy_from_user(&offs, argp, sizeof(loff_t)))
- return -EFAULT;
- if (!mtd->block_markbad)
- ret = -EOPNOTSUPP;
- else
- return mtd->block_markbad(mtd, offs);
+ if (copy_from_user(&offs, argp, sizeof(offs)))
+ ret = -EFAULT;
+ else {
+ if (!mtd->block_markbad)
+ ret = -EOPNOTSUPP;
+ else
+ ret = mtd->block_markbad(mtd, offs);
+ }
break;
}
@@ -673,106 +973,46 @@ static int mtd_ioctl(struct inode *inode,
struct file *file,
case OTPSELECT:
{
int mode;
- if (copy_from_user(&mode, argp, sizeof(int)))
- return -EFAULT;
-
- mfi->mode = MTD_MODE_NORMAL;
-
- ret = otp_select_filemode(mfi, mode);
-
- file->f_pos = 0;
+ if (copy_from_user(&mode, argp, sizeof(mode)))
+ ret = -EFAULT;
+ else {
+ mfi->mode = MTD_MODE_NORMAL;
+ ret = otp_select_filemode(mfi, mode);
+ file->f_pos = 0;
+ }
break;
}
case OTPGETREGIONCOUNT:
- case OTPGETREGIONINFO:
{
- struct otp_info *buf = kmalloc(4096, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
- ret = -EOPNOTSUPP;
- switch (mfi->mode) {
- case MTD_MODE_OTP_FACTORY:
- if (mtd->get_fact_prot_info)
- ret = mtd->get_fact_prot_info(mtd, buf, 4096);
- break;
- case MTD_MODE_OTP_USER:
- if (mtd->get_user_prot_info)
- ret = mtd->get_user_prot_info(mtd, buf, 4096);
- break;
- default:
- break;
- }
- if (ret >= 0) {
- if (cmd == OTPGETREGIONCOUNT) {
- int nbr = ret / sizeof(struct otp_info);
- ret = copy_to_user(argp, &nbr, sizeof(int));
- } else
- ret = copy_to_user(argp, buf, ret);
- if (ret)
- ret = -EFAULT;
- }
- kfree(buf);
+ int count;
+
+ ret = mtd_ioctl_otpregioninfo(mfi, mtd, &count, NULL);
+ if(ret == 0 && copy_to_user(argp, &count, sizeof(count)))
+ ret = -EFAULT;
break;
}
- case OTPLOCK:
- {
- struct otp_info oinfo;
+ case OTPGETREGIONINFO:
+ ret = mtd_ioctl_otpregioninfo(mfi, mtd, NULL, argp);
+ break;
- if (mfi->mode != MTD_MODE_OTP_USER)
- return -EINVAL;
- if (copy_from_user(&oinfo, argp, sizeof(oinfo)))
- return -EFAULT;
- if (!mtd->lock_user_prot_reg)
- return -EOPNOTSUPP;
- ret = mtd->lock_user_prot_reg(mtd, oinfo.start, oinfo.length);
+ case OTPLOCK:
+ ret = mtd_ioctl_otplock(mfi, mtd, argp);
break;
- }
#endif
case ECCGETLAYOUT:
- {
- if (!mtd->ecclayout)
- return -EOPNOTSUPP;
-
- if (copy_to_user(argp, mtd->ecclayout,
- sizeof(struct nand_ecclayout)))
- return -EFAULT;
+ ret = mtd_ioctl_ecclayout(mtd, argp);
break;
- }
case ECCGETSTATS:
- {
- if (copy_to_user(argp, &mtd->ecc_stats,
- sizeof(struct mtd_ecc_stats)))
- return -EFAULT;
+ ret = mtd_ioctl_eccstats(mtd, argp);
break;
- }
case MTDFILEMODE:
- {
- mfi->mode = 0;
-
- switch(arg) {
- case MTD_MODE_OTP_FACTORY:
- case MTD_MODE_OTP_USER:
- ret = otp_select_filemode(mfi, arg);
- break;
-
- case MTD_MODE_RAW:
- if (!mtd->read_oob || !mtd->write_oob)
- return -EOPNOTSUPP;
- mfi->mode = arg;
-
- case MTD_MODE_NORMAL:
- break;
- default:
- ret = -EINVAL;
- }
- file->f_pos = 0;
+ ret = mtd_ioctl_filemode(file, mfi, mtd, arg);
break;
- }
default:
ret = -ENOTTY;
@@ -781,12 +1021,19 @@ static int mtd_ioctl(struct inode *inode,
struct file *file,
return ret;
} /* memory_ioctl */
+#ifdef CONFIG_COMPAT
+#include "compat_ioctl.c"
+#endif
+
static const struct file_operations mtd_fops = {
.owner = THIS_MODULE,
.llseek = mtd_lseek,
.read = mtd_read,
.write = mtd_write,
.ioctl = mtd_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = mtd_compat_ioctl,
+#endif
.open = mtd_open,
.release = mtd_close,
};
diff --git a/include/mtd/mtd-abi.h b/include/mtd/mtd-abi.h
index c6c61cd..ae76363 100644
--- a/include/mtd/mtd-abi.h
+++ b/include/mtd/mtd-abi.h
@@ -5,17 +5,35 @@
#ifndef __MTD_ABI_H__
#define __MTD_ABI_H__
+#define MTD_MAJOR_VERSION 2
+#define MTD_MINOR_VERSION 0
+#define MTD_PATCHLEVEL_VERSION 0
+
struct erase_info_user {
uint32_t start;
uint32_t length;
};
+struct erase_info_user64 {
+ uint64_t start;
+ uint64_t length;
+ uint32_t res0[8];
+};
+
struct mtd_oob_buf {
uint32_t start;
uint32_t length;
unsigned char __user *ptr;
};
+struct mtd_oob_buf64 {
+ uint64_t start;
+ uint32_t res0;
+ uint32_t length;
+ unsigned char __user *ptr;
+ uint32_t res2[8];
+};
+
#define MTD_ABSENT 0
#define MTD_RAM 1
#define MTD_ROM 2
@@ -50,14 +68,25 @@ struct mtd_oob_buf {
struct mtd_info_user {
uint8_t type;
uint32_t flags;
- uint32_t size; // Total size of the MTD
+ uint32_t size; /* Total size of the MTD */
uint32_t erasesize;
uint32_t writesize;
- uint32_t oobsize; // Amount of OOB data per block (e.g. 16)
- /* The below two fields are obsolete and broken, do not use them
- * (TODO: remove at some point) */
- uint32_t ecctype;
- uint32_t eccsize;
+ uint32_t oobsize; /* OOB bytes per page (e.g. 16) */
+ uint32_t ecctype; /* Obsolete, always reports -1 */
+ uint32_t eccsize; /* Obsolete, always reports 0 */
+};
+
+struct mtd_info_user64 {
+ uint32_t type;
+ uint32_t flags;
+ uint64_t size; /* Total size of the MTD */
+ uint32_t res0;
+ uint32_t erasesize;
+ uint32_t res1;
+ uint32_t writesize;
+ uint32_t res2;
+ uint32_t oobsize; /* OOB bytes per page (e.g. 16) */
+ uint32_t res3[32];
};
struct region_info_user {
@@ -68,12 +97,31 @@ struct region_info_user {
uint32_t regionindex;
};
+struct region_info_user64 {
+ uint64_t offset; /* At which this region starts,
+ * from the beginning of the MTD */
+ uint32_t res0;
+ uint32_t erasesize; /* For this region */
+ uint32_t res1;
+ uint32_t numblocks; /* Number of blocks in this region */
+ uint32_t res2;
+ uint32_t regionindex;
+ uint32_t res3[16];
+};
+
struct otp_info {
uint32_t start;
uint32_t length;
uint32_t locked;
};
+struct mtd_abi_info {
+ uint32_t major;
+ uint32_t minor;
+ uint32_t patchlevel;
+ uint32_t res0[8];
+};
+
#define MEMGETINFO _IOR('M', 1, struct mtd_info_user)
#define MEMERASE _IOW('M', 2, struct erase_info_user)
#define MEMWRITEOOB _IOWR('M', 3, struct mtd_oob_buf)
@@ -94,6 +142,15 @@ struct otp_info {
#define ECCGETSTATS _IOR('M', 18, struct mtd_ecc_stats)
#define MTDFILEMODE _IO('M', 19)
+#define MEMABIINFO _IOR ('M', 20, struct mtd_abi_info)
+#define MEMGETINFO64 _IOR ('M', 21, struct mtd_info_user64)
+#define MEMERASE64 _IOW ('M', 22, struct erase_info_user64)
+#define MEMWRITEOOB64 _IOWR('M', 23, struct mtd_oob_buf64)
+#define MEMREADOOB64 _IOWR('M', 24, struct mtd_oob_buf64)
+#define MEMLOCK64 _IOW ('M', 25, struct erase_info_user64)
+#define MEMUNLOCK64 _IOW ('M', 26, struct erase_info_user64)
+#define MEMGETREGIONINFO64 _IOWR('M', 27, struct region_info_user64)
+
/*
* Obsolete legacy interface. Keep it in order not to break userspace
* interfaces
diff --git a/include/mtd/mtd-user.h b/include/mtd/mtd-user.h
index 170ceca..1b0da98 100644
--- a/include/mtd/mtd-user.h
+++ b/include/mtd/mtd-user.h
@@ -16,4 +16,8 @@ typedef struct region_info_user region_info_t;
typedef struct nand_oobinfo nand_oobinfo_t;
typedef struct nand_ecclayout nand_ecclayout_t;
+typedef struct mtd_info_user64 mtd_info64_t;
+typedef struct erase_info_user64 erase_info64_t;
+typedef struct region_info_user64 region_info64_t;
+
#endif /* __MTD_USER_H__ */
--
1.5.6.3
More information about the linux-mtd
mailing list