From 32229a4bb7d2a8dd2c3a719c9f4e0e5fa08bbd33 Mon Sep 17 00:00:00 2001 From: Mikhail Kshevetskiy Date: Sat, 4 Oct 2025 09:17:33 +0300 Subject: [PATCH] kernel: mtd: spinand: continuous mode fixes 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 Link: https://github.com/openwrt/openwrt/pull/20295 Signed-off-by: Robert Marko --- .../901-snand-mtk-bmt-support.patch | 6 +- ...nd-fix-direct-mapping-creation-sizes.patch | 62 ++++++++++++ ...ry-a-regular-dirmap-if-creating-a-di.patch | 97 +++++++++++++++++++ ...epeat-reading-in-regular-mode-if-con.patch | 63 ++++++++++++ ...nd-Add-support-for-Etron-EM73D044VCx.patch | 2 +- .../121-hack-spi-nand-1b-bbm.patch | 4 +- .../330-snand-mtk-bmt-support.patch | 6 +- ...Add-support-for-the-Fidelix-FM35X1GA.patch | 2 +- ...and-Add-calibration-support-for-spin.patch | 4 +- ...nor-Add-calibration-support-for-spi-.patch | 2 +- ...960-asus-hack-u-boot-ignore-mtdparts.patch | 4 +- 11 files changed, 237 insertions(+), 15 deletions(-) create mode 100644 target/linux/generic/pending-6.12/405-01-mtd-spinand-fix-direct-mapping-creation-sizes.patch create mode 100644 target/linux/generic/pending-6.12/405-02-mtd-spinand-try-a-regular-dirmap-if-creating-a-di.patch create mode 100644 target/linux/generic/pending-6.12/405-03-mtd-spinand-repeat-reading-in-regular-mode-if-con.patch diff --git a/target/linux/airoha/patches-6.12/901-snand-mtk-bmt-support.patch b/target/linux/airoha/patches-6.12/901-snand-mtk-bmt-support.patch index f715652319..eb77fde066 100644 --- a/target/linux/airoha/patches-6.12/901-snand-mtk-bmt-support.patch +++ b/target/linux/airoha/patches-6.12/901-snand-mtk-bmt-support.patch @@ -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 index 0000000000..3cb7844800 --- /dev/null +++ b/target/linux/generic/pending-6.12/405-01-mtd-spinand-fix-direct-mapping-creation-sizes.patch @@ -0,0 +1,62 @@ +From 5eea4228aaa3c5aeba843d8ea2a60993c268419d Mon Sep 17 00:00:00 2001 +From: Mikhail Kshevetskiy +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 +--- + 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 index 0000000000..5c54a9116c --- /dev/null +++ b/target/linux/generic/pending-6.12/405-02-mtd-spinand-try-a-regular-dirmap-if-creating-a-di.patch @@ -0,0 +1,97 @@ +From 9d13b7857de8834c6246fa5bc83f767675360240 Mon Sep 17 00:00:00 2001 +From: Mikhail Kshevetskiy +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 +--- + 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 index 0000000000..ad13fbafc3 --- /dev/null +++ b/target/linux/generic/pending-6.12/405-03-mtd-spinand-repeat-reading-in-regular-mode-if-con.patch @@ -0,0 +1,63 @@ +From 6ffbbe473b98e3944d4cb9379bf725b374e587a9 Mon Sep 17 00:00:00 2001 +From: Mikhail Kshevetskiy +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 +--- + 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 += diff --git a/target/linux/generic/pending-6.12/487-mtd-spinand-Add-support-for-Etron-EM73D044VCx.patch b/target/linux/generic/pending-6.12/487-mtd-spinand-Add-support-for-Etron-EM73D044VCx.patch index df22fda59c..34fb47a5fb 100644 --- a/target/linux/generic/pending-6.12/487-mtd-spinand-Add-support-for-Etron-EM73D044VCx.patch +++ b/target/linux/generic/pending-6.12/487-mtd-spinand-Add-support-for-Etron-EM73D044VCx.patch @@ -49,7 +49,7 @@ Submitted-by: Daniel Danzberger 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, diff --git a/target/linux/mediatek/patches-6.12/121-hack-spi-nand-1b-bbm.patch b/target/linux/mediatek/patches-6.12/121-hack-spi-nand-1b-bbm.patch index df29ec91d1..cc2410e352 100644 --- a/target/linux/mediatek/patches-6.12/121-hack-spi-nand-1b-bbm.patch +++ b/target/linux/mediatek/patches-6.12/121-hack-spi-nand-1b-bbm.patch @@ -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); diff --git a/target/linux/mediatek/patches-6.12/330-snand-mtk-bmt-support.patch b/target/linux/mediatek/patches-6.12/330-snand-mtk-bmt-support.patch index f715652319..eb77fde066 100644 --- a/target/linux/mediatek/patches-6.12/330-snand-mtk-bmt-support.patch +++ b/target/linux/mediatek/patches-6.12/330-snand-mtk-bmt-support.patch @@ -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/mediatek/patches-6.12/340-mtd-spinand-Add-support-for-the-Fidelix-FM35X1GA.patch b/target/linux/mediatek/patches-6.12/340-mtd-spinand-Add-support-for-the-Fidelix-FM35X1GA.patch index a7dc5105f5..7857f4c6aa 100644 --- a/target/linux/mediatek/patches-6.12/340-mtd-spinand-Add-support-for-the-Fidelix-FM35X1GA.patch +++ b/target/linux/mediatek/patches-6.12/340-mtd-spinand-Add-support-for-the-Fidelix-FM35X1GA.patch @@ -24,7 +24,7 @@ Signed-off-by: Davide Fioravanti 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, diff --git a/target/linux/mediatek/patches-6.12/435-drivers-mtd-spinand-Add-calibration-support-for-spin.patch b/target/linux/mediatek/patches-6.12/435-drivers-mtd-spinand-Add-calibration-support-for-spin.patch index 676ddec61e..258b8e19a3 100644 --- a/target/linux/mediatek/patches-6.12/435-drivers-mtd-spinand-Add-calibration-support-for-spin.patch +++ b/target/linux/mediatek/patches-6.12/435-drivers-mtd-spinand-Add-calibration-support-for-spin.patch @@ -11,7 +11,7 @@ Signed-off-by: SkyLake.Huang --- 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 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; diff --git a/target/linux/mediatek/patches-6.12/436-drivers-mtd-spi-nor-Add-calibration-support-for-spi-.patch b/target/linux/mediatek/patches-6.12/436-drivers-mtd-spi-nor-Add-calibration-support-for-spi-.patch index 4626a86e99..b55f76eed7 100644 --- a/target/linux/mediatek/patches-6.12/436-drivers-mtd-spi-nor-Add-calibration-support-for-spi-.patch +++ b/target/linux/mediatek/patches-6.12/436-drivers-mtd-spi-nor-Add-calibration-support-for-spi-.patch @@ -12,7 +12,7 @@ Signed-off-by: SkyLake.Huang --- 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; diff --git a/target/linux/mediatek/patches-6.12/960-asus-hack-u-boot-ignore-mtdparts.patch b/target/linux/mediatek/patches-6.12/960-asus-hack-u-boot-ignore-mtdparts.patch index 07d067125a..8d1680d5d4 100644 --- a/target/linux/mediatek/patches-6.12/960-asus-hack-u-boot-ignore-mtdparts.patch +++ b/target/linux/mediatek/patches-6.12/960-asus-hack-u-boot-ignore-mtdparts.patch @@ -29,7 +29,7 @@ Signed-off-by: Daniel Golle --- 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 { /* 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" }, -- 2.30.2