From: Frank Li <Frank.li@oss.nxp.com>
To: Koichiro Den <den@valinux.co.jp>
Cc: Manivannan Sadhasivam <mani@kernel.org>,
Vinod Koul <vkoul@kernel.org>, Frank Li <Frank.Li@kernel.org>,
Cai Huoqing <cai.huoqing@linux.dev>,
Serge Semin <fancer.lancer@gmail.com>,
Gustavo Pimentel <Gustavo.Pimentel@synopsys.com>,
Devendra K Verma <devendra.verma@amd.com>,
dmaengine@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH 7/7] dmaengine: dw-edma: Defer channel IRQ handling to workqueue
Date: Sat, 11 Jul 2026 09:55:04 -0500 [thread overview]
Message-ID: <alJZSKF7D-tx1BV5@SMW015318> (raw)
In-Reply-To: <20260710080903.2392888-8-den@valinux.co.jp>
On Fri, Jul 10, 2026 at 05:09:03PM +0900, Koichiro Den wrote:
> On some SoCs (e.g. R-Car S4) the endpoint-side eDMA raises a single
> fixed SPI that is hardwired to CPU0 and covers every read and write
> channel. Handling channel events directly in that hard IRQ context
> serializes the completion processing of all channels on one CPU:
> descriptor recycling and refill, client callbacks (the vchan tasklet
> runs on the scheduling CPU) and the doorbell writes all funnel through
> CPU0, while the handler additionally spins on each channel's vc.lock.
> Especially under multi-channels heavy load, the contention becomes a
> performance bottleneck.
>
> Keep the hard IRQ handler minimal and have it just clear the status and
> dispatch, defer the per-channel processing to work items. A work item
> per channel preserves per-channel ordering while letting the channels be
> processed in parallel on any CPU.
>
> Signed-off-by: Koichiro Den <den@valinux.co.jp>
> ---
It looks good, allen try convert bh tasklet to workqueue.
https://lore.kernel.org/dmaengine/CAOMdWSLVk136RzEyiN76zqk65VLwYms0hCDi5Kww9FQppie12A@mail.gmail.com/
Please double check if it can apply to dw-edma?
Frank
> Changes in v2:
> - New patch in v2, posted as part of this preparation series.
>
> drivers/dma/dw-edma/dw-edma-core.c | 73 ++++++++++++++++++++++++++++--
> drivers/dma/dw-edma/dw-edma-core.h | 12 +++++
> 2 files changed, 80 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
> index 5664421c6f15..704d8f9746e8 100644
> --- a/drivers/dma/dw-edma/dw-edma-core.c
> +++ b/drivers/dma/dw-edma/dw-edma-core.c
> @@ -31,6 +31,11 @@ struct dw_edma_desc *vd2dw_edma_desc(struct virt_dma_desc *vd)
> return container_of(vd, struct dw_edma_desc, vd);
> }
>
> +enum dw_edma_irq_event {
> + DW_EDMA_IRQ_DONE = BIT(0),
> + DW_EDMA_IRQ_ABORT = BIT(1),
> +};
> +
> static inline
> u64 dw_edma_get_pci_address(struct dw_edma_chan *chan, phys_addr_t cpu_addr)
> {
> @@ -748,6 +753,44 @@ static void dw_edma_abort_interrupt(struct dw_edma_chan *chan)
> chan->status = EDMA_ST_IDLE;
> }
>
> +static void dw_edma_irq_work(struct work_struct *work)
> +{
> + struct dw_edma_chan *chan = container_of(work, struct dw_edma_chan,
> + irq_work);
> + unsigned int events;
> +
> + do {
> + events = atomic_xchg(&chan->irq_pending, 0);
> +
> + if (events & DW_EDMA_IRQ_DONE)
> + dw_edma_done_interrupt(chan);
> + if (events & DW_EDMA_IRQ_ABORT)
> + dw_edma_abort_interrupt(chan);
> + /*
> + * Correctness does not depend on this loop: queue_work() can
> + * requeue once the work item starts running. Staying here just
> + * coalesces back-to-back channel events into one wakeup.
> + */
> + } while (atomic_read(&chan->irq_pending));
> +}
> +
> +static void dw_edma_queue_irq_work(struct dw_edma_chan *chan,
> + enum dw_edma_irq_event event)
> +{
> + atomic_or(event, &chan->irq_pending);
> + queue_work(chan->dw->wq, &chan->irq_work);
> +}
> +
> +static void dw_edma_done_interrupt_deferred(struct dw_edma_chan *chan)
> +{
> + dw_edma_queue_irq_work(chan, DW_EDMA_IRQ_DONE);
> +}
> +
> +static void dw_edma_abort_interrupt_deferred(struct dw_edma_chan *chan)
> +{
> + dw_edma_queue_irq_work(chan, DW_EDMA_IRQ_ABORT);
> +}
> +
> static void dw_edma_emul_irq_ack(struct irq_data *d)
> {
> struct dw_edma *dw = irq_data_get_irq_chip_data(d);
> @@ -842,8 +885,8 @@ static inline irqreturn_t dw_edma_interrupt_write_inner(int irq, void *data)
> struct dw_edma_irq *dw_irq = data;
>
> return dw_edma_core_handle_int(dw_irq, EDMA_DIR_WRITE,
> - dw_edma_done_interrupt,
> - dw_edma_abort_interrupt);
> + dw_edma_done_interrupt_deferred,
> + dw_edma_abort_interrupt_deferred);
> }
>
> static inline irqreturn_t dw_edma_interrupt_read_inner(int irq, void *data)
> @@ -851,8 +894,8 @@ static inline irqreturn_t dw_edma_interrupt_read_inner(int irq, void *data)
> struct dw_edma_irq *dw_irq = data;
>
> return dw_edma_core_handle_int(dw_irq, EDMA_DIR_READ,
> - dw_edma_done_interrupt,
> - dw_edma_abort_interrupt);
> + dw_edma_done_interrupt_deferred,
> + dw_edma_abort_interrupt_deferred);
> }
>
> static inline irqreturn_t dw_edma_interrupt_write(int irq, void *data)
> @@ -930,6 +973,7 @@ static void dw_edma_device_synchronize(struct dma_chan *dchan)
> struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
>
> dw_edma_wait_termination(dchan);
> + cancel_work_sync(&chan->irq_work);
> vchan_synchronize(&chan->vc);
> }
>
> @@ -972,6 +1016,8 @@ static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc)
> chan->configured = false;
> chan->request = EDMA_REQ_NONE;
> chan->status = EDMA_ST_IDLE;
> + INIT_WORK(&chan->irq_work, dw_edma_irq_work);
> + atomic_set(&chan->irq_pending, 0);
>
> if (chan->dir == EDMA_DIR_WRITE)
> chan->ll_max = (chip->ll_region_wr[chan->id].sz / EDMA_LL_SZ);
> @@ -1185,10 +1231,21 @@ int dw_edma_probe(struct dw_edma_chip *chip)
> /* Disable eDMA, only to establish the ideal initial conditions */
> dw_edma_core_off(dw);
>
> + /*
> + * Deferred IRQ works are queued from the hard IRQ handlers, so the
> + * workqueue must exist before any IRQ is requested.
> + */
> + dw->wq = alloc_workqueue("dw-edma:%s", WQ_UNBOUND | WQ_HIGHPRI, 0,
> + dev_name(chip->dev));
> + if (!dw->wq)
> + return -ENOMEM;
> +
> /* Request IRQs */
> err = dw_edma_irq_request(dw, &wr_alloc, &rd_alloc);
> - if (err)
> + if (err) {
> + destroy_workqueue(dw->wq);
> return err;
> + }
>
> /* Allocate a dedicated virtual IRQ for interrupt-emulation doorbells */
> err = dw_edma_emul_irq_alloc(dw);
> @@ -1211,6 +1268,7 @@ int dw_edma_probe(struct dw_edma_chip *chip)
> for (i = (dw->nr_irqs - 1); i >= 0; i--)
> free_irq(chip->ops->irq_vector(dev, i), &dw->irq[i]);
> dw_edma_emul_irq_free(dw);
> + destroy_workqueue(dw->wq);
>
> return err;
> }
> @@ -1235,6 +1293,11 @@ int dw_edma_remove(struct dw_edma_chip *chip)
> free_irq(chip->ops->irq_vector(dev, i), &dw->irq[i]);
> dw_edma_emul_irq_free(dw);
>
> + for (i = 0; i < dw->wr_ch_cnt + dw->rd_ch_cnt; i++)
> + cancel_work_sync(&dw->chan[i].irq_work);
> +
> + destroy_workqueue(dw->wq);
> +
> /* Deregister eDMA device */
> dma_async_device_unregister(&dw->dma);
> list_for_each_entry_safe(chan, _chan, &dw->dma.channels,
> diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h
> index 6474cacf7195..a6a9ed09fe1b 100644
> --- a/drivers/dma/dw-edma/dw-edma-core.h
> +++ b/drivers/dma/dw-edma/dw-edma-core.h
> @@ -9,8 +9,10 @@
> #ifndef _DW_EDMA_CORE_H
> #define _DW_EDMA_CORE_H
>
> +#include <linux/atomic.h>
> #include <linux/msi.h>
> #include <linux/dma/edma.h>
> +#include <linux/workqueue.h>
>
> #include "../virt-dma.h"
>
> @@ -87,6 +89,9 @@ struct dw_edma_chan {
>
> struct dma_slave_config config;
> bool non_ll;
> +
> + struct work_struct irq_work;
> + atomic_t irq_pending;
> };
>
> struct dw_edma_irq {
> @@ -109,6 +114,13 @@ struct dw_edma {
>
> struct dw_edma_chan *chan;
>
> + /*
> + * Deferred channel IRQ handling. WQ_HIGHPRI keeps
> + * completion processing from starving behind saturated user load;
> + * WQ_UNBOUND spreads per-channel works across CPUs.
> + */
> + struct workqueue_struct *wq;
> +
> raw_spinlock_t lock; /* Protect v0 shared registers */
>
> struct dw_edma_chip *chip;
> --
> 2.51.0
>
prev parent reply other threads:[~2026-07-11 14:55 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-10 8:08 [PATCH 0/7] dmaengine: dw-edma: Fixes and interrupt-path groundwork Koichiro Den
2026-07-10 8:08 ` [PATCH 1/7] dmaengine: dw-edma: Fix HDMA channel status register access Koichiro Den
2026-07-10 21:26 ` Frank Li
2026-07-10 8:08 ` [PATCH 2/7] dmaengine: dw-edma: Terminate STOP requests without callbacks Koichiro Den
2026-07-11 14:27 ` Frank Li
2026-07-10 8:08 ` [PATCH 3/7] dmaengine: dw-edma: Clean up vchan descriptors on termination Koichiro Den
2026-07-11 14:39 ` Frank Li
2026-07-10 8:09 ` [PATCH 4/7] dmaengine: dw-edma: Serialize channel state checks Koichiro Den
2026-07-10 8:09 ` [PATCH 5/7] dmaengine: dw-edma-pcie: Drop redundant pci_free_irq_vectors() Koichiro Den
2026-07-11 14:41 ` Frank Li
2026-07-10 8:09 ` [PATCH 6/7] dmaengine: dw-edma: Snapshot the v0 interrupt status once per handler pass Koichiro Den
2026-07-11 14:44 ` Frank Li
2026-07-10 8:09 ` [PATCH 7/7] dmaengine: dw-edma: Defer channel IRQ handling to workqueue Koichiro Den
2026-07-11 14:55 ` Frank Li [this message]
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=alJZSKF7D-tx1BV5@SMW015318 \
--to=frank.li@oss.nxp.com \
--cc=Frank.Li@kernel.org \
--cc=Gustavo.Pimentel@synopsys.com \
--cc=cai.huoqing@linux.dev \
--cc=den@valinux.co.jp \
--cc=devendra.verma@amd.com \
--cc=dmaengine@vger.kernel.org \
--cc=fancer.lancer@gmail.com \
--cc=linux-kernel@vger.kernel.org \
--cc=mani@kernel.org \
--cc=vkoul@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