From: Weibin Liu <liuwb@xiaopeng.com>
To: broonie@kernel.org
Cc: thangaraj.s@microchip.com, linux-spi@vger.kernel.org,
linux-kernel@vger.kernel.org, stable@vger.kernel.org
Subject: [PATCH] spi: spi-pci1xxxx: quiesce DMA engines on transfer timeout
Date: Tue, 29 Sep 2026 16:11:56 +0800 [thread overview]
Message-ID: <20260929081156.42124-1-liuwb@xiaopeng.com> (raw)
If a DMA-mapped transfer times out waiting for DMA_TERM and neither DMA
abort interrupt arrived, pci1xxxx_spi_transfer_with_dma() returns
through the error label while both DMA engines are still enabled, the
done/abort interrupt status is still armed and the transfer state
(p->xfer, p->tx_sgl, p->rx_sgl) is still registered.
The ISRs keep firing afterwards: they reprogram the DMA engines from
p->tx_sgl / p->rx_sgl and signal completion through p->xfer and
p->spi_xfer_done, all of which the SPI core is free to unmap or reuse
once transfer_one() has returned, which is a use-after-free. A late
done interrupt can also start the next transfer queued behind the
failed one.
Stop both DMA engines, drain the pending done/abort interrupt status
and synchronize the DMA IRQs before dropping the in-progress state and
the transfer pointers, and re-enable the engines afterwards so that
subsequent transfers keep working. Also make the DMA ISRs ignore
interrupts while no transfer is in progress so that a stray MSI after
the cleanup can no longer act on the cleared state.
Fixes: 9538edeb72c9 ("spi: mchp-pci1xxxx: DMA support for copying data to and from SPI Buf")
Cc: stable@vger.kernel.org # 6.8+
Signed-off-by: Weibin Liu <liuwb@xiaopeng.com>
---
Reviewer notes:
- The quiesce sequence only runs on the DMA timeout branch, i.e. when
neither DMA_TERM nor the two abort interrupts arrived; all other
paths behave as before. Every path still passes through the error
label, which now additionally drops the stale p->xfer, p->tx_sgl and
p->rx_sgl pointers.
- Ordering matters: the done/abort status bits are drained under the
same spinlocks the ISRs use, then p->spi_xfer_in_progress is dropped
and the DMA IRQs are synchronized before the engines are turned back
on, so an ISR can never observe a half-cleared state. The ISRs
additionally return IRQ_NONE while no transfer is in progress, which
also keeps the interrupt accounting right for stray MSIs after the
cleanup.
- readl_poll_timeout() waits at most 1 ms per engine; it only runs
after the transfer has already timed out, so the added latency stays
on the failure path.
Tested on x86_64: with this patch applied the driver builds, loads and
unloads cleanly; no PCI1XXXX device is available to exercise the DMA
timeout path on hardware.
drivers/spi/spi-pci1xxxx.c | 56 ++++++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/drivers/spi/spi-pci1xxxx.c b/drivers/spi/spi-pci1xxxx.c
index af6ed7849..b296d7f6e 100644
--- a/drivers/spi/spi-pci1xxxx.c
+++ b/drivers/spi/spi-pci1xxxx.c
@@ -542,6 +542,7 @@ static int pci1xxxx_spi_transfer_with_dma(struct spi_controller *spi_ctlr,
struct pci1xxxx_spi_internal *p = spi_controller_get_devdata(spi_ctlr);
struct pci1xxxx_spi *par = p->parent;
dma_addr_t tx_dma_addr = 0;
+ unsigned long flags;
int ret = 0;
u32 regval;
@@ -618,12 +619,58 @@ static int pci1xxxx_spi_transfer_with_dma(struct spi_controller *spi_ctlr,
p->dma_aborted_wr = false;
ret = -ECANCELED;
}
+
+ /*
+ * A timed out transfer leaves the DMA engines running and
+ * the done/abort interrupt status armed with p->xfer,
+ * p->tx_sgl and p->rx_sgl still registered. Stop the
+ * engines, drain the pending status and synchronize the
+ * IRQs before dropping the transfer state, otherwise a
+ * late interrupt makes the ISRs reprogram the engines
+ * from or signal completion through memory which the SPI
+ * core is about to unmap or free.
+ */
+ writel(SPI_DMA_ENGINE_DIS,
+ par->dma_offset_bar + SPI_DMA_GLOBAL_WR_ENGINE_EN);
+ writel(SPI_DMA_ENGINE_DIS,
+ par->dma_offset_bar + SPI_DMA_GLOBAL_RD_ENGINE_EN);
+ if (readl_poll_timeout(par->dma_offset_bar +
+ SPI_DMA_GLOBAL_WR_ENGINE_EN, regval,
+ regval == 0x0, 0, USEC_PER_MSEC) ||
+ readl_poll_timeout(par->dma_offset_bar +
+ SPI_DMA_GLOBAL_RD_ENGINE_EN, regval,
+ regval == 0x0, 0, USEC_PER_MSEC))
+ dev_warn(&par->dev->dev,
+ "SPI DMA engines failed to stop\n");
+
+ spin_lock_irqsave(&par->dma_wr_reg_lock, flags);
+ writel((SPI_DMA_DONE_INT_MASK(p->hw_inst) |
+ SPI_DMA_ABORT_INT_MASK(p->hw_inst)),
+ par->dma_offset_bar + SPI_DMA_INTR_WR_CLR);
+ spin_unlock_irqrestore(&par->dma_wr_reg_lock, flags);
+ spin_lock_irqsave(&par->dma_rd_reg_lock, flags);
+ writel((SPI_DMA_DONE_INT_MASK(p->hw_inst) |
+ SPI_DMA_ABORT_INT_MASK(p->hw_inst)),
+ par->dma_offset_bar + SPI_DMA_INTR_RD_CLR);
+ spin_unlock_irqrestore(&par->dma_rd_reg_lock, flags);
+
+ p->spi_xfer_in_progress = false;
+ synchronize_irq(p->irq[1]);
+ synchronize_irq(p->irq[2]);
+
+ writel(SPI_DMA_ENGINE_EN,
+ par->dma_offset_bar + SPI_DMA_GLOBAL_WR_ENGINE_EN);
+ writel(SPI_DMA_ENGINE_EN,
+ par->dma_offset_bar + SPI_DMA_GLOBAL_RD_ENGINE_EN);
goto error;
}
ret = 0;
error:
p->spi_xfer_in_progress = false;
+ p->xfer = NULL;
+ p->tx_sgl = NULL;
+ p->rx_sgl = NULL;
return ret;
}
@@ -695,6 +742,9 @@ static irqreturn_t pci1xxxx_spi_isr_dma_rd(int irq, void *dev)
unsigned long flags;
u32 regval;
+ if (!p->spi_xfer_in_progress)
+ return IRQ_NONE;
+
/* Clear the DMA RD INT and start spi xfer*/
regval = readl(p->parent->dma_offset_bar + SPI_DMA_INTR_RD_STS);
if (regval) {
@@ -723,6 +773,9 @@ static irqreturn_t pci1xxxx_spi_isr_dma_wr(int irq, void *dev)
unsigned long flags;
u32 regval;
+ if (!p->spi_xfer_in_progress)
+ return IRQ_NONE;
+
/* Clear the DMA WR INT */
regval = readl(p->parent->dma_offset_bar + SPI_DMA_INTR_WR_STS);
if (regval) {
@@ -755,6 +808,9 @@ static irqreturn_t pci1xxxx_spi_isr_dma(int irq, void *dev)
irqreturn_t spi_int_fired = IRQ_NONE;
u32 regval;
+ if (!p->spi_xfer_in_progress)
+ return IRQ_NONE;
+
/* Clear the SPI GO_BIT Interrupt */
regval = readl(p->parent->reg_base + SPI_MST_EVENT_REG_OFFSET(p->hw_inst));
if (regval & SPI_INTR) {
base-commit: 72d3fcf802c45d00b300f25b848a93c3a2bd7c7e
prerequisite-patch-id: e48582f6ffe124b3806593af6e22b74682c8a83f
prerequisite-patch-id: 369f74b9a7ecde56141b13ec671f9200345a3bab
prerequisite-patch-id: cbf6f60473c80add5b2cddf22d142f35a4e3c834
prerequisite-patch-id: b35e6ec691cd7a99f1ed86137a0ec101a8510c31
--
2.50.1
next reply other threads:[~2026-09-29 8:12 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-29 8:11 Weibin Liu [this message]
2026-10-14 15:37 ` Mark Brown
2026-10-16 2:00 ` Weibin Liu
2026-10-16 2:00 ` [PATCH v2] " Weibin Liu
2026-10-16 10:27 ` Mark Brown
2026-10-16 11:22 ` Mark Brown
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260929081156.42124-1-liuwb@xiaopeng.com \
--to=liuwb@xiaopeng.com \
--cc=broonie@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-spi@vger.kernel.org \
--cc=stable@vger.kernel.org \
--cc=thangaraj.s@microchip.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®