DMA Engine development
 help / color / mirror / Atom feed
* [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs
@ 2026-09-18 16:24 Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 01/22] dmaengine: ste_dma40: Fix physical cyclic capability Linus Walleij
                   ` (21 more replies)
  0 siblings, 22 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij, sashiko-bot

This series fixes 24 DMA40 bugs.

12 were found while reviewing the Ux500 LCLA SRAM power-domain conversion.

The cyclic transfer residue bug was exposed by Ux500 audio playback.

11 more issues were identified during review.

The method taken is: whenever Sashiko complains: fix the bug it complains
about if possible.

This has been boot tested on the Samsung Skomer device: DMA for MMC, wireless
SDIO and UART still works after these patches, and DMA for audio started
working.

Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
Changes in v3:
- Add a fix so physical channels advertise their existing cyclic support.
- Add a cyclic-transfer residue fix so DMAengine PCM reports the correct
  hardware pointer and Ux500 audio playback does not stop with -EIO.
- In patch 3, preserve cookie completion order when the next queued
  transfer fails to start and retire failed cyclic descriptors.
- In patch 4, enable runtime PM before requesting the IRQ so pending
  interrupts can be acknowledged during probe.
- In patch 5, keep valid interrupt handling with CONFIG_PM disabled and
  only drop a runtime PM reference when one was acquired.
- Add checked runtime PM resume handling to channel operations.
- Add an IRQ status patch returning IRQ_NONE when no DMA40 interrupt was
  acknowledged.
- In patch 8, keep the IRQ disabled until hardware initialization has
  configured and cleared the DMA40 interrupt state.
- In the DMA registration unwind patch, free the IRQ before unregistering
  the DMA devices and drain initialized tasklets before releasing storage.
- Add a fix releasing the LCPA SRAM node reference after reading it.
- Move the disabled-channels binding restoration to its own series.
- Store memcpy channel mappings per controller and check the property read.
- Add validation for disabled physical channel indexes.
- Reject DMA specifiers that do not contain exactly three cells.
- Reject transfer direction changes after channel allocation so logical
  channel resource masks are released correctly.
- In the event-group bounds patch, use variant-specific limits so DB8540
  event group 4 remains available.
- Add fixed-channel fixes that search later physical blocks and validate
  configured physical channel indexes.
- Move the generic DMAengine debugfs naming fix to its own series.
- Use `Assisted-by: LLM` for the existing assistance trailers.
- Rebase onto v7.3-rc1.
- Link to v2: https://lore.kernel.org/r/20260820-dma40-fixes-v2-0-63238334c707@kernel.org

Changes in v2:
- In patch 1, complete the failed-start descriptor through the normal
  tasklet path and drop the runtime PM reference instead of freeing the
  submitted descriptor directly.
- Add an IRQ fix to avoid register access when DMA40 is runtime suspended.
- Add a probe ordering fix so DMA40 hardware is initialized before
  DMAengine devices are registered.
- Add a probe unwind fix so DMAengine registrations are released before
  freeing IRQ and LCLA resources.
- Add an LCLA allocation fix so __get_free_pages() and free_pages() use an
  allocation order instead of a raw page count.
- Add a probe unwind fix so ESRAM LCLA mappings are not released with
  free_pages().
- Add a device tree parsing fix so memcpy-channels cannot overflow the
  memcpy channel array.
- Add a dev_type bounds fix so derived event groups cannot overflow
  phy_res or the priority/realtime register window.
- Add validation for fallback memcpy configurations so memcpy-channels
  entries cannot bypass the dev_type bounds checks.
- Add a DMAengine debugfs naming fix so drivers registering several
  DMAengine devices for one parent device do not trigger duplicate-name
  warnings.
- Link to v1: https://lore.kernel.org/r/20260820-dma40-fixes-v1-0-5e14815ad689@kernel.org

---
Linus Walleij (22):
      dmaengine: ste_dma40: Fix physical cyclic capability
      dmaengine: ste_dma40: Fix cyclic transfer residue
      dmaengine: ste_dma40: Fix failed start cleanup
      dmaengine: ste_dma40: Fix probe runtime PM disable
      dmaengine: ste_dma40: Check runtime PM in IRQ
      dmaengine: ste_dma40: Handle runtime PM resume errors
      dmaengine: ste_dma40: Return IRQ_NONE without interrupt status
      dmaengine: ste_dma40: Init hardware before registration
      dmaengine: ste_dma40: Fix probe IRQ leak
      dmaengine: ste_dma40: Fix DMA registration unwind
      dmaengine: ste_dma40: Fix LCLA allocation order
      dmaengine: ste_dma40: Fix probe LCLA free
      dmaengine: ste_dma40: Put the LCPA SRAM node
      dmaengine: ste_dma40: Fix memcpy channel parsing
      dmaengine: ste_dma40: Validate disabled channel indexes
      dmaengine: ste_dma40: Validate DMA specifier length
      dmaengine: ste_dma40: Reject direction changes after allocation
      dmaengine: ste_dma40: Fix logical channel bounds check
      dmaengine: ste_dma40: Fix event group bounds
      dmaengine: ste_dma40: Search all blocks for fixed logical channels
      dmaengine: ste_dma40: Validate fixed physical channel indexes
      dmaengine: ste_dma40: Validate memcpy configuration

 drivers/dma/ste_dma40.c | 387 ++++++++++++++++++++++++++++++++++--------------
 1 file changed, 277 insertions(+), 110 deletions(-)
---
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
change-id: 20260820-dma40-fixes-b99af66002bf

Best regards,
-- 
Linus Walleij <linusw@kernel.org>


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v3 01/22] dmaengine: ste_dma40: Fix physical cyclic capability
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 19:57   ` Frank Li
  2026-09-18 16:24 ` [PATCH v3 02/22] dmaengine: ste_dma40: Fix cyclic transfer residue Linus Walleij
                   ` (20 subsequent siblings)
  21 siblings, 1 reply; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij

d40_dmaengine_init() configures dma_both for physical channels that can
handle both slave and memcpy transfers. Physical DMA40 channel setup has
supported cyclic LLIs since cyclic transfer support was added, but the
DMA_CYCLIC capability is set on dma_slave a second time instead of
dma_both.

Set DMA_CYCLIC on dma_both so d40_ops_init() installs
device_prep_dma_cyclic and physical channels advertise cyclic support.

Fixes: 0c842b551063 ("dma40: cyclic xfer support")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 0d9ffa3e2663..e4d689c9eba8 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -2897,7 +2897,7 @@ static int __init d40_dmaengine_init(struct d40_base *base,
 	dma_cap_zero(base->dma_both.cap_mask);
 	dma_cap_set(DMA_SLAVE, base->dma_both.cap_mask);
 	dma_cap_set(DMA_MEMCPY, base->dma_both.cap_mask);
-	dma_cap_set(DMA_CYCLIC, base->dma_slave.cap_mask);
+	dma_cap_set(DMA_CYCLIC, base->dma_both.cap_mask);
 
 	d40_ops_init(base, &base->dma_both);
 	err = dmaenginem_async_device_register(&base->dma_both);

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 02/22] dmaengine: ste_dma40: Fix cyclic transfer residue
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 01/22] dmaengine: ste_dma40: Fix physical cyclic capability Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:44   ` sashiko-bot
  2026-09-18 21:41   ` Frank Li
  2026-09-18 16:24 ` [PATCH v3 03/22] dmaengine: ste_dma40: Fix failed start cleanup Linus Walleij
                   ` (19 subsequent siblings)
  21 siblings, 2 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij

DMA40 reads residue from the element count of the currently active LLI.
For a cyclic transfer this reports at most one period, not the bytes
remaining until the cyclic buffer wraps.

Once DMA40 advertises burst granularity, DMAengine PCM uses this residue
directly. For a four-period PCM buffer it consequently reports the
hardware pointer near three periods after every period interrupt. ALSA
eventually stops playback with -EIO although DMA period callbacks
continue.

Track the active period of cyclic descriptors and add all later periods
in the buffer to the current LLI residue. Update the active period in the
terminal-count interrupt before scheduling its callback so residue also
stays coherent around period boundaries.

Also reject invalid cyclic geometries before dividing or constructing
the scatterlist.

Fixes: 15c606686541 ("dmaengine: ste_dma40: indicate granularity on channels")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 42 +++++++++++++++++++++++++++++++++++++++---
 1 file changed, 39 insertions(+), 3 deletions(-)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index e4d689c9eba8..49e9139e0ad0 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -378,6 +378,9 @@ struct d40_lli_pool {
  * @lli_len: Number of llis of current descriptor.
  * @lli_current: Number of transferred llis.
  * @lcla_alloc: Number of LCLA entries allocated.
+ * @cyclic_buf_len: Length of the cyclic buffer.
+ * @cyclic_period_len: Length of one cyclic period.
+ * @cyclic_pos: Start of the current cyclic period in the buffer.
  * @txd: DMA engine struct. Used for among other things for communication
  * during a transfer.
  * @node: List entry.
@@ -396,6 +399,9 @@ struct d40_desc {
 	int				 lli_len;
 	int				 lli_current;
 	int				 lcla_alloc;
+	size_t				 cyclic_buf_len;
+	size_t				 cyclic_period_len;
+	size_t				 cyclic_pos;
 
 	struct dma_async_tx_descriptor	 txd;
 	struct list_head		 node;
@@ -1566,6 +1572,12 @@ static void dma_tc_handle(struct d40_chan *d40c)
 			if (d40d->lli_current == d40d->lli_len)
 				d40d->lli_current = 0;
 		}
+
+		if (d40d->cyclic_period_len) {
+			d40d->cyclic_pos += d40d->cyclic_period_len;
+			if (d40d->cyclic_pos >= d40d->cyclic_buf_len)
+				d40d->cyclic_pos = 0;
+		}
 	} else {
 		d40_lcla_free_all(d40c, d40d);
 
@@ -2108,15 +2120,27 @@ static bool d40_is_paused(struct d40_chan *d40c)
 
 }
 
