Merge to Fedora kernel-2.6.18-1.2224_FC5 patched with stable patch-2.6.18.1-vs2.0...
[linux-2.6.git] / drivers / mtd / mtdchar.c
index 6f04458..5b6acfc 100644 (file)
@@ -5,7 +5,6 @@
  *
  */
 
-#include <linux/config.h>
 #include <linux/device.h>
 #include <linux/fs.h>
 #include <linux/init.h>
@@ -49,42 +48,33 @@ static struct mtd_notifier notifier = {
 };
 
 /*
- * We use file->private_data to store a pointer to the MTDdevice.
- * Since alighment is at least 32 bits, we have 2 bits free for OTP
- * modes as well.
+ * Data structure to hold the pointer to the mtd device as well
+ * as mode information ofr various use cases.
  */
-
-#define TO_MTD(file) (struct mtd_info *)((long)((file)->private_data) & ~3L)
-
-#define MTD_MODE_OTP_FACT      1
-#define MTD_MODE_OTP_USER      2
-#define MTD_MODE(file)         ((long)((file)->private_data) & 3)
-
-#define SET_MTD_MODE(file, mode) \
-       do { long __p = (long)((file)->private_data); \
-            (file)->private_data = (void *)((__p & ~3L) | mode); } while (0)
+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_info *mtd = TO_MTD(file);
+       struct mtd_file_info *mfi = file->private_data;
+       struct mtd_info *mtd = mfi->mtd;
 
        switch (orig) {
-       case 0:
-               /* SEEK_SET */
+       case SEEK_SET:
                break;
-       case 1:
-               /* SEEK_CUR */
+       case SEEK_CUR:
                offset += file->f_pos;
                break;
-       case 2:
-               /* SEEK_END */
+       case SEEK_END:
                offset += mtd->size;
                break;
        default:
                return -EINVAL;
        }
 
-       if (offset >= 0 && offset < mtd->size)
+       if (offset >= 0 && offset <= mtd->size)
                return file->f_pos = offset;
 
        return -EINVAL;
@@ -97,6 +87,7 @@ static int mtd_open(struct inode *inode, struct file *file)
        int minor = iminor(inode);
        int devnum = minor >> 1;
        struct mtd_info *mtd;
+       struct mtd_file_info *mfi;
 
        DEBUG(MTD_DEBUG_LEVEL0, "MTD_open\n");
 
@@ -117,14 +108,20 @@ static int mtd_open(struct inode *inode, struct file *file)
                return -ENODEV;
        }
 
-       file->private_data = mtd;
-
        /* 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);
                return -EACCES;
        }
 
+       mfi = kzalloc(sizeof(*mfi), GFP_KERNEL);
+       if (!mfi) {
+               put_mtd_device(mtd);
+               return -ENOMEM;
+       }
+       mfi->mtd = mtd;
+       file->private_data = mfi;
+
        return 0;
 } /* mtd_open */
 
@@ -132,16 +129,17 @@ static int mtd_open(struct inode *inode, struct file *file)
 
 static int mtd_close(struct inode *inode, struct file *file)
 {
-       struct mtd_info *mtd;
+       struct mtd_file_info *mfi = file->private_data;
+       struct mtd_info *mtd = mfi->mtd;
 
        DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n");
 
-       mtd = TO_MTD(file);
-
        if (mtd->sync)
                mtd->sync(mtd);
 
        put_mtd_device(mtd);
+       file->private_data = NULL;
+       kfree(mfi);
 
        return 0;
 } /* mtd_close */
