From 9c317842eceece7efbaf07ac1a08c5d5e8be7ad0 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Tue, 21 Jul 2026 15:28:03 +0900 Subject: [PATCH] dmaengine: dw-edma: Add per-channel interrupt routing control DesignWare eDMA can signal completion locally through edma_int[] and remotely through IMWr/MSI. When channels are delegated to a remote frontend, the local endpoint side and the remote host side must not both service the same DONE/ABORT status. Add channel interrupt routing state and initialize it from the controller instance configuration. Update the eDMA and HDMA native paths so linked-list interrupt generation, HDMA non-linked-list interrupt enables, and DONE/ABORT masking follow the selected mode. For HDMA native non-linked-list channels, keep the local stop/abort enables set so status is latched. In remote mode, also enable remote signaling and mask the local interrupt pins. Keep the existing dw-edma-pcie host-side instances in remote interrupt routing mode so their IMWr/MSI completion model remains unchanged after local routing becomes the zero value. Note: - The routing mode describes where a channel should report completion. It does not by itself say whether this dw-edma instance owns the interrupt status. A local instance must ignore remote-only channels, and a remote instance must ignore local-only channels, even if such interrupts are unexpectedly delivered. Otherwise the non-owner side could steal the interrupt from the owner by clearing shared DONE/ABORT status. Cc: Devendra K Verma Suggested-by: Frank Li Signed-off-by: Koichiro Den Link: https://patch.msgid.link/20260721062815.4117887-3-den@valinux.co.jp Signed-off-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-core.c | 10 +++++++++ drivers/dma/dw-edma/dw-edma-core.h | 13 +++++++++++ drivers/dma/dw-edma/dw-edma-v0-core.c | 30 +++++++++++++++++++++---- drivers/dma/dw-edma/dw-hdma-v0-core.c | 25 ++++++++++++++------- include/linux/dma/edma.h | 32 +++++++++++++++++++++++++++ 5 files changed, 98 insertions(+), 12 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index e4dde3518b83..3a8610862cff 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -168,6 +168,15 @@ static void dw_edma_device_caps(struct dma_chan *dchan, } } +static enum dw_edma_ch_irq_mode +dw_edma_get_default_irq_mode(struct dw_edma_chan *chan) +{ + struct dw_edma_chip *chip = chan->dw->chip; + + return chip->flags & DW_EDMA_CHIP_LOCAL ? DW_EDMA_CH_IRQ_LOCAL : + DW_EDMA_CH_IRQ_REMOTE; +} + static int dw_edma_device_config(struct dma_chan *dchan, struct dma_slave_config *config) { @@ -931,6 +940,7 @@ 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; + chan->irq_mode = dw_edma_get_default_irq_mode(chan); INIT_WORK(&chan->irq_work, dw_edma_irq_work); atomic_set(&chan->irq_pending, 0); diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h index 90ba37737c3a..ff20965db4b2 100644 --- a/drivers/dma/dw-edma/dw-edma-core.h +++ b/drivers/dma/dw-edma/dw-edma-core.h @@ -76,6 +76,8 @@ struct dw_edma_chan { struct msi_msg msi; + enum dw_edma_ch_irq_mode irq_mode; + enum dw_edma_request request; enum dw_edma_status status; u8 configured; @@ -259,4 +261,15 @@ dw_edma_core_db_offset(struct dw_edma *dw) return dw->core->db_offset(dw); } +static inline bool +dw_edma_core_ch_ignore_irq(struct dw_edma_chan *chan) +{ + struct dw_edma *dw = chan->dw; + + if (dw->chip->flags & DW_EDMA_CHIP_LOCAL) + return chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE; + else + return chan->irq_mode == DW_EDMA_CH_IRQ_LOCAL; +} + #endif /* _DW_EDMA_CORE_H */ diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c index f1abbbacca5e..66a605f28a12 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-core.c +++ b/drivers/dma/dw-edma/dw-edma-v0-core.c @@ -255,6 +255,9 @@ dw_edma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir, for_each_set_bit(pos, &val, total) { chan = &dw->chan[pos + off]; + if (unlikely(dw_edma_core_ch_ignore_irq(chan))) + continue; + dw_edma_v0_core_clear_done_int(chan); done(chan); @@ -266,6 +269,9 @@ dw_edma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir, for_each_set_bit(pos, &val, total) { chan = &dw->chan[pos + off]; + if (unlikely(dw_edma_core_ch_ignore_irq(chan))) + continue; + dw_edma_v0_core_clear_abort_int(chan); abort(chan); @@ -353,12 +359,17 @@ static void dw_edma_v0_core_ch_enable(struct dw_edma_chan *chan) break; } } - /* Interrupt unmask - done, abort */ + /* Interrupt mask/unmask - done, abort */ raw_spin_lock_irqsave(&dw->lock, flags); tmp = GET_RW_32(dw, chan->dir, int_mask); - tmp &= ~FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id)); - tmp &= ~FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id)); + if (chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE) { + tmp |= FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id)); + tmp |= FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id)); + } else { + tmp &= ~FIELD_PREP(EDMA_V0_DONE_INT_MASK, BIT(chan->id)); + tmp &= ~FIELD_PREP(EDMA_V0_ABORT_INT_MASK, BIT(chan->id)); + } SET_RW_32(dw, chan->dir, int_mask, tmp); /* Linked list error */ tmp = GET_RW_32(dw, chan->dir, linked_list_err_en); @@ -473,7 +484,18 @@ dw_edma_v0_core_ll_data(struct dw_edma_chan *chan, struct dw_edma_burst *burst, if (irq) { control |= DW_EDMA_V0_LIE; - if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL)) + /* + * A local instance never issues transfers on a remote-routed + * channel: on CHIP_LOCAL instances, REMOTE routing denotes a + * channel handed over to the remote side, which programs the + * linked list through its own instance. The remote-only + * recipe (LIE|RIE with the local interrupt masked) is thus + * applied by the instance that owns the transfer, and the + * LIE-only write below never executes for a remote-routed + * channel. + */ + if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) && + chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE) control |= DW_EDMA_V0_RIE; } diff --git a/drivers/dma/dw-edma/dw-hdma-v0-core.c b/drivers/dma/dw-edma/dw-hdma-v0-core.c index 4df541bf34e4..e9b4bc66886a 100644 --- a/drivers/dma/dw-edma/dw-hdma-v0-core.c +++ b/drivers/dma/dw-edma/dw-hdma-v0-core.c @@ -51,15 +51,22 @@ __dw_ch_regs(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch) static u32 dw_hdma_v0_core_int_setup(struct dw_edma_chan *chan, u32 val) { - if (chan->non_ll) - val |= HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK; - else - val &= ~(HDMA_V0_STOP_INT_MASK | HDMA_V0_ABORT_INT_MASK); + val &= ~(HDMA_V0_LOCAL_ABORT_INT_EN | HDMA_V0_REMOTE_ABORT_INT_EN | + HDMA_V0_LOCAL_STOP_INT_EN | HDMA_V0_REMOTE_STOP_INT_EN | + HDMA_V0_ABORT_INT_MASK | HDMA_V0_STOP_INT_MASK); - val |= HDMA_V0_LOCAL_STOP_INT_EN | HDMA_V0_LOCAL_ABORT_INT_EN; - if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL)) - val |= HDMA_V0_REMOTE_STOP_INT_EN | - HDMA_V0_REMOTE_ABORT_INT_EN; + /* + * HDMA_INT_STATUS.STOP and .ABORT are latched only when LSIE and + * LAIE are enabled. A remote handler needs those status bits to + * identify the source of the IMWr, so keep local generation enabled + * and mask the local interrupt pins instead. + */ + val |= HDMA_V0_LOCAL_ABORT_INT_EN | HDMA_V0_LOCAL_STOP_INT_EN; + + if (chan->irq_mode == DW_EDMA_CH_IRQ_REMOTE) + val |= HDMA_V0_REMOTE_ABORT_INT_EN | + HDMA_V0_REMOTE_STOP_INT_EN | + HDMA_V0_ABORT_INT_MASK | HDMA_V0_STOP_INT_MASK; return val; } @@ -158,6 +165,8 @@ dw_hdma_v0_core_handle_int(struct dw_edma_irq *dw_irq, enum dw_edma_dir dir, for_each_set_bit(pos, mask, total) { chan = &dw->chan[pos + off]; + if (unlikely(dw_edma_core_ch_ignore_irq(chan))) + continue; val = dw_hdma_v0_core_status_int(chan); if (FIELD_GET(HDMA_V0_STOP_INT_MASK, val)) { diff --git a/include/linux/dma/edma.h b/include/linux/dma/edma.h index da7a5cc93ad4..17cbe7ce55aa 100644 --- a/include/linux/dma/edma.h +++ b/include/linux/dma/edma.h @@ -62,6 +62,38 @@ enum dw_edma_chip_flags { DW_EDMA_CHIP_LOCAL = BIT(0), }; +/** + * enum dw_edma_ch_irq_mode - per-channel interrupt routing control + * @DW_EDMA_CH_IRQ_LOCAL: local interrupt only (edma_int[]) + * @DW_EDMA_CH_IRQ_REMOTE: remote interrupt only (IMWr/MSI), without + * delivering local edma_int[]. + * + * DesignWare EP eDMA can signal interrupts locally through the edma_int[] + * bus, and remotely using posted memory writes (IMWr) that may be + * interpreted as MSI/MSI-X by the RC. + * + * For the v0 eDMA linked-list programming path, DMA_*_INT_MASK gates the local + * edma_int[] assertion, while there is no dedicated per-channel mask for IMWr + * generation. To request a remote-only interrupt, Synopsys recommends setting + * both LIE and RIE, and masking the local interrupt in DMA_*_INT_MASK. See the + * DesignWare endpoint databook 6.30a, Linked List Mode interrupt handling + * ("Software Programming of an Endpoint's LIE and RIE Bits for Linked List + * Transfers", Attention). + * + * A local (DW_EDMA_CHIP_LOCAL) instance never issues transfers on a + * remote-routed channel: REMOTE routing on such an instance denotes a channel + * handed over to and driven by the remote side, and the recipe above is + * applied by the driving instance. + * + * HDMA linked-list watermark interrupts have the same LWIE/RWIE guidance. HDMA + * non-linked-list mode has dedicated local and remote stop/abort interrupt + * enables. + */ +enum dw_edma_ch_irq_mode { + DW_EDMA_CH_IRQ_LOCAL = 0, + DW_EDMA_CH_IRQ_REMOTE, +}; + /** * struct dw_edma_chip - representation of DesignWare eDMA controller hardware * @dev: struct device of the eDMA controller