-static u32 stedma40_residue(struct dma_chan *chan)
+static u32 stedma40_residue(struct dma_chan *chan, dma_cookie_t cookie)
 {
 	struct d40_chan *d40c =
 		container_of(chan, struct d40_chan, chan);
+	struct d40_desc *d40d;
 	u32 bytes_left;
 	unsigned long flags;
 
 	spin_lock_irqsave(&d40c->lock, flags);
 	bytes_left = d40_residue(d40c);
+
+	d40d = d40_first_active_get(d40c);
+	/*
+	 * The hardware residue is for the current LLI. Cyclic transfers use
+	 * one LLI for each period, so include the later periods in the buffer.
+	 */
+	if (d40d && d40d->txd.cookie == cookie && d40d->cyclic &&
+	    d40d->cyclic_period_len)
+		bytes_left += d40d->cyclic_buf_len - d40d->cyclic_pos -
+			      d40d->cyclic_period_len;
+
 	spin_unlock_irqrestore(&d40c->lock, flags);
 
 	return bytes_left;
@@ -2524,11 +2548,17 @@ dma40_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t dma_addr,
 		     size_t buf_len, size_t period_len,
 		     enum dma_transfer_direction direction, unsigned long flags)
 {
-	unsigned int periods = buf_len / period_len;
+	unsigned int periods;
 	struct dma_async_tx_descriptor *txd;
+	struct d40_desc *desc;
 	struct scatterlist *sg;
 	int i;
 
+	if (!buf_len || !period_len || buf_len % period_len)
+		return NULL;
+
+	periods = buf_len / period_len;
+
 	sg = kzalloc_objs(struct scatterlist, periods + 1, GFP_NOWAIT);
 	if (!sg)
 		return NULL;
@@ -2543,6 +2573,12 @@ dma40_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t dma_addr,
 
 	txd = d40_prep_sg(chan, sg, sg, periods, direction,
 			  DMA_PREP_INTERRUPT);
+	if (txd) {
+		desc = container_of(txd, struct d40_desc, txd);
+		desc->cyclic_buf_len = buf_len;
+		desc->cyclic_period_len = period_len;
+		desc->cyclic_pos = 0;
+	}
 
 	kfree(sg);
 
@@ -2563,7 +2599,7 @@ static enum dma_status d40_tx_status(struct dma_chan *chan,
 
 	ret = dma_cookie_status(chan, cookie, txstate);
 	if (ret != DMA_COMPLETE && txstate)
-		dma_set_residue(txstate, stedma40_residue(chan));
+		dma_set_residue(txstate, stedma40_residue(chan, cookie));
 
 	if (d40_is_paused(d40c))
 		ret = DMA_PAUSED;

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 03/22] dmaengine: ste_dma40: Fix failed start cleanup
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 01/22] dmaengine: ste_dma40: Fix physical cyclic capability Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 02/22] dmaengine: ste_dma40: Fix cyclic transfer residue Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 04/22] dmaengine: ste_dma40: Fix probe runtime PM disable Linus Walleij
                   ` (18 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij, sashiko-bot

If d40_start() fails after a queued descriptor has been moved to the
active list, d40_queue_start() currently returns NULL without unwinding
the transfer state or clearing the channel busy flag.

Fix this pre-existing error path by completing the descriptor through the
normal tasklet path, clearing the busy flag, balancing the runtime PM
reference and returning an error pointer to distinguish the failure from
the no-work case. Do not free the descriptor directly, since it has
already been submitted and has a DMA cookie.

When starting the next queued transfer from the completion handler, put
the completed descriptor on the done list first. This preserves FIFO
completion order if the new transfer fails to start and prevents the
completed cookie from moving backwards.

Use done-list membership rather than the cyclic flag to identify terminal
descriptors in the tasklet. A cyclic descriptor that failed to start is
then completed and removed instead of remaining permanently at the head
of the done list.

Fixes: 7d83a854a1a4 ("dma40: remove "hardware link with previous jobs" code")
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/dmaengine/20260618051539.15E201F000E9@smtp.kernel.org/
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 25 +++++++++++++++++--------
 1 file changed, 17 insertions(+), 8 deletions(-)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 49e9139e0ad0..451a87992058 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1537,8 +1537,15 @@ static struct d40_desc *d40_queue_start(struct d40_chan *d40c)
 		/* Start dma job */
 		err = d40_start(d40c);
 
-		if (err)
-			return NULL;
+		if (err) {
+			d40_desc_remove(d40d);
+			d40_desc_done(d40c, d40d);
+			d40c->pending_tx++;
+			d40c->busy = false;
+			pm_runtime_put_autosuspend(d40c->base->dev);
+			tasklet_schedule(&d40c->tasklet);
+			return ERR_PTR(err);
+		}
 	}
 
 	return d40d;
@@ -1588,14 +1595,14 @@ static void dma_tc_handle(struct d40_chan *d40c)
 			return;
 		}
 
+		d40_desc_remove(d40d);
+		d40_desc_done(d40c, d40d);
+
 		if (d40_queue_start(d40c) == NULL) {
 			d40c->busy = false;
 
 			pm_runtime_put_autosuspend(d40c->base->dev);
 		}
-
-		d40_desc_remove(d40d);
-		d40_desc_done(d40c, d40d);
 	}
 
 	d40c->pending_tx++;
@@ -1609,20 +1616,22 @@ static void dma_tasklet(struct tasklet_struct *t)
 	struct d40_desc *d40d;
 	unsigned long flags;
 	bool callback_active;
+	bool from_done;
 	struct dmaengine_desc_callback cb;
 
 	spin_lock_irqsave(&d40c->lock, flags);
 
 	/* Get first entry from the done list */
 	d40d = d40_first_done(d40c);
-	if (d40d == NULL) {
+	from_done = !!d40d;
+	if (!from_done) {
 		/* Check if we have reached here for cyclic job */
 		d40d = d40_first_active_get(d40c);
 		if (d40d == NULL || !d40d->cyclic)
 			goto check_pending_tx;
 	}
 
-	if (!d40d->cyclic)
+	if (from_done)
 		dma_cookie_complete(&d40d->txd);
 
 	/*
@@ -1638,7 +1647,7 @@ static void dma_tasklet(struct tasklet_struct *t)
 	callback_active = !!(d40d->txd.flags & DMA_PREP_INTERRUPT);
 	dmaengine_desc_get_callback(&d40d->txd, &cb);
 
-	if (!d40d->cyclic) {
+	if (from_done) {
 		if (async_tx_test_ack(&d40d->txd)) {
 			d40_desc_remove(d40d);
 			d40_desc_free(d40c, d40d);

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 04/22] dmaengine: ste_dma40: Fix probe runtime PM disable
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (2 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 03/22] dmaengine: ste_dma40: Fix failed start cleanup Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 05/22] dmaengine: ste_dma40: Check runtime PM in IRQ Linus Walleij
                   ` (17 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij, sashiko-bot

Some d40_probe() error paths jump to destroy_cache before runtime PM has
been enabled for the DMA controller device. The label unconditionally
calls pm_runtime_disable(), which increments disable_depth even though
this probe attempt never enabled runtime PM.

Track whether this probe attempt enabled runtime PM before disabling it on
the error path. This is not about a later deferred-probe retry, since the
driver is registered with platform_driver_probe(); it keeps the probe
unwind balanced.

The interrupt handler uses pm_runtime_get_if_active() and cannot
acknowledge a pending interrupt while runtime PM is disabled. Request the
IRQ only after enabling runtime PM so the handler cannot enter an
unacknowledged interrupt loop during probe.

Fixes: 0618c077a8c2 ("dmaengine: ste_dma40: Fix PM disable depth imbalance in d40_probe")
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/dmaengine/20260618051539.15E201F000E9@smtp.kernel.org/
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 451a87992058..28fcd196d95b 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3543,6 +3543,7 @@ static int __init d40_probe(struct platform_device *pdev)
 	struct resource *res;
 	struct resource res_lcpa;
 	int num_reserved_chans;
+	bool runtime_pm_enabled = false;
 	u32 val;
 	int ret;
 
@@ -3630,12 +3631,6 @@ static int __init d40_probe(struct platform_device *pdev)
 		goto destroy_cache;
 	}
 
-	ret = request_irq(base->irq, d40_handle_interrupt, 0, D40_NAME, base);
-	if (ret) {
-		d40_err(dev, "No IRQ defined\n");
-		goto destroy_cache;
-	}
-
 	if (base->plat_data->use_esram_lcla) {
 
 		base->lcpa_regulator = regulator_get(base->dev, "lcla_esram");
@@ -3664,6 +3659,13 @@ static int __init d40_probe(struct platform_device *pdev)
 	pm_runtime_mark_last_busy(base->dev);
 	pm_runtime_set_active(base->dev);
 	pm_runtime_enable(base->dev);
+	runtime_pm_enabled = true;
+
+	ret = request_irq(base->irq, d40_handle_interrupt, 0, D40_NAME, base);
+	if (ret) {
+		d40_err(dev, "No IRQ defined\n");
+		goto destroy_cache;
+	}
 
 	ret = d40_dmaengine_init(base, num_reserved_chans);
 	if (ret)
@@ -3699,7 +3701,8 @@ static int __init d40_probe(struct platform_device *pdev)
 		regulator_disable(base->lcpa_regulator);
 		regulator_put(base->lcpa_regulator);
 	}
-	pm_runtime_disable(base->dev);
+	if (runtime_pm_enabled)
+		pm_runtime_disable(base->dev);
 
  report_failure:
 	d40_err(dev, "probe failed\n");

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 05/22] dmaengine: ste_dma40: Check runtime PM in IRQ
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (3 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 04/22] dmaengine: ste_dma40: Fix probe runtime PM disable Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 06/22] dmaengine: ste_dma40: Handle runtime PM resume errors Linus Walleij
                   ` (16 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij, sashiko-bot

d40_handle_interrupt() reads DMA40 interrupt registers unconditionally. A
spurious interrupt can arrive while the device is runtime suspended, after
dma40_runtime_suspend() has disabled the GCC clock.

Avoid touching the registers unless the device is runtime active by taking
a conditional runtime PM reference. Return IRQ_NONE when the device is
suspended, and drop the reference after handling an active interrupt.

When CONFIG_PM is disabled, pm_runtime_get_if_active() returns -EINVAL even
though the registers remain accessible. Keep handling interrupts in that
configuration and only drop the runtime PM reference when one was acquired.

Fixes: 7fb3e75e1833 ("dmaengine/ste_dma40: support pm in dma40")
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/dmaengine/20260819225114.AE1511F000E9@smtp.kernel.org/
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 28fcd196d95b..1baee6f4d919 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1688,6 +1688,11 @@ static irqreturn_t d40_handle_interrupt(int irq, void *data)
 	u32 *regs = base->regs_interrupt;
 	struct d40_interrupt_lookup *il = base->gen_dmac.il;
 	u32 il_size = base->gen_dmac.il_size;
+	int ret;
+
+	ret = pm_runtime_get_if_active(base->dev);
+	if (IS_ENABLED(CONFIG_PM) && ret <= 0)
+		return IRQ_NONE;
 
 	spin_lock(&base->interrupt_lock);
 
@@ -1736,6 +1741,9 @@ static irqreturn_t d40_handle_interrupt(int irq, void *data)
 
 	spin_unlock(&base->interrupt_lock);
 
+	if (ret > 0)
+		pm_runtime_put_autosuspend(base->dev);
+
 	return IRQ_HANDLED;
 }
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 06/22] dmaengine: ste_dma40: Handle runtime PM resume errors
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (4 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 05/22] dmaengine: ste_dma40: Check runtime PM in IRQ Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:37   ` sashiko-bot
  2026-09-18 16:24 ` [PATCH v3 07/22] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status Linus Walleij
                   ` (15 subsequent siblings)
  21 siblings, 1 reply; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij

Several channel operations use pm_runtime_get_sync() and access DMA40
registers without checking whether runtime resume succeeded. If resume
fails, the registers may be inaccessible. pm_runtime_get_sync() also
increments the usage counter on failure, making error unwinding easy to
unbalance.

Use pm_runtime_resume_and_get() and abort each operation before accessing
hardware when resume fails. Acquire the runtime PM reference before
allocating a channel so failure needs no channel-allocation rollback, and
leave a queued descriptor pending when the controller cannot be resumed.

Fixes: 7fb3e75e1833 ("dmaengine/ste_dma40: support pm in dma40")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 45 +++++++++++++++++++++++++++++++++------------
 1 file changed, 33 insertions(+), 12 deletions(-)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 1baee6f4d919..c6f633353f3e 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1454,11 +1454,14 @@ static int d40_pause(struct dma_chan *chan)
 		return 0;
 
 	spin_lock_irqsave(&d40c->lock, flags);
-	pm_runtime_get_sync(d40c->base->dev);
+	res = pm_runtime_resume_and_get(d40c->base->dev);
+	if (res < 0)
+		goto unlock;
 
 	res = d40_channel_execute_command(d40c, D40_DMA_SUSPEND_REQ);
 
 	pm_runtime_put_autosuspend(d40c->base->dev);
