* [PATCH] mtd: spinand: cache the last read page to avoid redundant SPI operations
@ 2026-09-07 11:10 fzz
2026-09-07 12:33 ` Miquel Raynal
2026-09-08 1:51 ` kernel test robot
0 siblings, 2 replies; 4+ messages in thread
From: fzz @ 2026-09-07 11:10 UTC (permalink / raw)
To: Miquel Raynal, Vignesh Raghavendra
Cc: Richard Weinberger, linux-mtd, linux-kernel, fzz
When squashfs reads files through mtdblock, the mtdblock layer
splits I/O into 512-byte sectors. For a 4K-page SPI NAND, this
means reading the same page 8 times (4096 / 512), generating 7x
redundant SPI read-from-cache operations. Each such operation
involves a full SPI bus transaction, significantly slowing down
boot time and file access.
Cache the last successfully read page in spinand_device to avoid
these redundant operations. When the same {target, eraseblock,
page} is requested consecutively, the data is served directly
from the bounce buffer (databuf) via memcpy, skipping the SPI
transaction entirely.
The cache is invalidated on write and erase operations. RAW reads,
OOB reads, and continuous reads are excluded from caching to
maintain correctness.
Signed-off-by: fzz <1768315307@qq.com>
---
drivers/mtd/nand/spi/core.c | 36 +++++++++++++++++++++++++++++++++++-
include/linux/mtd/spinand.h | 9 +++++++++
2 files changed, 44 insertions(+), 1 deletion(-)
diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 8bf9301f25e7..fc082127bda8 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -557,7 +557,14 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand,
req->ooblen);
else
memcpy(req->oobbuf.in, spinand->oobbuf + req->ooboffs,
- req->ooblen);
+ req->ooblen);
+ }
+
+ if (req->datalen && !req->continuous && !req->disable_ecc) {
+ spinand->cur_target_cache = req->pos.target;
+ spinand->cur_block_cache = req->pos.eraseblock;
+ spinand->cur_page_cache = req->pos.page;
+ spinand->cache_valid = true;
}
return 0;
@@ -833,6 +840,20 @@ static int spinand_mtd_regular_page_read(struct mtd_info *mtd, loff_t from,
if (disable_ecc)
iter.req.mode = MTD_OPS_RAW;
+ if (spinand->cache_valid && !disable_ecc &&
+ !iter.req.ooblen &&
+ iter.req.pos.target == spinand->cur_target_cache &&
+ iter.req.pos.eraseblock == spinand->cur_block_cache &&
+ iter.req.pos.page == spinand->cur_page_cache) {
+ if (iter.req.datalen)
+ memcpy(iter.req.databuf.in,
+ spinand->databuf + iter.req.dataoffs,
+ iter.req.datalen);
+ ops->retlen += iter.req.datalen;
+ ops->oobretlen += iter.req.ooblen;
+ continue;
+ }
+
ret = spinand_select_target(spinand, iter.req.pos.target);
if (ret)
break;
@@ -1064,6 +1085,12 @@ static int spinand_mtd_write(struct mtd_info *mtd, loff_t to,
if (ret)
break;
+ if (spinand->cache_valid &&
+ iter.req.pos.target == spinand->cur_target_cache &&
+ iter.req.pos.eraseblock == spinand->cur_block_cache &&
+ iter.req.pos.page == spinand->cur_page_cache)
+ spinand->cache_valid = false;
+
ret = spinand_write_page(spinand, &iter.req);
if (ret)
break;
@@ -1188,6 +1215,11 @@ static int spinand_erase(struct nand_device *nand, const struct nand_pos *pos)
if (!ret && (status & STATUS_ERASE_FAILED))
ret = -EIO;
+ if (!ret && spinand->cache_valid &&
+ spinand->cur_target_cache == pos->target &&
+ spinand->cur_block_cache == pos->eraseblock)
+ spinand->cache_valid = false;
+
return ret;
}
@@ -2025,6 +2057,7 @@ static void spinand_cleanup(struct spinand_device *spinand)
nanddev_ecc_engine_cleanup(nand);
nanddev_cleanup(nand);
spinand_manufacturer_cleanup(spinand);
+ spinand->cache_valid = false;
kfree(spinand->databuf);
kfree(spinand->scratchbuf);
}
@@ -2044,6 +2077,7 @@ static int spinand_probe(struct spi_mem *mem)
spi_mem_set_drvdata(mem, spinand);
spinand_set_of_node(spinand, mem->spi->dev.of_node);
mutex_init(&spinand->lock);
+ spinand->cache_valid = false;
mtd = spinand_to_mtd(spinand);
mtd->dev.parent = &mem->spi->dev;
diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
index 5f4c00ae72a7..887ef6d34a41 100644
--- a/include/linux/mtd/spinand.h
+++ b/include/linux/mtd/spinand.h
@@ -757,6 +757,10 @@ struct spinand_mem_ops {
* a command addressing a page or an eraseblock embedded in
* this die. Only required if your chip exposes several dies
* @cur_target: currently selected target/die
+ * @cur_target_cache: target of the cached page
+ * @cur_block_cache: eraseblock of the cached page
+ * @cur_page_cache: page number of the cached page
+ * @cache_valid: whether the cached page is valid
* @eccinfo: on-die ECC information
* @cfg_cache: config register cache. One entry per die
* @databuf: bounce buffer for data
@@ -798,6 +802,11 @@ struct spinand_device {
unsigned int target);
unsigned int cur_target;
+ unsigned int cur_target_cache;
+ unsigned int cur_block_cache;
+ unsigned int cur_page_cache;
+ bool cache_valid;
+
struct spinand_ecc_info eccinfo;
u8 *cfg_cache;
--
2.25.1
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] mtd: spinand: cache the last read page to avoid redundant SPI operations
2026-09-07 11:10 [PATCH] mtd: spinand: cache the last read page to avoid redundant SPI operations fzz
@ 2026-09-07 12:33 ` Miquel Raynal
2026-09-08 3:34 ` 1768315307
2026-09-08 1:51 ` kernel test robot
1 sibling, 1 reply; 4+ messages in thread
From: Miquel Raynal @ 2026-09-07 12:33 UTC (permalink / raw)
To: fzz; +Cc: Vignesh Raghavendra, Richard Weinberger, linux-mtd, linux-kernel
Hello,
On 07/09/2026 at 19:10:40 +08, fzz <1768315307@qq.com> wrote:
> When squashfs reads files through mtdblock, the mtdblock layer
> splits I/O into 512-byte sectors. For a 4K-page SPI NAND, this
> means reading the same page 8 times (4096 / 512), generating 7x
> redundant SPI read-from-cache operations. Each such operation
> involves a full SPI bus transaction, significantly slowing down
> boot time and file access.
>
> Cache the last successfully read page in spinand_device to avoid
> these redundant operations. When the same {target, eraseblock,
> page} is requested consecutively, the data is served directly
> from the bounce buffer (databuf) via memcpy, skipping the SPI
> transaction entirely.
>
> The cache is invalidated on write and erase operations. RAW reads,
> OOB reads, and continuous reads are excluded from caching to
> maintain correctness.
Why would you use mtdblock on top of a SPI NAND? It sounds like a very
bad choice.
Maybe mtdblock itself could be improved to avoid 512B accesses instead?
> Signed-off-by: fzz <1768315307@qq.com>
Unfortunately, for licensing and copyright reasons, pseudonyms are not
allowed in the Linux kernel.
> ---
> drivers/mtd/nand/spi/core.c | 36 +++++++++++++++++++++++++++++++++++-
> include/linux/mtd/spinand.h | 9 +++++++++
> 2 files changed, 44 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
> index 8bf9301f25e7..fc082127bda8 100644
> --- a/drivers/mtd/nand/spi/core.c
> +++ b/drivers/mtd/nand/spi/core.c
> @@ -557,7 +557,14 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand,
> req->ooblen);
> else
> memcpy(req->oobbuf.in, spinand->oobbuf + req->ooboffs,
> - req->ooblen);
> + req->ooblen);
Spurious change.
> + }
> +
Thanks,
Miquèl
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] mtd: spinand: cache the last read page to avoid redundant SPI operations
2026-09-07 11:10 [PATCH] mtd: spinand: cache the last read page to avoid redundant SPI operations fzz
2026-09-07 12:33 ` Miquel Raynal
@ 2026-09-08 1:51 ` kernel test robot
1 sibling, 0 replies; 4+ messages in thread
From: kernel test robot @ 2026-09-08 1:51 UTC (permalink / raw)
To: fzz, Miquel Raynal, Vignesh Raghavendra
Cc: oe-kbuild-all, Richard Weinberger, linux-mtd, linux-kernel, fzz
Hi fzz,
kernel test robot noticed the following build errors:
[auto build test ERROR on mtd/nand/next]
[also build test ERROR on linus/master v7.3-rc2 next-20260907]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/fzz/mtd-spinand-cache-the-last-read-page-to-avoid-redundant-SPI-operations/20260907-191040
base: https://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git nand/next
patch link: https://lore.kernel.org/r/tencent_219EED44C45D6CEC1D6B6724343E13733005%40qq.com
patch subject: [PATCH] mtd: spinand: cache the last read page to avoid redundant SPI operations
config: powerpc-randconfig-r072-20260908 (https://download.01.org/0day-ci/archive/20260908/202609080921.381EOoBZ-lkp@intel.com/config)
compiler: powerpc-linux-gcc (GCC) 9.5.0
smatch: v0.5.0-9187-g5189e3fb
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260908/202609080921.381EOoBZ-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202609080921.381EOoBZ-lkp@intel.com/
All errors (new ones prefixed by >>):
drivers/mtd/nand/spi/core.c: In function 'spinand_read_from_cache_op':
>> drivers/mtd/nand/spi/core.c:563:46: error: 'const struct nand_page_io_req' has no member named 'disable_ecc'
563 | if (req->datalen && !req->continuous && !req->disable_ecc) {
| ^~
vim +563 drivers/mtd/nand/spi/core.c
474
475 static int spinand_read_from_cache_op(struct spinand_device *spinand,
476 const struct nand_page_io_req *req)
477 {
478 struct nand_device *nand = spinand_to_nand(spinand);
479 struct mtd_info *mtd = spinand_to_mtd(spinand);
480 struct spi_mem_dirmap_desc *rdesc;
481 unsigned int nbytes = 0;
482 void *buf = NULL;
483 u16 column = 0;
484 ssize_t ret;
485
486 if (req->datalen) {
487 buf = spinand->databuf;
488 if (!req->continuous)
489 nbytes = nanddev_page_size(nand);
490 else
491 nbytes = round_up(req->dataoffs + req->datalen,
492 nanddev_page_size(nand));
493 column = 0;
494 }
495
496 if (req->ooblen) {
497 nbytes += nanddev_per_page_oobsize(nand);
498 if (!buf) {
499 buf = spinand->oobbuf;
500 column = nanddev_page_size(nand);
501 }
502 }
503
504 rdesc = spinand->dirmaps[req->pos.plane].rdesc;
505
506 if (spinand->op_templates->cont_read_cache && req->continuous)
507 rdesc->info.op_tmpl = &rdesc->info.secondary_op_tmpl;
508 else
509 rdesc->info.op_tmpl = &rdesc->info.primary_op_tmpl;
510
511 if (nand->ecc.engine->integration == NAND_ECC_ENGINE_INTEGRATION_PIPELINED &&
512 req->mode != MTD_OPS_RAW)
513 rdesc->info.op_tmpl->data.ecc = true;
514 else
515 rdesc->info.op_tmpl->data.ecc = false;
516
517 if (spinand->flags & SPINAND_HAS_READ_PLANE_SELECT_BIT)
518 column |= req->pos.plane << fls(nanddev_page_size(nand));
519
520 while (nbytes) {
521 ret = spi_mem_dirmap_read(rdesc, column, nbytes, buf);
522 if (ret < 0)
523 return ret;
524
525 if (!ret || ret > nbytes)
526 return -EIO;
527
528 nbytes -= ret;
529 column += ret;
530 buf += ret;
531
532 /*
533 * Dirmap accesses are allowed to toggle the CS.
534 * Toggling the CS during a continuous read is forbidden.
535 */
536 if (nbytes && req->continuous) {
537 /*
538 * Spi controller with broken support of continuous
539 * reading was detected. Disable future use of
540 * continuous reading and return -EAGAIN to retry
541 * reading within regular mode.
542 */
543 spinand->cont_read_possible = false;
544 return -EAGAIN;
545 }
546 }
547
548 if (req->datalen)
549 memcpy(req->databuf.in, spinand->databuf + req->dataoffs,
550 req->datalen);
551
552 if (req->ooblen) {
553 if (req->mode == MTD_OPS_AUTO_OOB)
554 mtd_ooblayout_get_databytes(mtd, req->oobbuf.in,
555 spinand->oobbuf,
556 req->ooboffs,
557 req->ooblen);
558 else
559 memcpy(req->oobbuf.in, spinand->oobbuf + req->ooboffs,
560 req->ooblen);
561 }
562
> 563 if (req->datalen && !req->continuous && !req->disable_ecc) {
564 spinand->cur_target_cache = req->pos.target;
565 spinand->cur_block_cache = req->pos.eraseblock;
566 spinand->cur_page_cache = req->pos.page;
567 spinand->cache_valid = true;
568 }
569
570 return 0;
571 }
572
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] mtd: spinand: cache the last read page to avoid redundant SPI operations
2026-09-07 12:33 ` Miquel Raynal
@ 2026-09-08 3:34 ` 1768315307
0 siblings, 0 replies; 4+ messages in thread
From: 1768315307 @ 2026-09-08 3:34 UTC (permalink / raw)
To: Miquel Raynal
Cc: Vignesh Raghavendra, Richard Weinberger, linux-mtd, linux-kernel
Hi Miquel,
Thanks for the review. Sent v2 with the fixes.
> why would you use mtdblock on top of a SPI NAND? It sounds like a very
> bad choice. Maybe mtdblock itself could be improved to avoid 512B
> accesses instead?
We use a read-only squashfs rootfs on mtdblock for SPI NAND. This is
a minimal configuration that avoids the complexity of UBI/UBIFS for
a read-only partition, and is commonly used in embedded devices like
routers and IoT systems.
The block layer's 512-byte sector size is not easily configurable per
device, and the driver already holds the full page in its bounce buffer
after each read. Adding a lightweight cache avoids the redundant SPI
reads without any additional hardware cost.
> Pseudonyms are not allowed in the Linux kernel.
Fixed in v2.
> Spurious change.
Fixed in v2.
Thanks,
Zongzhen Feng
原始邮件
发件人:Miquel Raynal <miquel.raynal@bootlin.com>
发件时间:2026年9月7日 20:33
收件人:fzz <1768315307@qq.com>
抄送:Vignesh Raghavendra <vigneshr@ti.com>, Richard Weinberger <richard@nod.at>, linux-mtd <linux-mtd@lists.infradead.org>, linux-kernel <linux-kernel@vger.kernel.org>
主题:Re: [PATCH] mtd: spinand: cache the last read page to avoid redundant SPI operations
Hello,
On 07/09/2026 at 19:10:40 +08, fzz <1768315307@qq.com> wrote:
> When squashfs reads files through mtdblock, the mtdblock layer
> splits I/O into 512-byte sectors. For a 4K-page SPI NAND, this
> means reading the same page 8 times (4096 / 512), generating 7x
> redundant SPI read-from-cache operations. Each such operation
> involves a full SPI bus transaction, significantly slowing down
> boot time and file access.
>
> Cache the last successfully read page in spinand_device to avoid
> these redundant operations. When the same {target, eraseblock,
> page} is requested consecutively, the data is served directly
> from the bounce buffer (databuf) via memcpy, skipping the SPI
> transaction entirely.
>
> The cache is invalidated on write and erase operations. RAW reads,
> OOB reads, and continuous reads are excluded from caching to
> maintain correctness.
Why would you use mtdblock on top of a SPI NAND? It sounds like a very
bad choice.
Maybe mtdblock itself could be improved to avoid 512B accesses instead?
> Signed-off-by: fzz <1768315307@qq.com>
Unfortunately, for licensing and copyright reasons, pseudonyms are not
allowed in the Linux kernel.
> ---
> drivers/mtd/nand/spi/core.c | 36 +++++++++++++++++++++++++++++++++++-
> include/linux/mtd/spinand.h | 9 +++++++++
> 2 files changed, 44 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
> index 8bf9301f25e7..fc082127bda8 100644
> --- a/drivers/mtd/nand/spi/core.c
> +++ b/drivers/mtd/nand/spi/core.c
> @@ -557,7 +557,14 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand,
> req->ooblen);
> else
> memcpy(req->oobbuf.in, spinand->oobbuf + req->ooboffs,
> - req->ooblen);
> + req->ooblen);
Spurious change.
> + }
> +
Thanks,
Miquèl
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-09-08 3:34 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-07 11:10 [PATCH] mtd: spinand: cache the last read page to avoid redundant SPI operations fzz
2026-09-07 12:33 ` Miquel Raynal
2026-09-08 3:34 ` 1768315307
2026-09-08 1:51 ` kernel test robot
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®