@@ -153,7 +151,8 @@ static int mtd_close(struct inode *inode, struct file *file)
 
 static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t *ppos)
 {
-       struct mtd_info *mtd = TO_MTD(file);
+       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;
@@ -170,36 +169,58 @@ static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t
 
        /* 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;
                else
                        len = count;
 
-               kbuf=kmalloc(len,GFP_KERNEL);
-               if (!kbuf)
-                       return -ENOMEM;
-
-               switch (MTD_MODE(file)) {
-               case MTD_MODE_OTP_FACT:
+               switch (mfi->mode) {
+               case MTD_MODE_OTP_FACTORY:
                        ret = mtd->read_fact_prot_reg(mtd, *ppos, len, &retlen, kbuf);
                        break;
                case MTD_MODE_OTP_USER:
                        ret = mtd->read_user_prot_reg(mtd, *ppos, len, &retlen, kbuf);
                        break;
+               case MTD_MODE_RAW:
+               {
+                       struct mtd_oob_ops ops;
+
+                       ops.mode = MTD_OOB_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);
+                       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 occured 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 || (ret == -EBADMSG)) {
+               if (!ret || (ret == -EUCLEAN) || (ret == -EBADMSG)) {
                        *ppos += retlen;
                        if (copy_to_user(buf, kbuf, retlen)) {
-                               kfree(kbuf);
+                               kfree(kbuf);
                                return -EFAULT;
                        }
                        else
@@ -215,15 +236,16 @@ static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t
                        return ret;
                }
 
-               kfree(kbuf);
        }
 
+       kfree(kbuf);
        return total_retlen;
 } /* mtd_read */
 
 static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count,loff_t *ppos)
 {
-       struct mtd_info *mtd = TO_MTD(file);
+       struct mtd_file_info *mfi = file->private_data;
+       struct mtd_info *mtd = mfi->mtd;
        char *kbuf;
        size_t retlen;
        size_t total_retlen=0;
@@ -241,25 +263,28 @@ static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count
        if (!count)
                return 0;
 
+       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;
                else
                        len = count;
 
-               kbuf=kmalloc(len,GFP_KERNEL);
-               if (!kbuf) {
-                       printk("kmalloc is null\n");
-                       return -ENOMEM;
-               }
-
                if (copy_from_user(kbuf, buf, len)) {
                        kfree(kbuf);
                        return -EFAULT;
                }
 
-               switch (MTD_MODE(file)) {
-               case MTD_MODE_OTP_FACT:
+               switch (mfi->mode) {
+               case MTD_MODE_OTP_FACTORY:
                        ret = -EROFS;
                        break;
                case MTD_MODE_OTP_USER:
@@ -269,6 +294,21 @@ static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count
                        }
                        ret = mtd->write_user_prot_reg(mtd, *ppos, len, &retlen, kbuf);
                        break;
+
+               case MTD_MODE_RAW:
+               {
+                       struct mtd_oob_ops ops;
+
+                       ops.mode = MTD_OOB_RAW;
+                       ops.datbuf = kbuf;
+                       ops.oobbuf = NULL;
+                       ops.len = len;
+
+                       ret = mtd->write_oob(mtd, *ppos, &ops);
+                       retlen = ops.retlen;
+                       break;
+               }
+
                default:
                        ret = (*(mtd->write))(mtd, *ppos, len, &retlen, kbuf);
                }
@@ -282,10 +322,9 @@ static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count
                        kfree(kbuf);
                        return ret;
                }
-
-               kfree(kbuf);
        }
 
+       kfree(kbuf);
        return total_retlen;
 } /* mtd_write */
 
@@ -299,13 +338,45 @@ static void mtdchar_erase_callback (struct erase_info *instr)
        wake_up((wait_queue_head_t *)instr->priv);
 }
 