+ unlock:
 	spin_unlock_irqrestore(&d40c->lock, flags);
 	return res;
 }
@@ -1478,13 +1481,16 @@ static int d40_resume(struct dma_chan *chan)
 		return 0;
 
 	spin_lock_irqsave(&d40c->lock, flags);
-	pm_runtime_get_sync(d40c->base->dev);
+	res = pm_runtime_resume_and_get(d40c->base->dev);
+	if (res < 0)
+		goto unlock;
 
 	/* If bytes left to transfer or linked tx resume job */
 	if (d40_residue(d40c) || d40_tx_is_linked(d40c))
 		res = d40_channel_execute_command(d40c, D40_DMA_RUN);
 
 	pm_runtime_put_autosuspend(d40c->base->dev);
+ unlock:
 	spin_unlock_irqrestore(&d40c->lock, flags);
 	return res;
 }
@@ -1521,8 +1527,13 @@ static struct d40_desc *d40_queue_start(struct d40_chan *d40c)
 
 	if (d40d != NULL) {
 		if (!d40c->busy) {
+			err = pm_runtime_resume_and_get(d40c->base->dev);
+			if (err < 0) {
+				chan_err(d40c, "Failed to resume DMA: %d\n",
+					 err);
+				return ERR_PTR(err);
+			}
 			d40c->busy = true;
-			pm_runtime_get_sync(d40c->base->dev);
 		}
 
 		/* Remove from queue */
@@ -2042,9 +2053,6 @@ static int d40_free_dma(struct d40_chan *d40c)
 	struct d40_phy_res *phy = d40c->phy_chan;
 	bool is_src;
 
-	/* Terminate all queued and active transfers */
-	d40_term_all(d40c);
-
 	if (phy == NULL) {
 		chan_err(d40c, "phy == null\n");
 		return -EINVAL;
@@ -2066,7 +2074,13 @@ static int d40_free_dma(struct d40_chan *d40c)
 		return -EINVAL;
 	}
 
-	pm_runtime_get_sync(d40c->base->dev);
+	res = pm_runtime_resume_and_get(d40c->base->dev);
+	if (res < 0)
+		return res;
+
+	/* Terminate all queued and active transfers */
+	d40_term_all(d40c);
+
 	res = d40_channel_execute_command(d40c, D40_DMA_STOP);
 	if (res) {
 		chan_err(d40c, "stop failed\n");
@@ -2458,10 +2472,14 @@ static int d40_alloc_chan_resources(struct dma_chan *chan)
 		err = d40_config_memcpy(d40c);
 		if (err) {
 			chan_err(d40c, "Failed to configure memcpy channel\n");
-			goto mark_last_busy;
+			goto unlock;
 		}
 	}
 
+	err = pm_runtime_resume_and_get(d40c->base->dev);
+	if (err < 0)
+		goto unlock;
+
 	err = d40_allocate_channel(d40c, &is_free_phy);
 	if (err) {
 		chan_err(d40c, "Failed to allocate channel\n");
@@ -2469,8 +2487,6 @@ static int d40_alloc_chan_resources(struct dma_chan *chan)
 		goto mark_last_busy;
 	}
 
-	pm_runtime_get_sync(d40c->base->dev);
-
 	d40_set_prio_realtime(d40c);
 
 	if (chan_is_logical(d40c)) {
@@ -2502,6 +2518,7 @@ static int d40_alloc_chan_resources(struct dma_chan *chan)
 		d40_config_write(d40c);
  mark_last_busy:
 	pm_runtime_put_autosuspend(d40c->base->dev);
+ unlock:
 	spin_unlock_irqrestore(&d40c->lock, flags);
 	return err;
 }
@@ -2658,7 +2675,10 @@ static int d40_terminate_all(struct dma_chan *chan)
 
 	spin_lock_irqsave(&d40c->lock, flags);
 
-	pm_runtime_get_sync(d40c->base->dev);
+	ret = pm_runtime_resume_and_get(d40c->base->dev);
+	if (ret < 0)
+		goto unlock;
+
 	ret = d40_channel_execute_command(d40c, D40_DMA_STOP);
 	if (ret)
 		chan_err(d40c, "Failed to stop channel\n");
@@ -2669,8 +2689,9 @@ static int d40_terminate_all(struct dma_chan *chan)
 		pm_runtime_put_autosuspend(d40c->base->dev);
 	d40c->busy = false;
 
+ unlock:
 	spin_unlock_irqrestore(&d40c->lock, flags);
-	return 0;
+	return ret;
 }
 
 static int

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 07/22] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (5 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 06/22] dmaengine: ste_dma40: Handle runtime PM resume errors Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 08/22] dmaengine: ste_dma40: Init hardware before registration Linus Walleij
                   ` (14 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij

d40_handle_interrupt() returns IRQ_HANDLED even when it does not find and
acknowledge an interrupt belonging to a registered DMA40 channel. If the
interrupt line remains asserted without any matching status bit, reporting
it as handled prevents the generic interrupt code from detecting the
stuck interrupt.

Track whether the handler acknowledges an interrupt and return IRQ_NONE
otherwise, allowing the generic spurious interrupt detector to disable a
faulty interrupt line.

Fixes: 8d318a50b3d7 ("DMAENGINE: Support for ST-Ericssons DMA40 block v3")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index c6f633353f3e..ba223d9dd3c9 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1690,6 +1690,7 @@ static void dma_tasklet(struct tasklet_struct *t)
 
 static irqreturn_t d40_handle_interrupt(int irq, void *data)
 {
+	irqreturn_t handled = IRQ_NONE;
 	int i;
 	u32 idx;
 	u32 row;
@@ -1738,6 +1739,7 @@ static irqreturn_t d40_handle_interrupt(int irq, void *data)
 
 		/* ACK interrupt */
 		writel(BIT(idx), base->virtbase + il[row].clr);
+		handled = IRQ_HANDLED;
 
 		spin_lock(&d40c->lock);
 
@@ -1755,7 +1757,7 @@ static irqreturn_t d40_handle_interrupt(int irq, void *data)
 	if (ret > 0)
 		pm_runtime_put_autosuspend(base->dev);
 
-	return IRQ_HANDLED;
+	return handled;
 }
 
 static int d40_validate_conf(struct d40_chan *d40c,

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 08/22] dmaengine: ste_dma40: Init hardware before registration
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (6 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 07/22] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 09/22] dmaengine: ste_dma40: Fix probe IRQ leak Linus Walleij
                   ` (13 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij, sashiko-bot

d40_probe() enables the interrupt handler and registers DMAengine devices
before calling d40_hw_init(). An interrupt pending from the bootloader can
therefore reach the handler while the hardware interrupt state is not
initialized and channel lookup entries are empty. The handler deliberately
does not acknowledge an interrupt for an unknown channel, so a level IRQ
can retrigger continuously.

Request the IRQ with IRQF_NO_AUTOEN, then initialize the hardware and set
the DMA segment limit. Enable the IRQ before registering DMAengine devices.
This ensures the handler and clients only observe initialized hardware
while still letting request_irq() fail before hardware interrupts are
enabled.

Fixes: 8d318a50b3d7 ("DMAENGINE: Support for ST-Ericssons DMA40 block v3")
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/dmaengine/20260819225215.5EA2F1F000E9@smtp.kernel.org/
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index ba223d9dd3c9..dc6a63f148ff 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3692,19 +3692,21 @@ static int __init d40_probe(struct platform_device *pdev)
 	pm_runtime_enable(base->dev);
 	runtime_pm_enabled = true;
 
-	ret = request_irq(base->irq, d40_handle_interrupt, 0, D40_NAME, base);
+	ret = request_irq(base->irq, d40_handle_interrupt, IRQF_NO_AUTOEN,
+			  D40_NAME, base);
 	if (ret) {
 		d40_err(dev, "No IRQ defined\n");
 		goto destroy_cache;
 	}
 
-	ret = d40_dmaengine_init(base, num_reserved_chans);
-	if (ret)
-		goto destroy_cache;
-
 	dma_set_max_seg_size(base->dev, STEDMA40_MAX_SEG_SIZE);
 
 	d40_hw_init(base);
+	enable_irq(base->irq);
+
+	ret = d40_dmaengine_init(base, num_reserved_chans);
+	if (ret)
+		goto destroy_cache;
 
 	ret = of_dma_controller_register(np, d40_xlate, NULL);
 	if (ret) {

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 09/22] dmaengine: ste_dma40: Fix probe IRQ leak
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (7 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 08/22] dmaengine: ste_dma40: Init hardware before registration Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 10/22] dmaengine: ste_dma40: Fix DMA registration unwind Linus Walleij
                   ` (12 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij, sashiko-bot

d40_probe() registers the hardware interrupt before several later probe
steps that can fail. Those error paths jump to destroy_cache without
freeing the IRQ, leaving the handler registered after probe resources have
been released.

Track successful IRQ registration and free the IRQ on later probe failure.

Fixes: 8d318a50b3d7 ("DMAENGINE: Support for ST-Ericssons DMA40 block v3")
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/dmaengine/20260618051539.15E201F000E9@smtp.kernel.org/
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index dc6a63f148ff..38c1cc43cb87 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3575,6 +3575,7 @@ static int __init d40_probe(struct platform_device *pdev)
 	struct resource res_lcpa;
 	int num_reserved_chans;
 	bool runtime_pm_enabled = false;
+	bool irq_requested = false;
 	u32 val;
 	int ret;
 
@@ -3698,6 +3699,7 @@ static int __init d40_probe(struct platform_device *pdev)
 		d40_err(dev, "No IRQ defined\n");
 		goto destroy_cache;
 	}
+	irq_requested = true;
 
 	dma_set_max_seg_size(base->dev, STEDMA40_MAX_SEG_SIZE);
 
@@ -3734,6 +3736,8 @@ static int __init d40_probe(struct platform_device *pdev)
 		regulator_disable(base->lcpa_regulator);
 		regulator_put(base->lcpa_regulator);
 	}
+	if (irq_requested)
+		free_irq(base->irq, base);
 	if (runtime_pm_enabled)
 		pm_runtime_disable(base->dev);
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 10/22] dmaengine: ste_dma40: Fix DMA registration unwind
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (8 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 09/22] dmaengine: ste_dma40: Fix probe IRQ leak Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 11/22] dmaengine: ste_dma40: Fix LCLA allocation order Linus Walleij
                   ` (11 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij, sashiko-bot

d40_dmaengine_init() registers DMAengine devices using devres-managed
unregister actions. If probe fails after one of those registrations, the
DMAengine devices stay visible until devres unwinds after d40_probe()
returns.

The channel tasklets are also initialized before each DMAengine device is
registered. An interrupt can therefore have queued a tasklet by the time a
later registration step fails, but unregistering the DMAengine devices
does not drain that tasklet before probe-owned storage is released.

Add tasklet cleanup actions to the DMAengine registration devres group.
On failure, free the IRQ before releasing the group so no new tasklets can
be scheduled, then unregister the DMAengine devices and drain their
tasklets before freeing the remaining probe resources. Keep the managed
registrations and cleanup actions in place on successful probe by removing
only the temporary group markers.

Fixes: 42ae6f1695be ("dmaengine: ste_dma40: Remove platform data")
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/dmaengine/20260819225215.5EA2F1F000E9@smtp.kernel.org/
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 46 ++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 44 insertions(+), 2 deletions(-)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 38c1cc43cb87..af885561f693 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -2898,6 +2898,18 @@ static void __init d40_chan_init(struct d40_base *base, struct dma_device *dma,
 	}
 }
 
