123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- From 2c9745d36e04ac27161acd78514f647b9b587ad4 Mon Sep 17 00:00:00 2001
- From: =?UTF-8?q?Micha=C5=82=20K=C4=99pie=C5=84?= <[email protected]>
- Date: Wed, 29 Jun 2022 14:57:37 +0200
- Subject: [PATCH 4/4] mtdchar: add MEMREAD ioctl
- MIME-Version: 1.0
- Content-Type: text/plain; charset=UTF-8
- Content-Transfer-Encoding: 8bit
- User-space applications making use of MTD devices via /dev/mtd*
- character devices currently have limited capabilities for reading data:
- - only deprecated methods of accessing OOB layout information exist,
- - there is no way to explicitly specify MTD operation mode to use; it
- is auto-selected based on the MTD file mode (MTD_FILE_MODE_*) set
- for the character device; in particular, this prevents using
- MTD_OPS_AUTO_OOB for reads,
- - all existing user-space interfaces which cause mtd_read() or
- mtd_read_oob() to be called (via mtdchar_read() and
- mtdchar_read_oob(), respectively) return success even when those
- functions return -EUCLEAN or -EBADMSG; this renders user-space
- applications using these interfaces unaware of any corrected
- bitflips or uncorrectable ECC errors detected during reads.
- Note that the existing MEMWRITE ioctl allows the MTD operation mode to
- be explicitly set, allowing user-space applications to write page data
- and OOB data without requiring them to know anything about the OOB
- layout of the MTD device they are writing to (MTD_OPS_AUTO_OOB). Also,
- the MEMWRITE ioctl does not mangle the return value of mtd_write_oob().
- Add a new ioctl, MEMREAD, which addresses the above issues. It is
- intended to be a read-side counterpart of the existing MEMWRITE ioctl.
- Similarly to the latter, the read operation is performed in a loop which
- processes at most mtd->erasesize bytes in each iteration. This is done
- to prevent unbounded memory allocations caused by calling kmalloc() with
- the 'size' argument taken directly from the struct mtd_read_req provided
- by user space. However, the new ioctl is implemented so that the values
- it returns match those that would have been returned if just a single
- mtd_read_oob() call was issued to handle the entire read operation in
- one go.
- Note that while just returning -EUCLEAN or -EBADMSG to user space would
- already be a valid and useful indication of the ECC algorithm detecting
- errors during a read operation, that signal would not be granular enough
- to cover all use cases. For example, knowing the maximum number of
- bitflips detected in a single ECC step during a read operation performed
- on a given page may be useful when dealing with an MTD partition whose
- ECC layout varies across pages (e.g. a partition consisting of a
- bootloader area using a "custom" ECC layout followed by data pages using
- a "standard" ECC layout). To address that, include ECC statistics in
- the structure returned to user space by the new MEMREAD ioctl.
- Link: https://www.infradead.org/pipermail/linux-mtd/2016-April/067085.html
- Suggested-by: Boris Brezillon <[email protected]>
- Signed-off-by: Michał Kępień <[email protected]>
- Acked-by: Richard Weinberger <[email protected]>
- Signed-off-by: Miquel Raynal <[email protected]>
- Link: https://lore.kernel.org/linux-mtd/[email protected]
- ---
- drivers/mtd/mtdchar.c | 139 +++++++++++++++++++++++++++++++++++++
- include/uapi/mtd/mtd-abi.h | 64 +++++++++++++++--
- 2 files changed, 198 insertions(+), 5 deletions(-)
- --- a/drivers/mtd/mtdchar.c
- +++ b/drivers/mtd/mtdchar.c
- @@ -621,6 +621,137 @@ static int mtdchar_write_ioctl(struct mt
- 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;
- @@ -643,6 +774,7 @@ static int mtdchar_ioctl(struct file *fi
- case MEMGETINFO:
- case MEMREADOOB:
- case MEMREADOOB64:
- + case MEMREAD:
- case MEMISLOCKED:
- case MEMGETOOBSEL:
- case MEMGETBADBLOCK:
- @@ -817,6 +949,13 @@ static int mtdchar_ioctl(struct file *fi
- break;
- }
-
- + case MEMREAD:
- + {
- + ret = mtdchar_read_ioctl(mtd,
- + (struct mtd_read_req __user *)arg);
- + break;
- + }
- +
- case MEMLOCK:
- {
- struct erase_info_user einfo;
- --- 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,
|