[MTD] NAND Expose the new raw mode function and status info to userspace
The raw read/write access to NAND (without ECC) has been changed in the
NAND rework. Expose the new way - setting the file mode via ioctl - to
userspace. Also allow to read out the ecc statistics information so userspace
tools can see that bitflips happened and whether errors where correctable
or not. Also expose the number of bad blocks for the partition, so nandwrite
can check if the data fits into the parition before writing to it.
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
diff --git a/drivers/mtd/mtdchar.c b/drivers/mtd/mtdchar.c
index a48210d..fdc535b 100644
--- a/drivers/mtd/mtdchar.c
+++ b/drivers/mtd/mtdchar.c
@@ -49,24 +49,18 @@
};
/*
- * 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:
@@ -97,6 +91,7 @@
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 +112,20 @@
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 +133,17 @@
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 +155,8 @@
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;
@@ -186,13 +189,26 @@
else
len = count;
- 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);
}
@@ -232,7 +248,8 @@
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;
@@ -270,8 +287,8 @@
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:
@@ -281,6 +298,21 @@
}
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);
}
@@ -310,10 +342,41 @@
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;
@@ -554,16 +617,6 @@
break;
}
- case ECCGETLAYOUT:
-
- if (!mtd->ecclayout)
- return -EOPNOTSUPP;
-
- if (copy_to_user(argp, &mtd->ecclayout,
- sizeof(struct nand_ecclayout)))
- return -EFAULT;
- break;
-
case MEMGETBADBLOCK:
{
loff_t offs;
@@ -596,25 +649,11 @@
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;
}
@@ -626,8 +665,8 @@
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;
@@ -635,6 +674,8 @@
if (mtd->get_user_prot_info)
ret = mtd->get_user_prot_info(mtd, buf, 4096);
break;
+ default:
+ break;
}
if (ret >= 0) {
if (cmd == OTPGETREGIONCOUNT) {
@@ -653,7 +694,7 @@
{
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;
@@ -664,6 +705,49 @@
}
#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;
}
diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c
index 3c8d5e6..1fea631 100644
--- a/drivers/mtd/mtdconcat.c
+++ b/drivers/mtd/mtdconcat.c
@@ -56,7 +56,7 @@
size_t * retlen, u_char * buf)
{
struct mtd_concat *concat = CONCAT(mtd);
- int ret = 0, err = -EINVAL;
+ int ret = 0, err;
int i;
*retlen = 0;
@@ -80,28 +80,29 @@
err = subdev->read(subdev, from, size, &retsize, buf);
- if (err && (err != -EBADMSG) && (err != -EUCLEAN))
- break;
-
/* Save information about bitflips! */
- if (err) {
- if (err == -EBADMSG)
+ if (unlikely(err)) {
+ if (err == -EBADMSG) {
+ mtd->ecc_stats.failed++;
ret = err;
- else if (!ret)
- ret = err;
- err = 0;
+ } else if (err == -EUCLEAN) {
+ mtd->ecc_stats.corrected++;
+ /* Do not overwrite -EBADMSG !! */
+ if (!ret)
+ ret = err;
+ } else
+ return err;
}
*retlen += retsize;
len -= size;
if (len == 0)
- break;
+ return ret;
- err = -EINVAL;
buf += size;
from = 0;
}
- return err ? err : ret;
+ return -EINVAL;
}
static int
@@ -244,7 +245,7 @@
{
struct mtd_concat *concat = CONCAT(mtd);
struct mtd_oob_ops devops = *ops;
- int i, err;
+ int i, err, ret = 0;
ops->retlen = 0;
@@ -262,12 +263,24 @@
err = subdev->read_oob(subdev, from, &devops);
ops->retlen += devops.retlen;
- if (err)
- return err;
+
+ /* Save information about bitflips! */
+ if (unlikely(err)) {
+ if (err == -EBADMSG) {
+ mtd->ecc_stats.failed++;
+ ret = err;
+ } else if (err == -EUCLEAN) {
+ mtd->ecc_stats.corrected++;
+ /* Do not overwrite -EBADMSG !! */
+ if (!ret)
+ ret = err;
+ } else
+ return err;
+ }
devops.len = ops->len - ops->retlen;
if (!devops.len)
- return 0;
+ return ret;
if (devops.datbuf)
devops.datbuf += devops.retlen;
@@ -655,6 +668,8 @@
}
err = subdev->block_markbad(subdev, ofs);
+ if (!err)
+ mtd->ecc_stats.badblocks++;
break;
}
@@ -717,6 +732,8 @@
if (subdev[0]->block_markbad)
concat->mtd.block_markbad = concat_block_markbad;
+ concat->mtd.ecc_stats.badblocks = subdev[0]->ecc_stats.badblocks;
+
concat->subdev[0] = subdev[0];
for (i = 1; i < num_devs; i++) {
@@ -744,6 +761,8 @@
subdev[i]->flags & MTD_WRITEABLE;
}
concat->mtd.size += subdev[i]->size;
+ concat->mtd.ecc_stats.badblocks +=
+ subdev[i]->ecc_stats.badblocks;
if (concat->mtd.writesize != subdev[i]->writesize ||
concat->mtd.oobsize != subdev[i]->oobsize ||
concat->mtd.ecctype != subdev[i]->ecctype ||
diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c
index f22aecc..77a7123 100644
--- a/drivers/mtd/mtdpart.c
+++ b/drivers/mtd/mtdpart.c
@@ -51,12 +51,21 @@
size_t *retlen, u_char *buf)
{
struct mtd_part *part = PART(mtd);
+ int res;
+
if (from >= mtd->size)
len = 0;
else if (from + len > mtd->size)
len = mtd->size - from;
- return part->master->read (part->master, from + part->offset,
+ res = part->master->read (part->master, from + part->offset,
len, retlen, buf);
+ if (unlikely(res)) {
+ if (res == -EUCLEAN)
+ mtd->ecc_stats.corrected++;
+ if (res == -EBADMSG)
+ mtd->ecc_stats.failed++;
+ }
+ return res;
}
static int part_point (struct mtd_info *mtd, loff_t from, size_t len,
@@ -82,12 +91,21 @@
struct mtd_oob_ops *ops)
{
struct mtd_part *part = PART(mtd);
+ int res;
if (from >= mtd->size)
return -EINVAL;
if (from + ops->len > mtd->size)
return -EINVAL;
- return part->master->read_oob(part->master, from + part->offset, ops);
+ res = part->master->read_oob(part->master, from + part->offset, ops);
+
+ if (unlikely(res)) {
+ if (res == -EUCLEAN)
+ mtd->ecc_stats.corrected++;
+ if (res == -EBADMSG)
+ mtd->ecc_stats.failed++;
+ }
+ return res;
}
static int part_read_user_prot_reg (struct mtd_info *mtd, loff_t from, size_t len,
@@ -246,12 +264,17 @@
static int part_block_markbad (struct mtd_info *mtd, loff_t ofs)
{
struct mtd_part *part = PART(mtd);
+ int res;
+
if (!(mtd->flags & MTD_WRITEABLE))
return -EROFS;
if (ofs >= mtd->size)
return -EINVAL;
ofs += part->offset;
- return part->master->block_markbad(part->master, ofs);
+ res = part->master->block_markbad(part->master, ofs);
+ if (!res)
+ mtd->ecc_stats.badblocks++;
+ return res;
}
/*
@@ -436,6 +459,16 @@
}
slave->mtd.ecclayout = master->ecclayout;
+ if (master->block_isbad) {
+ uint32_t offs = 0;
+
+ while(offs < slave->mtd.size) {
+ if (master->block_isbad(master,
+ offs + slave->offset))
+ slave->mtd.ecc_stats.badblocks++;
+ offs += slave->mtd.erasesize;
+ }
+ }
if(parts[i].mtdp)
{ /* store the object pointer (caller may or may not register it */
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index 7a3a449..ea6d2c3 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -347,7 +347,7 @@
{
struct nand_chip *chip = mtd->priv;
uint8_t buf[2] = { 0, 0 };
- int block;
+ int block, ret;
/* Get block number */
block = ((int)ofs) >> chip->bbt_erase_shift;
@@ -356,16 +356,22 @@
/* Do we have a flash based bad block table ? */
if (chip->options & NAND_USE_FLASH_BBT)
- return nand_update_bbt(mtd, ofs);
+ ret = nand_update_bbt(mtd, ofs);
+ else {
+ /* We write two bytes, so we dont have to mess with 16 bit
+ * access
+ */
+ ofs += mtd->oobsize;
+ chip->ops.len = 2;
+ chip->ops.datbuf = NULL;
+ chip->ops.oobbuf = buf;
+ chip->ops.ooboffs = chip->badblockpos & ~0x01;
- /* We write two bytes, so we dont have to mess with 16 bit access */
- ofs += mtd->oobsize;
- chip->ops.len = 2;
- chip->ops.datbuf = NULL;
- chip->ops.oobbuf = buf;
- chip->ops.ooboffs = chip->badblockpos & ~0x01;
-
- return nand_do_write_oob(mtd, ofs, &chip->ops);
+ ret = nand_do_write_oob(mtd, ofs, &chip->ops);
+ }
+ if (!ret)
+ mtd->ecc_stats.badblocks++;
+ return ret;
}
/**
diff --git a/drivers/mtd/nand/nand_bbt.c b/drivers/mtd/nand/nand_bbt.c
index 480c3cb..a612c4e 100644
--- a/drivers/mtd/nand/nand_bbt.c
+++ b/drivers/mtd/nand/nand_bbt.c
@@ -176,6 +176,7 @@
printk(KERN_DEBUG "nand_read_bbt: Reserved block at 0x%08x\n",
((offs << 2) + (act >> 1)) << this->bbt_erase_shift);
this->bbt[offs + (act >> 3)] |= 0x2 << (act & 0x06);
+ mtd->ecc_stats.bbtblocks++;
continue;
}
/* Leave it for now, if its matured we can move this
@@ -187,6 +188,7 @@
this->bbt[offs + (act >> 3)] |= 0x3 << (act & 0x06);
else
this->bbt[offs + (act >> 3)] |= 0x1 << (act & 0x06);
+ mtd->ecc_stats.badblocks++;
}
}
totlen -= len;
@@ -431,6 +433,7 @@
this->bbt[i >> 3] |= 0x03 << (i & 0x6);
printk(KERN_WARNING "Bad eraseblock %d at 0x%08x\n",
i >> 1, (unsigned int)from);
+ mtd->ecc_stats.badblocks++;
}
i += 2;
diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h
index e75bb58..9536567 100644
--- a/include/linux/mtd/mtd.h
+++ b/include/linux/mtd/mtd.h
@@ -56,17 +56,6 @@
u_int32_t numblocks; /* Number of blocks of erasesize in this region */
};
-/**
- * struct mtd_ecc_stats - error correction status
- *
- * @corrected: number of corrected bits
- * @failed: number of uncorrectable errors
- */
-struct mtd_ecc_stats {
- unsigned long corrected;
- unsigned long failed;
-};
-
/*
* oob operation modes
*
diff --git a/include/mtd/mtd-abi.h b/include/mtd/mtd-abi.h
index 54c673f..c11a589 100644
--- a/include/mtd/mtd-abi.h
+++ b/include/mtd/mtd-abi.h
@@ -99,6 +99,8 @@
#define OTPGETREGIONINFO _IOW('M', 15, struct otp_info)
#define OTPLOCK _IOR('M', 16, struct otp_info)
#define ECCGETLAYOUT _IOR('M', 17, struct nand_ecclayout)
+#define ECCGETSTATS _IOR('M', 18, struct mtd_ecc_stats)
+#define MTDFILEMODE _IO('M', 19)
/*
* Obsolete legacy interface. Keep it in order not to break userspace
@@ -128,4 +130,29 @@
struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES];
};
+/**
+ * struct mtd_ecc_stats - error correction status
+ *
+ * @corrected: number of corrected bits
+ * @failed: number of uncorrectable errors
+ * @badblocks: number of bad blocks in this partition
+ * @bbtblocks: number of blocks reserved for bad block tables
+ */
+struct mtd_ecc_stats {
+ uint32_t corrected;
+ uint32_t failed;
+ uint32_t badblocks;
+ uint32_t bbtblocks;
+};
+
+/*
+ * Read/write file modes for access to MTD
+ */
+enum mtd_file_modes {
+ MTD_MODE_NORMAL = MTD_OTP_OFF,
+ MTD_MODE_OTP_FACTORY = MTD_OTP_FACTORY,
+ MTD_MODE_OTP_USER = MTD_OTP_USER,
+ MTD_MODE_RAW,
+};
+
#endif /* __MTD_ABI_H__ */