+static void d40_kill_tasklets(void *data)
+{
+	struct dma_device *dma = data;
+	struct d40_chan *d40c;
+	struct dma_chan *chan;
+
+	list_for_each_entry(chan, &dma->channels, device_node) {
+		d40c = container_of(chan, struct d40_chan, chan);
+		tasklet_kill(&d40c->tasklet);
+	}
+}
+
 static void d40_ops_init(struct d40_base *base, struct dma_device *dev)
 {
 	if (dma_has_cap(DMA_SLAVE, dev->cap_mask)) {
@@ -2944,6 +2956,11 @@ static int __init d40_dmaengine_init(struct d40_base *base,
 
 	d40_ops_init(base, &base->dma_slave);
 
+	err = devm_add_action_or_reset(base->dev, d40_kill_tasklets,
+				       &base->dma_slave);
+	if (err)
+		goto exit;
+
 	err = dmaenginem_async_device_register(&base->dma_slave);
 
 	if (err) {
@@ -2959,6 +2976,11 @@ static int __init d40_dmaengine_init(struct d40_base *base,
 
 	d40_ops_init(base, &base->dma_memcpy);
 
+	err = devm_add_action_or_reset(base->dev, d40_kill_tasklets,
+				       &base->dma_memcpy);
+	if (err)
+		goto exit;
+
 	err = dmaenginem_async_device_register(&base->dma_memcpy);
 
 	if (err) {
@@ -2976,6 +2998,12 @@ static int __init d40_dmaengine_init(struct d40_base *base,
 	dma_cap_set(DMA_CYCLIC, base->dma_both.cap_mask);
 
 	d40_ops_init(base, &base->dma_both);
+
+	err = devm_add_action_or_reset(base->dev, d40_kill_tasklets,
+				       &base->dma_both);
+	if (err)
+		goto exit;
+
 	err = dmaenginem_async_device_register(&base->dma_both);
 
 	if (err) {
@@ -3573,6 +3601,7 @@ static int __init d40_probe(struct platform_device *pdev)
 	struct d40_base *base;
 	struct resource *res;
 	struct resource res_lcpa;
+	void *dmaenginem_reg_group;
 	int num_reserved_chans;
 	bool runtime_pm_enabled = false;
 	bool irq_requested = false;
@@ -3706,20 +3735,33 @@ static int __init d40_probe(struct platform_device *pdev)
 	d40_hw_init(base);
 	enable_irq(base->irq);
 
+	dmaenginem_reg_group = devres_open_group(dev, NULL, GFP_KERNEL);
+	if (!dmaenginem_reg_group) {
+		ret = -ENOMEM;
+		goto destroy_cache;
+	}
+
 	ret = d40_dmaengine_init(base, num_reserved_chans);
 	if (ret)
-		goto destroy_cache;
+		goto release_dmaenginem;
 
 	ret = of_dma_controller_register(np, d40_xlate, NULL);
 	if (ret) {
 		dev_err(dev,
 			"could not register of_dma_controller\n");
-		goto destroy_cache;
+		goto release_dmaenginem;
 	}
+	devres_remove_group(dev, dmaenginem_reg_group);
 
 	dev_info(base->dev, "initialized\n");
 	return 0;
 
+ release_dmaenginem:
+	if (irq_requested) {
+		free_irq(base->irq, base);
+		irq_requested = false;
+	}
+	devres_release_group(dev, dmaenginem_reg_group);
  destroy_cache:
 	if (base->lcla_pool.dma_addr)
 		dma_unmap_single(base->dev, base->lcla_pool.dma_addr,

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 11/22] dmaengine: ste_dma40: Fix LCLA allocation order
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (9 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 10/22] dmaengine: ste_dma40: Fix DMA registration unwind Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 12/22] dmaengine: ste_dma40: Fix probe LCLA free Linus Walleij
                   ` (10 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij, sashiko-bot

d40_lcla_allocate() calculates the number of pages needed for LCLA, but
passes that raw page count as the allocation order to __get_free_pages().
The same value is later passed to free_pages().

Store the allocation order with get_order() instead, and use a separate
byte size for allocation diagnostics, fallback kmalloc() sizing and DMA
mapping.

Fixes: 508849ade23c ("DMAENGINE: ste_dma40: allocate LCLA dynamically")
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/dmaengine/20260819225114.AE1511F000E9@smtp.kernel.org/
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 33 ++++++++++++++++-----------------
 1 file changed, 16 insertions(+), 17 deletions(-)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index af885561f693..3ffed78b0527 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -417,8 +417,8 @@ struct d40_desc {
  * @dma_addr: DMA address, if mapped
  * @base_unaligned: The original kmalloc pointer, if kmalloc is used.
  * This pointer is only there for clean-up on error.
- * @pages: The number of pages needed for all physical channels.
- * Only used later for clean-up on error
+ * @alloc_order: Order used for the LCLA page allocation.
+ * Only used later for clean-up on error.
  * @lock: Lock to protect the content in this struct.
  * @alloc_map: big map over which LCLA entry is own by which job.
  */
@@ -426,7 +426,7 @@ struct d40_lcla_pool {
 	void		*base;
 	dma_addr_t	dma_addr;
 	void		*base_unaligned;
-	int		 pages;
+	unsigned int	 alloc_order;
 	spinlock_t	 lock;
 	struct d40_desc	**alloc_map;
 };
@@ -3462,6 +3462,7 @@ static void __init d40_hw_init(struct d40_base *base)
 static int __init d40_lcla_allocate(struct d40_base *base)
 {
 	struct d40_lcla_pool *pool = &base->lcla_pool;
+	size_t lcla_size = SZ_1K * base->num_phy_chans;
 	unsigned long *page_list;
 	int i, j;
 	int ret;
@@ -3477,20 +3478,20 @@ static int __init d40_lcla_allocate(struct d40_base *base)
 	if (!page_list)
 		return -ENOMEM;
 
-	/* Calculating how many pages that are required */
-	base->lcla_pool.pages = SZ_1K * base->num_phy_chans / PAGE_SIZE;
+	base->lcla_pool.alloc_order = get_order(lcla_size);
 
 	for (i = 0; i < MAX_LCLA_ALLOC_ATTEMPTS; i++) {
 		page_list[i] = __get_free_pages(GFP_KERNEL,
-						base->lcla_pool.pages);
+						base->lcla_pool.alloc_order);
 		if (!page_list[i]) {
 
-			d40_err(base->dev, "Failed to allocate %d pages.\n",
-				base->lcla_pool.pages);
+			d40_err(base->dev, "Failed to allocate %zu bytes.\n",
+				lcla_size);
 			ret = -ENOMEM;
 
 			for (j = 0; j < i; j++)
-				free_pages(page_list[j], base->lcla_pool.pages);
+				free_pages(page_list[j],
+					   base->lcla_pool.alloc_order);
 			goto free_page_list;
 		}
 
@@ -3500,7 +3501,7 @@ static int __init d40_lcla_allocate(struct d40_base *base)
 	}
 
 	for (j = 0; j < i; j++)
-		free_pages(page_list[j], base->lcla_pool.pages);
+		free_pages(page_list[j], base->lcla_pool.alloc_order);
 
 	if (i < MAX_LCLA_ALLOC_ATTEMPTS) {
 		base->lcla_pool.base = (void *)page_list[i];
@@ -3510,10 +3511,9 @@ static int __init d40_lcla_allocate(struct d40_base *base)
 		 * alignment, try with allocating a big buffer.
 		 */
 		dev_warn(base->dev,
-			 "[%s] Failed to get %d pages @ 18 bit align.\n",
-			 __func__, base->lcla_pool.pages);
-		base->lcla_pool.base_unaligned = kmalloc(SZ_1K *
-							 base->num_phy_chans +
+			 "[%s] Failed to get %zu bytes @ 18 bit align.\n",
+			 __func__, lcla_size);
+		base->lcla_pool.base_unaligned = kmalloc(lcla_size +
 							 LCLA_ALIGNMENT,
 							 GFP_KERNEL);
 		if (!base->lcla_pool.base_unaligned) {
@@ -3525,8 +3525,7 @@ static int __init d40_lcla_allocate(struct d40_base *base)
 						 LCLA_ALIGNMENT);
 	}
 
-	pool->dma_addr = dma_map_single(base->dev, pool->base,
-					SZ_1K * base->num_phy_chans,
+	pool->dma_addr = dma_map_single(base->dev, pool->base, lcla_size,
 					DMA_TO_DEVICE);
 	if (dma_mapping_error(base->dev, pool->dma_addr)) {
 		pool->dma_addr = 0;
@@ -3770,7 +3769,7 @@ static int __init d40_probe(struct platform_device *pdev)
 
 	if (!base->lcla_pool.base_unaligned && base->lcla_pool.base)
 		free_pages((unsigned long)base->lcla_pool.base,
-			   base->lcla_pool.pages);
+			   base->lcla_pool.alloc_order);
 
 	kfree(base->lcla_pool.base_unaligned);
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 12/22] dmaengine: ste_dma40: Fix probe LCLA free
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (10 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 11/22] dmaengine: ste_dma40: Fix LCLA allocation order Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 13/22] dmaengine: ste_dma40: Put the LCPA SRAM node Linus Walleij
                   ` (9 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij, sashiko-bot

When LCLA is placed in ESRAM, d40_probe() stores a devm_ioremap()
address in lcla_pool.base and leaves base_unaligned unset. The
destroy_cache error path can then pass the ioremap address to
free_pages().

Only free lcla_pool.base with free_pages() when the driver allocated the
LCLA pool from normal memory. The ESRAM mapping is devm-managed.

Fixes: 339f5041089a ("dmaengine: ste_dma40: Use managed resources")
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/dmaengine/20260819225114.AE1511F000E9@smtp.kernel.org/
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 3ffed78b0527..104447f7080e 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3767,7 +3767,8 @@ static int __init d40_probe(struct platform_device *pdev)
 				 SZ_1K * base->num_phy_chans,
 				 DMA_TO_DEVICE);
 
-	if (!base->lcla_pool.base_unaligned && base->lcla_pool.base)
+	if (!base->plat_data->use_esram_lcla &&
+	    !base->lcla_pool.base_unaligned && base->lcla_pool.base)
 		free_pages((unsigned long)base->lcla_pool.base,
 			   base->lcla_pool.alloc_order);
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 13/22] dmaengine: ste_dma40: Put the LCPA SRAM node
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (11 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 12/22] dmaengine: ste_dma40: Fix probe LCLA free Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 14/22] dmaengine: ste_dma40: Fix memcpy channel parsing Linus Walleij
                   ` (8 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij

of_parse_phandle() takes a reference to the LCPA SRAM node, but d40_probe()
does not release it after translating the node into a resource. This leaks
the node reference for the lifetime of the system.

Put the node immediately after of_address_to_resource() so both the success
and error paths release the reference.

Fixes: 5a1a3b9c19dd ("dmaengine: ste_dma40: Get LCPA SRAM from SRAM node")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 104447f7080e..557b9620c488 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3632,6 +3632,7 @@ static int __init d40_probe(struct platform_device *pdev)
 	}
 	/* This is no device so read the address directly from the node */
 	ret = of_address_to_resource(np_lcpa, 0, &res_lcpa);
+	of_node_put(np_lcpa);
 	if (ret) {
 		dev_err(dev, "no LCPA SRAM resource\n");
 		goto report_failure;

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 14/22] dmaengine: ste_dma40: Fix memcpy channel parsing
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (12 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 13/22] dmaengine: ste_dma40: Put the LCPA SRAM node Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 15/22] dmaengine: ste_dma40: Validate disabled channel indexes Linus Walleij
                   ` (7 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij, sashiko-bot

d40_of_probe() validates the memcpy-channels property against
D40_MEMCPY_MAX_CHANS, but reads the property directly into a smaller global
array. A long property can therefore overwrite adjacent data. The mutable
global also lets a later DMA40 instance replace the memcpy channel mapping
used for future allocations on an earlier instance.

Store the mapping in the per-device platform data, validate the property
against that storage, and check the property read result. The property is
required and d40_probe() always populates the platform data, so remove the
obsolete global mapping and fallback.

Fixes: a7dacb68b35a ("dmaengine: ste_dma40: Allow memcpy channels to be configured from DT")
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/dmaengine/20260819225215.5EA2F1F000E9@smtp.kernel.org/
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 45 ++++++++++++++-------------------------------
 1 file changed, 14 insertions(+), 31 deletions(-)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 557b9620c488..a53155b1fe1f 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -28,6 +28,8 @@
 #include "ste_dma40.h"
 #include "ste_dma40_ll.h"
 
+#define D40_MEMCPY_MAX_CHANS	8
+
 /**
  * struct stedma40_platform_data - Configuration struct for the dma device.
  *
@@ -41,6 +43,7 @@
  * to use SoftLLI.
  * @use_esram_lcla: flag for mapping the lcla into esram region
  * @num_of_memcpy_chans: The number of channels reserved for memcpy.
+ * @memcpy_channels: The event lines used for memcpy.
  * @num_of_phy_chans: The number of physical channels implemented in HW.
  * 0 means reading the number of channels from DMA HW but this is only valid
  * for 'multiple of 4' channels, like 8.
@@ -51,6 +54,7 @@ struct stedma40_platform_data {
 	int				 num_of_soft_lli_chans;
 	bool				 use_esram_lcla;
 	int				 num_of_memcpy_chans;
+	u32				 memcpy_channels[D40_MEMCPY_MAX_CHANS];
 	int				 num_of_phy_chans;
 };
 
@@ -86,25 +90,6 @@ struct stedma40_platform_data {
 #define D40_ALLOC_PHY		BIT(30)
 #define D40_ALLOC_LOG_FREE	0
 
-#define D40_MEMCPY_MAX_CHANS	8
-
-/* Reserved event lines for memcpy only. */
-#define DB8500_DMA_MEMCPY_EV_0	51
-#define DB8500_DMA_MEMCPY_EV_1	56
-#define DB8500_DMA_MEMCPY_EV_2	57
-#define DB8500_DMA_MEMCPY_EV_3	58
-#define DB8500_DMA_MEMCPY_EV_4	59
-#define DB8500_DMA_MEMCPY_EV_5	60
-
-static int dma40_memcpy_channels[] = {
-	DB8500_DMA_MEMCPY_EV_0,
-	DB8500_DMA_MEMCPY_EV_1,
-	DB8500_DMA_MEMCPY_EV_2,
-	DB8500_DMA_MEMCPY_EV_3,
-	DB8500_DMA_MEMCPY_EV_4,
-	DB8500_DMA_MEMCPY_EV_5,
-};
-
 /* Default configuration for physical memcpy */
 static const struct stedma40_chan_cfg dma40_memcpy_conf_phy = {
 	.mode = STEDMA40_MODE_PHYSICAL,
@@ -2023,7 +2008,8 @@ static int d40_config_memcpy(struct d40_chan *d40c)
 
 	if (dma_has_cap(DMA_MEMCPY, cap) && !dma_has_cap(DMA_SLAVE, cap)) {
 		d40c->dma_cfg = dma40_memcpy_conf_log;
-		d40c->dma_cfg.dev_type = dma40_memcpy_channels[d40c->chan.chan_id];
+		d40c->dma_cfg.dev_type =
+			d40c->base->plat_data->memcpy_channels[d40c->chan.chan_id];
 
 		d40_log_cfg(&d40c->dma_cfg,
 			    &d40c->log_def.lcsp1, &d40c->log_def.lcsp3);
@@ -3293,12 +3279,7 @@ static int __init d40_hw_detect_init(struct platform_device *pdev,
 	num_phy_chans = min(num_phy_chans, STEDMA40_MAX_PHYS);
 
 	/* The number of channels used for memcpy */
-	if (plat_data->num_of_memcpy_chans)
-		num_memcpy_chans = plat_data->num_of_memcpy_chans;
-	else
-		num_memcpy_chans = ARRAY_SIZE(dma40_memcpy_channels);
-
-	num_memcpy_chans = min(num_memcpy_chans, D40_MEMCPY_MAX_CHANS);
+	num_memcpy_chans = plat_data->num_of_memcpy_chans;
 	num_log_chans = num_phy_chans * D40_MAX_LOG_CHAN_PER_PHY;
 
 	dev_info(dev,
@@ -3547,6 +3528,7 @@ static int __init d40_of_probe(struct device *dev,
 	struct stedma40_platform_data *pdata;
 	int num_phy = 0, num_memcpy = 0, num_disabled = 0;
 	const __be32 *list;
+	int ret;
 
 	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
 	if (!pdata)
@@ -3560,18 +3542,19 @@ static int __init d40_of_probe(struct device *dev,
 	list = of_get_property(np, "memcpy-channels", &num_memcpy);
 	num_memcpy /= sizeof(*list);
 
-	if (num_memcpy > D40_MEMCPY_MAX_CHANS || num_memcpy <= 0) {
+	if (num_memcpy > ARRAY_SIZE(pdata->memcpy_channels) ||
+	    num_memcpy <= 0) {
 		d40_err(dev,
 			"Invalid number of memcpy channels specified (%d)\n",
 			num_memcpy);
 		return -EINVAL;
 	}
+	ret = of_property_read_u32_array(np, "memcpy-channels",
+					 pdata->memcpy_channels, num_memcpy);
+	if (ret)
+		return ret;
 	pdata->num_of_memcpy_chans = num_memcpy;
 
-	of_property_read_u32_array(np, "memcpy-channels",
-				   dma40_memcpy_channels,
-				   num_memcpy);
-
 	list = of_get_property(np, "disabled-channels", &num_disabled);
 	num_disabled /= sizeof(*list);
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 15/22] dmaengine: ste_dma40: Validate disabled channel indexes
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (13 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 14/22] dmaengine: ste_dma40: Fix memcpy channel parsing Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 16/22] dmaengine: ste_dma40: Validate DMA specifier length Linus Walleij
                   ` (6 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij

d40_of_probe() checks how many entries disabled-channels contains, but not
the channel numbers themselves. d40_phy_res_init() later uses every value
as an index into base->phy_res, whose size is the number of channels found
in this DMA40 instance. An out-of-range value can therefore write beyond
the allocation.

Validate each disabled channel against the detected hardware channel count
before allocating and initializing the channel resources.

Fixes: 499c2bc3cc89 ("dmaengine: ste_dma40: Fetch disabled channels from DT")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index a53155b1fe1f..6a76e7d8ebd7 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3278,6 +3278,15 @@ static int __init d40_hw_detect_init(struct platform_device *pdev,
 
 	num_phy_chans = min(num_phy_chans, STEDMA40_MAX_PHYS);
 
+	for (i = 0; plat_data->disabled_channels[i] != -1; i++) {
+		int chan = plat_data->disabled_channels[i];
+
+		if (chan < 0 || chan >= num_phy_chans) {
+			dev_err(dev, "Invalid disabled channel %d\n", chan);
+			return -EINVAL;
+		}
+	}
+
 	/* The number of channels used for memcpy */
 	num_memcpy_chans = plat_data->num_of_memcpy_chans;
 	num_log_chans = num_phy_chans * D40_MAX_LOG_CHAN_PER_PHY;

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 16/22] dmaengine: ste_dma40: Validate DMA specifier length
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (14 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 15/22] dmaengine: ste_dma40: Validate disabled channel indexes Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 17/22] dmaengine: ste_dma40: Reject direction changes after allocation Linus Walleij
                   ` (5 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij

The DMA40 binding requires three cells, but d40_xlate() reads args[0],
args[1], and args[2] without checking args_count. A malformed provider node
can specify fewer cells, leaving some of these values uninitialized when
the OF DMA core invokes the translation callback.

Reject specifiers that do not contain exactly three cells before reading
the argument array.

Fixes: fa332de5c6b3 ("dmaengine: ste_dma40: Supply full Device Tree parsing support")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 6a76e7d8ebd7..abb3ca015417 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -2408,6 +2408,9 @@ static struct dma_chan *d40_xlate(struct of_phandle_args *dma_spec,
 	dma_cap_mask_t cap;
 	u32 flags;
 
+	if (dma_spec->args_count != 3)
+		return NULL;
+
 	memset(&cfg, 0, sizeof(struct stedma40_chan_cfg));
 
 	dma_cap_zero(cap);

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 17/22] dmaengine: ste_dma40: Reject direction changes after allocation
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (15 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 16/22] dmaengine: ste_dma40: Validate DMA specifier length Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 18/22] dmaengine: ste_dma40: Fix logical channel bounds check Linus Walleij
                   ` (4 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij, sashiko-bot

Logical channel allocation uses the configured direction to select the
source or destination allocation mask, derive the logical channel number,
and choose the LCPA location.

d40_set_runtime_config_write() nevertheless overwrites the configured
direction when a transfer is prepared for the opposite direction.
d40_free_dma() then clears the wrong allocation mask, leaking the original
resource and potentially releasing one used by another client.

Reject directions that differ from the direction used during allocation
and propagate runtime configuration errors to callers. Skip slave runtime
configuration for memcpy transfers, which also use d40_prep_sg() but do
not require it.

Fixes: 95e1400fa131 ("DMAENGINE: add runtime slave config to DMA40 v3")
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/dmaengine/20260618051539.15E201F000E9@smtp.kernel.org/
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 29 ++++++++++++++---------------
 1 file changed, 14 insertions(+), 15 deletions(-)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index abb3ca015417..07a940a26074 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -2281,7 +2281,13 @@ d40_prep_sg(struct dma_chan *dchan, struct scatterlist *sg_src,
 		return NULL;
 	}
 
-	d40_set_runtime_config_write(dchan, &chan->slave_config, direction);
+	if (direction != DMA_MEM_TO_MEM) {
+		ret = d40_set_runtime_config_write(dchan,
+						   &chan->slave_config,
+						   direction);
+		if (ret)
+			return NULL;
+	}
 
 	spin_lock_irqsave(&chan->lock, flags);
 
@@ -2745,6 +2751,13 @@ static int d40_set_runtime_config_write(struct dma_chan *chan,
 		return -EINVAL;
 	}
 
+	if (direction != cfg->dir) {
+		chan_err(d40c,
+			 "transfer direction %d differs from allocated direction %d\n",
+			 direction, cfg->dir);
+		return -EINVAL;
+	}
+
 	src_addr_width = config->src_addr_width;
 	src_maxburst = config->src_maxburst;
 	dst_addr_width = config->dst_addr_width;
@@ -2753,13 +2766,6 @@ static int d40_set_runtime_config_write(struct dma_chan *chan,
 	if (direction == DMA_DEV_TO_MEM) {
 		config_addr = config->src_addr;
 
-		if (cfg->dir != DMA_DEV_TO_MEM)
-			dev_dbg(d40c->base->dev,
-				"channel was not configured for peripheral "
-				"to memory transfer (%d) overriding\n",
-				cfg->dir);
-		cfg->dir = DMA_DEV_TO_MEM;
-
 		/* Configure the memory side */
 		if (dst_addr_width == DMA_SLAVE_BUSWIDTH_UNDEFINED)
 			dst_addr_width = src_addr_width;
@@ -2769,13 +2775,6 @@ static int d40_set_runtime_config_write(struct dma_chan *chan,
 	} else if (direction == DMA_MEM_TO_DEV) {
 		config_addr = config->dst_addr;
 
-		if (cfg->dir != DMA_MEM_TO_DEV)
-			dev_dbg(d40c->base->dev,
-				"channel was not configured for memory "
-				"to peripheral transfer (%d) overriding\n",
-				cfg->dir);
-		cfg->dir = DMA_MEM_TO_DEV;
-
 		/* Configure the memory side */
 		if (src_addr_width == DMA_SLAVE_BUSWIDTH_UNDEFINED)
 			src_addr_width = dst_addr_width;

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 18/22] dmaengine: ste_dma40: Fix logical channel bounds check
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (16 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 17/22] dmaengine: ste_dma40: Reject direction changes after allocation Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 19/22] dmaengine: ste_dma40: Fix event group bounds Linus Walleij
                   ` (3 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij, sashiko-bot

d40_validate_conf() checks the raw dev_type against num_log_chans,
but d40_allocate_channel() derives the lookup_log_chans index as either
2 * dev_type or 2 * dev_type + 1.

Validate the dev_type against the derived logical channel index limit so
channel allocation cannot write past lookup_log_chans.

Fixes: 26955c07dcf3 ("dmaengine: ste_dma40: Amalgamate DMA source and destination channel numbers")
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/dmaengine/20260618051539.15E201F000E9@smtp.kernel.org/
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 07a940a26074..26dbef630eef 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1750,15 +1750,26 @@ static int d40_validate_conf(struct d40_chan *d40c,
 {
 	int res = 0;
 	bool is_log = conf->mode == STEDMA40_MODE_LOGICAL;
+	bool invalid_dev_type = conf->dev_type < 0;
 
 	if (!conf->dir) {
 		chan_err(d40c, "Invalid direction.\n");
 		res = -EINVAL;
 	}
 
-	if ((is_log && conf->dev_type > d40c->base->num_log_chans)  ||
-	    (!is_log && conf->dev_type > d40c->base->num_phy_chans) ||
-	    (conf->dev_type < 0)) {
+	if (!invalid_dev_type && is_log) {
+		int max_dev_type;
+
+		if (conf->dir == DMA_DEV_TO_MEM)
+			max_dev_type = DIV_ROUND_UP(d40c->base->num_log_chans, 2);
+		else
+			max_dev_type = d40c->base->num_log_chans / 2;
+
+		invalid_dev_type = conf->dev_type >= max_dev_type;
+	}
+
+	if (invalid_dev_type ||
+	    (!is_log && conf->dev_type > d40c->base->num_phy_chans)) {
 		chan_err(d40c, "Invalid device type (%d)\n", conf->dev_type);
 		res = -EINVAL;
 	}

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 19/22] dmaengine: ste_dma40: Fix event group bounds
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (17 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 18/22] dmaengine: ste_dma40: Fix logical channel bounds check Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 20/22] dmaengine: ste_dma40: Search all blocks for fixed logical channels Linus Walleij
                   ` (2 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij, sashiko-bot

The dev_type validation can allow values whose derived event group has no
matching physical channel pair. d40_allocate_channel() then indexes
phy_res with j + event_group * 2, and __d40_set_prio_rt() uses the same
group to select priority and realtime registers.

Reject dev_type values outside the hardware event-group range, and stop
the physical-channel search before a partial final channel group can index
past phy_res.

Fixes: 26955c07dcf3 ("dmaengine: ste_dma40: Amalgamate DMA source and destination channel numbers")
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/dmaengine/20260819225008.5F9651F000E9@smtp.kernel.org/
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 23 +++++++++++++++++++++--
 1 file changed, 21 insertions(+), 2 deletions(-)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 26dbef630eef..5dbe11fceb4c 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -79,6 +79,10 @@ struct stedma40_platform_data {
 #define D40_LCLA_LINK_PER_EVENT_GRP 128
 #define D40_LCLA_END D40_LCLA_LINK_PER_EVENT_GRP
 
+/* Number of event groups per hardware register layout */
+#define D40_EVENT_GROUPS_V4A 4
+#define D40_EVENT_GROUPS_V4B 5
+
 /* Max number of logical channels per physical channel */
 #define D40_MAX_LOG_CHAN_PER_PHY 32
 
@@ -513,6 +517,7 @@ struct d40_chan {
  * @high_prio_clear: the high priority clear register
  * @interrupt_en: the interrupt enable register
  * @interrupt_clear: the interrupt clear register
+ * @num_event_groups: number of supported event groups
  * @il: the pointer to struct d40_interrupt_lookup
  * @il_size: the size of d40_interrupt_lookup array
  * @init_reg: the pointer to the struct d40_reg_val
@@ -527,6 +532,7 @@ struct d40_gen_dmac {
 	u32				 high_prio_clear;
 	u32				 interrupt_en;
 	u32				 interrupt_clear;
+	u32				 num_event_groups;
 	struct d40_interrupt_lookup	*il;
 	u32				 il_size;
 	struct d40_reg_val		*init_reg;
@@ -1752,6 +1758,11 @@ static int d40_validate_conf(struct d40_chan *d40c,
 	bool is_log = conf->mode == STEDMA40_MODE_LOGICAL;
 	bool invalid_dev_type = conf->dev_type < 0;
 
+	if (!invalid_dev_type &&
+	    D40_TYPE_TO_GROUP(conf->dev_type) >=
+	    d40c->base->gen_dmac.num_event_groups)
+		invalid_dev_type = true;
+
 	if (!conf->dir) {
 		chan_err(d40c, "Invalid direction.\n");
 		res = -EINVAL;
@@ -1934,8 +1945,12 @@ static int d40_allocate_channel(struct d40_chan *d40c, bool *first_phy_user)
 				}
 			}
 		} else
-			for (j = 0; j < d40c->base->num_phy_chans; j += 8) {
+			for (j = 0; j < d40c->base->num_phy_chans;
+			     j += D40_GROUP_SIZE) {
 				int phy_num = j  + event_group * 2;
+				if (phy_num + 1 >= num_phy_chans)
+					break;
+
 				for (i = phy_num; i < phy_num + 2; i++) {
 					if (d40_alloc_mask_set(&phys[i],
 							       is_src,
@@ -1955,8 +1970,10 @@ static int d40_allocate_channel(struct d40_chan *d40c, bool *first_phy_user)
 		return -EINVAL;
 
 	/* Find logical channel */
-	for (j = 0; j < d40c->base->num_phy_chans; j += 8) {
+	for (j = 0; j < d40c->base->num_phy_chans; j += D40_GROUP_SIZE) {
 		int phy_num = j + event_group * 2;
+		if (phy_num + 1 >= num_phy_chans)
+			break;
 
 		if (d40c->dma_cfg.use_fixed_channel) {
 			i = d40c->dma_cfg.phy_channel;
@@ -3326,6 +3343,7 @@ static int __init d40_hw_detect_init(struct platform_device *pdev,
 	base->log_chans = &base->phy_chans[num_phy_chans];
 
 	if (base->plat_data->num_of_phy_chans == 14) {
+		base->gen_dmac.num_event_groups = D40_EVENT_GROUPS_V4B;
 		base->gen_dmac.backup = d40_backup_regs_v4b;
 		base->gen_dmac.backup_size = BACKUP_REGS_SZ_V4B;
 		base->gen_dmac.interrupt_en = D40_DREG_CPCMIS;
@@ -3339,6 +3357,7 @@ static int __init d40_hw_detect_init(struct platform_device *pdev,
 		base->gen_dmac.init_reg = dma_init_reg_v4b;
 		base->gen_dmac.init_reg_size = ARRAY_SIZE(dma_init_reg_v4b);
 	} else {
+		base->gen_dmac.num_event_groups = D40_EVENT_GROUPS_V4A;
 		if (base->rev >= 3) {
 			base->gen_dmac.backup = d40_backup_regs_v4a;
 			base->gen_dmac.backup_size = BACKUP_REGS_SZ_V4A;

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 20/22] dmaengine: ste_dma40: Search all blocks for fixed logical channels
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (18 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 19/22] dmaengine: ste_dma40: Fix event group bounds Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 21/22] dmaengine: ste_dma40: Validate fixed physical channel indexes Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 22/22] dmaengine: ste_dma40: Validate memcpy configuration Linus Walleij
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij, sashiko-bot

Fixed logical channel allocation scans physical channels in blocks of
eight. When the requested physical channel does not belong to the first
block, d40_allocate_channel() returns -EINVAL instead of checking later
blocks.

Skip nonmatching blocks so controllers with more than eight physical
channels can allocate fixed logical channels from the later blocks.

Fixes: 5cd326fd27da ("dmaengine/ste_dma40: allow fixed physical channel")
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/dmaengine/20260819225008.5F9651F000E9@smtp.kernel.org/
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 5dbe11fceb4c..37b96debe7bb 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1978,11 +1978,8 @@ static int d40_allocate_channel(struct d40_chan *d40c, bool *first_phy_user)
 		if (d40c->dma_cfg.use_fixed_channel) {
 			i = d40c->dma_cfg.phy_channel;
 
-			if ((i != phy_num) && (i != phy_num + 1)) {
-				dev_err(chan2dev(d40c),
-					"invalid fixed phy channel %d\n", i);
-				return -EINVAL;
-			}
+			if (i != phy_num && i != phy_num + 1)
+				continue;
 
 			if (d40_alloc_mask_set(&phys[i], is_src, event_line,
 					       is_log, first_phy_user))

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 21/22] dmaengine: ste_dma40: Validate fixed physical channel indexes
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (19 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 20/22] dmaengine: ste_dma40: Search all blocks for fixed logical channels Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  2026-09-18 16:24 ` [PATCH v3 22/22] dmaengine: ste_dma40: Validate memcpy configuration Linus Walleij
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij, sashiko-bot

Fixed physical memcpy allocation uses dma_cfg.phy_channel directly as an
index into phy_res when use_fixed_channel is set. d40_validate_conf()
validates dev_type but not the fixed physical channel, allowing an invalid
configuration to access memory outside the array.

Validate the fixed physical channel centrally before accepting the channel
configuration.

Fixes: f000df8c5a0e ("dmaengine: ste_dma40: support fixed physical channel allocation")
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/dmaengine/20260819225008.5F9651F000E9@smtp.kernel.org/
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 37b96debe7bb..3310f5086122 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1785,6 +1785,14 @@ static int d40_validate_conf(struct d40_chan *d40c,
 		res = -EINVAL;
 	}
 
+	if (conf->use_fixed_channel &&
+	    (conf->phy_channel < 0 ||
+	     conf->phy_channel >= d40c->base->num_phy_chans)) {
+		chan_err(d40c, "Invalid physical channel (%d)\n",
+			 conf->phy_channel);
+		res = -EINVAL;
+	}
+
 	if (conf->dir == DMA_DEV_TO_DEV) {
 		/*
 		 * DMAC HW supports it. Will be added to this driver,

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v3 22/22] dmaengine: ste_dma40: Validate memcpy configuration
  2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
                   ` (20 preceding siblings ...)
  2026-09-18 16:24 ` [PATCH v3 21/22] dmaengine: ste_dma40: Validate fixed physical channel indexes Linus Walleij
@ 2026-09-18 16:24 ` Linus Walleij
  21 siblings, 0 replies; 27+ messages in thread
From: Linus Walleij @ 2026-09-18 16:24 UTC (permalink / raw)
  To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij, sashiko-bot

d40_config_memcpy() builds a default memcpy configuration without passing
it through d40_validate_conf(). A dev_type supplied through the
memcpy-channels device tree property can therefore bypass the bounds
checks added for client configurations.

The generic DMA direction enum assigns zero to DMA_MEM_TO_MEM, unlike
DMA40's old private enum. Allow memory-to-memory directions in the
validator so checking the generated configuration does not reject all
memcpy channels.

Validate the generated memcpy configuration before deriving logical
channel registers or allocating the channel.

Fixes: 2c2b62d5d911 ("dmaengine: ste_dma40: Replace ST-E's home-brew DMA direction defs with generic ones")
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/dmaengine/20260819225008.5F9651F000E9@smtp.kernel.org/
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
 drivers/dma/ste_dma40.c | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 3310f5086122..4969e78f365d 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1763,7 +1763,8 @@ static int d40_validate_conf(struct d40_chan *d40c,
 	    d40c->base->gen_dmac.num_event_groups)
 		invalid_dev_type = true;
 
-	if (!conf->dir) {
+	if (conf->dir != DMA_MEM_TO_MEM &&
+	    !is_slave_direction(conf->dir)) {
 		chan_err(d40c, "Invalid direction.\n");
 		res = -EINVAL;
 	}
@@ -2038,12 +2039,17 @@ static int d40_allocate_channel(struct d40_chan *d40c, bool *first_phy_user)
 static int d40_config_memcpy(struct d40_chan *d40c)
 {
 	dma_cap_mask_t cap = d40c->chan.device->cap_mask;
+	int ret;
 
 	if (dma_has_cap(DMA_MEMCPY, cap) && !dma_has_cap(DMA_SLAVE, cap)) {
 		d40c->dma_cfg = dma40_memcpy_conf_log;
 		d40c->dma_cfg.dev_type =
 			d40c->base->plat_data->memcpy_channels[d40c->chan.chan_id];
 
+		ret = d40_validate_conf(d40c, &d40c->dma_cfg);
+		if (ret)
+			return ret;
+
 		d40_log_cfg(&d40c->dma_cfg,
 			    &d40c->log_def.lcsp1, &d40c->log_def.lcsp3);
 
@@ -2051,6 +2057,10 @@ static int d40_config_memcpy(struct d40_chan *d40c)
 		   dma_has_cap(DMA_SLAVE, cap)) {
 		d40c->dma_cfg = dma40_memcpy_conf_phy;
 
+		ret = d40_validate_conf(d40c, &d40c->dma_cfg);
+		if (ret)
+			return ret;
+
 		/* Generate interrupt at end of transfer or relink. */
 		d40c->dst_def_cfg |= BIT(D40_SREG_CFG_TIM_POS);
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 06/22] dmaengine: ste_dma40: Handle runtime PM resume errors
  2026-09-18 16:24 ` [PATCH v3 06/22] dmaengine: ste_dma40: Handle runtime PM resume errors Linus Walleij
@ 2026-09-18 16:37   ` sashiko-bot
  0 siblings, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-09-18 16:37 UTC (permalink / raw)
  To: Linus Walleij; +Cc: vkoul, Frank.Li, dmaengine

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] dmaengine: ste_dma40: skip channel teardown on PM resume failure
- [High] dmaengine: ste_dma40: leave descriptor queued on PM resume failure
--

commit ed522d6297daebccbf846812d9e9d09ad29b8000
Author: Linus Walleij <linusw@kernel.org>

    dmaengine: ste_dma40: Handle runtime PM resume errors
    
    This commit replaces pm_runtime_get_sync() with pm_runtime_resume_and_get()
    in several ste_dma40 channel operations. It adds error checking to abort
    hardware access if the PM resume fails.

> diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
> index 1baee6f4d919a..c6f633353f3ea 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c

[ ... ]

> @@ -1521,8 +1527,13 @@ static struct d40_desc *d40_queue_start(struct d40_chan *d40c)
>  
>  	if (d40d != NULL) {
>  		if (!d40c->busy) {
> +			err = pm_runtime_resume_and_get(d40c->base->dev);
> +			if (err < 0) {
> +				chan_err(d40c, "Failed to resume DMA: %d\n",
> +					 err);
> +				return ERR_PTR(err);
> +			}

[Severity: High]
Does returning an error here without removing the pending descriptor from the
queue or scheduling a completion tasklet cause issues? If a caller such as
dma_async_issue_pending() triggers this, it appears the DMA client will hang
indefinitely waiting for a transfer completion callback that never arrives.

>  			d40c->busy = true;
> -			pm_runtime_get_sync(d40c->base->dev);
>  		}
>  
>  		/* Remove from queue */

[ ... ]

> @@ -2042,9 +2053,6 @@ static int d40_free_dma(struct d40_chan *d40c)
>  	struct d40_phy_res *phy = d40c->phy_chan;
>  	bool is_src;
>  
> -	/* Terminate all queued and active transfers */
> -	d40_term_all(d40c);
> -
>  	if (phy == NULL) {
>  		chan_err(d40c, "phy == null\n");
>  		return -EINVAL;
> @@ -2066,7 +2074,13 @@ static int d40_free_dma(struct d40_chan *d40c)
>  		return -EINVAL;
>  	}
>  
> -	pm_runtime_get_sync(d40c->base->dev);
> +	res = pm_runtime_resume_and_get(d40c->base->dev);
> +	if (res < 0)
> +		return res;
> +
> +	/* Terminate all queued and active transfers */
> +	d40_term_all(d40c);
> +

[Severity: High]
Can this early return on PM resume failure cause hardware channels and
descriptor memory to leak? By returning before calling d40_term_all() and
d40_alloc_mask_free(), the physical channel state seems permanently locked to
allocated and descriptors are not released.

>  	res = d40_channel_execute_command(d40c, D40_DMA_STOP);
>  	if (res) {
>  		chan_err(d40c, "stop failed\n");

[ ... ]

> @@ -2658,7 +2675,10 @@ static int d40_terminate_all(struct dma_chan *chan)
>  
>  	spin_lock_irqsave(&d40c->lock, flags);
>  
> -	pm_runtime_get_sync(d40c->base->dev);
> +	ret = pm_runtime_resume_and_get(d40c->base->dev);
> +	if (ret < 0)
> +		goto unlock;
> +

[Severity: High]
Does jumping to the unlock label here skip d40_term_all()? If
pm_runtime_resume_and_get() fails, it looks like pending and active
descriptors are left in the queues and permanently leaked instead of being
cleaned up.

>  	ret = d40_channel_execute_command(d40c, D40_DMA_STOP);
>  	if (ret)
>  		chan_err(d40c, "Failed to stop channel\n");

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260918-dma40-fixes-v3-0-8dd8450669e8@kernel.org?part=6

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 02/22] dmaengine: ste_dma40: Fix cyclic transfer residue
  2026-09-18 16:24 ` [PATCH v3 02/22] dmaengine: ste_dma40: Fix cyclic transfer residue Linus Walleij
@ 2026-09-18 16:44   ` sashiko-bot
  2026-09-18 21:41   ` Frank Li
  1 sibling, 0 replies; 27+ messages in thread
From: sashiko-bot @ 2026-09-18 16:44 UTC (permalink / raw)
  To: Linus Walleij; +Cc: vkoul, Frank.Li, dmaengine

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] dmaengine: ste_dma40: unvalidated cyclic period length causes state corruption
- [Medium] dmaengine: ste_dma40: race condition in cyclic residue calculation
--

commit 80bd7a5b773ac08dae8cfb0539ed88005fe2df44
Author: Linus Walleij <linusw@kernel.org>

dmaengine: ste_dma40: Fix cyclic transfer residue

DMA40 reads residue from the element count of the currently active LLI.
For a cyclic transfer this reports at most one period, not the bytes
remaining until the cyclic buffer wraps. This patch tracks the active
period of cyclic descriptors and adds all later periods in the buffer to
the current LLI residue.

> diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
[ ... ]
> @@ -2108,15 +2120,27 @@ static bool d40_is_paused(struct d40_chan *d40c)
>  
>  }
>  
> -static u32 stedma40_residue(struct dma_chan *chan)
> +static u32 stedma40_residue(struct dma_chan *chan, dma_cookie_t cookie)
>  {
>  	struct d40_chan *d40c =
>  		container_of(chan, struct d40_chan, chan);
> +	struct d40_desc *d40d;
>  	u32 bytes_left;
>  	unsigned long flags;
>  
>  	spin_lock_irqsave(&d40c->lock, flags);
>  	bytes_left = d40_residue(d40c);
> +
> +	d40d = d40_first_active_get(d40c);
> +	/*
> +	 * The hardware residue is for the current LLI. Cyclic transfers use
> +	 * one LLI for each period, so include the later periods in the buffer.
> +	 */
> +	if (d40d && d40d->txd.cookie == cookie && d40d->cyclic &&
> +	    d40d->cyclic_period_len)
> +		bytes_left += d40d->cyclic_buf_len - d40d->cyclic_pos -
> +			      d40d->cyclic_period_len;

[Severity: Medium]
Can this create a race condition when a client asynchronously polls the DMA
residue?

If the hardware has just completed a period and advanced its internal LLI,
but the terminal-count interrupt hasn't updated the software cyclic_pos yet:

dma_tc_handle() {
	...
	if (d40d->cyclic_period_len) {
		d40d->cyclic_pos += d40d->cyclic_period_len;
	...
}

Could stedma40_residue() read the current hardware residue without
synchronizing with the interrupt handler's update, causing the residue (and
pointers for clients like ALSA) to occasionally jump backward and cause audio
glitches?

[ ... ]
> @@ -2524,11 +2548,17 @@ dma40_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t dma_addr,
>  		     size_t buf_len, size_t period_len,
>  		     enum dma_transfer_direction direction, unsigned long flags)
>  {
> -	unsigned int periods = buf_len / period_len;
>  	struct dma_async_tx_descriptor *txd;
> +	struct d40_desc *desc;
>  	struct scatterlist *sg;
>  	int i;
>  
> +	if (!buf_len || !period_len || buf_len % period_len)
> +		return NULL;
> +
> +	periods = buf_len / period_len;

[Severity: High]
Does this also need to validate that period_len is within the hardware's
maximum segment size?

If a cyclic period_len exceeds the maximum segment size (for example,
STEDMA40_MAX_SEG_SIZE * data_width), it seems it would be silently split
into multiple LLIs. For logical channels, every LLI unconditionally
triggers a terminal-count interrupt.

If that happens, wouldn't dma_tc_handle() erroneously advance cyclic_pos
by the full period_len on every LLI interrupt?

dma_tc_handle() {
	...
	if (d40d->cyclic_period_len) {
		d40d->cyclic_pos += d40d->cyclic_period_len;
	...
}

This seems like it would permanently drift the residue tracking in
stedma40_residue() and trigger excessive callbacks if a client requests a
large cyclic buffer.

>  
>  	sg = kzalloc_objs(struct scatterlist, periods + 1, GFP_NOWAIT);
>  	if (!sg)

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260918-dma40-fixes-v3-0-8dd8450669e8@kernel.org?part=2

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 01/22] dmaengine: ste_dma40: Fix physical cyclic capability
  2026-09-18 16:24 ` [PATCH v3 01/22] dmaengine: ste_dma40: Fix physical cyclic capability Linus Walleij
@ 2026-09-18 19:57   ` Frank Li
  0 siblings, 0 replies; 27+ messages in thread
From: Frank Li @ 2026-09-18 19:57 UTC (permalink / raw)
  To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel

On Fri, Sep 18, 2026 at 06:24:20PM +0200, Linus Walleij wrote:
> d40_dmaengine_init() configures dma_both for physical channels that can
> handle both slave and memcpy transfers. Physical DMA40 channel setup has
> supported cyclic LLIs since cyclic transfer support was added, but the
> DMA_CYCLIC capability is set on dma_slave a second time instead of
> dma_both.
>
> Set DMA_CYCLIC on dma_both so d40_ops_init() installs
> device_prep_dma_cyclic and physical channels advertise cyclic support.
>
> Fixes: 0c842b551063 ("dma40: cyclic xfer support")
> Assisted-by: LLM
> Signed-off-by: Linus Walleij <linusw@kernel.org>
> ---

Reviewed-by: Frank Li <Frank.Li@nxp.com>

>  drivers/dma/ste_dma40.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
> index 0d9ffa3e2663..e4d689c9eba8 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -2897,7 +2897,7 @@ static int __init d40_dmaengine_init(struct d40_base *base,
>  	dma_cap_zero(base->dma_both.cap_mask);
>  	dma_cap_set(DMA_SLAVE, base->dma_both.cap_mask);
>  	dma_cap_set(DMA_MEMCPY, base->dma_both.cap_mask);
> -	dma_cap_set(DMA_CYCLIC, base->dma_slave.cap_mask);
> +	dma_cap_set(DMA_CYCLIC, base->dma_both.cap_mask);
>
>  	d40_ops_init(base, &base->dma_both);
>  	err = dmaenginem_async_device_register(&base->dma_both);
>
> --
> 2.55.0
>

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3 02/22] dmaengine: ste_dma40: Fix cyclic transfer residue
  2026-09-18 16:24 ` [PATCH v3 02/22] dmaengine: ste_dma40: Fix cyclic transfer residue Linus Walleij
  2026-09-18 16:44   ` sashiko-bot
@ 2026-09-18 21:41   ` Frank Li
  1 sibling, 0 replies; 27+ messages in thread
From: Frank Li @ 2026-09-18 21:41 UTC (permalink / raw)
  To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel

On Fri, Sep 18, 2026 at 06:24:21PM +0200, Linus Walleij wrote:
> DMA40 reads residue from the element count of the currently active LLI.
> For a cyclic transfer this reports at most one period, not the bytes
> remaining until the cyclic buffer wraps.
>
> Once DMA40 advertises burst granularity, DMAengine PCM uses this residue
> directly. For a four-period PCM buffer it consequently reports the
> hardware pointer near three periods after every period interrupt. ALSA
> eventually stops playback with -EIO although DMA period callbacks
> continue.
>
> Track the active period of cyclic descriptors and add all later periods
> in the buffer to the current LLI residue. Update the active period in the
> terminal-count interrupt before scheduling its callback so residue also
> stays coherent around period boundaries.
>
> Also reject invalid cyclic geometries before dividing or constructing
> the scatterlist.
>
> Fixes: 15c606686541 ("dmaengine: ste_dma40: indicate granularity on channels")
> Assisted-by: LLM
> Signed-off-by: Linus Walleij <linusw@kernel.org>
> ---
>  drivers/dma/ste_dma40.c | 42 +++++++++++++++++++++++++++++++++++++++---
>  1 file changed, 39 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
> index e4d689c9eba8..49e9139e0ad0 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -378,6 +378,9 @@ struct d40_lli_pool {
>   * @lli_len: Number of llis of current descriptor.
>   * @lli_current: Number of transferred llis.
>   * @lcla_alloc: Number of LCLA entries allocated.
> + * @cyclic_buf_len: Length of the cyclic buffer.
> + * @cyclic_period_len: Length of one cyclic period.
> + * @cyclic_pos: Start of the current cyclic period in the buffer.
>   * @txd: DMA engine struct. Used for among other things for communication
>   * during a transfer.
>   * @node: List entry.
> @@ -396,6 +399,9 @@ struct d40_desc {
>  	int				 lli_len;
>  	int				 lli_current;
>  	int				 lcla_alloc;
> +	size_t				 cyclic_buf_len;
> +	size_t				 cyclic_period_len;
> +	size_t				 cyclic_pos;
>
>  	struct dma_async_tx_descriptor	 txd;
>  	struct list_head		 node;
> @@ -1566,6 +1572,12 @@ static void dma_tc_handle(struct d40_chan *d40c)
>  			if (d40d->lli_current == d40d->lli_len)
>  				d40d->lli_current = 0;
>  		}
> +
> +		if (d40d->cyclic_period_len) {
> +			d40d->cyclic_pos += d40d->cyclic_period_len;

You depend irq handle will be called every period.

if period is short, like 100us, is it possible hardware combine twice or
more irq to once, cause miss count cyclic_pos?

> +			if (d40d->cyclic_pos >= d40d->cyclic_buf_len)
> +				d40d->cyclic_pos = 0;
> +		}
>  	} else {
>  		d40_lcla_free_all(d40c, d40d);
>
> @@ -2108,15 +2120,27 @@ static bool d40_is_paused(struct d40_chan *d40c)
>
>  }
>
> -static u32 stedma40_residue(struct dma_chan *chan)
> +static u32 stedma40_residue(struct dma_chan *chan, dma_cookie_t cookie)
>  {
>  	struct d40_chan *d40c =
>  		container_of(chan, struct d40_chan, chan);
> +	struct d40_desc *d40d;
>  	u32 bytes_left;
>  	unsigned long flags;
>
>  	spin_lock_irqsave(&d40c->lock, flags);
>  	bytes_left = d40_residue(d40c);
> +
> +	d40d = d40_first_active_get(d40c);
> +	/*
> +	 * The hardware residue is for the current LLI. Cyclic transfers use
> +	 * one LLI for each period, so include the later periods in the buffer.
> +	 */
> +	if (d40d && d40d->txd.cookie == cookie && d40d->cyclic &&
> +	    d40d->cyclic_period_len)
> +		bytes_left += d40d->cyclic_buf_len - d40d->cyclic_pos -
> +			      d40d->cyclic_period_len;
> +

I have some confuse about this formular, what bytes_left = d40_residue(d40c);

d40_residue() read some register value, I am not sure what's means.

and why d40d->cyclic_period_len,  d40d->cyclic_pos += d40d->cyclic_period_len;

pos should already consider cyclic_period_len.

Frank

>  	spin_unlock_irqrestore(&d40c->lock, flags);
>
>  	return bytes_left;
> @@ -2524,11 +2548,17 @@ dma40_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t dma_addr,
>  		     size_t buf_len, size_t period_len,
>  		     enum dma_transfer_direction direction, unsigned long flags)
>  {
> -	unsigned int periods = buf_len / period_len;
> +	unsigned int periods;
>  	struct dma_async_tx_descriptor *txd;
> +	struct d40_desc *desc;
>  	struct scatterlist *sg;
>  	int i;
>
> +	if (!buf_len || !period_len || buf_len % period_len)
> +		return NULL;
> +
> +	periods = buf_len / period_len;
> +
>  	sg = kzalloc_objs(struct scatterlist, periods + 1, GFP_NOWAIT);
>  	if (!sg)
>  		return NULL;
> @@ -2543,6 +2573,12 @@ dma40_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t dma_addr,
>
>  	txd = d40_prep_sg(chan, sg, sg, periods, direction,
>  			  DMA_PREP_INTERRUPT);
> +	if (txd) {
> +		desc = container_of(txd, struct d40_desc, txd);
> +		desc->cyclic_buf_len = buf_len;
> +		desc->cyclic_period_len = period_len;
> +		desc->cyclic_pos = 0;
> +	}
>
>  	kfree(sg);
>
> @@ -2563,7 +2599,7 @@ static enum dma_status d40_tx_status(struct dma_chan *chan,
>
>  	ret = dma_cookie_status(chan, cookie, txstate);
>  	if (ret != DMA_COMPLETE && txstate)
> -		dma_set_residue(txstate, stedma40_residue(chan));
> +		dma_set_residue(txstate, stedma40_residue(chan, cookie));
>
>  	if (d40_is_paused(d40c))
>  		ret = DMA_PAUSED;
>
> --
> 2.55.0
>

^ permalink raw reply	[flat|nested] 27+ messages in thread

end of thread, other threads:[~2026-09-18 21:42 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-18 16:24 [PATCH v3 00/22] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
2026-09-18 16:24 ` [PATCH v3 01/22] dmaengine: ste_dma40: Fix physical cyclic capability Linus Walleij
2026-09-18 19:57   ` Frank Li
2026-09-18 16:24 ` [PATCH v3 02/22] dmaengine: ste_dma40: Fix cyclic transfer residue Linus Walleij
2026-09-18 16:44   ` sashiko-bot
2026-09-18 21:41   ` Frank Li
2026-09-18 16:24 ` [PATCH v3 03/22] dmaengine: ste_dma40: Fix failed start cleanup Linus Walleij
2026-09-18 16:24 ` [PATCH v3 04/22] dmaengine: ste_dma40: Fix probe runtime PM disable Linus Walleij
2026-09-18 16:24 ` [PATCH v3 05/22] dmaengine: ste_dma40: Check runtime PM in IRQ Linus Walleij
2026-09-18 16:24 ` [PATCH v3 06/22] dmaengine: ste_dma40: Handle runtime PM resume errors Linus Walleij
2026-09-18 16:37   ` sashiko-bot
2026-09-18 16:24 ` [PATCH v3 07/22] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status Linus Walleij
2026-09-18 16:24 ` [PATCH v3 08/22] dmaengine: ste_dma40: Init hardware before registration Linus Walleij
2026-09-18 16:24 ` [PATCH v3 09/22] dmaengine: ste_dma40: Fix probe IRQ leak Linus Walleij
2026-09-18 16:24 ` [PATCH v3 10/22] dmaengine: ste_dma40: Fix DMA registration unwind Linus Walleij
2026-09-18 16:24 ` [PATCH v3 11/22] dmaengine: ste_dma40: Fix LCLA allocation order Linus Walleij
2026-09-18 16:24 ` [PATCH v3 12/22] dmaengine: ste_dma40: Fix probe LCLA free Linus Walleij
2026-09-18 16:24 ` [PATCH v3 13/22] dmaengine: ste_dma40: Put the LCPA SRAM node Linus Walleij
2026-09-18 16:24 ` [PATCH v3 14/22] dmaengine: ste_dma40: Fix memcpy channel parsing Linus Walleij
2026-09-18 16:24 ` [PATCH v3 15/22] dmaengine: ste_dma40: Validate disabled channel indexes Linus Walleij
2026-09-18 16:24 ` [PATCH v3 16/22] dmaengine: ste_dma40: Validate DMA specifier length Linus Walleij
2026-09-18 16:24 ` [PATCH v3 17/22] dmaengine: ste_dma40: Reject direction changes after allocation Linus Walleij
2026-09-18 16:24 ` [PATCH v3 18/22] dmaengine: ste_dma40: Fix logical channel bounds check Linus Walleij
2026-09-18 16:24 ` [PATCH v3 19/22] dmaengine: ste_dma40: Fix event group bounds Linus Walleij
2026-09-18 16:24 ` [PATCH v3 20/22] dmaengine: ste_dma40: Search all blocks for fixed logical channels Linus Walleij
2026-09-18 16:24 ` [PATCH v3 21/22] dmaengine: ste_dma40: Validate fixed physical channel indexes Linus Walleij
2026-09-18 16:24 ` [PATCH v3 22/22] dmaengine: ste_dma40: Validate memcpy configuration Linus Walleij

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox