From 969854a5d7115b582722c669de013f2978267c9a Mon Sep 17 00:00:00 2001 From: Max Hsu Date: Sat, 21 Feb 2026 03:43:53 +0800 Subject: [PATCH 1/5] dmaengine: sf-pdma: add missing PDMA base offset to register calculations The PDMA control registers start at offset 0x80000 from the PDMA base address, according to the FU540-C000 v1p1 manual [1]. The current SF_PDMA_REG_BASE macro is missing this offset: Current: pdma->membase + (PDMA_CHAN_OFFSET * ch) Correct: pdma->membase + 0x80000 + (PDMA_CHAN_OFFSET * ch) Fix by adding PDMA_BASE_OFFSET (0x80000) to the register address calculation. Link: https://www.sifive.com/document-file/freedom-u540-c000-manual [1] Fixes: 6973886ad58e ("dmaengine: sf-pdma: add platform DMA support for HiFive Unleashed A00") Signed-off-by: Max Hsu Signed-off-by: Linux RISC-V bot --- drivers/dma/sf-pdma/sf-pdma.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/dma/sf-pdma/sf-pdma.h b/drivers/dma/sf-pdma/sf-pdma.h index 215e07183d7e26..d33551eb2ee816 100644 --- a/drivers/dma/sf-pdma/sf-pdma.h +++ b/drivers/dma/sf-pdma/sf-pdma.h @@ -24,7 +24,7 @@ #define PDMA_MAX_NR_CH 4 -#define PDMA_BASE_ADDR 0x3000000 +#define PDMA_BASE_OFFSET 0x80000 #define PDMA_CHAN_OFFSET 0x1000 /* Register Offset */ @@ -54,7 +54,7 @@ /* Error Recovery */ #define MAX_RETRY 1 -#define SF_PDMA_REG_BASE(ch) (pdma->membase + (PDMA_CHAN_OFFSET * (ch))) +#define SF_PDMA_REG_BASE(ch) (pdma->membase + PDMA_BASE_OFFSET + (PDMA_CHAN_OFFSET * (ch))) struct pdma_regs { /* read-write regs */ From 6b91bbd72b8e96b972a732ffa21497322c0abb8f Mon Sep 17 00:00:00 2001 From: Max Hsu Date: Sat, 21 Feb 2026 03:43:54 +0800 Subject: [PATCH 2/5] dmaengine: sf-pdma: fix race between done and error interrupts According to the FU540-C000 v1p5 [1] and FU740-C000 v1p7 [2] specs, when a DMA transaction error occurs, the hardware sets both the DONE and ERROR interrupt bits simultaneously. On SMP systems, this can cause the done_isr and err_isr to execute concurrently on different CPUs, leading to race conditions and NULL pointer dereferences. Fix by: - In done_isr: abort if ERROR bit is set or DONE bit was already cleared - In err_isr: clear both DONE and ERROR bits to prevent done_isr from processing the same transaction Link: https://www.sifive.com/document-file/freedom-u540-c000-manual [1] Link: https://www.sifive.com/document-file/freedom-u740-c000-manual [2] Fixes: 6973886ad58e ("dmaengine: sf-pdma: add platform DMA support for HiFive Unleashed A00") Cc: stable@vger.kernel.org Signed-off-by: Max Hsu Signed-off-by: Linux RISC-V bot --- drivers/dma/sf-pdma/sf-pdma.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/drivers/dma/sf-pdma/sf-pdma.c b/drivers/dma/sf-pdma/sf-pdma.c index 7ad3c29be14679..ac7d3b127a2449 100644 --- a/drivers/dma/sf-pdma/sf-pdma.c +++ b/drivers/dma/sf-pdma/sf-pdma.c @@ -346,9 +346,25 @@ static irqreturn_t sf_pdma_done_isr(int irq, void *dev_id) struct sf_pdma_chan *chan = dev_id; struct pdma_regs *regs = &chan->regs; u64 residue; + u32 control_reg; spin_lock(&chan->vchan.lock); - writel((readl(regs->ctrl)) & ~PDMA_DONE_STATUS_MASK, regs->ctrl); + control_reg = readl(regs->ctrl); + if (control_reg & PDMA_ERR_STATUS_MASK) { + spin_unlock(&chan->vchan.lock); + return IRQ_HANDLED; + } + + /* + * Check if DONE bit is still set. If not, the error ISR on another + * CPU has already cleared it, so abort to avoid double-processing. + */ + if (!(control_reg & PDMA_DONE_STATUS_MASK)) { + spin_unlock(&chan->vchan.lock); + return IRQ_HANDLED; + } + + writel((control_reg & ~PDMA_DONE_STATUS_MASK), regs->ctrl); residue = readq(regs->residue); if (!residue) { @@ -375,7 +391,7 @@ static irqreturn_t sf_pdma_err_isr(int irq, void *dev_id) struct pdma_regs *regs = &chan->regs; spin_lock(&chan->lock); - writel((readl(regs->ctrl)) & ~PDMA_ERR_STATUS_MASK, regs->ctrl); + writel((readl(regs->ctrl)) & ~(PDMA_DONE_STATUS_MASK | PDMA_ERR_STATUS_MASK), regs->ctrl); spin_unlock(&chan->lock); tasklet_schedule(&chan->err_tasklet); From e1f78bdca4ebef0de62f85c9f356b855a7bcac8e Mon Sep 17 00:00:00 2001 From: Max Hsu Date: Sat, 21 Feb 2026 03:43:55 +0800 Subject: [PATCH 3/5] dmaengine: sf-pdma: fix NULL pointer dereference in error and done handlers Fix NULL pointer dereferences in both the error and done tasklets that can occur due to race conditions during channel termination or completion. Both tasklets (sf_pdma_errbh_tasklet and sf_pdma_donebh_tasklet) dereference chan->desc without checking if it's NULL. However, chan->desc can be NULL in legitimate scenarios: 1. During sf_pdma_terminate_all(): The function sets chan->desc = NULL while holding vchan.lock, but interrupts for previously submitted transactions could fire after the lock is released, before the hardware is fully quiesced. These interrupts can schedule tasklets that will run with chan->desc = NULL. 2. During channel cleanup: Similar race condition during sf_pdma_free_chan_resources(). The fix adds NULL checks at the beginning of both tasklets, protected by vchan.lock, using the same lock that terminate_all and free_chan_resources use when setting chan->desc = NULL. This ensures that either: - The descriptor is valid and we can safely process it, or - The descriptor was already freed and we safely skip processing Fixes: 6973886ad58e ("dmaengine: sf-pdma: add platform DMA support for HiFive Unleashed A00") Cc: stable@vger.kernel.org Signed-off-by: Max Hsu Signed-off-by: Linux RISC-V bot --- drivers/dma/sf-pdma/sf-pdma.c | 43 +++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/drivers/dma/sf-pdma/sf-pdma.c b/drivers/dma/sf-pdma/sf-pdma.c index ac7d3b127a2449..70e4afcda52a15 100644 --- a/drivers/dma/sf-pdma/sf-pdma.c +++ b/drivers/dma/sf-pdma/sf-pdma.c @@ -298,33 +298,56 @@ static void sf_pdma_free_desc(struct virt_dma_desc *vdesc) static void sf_pdma_donebh_tasklet(struct tasklet_struct *t) { struct sf_pdma_chan *chan = from_tasklet(chan, t, done_tasklet); + struct sf_pdma_desc *desc; unsigned long flags; - spin_lock_irqsave(&chan->lock, flags); - if (chan->xfer_err) { - chan->retries = MAX_RETRY; - chan->status = DMA_COMPLETE; - chan->xfer_err = false; + spin_lock_irqsave(&chan->vchan.lock, flags); + desc = chan->desc; + if (!desc) { + /* + * The descriptor was already freed (e.g., by terminate_all + * or completion on another CPU). Nothing to do. + */ + spin_unlock_irqrestore(&chan->vchan.lock, flags); + return; } - spin_unlock_irqrestore(&chan->lock, flags); - spin_lock_irqsave(&chan->vchan.lock, flags); - list_del(&chan->desc->vdesc.node); - vchan_cookie_complete(&chan->desc->vdesc); + list_del(&desc->vdesc.node); + vchan_cookie_complete(&desc->vdesc); chan->desc = sf_pdma_get_first_pending_desc(chan); if (chan->desc) sf_pdma_xfer_desc(chan); spin_unlock_irqrestore(&chan->vchan.lock, flags); + + spin_lock_irqsave(&chan->lock, flags); + if (chan->xfer_err) { + chan->retries = MAX_RETRY; + chan->status = DMA_COMPLETE; + chan->xfer_err = false; + } + spin_unlock_irqrestore(&chan->lock, flags); } static void sf_pdma_errbh_tasklet(struct tasklet_struct *t) { struct sf_pdma_chan *chan = from_tasklet(chan, t, err_tasklet); - struct sf_pdma_desc *desc = chan->desc; + struct sf_pdma_desc *desc; unsigned long flags; + spin_lock_irqsave(&chan->vchan.lock, flags); + desc = chan->desc; + if (!desc) { + /* + * The descriptor was already freed (e.g., by terminate_all + * or completion on another CPU). Nothing to do. + */ + spin_unlock_irqrestore(&chan->vchan.lock, flags); + return; + } + spin_unlock_irqrestore(&chan->vchan.lock, flags); + spin_lock_irqsave(&chan->lock, flags); if (chan->retries <= 0) { /* fail to recover */ From 39d29730f437658b3682c16fbba34590821b0f9b Mon Sep 17 00:00:00 2001 From: Max Hsu Date: Sat, 21 Feb 2026 03:43:56 +0800 Subject: [PATCH 4/5] dt-bindings: dma: sifive,fu540-c000-pdma: add fu740 support Add "sifive,fu740-c000-pdma" compatible string. Signed-off-by: Max Hsu Signed-off-by: Linux RISC-V bot --- .../devicetree/bindings/dma/sifive,fu540-c000-pdma.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/dma/sifive,fu540-c000-pdma.yaml b/Documentation/devicetree/bindings/dma/sifive,fu540-c000-pdma.yaml index 609e38901434a4..b6c49060bc6f3f 100644 --- a/Documentation/devicetree/bindings/dma/sifive,fu540-c000-pdma.yaml +++ b/Documentation/devicetree/bindings/dma/sifive,fu540-c000-pdma.yaml @@ -36,6 +36,7 @@ properties: - enum: - microchip,mpfs-pdma - sifive,fu540-c000-pdma + - sifive,fu740-c000-pdma - const: sifive,pdma0 description: Should be "sifive,-pdma" and "sifive,pdma". From a24bb18eeaedacf5a7bbf2f646d4f693f0498465 Mon Sep 17 00:00:00 2001 From: Max Hsu Date: Sat, 21 Feb 2026 03:43:57 +0800 Subject: [PATCH 5/5] riscv: dts: sifive: fu740: add PDMA device node The FU740 SoC includes a 4-channel Platform DMA (PDMA) controller. Add the device node to enable DMA support. Signed-off-by: Max Hsu Signed-off-by: Linux RISC-V bot --- arch/riscv/boot/dts/sifive/fu740-c000.dtsi | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arch/riscv/boot/dts/sifive/fu740-c000.dtsi b/arch/riscv/boot/dts/sifive/fu740-c000.dtsi index 6150f3397bff92..30d0d6837c57ed 100644 --- a/arch/riscv/boot/dts/sifive/fu740-c000.dtsi +++ b/arch/riscv/boot/dts/sifive/fu740-c000.dtsi @@ -329,6 +329,15 @@ clocks = <&prci FU740_PRCI_CLK_PCLK>; status = "disabled"; }; + dma: dma-controller@3000000 { + compatible = "sifive,fu740-c000-pdma", "sifive,pdma0"; + reg = <0x0 0x3000000 0x0 0x100000>; + interrupt-parent = <&plic0>; + interrupts = <11>, <12>, <13>, <14>, <15>, <16>, <17>, <18>; + dma-channels = <4>; + clocks = <&prci FU740_PRCI_CLK_PCLK>; + #dma-cells = <1>; + }; pcie@e00000000 { compatible = "sifive,fu740-pcie"; #address-cells = <3>;