* [PATCH v4 0/7] spi: Add support for panic mem writes
@ 2026-10-16 10:29 ` Paul Cercueil
0 siblings, 0 replies; 20+ messages in thread
From: Paul Cercueil @ 2026-10-16 10:29 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, linux-kernel, linux-spi, Bharat Bhushan,
Sunil Kovvuri Goutham, Tanmay Jagdale, Paul Cercueil
Hi,
Here is the v4 of my patchset that updates the MTD spi-nor code as well
as the spi-mem code, to add support for panic mem writes to a memory
partition configured as pstore.
This revision adds a workaround in mtd/spi-nor to avoid a call to
kmalloc(), and fixes a kernel doc mismatch.
Changelog:
- [2/7]:
- Also update kernel doc for new 'timeout_ms' parameter
- [3/7]:
- kmalloc() is not safe in panic context; reuse boucebuf instead
- Patch [1/7] and 4->7 unchanged since v3.
Cheers,
-Paul
Tanmay Jagdale (7):
spi: spi-mem: Add support for panic mem writes
mtd: spi-nor: Wait for NOR ready with poll_timeout_us()
mtd: spi-nor: Add support for panic writes
spi: cadence-xspi: Clear "SDMA ready" IRQ flag after polling
spi: cadence-xspi: Add irq-less support
spi: cadence-xspi: Add support for panic writes
spi: dt-bindings: cadence: Make XSPI's IRQ line optional
.../devicetree/bindings/spi/cdns,xspi.yaml | 1 -
drivers/mtd/spi-nor/core.c | 128 ++++++++++++++----
drivers/spi/spi-cadence-xspi.c | 114 +++++++++-------
drivers/spi/spi-mem.c | 42 +++++-
include/linux/spi/spi-mem.h | 5 +
5 files changed, 209 insertions(+), 81 deletions(-)
--
2.47.3
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 20+ messages in thread* [PATCH v4 1/7] spi: spi-mem: Add support for panic mem writes
2026-10-16 10:29 ` Paul Cercueil
@ 2026-10-16 10:29 ` Paul Cercueil
-1 siblings, 0 replies; 20+ messages in thread
From: Paul Cercueil @ 2026-10-16 10:29 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, linux-kernel, linux-spi, Bharat Bhushan,
Sunil Kovvuri Goutham, Tanmay Jagdale, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
Add a 'panic_write' flag to the controller's capabilities struct.
When set, the SPI controller indicates that it will be able to send
commands to a connected NOR or NAND memory in the context of a kernel
panic, which means without sleeping and without relying on IRQs.
Whether or not a given memory operation is a panic mem write is given by
the spi_mem_op.panic_write flag.
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: - Don't call spi_mem_access_start/end on panic. We don't need to
flush the SPI queue at that point, and we don't need to lock the
mutexes.
- Check that the SPI controller's PM status is active, and bail out
otherwise.
v3: - Use pm_runtime_active() instead of pm_runtime_get_if_active(). The
latter cannot be used in a panic context.
- Forbid fallback to regular SPI on panic
v4: No change
drivers/spi/spi-mem.c | 42 +++++++++++++++++++++++++++++++++----
include/linux/spi/spi-mem.h | 5 +++++
2 files changed, 43 insertions(+), 4 deletions(-)
diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
index b94680b60af9..9ad42dd9de70 100644
--- a/drivers/spi/spi-mem.c
+++ b/drivers/spi/spi-mem.c
@@ -398,6 +398,31 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
u8 *tmpbuf;
int ret;
+ if (op->panic_write) {
+ if (!spi_mem_controller_is_capable(ctlr, panic_write))
+ return -EOPNOTSUPP;
+
+ /*
+ * Only support panic writes if we'll go through .exec_op().
+ * The regular SPI path (spi_sync(), etc.) explicitly mentions
+ * that it can only be used from a context that may sleep, which
+ * is not the case on a kernel panic.
+ * If this changes in the future, this could be reworked, but
+ * for now this will have to do.
+ */
+ if (WARN_ON_ONCE(!ctlr->mem_ops || !ctlr->mem_ops->exec_op))
+ return -EOPNOTSUPP;
+
+ if (spi_get_csgpiod(mem->spi, 0))
+ return -EPERM;
+
+ /* On panic IRQs are disabled so we cannot wait on locks */
+ if (mutex_is_locked(&ctlr->bus_lock_mutex) ||
+ mutex_is_locked(&ctlr->io_mutex)) {
+ return -EPERM;
+ }
+ }
+
/* Make sure the operation frequency is correct before going futher */
spi_mem_adjust_op_freq(mem, (struct spi_mem_op *)op);
@@ -420,15 +445,20 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
return -EOPNOTSUPP;
if (ctlr->mem_ops && ctlr->mem_ops->exec_op && !spi_get_csgpiod(mem->spi, 0)) {
- ret = spi_mem_access_start(mem);
- if (ret)
- return ret;
+ if (!op->panic_write) {
+ ret = spi_mem_access_start(mem);
+ if (ret)
+ return ret;
+ } else if (!pm_runtime_active(ctlr->dev.parent)) {
+ return -EPERM;
+ }
trace_spi_mem_start_op(mem, op);
ret = ctlr->mem_ops->exec_op(mem, op);
trace_spi_mem_stop_op(mem, op);
- spi_mem_access_end(mem);
+ if (!op->panic_write)
+ spi_mem_access_end(mem);
/*
* Some controllers only optimize specific paths (typically the
@@ -441,6 +471,10 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
return ret;
}
+
+ /* On panic write we can't fall back to the fallback SPI path */
+ if (op->panic_write)
+ return ret;
}
tmpbufsize = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes;
diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h
index 0cce6b57242a..2633307bfad5 100644
--- a/include/linux/spi/spi-mem.h
+++ b/include/linux/spi/spi-mem.h
@@ -169,6 +169,7 @@ enum spi_mem_data_dir {
* operation does not involve transferring data
* @data.buf.in: input buffer (must be DMA-able)
* @data.buf.out: output buffer (must be DMA-able)
+ * @panic_write: The command is executed in the context of a kernel panic
* @max_freq: frequency limitation wrt this operation. 0 means there is no
* specific constraint and the highest achievable frequency can be
* attempted.
@@ -211,6 +212,7 @@ struct spi_mem_op {
} buf;
} data;
+ bool panic_write;
unsigned int max_freq;
};
@@ -392,6 +394,8 @@ struct spi_controller_mem_ops {
* is a pause in the transfer (eg. internal bus contention or
* DMA arbitration on an interconnect). Features such as NAND
* continuous reads shall not be leveraged.
+ * @panic_write: Set to true if the controller driver supports writing to a
+ * connected NAND/NOR memory in a kernel panic context
*/
struct spi_controller_mem_caps {
bool dtr;
@@ -400,6 +404,7 @@ struct spi_controller_mem_caps {
bool per_op_freq;
bool secondary_op_tmpl;
bool no_cs_assertion;
+ bool panic_write;
};
#define spi_mem_controller_is_capable(ctlr, cap) \
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v4 1/7] spi: spi-mem: Add support for panic mem writes
@ 2026-10-16 10:29 ` Paul Cercueil
0 siblings, 0 replies; 20+ messages in thread
From: Paul Cercueil @ 2026-10-16 10:29 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, linux-kernel, linux-spi, Bharat Bhushan,
Sunil Kovvuri Goutham, Tanmay Jagdale, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
Add a 'panic_write' flag to the controller's capabilities struct.
When set, the SPI controller indicates that it will be able to send
commands to a connected NOR or NAND memory in the context of a kernel
panic, which means without sleeping and without relying on IRQs.
Whether or not a given memory operation is a panic mem write is given by
the spi_mem_op.panic_write flag.
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: - Don't call spi_mem_access_start/end on panic. We don't need to
flush the SPI queue at that point, and we don't need to lock the
mutexes.
- Check that the SPI controller's PM status is active, and bail out
otherwise.
v3: - Use pm_runtime_active() instead of pm_runtime_get_if_active(). The
latter cannot be used in a panic context.
- Forbid fallback to regular SPI on panic
v4: No change
drivers/spi/spi-mem.c | 42 +++++++++++++++++++++++++++++++++----
include/linux/spi/spi-mem.h | 5 +++++
2 files changed, 43 insertions(+), 4 deletions(-)
diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
index b94680b60af9..9ad42dd9de70 100644
--- a/drivers/spi/spi-mem.c
+++ b/drivers/spi/spi-mem.c
@@ -398,6 +398,31 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
u8 *tmpbuf;
int ret;
+ if (op->panic_write) {
+ if (!spi_mem_controller_is_capable(ctlr, panic_write))
+ return -EOPNOTSUPP;
+
+ /*
+ * Only support panic writes if we'll go through .exec_op().
+ * The regular SPI path (spi_sync(), etc.) explicitly mentions
+ * that it can only be used from a context that may sleep, which
+ * is not the case on a kernel panic.
+ * If this changes in the future, this could be reworked, but
+ * for now this will have to do.
+ */
+ if (WARN_ON_ONCE(!ctlr->mem_ops || !ctlr->mem_ops->exec_op))
+ return -EOPNOTSUPP;
+
+ if (spi_get_csgpiod(mem->spi, 0))
+ return -EPERM;
+
+ /* On panic IRQs are disabled so we cannot wait on locks */
+ if (mutex_is_locked(&ctlr->bus_lock_mutex) ||
+ mutex_is_locked(&ctlr->io_mutex)) {
+ return -EPERM;
+ }
+ }
+
/* Make sure the operation frequency is correct before going futher */
spi_mem_adjust_op_freq(mem, (struct spi_mem_op *)op);
@@ -420,15 +445,20 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
return -EOPNOTSUPP;
if (ctlr->mem_ops && ctlr->mem_ops->exec_op && !spi_get_csgpiod(mem->spi, 0)) {
- ret = spi_mem_access_start(mem);
- if (ret)
- return ret;
+ if (!op->panic_write) {
+ ret = spi_mem_access_start(mem);
+ if (ret)
+ return ret;
+ } else if (!pm_runtime_active(ctlr->dev.parent)) {
+ return -EPERM;
+ }
trace_spi_mem_start_op(mem, op);
ret = ctlr->mem_ops->exec_op(mem, op);
trace_spi_mem_stop_op(mem, op);
- spi_mem_access_end(mem);
+ if (!op->panic_write)
+ spi_mem_access_end(mem);
/*
* Some controllers only optimize specific paths (typically the
@@ -441,6 +471,10 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op)
return ret;
}
+
+ /* On panic write we can't fall back to the fallback SPI path */
+ if (op->panic_write)
+ return ret;
}
tmpbufsize = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes;
diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h
index 0cce6b57242a..2633307bfad5 100644
--- a/include/linux/spi/spi-mem.h
+++ b/include/linux/spi/spi-mem.h
@@ -169,6 +169,7 @@ enum spi_mem_data_dir {
* operation does not involve transferring data
* @data.buf.in: input buffer (must be DMA-able)
* @data.buf.out: output buffer (must be DMA-able)
+ * @panic_write: The command is executed in the context of a kernel panic
* @max_freq: frequency limitation wrt this operation. 0 means there is no
* specific constraint and the highest achievable frequency can be
* attempted.
@@ -211,6 +212,7 @@ struct spi_mem_op {
} buf;
} data;
+ bool panic_write;
unsigned int max_freq;
};
@@ -392,6 +394,8 @@ struct spi_controller_mem_ops {
* is a pause in the transfer (eg. internal bus contention or
* DMA arbitration on an interconnect). Features such as NAND
* continuous reads shall not be leveraged.
+ * @panic_write: Set to true if the controller driver supports writing to a
+ * connected NAND/NOR memory in a kernel panic context
*/
struct spi_controller_mem_caps {
bool dtr;
@@ -400,6 +404,7 @@ struct spi_controller_mem_caps {
bool per_op_freq;
bool secondary_op_tmpl;
bool no_cs_assertion;
+ bool panic_write;
};
#define spi_mem_controller_is_capable(ctlr, cap) \
--
2.47.3
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v4 2/7] mtd: spi-nor: Wait for NOR ready with poll_timeout_us()
2026-10-16 10:29 ` Paul Cercueil
@ 2026-10-16 10:29 ` Paul Cercueil
-1 siblings, 0 replies; 20+ messages in thread
From: Paul Cercueil @ 2026-10-16 10:29 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, linux-kernel, linux-spi, Bharat Bhushan,
Sunil Kovvuri Goutham, Tanmay Jagdale, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
Instead of polling for the spi_nor_ready() with a custom loop, use
poll_timeout_us(). The sleep period between two polls is set to 100 us
which I think should be fine.
This will make it simpler to support waiting for the SPI NOR in atomic
context later.
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: New patch
v3: Set 'timeout_ms' parameter to u64 to prevent u32 overflow
v4: Also update kernel doc for new 'timeout_ms' parameter
drivers/mtd/spi-nor/core.c | 42 +++++++++++++++-----------------------
1 file changed, 17 insertions(+), 25 deletions(-)
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 8bc117b46e02..7f347141ea3f 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -12,6 +12,7 @@
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
+#include <linux/iopoll.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/mtd/mtd.h>
@@ -32,13 +33,13 @@
* For everything but full-chip erase; probably could be much smaller, but kept
* around for safety for now
*/
-#define DEFAULT_READY_WAIT_JIFFIES (40UL * HZ)
+#define DEFAULT_READY_TIMEOUT_MS 40000UL
/*
* For full-chip erase, calibrated to a 2MB flash (M25P16); should be scaled up
* for larger flash
*/
-#define CHIP_ERASE_2MB_READY_WAIT_JIFFIES (40UL * HZ)
+#define CHIP_ERASE_2MB_READY_TIMEOUT_MS 40000UL
#define SPI_NOR_MAX_ADDR_NBYTES 4
@@ -707,34 +708,25 @@ static int spi_nor_ready(struct spi_nor *nor)
* spi_nor_wait_till_ready_with_timeout() - Service routine to read the
* Status Register until ready, or timeout occurs.
* @nor: pointer to "struct spi_nor".
- * @timeout_jiffies: jiffies to wait until timeout.
+ * @timeout_ms: timeout value in milliseconds.
*
* Return: 0 on success, -errno otherwise.
*/
-static int spi_nor_wait_till_ready_with_timeout(struct spi_nor *nor,
- unsigned long timeout_jiffies)
+static int spi_nor_wait_till_ready_with_timeout(struct spi_nor *nor, u64 timeout_ms)
{
- unsigned long deadline;
- int timeout = 0, ret;
+ int ret, status;
- deadline = jiffies + timeout_jiffies;
-
- while (!timeout) {
- if (time_after_eq(jiffies, deadline))
- timeout = 1;
-
- ret = spi_nor_ready(nor);
- if (ret < 0)
- return ret;
- if (ret)
- return 0;
-
- cond_resched();
+ ret = poll_timeout_us(status = spi_nor_ready(nor), status,
+ 100, timeout_ms * 1000, false);
+ if (ret < 0) {
+ dev_dbg(nor->dev, "flash operation timed out\n");
+ return ret;
}
- dev_dbg(nor->dev, "flash operation timed out\n");
+ if (status < 0)
+ return status;
- return -ETIMEDOUT;
+ return 0;
}
/**
@@ -747,7 +739,7 @@ static int spi_nor_wait_till_ready_with_timeout(struct spi_nor *nor,
int spi_nor_wait_till_ready(struct spi_nor *nor)
{
return spi_nor_wait_till_ready_with_timeout(nor,
- DEFAULT_READY_WAIT_JIFFIES);
+ DEFAULT_READY_TIMEOUT_MS);
}
/**
@@ -1781,8 +1773,8 @@ static int spi_nor_erase_dice(struct spi_nor *nor, loff_t addr,
* pull these from CFI/SFDP, but these values should be good
* enough for now.
*/
- timeout = max(CHIP_ERASE_2MB_READY_WAIT_JIFFIES,
- CHIP_ERASE_2MB_READY_WAIT_JIFFIES *
+ timeout = max(CHIP_ERASE_2MB_READY_TIMEOUT_MS,
+ CHIP_ERASE_2MB_READY_TIMEOUT_MS *
(unsigned long)(nor->mtd.size / SZ_2M));
do {
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v4 2/7] mtd: spi-nor: Wait for NOR ready with poll_timeout_us()
@ 2026-10-16 10:29 ` Paul Cercueil
0 siblings, 0 replies; 20+ messages in thread
From: Paul Cercueil @ 2026-10-16 10:29 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, linux-kernel, linux-spi, Bharat Bhushan,
Sunil Kovvuri Goutham, Tanmay Jagdale, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
Instead of polling for the spi_nor_ready() with a custom loop, use
poll_timeout_us(). The sleep period between two polls is set to 100 us
which I think should be fine.
This will make it simpler to support waiting for the SPI NOR in atomic
context later.
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: New patch
v3: Set 'timeout_ms' parameter to u64 to prevent u32 overflow
v4: Also update kernel doc for new 'timeout_ms' parameter
drivers/mtd/spi-nor/core.c | 42 +++++++++++++++-----------------------
1 file changed, 17 insertions(+), 25 deletions(-)
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 8bc117b46e02..7f347141ea3f 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -12,6 +12,7 @@
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
+#include <linux/iopoll.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/mtd/mtd.h>
@@ -32,13 +33,13 @@
* For everything but full-chip erase; probably could be much smaller, but kept
* around for safety for now
*/
-#define DEFAULT_READY_WAIT_JIFFIES (40UL * HZ)
+#define DEFAULT_READY_TIMEOUT_MS 40000UL
/*
* For full-chip erase, calibrated to a 2MB flash (M25P16); should be scaled up
* for larger flash
*/
-#define CHIP_ERASE_2MB_READY_WAIT_JIFFIES (40UL * HZ)
+#define CHIP_ERASE_2MB_READY_TIMEOUT_MS 40000UL
#define SPI_NOR_MAX_ADDR_NBYTES 4
@@ -707,34 +708,25 @@ static int spi_nor_ready(struct spi_nor *nor)
* spi_nor_wait_till_ready_with_timeout() - Service routine to read the
* Status Register until ready, or timeout occurs.
* @nor: pointer to "struct spi_nor".
- * @timeout_jiffies: jiffies to wait until timeout.
+ * @timeout_ms: timeout value in milliseconds.
*
* Return: 0 on success, -errno otherwise.
*/
-static int spi_nor_wait_till_ready_with_timeout(struct spi_nor *nor,
- unsigned long timeout_jiffies)
+static int spi_nor_wait_till_ready_with_timeout(struct spi_nor *nor, u64 timeout_ms)
{
- unsigned long deadline;
- int timeout = 0, ret;
+ int ret, status;
- deadline = jiffies + timeout_jiffies;
-
- while (!timeout) {
- if (time_after_eq(jiffies, deadline))
- timeout = 1;
-
- ret = spi_nor_ready(nor);
- if (ret < 0)
- return ret;
- if (ret)
- return 0;
-
- cond_resched();
+ ret = poll_timeout_us(status = spi_nor_ready(nor), status,
+ 100, timeout_ms * 1000, false);
+ if (ret < 0) {
+ dev_dbg(nor->dev, "flash operation timed out\n");
+ return ret;
}
- dev_dbg(nor->dev, "flash operation timed out\n");
+ if (status < 0)
+ return status;
- return -ETIMEDOUT;
+ return 0;
}
/**
@@ -747,7 +739,7 @@ static int spi_nor_wait_till_ready_with_timeout(struct spi_nor *nor,
int spi_nor_wait_till_ready(struct spi_nor *nor)
{
return spi_nor_wait_till_ready_with_timeout(nor,
- DEFAULT_READY_WAIT_JIFFIES);
+ DEFAULT_READY_TIMEOUT_MS);
}
/**
@@ -1781,8 +1773,8 @@ static int spi_nor_erase_dice(struct spi_nor *nor, loff_t addr,
* pull these from CFI/SFDP, but these values should be good
* enough for now.
*/
- timeout = max(CHIP_ERASE_2MB_READY_WAIT_JIFFIES,
- CHIP_ERASE_2MB_READY_WAIT_JIFFIES *
+ timeout = max(CHIP_ERASE_2MB_READY_TIMEOUT_MS,
+ CHIP_ERASE_2MB_READY_TIMEOUT_MS *
(unsigned long)(nor->mtd.size / SZ_2M));
do {
--
2.47.3
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v4 3/7] mtd: spi-nor: Add support for panic writes
2026-10-16 10:29 ` Paul Cercueil
@ 2026-10-16 10:29 ` Paul Cercueil
-1 siblings, 0 replies; 20+ messages in thread
From: Paul Cercueil @ 2026-10-16 10:29 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, linux-kernel, linux-spi, Bharat Bhushan,
Sunil Kovvuri Goutham, Tanmay Jagdale, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
If the SPI controller supports it, provide the functionality of doing a
panic write.
Update the spi-nor core code and controllers to make sure they won't try
to grab locked mutexes or sleep when doing a panic write, as preemption
and IRQs are disabled.
The information is carried down to the SPI controller driver through the
spi_mem_op structure. If the corresponding flag is set there, and the
SPI driver does support panic writes, it then has to respect the same
rules.
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: - Update spi_nor_wait_till_ready_with_timeout() to use
poll_timeout_us_atomic() on panic
- Alloc temp buffer with GFP_ATOMIC in spi_nor_octal_dtr_write()
on panic
- Bail out if nor->rww.ongoing_pe is set in spi_nor_panic_write()
v3: Use GFP_KERNEL xor GFP_ATOMIC
v4: kmalloc() is not safe in panic context; reuse boucebuf instead
drivers/mtd/spi-nor/core.c | 92 ++++++++++++++++++++++++++++++++++----
1 file changed, 84 insertions(+), 8 deletions(-)
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 7f347141ea3f..10ec80c54504 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -97,6 +97,9 @@ void spi_nor_spimem_setup_op(const struct spi_nor *nor,
if (op->data.nbytes)
op->data.buswidth = spi_nor_get_protocol_data_nbits(proto);
+ if (nor->mtd.oops_panic_write)
+ op->panic_write = true;
+
if (spi_nor_protocol_is_dtr(proto)) {
/*
* SPIMEM supports mixed DTR modes, but right now we can only
@@ -282,7 +285,7 @@ static ssize_t spi_nor_spimem_write_data(struct spi_nor *nor, loff_t to,
if (spi_nor_spimem_bounce(nor, &op))
memcpy(nor->bouncebuf, buf, op.data.nbytes);
- if (nor->dirmap.wdesc) {
+ if (nor->dirmap.wdesc && !nor->mtd.oops_panic_write) {
nbytes = spi_mem_dirmap_write(nor->dirmap.wdesc, op.addr.val,
op.data.nbytes, op.data.buf.out);
} else {
@@ -665,6 +668,9 @@ static void spi_nor_rww_end_rdst(struct spi_nor *nor)
static int spi_nor_lock_rdst(struct spi_nor *nor)
{
+ if (nor->mtd.oops_panic_write)
+ return 0;
+
if (spi_nor_use_parallel_locking(nor))
return spi_nor_rww_start_rdst(nor);
@@ -673,6 +679,9 @@ static int spi_nor_lock_rdst(struct spi_nor *nor)
static void spi_nor_unlock_rdst(struct spi_nor *nor)
{
+ if (nor->mtd.oops_panic_write)
+ return;
+
if (spi_nor_use_parallel_locking(nor)) {
spi_nor_rww_end_rdst(nor);
wake_up(&nor->rww.wait);
@@ -716,8 +725,14 @@ static int spi_nor_wait_till_ready_with_timeout(struct spi_nor *nor, u64 timeout
{
int ret, status;
- ret = poll_timeout_us(status = spi_nor_ready(nor), status,
- 100, timeout_ms * 1000, false);
+ if (nor->mtd.oops_panic_write) {
+ ret = poll_timeout_us_atomic(status = spi_nor_ready(nor), status,
+ 100, timeout_ms * 1000, false);
+ } else {
+ ret = poll_timeout_us(status = spi_nor_ready(nor), status,
+ 100, timeout_ms * 1000, false);
+ }
+
if (ret < 0) {
dev_dbg(nor->dev, "flash operation timed out\n");
return ret;
@@ -1283,6 +1298,9 @@ static void spi_nor_rww_end_io(struct spi_nor *nor)
static int spi_nor_lock_device(struct spi_nor *nor)
{
+ if (nor->mtd.oops_panic_write)
+ return 0;
+
if (!spi_nor_use_parallel_locking(nor))
return 0;
@@ -1291,6 +1309,9 @@ static int spi_nor_lock_device(struct spi_nor *nor)
static void spi_nor_unlock_device(struct spi_nor *nor)
{
+ if (nor->mtd.oops_panic_write)
+ return;
+
if (spi_nor_use_parallel_locking(nor)) {
spi_nor_rww_end_io(nor);
wake_up(&nor->rww.wait);
@@ -1328,6 +1349,9 @@ int spi_nor_prep_and_lock(struct spi_nor *nor)
{
int ret;
+ if (nor->mtd.oops_panic_write)
+ return 0;
+
ret = spi_nor_prep(nor);
if (ret)
return ret;
@@ -1343,6 +1367,9 @@ int spi_nor_prep_and_lock(struct spi_nor *nor)
void spi_nor_unlock_and_unprep(struct spi_nor *nor)
{
+ if (nor->mtd.oops_panic_write)
+ return;
+
if (!spi_nor_use_parallel_locking(nor)) {
mutex_unlock(&nor->lock);
} else {
@@ -1399,6 +1426,9 @@ static int spi_nor_prep_and_lock_pe(struct spi_nor *nor, loff_t start, size_t le
{
int ret;
+ if (nor->mtd.oops_panic_write)
+ return 0;
+
ret = spi_nor_prep(nor);
if (ret)
return ret;
@@ -1414,6 +1444,9 @@ static int spi_nor_prep_and_lock_pe(struct spi_nor *nor, loff_t start, size_t le
static void spi_nor_unlock_and_unprep_pe(struct spi_nor *nor, loff_t start, size_t len)
{
+ if (nor->mtd.oops_panic_write)
+ return;
+
if (!spi_nor_use_parallel_locking(nor)) {
mutex_unlock(&nor->lock);
} else {
@@ -1472,6 +1505,9 @@ static int spi_nor_prep_and_lock_rd(struct spi_nor *nor, loff_t start, size_t le
{
int ret;
+ if (nor->mtd.oops_panic_write)
+ return 0;
+
ret = spi_nor_prep(nor);
if (ret)
return ret;
@@ -1487,6 +1523,9 @@ static int spi_nor_prep_and_lock_rd(struct spi_nor *nor, loff_t start, size_t le
static void spi_nor_unlock_and_unprep_rd(struct spi_nor *nor, loff_t start, size_t len)
{
+ if (nor->mtd.oops_panic_write)
+ return;
+
if (!spi_nor_use_parallel_locking(nor)) {
mutex_unlock(&nor->lock);
} else {
@@ -2203,9 +2242,20 @@ static int spi_nor_octal_dtr_write(struct spi_nor *nor, loff_t to, size_t len,
if (IS_ALIGNED(to, 2) && IS_ALIGNED(len, 2))
return spi_nor_write_data(nor, to, len, buf);
- tmp_buf = kmalloc(nor->params->page_size, GFP_KERNEL);
- if (!tmp_buf)
- return -ENOMEM;
+ if (nor->mtd.oops_panic_write) {
+ /*
+ * The kernel is panicking; kmalloc() is not safe. Shamelessly
+ * reuse the 'bouncebuf' buffer, which is the size we want, and
+ * won't be reused anymore at this point (the first two bytes
+ * might be overwritten, but only after we transferred them, so
+ * it's safe).
+ */
+ tmp_buf = nor->bouncebuf;
+ } else {
+ tmp_buf = kmalloc(nor->params->page_size, GFP_KERNEL);
+ if (!tmp_buf)
+ return -ENOMEM;
+ }
memset(tmp_buf, 0xff, nor->params->page_size);
@@ -2243,7 +2293,8 @@ static int spi_nor_octal_dtr_write(struct spi_nor *nor, loff_t to, size_t len,
ret -= end - (to + len);
out:
- kfree(tmp_buf);
+ if (!nor->mtd.oops_panic_write)
+ kfree(tmp_buf);
return ret;
}
@@ -3385,6 +3436,26 @@ static void spi_nor_soft_reset(struct spi_nor *nor)
usleep_range(SPI_NOR_SRST_SLEEP_MIN, SPI_NOR_SRST_SLEEP_MAX);
}
+static int spi_nor_panic_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct spi_nor *nor = mtd_to_spi_nor(mtd);
+
+ /*
+ * At this point preemption and local interrupts are disabled, so we
+ * can't get the lock if it's taken.
+ */
+ if (mutex_is_locked(&nor->lock))
+ return -EPERM;
+
+ if (spi_nor_use_parallel_locking(nor) &&
+ (nor->rww.ongoing_io || nor->rww.ongoing_rd || nor->rww.ongoing_pe)) {
+ return -EPERM;
+ }
+
+ return spi_nor_write(mtd, to, len, retlen, buf);
+}
+
/* mtd suspend handler */
static int spi_nor_suspend(struct mtd_info *mtd)
{
@@ -3588,8 +3659,13 @@ static int spi_nor_set_mtd_info(struct spi_nor *nor)
mtd->size = nor->params->size;
mtd->_read = spi_nor_read;
/* Might be already set by some SST flashes. */
- if (!mtd->_write)
+ if (!mtd->_write) {
mtd->_write = spi_nor_write;
+ if (nor->spimem &&
+ spi_mem_controller_is_capable(nor->spimem->spi->controller, panic_write)) {
+ mtd->_panic_write = spi_nor_panic_write;
+ }
+ }
mtd->_suspend = spi_nor_suspend;
mtd->_resume = spi_nor_resume;
mtd->_get_device = spi_nor_get_device;
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v4 3/7] mtd: spi-nor: Add support for panic writes
@ 2026-10-16 10:29 ` Paul Cercueil
0 siblings, 0 replies; 20+ messages in thread
From: Paul Cercueil @ 2026-10-16 10:29 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, linux-kernel, linux-spi, Bharat Bhushan,
Sunil Kovvuri Goutham, Tanmay Jagdale, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
If the SPI controller supports it, provide the functionality of doing a
panic write.
Update the spi-nor core code and controllers to make sure they won't try
to grab locked mutexes or sleep when doing a panic write, as preemption
and IRQs are disabled.
The information is carried down to the SPI controller driver through the
spi_mem_op structure. If the corresponding flag is set there, and the
SPI driver does support panic writes, it then has to respect the same
rules.
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: - Update spi_nor_wait_till_ready_with_timeout() to use
poll_timeout_us_atomic() on panic
- Alloc temp buffer with GFP_ATOMIC in spi_nor_octal_dtr_write()
on panic
- Bail out if nor->rww.ongoing_pe is set in spi_nor_panic_write()
v3: Use GFP_KERNEL xor GFP_ATOMIC
v4: kmalloc() is not safe in panic context; reuse boucebuf instead
drivers/mtd/spi-nor/core.c | 92 ++++++++++++++++++++++++++++++++++----
1 file changed, 84 insertions(+), 8 deletions(-)
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 7f347141ea3f..10ec80c54504 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -97,6 +97,9 @@ void spi_nor_spimem_setup_op(const struct spi_nor *nor,
if (op->data.nbytes)
op->data.buswidth = spi_nor_get_protocol_data_nbits(proto);
+ if (nor->mtd.oops_panic_write)
+ op->panic_write = true;
+
if (spi_nor_protocol_is_dtr(proto)) {
/*
* SPIMEM supports mixed DTR modes, but right now we can only
@@ -282,7 +285,7 @@ static ssize_t spi_nor_spimem_write_data(struct spi_nor *nor, loff_t to,
if (spi_nor_spimem_bounce(nor, &op))
memcpy(nor->bouncebuf, buf, op.data.nbytes);
- if (nor->dirmap.wdesc) {
+ if (nor->dirmap.wdesc && !nor->mtd.oops_panic_write) {
nbytes = spi_mem_dirmap_write(nor->dirmap.wdesc, op.addr.val,
op.data.nbytes, op.data.buf.out);
} else {
@@ -665,6 +668,9 @@ static void spi_nor_rww_end_rdst(struct spi_nor *nor)
static int spi_nor_lock_rdst(struct spi_nor *nor)
{
+ if (nor->mtd.oops_panic_write)
+ return 0;
+
if (spi_nor_use_parallel_locking(nor))
return spi_nor_rww_start_rdst(nor);
@@ -673,6 +679,9 @@ static int spi_nor_lock_rdst(struct spi_nor *nor)
static void spi_nor_unlock_rdst(struct spi_nor *nor)
{
+ if (nor->mtd.oops_panic_write)
+ return;
+
if (spi_nor_use_parallel_locking(nor)) {
spi_nor_rww_end_rdst(nor);
wake_up(&nor->rww.wait);
@@ -716,8 +725,14 @@ static int spi_nor_wait_till_ready_with_timeout(struct spi_nor *nor, u64 timeout
{
int ret, status;
- ret = poll_timeout_us(status = spi_nor_ready(nor), status,
- 100, timeout_ms * 1000, false);
+ if (nor->mtd.oops_panic_write) {
+ ret = poll_timeout_us_atomic(status = spi_nor_ready(nor), status,
+ 100, timeout_ms * 1000, false);
+ } else {
+ ret = poll_timeout_us(status = spi_nor_ready(nor), status,
+ 100, timeout_ms * 1000, false);
+ }
+
if (ret < 0) {
dev_dbg(nor->dev, "flash operation timed out\n");
return ret;
@@ -1283,6 +1298,9 @@ static void spi_nor_rww_end_io(struct spi_nor *nor)
static int spi_nor_lock_device(struct spi_nor *nor)
{
+ if (nor->mtd.oops_panic_write)
+ return 0;
+
if (!spi_nor_use_parallel_locking(nor))
return 0;
@@ -1291,6 +1309,9 @@ static int spi_nor_lock_device(struct spi_nor *nor)
static void spi_nor_unlock_device(struct spi_nor *nor)
{
+ if (nor->mtd.oops_panic_write)
+ return;
+
if (spi_nor_use_parallel_locking(nor)) {
spi_nor_rww_end_io(nor);
wake_up(&nor->rww.wait);
@@ -1328,6 +1349,9 @@ int spi_nor_prep_and_lock(struct spi_nor *nor)
{
int ret;
+ if (nor->mtd.oops_panic_write)
+ return 0;
+
ret = spi_nor_prep(nor);
if (ret)
return ret;
@@ -1343,6 +1367,9 @@ int spi_nor_prep_and_lock(struct spi_nor *nor)
void spi_nor_unlock_and_unprep(struct spi_nor *nor)
{
+ if (nor->mtd.oops_panic_write)
+ return;
+
if (!spi_nor_use_parallel_locking(nor)) {
mutex_unlock(&nor->lock);
} else {
@@ -1399,6 +1426,9 @@ static int spi_nor_prep_and_lock_pe(struct spi_nor *nor, loff_t start, size_t le
{
int ret;
+ if (nor->mtd.oops_panic_write)
+ return 0;
+
ret = spi_nor_prep(nor);
if (ret)
return ret;
@@ -1414,6 +1444,9 @@ static int spi_nor_prep_and_lock_pe(struct spi_nor *nor, loff_t start, size_t le
static void spi_nor_unlock_and_unprep_pe(struct spi_nor *nor, loff_t start, size_t len)
{
+ if (nor->mtd.oops_panic_write)
+ return;
+
if (!spi_nor_use_parallel_locking(nor)) {
mutex_unlock(&nor->lock);
} else {
@@ -1472,6 +1505,9 @@ static int spi_nor_prep_and_lock_rd(struct spi_nor *nor, loff_t start, size_t le
{
int ret;
+ if (nor->mtd.oops_panic_write)
+ return 0;
+
ret = spi_nor_prep(nor);
if (ret)
return ret;
@@ -1487,6 +1523,9 @@ static int spi_nor_prep_and_lock_rd(struct spi_nor *nor, loff_t start, size_t le
static void spi_nor_unlock_and_unprep_rd(struct spi_nor *nor, loff_t start, size_t len)
{
+ if (nor->mtd.oops_panic_write)
+ return;
+
if (!spi_nor_use_parallel_locking(nor)) {
mutex_unlock(&nor->lock);
} else {
@@ -2203,9 +2242,20 @@ static int spi_nor_octal_dtr_write(struct spi_nor *nor, loff_t to, size_t len,
if (IS_ALIGNED(to, 2) && IS_ALIGNED(len, 2))
return spi_nor_write_data(nor, to, len, buf);
- tmp_buf = kmalloc(nor->params->page_size, GFP_KERNEL);
- if (!tmp_buf)
- return -ENOMEM;
+ if (nor->mtd.oops_panic_write) {
+ /*
+ * The kernel is panicking; kmalloc() is not safe. Shamelessly
+ * reuse the 'bouncebuf' buffer, which is the size we want, and
+ * won't be reused anymore at this point (the first two bytes
+ * might be overwritten, but only after we transferred them, so
+ * it's safe).
+ */
+ tmp_buf = nor->bouncebuf;
+ } else {
+ tmp_buf = kmalloc(nor->params->page_size, GFP_KERNEL);
+ if (!tmp_buf)
+ return -ENOMEM;
+ }
memset(tmp_buf, 0xff, nor->params->page_size);
@@ -2243,7 +2293,8 @@ static int spi_nor_octal_dtr_write(struct spi_nor *nor, loff_t to, size_t len,
ret -= end - (to + len);
out:
- kfree(tmp_buf);
+ if (!nor->mtd.oops_panic_write)
+ kfree(tmp_buf);
return ret;
}
@@ -3385,6 +3436,26 @@ static void spi_nor_soft_reset(struct spi_nor *nor)
usleep_range(SPI_NOR_SRST_SLEEP_MIN, SPI_NOR_SRST_SLEEP_MAX);
}
+static int spi_nor_panic_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct spi_nor *nor = mtd_to_spi_nor(mtd);
+
+ /*
+ * At this point preemption and local interrupts are disabled, so we
+ * can't get the lock if it's taken.
+ */
+ if (mutex_is_locked(&nor->lock))
+ return -EPERM;
+
+ if (spi_nor_use_parallel_locking(nor) &&
+ (nor->rww.ongoing_io || nor->rww.ongoing_rd || nor->rww.ongoing_pe)) {
+ return -EPERM;
+ }
+
+ return spi_nor_write(mtd, to, len, retlen, buf);
+}
+
/* mtd suspend handler */
static int spi_nor_suspend(struct mtd_info *mtd)
{
@@ -3588,8 +3659,13 @@ static int spi_nor_set_mtd_info(struct spi_nor *nor)
mtd->size = nor->params->size;
mtd->_read = spi_nor_read;
/* Might be already set by some SST flashes. */
- if (!mtd->_write)
+ if (!mtd->_write) {
mtd->_write = spi_nor_write;
+ if (nor->spimem &&
+ spi_mem_controller_is_capable(nor->spimem->spi->controller, panic_write)) {
+ mtd->_panic_write = spi_nor_panic_write;
+ }
+ }
mtd->_suspend = spi_nor_suspend;
mtd->_resume = spi_nor_resume;
mtd->_get_device = spi_nor_get_device;
--
2.47.3
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH v4 4/7] spi: cadence-xspi: Clear "SDMA ready" IRQ flag after polling
2026-10-16 10:29 ` Paul Cercueil
@ 2026-10-16 10:29 ` Paul Cercueil
-1 siblings, 0 replies; 20+ messages in thread
From: Paul Cercueil @ 2026-10-16 10:29 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, linux-kernel, linux-spi, Bharat Bhushan,
Sunil Kovvuri Goutham, Tanmay Jagdale, stable, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
When polling for the "SDMA ready" event, once the corresponding bit is
set in the CDNS_XSPI_SDMA_TRIGGER we should reset it to zero. Otherwise,
any subsequent call to cdns_xspi_is_sdma_ready() will just return early.
Fixes: 931e389ded0f ("spi: cadence: Add Marvell xfer operation support")
Cc: stable@vger.kernel.org
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: New patch
v3-v4: No change
drivers/spi/spi-cadence-xspi.c | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
index 39c868a5b171..095bf640b379 100644
--- a/drivers/spi/spi-cadence-xspi.c
+++ b/drivers/spi/spi-cadence-xspi.c
@@ -1068,12 +1068,20 @@ static bool cdns_xspi_is_sdma_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
{
u32 ctrl_stat;
- return !readl_relaxed_poll_timeout
- (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
- ctrl_stat,
- (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
- sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
- sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0);
+ if (readl_relaxed_poll_timeout
+ (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
+ ctrl_stat,
+ (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
+ sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
+ sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0)) {
+ return false;
+ }
+
+ /* Clear the flag */
+ writel(CDNS_XSPI_SDMA_TRIGGER,
+ cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG);
+
+ return true;
}
static int cdns_xspi_transfer_one_message_b0(struct spi_controller *controller,
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v4 4/7] spi: cadence-xspi: Clear "SDMA ready" IRQ flag after polling
@ 2026-10-16 10:29 ` Paul Cercueil
0 siblings, 0 replies; 20+ messages in thread
From: Paul Cercueil @ 2026-10-16 10:29 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, linux-kernel, linux-spi, Bharat Bhushan,
Sunil Kovvuri Goutham, Tanmay Jagdale, stable, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
When polling for the "SDMA ready" event, once the corresponding bit is
set in the CDNS_XSPI_SDMA_TRIGGER we should reset it to zero. Otherwise,
any subsequent call to cdns_xspi_is_sdma_ready() will just return early.
Fixes: 931e389ded0f ("spi: cadence: Add Marvell xfer operation support")
Cc: stable@vger.kernel.org
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: New patch
v3-v4: No change
drivers/spi/spi-cadence-xspi.c | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
index 39c868a5b171..095bf640b379 100644
--- a/drivers/spi/spi-cadence-xspi.c
+++ b/drivers/spi/spi-cadence-xspi.c
@@ -1068,12 +1068,20 @@ static bool cdns_xspi_is_sdma_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
{
u32 ctrl_stat;
- return !readl_relaxed_poll_timeout
- (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
- ctrl_stat,
- (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
- sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
- sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0);
+ if (readl_relaxed_poll_timeout
+ (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
+ ctrl_stat,
+ (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
+ sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
+ sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0)) {
+ return false;
+ }
+
+ /* Clear the flag */
+ writel(CDNS_XSPI_SDMA_TRIGGER,
+ cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG);
+
+ return true;
}
static int cdns_xspi_transfer_one_message_b0(struct spi_controller *controller,
--
2.47.3
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v4 4/7] spi: cadence-xspi: Clear "SDMA ready" IRQ flag after polling
2026-10-16 10:29 ` Paul Cercueil
(?)
@ 2026-10-16 10:42 ` sashiko-bot
-1 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-10-16 10:42 UTC (permalink / raw)
To: Paul Cercueil, Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, Bharat Bhushan, Sunil Kovvuri Goutham, Tanmay Jagdale
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] spi: shared IRQ handler steals W1C polling bits causing spurious timeouts
--
commit b280fd3a1279873016d8fdc9d7bc4176c43d76de
Author: Tanmay Jagdale <tanmay@marvell.com>
spi: cadence-xspi: Clear "SDMA ready" IRQ flag after polling
When polling for the "SDMA ready" event, once the corresponding bit is
set in the CDNS_XSPI_SDMA_TRIGGER we should reset it to zero. Otherwise,
any subsequent call to cdns_xspi_is_sdma_ready() will just return early.
> diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
> index 39c868a5b171c..095bf640b379a 100644
> --- a/drivers/spi/spi-cadence-xspi.c
> +++ b/drivers/spi/spi-cadence-xspi.c
> @@ -1068,12 +1068,20 @@ static bool cdns_xspi_is_sdma_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
> {
> u32 ctrl_stat;
>
> - return !readl_relaxed_poll_timeout
> - (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
> - ctrl_stat,
> - (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
> - sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
> - sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0);
> + if (readl_relaxed_poll_timeout
> + (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
> + ctrl_stat,
> + (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
> + sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
> + sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0)) {
[Severity: High]
Can the shared IRQ handler steal the W1C bits being polled here, causing a
spurious timeout?
Because cdns_xspi_probe() registers cdns_xspi_irq_handler() with the
IRQF_SHARED flag, an interrupt from another device on the same line could
trigger cdns_xspi_irq_handler() during this polling loop.
If that occurs, the handler executes the following:
drivers/spi/spi-cadence-xspi.c:cdns_xspi_irq_handler() {
...
irq_status = readl(cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG);
writel(irq_status, cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG);
...
}
This reads CDNS_XSPI_INTR_STATUS_REG and writes the exact value back, clearing
any active Write-1-to-Clear (W1C) bits. If CDNS_XSPI_SDMA_TRIGGER is set, the
IRQ handler would clear it before this polling loop observes it, leading to a
timeout and failing the SPI transfer.
> + return false;
> + }
> +
> + /* Clear the flag */
> + writel(CDNS_XSPI_SDMA_TRIGGER,
> + cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG);
> +
> + return true;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261016102917.171095-1-paul.cercueil@bootlin.com?part=4
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v4 5/7] spi: cadence-xspi: Add irq-less support
2026-10-16 10:29 ` Paul Cercueil
@ 2026-10-16 10:29 ` Paul Cercueil
-1 siblings, 0 replies; 20+ messages in thread
From: Paul Cercueil @ 2026-10-16 10:29 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, linux-kernel, linux-spi, Bharat Bhushan,
Sunil Kovvuri Goutham, Tanmay Jagdale, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
Make the interrupt line optional. If not provided, the driver will
instead resort to polling registers.
This can be useful in itself, but will be used later to support write on
kernel panic.
Note the cdns_xspi_is_{sdma,stig}_ready() seem to have moved for no
reason; they were actually moved out of a conditional compilation block
(#ifdef CONFIG_64BIT), as they are needed in conf-independent paths now.
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: Make sure we only enable interrupts if not using polling mode
v3-v4: No change
drivers/spi/spi-cadence-xspi.c | 104 ++++++++++++++++++---------------
1 file changed, 58 insertions(+), 46 deletions(-)
diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
index 095bf640b379..2a2a40bf0a4c 100644
--- a/drivers/spi/spi-cadence-xspi.c
+++ b/drivers/spi/spi-cadence-xspi.c
@@ -365,6 +365,38 @@ static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_dev *cdns_xspi)
100, 1000);
}
+static bool cdns_xspi_is_stig_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
+{
+ u32 ctrl_stat;
+
+ return !readl_relaxed_poll_timeout
+ (cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
+ ctrl_stat,
+ ((ctrl_stat & BIT(3)) == 0),
+ sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
+ sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0);
+}
+
+static bool cdns_xspi_is_sdma_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
+{
+ u32 ctrl_stat;
+
+ if (readl_relaxed_poll_timeout
+ (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
+ ctrl_stat,
+ (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
+ sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
+ sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0)) {
+ return false;
+ }
+
+ /* Clear the flag */
+ writel(CDNS_XSPI_SDMA_TRIGGER,
+ cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG);
+
+ return true;
+}
+
static void cdns_xspi_trigger_command(struct cdns_xspi_dev *cdns_xspi,
u32 cmd_regs[6])
{
@@ -532,7 +564,8 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
writel(FIELD_PREP(CDNS_XSPI_CTRL_WORK_MODE, CDNS_XSPI_WORK_MODE_STIG),
cdns_xspi->iobase + CDNS_XSPI_CTRL_CONFIG_REG);
- cdns_xspi->set_interrupts_handler(cdns_xspi, true);
+ if (cdns_xspi->irq >= 0)
+ cdns_xspi->set_interrupts_handler(cdns_xspi, true);
cdns_xspi->sdma_error = false;
memset(cmd_regs, 0, sizeof(cmd_regs));
@@ -562,16 +595,25 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
cdns_xspi_trigger_command(cdns_xspi, cmd_regs);
- wait_for_completion(&cdns_xspi->sdma_complete);
- if (cdns_xspi->sdma_error) {
- cdns_xspi->set_interrupts_handler(cdns_xspi, false);
+ if (cdns_xspi->irq >= 0) {
+ wait_for_completion(&cdns_xspi->sdma_complete);
+ if (cdns_xspi->sdma_error) {
+ cdns_xspi->set_interrupts_handler(cdns_xspi, false);
+ return -EIO;
+ }
+ } else if (!cdns_xspi_is_sdma_ready(cdns_xspi, true)) {
return -EIO;
}
+
cdns_xspi->sdma_handler(cdns_xspi);
}
- wait_for_completion(&cdns_xspi->cmd_complete);
- cdns_xspi->set_interrupts_handler(cdns_xspi, false);
+ if (cdns_xspi->irq >= 0) {
+ wait_for_completion(&cdns_xspi->cmd_complete);
+ cdns_xspi->set_interrupts_handler(cdns_xspi, false);
+ } else if (!cdns_xspi_is_stig_ready(cdns_xspi, true)) {
+ return -EIO;
+ }
cmd_status = cdns_xspi_check_command_status(cdns_xspi);
if (cmd_status)
@@ -1052,38 +1094,6 @@ static int cdns_xspi_prepare_transfer(int cs, int dir, int len, u32 *cmd_regs)
return 0;
}
-static bool cdns_xspi_is_stig_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
-{
- u32 ctrl_stat;
-
- return !readl_relaxed_poll_timeout
- (cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
- ctrl_stat,
- ((ctrl_stat & BIT(3)) == 0),
- sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
- sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0);
-}
-
-static bool cdns_xspi_is_sdma_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
-{
- u32 ctrl_stat;
-
- if (readl_relaxed_poll_timeout
- (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
- ctrl_stat,
- (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
- sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
- sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0)) {
- return false;
- }
-
- /* Clear the flag */
- writel(CDNS_XSPI_SDMA_TRIGGER,
- cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG);
-
- return true;
-}
-
static int cdns_xspi_transfer_one_message_b0(struct spi_controller *controller,
struct spi_message *m)
{
@@ -1276,15 +1286,17 @@ static int cdns_xspi_probe(struct platform_device *pdev)
}
#endif
- cdns_xspi->irq = platform_get_irq(pdev, 0);
- if (cdns_xspi->irq < 0)
- return -ENXIO;
+ cdns_xspi->irq = platform_get_irq_optional(pdev, 0);
+ if (cdns_xspi->irq < 0 && cdns_xspi->irq != -ENXIO)
+ return cdns_xspi->irq;
- ret = devm_request_irq(dev, cdns_xspi->irq, cdns_xspi_irq_handler,
- IRQF_SHARED, pdev->name, cdns_xspi);
- if (ret) {
- dev_err(dev, "Failed to request IRQ: %d\n", cdns_xspi->irq);
- return ret;
+ if (cdns_xspi->irq >= 0) {
+ ret = devm_request_irq(dev, cdns_xspi->irq, cdns_xspi_irq_handler,
+ IRQF_SHARED, pdev->name, cdns_xspi);
+ if (ret) {
+ dev_err(dev, "Failed to request IRQ: %d\n", cdns_xspi->irq);
+ return ret;
+ }
}
#ifdef CONFIG_64BIT
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v4 5/7] spi: cadence-xspi: Add irq-less support
@ 2026-10-16 10:29 ` Paul Cercueil
0 siblings, 0 replies; 20+ messages in thread
From: Paul Cercueil @ 2026-10-16 10:29 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, linux-kernel, linux-spi, Bharat Bhushan,
Sunil Kovvuri Goutham, Tanmay Jagdale, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
Make the interrupt line optional. If not provided, the driver will
instead resort to polling registers.
This can be useful in itself, but will be used later to support write on
kernel panic.
Note the cdns_xspi_is_{sdma,stig}_ready() seem to have moved for no
reason; they were actually moved out of a conditional compilation block
(#ifdef CONFIG_64BIT), as they are needed in conf-independent paths now.
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: Make sure we only enable interrupts if not using polling mode
v3-v4: No change
drivers/spi/spi-cadence-xspi.c | 104 ++++++++++++++++++---------------
1 file changed, 58 insertions(+), 46 deletions(-)
diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
index 095bf640b379..2a2a40bf0a4c 100644
--- a/drivers/spi/spi-cadence-xspi.c
+++ b/drivers/spi/spi-cadence-xspi.c
@@ -365,6 +365,38 @@ static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_dev *cdns_xspi)
100, 1000);
}
+static bool cdns_xspi_is_stig_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
+{
+ u32 ctrl_stat;
+
+ return !readl_relaxed_poll_timeout
+ (cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
+ ctrl_stat,
+ ((ctrl_stat & BIT(3)) == 0),
+ sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
+ sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0);
+}
+
+static bool cdns_xspi_is_sdma_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
+{
+ u32 ctrl_stat;
+
+ if (readl_relaxed_poll_timeout
+ (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
+ ctrl_stat,
+ (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
+ sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
+ sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0)) {
+ return false;
+ }
+
+ /* Clear the flag */
+ writel(CDNS_XSPI_SDMA_TRIGGER,
+ cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG);
+
+ return true;
+}
+
static void cdns_xspi_trigger_command(struct cdns_xspi_dev *cdns_xspi,
u32 cmd_regs[6])
{
@@ -532,7 +564,8 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
writel(FIELD_PREP(CDNS_XSPI_CTRL_WORK_MODE, CDNS_XSPI_WORK_MODE_STIG),
cdns_xspi->iobase + CDNS_XSPI_CTRL_CONFIG_REG);
- cdns_xspi->set_interrupts_handler(cdns_xspi, true);
+ if (cdns_xspi->irq >= 0)
+ cdns_xspi->set_interrupts_handler(cdns_xspi, true);
cdns_xspi->sdma_error = false;
memset(cmd_regs, 0, sizeof(cmd_regs));
@@ -562,16 +595,25 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
cdns_xspi_trigger_command(cdns_xspi, cmd_regs);
- wait_for_completion(&cdns_xspi->sdma_complete);
- if (cdns_xspi->sdma_error) {
- cdns_xspi->set_interrupts_handler(cdns_xspi, false);
+ if (cdns_xspi->irq >= 0) {
+ wait_for_completion(&cdns_xspi->sdma_complete);
+ if (cdns_xspi->sdma_error) {
+ cdns_xspi->set_interrupts_handler(cdns_xspi, false);
+ return -EIO;
+ }
+ } else if (!cdns_xspi_is_sdma_ready(cdns_xspi, true)) {
return -EIO;
}
+
cdns_xspi->sdma_handler(cdns_xspi);
}
- wait_for_completion(&cdns_xspi->cmd_complete);
- cdns_xspi->set_interrupts_handler(cdns_xspi, false);
+ if (cdns_xspi->irq >= 0) {
+ wait_for_completion(&cdns_xspi->cmd_complete);
+ cdns_xspi->set_interrupts_handler(cdns_xspi, false);
+ } else if (!cdns_xspi_is_stig_ready(cdns_xspi, true)) {
+ return -EIO;
+ }
cmd_status = cdns_xspi_check_command_status(cdns_xspi);
if (cmd_status)
@@ -1052,38 +1094,6 @@ static int cdns_xspi_prepare_transfer(int cs, int dir, int len, u32 *cmd_regs)
return 0;
}
-static bool cdns_xspi_is_stig_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
-{
- u32 ctrl_stat;
-
- return !readl_relaxed_poll_timeout
- (cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
- ctrl_stat,
- ((ctrl_stat & BIT(3)) == 0),
- sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
- sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0);
-}
-
-static bool cdns_xspi_is_sdma_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
-{
- u32 ctrl_stat;
-
- if (readl_relaxed_poll_timeout
- (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
- ctrl_stat,
- (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
- sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
- sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0)) {
- return false;
- }
-
- /* Clear the flag */
- writel(CDNS_XSPI_SDMA_TRIGGER,
- cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG);
-
- return true;
-}
-
static int cdns_xspi_transfer_one_message_b0(struct spi_controller *controller,
struct spi_message *m)
{
@@ -1276,15 +1286,17 @@ static int cdns_xspi_probe(struct platform_device *pdev)
}
#endif
- cdns_xspi->irq = platform_get_irq(pdev, 0);
- if (cdns_xspi->irq < 0)
- return -ENXIO;
+ cdns_xspi->irq = platform_get_irq_optional(pdev, 0);
+ if (cdns_xspi->irq < 0 && cdns_xspi->irq != -ENXIO)
+ return cdns_xspi->irq;
- ret = devm_request_irq(dev, cdns_xspi->irq, cdns_xspi_irq_handler,
- IRQF_SHARED, pdev->name, cdns_xspi);
- if (ret) {
- dev_err(dev, "Failed to request IRQ: %d\n", cdns_xspi->irq);
- return ret;
+ if (cdns_xspi->irq >= 0) {
+ ret = devm_request_irq(dev, cdns_xspi->irq, cdns_xspi_irq_handler,
+ IRQF_SHARED, pdev->name, cdns_xspi);
+ if (ret) {
+ dev_err(dev, "Failed to request IRQ: %d\n", cdns_xspi->irq);
+ return ret;
+ }
}
#ifdef CONFIG_64BIT
--
2.47.3
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v4 5/7] spi: cadence-xspi: Add irq-less support
2026-10-16 10:29 ` Paul Cercueil
(?)
@ 2026-10-16 10:45 ` sashiko-bot
-1 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-10-16 10:45 UTC (permalink / raw)
To: Paul Cercueil, Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, Bharat Bhushan, Sunil Kovvuri Goutham, Tanmay Jagdale
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] spi: cadence-xspi: unbounded wait for completion on hardware interrupts
- [High] spi: cadence-xspi: missing reinit_completion() causes completion state leak
- [High] spi: cadence-xspi: shared IRQ handler steals status bits breaking polling
- [High] spi: cadence-xspi: SDMA error ignored and uncleared in polling mode
- [High] spi: cadence-xspi: 1ms poll timeout is too short for large transfers
--
commit fb747b04ad9d770849280630aa3b2694cba31fbe
Author: Tanmay Jagdale <tanmay@marvell.com>
spi: cadence-xspi: Add irq-less support
This patch makes the interrupt line optional. If an interrupt is not provided,
the driver will fall back to polling the controller registers for completion.
> diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
> index 095bf640b379a..2a2a40bf0a4c4 100644
> --- a/drivers/spi/spi-cadence-xspi.c
> +++ b/drivers/spi/spi-cadence-xspi.c
> @@ -365,6 +365,38 @@ static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_dev *cdns_xspi)
> 100, 1000);
> }
>
> +static bool cdns_xspi_is_stig_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
> +{
> + u32 ctrl_stat;
> +
> + return !readl_relaxed_poll_timeout
> + (cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
> + ctrl_stat,
> + ((ctrl_stat & BIT(3)) == 0),
> + sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
> + sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0);
> +}
[Severity: High]
Is MRVL_XSPI_POLL_TIMEOUT_US (1ms) long enough for large transfers?
When irq < 0 or during a panic write, cdns_xspi_send_stig_command() uses
this function to poll. The driver allows transfers up to sdmasize (often
64KB), which can take ~21ms at 25MHz. The 1ms timeout will systematically
fail these larger transfers, causing hardware state corruption.
> +
> +static bool cdns_xspi_is_sdma_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
> +{
> + u32 ctrl_stat;
> +
> + if (readl_relaxed_poll_timeout
> + (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
> + ctrl_stat,
> + (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
> + sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
> + sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0)) {
> + return false;
> + }
[Severity: High]
If an SDMA transaction encounters an error while in polling mode,
will this loop ignore CDNS_XSPI_SDMA_ERROR?
If CDNS_XSPI_SDMA_ERROR is asserted alongside CDNS_XSPI_SDMA_TRIGGER, the
function returns true despite the error. If only the error is asserted,
it will wait the full timeout and return false, but it never clears the W1C
CDNS_XSPI_SDMA_ERROR bit, permanently wedging the DMA hardware state machine.
> +
> + /* Clear the flag */
> + writel(CDNS_XSPI_SDMA_TRIGGER,
> + cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG);
[Severity: High]
Could the shared IRQ handler steal the trigger bit from this polling loop?
In cdns_xspi_transfer_one_message_b0(), the driver performs normal SPI
transfers by polling cdns_xspi_is_sdma_ready(). If another device sharing
the same IRQ line fires an interrupt, cdns_xspi_irq_handler() will read
CDNS_XSPI_INTR_STATUS_REG and unconditionally write it back, clearing all
pending bits. This can cause the polling loop to time out and the transfer
to incorrectly fail with -EIO.
> +
> + return true;
> +}
[ ... ]
> @@ -562,16 +595,25 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
>
> cdns_xspi_trigger_command(cdns_xspi, cmd_regs);
>
> - wait_for_completion(&cdns_xspi->sdma_complete);
> - if (cdns_xspi->sdma_error) {
> - cdns_xspi->set_interrupts_handler(cdns_xspi, false);
> + if (cdns_xspi->irq >= 0) {
> + wait_for_completion(&cdns_xspi->sdma_complete);
[Severity: High]
This problem wasn't introduced by this patch, but does the completion
state need to be reinitialized via reinit_completion() before the hardware
command is triggered?
If an error occurs or a spurious interrupt fires, cdns_xspi_irq_handler()
could call complete() multiple times for cdns_xspi->sdma_complete. Without
reinit_completion(), leftover completion tokens will cause subsequent
wait_for_completion() calls to return immediately while the hardware is
still transferring data, leading to memory and state corruption.
[Severity: High]
This problem wasn't introduced by this patch, but does this wait need a
timeout?
If the hardware fails to generate the interrupt, the calling thread will
hang indefinitely in TASK_UNINTERRUPTIBLE, creating a Denial of Service
condition.
> + if (cdns_xspi->sdma_error) {
> + cdns_xspi->set_interrupts_handler(cdns_xspi, false);
> + return -EIO;
> + }
> + } else if (!cdns_xspi_is_sdma_ready(cdns_xspi, true)) {
> return -EIO;
> }
> +
> cdns_xspi->sdma_handler(cdns_xspi);
> }
>
> - wait_for_completion(&cdns_xspi->cmd_complete);
> - cdns_xspi->set_interrupts_handler(cdns_xspi, false);
> + if (cdns_xspi->irq >= 0) {
> + wait_for_completion(&cdns_xspi->cmd_complete);
[Severity: High]
This problem wasn't introduced by this patch, but should this wait also
have a timeout?
Just like with sdma_complete, if the hardware fails to generate the STIG
completion interrupt, the calling thread will hang indefinitely.
> + cdns_xspi->set_interrupts_handler(cdns_xspi, false);
> + } else if (!cdns_xspi_is_stig_ready(cdns_xspi, true)) {
> + return -EIO;
> + }
>
> cmd_status = cdns_xspi_check_command_status(cdns_xspi);
> if (cmd_status)
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261016102917.171095-1-paul.cercueil@bootlin.com?part=5
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v4 6/7] spi: cadence-xspi: Add support for panic writes
2026-10-16 10:29 ` Paul Cercueil
@ 2026-10-16 10:29 ` Paul Cercueil
-1 siblings, 0 replies; 20+ messages in thread
From: Paul Cercueil @ 2026-10-16 10:29 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, linux-kernel, linux-spi, Bharat Bhushan,
Sunil Kovvuri Goutham, Tanmay Jagdale, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
Advertise support for panic writes. When a memory op is received with
the 'panic' flag set, the driver will then work without relying on
interrupts and without sleeping.
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: - Don't enable interrupts if doing a panic write
- Use a new cdns_readl_poll() macro that will resolve to calling
readl_relaxed_poll_timeout_atomic() when doing a panic write, or
readl_relaxed_poll_timeout() otherwise.
- Drop cdns_xspi->panic_write. We don't actually need to check it in
cdns_xspi_is_dll_locked(), because this code is never called in
the kernel panic path.
v3-v4: No change
drivers/spi/spi-cadence-xspi.c | 52 ++++++++++++++++++----------------
1 file changed, 27 insertions(+), 25 deletions(-)
diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
index 2a2a40bf0a4c..a7fd715c45d5 100644
--- a/drivers/spi/spi-cadence-xspi.c
+++ b/drivers/spi/spi-cadence-xspi.c
@@ -287,6 +287,10 @@
#define GENERIC_CMD_REG_3_NEEDED(len) (len > 7)
#define GENERIC_CMD_REG_2_NEEDED(len) (len > 3)
+#define cdns_readl_poll(addr, val, cond, delay_us, timeout_us, can_sleep) \
+ ((can_sleep) ? readl_relaxed_poll_timeout(addr, val, cond, delay_us, timeout_us) \
+ : readl_relaxed_poll_timeout_atomic(addr, val, cond, delay_us, timeout_us))
+
enum cdns_xspi_stig_instr_type {
CDNS_XSPI_STIG_INSTR_TYPE_0,
CDNS_XSPI_STIG_INSTR_TYPE_1,
@@ -353,40 +357,33 @@ struct cdns_xspi_dev {
int current_xfer_qword;
};
-static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_dev *cdns_xspi)
+static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_dev *cdns_xspi, bool sleep)
{
u32 ctrl_stat;
- return readl_relaxed_poll_timeout(cdns_xspi->iobase +
- CDNS_XSPI_CTRL_STATUS_REG,
- ctrl_stat,
- ((ctrl_stat &
- CDNS_XSPI_CTRL_BUSY) == 0),
- 100, 1000);
+ return cdns_readl_poll(cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
+ ctrl_stat, !(ctrl_stat & CDNS_XSPI_CTRL_BUSY),
+ 100, 1000, sleep);
}
static bool cdns_xspi_is_stig_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
{
u32 ctrl_stat;
- return !readl_relaxed_poll_timeout
- (cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
- ctrl_stat,
- ((ctrl_stat & BIT(3)) == 0),
- sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
- sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0);
+ return !cdns_readl_poll(cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
+ ctrl_stat, !(ctrl_stat & BIT(3)),
+ MRVL_XSPI_POLL_DELAY_US,
+ MRVL_XSPI_POLL_TIMEOUT_US, sleep);
}
static bool cdns_xspi_is_sdma_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
{
u32 ctrl_stat;
- if (readl_relaxed_poll_timeout
- (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
- ctrl_stat,
- (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
- sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
- sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0)) {
+ if (cdns_readl_poll(cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
+ ctrl_stat, (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
+ MRVL_XSPI_POLL_DELAY_US, MRVL_XSPI_POLL_TIMEOUT_US,
+ sleep)) {
return false;
}
@@ -557,14 +554,14 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
int ret;
int dummybytes = op->dummy.nbytes;
- ret = cdns_xspi_wait_for_controller_idle(cdns_xspi);
+ ret = cdns_xspi_wait_for_controller_idle(cdns_xspi, !op->panic_write);
if (ret < 0)
return -EIO;
writel(FIELD_PREP(CDNS_XSPI_CTRL_WORK_MODE, CDNS_XSPI_WORK_MODE_STIG),
cdns_xspi->iobase + CDNS_XSPI_CTRL_CONFIG_REG);
- if (cdns_xspi->irq >= 0)
+ if (cdns_xspi->irq >= 0 && !op->panic_write)
cdns_xspi->set_interrupts_handler(cdns_xspi, true);
cdns_xspi->sdma_error = false;
@@ -595,23 +592,23 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
cdns_xspi_trigger_command(cdns_xspi, cmd_regs);
- if (cdns_xspi->irq >= 0) {
+ if (cdns_xspi->irq >= 0 && !op->panic_write) {
wait_for_completion(&cdns_xspi->sdma_complete);
if (cdns_xspi->sdma_error) {
cdns_xspi->set_interrupts_handler(cdns_xspi, false);
return -EIO;
}
- } else if (!cdns_xspi_is_sdma_ready(cdns_xspi, true)) {
+ } else if (!cdns_xspi_is_sdma_ready(cdns_xspi, !op->panic_write)) {
return -EIO;
}
cdns_xspi->sdma_handler(cdns_xspi);
}
- if (cdns_xspi->irq >= 0) {
+ if (cdns_xspi->irq >= 0 && !op->panic_write) {
wait_for_completion(&cdns_xspi->cmd_complete);
cdns_xspi->set_interrupts_handler(cdns_xspi, false);
- } else if (!cdns_xspi_is_stig_ready(cdns_xspi, true)) {
+ } else if (!cdns_xspi_is_stig_ready(cdns_xspi, !op->panic_write)) {
return -EIO;
}
@@ -1195,6 +1192,10 @@ static int cdns_xspi_transfer_one_message_b0(struct spi_controller *controller,
}
#endif
+static const struct spi_controller_mem_caps cdns_xspi_mem_caps = {
+ .panic_write = true,
+};
+
static int cdns_xspi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -1315,6 +1316,7 @@ static int cdns_xspi_probe(struct platform_device *pdev)
}
host->num_chipselect = 1 << cdns_xspi->hw_num_banks;
+ host->mem_caps = &cdns_xspi_mem_caps;
ret = devm_spi_register_controller(dev, host);
if (ret) {
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v4 6/7] spi: cadence-xspi: Add support for panic writes
@ 2026-10-16 10:29 ` Paul Cercueil
0 siblings, 0 replies; 20+ messages in thread
From: Paul Cercueil @ 2026-10-16 10:29 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, linux-kernel, linux-spi, Bharat Bhushan,
Sunil Kovvuri Goutham, Tanmay Jagdale, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
Advertise support for panic writes. When a memory op is received with
the 'panic' flag set, the driver will then work without relying on
interrupts and without sleeping.
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: - Don't enable interrupts if doing a panic write
- Use a new cdns_readl_poll() macro that will resolve to calling
readl_relaxed_poll_timeout_atomic() when doing a panic write, or
readl_relaxed_poll_timeout() otherwise.
- Drop cdns_xspi->panic_write. We don't actually need to check it in
cdns_xspi_is_dll_locked(), because this code is never called in
the kernel panic path.
v3-v4: No change
drivers/spi/spi-cadence-xspi.c | 52 ++++++++++++++++++----------------
1 file changed, 27 insertions(+), 25 deletions(-)
diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
index 2a2a40bf0a4c..a7fd715c45d5 100644
--- a/drivers/spi/spi-cadence-xspi.c
+++ b/drivers/spi/spi-cadence-xspi.c
@@ -287,6 +287,10 @@
#define GENERIC_CMD_REG_3_NEEDED(len) (len > 7)
#define GENERIC_CMD_REG_2_NEEDED(len) (len > 3)
+#define cdns_readl_poll(addr, val, cond, delay_us, timeout_us, can_sleep) \
+ ((can_sleep) ? readl_relaxed_poll_timeout(addr, val, cond, delay_us, timeout_us) \
+ : readl_relaxed_poll_timeout_atomic(addr, val, cond, delay_us, timeout_us))
+
enum cdns_xspi_stig_instr_type {
CDNS_XSPI_STIG_INSTR_TYPE_0,
CDNS_XSPI_STIG_INSTR_TYPE_1,
@@ -353,40 +357,33 @@ struct cdns_xspi_dev {
int current_xfer_qword;
};
-static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_dev *cdns_xspi)
+static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_dev *cdns_xspi, bool sleep)
{
u32 ctrl_stat;
- return readl_relaxed_poll_timeout(cdns_xspi->iobase +
- CDNS_XSPI_CTRL_STATUS_REG,
- ctrl_stat,
- ((ctrl_stat &
- CDNS_XSPI_CTRL_BUSY) == 0),
- 100, 1000);
+ return cdns_readl_poll(cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
+ ctrl_stat, !(ctrl_stat & CDNS_XSPI_CTRL_BUSY),
+ 100, 1000, sleep);
}
static bool cdns_xspi_is_stig_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
{
u32 ctrl_stat;
- return !readl_relaxed_poll_timeout
- (cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
- ctrl_stat,
- ((ctrl_stat & BIT(3)) == 0),
- sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
- sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0);
+ return !cdns_readl_poll(cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
+ ctrl_stat, !(ctrl_stat & BIT(3)),
+ MRVL_XSPI_POLL_DELAY_US,
+ MRVL_XSPI_POLL_TIMEOUT_US, sleep);
}
static bool cdns_xspi_is_sdma_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
{
u32 ctrl_stat;
- if (readl_relaxed_poll_timeout
- (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
- ctrl_stat,
- (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
- sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
- sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0)) {
+ if (cdns_readl_poll(cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
+ ctrl_stat, (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
+ MRVL_XSPI_POLL_DELAY_US, MRVL_XSPI_POLL_TIMEOUT_US,
+ sleep)) {
return false;
}
@@ -557,14 +554,14 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
int ret;
int dummybytes = op->dummy.nbytes;
- ret = cdns_xspi_wait_for_controller_idle(cdns_xspi);
+ ret = cdns_xspi_wait_for_controller_idle(cdns_xspi, !op->panic_write);
if (ret < 0)
return -EIO;
writel(FIELD_PREP(CDNS_XSPI_CTRL_WORK_MODE, CDNS_XSPI_WORK_MODE_STIG),
cdns_xspi->iobase + CDNS_XSPI_CTRL_CONFIG_REG);
- if (cdns_xspi->irq >= 0)
+ if (cdns_xspi->irq >= 0 && !op->panic_write)
cdns_xspi->set_interrupts_handler(cdns_xspi, true);
cdns_xspi->sdma_error = false;
@@ -595,23 +592,23 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
cdns_xspi_trigger_command(cdns_xspi, cmd_regs);
- if (cdns_xspi->irq >= 0) {
+ if (cdns_xspi->irq >= 0 && !op->panic_write) {
wait_for_completion(&cdns_xspi->sdma_complete);
if (cdns_xspi->sdma_error) {
cdns_xspi->set_interrupts_handler(cdns_xspi, false);
return -EIO;
}
- } else if (!cdns_xspi_is_sdma_ready(cdns_xspi, true)) {
+ } else if (!cdns_xspi_is_sdma_ready(cdns_xspi, !op->panic_write)) {
return -EIO;
}
cdns_xspi->sdma_handler(cdns_xspi);
}
- if (cdns_xspi->irq >= 0) {
+ if (cdns_xspi->irq >= 0 && !op->panic_write) {
wait_for_completion(&cdns_xspi->cmd_complete);
cdns_xspi->set_interrupts_handler(cdns_xspi, false);
- } else if (!cdns_xspi_is_stig_ready(cdns_xspi, true)) {
+ } else if (!cdns_xspi_is_stig_ready(cdns_xspi, !op->panic_write)) {
return -EIO;
}
@@ -1195,6 +1192,10 @@ static int cdns_xspi_transfer_one_message_b0(struct spi_controller *controller,
}
#endif
+static const struct spi_controller_mem_caps cdns_xspi_mem_caps = {
+ .panic_write = true,
+};
+
static int cdns_xspi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -1315,6 +1316,7 @@ static int cdns_xspi_probe(struct platform_device *pdev)
}
host->num_chipselect = 1 << cdns_xspi->hw_num_banks;
+ host->mem_caps = &cdns_xspi_mem_caps;
ret = devm_spi_register_controller(dev, host);
if (ret) {
--
2.47.3
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v4 6/7] spi: cadence-xspi: Add support for panic writes
2026-10-16 10:29 ` Paul Cercueil
(?)
@ 2026-10-16 10:50 ` sashiko-bot
-1 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-10-16 10:50 UTC (permalink / raw)
To: Paul Cercueil, Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, Bharat Bhushan, Sunil Kovvuri Goutham, Tanmay Jagdale
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] spi: cadence-xspi: missing interrupt status clear causes premature SDMA readiness
- [High] spi: cadence-xspi: shared IRQ steals SDMA_TRIGGER from polling loop
- [High] spi: cadence-xspi: 1ms timeout causes panic writes to fail if controller is busy
- [High] spi: cadence-xspi: missing reinit_completion() allows premature completion
- [Medium] spi: cadence-xspi: SDMA polling ignores error status
--
commit 7f5b0e01fa6683f0eb21af13e7014a84bdbb381d
Author: Tanmay Jagdale <tanmay@marvell.com>
spi: cadence-xspi: Add support for panic writes
Advertise support for panic writes. When a memory op is received with
the 'panic' flag set, the driver will then work without relying on
interrupts and without sleeping.
> diff --git a/drivers/spi/spi-cadence-xspi.c b/drivers/spi/spi-cadence-xspi.c
> index 2a2a40bf0a4c4..a7fd715c45d5f 100644
> --- a/drivers/spi/spi-cadence-xspi.c
> +++ b/drivers/spi/spi-cadence-xspi.c
> @@ -353,40 +357,33 @@ struct cdns_xspi_dev {
> int current_xfer_qword;
> };
>
> -static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_dev *cdns_xspi)
> +static int cdns_xspi_wait_for_controller_idle(struct cdns_xspi_dev *cdns_xspi, bool sleep)
> {
> u32 ctrl_stat;
>
> - return readl_relaxed_poll_timeout(cdns_xspi->iobase +
> - CDNS_XSPI_CTRL_STATUS_REG,
> - ctrl_stat,
> - ((ctrl_stat &
> - CDNS_XSPI_CTRL_BUSY) == 0),
> - 100, 1000);
> + return cdns_readl_poll(cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
> + ctrl_stat, !(ctrl_stat & CDNS_XSPI_CTRL_BUSY),
> + 100, 1000, sleep);
[Severity: High]
Does this 1000us timeout cause panic writes to fail if the controller is busy?
If a kernel panic occurs while the controller is executing a large data
transfer, the hardware might still be busy finishing the interrupted transfer.
The 1ms timeout here will cause the function to fail with -ETIMEDOUT, instantly
aborting the panic write.
> }
>
> static bool cdns_xspi_is_stig_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
> {
> u32 ctrl_stat;
>
> - return !readl_relaxed_poll_timeout
> - (cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
> - ctrl_stat,
> - ((ctrl_stat & BIT(3)) == 0),
> - sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
> - sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0);
> + return !cdns_readl_poll(cdns_xspi->iobase + CDNS_XSPI_CTRL_STATUS_REG,
> + ctrl_stat, !(ctrl_stat & BIT(3)),
> + MRVL_XSPI_POLL_DELAY_US,
> + MRVL_XSPI_POLL_TIMEOUT_US, sleep);
[Severity: High]
Does this timeout also cause issues for panic writes?
STIG command completion also relies on a 1000us timeout (via
MRVL_XSPI_POLL_TIMEOUT_US), which might be insufficient for large data
transfers during a panic write.
> }
>
> static bool cdns_xspi_is_sdma_ready(struct cdns_xspi_dev *cdns_xspi, bool sleep)
> {
> u32 ctrl_stat;
>
> - if (readl_relaxed_poll_timeout
> - (cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
> - ctrl_stat,
> - (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
> - sleep ? MRVL_XSPI_POLL_DELAY_US : 0,
> - sleep ? MRVL_XSPI_POLL_TIMEOUT_US : 0)) {
> + if (cdns_readl_poll(cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
> + ctrl_stat, (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
> + MRVL_XSPI_POLL_DELAY_US, MRVL_XSPI_POLL_TIMEOUT_US,
> + sleep)) {
[Severity: High]
Does this polling loop rely on a hardware bit that could be stolen by the
shared IRQ handler?
If a concurrent interrupt from another device on the same shared IRQ line
invokes cdns_xspi_irq_handler(), it unconditionally clears the raw interrupt
status:
cdns_xspi_irq_handler()
irq_status = readl(cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG);
writel(irq_status, cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG);
This steals the SDMA_TRIGGER bit without verifying if interrupts were actually
enabled, which would cause this active polling loop to time out and fail the
transfer.
[Severity: Medium]
Does this SDMA polling ignore the error status?
This logic explicitly checks only for the trigger bit and ignores
CDNS_XSPI_SDMA_ERROR. If the hardware asserts only the error bit on failure,
the driver will needlessly wait for the full timeout instead of failing fast.
Additionally, if the hardware asserts both the trigger and error bits
simultaneously on a failure, the driver will match the trigger bit, return
true, and treat the failed transaction as successful, potentially leading to
silent data corruption.
> return false;
> }
[ ... ]
> @@ -557,14 +554,14 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
> int ret;
> int dummybytes = op->dummy.nbytes;
>
> - ret = cdns_xspi_wait_for_controller_idle(cdns_xspi);
> + ret = cdns_xspi_wait_for_controller_idle(cdns_xspi, !op->panic_write);
> if (ret < 0)
> return -EIO;
>
> writel(FIELD_PREP(CDNS_XSPI_CTRL_WORK_MODE, CDNS_XSPI_WORK_MODE_STIG),
> cdns_xspi->iobase + CDNS_XSPI_CTRL_CONFIG_REG);
>
> - if (cdns_xspi->irq >= 0)
> + if (cdns_xspi->irq >= 0 && !op->panic_write)
> cdns_xspi->set_interrupts_handler(cdns_xspi, true);
[Severity: High]
Does this miss clearing stale interrupt status bits left behind from an
interrupted command?
During a panic write, normal interrupt handling is bypassed. If a panic occurs
while a previous SPI command was executing, the CDNS_XSPI_INTR_STATUS_REG will
have stale flags set.
When the panic write issues a new command, cdns_xspi_is_sdma_ready() will
immediately read this stale flag, clear it, and return true.
This causes the driver to prematurely interact with the SDMA buffer before the
new command's SDMA phase is actually ready, which could corrupt the panic write
data.
> cdns_xspi->sdma_error = false;
>
> @@ -595,23 +592,23 @@ static int cdns_xspi_send_stig_command(struct cdns_xspi_dev *cdns_xspi,
>
> cdns_xspi_trigger_command(cdns_xspi, cmd_regs);
>
> - if (cdns_xspi->irq >= 0) {
> + if (cdns_xspi->irq >= 0 && !op->panic_write) {
> wait_for_completion(&cdns_xspi->sdma_complete);
> if (cdns_xspi->sdma_error) {
> cdns_xspi->set_interrupts_handler(cdns_xspi, false);
> return -EIO;
> }
[Severity: High]
Are we missing a reinit_completion() call here?
When this error path is taken, cmd_complete is not waited upon. If the hardware
subsequently asserts the STIG_DONE interrupt, cmd_complete is completed,
leaving the completion count at 1.
> - } else if (!cdns_xspi_is_sdma_ready(cdns_xspi, true)) {
> + } else if (!cdns_xspi_is_sdma_ready(cdns_xspi, !op->panic_write)) {
> return -EIO;
> }
>
> cdns_xspi->sdma_handler(cdns_xspi);
> }
>
> - if (cdns_xspi->irq >= 0) {
> + if (cdns_xspi->irq >= 0 && !op->panic_write) {
> wait_for_completion(&cdns_xspi->cmd_complete);
[Severity: High]
This wait will return immediately if a previous aborted command left a lingering
completion count, causing the new command to prematurely continue execution.
Should we call reinit_completion() before triggering commands?
> cdns_xspi->set_interrupts_handler(cdns_xspi, false);
> - } else if (!cdns_xspi_is_stig_ready(cdns_xspi, true)) {
> + } else if (!cdns_xspi_is_stig_ready(cdns_xspi, !op->panic_write)) {
> return -EIO;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261016102917.171095-1-paul.cercueil@bootlin.com?part=6
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v4 7/7] spi: dt-bindings: cadence: Make XSPI's IRQ line optional
2026-10-16 10:29 ` Paul Cercueil
@ 2026-10-16 10:29 ` Paul Cercueil
-1 siblings, 0 replies; 20+ messages in thread
From: Paul Cercueil @ 2026-10-16 10:29 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, linux-kernel, linux-spi, Bharat Bhushan,
Sunil Kovvuri Goutham, Tanmay Jagdale, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
The spi-cadence-xspi driver will happily work without an IRQ line
registered, which means that this IRQ line is optional.
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: New patch
v3-v4: No change
Documentation/devicetree/bindings/spi/cdns,xspi.yaml | 1 -
1 file changed, 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
index 38a5795589de..dd8d03c07e68 100644
--- a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
+++ b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
@@ -43,7 +43,6 @@ properties:
required:
- compatible
- reg
- - interrupts
allOf:
- $ref: spi-controller.yaml#
--
2.47.3
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH v4 7/7] spi: dt-bindings: cadence: Make XSPI's IRQ line optional
@ 2026-10-16 10:29 ` Paul Cercueil
0 siblings, 0 replies; 20+ messages in thread
From: Paul Cercueil @ 2026-10-16 10:29 UTC (permalink / raw)
To: Pratyush Yadav, Michael Walle, Takahiro Kuwano, Miquel Raynal,
Richard Weinberger, Vignesh Raghavendra, Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, linux-kernel, linux-spi, Bharat Bhushan,
Sunil Kovvuri Goutham, Tanmay Jagdale, Paul Cercueil
From: Tanmay Jagdale <tanmay@marvell.com>
The spi-cadence-xspi driver will happily work without an IRQ line
registered, which means that this IRQ line is optional.
Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
---
Notes:
v2: New patch
v3-v4: No change
Documentation/devicetree/bindings/spi/cdns,xspi.yaml | 1 -
1 file changed, 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
index 38a5795589de..dd8d03c07e68 100644
--- a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
+++ b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
@@ -43,7 +43,6 @@ properties:
required:
- compatible
- reg
- - interrupts
allOf:
- $ref: spi-controller.yaml#
--
2.47.3
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v4 7/7] spi: dt-bindings: cadence: Make XSPI's IRQ line optional
2026-10-16 10:29 ` Paul Cercueil
(?)
@ 2026-10-16 10:39 ` sashiko-bot
-1 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-10-16 10:39 UTC (permalink / raw)
To: Paul Cercueil, Pratyush Yadav, Michael Walle, Takahiro Kuwano,
Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Mark Brown
Cc: Kees Cook, Tony Luck, Guilherme G . Piccoli, Thomas Petazzoni,
linux-mtd, Bharat Bhushan, Sunil Kovvuri Goutham, Tanmay Jagdale
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] spi: cadence-xspi: polling mode silently ignores SDMA errors
--
commit 457f64897c53f7e99d4fa7392dbbec63291a9a97
Author: Tanmay Jagdale <tanmay@marvell.com>
spi: dt-bindings: cadence: Make XSPI's IRQ line optional
This commit modifies the Cadence XSPI device tree bindings to make the
interrupts property optional. It indicates that the spi-cadence-xspi
driver functions in polling mode when an IRQ is not provided.
> spi: dt-bindings: cadence: Make XSPI's IRQ line optional
>
> The spi-cadence-xspi driver will happily work without an IRQ line
> registered, which means that this IRQ line is optional.
[Severity: High]
Does the driver actually work correctly without an IRQ line?
Looking at cdns_xspi_is_sdma_ready() in drivers/spi/spi-cadence-xspi.c,
the polling loop checks for the trigger bit but fails to check or clear the
SDMA error bit:
drivers/spi/spi-cadence-xspi.c:cdns_xspi_is_sdma_ready() {
...
if (cdns_readl_poll(cdns_xspi->iobase + CDNS_XSPI_INTR_STATUS_REG,
ctrl_stat, (ctrl_stat & CDNS_XSPI_SDMA_TRIGGER),
MRVL_XSPI_POLL_DELAY_US, MRVL_XSPI_POLL_TIMEOUT_US,
sleep)) {
return false;
}
...
}
If a hardware SDMA transaction error occurs while the device is in polling
mode, the error is ignored. This could cause the driver to blindly proceed
or unnecessarily time out, potentially resulting in silent data corruption
during flash I/O.
Should the polling logic handle CDNS_XSPI_SDMA_ERROR before making the
interrupts property optional?
>
> Signed-off-by: Tanmay Jagdale <tanmay@marvell.com>
> Co-developed-by: Paul Cercueil <paul.cercueil@bootlin.com>
> Signed-off-by: Paul Cercueil <paul.cercueil@bootlin.com>
>
> diff --git a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
> index 38a5795589de2..dd8d03c07e688 100644
> --- a/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
> +++ b/Documentation/devicetree/bindings/spi/cdns,xspi.yaml
> @@ -43,7 +43,6 @@ properties:
> required:
> - compatible
> - reg
> - - interrupts
>
> allOf:
> - $ref: spi-controller.yaml#
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261016102917.171095-1-paul.cercueil@bootlin.com?part=7
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 20+ messages in thread