mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [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®