kernel: mtd: spinand: continuous mode fixes
authorMikhail Kshevetskiy <[email protected]>
Sat, 4 Oct 2025 06:17:33 +0000 (09:17 +0300)
committerRobert Marko <[email protected]>
Thu, 9 Oct 2025 14:37:25 +0000 (16:37 +0200)
Continuous reading mode is broken for some spi controllers. There are two
possible bug scenarios:

1) "continuous mode" flash and spi controller without dirmap support,
   but with restriction on transfer length in adjust_op_size()

2) "continuous mode" flash and spi controller with dirmap support for a
   single flash page

In the first case, any read that exceeds the limit specified in adjust_op_size()
will result in an EIO error. The limit may even be less than a size of a single
flash page. In this case, any read will result in an error.

In the second case, any read larger than flash page size will result in an EIO
error or spinand driver spoofing (because the spi controller driver returns
more bytes than were actually read).

This patch series tries to fix continuous reading (spinand driver side).
Unfortunately these fixes can't resolve "spinand driver spoofing" case.
Spi controller drivers might need fixes as well.

Signed-off-by: Mikhail Kshevetskiy <[email protected]>
Link: https://github.com/openwrt/openwrt/pull/20295
Signed-off-by: Robert Marko <[email protected]>
target/linux/airoha/patches-6.12/901-snand-mtk-bmt-support.patch
target/linux/generic/pending-6.12/405-01-mtd-spinand-fix-direct-mapping-creation-sizes.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/405-02-mtd-spinand-try-a-regular-dirmap-if-creating-a-di.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/405-03-mtd-spinand-repeat-reading-in-regular-mode-if-con.patch [new file with mode: 0644]
target/linux/generic/pending-6.12/487-mtd-spinand-Add-support-for-Etron-EM73D044VCx.patch
target/linux/mediatek/patches-6.12/121-hack-spi-nand-1b-bbm.patch
target/linux/mediatek/patches-6.12/330-snand-mtk-bmt-support.patch
target/linux/mediatek/patches-6.12/340-mtd-spinand-Add-support-for-the-Fidelix-FM35X1GA.patch
target/linux/mediatek/patches-6.12/435-drivers-mtd-spinand-Add-calibration-support-for-spin.patch
target/linux/mediatek/patches-6.12/436-drivers-mtd-spi-nor-Add-calibration-support-for-spi-.patch
target/linux/mediatek/patches-6.12/960-asus-hack-u-boot-ignore-mtdparts.patch

