[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>
This commit is contained in:
Thomas Gleixner 2006-05-30 00:37:34 +02:00
parent 9a1fcdfd4b
commit f1a28c0284
7 changed files with 259 additions and 98 deletions

View file

@ -49,24 +49,18 @@ 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:
@ -97,6 +91,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 +112,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 +133,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 +155,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;
@ -186,13 +189,26 @@ static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t
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_read(struct file *file, char __user *buf, size_t count,loff_t
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 @@ static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count
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 @@ 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);
}
@ -310,10 +342,41 @@ 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;
@ -554,16 +617,6 @@ static int mtd_ioctl(struct inode *inode, struct file *file,
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 @@ static int mtd_ioctl(struct inode *inode, struct file *file,
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 @@ 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;
@ -635,6 +674,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) {
@ -653,7 +694,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;
@ -664,6 +705,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;
}

View file

@ -56,7 +56,7 @@ concat_read(struct mtd_info *mtd, loff_t from, size_t len,
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 @@ concat_read(struct mtd_info *mtd, loff_t from, size_t len,
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 @@ concat_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
{
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 @@ concat_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
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 @@ static int concat_block_markbad(struct mtd_info *mtd, loff_t ofs)
}
err = subdev->block_markbad(subdev, ofs);
if (!err)
mtd->ecc_stats.badblocks++;
break;
}
@ -717,6 +732,8 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c
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 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c
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 ||

View file

@ -51,12 +51,21 @@ static int part_read (struct mtd_info *mtd, loff_t from, size_t len,
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 @@ static int part_read_oob(struct mtd_info *mtd, loff_t from,
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_isbad (struct mtd_info *mtd, loff_t ofs)
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 @@ int add_mtd_partitions(struct mtd_info *master,
}
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 */

View file

@ -347,7 +347,7 @@ static int nand_default_block_markbad(struct mtd_info *mtd, loff_t ofs)
{
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 @@ static int nand_default_block_markbad(struct mtd_info *mtd, loff_t ofs)
/* 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;
}
/**

View file

@ -176,6 +176,7 @@ static int read_bbt(struct mtd_info *mtd, uint8_t *buf, int page, int num,
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 @@ static int read_bbt(struct mtd_info *mtd, uint8_t *buf, int page, int num,
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 @@ static int create_bbt(struct mtd_info *mtd, uint8_t *buf,
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;

View file

@ -56,17 +56,6 @@ struct mtd_erase_region_info {
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
*

View file

@ -99,6 +99,8 @@ struct otp_info {
#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_ecclayout {
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__ */