* [PATCH 0/3] mtd: point() for cfi_cmdset_0002, on simple maps only
@ 2026-10-10 17:21 Orgad Shaneh
2026-10-10 17:21 ` [PATCH 1/3] mtd: maps: only point() maps read through the simple accessors Orgad Shaneh
` (3 more replies)
0 siblings, 4 replies; 12+ messages in thread
From: Orgad Shaneh @ 2026-10-10 17:21 UTC (permalink / raw)
To: miquel.raynal, richard, vigneshr, tsbogend
Cc: linux-mtd, linux-mips, linux-kernel, linusw, kaloz, ulli.kroll,
john, nico, dwmw2, corbet, linux-doc
jffs2 scans a flash in place through mtd_point() when the chip driver
offers it, and copies every used eraseblock through mtd_read()
otherwise. cfi_cmdset_0002 never had point(), so every jffs2 mount on
an AMD-style NOR copies the whole used area. On an Octeon CN6635 board
with a 100 MB jffs2 partition on an 8-bit M29EW that takes 15-30 s at
every boot.
1/3 tightens the existing gate first. map_is_linear() only checks for
a physical address, so cfi_cmdset_0001 already points at maps whose
own accessors swap bytes (IXP4xx), switch pins (Gemini) or lock the
bus (lantiq). The new map_is_simple() also requires the simple
accessors.
2/3 adds point()/unpoint() to cfi_cmdset_0002 behind the same gate,
modeled on cfi_cmdset_0001, and lists it in the cramfs documentation.
3/3 lets the Octeon flash map use the simple accessors when no eMMC
host shares its boot bus. Without it, 2/3 changes nothing on Octeon.
It builds without 1/3 and 2/3 but only pays off once they are in, so
taking all three through mtd with an ack from Thomas seems simplest.
Tested on that board with 7.2.9: the mount drops to 0.6-0.8 s. The
md5s of the images on the jffs2 partition held across reboots and
across ten 8 MB write/delete rounds. The series applies to v7.3-rc4
and to mtd/next.
Separately, for the MTD maintainers to judge: the write-buffer count
truncation that cfc5ebc9540e ("mtd: cfi_cmdset_0002: cap the
write-buffer chunk at 256 bytes on an x8 device") fixed in
cfi_cmdset_0002 has two siblings. do_write_buffer() in cfi_cmdset_0001
writes CMD(words) and the one in cfi_cmdset_0020 writes
CMD(len / map_bankwidth(map) - 1). On an x8 device CMD() keeps only
the low 8 bits of the count in each device lane, so a write buffer
larger than 256 bytes per device would be programmed with a truncated
count. I have no x8 Intel or ST part to test on, so I have not touched
either; I do not know whether such a part exists.
Orgad Shaneh (3):
mtd: maps: only point() maps read through the simple accessors
mtd: cfi_cmdset_0002: implement point() for simple linear maps
MIPS: Octeon: flash: use the simple map accessors without a shared
eMMC
Documentation/filesystems/cramfs.rst | 11 +-
arch/mips/cavium-octeon/flash_setup.c | 37 +++++-
drivers/mtd/chips/cfi_cmdset_0001.c | 2 +-
drivers/mtd/chips/cfi_cmdset_0002.c | 156 +++++++++++++++++++++++++-
drivers/mtd/maps/map_funcs.c | 12 ++
include/linux/mtd/map.h | 2 +
6 files changed, 207 insertions(+), 13 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 1/3] mtd: maps: only point() maps read through the simple accessors
2026-10-10 17:21 [PATCH 0/3] mtd: point() for cfi_cmdset_0002, on simple maps only Orgad Shaneh
@ 2026-10-10 17:21 ` Orgad Shaneh
2026-10-10 17:21 ` [PATCH 2/3] mtd: cfi_cmdset_0002: implement point() for simple linear maps Orgad Shaneh
` (2 subsequent siblings)
3 siblings, 0 replies; 12+ messages in thread
From: Orgad Shaneh @ 2026-10-10 17:21 UTC (permalink / raw)
To: miquel.raynal, richard, vigneshr, tsbogend
Cc: linux-mtd, linux-mips, linux-kernel, linusw, kaloz, ulli.kroll,
john, nico, dwmw2, corbet, linux-doc
map_is_linear() only checks that a map has a physical address. Map
drivers that install their own accessors keep one, and a pointer
bypasses whatever those accessors do:
- physmap's IXP4xx support: the expansion bus only takes 16-bit
accesses (the reason it cannot use memcpy_fromio()), and on
little-endian kernels every access also flips address bit 1 and
swaps bytes;
- physmap's Gemini support enables the flash pins only inside its
accessors;
- lantiq-flash serializes the external bus against PCI and copies byte
by byte because the EBU cannot take a prefetching memcpy;
- cobalt-flash reads at an offset inside a PCI BAR while leaving phys 0.
cfi_cmdset_0001 hands such pointers to jffs2 today, and the IXP4xx
boards carry Intel StrataFlash. Add map_is_simple(), true when the map
reads through the simple accessors (always true without
CONFIG_MTD_COMPLEX_MAPPINGS, where the accessors are inline), and
require it before enabling point(). Maps with custom accessors fall
back to copying through them; on big-endian IXP4xx, where pointed reads
happened to work, jffs2 mounts get slower.
Fixes: 9d3b5086f6d4 ("mtd: physmap_of_gemini: Handle pin control")
Fixes: 2aba2f2a704d ("mtd: physmap_of: add a hook for Intel IXP4xx flash probing")
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Orgad Shaneh <orgads@gmail.com>
Cc: Linus Walleij <linusw@kernel.org>
Cc: Imre Kaloz <kaloz@openwrt.org>
Cc: Hans Ulli Kroll <ulli.kroll@googlemail.com>
Cc: John Crispin <john@phrozen.org>
---
drivers/mtd/chips/cfi_cmdset_0001.c | 2 +-
drivers/mtd/maps/map_funcs.c | 12 ++++++++++++
include/linux/mtd/map.h | 2 ++
3 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/drivers/mtd/chips/cfi_cmdset_0001.c b/drivers/mtd/chips/cfi_cmdset_0001.c
index b73596a..3f3364c 100644
--- a/drivers/mtd/chips/cfi_cmdset_0001.c
+++ b/drivers/mtd/chips/cfi_cmdset_0001.c
@@ -299,7 +299,7 @@ static void fixup_LH28F640BF(struct mtd_info *mtd)
static void fixup_use_point(struct mtd_info *mtd)
{
struct map_info *map = mtd->priv;
- if (!mtd->_point && map_is_linear(map)) {
+ if (!mtd->_point && map_is_linear(map) && map_is_simple(map)) {
mtd->_point = cfi_intelext_point;
mtd->_unpoint = cfi_intelext_unpoint;
}
diff --git a/drivers/mtd/maps/map_funcs.c b/drivers/mtd/maps/map_funcs.c
index 1a4add9..ccf1dbf 100644
--- a/drivers/mtd/maps/map_funcs.c
+++ b/drivers/mtd/maps/map_funcs.c
@@ -41,5 +41,17 @@ void simple_map_init(struct map_info *map)
}
EXPORT_SYMBOL(simple_map_init);
+
+/*
+ * True when every access goes through the simple accessors, so a reader
+ * may use map->virt directly; a map with its own accessors (byte swaps,
+ * bus locking, address fixups) must not be pointed at.
+ */
+bool map_is_simple(struct map_info *map)
+{
+ return map->read == simple_map_read &&
+ map->copy_from == simple_map_copy_from;
+}
+EXPORT_SYMBOL(map_is_simple);
MODULE_DESCRIPTION("Out-of-line map I/O");
MODULE_LICENSE("GPL");
diff --git a/include/linux/mtd/map.h b/include/linux/mtd/map.h
index 75b0b2a..ebee3fe 100644
--- a/include/linux/mtd/map.h
+++ b/include/linux/mtd/map.h
@@ -449,6 +449,7 @@ static inline void inline_map_copy_to(struct map_info *map, unsigned long to, co
#define map_copy_to(map, to, from, len) (map)->copy_to(map, to, from, len)
extern void simple_map_init(struct map_info *);
+bool map_is_simple(struct map_info *map);
#define map_is_linear(map) (map->phys != NO_XIP)
#else
@@ -460,6 +461,7 @@ extern void simple_map_init(struct map_info *);
#define simple_map_init(map) BUG_ON(!map_bankwidth_supported((map)->bankwidth))
#define map_is_linear(map) ({ (void)(map); 1; })
+#define map_is_simple(map) ({ (void)(map); 1; })
#endif /* !CONFIG_MTD_COMPLEX_MAPPINGS */
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 2/3] mtd: cfi_cmdset_0002: implement point() for simple linear maps
2026-10-10 17:21 [PATCH 0/3] mtd: point() for cfi_cmdset_0002, on simple maps only Orgad Shaneh
2026-10-10 17:21 ` [PATCH 1/3] mtd: maps: only point() maps read through the simple accessors Orgad Shaneh
@ 2026-10-10 17:21 ` Orgad Shaneh
2026-10-10 17:31 ` sashiko-bot
2026-10-10 17:21 ` [PATCH 3/3] MIPS: Octeon: flash: use the simple map accessors without a shared eMMC Orgad Shaneh
2026-10-10 18:08 ` [PATCH v2 0/3] mtd: point() for cfi_cmdset_0002, on simple maps only Orgad Shaneh
3 siblings, 1 reply; 12+ messages in thread
From: Orgad Shaneh @ 2026-10-10 17:21 UTC (permalink / raw)
To: miquel.raynal, richard, vigneshr, tsbogend
Cc: linux-mtd, linux-mips, linux-kernel, linusw, kaloz, ulli.kroll,
john, nico, dwmw2, corbet, linux-doc
cfi_cmdset_0001 lets a linearly mapped chip be pointed at, so jffs2 can
scan it in place. cfi_cmdset_0002 never got point(), and the jffs2 mount
on an AMD-style NOR copies every used eraseblock through
map_copy_from() instead.
Add a point()/unpoint() pair modeled on cfi_intelext_point(), entering
array mode with the AMD reset command; get_chip() already handles
FL_POINT. Like cfi_cmdset_0001 it is only enabled for maps that are
linear and read through the simple accessors. Where it differs:
- Points share the chip when no operation is suspended, and the reboot
reset may proceed from an idle FL_POINT (the chip is already in array
mode); an unpoint after that reset is not an error. On cfi_cmdset_0001
a point held across reboot leaves cfi_intelext_reset() waiting
forever.
- unpoint() releases the chip only when the last reference goes, and
point() returns the error when nothing could be pointed.
As with cfi_cmdset_0001, a held point makes writes, erases and sync on
that chip wait uninterruptibly for the last unpoint, and system suspend
fails with -EAGAIN. That matters for cramfs on MTD (CONFIG_CRAMFS_MTD),
which keeps its whole image pointed while mounted: before this patch
mtd_point() failed on AMD chips and cramfs fell back to the block
device; now, with cramfs and jffs2 on the same chip, jffs2 writes and
garbage collection, flash_erase, sync(2) - and so reboot(8), which syncs
first - block until cramfs is unmounted. Name cfi_cmdset_0002 next to
cfi_cmdset_0001 in the cramfs documentation's list of drivers that
support point().
On an Octeon CN6635 board with a 100 MB jffs2 partition on an M29EW
(8-bit bus, ~3.4 MB/s reads) the mount drops from 15-30 s to under 1 s.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Orgad Shaneh <orgads@gmail.com>
Cc: Nicolas Pitre <nico@fluxnic.net>
Cc: David Woodhouse <dwmw2@infradead.org>
---
Documentation/filesystems/cramfs.rst | 11 +-
drivers/mtd/chips/cfi_cmdset_0002.c | 156 ++++++++++++++++++++++++++-
2 files changed, 160 insertions(+), 7 deletions(-)
diff --git a/Documentation/filesystems/cramfs.rst b/Documentation/filesystems/cramfs.rst
index 221c0bf..7b706c5 100644
--- a/Documentation/filesystems/cramfs.rst
+++ b/Documentation/filesystems/cramfs.rst
@@ -75,11 +75,12 @@ The location of the cramfs image in memory is system dependent. You must
know the proper physical address where the cramfs image is located and
configure an MTD device for it. Also, that MTD device must be supported
by a map driver that implements the "point" method. Examples of such
-MTD drivers are cfi_cmdset_0001 (Intel/Sharp CFI flash) or physmap
-(Flash device in physical memory map). MTD partitions based on such devices
-are fine too. Then that device should be specified with the "mtd:" prefix
-as the mount device argument. For example, to mount the MTD device named
-"fs_partition" on the /mnt directory::
+MTD drivers are cfi_cmdset_0001 (Intel/Sharp CFI flash), cfi_cmdset_0002
+(AMD/Fujitsu CFI flash) or physmap (Flash device in physical memory map).
+MTD partitions based on such devices are fine too. Then that device should
+be specified with the "mtd:" prefix as the mount device argument. For
+example, to mount the MTD device named "fs_partition" on the /mnt
+directory::
$ mount -t cramfs mtd:fs_partition /mnt
diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002.c
index bd4f1ed..67dfa8f 100644
--- a/drivers/mtd/chips/cfi_cmdset_0002.c
+++ b/drivers/mtd/chips/cfi_cmdset_0002.c
@@ -63,6 +63,9 @@ enum cfi_quirks {
};
static int cfi_amdstd_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
+static int cfi_amdstd_point(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, void **virt, resource_size_t *phys);
+static int cfi_amdstd_unpoint(struct mtd_info *mtd, loff_t from, size_t len);
static int cfi_amdstd_write_words(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
#if !FORCE_WORD_WRITE
static int cfi_amdstd_write_buffers(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
@@ -512,6 +515,22 @@ static struct cfi_fixup jedec_fixup_table[] = {
{ 0, 0, NULL }
};
+/*
+ * Let jffs2 scan a linearly mapped flash in place, as cfi_cmdset_0001
+ * does: without point() its mount copies every used eraseblock. Only
+ * for maps read through the simple accessors - a pointer bypasses any
+ * byte swapping or bus locking a map driver does in its own.
+ */
+static void fixup_use_point(struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+
+ if (!mtd->_point && map_is_linear(map) && map_is_simple(map)) {
+ mtd->_point = cfi_amdstd_point;
+ mtd->_unpoint = cfi_amdstd_unpoint;
+ }
+}
+
static struct cfi_fixup fixup_table[] = {
/* The CFI vendor ids and the JEDEC vendor IDs appear
* to be common. It is like the devices id's are as
@@ -519,6 +538,7 @@ static struct cfi_fixup fixup_table[] = {
* we know that is the case.
*/
{ CFI_MFR_ANY, CFI_ID_ANY, fixup_use_erase_chip },
+ { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_point },
{ CFI_MFR_ATMEL, AT49BV6416, fixup_use_atmel_lock },
{ 0, 0, NULL }
};
@@ -976,8 +996,13 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr
return -EIO;
case FL_POINT:
- /* Only if there's no operation suspended... */
- if (mode == FL_READY && chip->oldstate == FL_READY)
+ /*
+ * Only if there's no operation suspended: the chip is in
+ * array mode, so reads, further points and the reboot
+ * reset (which only re-enters array mode) can go ahead.
+ */
+ if ((mode == FL_READY || mode == FL_POINT ||
+ mode == FL_SHUTDOWN) && chip->oldstate == FL_READY)
return 0;
fallthrough;
default:
@@ -1216,6 +1241,133 @@ do { \
#endif
+static int do_point_onechip(struct map_info *map, struct flchip *chip,
+ loff_t adr, size_t len)
+{
+ unsigned long cmd_addr;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int ret;
+
+ adr += chip->start;
+
+ /* Ensure cmd read/writes are aligned. */
+ cmd_addr = adr & ~(map_bankwidth(map) - 1);
+
+ mutex_lock(&chip->mutex);
+ ret = get_chip(map, chip, cmd_addr, FL_POINT);
+ if (!ret) {
+ if (chip->state != FL_POINT && chip->state != FL_READY)
+ map_write(map, CMD(0xf0), cmd_addr);
+
+ chip->state = FL_POINT;
+ chip->ref_point_counter++;
+ }
+ mutex_unlock(&chip->mutex);
+
+ return ret;
+}
+
+static int cfi_amdstd_point(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, void **virt, resource_size_t *phys)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long ofs, last_end = 0;
+ int chipnum;
+ int ret = -EINVAL;
+
+ if (!map->virt)
+ return -EINVAL;
+
+ /* ofs: offset within the first chip that the first read should start */
+ chipnum = (from >> cfi->chipshift);
+ ofs = from - (chipnum << cfi->chipshift);
+
+ *virt = map->virt + cfi->chips[chipnum].start + ofs;
+ if (phys)
+ *phys = map->phys + cfi->chips[chipnum].start + ofs;
+
+ while (len) {
+ unsigned long thislen;
+
+ if (chipnum >= cfi->numchips)
+ break;
+
+ /* We cannot point across chips that are virtually disjoint */
+ if (!last_end)
+ last_end = cfi->chips[chipnum].start;
+ else if (cfi->chips[chipnum].start != last_end)
+ break;
+
+ if ((len + ofs - 1) >> cfi->chipshift)
+ thislen = (1 << cfi->chipshift) - ofs;
+ else
+ thislen = len;
+
+ ret = do_point_onechip(map, &cfi->chips[chipnum], ofs, thislen);
+ if (ret)
+ break;
+
+ *retlen += thislen;
+ len -= thislen;
+
+ ofs = 0;
+ last_end += 1 << cfi->chipshift;
+ chipnum++;
+ }
+ return *retlen ? 0 : ret;
+}
+
+static int cfi_amdstd_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long ofs;
+ int chipnum, err = 0;
+
+ /* ofs: offset within the first chip that the first read should start */
+ chipnum = (from >> cfi->chipshift);
+ ofs = from - (chipnum << cfi->chipshift);
+
+ while (len && !err) {
+ unsigned long thislen;
+ struct flchip *chip;
+
+ if (chipnum >= cfi->numchips)
+ break;
+ chip = &cfi->chips[chipnum];
+
+ if ((len + ofs - 1) >> cfi->chipshift)
+ thislen = (1 << cfi->chipshift) - ofs;
+ else
+ thislen = len;
+
+ mutex_lock(&chip->mutex);
+ if (chip->state == FL_POINT) {
+ /* The last reader resumes whatever the first suspended */
+ if (--chip->ref_point_counter == 0) {
+ chip->state = FL_READY;
+ put_chip(map, chip, chip->start);
+ }
+ } else if (chip->state != FL_SHUTDOWN) {
+ /*
+ * After the reboot reset nothing is pointed any more;
+ * ref_point_counter no longer matters there.
+ */
+ pr_err("%s: Error: unpoint called on non pointed region\n",
+ map->name);
+ err = -EINVAL;
+ }
+ mutex_unlock(&chip->mutex);
+
+ len -= thislen;
+ ofs = 0;
+ chipnum++;
+ }
+
+ return err;
+}
+
static inline int do_read_onechip(struct map_info *map, struct flchip *chip, loff_t adr, size_t len, u_char *buf)
{
unsigned long cmd_addr;
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 3/3] MIPS: Octeon: flash: use the simple map accessors without a shared eMMC
2026-10-10 17:21 [PATCH 0/3] mtd: point() for cfi_cmdset_0002, on simple maps only Orgad Shaneh
2026-10-10 17:21 ` [PATCH 1/3] mtd: maps: only point() maps read through the simple accessors Orgad Shaneh
2026-10-10 17:21 ` [PATCH 2/3] mtd: cfi_cmdset_0002: implement point() for simple linear maps Orgad Shaneh
@ 2026-10-10 17:21 ` Orgad Shaneh
2026-10-10 17:32 ` sashiko-bot
2026-10-10 18:08 ` [PATCH v2 0/3] mtd: point() for cfi_cmdset_0002, on simple maps only Orgad Shaneh
3 siblings, 1 reply; 12+ messages in thread
From: Orgad Shaneh @ 2026-10-10 17:21 UTC (permalink / raw)
To: miquel.raynal, richard, vigneshr, tsbogend
Cc: linux-mtd, linux-mips, linux-kernel, linusw, kaloz, ulli.kroll,
john, nico, dwmw2, corbet, linux-doc
CAVIUM_OCTEON_SOC selects MTD_COMPLEX_MAPPINGS, and since
commit 8c1e6b14e27d ("MIPS: OCTEON: Protect accesses to bootbus flash
with octeon_bootbus_sem.") the flash map accessors take the semaphore
around every access. Its only other user is the eMMC host of the older
parts ("cavium,octeon-6130-mmc" in drivers/mmc/host/cavium-octeon.c),
which shares the boot bus; the CIU3 parts serialize their eMMC with a
lock of their own.
On boards without that host the semaphore guards nothing, but the
custom accessors make the map non-simple, so MTD will not let jffs2
point at it and its mount copies every used eraseblock. Use
simple_map_init() there. The test is for the node, not the driver, so
a disabled mmc node keeps the semaphore.
simple_map_init() BUG()s on an unsupported bank width; the old code
warned and then hit the BUG() in the inline map accessors on the first
probe command. Check the width first and fail the probe instead, and
unmap the window when the probe fails.
On an Octeon CN6635 board (no eMMC) with a 100 MB jffs2 partition on
an 8-bit NOR the mount drops from 15-30 s to under 1 s together with
point() support in cfi_cmdset_0002.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Orgad Shaneh <orgads@gmail.com>
---
arch/mips/cavium-octeon/flash_setup.c | 37 +++++++++++++++++++++++----
1 file changed, 32 insertions(+), 5 deletions(-)
diff --git a/arch/mips/cavium-octeon/flash_setup.c b/arch/mips/cavium-octeon/flash_setup.c
index 9242521..a616e7a 100644
--- a/arch/mips/cavium-octeon/flash_setup.c
+++ b/arch/mips/cavium-octeon/flash_setup.c
@@ -63,6 +63,17 @@ static void octeon_flash_map_copy_to(struct map_info *map, unsigned long to,
up(&octeon_bootbus_sem);
}
+static bool octeon_flash_bus_shared(void)
+{
+ struct device_node *np;
+ bool shared;
+
+ np = of_find_compatible_node(NULL, NULL, "cavium,octeon-6130-mmc");
+ shared = !!np;
+ of_node_put(np);
+ return shared;
+}
+
/*
* Module/ driver initialization.
*
@@ -102,11 +113,26 @@ static int octeon_flash_probe(struct platform_device *pdev)
flash_map.virt = ioremap(flash_map.phys, flash_map.size);
pr_notice("Bootbus flash: Setting flash for %luMB flash at "
"0x%08llx\n", flash_map.size >> 20, flash_map.phys);
- WARN_ON(!map_bankwidth_supported(flash_map.bankwidth));
- flash_map.read = octeon_flash_map_read;
- flash_map.write = octeon_flash_map_write;
- flash_map.copy_from = octeon_flash_map_copy_from;
- flash_map.copy_to = octeon_flash_map_copy_to;
+ if (!map_bankwidth_supported(flash_map.bankwidth)) {
+ pr_err("Unsupported flash bank width %d\n",
+ flash_map.bankwidth);
+ iounmap(flash_map.virt);
+ return -EINVAL;
+ }
+ /*
+ * The boot bus semaphore only serializes the flash against
+ * an eMMC host that shares the bus (the CIU3 parts have
+ * their own). Without one, use the simple accessors: they
+ * let MTD point at the flash, so jffs2 scans it in place.
+ */
+ if (octeon_flash_bus_shared()) {
+ flash_map.read = octeon_flash_map_read;
+ flash_map.write = octeon_flash_map_write;
+ flash_map.copy_from = octeon_flash_map_copy_from;
+ flash_map.copy_to = octeon_flash_map_copy_to;
+ } else {
+ simple_map_init(&flash_map);
+ }
mymtd = do_map_probe("cfi_probe", &flash_map);
if (mymtd) {
mymtd->owner = THIS_MODULE;
@@ -114,6 +140,7 @@ static int octeon_flash_probe(struct platform_device *pdev)
NULL, NULL, 0);
} else {
pr_err("Failed to register MTD device for flash\n");
+ iounmap(flash_map.virt);
}
}
return 0;
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 2/3] mtd: cfi_cmdset_0002: implement point() for simple linear maps
2026-10-10 17:21 ` [PATCH 2/3] mtd: cfi_cmdset_0002: implement point() for simple linear maps Orgad Shaneh
@ 2026-10-10 17:31 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-10-10 17:31 UTC (permalink / raw)
To: vigneshr, Orgad Shaneh, miquel.raynal, tsbogend, richard
Cc: linusw, corbet, linux-mips, linux-mtd, john, kaloz, ulli.kroll,
nico, dwmw2, linux-doc, linux-kernel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] mtd: cfi_cmdset_0002: unbounded TASK_UNINTERRUPTIBLE sleep causes Hung Task Panic
--
commit 97c2e746b1f52c7744c56b40bf00fe410dca196f
Author: Orgad Shaneh <orgads@gmail.com>
mtd: cfi_cmdset_0002: implement point() for simple linear maps
This commit adds point() and unpoint() support for simple linear maps to
the cfi_cmdset_0002 driver. This allows filesystems like jffs2 or cramfs
to scan memory in place without copying every used eraseblock. When a chip
is pointed to, writes, erases, and sync operations wait for the unpoint.
> diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002.c
> index 517db2f2707f4..67578fef02bfa 100644
> --- a/drivers/mtd/chips/cfi_cmdset_0002.c
> +++ b/drivers/mtd/chips/cfi_cmdset_0002.c
[ ... ]
> @@ -960,8 +980,13 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr
> return -EIO;
>
> case FL_POINT:
> - /* Only if there's no operation suspended... */
> - if (mode == FL_READY && chip->oldstate == FL_READY)
> + /*
> + * Only if there's no operation suspended: the chip is in
> + * array mode, so reads, further points and the reboot
> + * reset (which only re-enters array mode) can go ahead.
> + */
> + if ((mode == FL_READY || mode == FL_POINT ||
> + mode == FL_SHUTDOWN) && chip->oldstate == FL_READY)
> return 0;
> fallthrough;
[Severity: High]
Does this logic in get_chip() cause unintended blocking when the chip has a
suspended operation?
If a filesystem like CramFS mounts an MTD partition and calls mtd_point(),
chip->state becomes FL_POINT indefinitely until unmount. If this point
happens while an erase is suspended (chip->oldstate == FL_ERASING),
requests for FL_READY or FL_SHUTDOWN will fail the check for
chip->oldstate == FL_READY.
They will then fall through to the default sleep path. Because the point is
held indefinitely by the filesystem, could this cause the reboot notifier
(FL_SHUTDOWN) to deadlock permanently?
> default:
> sleep:
> set_current_state(TASK_UNINTERRUPTIBLE);
> add_wait_queue(&chip->wq, &wait);
> mutex_unlock(&chip->mutex);
> schedule();
> remove_wait_queue(&chip->wq, &wait);
> mutex_lock(&chip->mutex);
> goto resettime;
[Severity: High]
Is it safe to use TASK_UNINTERRUPTIBLE for unbounded synchronization here?
When chip->state is FL_POINT (held long-term by a mounted filesystem), and
another task initiates a flash write, erase, or sync, get_chip() is called
with FL_WRITING, FL_ERASING, or FL_SYNCING. These modes do not match the
FL_POINT exceptions and fall through directly to this sleep block.
Since FL_POINT won't be released until the filesystem is unmounted, tasks
sleeping here will remain in TASK_UNINTERRUPTIBLE indefinitely. Could this
cause a hung task panic after the standard 120-second timeout, allowing a
local user to crash the system simply by running sync(2) while CramFS is
mounted?
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261010172142.2138956-1-orgads@gmail.com?part=2
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 3/3] MIPS: Octeon: flash: use the simple map accessors without a shared eMMC
2026-10-10 17:21 ` [PATCH 3/3] MIPS: Octeon: flash: use the simple map accessors without a shared eMMC Orgad Shaneh
@ 2026-10-10 17:32 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-10-10 17:32 UTC (permalink / raw)
To: miquel.raynal, richard, vigneshr, Orgad Shaneh, tsbogend
Cc: dwmw2, ulli.kroll, corbet, kaloz, linux-doc, linux-kernel,
linusw, nico, linux-mtd, linux-mips, john
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] mips: cavium-octeon: global state corruption on multiple flash probes
--
commit 9e242ec4162d9c7c06bd10d98f2bbfb32869d150
Author: Orgad Shaneh <orgads@gmail.com>
MIPS: Octeon: flash: use the simple map accessors without a shared eMMC
This patch updates the Cavium Octeon flash setup to use simple map accessors
when there is no shared eMMC host on the boot bus. It also changes the flash
bank width check to fail and unmap the memory if unsupported, rather than
just warning.
> diff --git a/arch/mips/cavium-octeon/flash_setup.c b/arch/mips/cavium-octeon/flash_setup.c
> index 3395acde4d60a..28fbe3d15c956 100644
> --- a/arch/mips/cavium-octeon/flash_setup.c
> +++ b/arch/mips/cavium-octeon/flash_setup.c
[ ... ]
> @@ -102,11 +113,26 @@ static int octeon_flash_probe(struct platform_device *pdev)
> flash_map.virt = ioremap(flash_map.phys, flash_map.size);
> pr_notice("Bootbus flash: Setting flash for %luMB flash at "
> "0x%08llx\n", flash_map.size >> 20, flash_map.phys);
> - WARN_ON(!map_bankwidth_supported(flash_map.bankwidth));
> - flash_map.read = octeon_flash_map_read;
> - flash_map.write = octeon_flash_map_write;
> - flash_map.copy_from = octeon_flash_map_copy_from;
> - flash_map.copy_to = octeon_flash_map_copy_to;
> + if (!map_bankwidth_supported(flash_map.bankwidth)) {
> + pr_err("Unsupported flash bank width %d\n",
> + flash_map.bankwidth);
> + iounmap(flash_map.virt);
> + return -EINVAL;
> + }
[Severity: High]
Since flash_map is a static global structure, what happens if there are
multiple flash devices probed sequentially?
The line just above this block unconditionally overwrites flash_map.virt
with a new ioremap() on each probe, which appears to leak the previous
mapping and corrupt the state of any already-registered MTD device.
If octeon_flash_probe() executes a second time and fails the bank width check
here, it calls iounmap(flash_map.virt) and returns an error. Does this unmap
operation turn the first successfully probed MTD device's shared reference
into a dangling pointer, leading to an invalid memory access when the first
device is accessed?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261010172142.2138956-1-orgads@gmail.com?part=3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 0/3] mtd: point() for cfi_cmdset_0002, on simple maps only
2026-10-10 17:21 [PATCH 0/3] mtd: point() for cfi_cmdset_0002, on simple maps only Orgad Shaneh
` (2 preceding siblings ...)
2026-10-10 17:21 ` [PATCH 3/3] MIPS: Octeon: flash: use the simple map accessors without a shared eMMC Orgad Shaneh
@ 2026-10-10 18:08 ` Orgad Shaneh
2026-10-10 18:08 ` [PATCH v2 1/3] mtd: maps: only point() maps read through the simple accessors Orgad Shaneh
` (2 more replies)
3 siblings, 3 replies; 12+ messages in thread
From: Orgad Shaneh @ 2026-10-10 18:08 UTC (permalink / raw)
To: miquel.raynal, richard, vigneshr, tsbogend
Cc: linux-mtd, linux-mips, linux-kernel, linusw, kaloz, ulli.kroll,
john, nico, dwmw2, corbet, linux-doc
jffs2 scans a flash in place through mtd_point() when the chip driver
offers it, and copies every used eraseblock through mtd_read()
otherwise. cfi_cmdset_0002 never had point(), so every jffs2 mount on
an AMD-style NOR copies the whole used area. On an Octeon CN6635 board
with a 100 MB jffs2 partition on an 8-bit M29EW that takes 15-30 s at
every boot.
1/3 tightens the existing gate first. map_is_linear() only checks for
a physical address, so cfi_cmdset_0001 already points at maps whose
own accessors swap bytes (IXP4xx), switch pins (Gemini) or lock the
bus (lantiq). The new map_is_simple() also requires the simple
accessors.
2/3 adds point()/unpoint() to cfi_cmdset_0002 behind the same gate,
modeled on cfi_cmdset_0001, and lists it in the cramfs documentation.
3/3 lets the Octeon flash map use the simple accessors when no eMMC
host shares its boot bus. Without it, 2/3 changes nothing on Octeon.
It builds without 1/3 and 2/3 but only pays off once they are in, so
taking all three through mtd with an ack from Thomas seems simplest.
Tested on that board with 7.2.9: the mount drops to 0.6-0.8 s. The
md5s of the images on the jffs2 partition held across reboots and
across ten 8 MB write/delete rounds. The series applies to v7.3-rc4
and to mtd/next.
Separately, for the MTD maintainers to judge: the write-buffer count
truncation that cfc5ebc9540e ("mtd: cfi_cmdset_0002: cap the
write-buffer chunk at 256 bytes on an x8 device") fixed in
cfi_cmdset_0002 has two siblings. do_write_buffer() in cfi_cmdset_0001
writes CMD(words) and the one in cfi_cmdset_0020 writes
CMD(len / map_bankwidth(map) - 1). On an x8 device CMD() keeps only
the low 8 bits of the count in each device lane, so a write buffer
larger than 256 bytes per device would be programmed with a truncated
count. I have no x8 Intel or ST part to test on, so I have not touched
either; I do not know whether such a part exists.
Changes in v2, all from sashiko's review of v1:
- 2/3: a point no longer suspends an erase in progress. cramfs holds its
point for the whole mount, so the erase - and the task waiting on it,
and the reboot reset - would stay suspended until umount.
- 3/3: the bank width is checked before ioremap(), and the iounmap()
calls v1 added are gone; flash_map is a single static instance, so
on a second probe they could unmap what the first one registered.
- Not changed: sashiko also asked about writers sleeping
uninterruptibly behind a long-held point. That is how
cfi_cmdset_0001 has always behaved, and 2/3 describes the cramfs
consequence: a cramfs mounted from /dev/mtdblockN on AMD flash
today falls back to the block device, and after this series it
takes the direct path, so writes to other partitions of the same
chip wait until it is unmounted. If you would rather not change
that, I can make point() in cfi_cmdset_0002 opt-in.
v1: https://lore.kernel.org/all/20261010172142.2138956-1-orgads@gmail.com/
Orgad Shaneh (3):
mtd: maps: only point() maps read through the simple accessors
mtd: cfi_cmdset_0002: implement point() for simple linear maps
MIPS: Octeon: flash: use the simple map accessors without a shared
eMMC
Documentation/filesystems/cramfs.rst | 11 +-
arch/mips/cavium-octeon/flash_setup.c | 36 +++++-
drivers/mtd/chips/cfi_cmdset_0001.c | 2 +-
drivers/mtd/chips/cfi_cmdset_0002.c | 162 +++++++++++++++++++++++++-
drivers/mtd/maps/map_funcs.c | 12 ++
include/linux/mtd/map.h | 2 +
6 files changed, 211 insertions(+), 14 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 1/3] mtd: maps: only point() maps read through the simple accessors
2026-10-10 18:08 ` [PATCH v2 0/3] mtd: point() for cfi_cmdset_0002, on simple maps only Orgad Shaneh
@ 2026-10-10 18:08 ` Orgad Shaneh
2026-10-10 18:08 ` [PATCH v2 2/3] mtd: cfi_cmdset_0002: implement point() for simple linear maps Orgad Shaneh
2026-10-10 18:08 ` [PATCH v2 3/3] MIPS: Octeon: flash: use the simple map accessors without a shared eMMC Orgad Shaneh
2 siblings, 0 replies; 12+ messages in thread
From: Orgad Shaneh @ 2026-10-10 18:08 UTC (permalink / raw)
To: miquel.raynal, richard, vigneshr, tsbogend
Cc: linux-mtd, linux-mips, linux-kernel, linusw, kaloz, ulli.kroll,
john, nico, dwmw2, corbet, linux-doc
map_is_linear() only checks that a map has a physical address. Map
drivers that install their own accessors keep one, and a pointer
bypasses whatever those accessors do:
- physmap's IXP4xx support: the expansion bus only takes 16-bit
accesses (the reason it cannot use memcpy_fromio()), and on
little-endian kernels every access also flips address bit 1 and
swaps bytes;
- physmap's Gemini support enables the flash pins only inside its
accessors;
- lantiq-flash serializes the external bus against PCI and copies byte
by byte because the EBU cannot take a prefetching memcpy;
- cobalt-flash reads at an offset inside a PCI BAR while leaving phys 0.
cfi_cmdset_0001 hands such pointers to jffs2 today, and the IXP4xx
boards carry Intel StrataFlash. Add map_is_simple(), true when the map
reads through the simple accessors (always true without
CONFIG_MTD_COMPLEX_MAPPINGS, where the accessors are inline), and
require it before enabling point(). Maps with custom accessors fall
back to copying through them; on big-endian IXP4xx, where pointed reads
happened to work, jffs2 mounts get slower.
Fixes: 9d3b5086f6d4 ("mtd: physmap_of_gemini: Handle pin control")
Fixes: 2aba2f2a704d ("mtd: physmap_of: add a hook for Intel IXP4xx flash probing")
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Orgad Shaneh <orgads@gmail.com>
Cc: Linus Walleij <linusw@kernel.org>
Cc: Imre Kaloz <kaloz@openwrt.org>
Cc: Hans Ulli Kroll <ulli.kroll@googlemail.com>
Cc: John Crispin <john@phrozen.org>
---
v2: no change.
drivers/mtd/chips/cfi_cmdset_0001.c | 2 +-
drivers/mtd/maps/map_funcs.c | 12 ++++++++++++
include/linux/mtd/map.h | 2 ++
3 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/drivers/mtd/chips/cfi_cmdset_0001.c b/drivers/mtd/chips/cfi_cmdset_0001.c
index b73596a..3f3364c 100644
--- a/drivers/mtd/chips/cfi_cmdset_0001.c
+++ b/drivers/mtd/chips/cfi_cmdset_0001.c
@@ -299,7 +299,7 @@ static void fixup_LH28F640BF(struct mtd_info *mtd)
static void fixup_use_point(struct mtd_info *mtd)
{
struct map_info *map = mtd->priv;
- if (!mtd->_point && map_is_linear(map)) {
+ if (!mtd->_point && map_is_linear(map) && map_is_simple(map)) {
mtd->_point = cfi_intelext_point;
mtd->_unpoint = cfi_intelext_unpoint;
}
diff --git a/drivers/mtd/maps/map_funcs.c b/drivers/mtd/maps/map_funcs.c
index 1a4add9..ccf1dbf 100644
--- a/drivers/mtd/maps/map_funcs.c
+++ b/drivers/mtd/maps/map_funcs.c
@@ -41,5 +41,17 @@ void simple_map_init(struct map_info *map)
}
EXPORT_SYMBOL(simple_map_init);
+
+/*
+ * True when every access goes through the simple accessors, so a reader
+ * may use map->virt directly; a map with its own accessors (byte swaps,
+ * bus locking, address fixups) must not be pointed at.
+ */
+bool map_is_simple(struct map_info *map)
+{
+ return map->read == simple_map_read &&
+ map->copy_from == simple_map_copy_from;
+}
+EXPORT_SYMBOL(map_is_simple);
MODULE_DESCRIPTION("Out-of-line map I/O");
MODULE_LICENSE("GPL");
diff --git a/include/linux/mtd/map.h b/include/linux/mtd/map.h
index 75b0b2a..ebee3fe 100644
--- a/include/linux/mtd/map.h
+++ b/include/linux/mtd/map.h
@@ -449,6 +449,7 @@ static inline void inline_map_copy_to(struct map_info *map, unsigned long to, co
#define map_copy_to(map, to, from, len) (map)->copy_to(map, to, from, len)
extern void simple_map_init(struct map_info *);
+bool map_is_simple(struct map_info *map);
#define map_is_linear(map) (map->phys != NO_XIP)
#else
@@ -460,6 +461,7 @@ extern void simple_map_init(struct map_info *);
#define simple_map_init(map) BUG_ON(!map_bankwidth_supported((map)->bankwidth))
#define map_is_linear(map) ({ (void)(map); 1; })
+#define map_is_simple(map) ({ (void)(map); 1; })
#endif /* !CONFIG_MTD_COMPLEX_MAPPINGS */
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 2/3] mtd: cfi_cmdset_0002: implement point() for simple linear maps
2026-10-10 18:08 ` [PATCH v2 0/3] mtd: point() for cfi_cmdset_0002, on simple maps only Orgad Shaneh
2026-10-10 18:08 ` [PATCH v2 1/3] mtd: maps: only point() maps read through the simple accessors Orgad Shaneh
@ 2026-10-10 18:08 ` Orgad Shaneh
2026-10-10 18:18 ` sashiko-bot
2026-10-10 18:08 ` [PATCH v2 3/3] MIPS: Octeon: flash: use the simple map accessors without a shared eMMC Orgad Shaneh
2 siblings, 1 reply; 12+ messages in thread
From: Orgad Shaneh @ 2026-10-10 18:08 UTC (permalink / raw)
To: miquel.raynal, richard, vigneshr, tsbogend
Cc: linux-mtd, linux-mips, linux-kernel, linusw, kaloz, ulli.kroll,
john, nico, dwmw2, corbet, linux-doc
cfi_cmdset_0001 lets a linearly mapped chip be pointed at, so jffs2 can
scan it in place. cfi_cmdset_0002 never got point(), and the jffs2 mount
on an AMD-style NOR copies every used eraseblock through
map_copy_from() instead.
Add a point()/unpoint() pair modeled on cfi_intelext_point(), entering
array mode with the AMD reset command; get_chip() already handles
FL_POINT. Like cfi_cmdset_0001 it is only enabled for maps that are
linear and read through the simple accessors. Where it differs:
- Points share the chip when no operation is suspended, and the reboot
reset may proceed from an idle FL_POINT (the chip is already in array
mode); an unpoint after that reset is not an error. On cfi_cmdset_0001
a point held across reboot leaves cfi_intelext_reset() waiting
forever.
- unpoint() releases the chip only when the last reference goes, and
point() returns the error when nothing could be pointed.
- A point never suspends an erase; it waits for the erase to finish.
The erase would otherwise stay suspended for as long as the point
is held, and the reboot reset with it.
As with cfi_cmdset_0001, a held point makes writes, erases and sync on
that chip wait uninterruptibly for the last unpoint, and system suspend
fails with -EAGAIN. That matters for cramfs on MTD (CONFIG_CRAMFS_MTD),
which keeps its whole image pointed while mounted: before this patch
mtd_point() failed on AMD chips and cramfs fell back to the block
device; now, with cramfs and jffs2 on the same chip, jffs2 writes and
garbage collection, flash_erase, sync(2) - and so reboot(8), which syncs
first - block until cramfs is unmounted. Name cfi_cmdset_0002 next to
cfi_cmdset_0001 in the cramfs documentation's list of drivers that
support point().
On an Octeon CN6635 board with a 100 MB jffs2 partition on an M29EW
(8-bit bus, ~3.4 MB/s reads) the mount drops from 15-30 s to under 1 s.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Orgad Shaneh <orgads@gmail.com>
Cc: Nicolas Pitre <nico@fluxnic.net>
Cc: David Woodhouse <dwmw2@infradead.org>
---
v2: a point no longer suspends an erase in progress; it waits for
the erase instead (sashiko).
Documentation/filesystems/cramfs.rst | 11 +-
drivers/mtd/chips/cfi_cmdset_0002.c | 162 ++++++++++++++++++++++++++-
2 files changed, 165 insertions(+), 8 deletions(-)
diff --git a/Documentation/filesystems/cramfs.rst b/Documentation/filesystems/cramfs.rst
index 221c0bf..7b706c5 100644
--- a/Documentation/filesystems/cramfs.rst
+++ b/Documentation/filesystems/cramfs.rst
@@ -75,11 +75,12 @@ The location of the cramfs image in memory is system dependent. You must
know the proper physical address where the cramfs image is located and
configure an MTD device for it. Also, that MTD device must be supported
by a map driver that implements the "point" method. Examples of such
-MTD drivers are cfi_cmdset_0001 (Intel/Sharp CFI flash) or physmap
-(Flash device in physical memory map). MTD partitions based on such devices
-are fine too. Then that device should be specified with the "mtd:" prefix
-as the mount device argument. For example, to mount the MTD device named
-"fs_partition" on the /mnt directory::
+MTD drivers are cfi_cmdset_0001 (Intel/Sharp CFI flash), cfi_cmdset_0002
+(AMD/Fujitsu CFI flash) or physmap (Flash device in physical memory map).
+MTD partitions based on such devices are fine too. Then that device should
+be specified with the "mtd:" prefix as the mount device argument. For
+example, to mount the MTD device named "fs_partition" on the /mnt
+directory::
$ mount -t cramfs mtd:fs_partition /mnt
diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002.c
index bd4f1ed..7abb101 100644
--- a/drivers/mtd/chips/cfi_cmdset_0002.c
+++ b/drivers/mtd/chips/cfi_cmdset_0002.c
@@ -63,6 +63,9 @@ enum cfi_quirks {
};
static int cfi_amdstd_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
+static int cfi_amdstd_point(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, void **virt, resource_size_t *phys);
+static int cfi_amdstd_unpoint(struct mtd_info *mtd, loff_t from, size_t len);
static int cfi_amdstd_write_words(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
#if !FORCE_WORD_WRITE
static int cfi_amdstd_write_buffers(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
@@ -512,6 +515,22 @@ static struct cfi_fixup jedec_fixup_table[] = {
{ 0, 0, NULL }
};
+/*
+ * Let jffs2 scan a linearly mapped flash in place, as cfi_cmdset_0001
+ * does: without point() its mount copies every used eraseblock. Only
+ * for maps read through the simple accessors - a pointer bypasses any
+ * byte swapping or bus locking a map driver does in its own.
+ */
+static void fixup_use_point(struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+
+ if (!mtd->_point && map_is_linear(map) && map_is_simple(map)) {
+ mtd->_point = cfi_amdstd_point;
+ mtd->_unpoint = cfi_amdstd_unpoint;
+ }
+}
+
static struct cfi_fixup fixup_table[] = {
/* The CFI vendor ids and the JEDEC vendor IDs appear
* to be common. It is like the devices id's are as
@@ -519,6 +538,7 @@ static struct cfi_fixup fixup_table[] = {
* we know that is the case.
*/
{ CFI_MFR_ANY, CFI_ID_ANY, fixup_use_erase_chip },
+ { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_point },
{ CFI_MFR_ATMEL, AT49BV6416, fixup_use_atmel_lock },
{ 0, 0, NULL }
};
@@ -922,8 +942,12 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr
return 0;
case FL_ERASING:
+ /*
+ * Not for a point: the erase would stay suspended for as
+ * long as it is held, which for cramfs is the whole mount.
+ */
if (!cfip || !(cfip->EraseSuspend & (0x1|0x2)) ||
- !(mode == FL_READY || mode == FL_POINT ||
+ !(mode == FL_READY ||
(mode == FL_WRITING && (cfip->EraseSuspend & 0x2))))
goto sleep;
@@ -976,8 +1000,13 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr
return -EIO;
case FL_POINT:
- /* Only if there's no operation suspended... */
- if (mode == FL_READY && chip->oldstate == FL_READY)
+ /*
+ * Only if there's no operation suspended: the chip is in
+ * array mode, so reads, further points and the reboot
+ * reset (which only re-enters array mode) can go ahead.
+ */
+ if ((mode == FL_READY || mode == FL_POINT ||
+ mode == FL_SHUTDOWN) && chip->oldstate == FL_READY)
return 0;
fallthrough;
default:
@@ -1216,6 +1245,133 @@ do { \
#endif
+static int do_point_onechip(struct map_info *map, struct flchip *chip,
+ loff_t adr, size_t len)
+{
+ unsigned long cmd_addr;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int ret;
+
+ adr += chip->start;
+
+ /* Ensure cmd read/writes are aligned. */
+ cmd_addr = adr & ~(map_bankwidth(map) - 1);
+
+ mutex_lock(&chip->mutex);
+ ret = get_chip(map, chip, cmd_addr, FL_POINT);
+ if (!ret) {
+ if (chip->state != FL_POINT && chip->state != FL_READY)
+ map_write(map, CMD(0xf0), cmd_addr);
+
+ chip->state = FL_POINT;
+ chip->ref_point_counter++;
+ }
+ mutex_unlock(&chip->mutex);
+
+ return ret;
+}
+
+static int cfi_amdstd_point(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, void **virt, resource_size_t *phys)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long ofs, last_end = 0;
+ int chipnum;
+ int ret = -EINVAL;
+
+ if (!map->virt)
+ return -EINVAL;
+
+ /* ofs: offset within the first chip that the first read should start */
+ chipnum = (from >> cfi->chipshift);
+ ofs = from - (chipnum << cfi->chipshift);
+
+ *virt = map->virt + cfi->chips[chipnum].start + ofs;
+ if (phys)
+ *phys = map->phys + cfi->chips[chipnum].start + ofs;
+
+ while (len) {
+ unsigned long thislen;
+
+ if (chipnum >= cfi->numchips)
+ break;
+
+ /* We cannot point across chips that are virtually disjoint */
+ if (!last_end)
+ last_end = cfi->chips[chipnum].start;
+ else if (cfi->chips[chipnum].start != last_end)
+ break;
+
+ if ((len + ofs - 1) >> cfi->chipshift)
+ thislen = (1 << cfi->chipshift) - ofs;
+ else
+ thislen = len;
+
+ ret = do_point_onechip(map, &cfi->chips[chipnum], ofs, thislen);
+ if (ret)
+ break;
+
+ *retlen += thislen;
+ len -= thislen;
+
+ ofs = 0;
+ last_end += 1 << cfi->chipshift;
+ chipnum++;
+ }
+ return *retlen ? 0 : ret;
+}
+
+static int cfi_amdstd_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long ofs;
+ int chipnum, err = 0;
+
+ /* ofs: offset within the first chip that the first read should start */
+ chipnum = (from >> cfi->chipshift);
+ ofs = from - (chipnum << cfi->chipshift);
+
+ while (len && !err) {
+ unsigned long thislen;
+ struct flchip *chip;
+
+ if (chipnum >= cfi->numchips)
+ break;
+ chip = &cfi->chips[chipnum];
+
+ if ((len + ofs - 1) >> cfi->chipshift)
+ thislen = (1 << cfi->chipshift) - ofs;
+ else
+ thislen = len;
+
+ mutex_lock(&chip->mutex);
+ if (chip->state == FL_POINT) {
+ /* The last reader resumes whatever the first suspended */
+ if (--chip->ref_point_counter == 0) {
+ chip->state = FL_READY;
+ put_chip(map, chip, chip->start);
+ }
+ } else if (chip->state != FL_SHUTDOWN) {
+ /*
+ * After the reboot reset nothing is pointed any more;
+ * ref_point_counter no longer matters there.
+ */
+ pr_err("%s: Error: unpoint called on non pointed region\n",
+ map->name);
+ err = -EINVAL;
+ }
+ mutex_unlock(&chip->mutex);
+
+ len -= thislen;
+ ofs = 0;
+ chipnum++;
+ }
+
+ return err;
+}
+
static inline int do_read_onechip(struct map_info *map, struct flchip *chip, loff_t adr, size_t len, u_char *buf)
{
unsigned long cmd_addr;
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 3/3] MIPS: Octeon: flash: use the simple map accessors without a shared eMMC
2026-10-10 18:08 ` [PATCH v2 0/3] mtd: point() for cfi_cmdset_0002, on simple maps only Orgad Shaneh
2026-10-10 18:08 ` [PATCH v2 1/3] mtd: maps: only point() maps read through the simple accessors Orgad Shaneh
2026-10-10 18:08 ` [PATCH v2 2/3] mtd: cfi_cmdset_0002: implement point() for simple linear maps Orgad Shaneh
@ 2026-10-10 18:08 ` Orgad Shaneh
2026-10-10 18:18 ` sashiko-bot
2 siblings, 1 reply; 12+ messages in thread
From: Orgad Shaneh @ 2026-10-10 18:08 UTC (permalink / raw)
To: miquel.raynal, richard, vigneshr, tsbogend
Cc: linux-mtd, linux-mips, linux-kernel, linusw, kaloz, ulli.kroll,
john, nico, dwmw2, corbet, linux-doc
CAVIUM_OCTEON_SOC selects MTD_COMPLEX_MAPPINGS, and since
commit 8c1e6b14e27d ("MIPS: OCTEON: Protect accesses to bootbus flash
with octeon_bootbus_sem.") the flash map accessors take the semaphore
around every access. Its only other user is the eMMC host of the older
parts ("cavium,octeon-6130-mmc" in drivers/mmc/host/cavium-octeon.c),
which shares the boot bus; the CIU3 parts serialize their eMMC with a
lock of their own.
On boards without that host the semaphore guards nothing, but the
custom accessors make the map non-simple, so MTD will not let jffs2
point at it and its mount copies every used eraseblock. Use
simple_map_init() there. The test is for the node, not the driver, so
a disabled mmc node keeps the semaphore.
simple_map_init() BUG()s on an unsupported bank width; the old code
warned and then hit the BUG() in the inline map accessors on the first
probe command. Check the width before mapping anything and fail the
probe instead.
On an Octeon CN6635 board (no eMMC) with a 100 MB jffs2 partition on
an 8-bit NOR the mount drops from 15-30 s to under 1 s together with
point() support in cfi_cmdset_0002.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Orgad Shaneh <orgads@gmail.com>
---
v2: check the bank width before ioremap() and drop the added
iounmap() calls: flash_map is one static instance, so they could
unmap a mapping that an earlier probe registered (sashiko).
arch/mips/cavium-octeon/flash_setup.c | 36 +++++++++++++++++++++++----
1 file changed, 31 insertions(+), 5 deletions(-)
diff --git a/arch/mips/cavium-octeon/flash_setup.c b/arch/mips/cavium-octeon/flash_setup.c
index 9242521..1c0b7d7 100644
--- a/arch/mips/cavium-octeon/flash_setup.c
+++ b/arch/mips/cavium-octeon/flash_setup.c
@@ -63,6 +63,17 @@ static void octeon_flash_map_copy_to(struct map_info *map, unsigned long to,
up(&octeon_bootbus_sem);
}
+static bool octeon_flash_bus_shared(void)
+{
+ struct device_node *np;
+ bool shared;
+
+ np = of_find_compatible_node(NULL, NULL, "cavium,octeon-6130-mmc");
+ shared = !!np;
+ of_node_put(np);
+ return shared;
+}
+
/*
* Module/ driver initialization.
*
@@ -85,6 +96,12 @@ static int octeon_flash_probe(struct platform_device *pdev)
*/
region_cfg.u64 = cvmx_read_csr(CVMX_MIO_BOOT_REG_CFGX(cs));
if (region_cfg.s.en) {
+ /* 8-bit bus (0 + 1) or 16-bit bus (1 + 1) */
+ if (!map_bankwidth_supported(region_cfg.s.width + 1)) {
+ pr_err("Unsupported flash bank width %d\n",
+ region_cfg.s.width + 1);
+ return -EINVAL;
+ }
/*
* The bootloader always takes the flash and sets its
* address so the entire flash fits below
@@ -102,11 +119,20 @@ static int octeon_flash_probe(struct platform_device *pdev)
flash_map.virt = ioremap(flash_map.phys, flash_map.size);
pr_notice("Bootbus flash: Setting flash for %luMB flash at "
"0x%08llx\n", flash_map.size >> 20, flash_map.phys);
- WARN_ON(!map_bankwidth_supported(flash_map.bankwidth));
- flash_map.read = octeon_flash_map_read;
- flash_map.write = octeon_flash_map_write;
- flash_map.copy_from = octeon_flash_map_copy_from;
- flash_map.copy_to = octeon_flash_map_copy_to;
+ /*
+ * The boot bus semaphore only serializes the flash against
+ * an eMMC host that shares the bus (the CIU3 parts have
+ * their own). Without one, use the simple accessors: they
+ * let MTD point at the flash, so jffs2 scans it in place.
+ */
+ if (octeon_flash_bus_shared()) {
+ flash_map.read = octeon_flash_map_read;
+ flash_map.write = octeon_flash_map_write;
+ flash_map.copy_from = octeon_flash_map_copy_from;
+ flash_map.copy_to = octeon_flash_map_copy_to;
+ } else {
+ simple_map_init(&flash_map);
+ }
mymtd = do_map_probe("cfi_probe", &flash_map);
if (mymtd) {
mymtd->owner = THIS_MODULE;
--
2.53.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 2/3] mtd: cfi_cmdset_0002: implement point() for simple linear maps
2026-10-10 18:08 ` [PATCH v2 2/3] mtd: cfi_cmdset_0002: implement point() for simple linear maps Orgad Shaneh
@ 2026-10-10 18:18 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-10-10 18:18 UTC (permalink / raw)
To: Orgad Shaneh, miquel.raynal, vigneshr, richard, tsbogend
Cc: dwmw2, corbet, linux-doc, linux-mips, ulli.kroll, linux-mtd,
kaloz, linusw, linux-kernel, john, nico
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] mtd: cfi_cmdset_0002: FL_POINT deadlocks and hangs XIP erases via FL_XIP_WHILE_ERASING
--
commit 5f3b3543d5ee6dad5e8171d86ea7a290abab737e
Author: Orgad Shaneh <orgads@gmail.com>
mtd: cfi_cmdset_0002: implement point() for simple linear maps
This commit adds a point() and unpoint() pair for cfi_cmdset_0002, modeled
on cfi_intelext_point(), allowing linearly mapped chips to be scanned in
place. It handles FL_POINT operations and ensures that a point does not
suspend an erase, but waits for it to finish instead.
> diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002.c
> index 517db2f2707f4..b0330839a2133 100644
> --- a/drivers/mtd/chips/cfi_cmdset_0002.c
> +++ b/drivers/mtd/chips/cfi_cmdset_0002.c
[ ... ]
> @@ -906,8 +926,12 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr
> return 0;
>
> case FL_ERASING:
> + /*
> + * Not for a point: the erase would stay suspended for as
> + * long as it is held, which for cramfs is the whole mount.
> + */
> if (!cfip || !(cfip->EraseSuspend & (0x1|0x2)) ||
> - !(mode == FL_READY || mode == FL_POINT ||
> + !(mode == FL_READY ||
> (mode == FL_WRITING && (cfip->EraseSuspend & 0x2))))
> goto sleep;
[Severity: High]
This patch correctly prevents FL_POINT from suspending an erase here, but
does this also need to be applied to FL_XIP_WHILE_ERASING?
If mtd_point() is called during FL_XIP_WHILE_ERASING, get_chip() still
allows mode == FL_POINT:
drivers/mtd/chips/cfi_cmdset_0002.c:get_chip() {
...
case FL_XIP_WHILE_ERASING:
if (mode != FL_READY && mode != FL_POINT &&
(!cfip || !(cfip->EraseSuspend&2)))
goto sleep;
chip->oldstate = chip->state;
chip->state = FL_READY;
return 0;
...
}
This allows do_point_onechip() to successfully change chip->state to FL_POINT
while leaving chip->oldstate as FL_XIP_WHILE_ERASING.
[ ... ]
> @@ -960,8 +984,13 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr
> return -EIO;
>
> case FL_POINT:
> - /* Only if there's no operation suspended... */
> - if (mode == FL_READY && chip->oldstate == FL_READY)
> + /*
> + * Only if there's no operation suspended: the chip is in
> + * array mode, so reads, further points and the reboot
> + * reset (which only re-enters array mode) can go ahead.
> + */
> + if ((mode == FL_READY || mode == FL_POINT ||
> + mode == FL_SHUTDOWN) && chip->oldstate == FL_READY)
> return 0;
> fallthrough;
> default:
[Severity: High]
If chip->oldstate is FL_XIP_WHILE_ERASING because it wasn't filtered out
above, will subsequent operations deadlock here?
Because oldstate is not FL_READY, normal read operations (FL_READY) would
fall through to the sleep block indefinitely until the point is released
(which for cramfs is the mount lifetime).
Could this also cause the XIP background erase loop to spin indefinitely
waiting for chip->state to return to FL_XIP_WHILE_ERASING?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261010180840.2152492-1-orgads@gmail.com?part=2
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v2 3/3] MIPS: Octeon: flash: use the simple map accessors without a shared eMMC
2026-10-10 18:08 ` [PATCH v2 3/3] MIPS: Octeon: flash: use the simple map accessors without a shared eMMC Orgad Shaneh
@ 2026-10-10 18:18 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-10-10 18:18 UTC (permalink / raw)
To: miquel.raynal, Orgad Shaneh, tsbogend, richard, vigneshr
Cc: kaloz, corbet, dwmw2, john, linux-mtd, linux-doc, linux-mips,
linusw, linux-kernel, nico, ulli.kroll
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] mips: cavium-octeon: static map_info overwrite and memory leak in octeon_flash_probe
--
commit 2d9aae5bb3f634f71b28d3ae93087e12e701fbe6
Author: Orgad Shaneh <orgads@gmail.com>
MIPS: Octeon: flash: use the simple map accessors without a shared eMMC
This commit updates the Octeon flash driver to use simple_map_init()
when the boot bus isn't shared with an eMMC host, reducing mount times
significantly. It also checks for supported bank widths earlier in the
probe function to avoid hitting BUG calls on unsupported widths.
> diff --git a/arch/mips/cavium-octeon/flash_setup.c b/arch/mips/cavium-octeon/flash_setup.c
> index 3395acde4d60a..5448d5139c640 100644
> --- a/arch/mips/cavium-octeon/flash_setup.c
> +++ b/arch/mips/cavium-octeon/flash_setup.c
[ ... ]
> @@ -85,6 +96,12 @@ static int octeon_flash_probe(struct platform_device *pdev)
[ ... ]
> flash_map.size = 0x1fc00000 - flash_map.phys;
> /* 8-bit bus (0 + 1) or 16-bit bus (1 + 1) */
> flash_map.bankwidth = region_cfg.s.width + 1;
> flash_map.virt = ioremap(flash_map.phys, flash_map.size);
[Severity: Medium]
If multiple flash devices are probed, does this unconditionally overwrite
the static flash_map.virt with a new ioremap() mapping?
If an earlier probe successfully registered an MTD device, overwriting
the singleton flash_map could corrupt the previous device's mapping.
> pr_notice("Bootbus flash: Setting flash for %luMB flash at "
> "0x%08llx\n", flash_map.size >> 20, flash_map.phys);
[ ... ]
> mymtd = do_map_probe("cfi_probe", &flash_map);
> if (mymtd) {
> mymtd->owner = THIS_MODULE;
> mtd_device_parse_register(mymtd, part_probe_types,
> NULL, NULL, 0);
> } else {
> pr_err("Failed to register MTD device for flash\n");
> }
[Severity: Medium]
Is an iounmap(flash_map.virt) needed here when do_map_probe() fails?
Without it, this error path appears to leak the virtual mapping.
The v2 changelog mentions omitting iounmap() because it could unmap a
mapping that an earlier probe registered. However, since the static
flash_map.virt is unconditionally overwritten by ioremap() earlier in
this function, wouldn't the singleton already be corrupted if a prior
probe existed?
Skipping iounmap() in this path appears to just leak memory rather than
safely preserving any previous mapping.
> }
> return 0;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261010180840.2152492-1-orgads@gmail.com?part=3
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-10-10 18:18 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-10 17:21 [PATCH 0/3] mtd: point() for cfi_cmdset_0002, on simple maps only Orgad Shaneh
2026-10-10 17:21 ` [PATCH 1/3] mtd: maps: only point() maps read through the simple accessors Orgad Shaneh
2026-10-10 17:21 ` [PATCH 2/3] mtd: cfi_cmdset_0002: implement point() for simple linear maps Orgad Shaneh
2026-10-10 17:31 ` sashiko-bot
2026-10-10 17:21 ` [PATCH 3/3] MIPS: Octeon: flash: use the simple map accessors without a shared eMMC Orgad Shaneh
2026-10-10 17:32 ` sashiko-bot
2026-10-10 18:08 ` [PATCH v2 0/3] mtd: point() for cfi_cmdset_0002, on simple maps only Orgad Shaneh
2026-10-10 18:08 ` [PATCH v2 1/3] mtd: maps: only point() maps read through the simple accessors Orgad Shaneh
2026-10-10 18:08 ` [PATCH v2 2/3] mtd: cfi_cmdset_0002: implement point() for simple linear maps Orgad Shaneh
2026-10-10 18:18 ` sashiko-bot
2026-10-10 18:08 ` [PATCH v2 3/3] MIPS: Octeon: flash: use the simple map accessors without a shared eMMC Orgad Shaneh
2026-10-10 18:18 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®