From: Vishwaroop A <va@nvidia.com>
To: Thierry Reding <thierry.reding@kernel.org>,
Jon Hunter <jonathanh@nvidia.com>,
Mark Brown <broonie@kernel.org>
Cc: Vishwaroop A <va@nvidia.com>,
Laxman Dewangan <ldewangan@nvidia.com>,
Sowjanya Komatineni <skomatineni@nvidia.com>,
Breno Leitao <leitao@debian.org>,
Suresh Mangipudi <smangipudi@nvidia.com>,
"Krishna Yarlagadda" <kyarlagadda@nvidia.com>,
<linux-tegra@vger.kernel.org>, <linux-spi@vger.kernel.org>,
<linux-kernel@vger.kernel.org>
Subject: [PATCH v4 2/3] spi: tegra210-quad: Cache TRANS_STATUS in ISR for timeout handler
Date: Wed, 10 Jun 2026 06:24:01 +0000 [thread overview]
Message-ID: <2ed90a7d175f7967e621943b772115da655c477d.1781037385.git.va@nvidia.com> (raw)
In-Reply-To: <cover.1781037385.git.va@nvidia.com>
On heavily loaded systems the workqueue bottom half can be delayed
long enough for wait_for_completion_timeout() to expire before the
ISR's queued work actually runs. Reading QSPI_TRANS_STATUS directly
from the controller in the timeout handler races with both the
workqueue handler and the controller itself, and can mis-classify a
transfer that genuinely timed out as having "completed".
Cache the controller status captured by the hard IRQ before it is
acked, and let the timeout handler consume that cache:
- tegra_qspi_isr() reads QSPI_FIFO_STATUS and QSPI_TRANS_STATUS,
derives tx_status / rx_status, then publishes them via WRITE_ONCE()
and the trans_status cache via smp_store_release() before masking
and acking the controller IRQ.
- tegra_qspi_handle_timeout() consumes trans_status with a paired
smp_load_acquire(). The release/acquire pair guarantees that a
timeout handler which observes a non-zero trans_status also
observes the matching status_reg / tx_status / rx_status updates.
- tegra_qspi_setup_transfer_one() clears the cache with
smp_store_release() under the spinlock before unmasking the IRQ
for the new transfer, so a stale RDY bit from the previous
transfer cannot fool the timeout handler.
- If the cache is zero at timeout time the ISR genuinely never ran
(lost IRQ), so the handler falls back to a direct controller read
so we do not unconditionally surface a timeout when the hardware
has actually finished.
curr_xfer is re-checked under the spinlock so that a workqueue handler
which races with the timeout path cannot double-complete the transfer.
Signed-off-by: Vishwaroop A <va@nvidia.com>
---
drivers/spi/spi-tegra210-quad.c | 65 ++++++++++++++++++++++++++-------
1 file changed, 51 insertions(+), 14 deletions(-)
diff --git a/drivers/spi/spi-tegra210-quad.c b/drivers/spi/spi-tegra210-quad.c
index dd5d40e0dcbc..f0b15d13e433 100644
--- a/drivers/spi/spi-tegra210-quad.c
+++ b/drivers/spi/spi-tegra210-quad.c
@@ -214,6 +214,7 @@ struct tegra_qspi {
u32 tx_status;
u32 rx_status;
u32 status_reg;
+ u32 trans_status;
bool is_packed;
bool use_dma;
@@ -861,6 +862,13 @@ static u32 tegra_qspi_setup_transfer_one(struct spi_device *spi, struct spi_tran
tqspi->cur_rx_pos = 0;
tqspi->cur_tx_pos = 0;
tqspi->curr_xfer = t;
+ /*
+ * Pairs with smp_load_acquire() in tegra_qspi_handle_timeout().
+ * Clearing the cached trans_status before unmasking the IRQ for
+ * the new transfer prevents a stale RDY bit from the previous
+ * transfer fooling the timeout handler into a false recovery.
+ */
+ smp_store_release(&tqspi->trans_status, 0);
spin_unlock_irqrestore(&tqspi->lock, flags);
if (is_first_of_msg) {
@@ -1075,26 +1083,43 @@ static irqreturn_t handle_dma_based_xfer(struct tegra_qspi *tqspi);
*/
static int tegra_qspi_handle_timeout(struct tegra_qspi *tqspi)
{
+ unsigned long flags;
irqreturn_t ret;
u32 status;
- /* Check if hardware actually completed the transfer */
- status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS);
+ /*
+ * Pairs with smp_store_release() in tegra_qspi_isr(). If the ISR
+ * managed to run before wait_for_completion_timeout() expired, the
+ * cached value lets us classify the timeout without racing with a
+ * concurrent HW write to QSPI_TRANS_STATUS.
+ *
+ * A zero cache means the ISR never ran for this transfer (the IRQ
+ * was lost entirely). In that case fall back to reading the
+ * controller directly so we do not unconditionally surface a
+ * timeout when the hardware has actually finished.
+ */
+ status = smp_load_acquire(&tqspi->trans_status);
+ if (!status)
+ status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS);
+
if (!(status & QSPI_RDY))
return -ETIMEDOUT;
+ spin_lock_irqsave(&tqspi->lock, flags);
/*
- * Hardware completed but interrupt was lost/delayed. Manually
- * process the completion by calling the appropriate handler.
+ * ISR or workqueue may have already completed the transfer
+ * and cleared curr_xfer between the completion timeout and now.
*/
+ if (!tqspi->curr_xfer) {
+ spin_unlock_irqrestore(&tqspi->lock, flags);
+ return 0;
+ }
+
+ spin_unlock_irqrestore(&tqspi->lock, flags);
+
dev_warn_ratelimited(tqspi->dev,
"QSPI interrupt timeout, but transfer complete\n");
- /* Clear the transfer status */
- status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS);
- tegra_qspi_writel(tqspi, status, QSPI_TRANS_STATUS);
-
- /* Manually trigger completion handler */
if (!tqspi->is_curr_dma_xfer)
ret = handle_cpu_based_xfer(tqspi);
else
@@ -1658,6 +1683,7 @@ static void tegra_qspi_work_handler(struct work_struct *work)
static irqreturn_t tegra_qspi_isr(int irq, void *context_data)
{
struct tegra_qspi *tqspi = context_data;
+ u32 status_reg, trans_status;
if (!READ_ONCE(tqspi->curr_xfer)) {
tegra_qspi_mask_clear_irq(tqspi);
@@ -1665,16 +1691,27 @@ static irqreturn_t tegra_qspi_isr(int irq, void *context_data)
}
spin_lock(&tqspi->lock);
- tqspi->status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS);
+ status_reg = tegra_qspi_readl(tqspi, QSPI_FIFO_STATUS);
+ trans_status = tegra_qspi_readl(tqspi, QSPI_TRANS_STATUS);
tegra_qspi_mask_clear_irq(tqspi);
if (tqspi->cur_direction & DATA_DIR_TX)
- tqspi->tx_status = tqspi->status_reg &
- (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF);
+ WRITE_ONCE(tqspi->tx_status,
+ status_reg & (QSPI_TX_FIFO_UNF | QSPI_TX_FIFO_OVF));
if (tqspi->cur_direction & DATA_DIR_RX)
- tqspi->rx_status = tqspi->status_reg &
- (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF);
+ WRITE_ONCE(tqspi->rx_status,
+ status_reg & (QSPI_RX_FIFO_OVF | QSPI_RX_FIFO_UNF));
+
+ WRITE_ONCE(tqspi->status_reg, status_reg);
+ /*
+ * smp_store_release() pairs with smp_load_acquire() in
+ * tegra_qspi_handle_timeout(). Publishing trans_status last with
+ * release semantics guarantees that a timeout handler which sees
+ * a non-zero trans_status also observes the WRITE_ONCE() updates
+ * to status_reg / tx_status / rx_status above.
+ */
+ smp_store_release(&tqspi->trans_status, trans_status);
spin_unlock(&tqspi->lock);
--
2.17.1
next prev parent reply other threads:[~2026-06-10 6:24 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-10 6:23 [PATCH v4 0/3] spi: tegra210-quad: Improve interrupt handling for loaded systems Vishwaroop A
2026-06-10 6:24 ` [PATCH v4 1/3] spi: tegra210-quad: Convert to hard IRQ with high-priority workqueue Vishwaroop A
2026-06-12 15:29 ` Mark Brown
2026-06-10 6:24 ` Vishwaroop A [this message]
2026-06-12 16:08 ` [PATCH v4 2/3] spi: tegra210-quad: Cache TRANS_STATUS in ISR for timeout handler Mark Brown
2026-06-10 6:24 ` [PATCH v4 3/3] spi: tegra210-quad: Process small PIO transfers in hard IRQ context Vishwaroop A
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=2ed90a7d175f7967e621943b772115da655c477d.1781037385.git.va@nvidia.com \
--to=va@nvidia.com \
--cc=broonie@kernel.org \
--cc=jonathanh@nvidia.com \
--cc=kyarlagadda@nvidia.com \
--cc=ldewangan@nvidia.com \
--cc=leitao@debian.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-spi@vger.kernel.org \
--cc=linux-tegra@vger.kernel.org \
--cc=skomatineni@nvidia.com \
--cc=smangipudi@nvidia.com \
--cc=thierry.reding@kernel.org \
/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®