Initial commit
Some checks failed
Build Kernel / Build all affected Kernels (push) Has been cancelled
Build all core packages / Build all core packages for selected target (push) Has been cancelled
Build and Push prebuilt tools container / Build and Push all prebuilt containers (push) Has been cancelled
Build Toolchains / Build Toolchains for each target (push) Has been cancelled
Build host tools / Build host tools for linux and macos based systems (push) Has been cancelled
Coverity scan build / Coverity x86/64 build (push) Has been cancelled
Some checks failed
Build Kernel / Build all affected Kernels (push) Has been cancelled
Build all core packages / Build all core packages for selected target (push) Has been cancelled
Build and Push prebuilt tools container / Build and Push all prebuilt containers (push) Has been cancelled
Build Toolchains / Build Toolchains for each target (push) Has been cancelled
Build host tools / Build host tools for linux and macos based systems (push) Has been cancelled
Coverity scan build / Coverity x86/64 build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,321 @@
|
||||
From 2c9745d36e04ac27161acd78514f647b9b587ad4 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Micha=C5=82=20K=C4=99pie=C5=84?= <kernel@kempniu.pl>
|
||||
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 <boris.brezillon@collabora.com>
|
||||
Signed-off-by: Michał Kępień <kernel@kempniu.pl>
|
||||
Acked-by: Richard Weinberger <richard@nod.at>
|
||||
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
|
||||
Link: https://lore.kernel.org/linux-mtd/20220629125737.14418-5-kernel@kempniu.pl
|
||||
---
|
||||
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,
|
||||
Reference in New Issue
Block a user