index f7156523199490de78fe57ca56803de5709d9013..eb77fde0668980bdec1eb33b3fe999a46394cf56 100644 (file)
@@ -8,7 +8,7 @@
  
  static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)
  {
-@@ -1530,6 +1531,7 @@ static int spinand_probe(struct spi_mem
+@@ -1574,6 +1575,7 @@ static int spinand_probe(struct spi_mem
        if (ret)
                return ret;
  
@@ -16,7 +16,7 @@
        ret = mtd_device_register(mtd, NULL, 0);
        if (ret)
                goto err_spinand_cleanup;
-@@ -1537,6 +1539,7 @@ static int spinand_probe(struct spi_mem
+@@ -1581,6 +1583,7 @@ static int spinand_probe(struct spi_mem
        return 0;
  
  err_spinand_cleanup:
@@ -24,7 +24,7 @@
        spinand_cleanup(spinand);
  
        return ret;
-@@ -1555,6 +1558,7 @@ static int spinand_remove(struct spi_mem
+@@ -1599,6 +1602,7 @@ static int spinand_remove(struct spi_mem
        if (ret)
                return ret;
  
diff --git a/target/linux/generic/pending-6.12/405-01-mtd-spinand-fix-direct-mapping-creation-sizes.patch b/target/linux/generic/pending-6.12/405-01-mtd-spinand-fix-direct-mapping-creation-sizes.patch
new file mode 100644 (file)
index 0000000..3cb7844
--- /dev/null
@@ -0,0 +1,62 @@
+From 5eea4228aaa3c5aeba843d8ea2a60993c268419d Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <[email protected]>
+Date: Sun, 3 Aug 2025 17:23:27 +0300
+Subject: [PATCH RESEND v5 1/3] mtd: spinand: fix direct mapping creation sizes
+
+Continuous mode is only supported for data reads, thus writing
+requires only single flash page mapping.
+
+Signed-off-by: Mikhail Kshevetskiy <[email protected]>
+---
+ drivers/mtd/nand/spi/core.c | 14 +++++++-------
+ 1 file changed, 7 insertions(+), 7 deletions(-)
+
+--- a/drivers/mtd/nand/spi/core.c
++++ b/drivers/mtd/nand/spi/core.c
+@@ -1028,18 +1028,13 @@ static int spinand_create_dirmap(struct
+                                unsigned int plane)
+ {
+       struct nand_device *nand = spinand_to_nand(spinand);
+-      struct spi_mem_dirmap_info info = {
+-              .length = nanddev_page_size(nand) +
+-                        nanddev_per_page_oobsize(nand),
+-      };
++      struct spi_mem_dirmap_info info = { 0 };
+       struct spi_mem_dirmap_desc *desc;
+-      if (spinand->cont_read_possible)
+-              info.length = nanddev_eraseblock_size(nand);
+-
+       /* The plane number is passed in MSB just above the column address */
+       info.offset = plane << fls(nand->memorg.pagesize);
++      info.length = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand);
+       info.op_tmpl = *spinand->op_templates.update_cache;
+       desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
+                                         spinand->spimem, &info);
+@@ -1048,6 +1043,8 @@ static int spinand_create_dirmap(struct
+       spinand->dirmaps[plane].wdesc = desc;
++      if (spinand->cont_read_possible)
++              info.length = nanddev_eraseblock_size(nand);
+       info.op_tmpl = *spinand->op_templates.read_cache;
+       desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
+                                         spinand->spimem, &info);
+@@ -1063,6 +1060,7 @@ static int spinand_create_dirmap(struct
+               return 0;
+       }
++      info.length = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand);
+       info.op_tmpl = *spinand->op_templates.update_cache;
+       info.op_tmpl.data.ecc = true;
+       desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
+@@ -1072,6 +1070,8 @@ static int spinand_create_dirmap(struct
+       spinand->dirmaps[plane].wdesc_ecc = desc;
++      if (spinand->cont_read_possible)
++              info.length = nanddev_eraseblock_size(nand);
+       info.op_tmpl = *spinand->op_templates.read_cache;
+       info.op_tmpl.data.ecc = true;
+       desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
diff --git a/target/linux/generic/pending-6.12/405-02-mtd-spinand-try-a-regular-dirmap-if-creating-a-di.patch b/target/linux/generic/pending-6.12/405-02-mtd-spinand-try-a-regular-dirmap-if-creating-a-di.patch
new file mode 100644 (file)
index 0000000..5c54a91
--- /dev/null
@@ -0,0 +1,97 @@
+From 9d13b7857de8834c6246fa5bc83f767675360240 Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <[email protected]>
+Date: Sun, 3 Aug 2025 19:06:40 +0300
+Subject: [PATCH RESEND v5 2/3] mtd: spinand: try a regular dirmap if creating
+ a dirmap for continuous reading fails
+
+Continuous reading may result in multiple flash pages reading in one
+operation. Typically only one flash page has read/written (a little bit
+more than 2-4 Kb), but continuous reading requires the spi controller
+to read up to 512 Kb in one operation without toggling CS in beetween.
+
+Roughly speaking spi controllers can be divided on 2 categories:
+ * spi controllers without dirmap acceleration support
+ * spi controllers with dirmap acceleration support
+
+Firt of them will have issues with continuous reading if restriction on
+the transfer length is implemented in the adjust_op_size() handler.
+Second group often supports acceleration of single page only reading.
+Thus enabling of continuous reading can break flash reading.
+
+This patch tries to create dirmap for continuous reading first and
+fallback to regular reading if spi controller refuses to create it.
+
+Signed-off-by: Mikhail Kshevetskiy <[email protected]>
+---
+ drivers/mtd/nand/spi/core.c | 43 ++++++++++++++++++++++++++++++-------
+ 1 file changed, 35 insertions(+), 8 deletions(-)
+
+--- a/drivers/mtd/nand/spi/core.c
++++ b/drivers/mtd/nand/spi/core.c
+@@ -1024,6 +1024,39 @@ static int spinand_mtd_block_isreserved(
+       return ret;
+ }
++static struct spi_mem_dirmap_desc *spinand_create_rdesc(
++                                      struct spinand_device *spinand,
++                                      struct spi_mem_dirmap_info *info)
++{
++      struct nand_device *nand = spinand_to_nand(spinand);
++      struct spi_mem_dirmap_desc *desc = NULL;
++
++      if (spinand->cont_read_possible) {
++              /*
++               * spi controller may return an error if info->length is
++               * too large
++               */
++              info->length = nanddev_eraseblock_size(nand);
++              desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
++                                                spinand->spimem, info);
++      }
++
++      if (IS_ERR_OR_NULL(desc)) {
++              /*
++               * continuous reading is not supported by flash or
++               * its spi controller, use regular reading
++               */
++              spinand->cont_read_possible = false;
++
++              info->length = nanddev_page_size(nand) +
++                             nanddev_per_page_oobsize(nand);
++              desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
++                                                spinand->spimem, info);
++      }
++
++      return desc;
++}
++
+ static int spinand_create_dirmap(struct spinand_device *spinand,
+                                unsigned int plane)
+ {
+@@ -1043,11 +1076,8 @@ static int spinand_create_dirmap(struct
+       spinand->dirmaps[plane].wdesc = desc;
+-      if (spinand->cont_read_possible)
+-              info.length = nanddev_eraseblock_size(nand);
+       info.op_tmpl = *spinand->op_templates.read_cache;
+-      desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
+-                                        spinand->spimem, &info);
++      desc = spinand_create_rdesc(spinand, &info);
+       if (IS_ERR(desc))
+               return PTR_ERR(desc);
+@@ -1070,12 +1100,9 @@ static int spinand_create_dirmap(struct
+       spinand->dirmaps[plane].wdesc_ecc = desc;
+-      if (spinand->cont_read_possible)
+-              info.length = nanddev_eraseblock_size(nand);
+       info.op_tmpl = *spinand->op_templates.read_cache;
+       info.op_tmpl.data.ecc = true;
+-      desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
+-                                        spinand->spimem, &info);
++      desc = spinand_create_rdesc(spinand, &info);
+       if (IS_ERR(desc))
+               return PTR_ERR(desc);
diff --git a/target/linux/generic/pending-6.12/405-03-mtd-spinand-repeat-reading-in-regular-mode-if-con.patch b/target/linux/generic/pending-6.12/405-03-mtd-spinand-repeat-reading-in-regular-mode-if-con.patch
new file mode 100644 (file)
index 0000000..ad13fba
--- /dev/null
@@ -0,0 +1,63 @@
+From 6ffbbe473b98e3944d4cb9379bf725b374e587a9 Mon Sep 17 00:00:00 2001
+From: Mikhail Kshevetskiy <[email protected]>
+Date: Sun, 3 Aug 2025 17:54:17 +0300
+Subject: [PATCH RESEND v5 3/3] mtd: spinand: repeat reading in regular mode if
+ continuous reading fails
+
+Continuous reading may result in multiple flash pages reading in one
+operation. Unfortunately, not all spinand controllers support such
+large reading. They will read less data. Unfortunately, the operation
+can't be continued.
+
+In this case:
+ * disable continuous reading on this (not good enough) spi controller
+ * repeat reading in regular mode.
+
+Signed-off-by: Mikhail Kshevetskiy <[email protected]>
+---
+ drivers/mtd/nand/spi/core.c | 25 +++++++++++++++++++++----
+ 1 file changed, 21 insertions(+), 4 deletions(-)
+
+--- a/drivers/mtd/nand/spi/core.c
++++ b/drivers/mtd/nand/spi/core.c
+@@ -427,8 +427,16 @@ static int spinand_read_from_cache_op(st
+                * Dirmap accesses are allowed to toggle the CS.
+                * Toggling the CS during a continuous read is forbidden.
+                */
+-              if (nbytes && req->continuous)
+-                      return -EIO;
++              if (nbytes && req->continuous) {
++                      /*
++                       * Spi controller with broken support of continuous
++                       * reading was detected. Disable future use of
++                       * continuous reading and return -EAGAIN to retry
++                       * reading within regular mode.
++                       */
++                      spinand->cont_read_possible = false;
++                      return -EAGAIN;
++              }
+       }
+       if (req->datalen)
+@@ -841,10 +849,19 @@ static int spinand_mtd_read(struct mtd_i
+       old_stats = mtd->ecc_stats;
+-      if (spinand_use_cont_read(mtd, from, ops))
++      if (spinand_use_cont_read(mtd, from, ops)) {
+               ret = spinand_mtd_continuous_page_read(mtd, from, ops, &max_bitflips);
+-      else
++              if (ret == -EAGAIN && !spinand->cont_read_possible) {
++                      /*
++                       * Spi controller with broken support of continuous
++                       * reading was detected (see spinand_read_from_cache_op()),
++                       * repeat reading in regular mode.
++                       */
++                      ret = spinand_mtd_regular_page_read(mtd, from, ops, &max_bitflips);
++              }
++      } else {
+               ret = spinand_mtd_regular_page_read(mtd, from, ops, &max_bitflips);
++      }
+       if (ops->stats) {
+               ops->stats->uncorrectable_errors +=
index df22fda59c7708bc91c18314d2b94ca23f0626f5..34fb47a5fb9062823953277e899abfec5e04152a 100644 (file)
@@ -49,7 +49,7 @@ Submitted-by: Daniel Danzberger <[email protected]>
  obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
 --- a/drivers/mtd/nand/spi/core.c
 +++ b/drivers/mtd/nand/spi/core.c
-@@ -1115,6 +1115,7 @@ static const struct spinand_manufacturer
+@@ -1159,6 +1159,7 @@ static const struct spinand_manufacturer
        &alliancememory_spinand_manufacturer,
        &ato_spinand_manufacturer,
        &esmt_c8_spinand_manufacturer,
index df29ec91d1070cb925efdb74daadae5636a60de6..cc2410e3524f475cf4131c79983d5832658213ba 100644 (file)
@@ -1,6 +1,6 @@
 --- a/drivers/mtd/nand/spi/core.c
 +++ b/drivers/mtd/nand/spi/core.c
-@@ -896,7 +896,7 @@ static int spinand_mtd_write(struct mtd_
+@@ -913,7 +913,7 @@ static int spinand_mtd_write(struct mtd_
  static bool spinand_isbad(struct nand_device *nand, const struct nand_pos *pos)
  {
        struct spinand_device *spinand = nand_to_spinand(nand);
@@ -9,7 +9,7 @@
        struct nand_page_io_req req = {
                .pos = *pos,
                .ooblen = sizeof(marker),
-@@ -907,7 +907,7 @@ static bool spinand_isbad(struct nand_de
+@@ -924,7 +924,7 @@ static bool spinand_isbad(struct nand_de
  
        spinand_select_target(spinand, pos->target);
        spinand_read_page(spinand, &req);
index f7156523199490de78fe57ca56803de5709d9013..eb77fde0668980bdec1eb33b3fe999a46394cf56 100644 (file)
@@ -8,7 +8,7 @@
  
  static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)
  {
-@@ -1530,6 +1531,7 @@ static int spinand_probe(struct spi_mem
+@@ -1574,6 +1575,7 @@ static int spinand_probe(struct spi_mem
        if (ret)
                return ret;
  
@@ -16,7 +16,7 @@
        ret = mtd_device_register(mtd, NULL, 0);
        if (ret)
                goto err_spinand_cleanup;
-@@ -1537,6 +1539,7 @@ static int spinand_probe(struct spi_mem
+@@ -1581,6 +1583,7 @@ static int spinand_probe(struct spi_mem
        return 0;
  
  err_spinand_cleanup:
@@ -24,7 +24,7 @@
        spinand_cleanup(spinand);
  
        return ret;
-@@ -1555,6 +1558,7 @@ static int spinand_remove(struct spi_mem
+@@ -1599,6 +1602,7 @@ static int spinand_remove(struct spi_mem
        if (ret)
                return ret;
  
index a7dc5105f5692b426a819339488f7a72fe9f796f..7857f4c6aa9b57e31a8844a314044bd6ac9d1804 100644 (file)
@@ -24,7 +24,7 @@ Signed-off-by: Davide Fioravanti <[email protected]>
  obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
 --- a/drivers/mtd/nand/spi/core.c
 +++ b/drivers/mtd/nand/spi/core.c
-@@ -1118,6 +1118,7 @@ static const struct spinand_manufacturer
+@@ -1162,6 +1162,7 @@ static const struct spinand_manufacturer
        &esmt_c8_spinand_manufacturer,
        &etron_spinand_manufacturer,
        &fmsh_spinand_manufacturer,
index 676ddec61eeda7823910c569780a56d2a3c8fa99..258b8e19a3d0c3673d9cec10683dea850f3972e5 100644 (file)
@@ -11,7 +11,7 @@ Signed-off-by: SkyLake.Huang <[email protected]>
 
 --- a/drivers/mtd/nand/spi/core.c
 +++ b/drivers/mtd/nand/spi/core.c
-@@ -1156,6 +1156,56 @@ static int spinand_manufacturer_match(st
+@@ -1200,6 +1200,56 @@ static int spinand_manufacturer_match(st
        return -EOPNOTSUPP;
  }
  
@@ -68,7 +68,7 @@ Signed-off-by: SkyLake.Huang <[email protected]>
  static int spinand_id_detect(struct spinand_device *spinand)
  {
        u8 *id = spinand->id.data;
-@@ -1407,6 +1457,10 @@ static int spinand_init(struct spinand_d
+@@ -1451,6 +1501,10 @@ static int spinand_init(struct spinand_d
        if (!spinand->scratchbuf)
                return -ENOMEM;
  
index 4626a86e996eb03a44ac74d5a9d1bb03596dcda0..b55f76eed7fd97443781ec052358868f338362e8 100644 (file)
@@ -12,7 +12,7 @@ Signed-off-by: SkyLake.Huang <[email protected]>
 
 --- a/drivers/mtd/nand/spi/core.c
 +++ b/drivers/mtd/nand/spi/core.c
-@@ -1197,7 +1197,10 @@ static int spinand_cal_read(void *priv,
+@@ -1241,7 +1241,10 @@ static int spinand_cal_read(void *priv,
        if (ret)
                return ret;
  
index 07d067125a7322c8103f0542efc876316d47b314..8d1680d5d49e04386369e9f4f375a59d3c41425f 100644 (file)
@@ -29,7 +29,7 @@ Signed-off-by: Daniel Golle <[email protected]>
 
 --- a/drivers/mtd/nand/spi/core.c
 +++ b/drivers/mtd/nand/spi/core.c
-@@ -1624,6 +1624,7 @@ static int spinand_remove(struct spi_mem
+@@ -1668,6 +1668,7 @@ static int spinand_remove(struct spi_mem
  
  static const struct spi_device_id spinand_ids[] = {
        { .name = "spi-nand" },
@@ -37,7 +37,7 @@ Signed-off-by: Daniel Golle <[email protected]>
        { /* sentinel */ },
  };
  MODULE_DEVICE_TABLE(spi, spinand_ids);
-@@ -1631,6 +1632,7 @@ MODULE_DEVICE_TABLE(spi, spinand_ids);
+@@ -1675,6 +1676,7 @@ MODULE_DEVICE_TABLE(spi, spinand_ids);
  #ifdef CONFIG_OF
  static const struct of_device_id spinand_of_ids[] = {
        { .compatible = "spi-nand" },