+#if defined(CONFIG_MTD_OTP) || defined(CONFIG_MTD_ONENAND_OTP)
+static int otp_select_filemode(struct mtd_file_info *mfi, int mode)
+{
+       struct mtd_info *mtd = mfi->mtd;
+       int ret = 0;
+
+       switch (mode) {
+       case MTD_OTP_FACTORY:
+               if (!mtd->read_fact_prot_reg)
+                       ret = -EOPNOTSUPP;
+               else
+                       mfi->mode = MTD_MODE_OTP_FACTORY;
+               break;
+       case MTD_OTP_USER:
+               if (!mtd->read_fact_prot_reg)
+                       ret = -EOPNOTSUPP;
+               else
+                       mfi->mode = MTD_MODE_OTP_USER;
+               break;
+       default:
+               ret = -EINVAL;
+       case MTD_OTP_OFF:
+               break;
+       }
+       return ret;
+}
+#else
+# define otp_select_filemode(f,m)      -EOPNOTSUPP
+#endif
+
 static int mtd_ioctl(struct inode *inode, struct file *file,
                     u_int cmd, u_long arg)
 {
-       struct mtd_info *mtd = TO_MTD(file);
+       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;
+       struct mtd_info_user info;
 
        DEBUG(MTD_DEBUG_LEVEL0, "MTD_ioctl\n");
 
@@ -341,7 +412,15 @@ static int mtd_ioctl(struct inode *inode, struct file *file,
        }
 
        case MEMGETINFO:
-               if (copy_to_user(argp, mtd, sizeof(struct mtd_info_user)))
+               info.type       = mtd->type;
+               info.flags      = mtd->flags;
+               info.size       = mtd->size;
+               info.erasesize  = mtd->erasesize;
+               info.writesize  = mtd->writesize;
+               info.oobsize    = mtd->oobsize;
+               info.ecctype    = mtd->ecctype;
+               info.eccsize    = mtd->eccsize;
+               if (copy_to_user(argp, &info, sizeof(struct mtd_info_user)))
                        return -EFAULT;
                break;
 
@@ -400,8 +479,7 @@ static int mtd_ioctl(struct inode *inode, struct file *file,
        case MEMWRITEOOB:
        {
                struct mtd_oob_buf buf;
-               void *databuf;
-               ssize_t retlen;
+               struct mtd_oob_ops ops;
 
                if(!(file->f_mode & 2))
                        return -EPERM;
@@ -409,7 +487,7 @@ static int mtd_ioctl(struct inode *inode, struct file *file,
                if (copy_from_user(&buf, argp, sizeof(struct mtd_oob_buf)))
                        return -EFAULT;
 
-               if (buf.length > 0x4096)
+               if (buf.length > 4096)
                        return -EINVAL;
 
                if (!mtd->write_oob)
@@ -421,21 +499,32 @@ static int mtd_ioctl(struct inode *inode, struct file *file,
                if (ret)
                        return ret;
 
-               databuf = kmalloc(buf.length, GFP_KERNEL);
-               if (!databuf)
+               ops.len = buf.length;
+               ops.ooblen = buf.length;
+               ops.ooboffs = buf.start & (mtd->oobsize - 1);
+               ops.datbuf = NULL;
+               ops.mode = MTD_OOB_PLACE;
+
+               if (ops.ooboffs && ops.len > (mtd->oobsize - ops.ooboffs))
+                       return -EINVAL;
+
+               ops.oobbuf = kmalloc(buf.length, GFP_KERNEL);
+               if (!ops.oobbuf)
                        return -ENOMEM;
 
-               if (copy_from_user(databuf, buf.ptr, buf.length)) {
-                       kfree(databuf);
+               if (copy_from_user(ops.oobbuf, buf.ptr, buf.length)) {
+                       kfree(ops.oobbuf);
                        return -EFAULT;
                }
 
-               ret = (mtd->write_oob)(mtd, buf.start, buf.length, &retlen, databuf);
+               buf.start &= ~(mtd->oobsize - 1);
+               ret = mtd->write_oob(mtd, buf.start, &ops);
 
-               if (copy_to_user(argp + sizeof(uint32_t), &retlen, sizeof(uint32_t)))
+               if (copy_to_user(argp + sizeof(uint32_t), &ops.retlen,
+                                sizeof(uint32_t)))
                        ret = -EFAULT;
 
-               kfree(databuf);
+               kfree(ops.oobbuf);
                break;
 
        }
@@ -443,13 +532,12 @@ static int mtd_ioctl(struct inode *inode, struct file *file,
        case MEMREADOOB:
        {
                struct mtd_oob_buf buf;
-               void *databuf;
-               ssize_t retlen;
+               struct mtd_oob_ops ops;
 
                if (copy_from_user(&buf, argp, sizeof(struct mtd_oob_buf)))
                        return -EFAULT;
 
-               if (buf.length > 0x4096)
+               if (buf.length > 4096)
                        return -EINVAL;
 
                if (!mtd->read_oob)
@@ -457,22 +545,32 @@ static int mtd_ioctl(struct inode *inode, struct file *file,
                else
                        ret = access_ok(VERIFY_WRITE, buf.ptr,
                                        buf.length) ? 0 : -EFAULT;
-
                if (ret)
                        return ret;
 
-               databuf = kmalloc(buf.length, GFP_KERNEL);
-               if (!databuf)
+               ops.len = buf.length;
+               ops.ooblen = buf.length;
+               ops.ooboffs = buf.start & (mtd->oobsize - 1);
+               ops.datbuf = NULL;
+               ops.mode = MTD_OOB_PLACE;
+
+               if (ops.ooboffs && ops.len > (mtd->oobsize - ops.ooboffs))
+                       return -EINVAL;
+
+               ops.oobbuf = kmalloc(buf.length, GFP_KERNEL);
+               if (!ops.oobbuf)
                        return -ENOMEM;
 
-               ret = (mtd->read_oob)(mtd, buf.start, buf.length, &retlen, databuf);
+               buf.start &= ~(mtd->oobsize - 1);
+               ret = mtd->read_oob(mtd, buf.start, &ops);
 
-               if (put_user(retlen, (uint32_t __user *)argp))
+               if (put_user(ops.retlen, (uint32_t __user *)argp))
                        ret = -EFAULT;
-               else if (retlen && copy_to_user(buf.ptr, databuf, retlen))
+               else if (ops.retlen && copy_to_user(buf.ptr, ops.oobbuf,
+                                                   ops.retlen))
                        ret = -EFAULT;
 
-               kfree(databuf);
+               kfree(ops.oobbuf);
                break;
        }
 
@@ -504,16 +602,22 @@ static int mtd_ioctl(struct inode *inode, struct file *file,
                break;
        }
 
-       case MEMSETOOBSEL:
-       {
-               if (copy_from_user(&mtd->oobinfo, argp, sizeof(struct nand_oobinfo)))
-                       return -EFAULT;
-               break;
-       }
-
+       /* Legacy interface */
        case MEMGETOOBSEL:
        {
-               if (copy_to_user(argp, &(mtd->oobinfo), sizeof(struct nand_oobinfo)))
+               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));
+
+               if (copy_to_user(argp, &oi, sizeof(struct nand_oobinfo)))
                        return -EFAULT;
                break;
        }
@@ -544,31 +648,17 @@ static int mtd_ioctl(struct inode *inode, struct file *file,
                break;
        }
 
-#ifdef CONFIG_MTD_OTP
+#if defined(CONFIG_MTD_OTP) || defined(CONFIG_MTD_ONENAND_OTP)
        case OTPSELECT:
        {
                int mode;
                if (copy_from_user(&mode, argp, sizeof(int)))
                        return -EFAULT;
-               SET_MTD_MODE(file, 0);
-               switch (mode) {
-               case MTD_OTP_FACTORY:
-                       if (!mtd->read_fact_prot_reg)
-                               ret = -EOPNOTSUPP;
-                       else
-                               SET_MTD_MODE(file, MTD_MODE_OTP_FACT);
-                       break;
-               case MTD_OTP_USER:
-                       if (!mtd->read_fact_prot_reg)
-                               ret = -EOPNOTSUPP;
-                       else
-                               SET_MTD_MODE(file, MTD_MODE_OTP_USER);
-                       break;
-               default:
-                       ret = -EINVAL;
-               case MTD_OTP_OFF:
-                       break;
-               }
+
+               mfi->mode = MTD_MODE_NORMAL;
+
+               ret = otp_select_filemode(mfi, mode);
+
                file->f_pos = 0;
                break;
        }
@@ -580,8 +670,8 @@ static int mtd_ioctl(struct inode *inode, struct file *file,
                if (!buf)
                        return -ENOMEM;
                ret = -EOPNOTSUPP;
-               switch (MTD_MODE(file)) {
-               case MTD_MODE_OTP_FACT:
+               switch (mfi->mode) {
+               case MTD_MODE_OTP_FACTORY:
                        if (mtd->get_fact_prot_info)
                                ret = mtd->get_fact_prot_info(mtd, buf, 4096);
                        break;
@@ -589,6 +679,8 @@ static int mtd_ioctl(struct inode *inode, struct file *file,
                        if (mtd->get_user_prot_info)
                                ret = mtd->get_user_prot_info(mtd, buf, 4096);
                        break;
+               default:
+                       break;
                }
                if (ret >= 0) {
                        if (cmd == OTPGETREGIONCOUNT) {
@@ -607,7 +699,7 @@ static int mtd_ioctl(struct inode *inode, struct file *file,
        {
                struct otp_info info;
 
-               if (MTD_MODE(file) != MTD_MODE_OTP_USER)
+               if (mfi->mode != MTD_MODE_OTP_USER)
                        return -EINVAL;
                if (copy_from_user(&info, argp, sizeof(info)))
                        return -EFAULT;
@@ -618,6 +710,49 @@ static int mtd_ioctl(struct inode *inode, struct file *file,
        }
 #endif
 
+       case ECCGETLAYOUT:
+       {
+               if (!mtd->ecclayout)
+                       return -EOPNOTSUPP;
+
+               if (copy_to_user(argp, &mtd->ecclayout,
+                                sizeof(struct nand_ecclayout)))
+                       return -EFAULT;
+               break;
+       }
+
+       case ECCGETSTATS:
+       {
+               if (copy_to_user(argp, &mtd->ecc_stats,
+                                sizeof(struct mtd_ecc_stats)))
+                       return -EFAULT;
+               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;
+               break;
+       }
+
        default:
                ret = -ENOTTY;
        }