* [PATCH v5 0/4] spi: dw: use threaded interrupt
@ 2026-10-05 15:17 Jisheng Zhang
2026-10-05 15:17 ` [PATCH v5 1/4] spi: dw: remove useless dws->transfer_handler check Jisheng Zhang
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Jisheng Zhang @ 2026-10-05 15:17 UTC (permalink / raw)
To: Mark Brown, Joseph Steel; +Cc: linux-spi, linux-kernel
To avoid blocking for an excessive amount of time, eventually impacting
on system responsiveness, hard interrupt handlers should finish
executing in as little time as possible.
Use threaded interrupt and move the SPI transfer handling to an
interrupt thread when non-native CS and host only.
After that, since the dw_reader() and dw_writer() are called in
threaded ISR now, so we can delay the unmasking interrupts until no
rx and tx action is taken, thus reduce the interrupt numbers further.
Tested with below two cmds
./spidev_test -D /dev/spidev1.3 -s 30000000 -S 327680 -I 1
./spidev_test -D /dev/spidev1.3 -s 30000000 -S 327680 -I 1000
./rtla timerlat top -q -k -P f:95
The first cmd is to check the interrupt numbers optmizaion result, the
2nd cmd group is to check the threaded interrupt improvement.
Before the patch:
each 320KB spi spidev_test transfer triggers 33118 interrupts
spidev_test reports ~22090 kbps
and rtla reports:
Timer Latency
0 00:00:37 | IRQ Timer Latency (us) | Thread Timer Latency (us)
CPU COUNT | cur min avg max | cur min avg max
0 #9958 | 1 0 67 103394 | 6 4 2198 105031
1 #36902 | 1 0 1 18 | 5 4 5 29
After the patch:
each 320KB spi spidev_test transfer only triggers 1 interrupts
spidev_test reports ~23520 kbps
and now rtla reports:
Timer Latency
0 00:00:58 | IRQ Timer Latency (us) | Thread Timer Latency (us)
CPU COUNT | cur min avg max | cur min avg max
0 #58362 | 1 0 0 29 | 6 3 4 56
1 #58363 | 1 0 1 23 | 6 4 5 68
In summary:
before the patch after the patch
33118 interrutps 1 interrupts reduced by 33117 times!
103394 us max latency 29 us max latency reduced by 3564 times!
22090 kbps rate 23520 kbps rate improved by 6.5%
Since v4:
- rebase on the latest spi for-next tree
- set a local flag before we complete the transfer and use that to
decide if we wake the thread.
Since v3:
- If native cs, don't use threaded interrupt
- do one round of FIFO in the hardirq
- add cond_resched in dw_spi_irq_thread_fn
Since v2:
- rebase against latest version
- remove the "spi: dw: use DW_SPI_INT_MASK instead of hardcoded 0xff"
- add three more patches to clean up irq code introduced by recent "enhanced
spi" support
- Don't use threaded interrupt for target mode and the enhanced spi
Since v1:
- rebase against latest version
- drop two patches which have been merged
- correct some performance numbers
- don't move request irq code block so no changes for err handling code path
- move spi_finalize_current_transfer to the end of threaded irq fn
- don't rely on irq status in threaded fn, but try rx and tx as much
as possible in the loop
Jisheng Zhang (4):
spi: dw: remove useless dws->transfer_handler check
spi: dw: remove duplicated "!rx_len && !tx_len" handling from
dw_spi_irq
spi: dw: restore previous irq handling behavior when !ctlr->cur_msg
spi: dw: use threaded interrupt and optimize the threaded ISR
drivers/spi/spi-dw-core.c | 79 ++++++++++++++++++++++++++++++++-------
1 file changed, 66 insertions(+), 13 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH v5 1/4] spi: dw: remove useless dws->transfer_handler check 2026-10-05 15:17 [PATCH v5 0/4] spi: dw: use threaded interrupt Jisheng Zhang @ 2026-10-05 15:17 ` Jisheng Zhang 2026-10-05 15:17 ` [PATCH v5 2/4] spi: dw: remove duplicated "!rx_len && !tx_len" handling from dw_spi_irq Jisheng Zhang ` (2 subsequent siblings) 3 siblings, 0 replies; 5+ messages in thread From: Jisheng Zhang @ 2026-10-05 15:17 UTC (permalink / raw) To: Mark Brown, Joseph Steel; +Cc: linux-spi, linux-kernel transfer_handler is initialized before any DW SPI interrupt is unmasked. While the IRQ is registered but the handler is NULL, dw_spi_hw_init() has all DW SPI interrupts disabled. Therefore dw_spi_irq() cannot observe a DW SPI interrupt with a NULL transfer_handler. Signed-off-by: Jisheng Zhang <jszhang@kernel.org> --- drivers/spi/spi-dw-core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 1f0e93800908..f5a80105ceeb 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -280,8 +280,7 @@ static irqreturn_t dw_spi_irq(int irq, void *dev_id) if (!irq_status) return IRQ_NONE; - if (!dws->transfer_handler || - (!ctlr->cur_msg && dws->transfer_handler == dw_spi_transfer_handler)) { + if (!ctlr->cur_msg && dws->transfer_handler == dw_spi_transfer_handler) { dw_spi_mask_intr(dws, 0xff); return IRQ_HANDLED; } -- 2.53.0 ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v5 2/4] spi: dw: remove duplicated "!rx_len && !tx_len" handling from dw_spi_irq 2026-10-05 15:17 [PATCH v5 0/4] spi: dw: use threaded interrupt Jisheng Zhang 2026-10-05 15:17 ` [PATCH v5 1/4] spi: dw: remove useless dws->transfer_handler check Jisheng Zhang @ 2026-10-05 15:17 ` Jisheng Zhang 2026-10-05 15:17 ` [PATCH v5 3/4] spi: dw: restore previous irq handling behavior when !ctlr->cur_msg Jisheng Zhang 2026-10-05 15:17 ` [PATCH v5 4/4] spi: dw: use threaded interrupt and optimize the threaded ISR Jisheng Zhang 3 siblings, 0 replies; 5+ messages in thread From: Jisheng Zhang @ 2026-10-05 15:17 UTC (permalink / raw) To: Mark Brown, Joseph Steel; +Cc: linux-spi, linux-kernel, Changhuang Liang dw_spi_irq() need not handle enhanced-transfer's "!rx_len && !tx_len" case as that state is already handled by dw_spi_enh_handler() Signed-off-by: Jisheng Zhang <jszhang@kernel.org> Reviewed-by: Changhuang Liang <changhuang.liang@starfivetech.com> --- drivers/spi/spi-dw-core.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index f5a80105ceeb..f82a632ca71c 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -284,12 +284,6 @@ static irqreturn_t dw_spi_irq(int irq, void *dev_id) dw_spi_mask_intr(dws, 0xff); return IRQ_HANDLED; } - if (dws->transfer_handler == dw_spi_enh_handler && - !dws->rx_len && !dws->tx_len) { - dw_spi_mask_intr(dws, 0xff); - spi_finalize_current_transfer(ctlr); - return IRQ_HANDLED; - } return dws->transfer_handler(dws); } -- 2.53.0 ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v5 3/4] spi: dw: restore previous irq handling behavior when !ctlr->cur_msg 2026-10-05 15:17 [PATCH v5 0/4] spi: dw: use threaded interrupt Jisheng Zhang 2026-10-05 15:17 ` [PATCH v5 1/4] spi: dw: remove useless dws->transfer_handler check Jisheng Zhang 2026-10-05 15:17 ` [PATCH v5 2/4] spi: dw: remove duplicated "!rx_len && !tx_len" handling from dw_spi_irq Jisheng Zhang @ 2026-10-05 15:17 ` Jisheng Zhang 2026-10-05 15:17 ` [PATCH v5 4/4] spi: dw: use threaded interrupt and optimize the threaded ISR Jisheng Zhang 3 siblings, 0 replies; 5+ messages in thread From: Jisheng Zhang @ 2026-10-05 15:17 UTC (permalink / raw) To: Mark Brown, Joseph Steel; +Cc: linux-spi, linux-kernel, Changhuang Liang Recent "enhanced" support changes the dw_spi_irq() behavior a bit when !!ctlr->cur_msg: if (!ctlr->cur_msg && dws->transfer_handler == dw_spi_transfer_handler) { dw_spi_mask_intr(dws, 0xff); return IRQ_HANDLED; } But it misses the dma case, where the transfer_handler == dw_spi_dma_transfer_handler, so this changes the previous long time working behavior, let's restore the previous handling by only filtering out the dw_spi_enh_handler. Signed-off-by: Jisheng Zhang <jszhang@kernel.org> Reviewed-by: Changhuang Liang <changhuang.liang@starfivetech.com> --- drivers/spi/spi-dw-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index f82a632ca71c..06f4b0ea954e 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -280,7 +280,7 @@ static irqreturn_t dw_spi_irq(int irq, void *dev_id) if (!irq_status) return IRQ_NONE; - if (!ctlr->cur_msg && dws->transfer_handler == dw_spi_transfer_handler) { + if (!ctlr->cur_msg && dws->transfer_handler != dw_spi_enh_handler) { dw_spi_mask_intr(dws, 0xff); return IRQ_HANDLED; } -- 2.53.0 ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v5 4/4] spi: dw: use threaded interrupt and optimize the threaded ISR 2026-10-05 15:17 [PATCH v5 0/4] spi: dw: use threaded interrupt Jisheng Zhang ` (2 preceding siblings ...) 2026-10-05 15:17 ` [PATCH v5 3/4] spi: dw: restore previous irq handling behavior when !ctlr->cur_msg Jisheng Zhang @ 2026-10-05 15:17 ` Jisheng Zhang 3 siblings, 0 replies; 5+ messages in thread From: Jisheng Zhang @ 2026-10-05 15:17 UTC (permalink / raw) To: Mark Brown, Joseph Steel; +Cc: linux-spi, linux-kernel To avoid blocking for an excessive amount of time, eventually impacting on system responsiveness, hard interrupt handlers should finish executing in as little time as possible. Use threaded interrupt and move the SPI transfer handling to an interrupt thread when non-native CS and host only. After that, since the dw_reader() and dw_writer() are called in threaded ISR now, so we can delay the unmasking interrupts until no rx and tx action is taken, thus reduce the interrupt numbers further. Tested with below two cmds ./spidev_test -D /dev/spidev1.3 -s 30000000 -S 327680 -I 1 ./spidev_test -D /dev/spidev1.3 -s 30000000 -S 327680 -I 1000 ./rtla timerlat top -q -k -P f:95 The first cmd is to check the interrupt numbers optmizaion result, the 2nd cmd group is to check the threaded interrupt improvement. Before the patch: each 320KB spi spidev_test transfer triggers 33118 interrupts spidev_test reports ~22090 kbps and rtla reports: Timer Latency 0 00:00:37 | IRQ Timer Latency (us) | Thread Timer Latency (us) CPU COUNT | cur min avg max | cur min avg max 0 #9958 | 1 0 67 103394 | 6 4 2198 105031 1 #36902 | 1 0 1 18 | 5 4 5 29 After the patch: each 320KB spi spidev_test transfer only triggers 1 interrupts spidev_test reports ~23520 kbps and now rtla reports: Timer Latency 0 00:00:58 | IRQ Timer Latency (us) | Thread Timer Latency (us) CPU COUNT | cur min avg max | cur min avg max 0 #58362 | 1 0 0 29 | 6 3 4 56 1 #58363 | 1 0 1 23 | 6 4 5 68 In summary: before the patch after the patch 33118 interrutps 1 interrupts reduced by 33117 times! 103394 us max latency 29 us max latency reduced by 3564 times! 22090 kbps rate 23520 kbps rate improved by 6.5% Signed-off-by: Jisheng Zhang <jszhang@kernel.org> --- drivers/spi/spi-dw-core.c | 70 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c index 06f4b0ea954e..a73c051437b6 100644 --- a/drivers/spi/spi-dw-core.c +++ b/drivers/spi/spi-dw-core.c @@ -132,10 +132,11 @@ static inline u32 dw_spi_rx_max(struct dw_spi *dws) return min_t(u32, dws->rx_len, dw_readl(dws, DW_SPI_RXFLR)); } -static void dw_writer(struct dw_spi *dws) +static u32 dw_writer(struct dw_spi *dws) { u32 max = dw_spi_tx_max(dws); u32 txw = 0; + u32 tx = 0; while (max--) { if (dws->tx) { @@ -150,13 +151,16 @@ static void dw_writer(struct dw_spi *dws) } dw_write_io_reg(dws, DW_SPI_DR, txw); --dws->tx_len; + ++tx; } + return tx; } -static void dw_reader(struct dw_spi *dws) +static u32 dw_reader(struct dw_spi *dws) { u32 max = dw_spi_rx_max(dws); u32 rxw; + u32 rx = 0; while (max--) { rxw = dw_read_io_reg(dws, DW_SPI_DR); @@ -171,7 +175,9 @@ static void dw_reader(struct dw_spi *dws) dws->rx += dws->n_bytes; } --dws->rx_len; + ++rx; } + return rx; } int dw_spi_check_status(struct dw_spi *dws, bool raw) @@ -210,9 +216,55 @@ int dw_spi_check_status(struct dw_spi *dws, bool raw) } EXPORT_SYMBOL_NS_GPL(dw_spi_check_status, "SPI_DW_CORE"); +static irqreturn_t dw_spi_irq_thread_fn(int irq, void *dev_id) +{ + struct spi_controller *ctlr = dev_id; + struct dw_spi *dws = spi_controller_get_devdata(ctlr); + u32 rx, tx, imask, mask = 0; + bool finalize = false; + + do { + /* + * Read data from the Rx FIFO every time we've got a chance executing + * this method. If there is nothing left to receive, terminate the + * procedure. Otherwise adjust the Rx FIFO Threshold level if it's a + * final stage of the transfer. By doing so we'll get the next IRQ + * right when the leftover incoming data is received. + */ + rx = dw_reader(dws); + if (!dws->rx_len) { + mask |= 0xff; + finalize = true; + } else if (dws->rx_len <= dw_readl(dws, DW_SPI_RXFTLR)) { + dw_writel(dws, DW_SPI_RXFTLR, dws->rx_len - 1); + } + + /* + * Send data out as much as possible. The Tx FIFO Empty IRQ will be + * disabled after the data transmission is finished so not to + * have the TXE IRQ flood at the final stage of the transfer. + */ + tx = dw_writer(dws); + if (!dws->tx_len) + mask |= DW_SPI_INT_TXEI; + cond_resched(); + } while (rx != 0 || tx != 0); + + imask = DW_SPI_INT_TXEI | DW_SPI_INT_TXOI | + DW_SPI_INT_RXUI | DW_SPI_INT_RXOI | DW_SPI_INT_RXFI; + imask &= ~mask; + dw_spi_umask_intr(dws, imask); + + if (finalize) + spi_finalize_current_transfer(dws->ctlr); + + return IRQ_HANDLED; +} + static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws) { u16 irq_status = dw_readl(dws, DW_SPI_ISR); + bool finalize = false; if (dw_spi_check_status(dws, false)) { spi_finalize_current_transfer(dws->ctlr); @@ -229,6 +281,7 @@ static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws) dw_reader(dws); if (!dws->rx_len) { dw_spi_mask_intr(dws, 0xff); + finalize = true; spi_finalize_current_transfer(dws->ctlr); } else if (dws->rx_len <= dw_readl(dws, DW_SPI_RXFTLR)) { dw_writel(dws, DW_SPI_RXFTLR, dws->rx_len - 1); @@ -245,7 +298,14 @@ static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws) dw_spi_mask_intr(dws, DW_SPI_INT_TXEI); } - return IRQ_HANDLED; + if (spi_controller_is_target(dws->ctlr) || finalize || + !dws->rx_len || !dws->tx_len || + !spi_get_csgpiod(dws->ctlr->cur_msg->spi, 0)) { + return IRQ_HANDLED; + } + + dw_spi_mask_intr(dws, 0xff); + return IRQ_WAKE_THREAD; } static irqreturn_t dw_spi_enh_handler(struct dw_spi *dws) @@ -1334,8 +1394,8 @@ int dw_spi_add_controller(struct device *dev, struct dw_spi *dws) /* Basic HW init */ dw_spi_hw_init(dev, dws); - ret = request_irq(dws->irq, dw_spi_irq, IRQF_SHARED, dev_name(dev), - ctlr); + ret = request_threaded_irq(dws->irq, dw_spi_irq, dw_spi_irq_thread_fn, + IRQF_SHARED, dev_name(dev), ctlr); if (ret < 0 && ret != -ENOTCONN) { dev_err(dev, "can not request IRQ\n"); goto err_free_ctlr; -- 2.53.0 ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-05 15:37 UTC | newest] Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-10-05 15:17 [PATCH v5 0/4] spi: dw: use threaded interrupt Jisheng Zhang 2026-10-05 15:17 ` [PATCH v5 1/4] spi: dw: remove useless dws->transfer_handler check Jisheng Zhang 2026-10-05 15:17 ` [PATCH v5 2/4] spi: dw: remove duplicated "!rx_len && !tx_len" handling from dw_spi_irq Jisheng Zhang 2026-10-05 15:17 ` [PATCH v5 3/4] spi: dw: restore previous irq handling behavior when !ctlr->cur_msg Jisheng Zhang 2026-10-05 15:17 ` [PATCH v5 4/4] spi: dw: use threaded interrupt and optimize the threaded ISR Jisheng Zhang
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®