diff --git a/drivers/mtd/mtdchar.c b/drivers/mtd/mtdchar.c index 05860288a7af..01f1c6792df9 100644 --- a/drivers/mtd/mtdchar.c +++ b/drivers/mtd/mtdchar.c @@ -688,6 +688,137 @@ static int mtdchar_write_ioctl(struct mtd_info *mtd, return ret; } +static int mtdchar_read_ioctl(struct mtd_info *mtd, + struct mtd_read_req __user *argp) +{ + struct mtd_info *master = mtd_get_master(mtd); + struct mtd_read_req req; + void __user *usr_data, *usr_oob; + uint8_t *datbuf = NULL, *oobbuf = NULL; + size_t datbuf_len, oobbuf_len; + size_t orig_len, orig_ooblen; + int ret = 0; + + if (copy_from_user(&req, argp, sizeof(req))) + return -EFAULT; + + orig_len = req.len; + orig_ooblen = req.ooblen; + + usr_data = (void __user *)(uintptr_t)req.usr_data; + usr_oob = (void __user *)(uintptr_t)req.usr_oob; + + if (!master->_read_oob) + return -EOPNOTSUPP; + + if (!usr_data) + req.len = 0; + + if (!usr_oob) + req.ooblen = 0; + + req.ecc_stats.uncorrectable_errors = 0; + req.ecc_stats.corrected_bitflips = 0; + req.ecc_stats.max_bitflips = 0; + + req.len &= 0xffffffff; + req.ooblen &= 0xffffffff; + + if (req.start + req.len > mtd->size) { + ret = -EINVAL; + goto out; + } + + datbuf_len = min_t(size_t, req.len, mtd->erasesize); + if (datbuf_len > 0) { + datbuf = kvmalloc(datbuf_len, GFP_KERNEL); + if (!datbuf) { + ret = -ENOMEM; + goto out; + } + } + + oobbuf_len = min_t(size_t, req.ooblen, mtd->erasesize); + if (oobbuf_len > 0) { + oobbuf = kvmalloc(oobbuf_len, GFP_KERNEL); + if (!oobbuf) { + ret = -ENOMEM; + goto out; + } + } + + while (req.len > 0 || (!usr_data && req.ooblen > 0)) { + struct mtd_req_stats stats; + struct mtd_oob_ops ops = { + .mode = req.mode, + .len = min_t(size_t, req.len, datbuf_len), + .ooblen = min_t(size_t, req.ooblen, oobbuf_len), + .datbuf = datbuf, + .oobbuf = oobbuf, + .stats = &stats, + }; + + /* + * Shorten non-page-aligned, eraseblock-sized reads so that the + * read ends on an eraseblock boundary. This is necessary in + * order to prevent OOB data for some pages from being + * duplicated in the output of non-page-aligned reads requiring + * multiple mtd_read_oob() calls to be completed. + */ + if (ops.len == mtd->erasesize) + ops.len -= mtd_mod_by_ws(req.start + ops.len, mtd); + + ret = mtd_read_oob(mtd, (loff_t)req.start, &ops); + + req.ecc_stats.uncorrectable_errors += + stats.uncorrectable_errors; + req.ecc_stats.corrected_bitflips += stats.corrected_bitflips; + req.ecc_stats.max_bitflips = + max(req.ecc_stats.max_bitflips, stats.max_bitflips); + + if (ret && !mtd_is_bitflip_or_eccerr(ret)) + break; + + if (copy_to_user(usr_data, ops.datbuf, ops.retlen) || + copy_to_user(usr_oob, ops.oobbuf, ops.oobretlen)) { + ret = -EFAULT; + break; + } + + req.start += ops.retlen; + req.len -= ops.retlen; + usr_data += ops.retlen; + + req.ooblen -= ops.oobretlen; + usr_oob += ops.oobretlen; + } + + /* + * As multiple iterations of the above loop (and therefore multiple + * mtd_read_oob() calls) may be necessary to complete the read request, + * adjust the final return code to ensure it accounts for all detected + * ECC errors. + */ + if (!ret || mtd_is_bitflip(ret)) { + if (req.ecc_stats.uncorrectable_errors > 0) + ret = -EBADMSG; + else if (req.ecc_stats.corrected_bitflips > 0) + ret = -EUCLEAN; + } + +out: + req.len = orig_len - req.len; + req.ooblen = orig_ooblen - req.ooblen; + + if (copy_to_user(argp, &req, sizeof(req))) + ret = -EFAULT; + + kvfree(datbuf); + kvfree(oobbuf); + + return ret; +} + static int mtdchar_ioctl(struct file *file, u_int cmd, u_long arg) { struct mtd_file_info *mfi = file->private_data; @@ -710,6 +841,7 @@ static int mtdchar_ioctl(struct file *file, u_int cmd, u_long arg) case MEMGETINFO: case MEMREADOOB: case MEMREADOOB64: + case MEMREAD: case MEMISLOCKED: case MEMGETOOBSEL: case MEMGETBADBLOCK: @@ -884,6 +1016,13 @@ static int mtdchar_ioctl(struct file *file, u_int cmd, u_long arg) break; } + case MEMREAD: + { + ret = mtdchar_read_ioctl(mtd, + (struct mtd_read_req __user *)arg); + break; + } + case MEMLOCK: { struct erase_info_user einfo; diff --git a/include/uapi/mtd/mtd-abi.h b/include/uapi/mtd/mtd-abi.h index 890d9e5b76d7..714d55b49d2a 100644 --- a/include/uapi/mtd/mtd-abi.h +++ b/include/uapi/mtd/mtd-abi.h @@ -55,9 +55,9 @@ struct mtd_oob_buf64 { * @MTD_OPS_RAW: data are transferred as-is, with no error correction; * this mode implies %MTD_OPS_PLACE_OOB * - * These modes can be passed to ioctl(MEMWRITE) and are also used internally. - * See notes on "MTD file modes" for discussion on %MTD_OPS_RAW vs. - * %MTD_FILE_MODE_RAW. + * These modes can be passed to ioctl(MEMWRITE) and ioctl(MEMREAD); they are + * also used internally. See notes on "MTD file modes" for discussion on + * %MTD_OPS_RAW vs. %MTD_FILE_MODE_RAW. */ enum { MTD_OPS_PLACE_OOB = 0, @@ -91,6 +91,53 @@ struct mtd_write_req { __u8 padding[7]; }; +/** + * struct mtd_read_req_ecc_stats - ECC statistics for a read operation + * + * @uncorrectable_errors: the number of uncorrectable errors that happened + * during the read operation + * @corrected_bitflips: the number of bitflips corrected during the read + * operation + * @max_bitflips: the maximum number of bitflips detected in any single ECC + * step for the data read during the operation; this information + * can be used to decide whether the data stored in a specific + * region of the MTD device should be moved somewhere else to + * avoid data loss. + */ +struct mtd_read_req_ecc_stats { + __u32 uncorrectable_errors; + __u32 corrected_bitflips; + __u32 max_bitflips; +}; + +/** + * struct mtd_read_req - data structure for requesting a read operation + * + * @start: start address + * @len: length of data buffer (only lower 32 bits are used) + * @ooblen: length of OOB buffer (only lower 32 bits are used) + * @usr_data: user-provided data buffer + * @usr_oob: user-provided OOB buffer + * @mode: MTD mode (see "MTD operation modes") + * @padding: reserved, must be set to 0 + * @ecc_stats: ECC statistics for the read operation + * + * This structure supports ioctl(MEMREAD) operations, allowing data and/or OOB + * reads in various modes. To read from OOB-only, set @usr_data == NULL, and to + * read data-only, set @usr_oob == NULL. However, setting both @usr_data and + * @usr_oob to NULL is not allowed. + */ +struct mtd_read_req { + __u64 start; + __u64 len; + __u64 ooblen; + __u64 usr_data; + __u64 usr_oob; + __u8 mode; + __u8 padding[7]; + struct mtd_read_req_ecc_stats ecc_stats; +}; + #define MTD_ABSENT 0 #define MTD_RAM 1 #define MTD_ROM 2 @@ -207,6 +254,12 @@ struct otp_info { #define MEMWRITE _IOWR('M', 24, struct mtd_write_req) /* Erase a given range of user data (must be in mode %MTD_FILE_MODE_OTP_USER) */ #define OTPERASE _IOW('M', 25, struct otp_info) +/* + * Most generic read interface; can read in-band and/or out-of-band in various + * modes (see "struct mtd_read_req"). This ioctl is not supported for flashes + * without OOB, e.g., NOR flash. + */ +#define MEMREAD _IOWR('M', 26, struct mtd_read_req) /* * Obsolete legacy interface. Keep it in order not to break userspace @@ -270,8 +323,9 @@ struct mtd_ecc_stats { * Note: %MTD_FILE_MODE_RAW provides the same functionality as %MTD_OPS_RAW - * raw access to the flash, without error correction or autoplacement schemes. * Wherever possible, the MTD_OPS_* mode will override the MTD_FILE_MODE_* mode - * (e.g., when using ioctl(MEMWRITE)), but in some cases, the MTD_FILE_MODE is - * used out of necessity (e.g., `write()', ioctl(MEMWRITEOOB64)). + * (e.g., when using ioctl(MEMWRITE) or ioctl(MEMREAD)), but in some cases, the + * MTD_FILE_MODE is used out of necessity (e.g., `write()', + * ioctl(MEMWRITEOOB64)). */ enum mtd_file_modes { MTD_FILE_MODE_NORMAL = MTD_OTP_OFF,