[PATCH v3 1/3] mtd: mtdpart: write support for minor-aligned partitions

Thibaut VARÈNE
2022-07-29T09:19:27Z

This patch enables writing to mtd partitions where a partition boundary
sits on a "minor" erasesize boundary.

This patch adds a uint32_t `erasesize_minor` to struct mtd_info: the
smallest erasesize supported by the device, also exposed in sysfs.

This patch is a no-op if erasesize_minor is unset (0) and has no
userspace-visible side effect (in particular the reported erasesize in
/proc/mtd is unchanged). This new feature is only enabled for single
eraseregion devices, where it makes most sense.

This patch addresses an outstanding mtdpart.c FIXME that has been
present since the start of the linux git history.

Signed-off-by: John Thomson <git@johnthomson.fastmail.com.au>
Signed-off-by: Thibaut VARÈNE <hacks+kernel@slashdirt.org>
---
Documentation/ABI/testing/sysfs-class-mtd | 8 ++++++
drivers/mtd/mtdcore.c | 10 +++++++
drivers/mtd/mtdpart.c | 35 ++++++++++++++++-------
include/linux/mtd/mtd.h | 2 ++
4 files changed, 45 insertions(+), 10 deletions(-)

diff --git a/Documentation/ABI/testing/sysfs-class-mtd b/Documentation/ABI/testing/sysfs-class-mtd
index 3bc7c0a95..786068f63 100644
--- a/Documentation/ABI/testing/sysfs-class-mtd
+++ b/Documentation/ABI/testing/sysfs-class-mtd
@@ -240,3 +240,11 @@ Contact: linux-mtd@lists.infradead.org
Description:
Number of bytes available for a client to place data into
the out of band area.
+
+What: /sys/class/mtd/mtdX/erasesize_minor
+Date: July 2022
+KernelVersion: 5.20
+Contact: linux-mtd@lists.infradead.org
+Description:
+ "Minor" erase size for the device. If supported, this is
+ the smallest eraseblock size for the device.
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 9eb0680db..ccb80197d 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -168,6 +168,15 @@ static ssize_t mtd_erasesize_show(struct device *dev,
}
MTD_DEVICE_ATTR_RO(erasesize);

+static ssize_t mtd_erasesize_minor_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mtd_info *mtd = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%lu\n", (unsigned long)mtd->erasesize_minor);
+}
+MTD_DEVICE_ATTR_RO(erasesize_minor);
+
static ssize_t mtd_writesize_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -313,6 +322,7 @@ static struct attribute *mtd_attrs[] = {
&dev_attr_flags.attr,
&dev_attr_size.attr,
&dev_attr_erasesize.attr,
+ &dev_attr_erasesize_minor.attr,
&dev_attr_writesize.attr,
&dev_attr_subpagesize.attr,
&dev_attr_oobsize.attr,
diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c
index d442fa94c..6bf21567f 100644
--- a/drivers/mtd/mtdpart.c
+++ b/drivers/mtd/mtdpart.c
@@ -39,6 +39,7 @@ static struct mtd_info *allocate_partition(struct mtd_info *parent,
struct mtd_info *master = mtd_get_master(parent);
int wr_alignment = (parent->flags & MTD_NO_ERASE) ?
master->writesize : master->erasesize;
+ int wr_alignment_minor = 0;
u64 parent_size = mtd_is_partition(parent) ?
parent->part.size : parent->size;
struct mtd_info *child;
@@ -163,6 +164,7 @@ static struct mtd_info *allocate_partition(struct mtd_info *parent,
} else {
/* Single erase size */
child->erasesize = master->erasesize;
+ child->erasesize_minor = master->erasesize_minor;
}

/*
@@ -170,26 +172,39 @@ static struct mtd_info *allocate_partition(struct mtd_info *parent,
* exposes several regions with different erasesize. Adjust
* wr_alignment accordingly.
*/
- if (!(child->flags & MTD_NO_ERASE))
+ if (!(child->flags & MTD_NO_ERASE)) {
wr_alignment = child->erasesize;
+ wr_alignment_minor = child->erasesize_minor;
+ }

tmp = mtd_get_master_ofs(child, 0);
remainder = do_div(tmp, wr_alignment);
if ((child->flags & MTD_WRITEABLE) && remainder) {
- /* Doesn't start on a boundary of major erase size */
- /* FIXME: Let it be writable if it is on a boundary of
- * _minor_ erase size though */
- child->flags &= ~MTD_WRITEABLE;
- printk(KERN_WARNING"mtd: partition \"%s\" doesn't start on an erase/write block boundary -- force read-only\n",
- part->name);
+ if (wr_alignment_minor) {
+ /* rely on minor being a factor of major erasesize */
+ tmp = remainder;
+ remainder = do_div(tmp, wr_alignment_minor);
+ }
+ if (remainder) {
+ child->flags &= ~MTD_WRITEABLE;
+ pr_warn("mtd: partition \"%s\" doesn't start on an erase/write block boundary -- force read-only\n",
+ part->name);
+ }
}

tmp = mtd_get_master_ofs(child, 0) + child->part.size;
remainder = do_div(tmp, wr_alignment);
if ((child->flags & MTD_WRITEABLE) && remainder) {
- child->flags &= ~MTD_WRITEABLE;
- printk(KERN_WARNING"mtd: partition \"%s\" doesn't end on an erase/write block -- force read-only\n",
- part->name);
+ if (wr_alignment_minor) {
+ tmp = remainder;
+ remainder = do_div(tmp, wr_alignment_minor);
+ }
+
+ if (remainder) {
+ child->flags &= ~MTD_WRITEABLE;
+ pr_warn("mtd: partition \"%s\" doesn't end on an erase/write block boundary -- force read-only\n",
+ part->name);
+ }
}

child->size = child->part.size;
diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h
index 955aee14b..8efbc929e 100644
--- a/include/linux/mtd/mtd.h
+++ b/include/linux/mtd/mtd.h
@@ -238,6 +238,8 @@ struct mtd_info {
* information below if they desire
*/
uint32_t erasesize;
+ /* "Minor" (smallest) erase size supported by the whole device */
+ uint32_t erasesize_minor;
/* Minimal writable flash unit size. In case of NOR flash it is 1 (even
* though individual bits can be cleared), in case of NAND flash it is
* one NAND page (or half, or one-fourths of it), in case of ECC-ed NOR