* [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs
@ 2026-09-19 22:22 Linus Walleij
2026-09-19 22:22 ` [PATCH v4 01/23] dmaengine: ste_dma40: Fix physical cyclic capability Linus Walleij
` (22 more replies)
0 siblings, 23 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: dmaengine, phone-devel, Linus Walleij, Frank Li, sashiko-bot
This series fixes 26 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.
13 more issues were identified during review and hardware-manual audit.
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 v4:
- Rework cyclic-transfer residue reporting to use the current memory-side
hardware pointer and reject periods that need more than one LLI.
- Add cyclic callback recovery when terminal-count interrupts coalesce.
- Retire all issued descriptors and release software channel state when a
runtime PM resume fails, while avoiding inaccessible hardware registers.
- Validate physical event IDs against the variant event-group limit rather
than the physical channel count, as confirmed by the DB8500 manual.
- Link to v3: https://lore.kernel.org/r/20260918-dma40-fixes-v3-0-8dd8450669e8@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 (23):
dmaengine: ste_dma40: Fix physical cyclic capability
dmaengine: ste_dma40: Fix cyclic transfer residue
dmaengine: ste_dma40: Recover coalesced cyclic callbacks
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 | 514 +++++++++++++++++++++++++++++++++++++-----------
1 file changed, 396 insertions(+), 118 deletions(-)
---
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
change-id: 20260820-dma40-fixes-b99af66002bf
Best regards,
--
Linus Walleij <linusw@kernel.org>
^ permalink raw reply [flat|nested] 26+ messages in thread
* [PATCH v4 01/23] dmaengine: ste_dma40: Fix physical cyclic capability
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 02/23] dmaengine: ste_dma40: Fix cyclic transfer residue Linus Walleij
` (21 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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] 26+ messages in thread
* [PATCH v4 02/23] dmaengine: ste_dma40: Fix cyclic transfer residue
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
2026-09-19 22:22 ` [PATCH v4 01/23] dmaengine: ste_dma40: Fix physical cyclic capability Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks Linus Walleij
` (20 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 UTC (permalink / raw)
To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij, Frank Li
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.
Calculate cyclic residue from the current memory-side hardware pointer
instead. Read the destination pointer for capture and the source pointer
for playback. Sample the split logical channel pointer coherently and
retain the last valid residue during relink transitions.
This avoids counting terminal-count interrupts, which races with hardware
advancing to the next LLI and cannot account for coalesced interrupt
status. Also reject cyclic periods that expand into multiple LLIs because
logical cyclic LLIs each request a terminal-count interrupt and would
generate more than one callback per period.
Reject invalid cyclic geometries before dividing or constructing the
scatterlist as well.
Reported-by: Frank Li <Frank.li@oss.nxp.com>
Closes: https://lore.kernel.org/dmaengine/aq2wIPJW6viUxyy9@SMW015318/
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 | 104 +++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 99 insertions(+), 5 deletions(-)
diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index e4d689c9eba8..c9983e600daf 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_dma_addr: Start address of the cyclic buffer.
+ * @cyclic_buf_len: Length of the cyclic buffer.
+ * @cyclic_residue: Last valid cyclic residue sample.
* @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;
+ dma_addr_t cyclic_dma_addr;
+ size_t cyclic_buf_len;
+ size_t cyclic_residue;
struct dma_async_tx_descriptor txd;
struct list_head node;
@@ -1420,6 +1426,64 @@ static u32 d40_residue(struct d40_chan *d40c)
return num_elt * d40c->dma_cfg.dst_info.data_width;
}
+static bool d40_current_addr(struct d40_chan *d40c, dma_addr_t *addr)
+{
+ bool dst = d40c->dma_cfg.dir == DMA_DEV_TO_MEM;
+ void __iomem *high_reg;
+ void __iomem *low_reg;
+ u32 low;
+ u32 high;
+ u32 check;
+ int i;
+
+ if (chan_is_physical(d40c)) {
+ *addr = readl(chan_base(d40c) +
+ (dst ? D40_CHAN_REG_SDPTR : D40_CHAN_REG_SSPTR));
+ return true;
+ }
+
+ if (dst) {
+ low_reg = &d40c->lcpa->lcsp2;
+ high_reg = &d40c->lcpa->lcsp3;
+ } else {
+ low_reg = &d40c->lcpa->lcsp0;
+ high_reg = &d40c->lcpa->lcsp1;
+ }
+
+ for (i = 0; i < 3; i++) {
+ high = readl(high_reg) & D40_MEM_LCSP1_SPTR_MASK;
+ low = readl(low_reg) & D40_MEM_LCSP0_SPTR_MASK;
+ check = readl(high_reg) & D40_MEM_LCSP1_SPTR_MASK;
+ if (high == check) {
+ *addr = low | high;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool d40_cyclic_offset(struct d40_chan *d40c, struct d40_desc *d40d,
+ size_t *offset)
+{
+ dma_addr_t current_addr;
+ dma_addr_t current_offset;
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ if (!d40_current_addr(d40c, ¤t_addr))
+ continue;
+
+ current_offset = current_addr - d40d->cyclic_dma_addr;
+ if (current_offset <= d40d->cyclic_buf_len) {
+ *offset = current_offset;
+ return true;
+ }
+ }
+
+ return false;
+}
+
static bool d40_tx_is_linked(struct d40_chan *d40c)
{
bool is_link;
@@ -1566,6 +1630,7 @@ static void dma_tc_handle(struct d40_chan *d40c)
if (d40d->lli_current == d40d->lli_len)
d40d->lli_current = 0;
}
+
} else {
d40_lcla_free_all(d40c, d40d);
@@ -2108,15 +2173,26 @@ 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;
+ size_t offset;
u32 bytes_left;
unsigned long flags;
spin_lock_irqsave(&d40c->lock, flags);
- bytes_left = d40_residue(d40c);
+ d40d = d40_first_active_get(d40c);
+ if (d40d && d40d->txd.cookie == cookie && d40d->cyclic &&
+ d40d->cyclic_buf_len) {
+ if (d40_cyclic_offset(d40c, d40d, &offset))
+ d40d->cyclic_residue = d40d->cyclic_buf_len - offset;
+ bytes_left = d40d->cyclic_residue;
+ } else {
+ bytes_left = d40_residue(d40c);
+ }
+
spin_unlock_irqrestore(&d40c->lock, flags);
return bytes_left;
@@ -2246,8 +2322,13 @@ d40_prep_sg(struct dma_chan *dchan, struct scatterlist *sg_src,
if (desc == NULL)
goto unlock;
- if (sg_next(&sg_src[sg_len - 1]) == sg_src)
+ if (sg_next(&sg_src[sg_len - 1]) == sg_src) {
desc->cyclic = true;
+ if (desc->lli_len != sg_len) {
+ chan_err(chan, "Cyclic periods must fit in one LLI\n");
+ goto free_desc;
+ }
+ }
src_dev_addr = 0;
dst_dev_addr = 0;
@@ -2524,11 +2605,18 @@ 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;
+ dma_addr_t buf_addr = dma_addr;
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 +2631,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_dma_addr = buf_addr;
+ desc->cyclic_buf_len = buf_len;
+ desc->cyclic_residue = buf_len;
+ }
kfree(sg);
@@ -2563,7 +2657,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] 26+ messages in thread
* [PATCH v4 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
2026-09-19 22:22 ` [PATCH v4 01/23] dmaengine: ste_dma40: Fix physical cyclic capability Linus Walleij
2026-09-19 22:22 ` [PATCH v4 02/23] dmaengine: ste_dma40: Fix cyclic transfer residue Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:37 ` sashiko-bot
2026-09-19 22:22 ` [PATCH v4 04/23] dmaengine: ste_dma40: Fix failed start cleanup Linus Walleij
` (19 subsequent siblings)
22 siblings, 1 reply; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 UTC (permalink / raw)
To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij
DMA40 exposes one terminal-count status bit per channel. If more than one
cyclic period completes before the interrupt handler clears the bit, the
events coalesce and the driver schedules only one callback. Short audio
periods can consequently lose period notifications.
Use the current memory-side pointer to find the period boundary reached
since callbacks were last queued. Add every elapsed period to pending_tx
so the tasklet invokes one callback for each of them. Fall back to one
callback when the pointer cannot be sampled.
If the pointer has returned to the same period boundary, account for one
complete buffer lap. The hardware has no counter that could distinguish
multiple complete laps, but recovering one lap avoids silently losing all
callbacks represented by the observed terminal-count interrupt.
The preceding cyclic-residue fix ensures that every cyclic period fits in
one LLI, so each boundary corresponds to one client callback.
Fixes: 0c842b551063 ("dma40: cyclic xfer support")
Assisted-by: LLM
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/dma/ste_dma40.c | 44 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 43 insertions(+), 1 deletion(-)
diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index c9983e600daf..b992471e05f2 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -381,6 +381,8 @@ struct d40_lli_pool {
* @cyclic_dma_addr: Start address of the cyclic buffer.
* @cyclic_buf_len: Length of the cyclic buffer.
* @cyclic_residue: Last valid cyclic residue sample.
+ * @cyclic_period_len: Length of one cyclic period.
+ * @cyclic_callback_pos: Position after the callbacks already queued.
* @txd: DMA engine struct. Used for among other things for communication
* during a transfer.
* @node: List entry.
@@ -402,6 +404,8 @@ struct d40_desc {
dma_addr_t cyclic_dma_addr;
size_t cyclic_buf_len;
size_t cyclic_residue;
+ size_t cyclic_period_len;
+ size_t cyclic_callback_pos;
struct dma_async_tx_descriptor txd;
struct list_head node;
@@ -1484,6 +1488,40 @@ static bool d40_cyclic_offset(struct d40_chan *d40c, struct d40_desc *d40d,
return false;
}
+static unsigned int d40_cyclic_periods_elapsed(struct d40_chan *d40c,
+ struct d40_desc *d40d)
+{
+ size_t current_pos;
+ size_t offset;
+ unsigned int periods;
+
+ if (!d40d->cyclic_period_len ||
+ !d40_cyclic_offset(d40c, d40d, &offset))
+ return 1;
+
+ current_pos = rounddown(offset, d40d->cyclic_period_len);
+ if (!d40_residue(d40c) && current_pos != offset)
+ current_pos += d40d->cyclic_period_len;
+ if (current_pos == d40d->cyclic_buf_len)
+ current_pos = 0;
+
+ if (current_pos > d40d->cyclic_callback_pos) {
+ periods = (current_pos - d40d->cyclic_callback_pos) /
+ d40d->cyclic_period_len;
+ } else if (current_pos < d40d->cyclic_callback_pos) {
+ periods = (d40d->cyclic_buf_len -
+ d40d->cyclic_callback_pos + current_pos) /
+ d40d->cyclic_period_len;
+ } else {
+ /* At least one interrupt occurred, so assume one buffer lap. */
+ periods = d40d->cyclic_buf_len / d40d->cyclic_period_len;
+ }
+
+ d40d->cyclic_callback_pos = current_pos;
+
+ return periods;
+}
+
static bool d40_tx_is_linked(struct d40_chan *d40c)
{
bool is_link;
@@ -1606,6 +1644,7 @@ static struct d40_desc *d40_queue_start(struct d40_chan *d40c)
static void dma_tc_handle(struct d40_chan *d40c)
{
struct d40_desc *d40d;
+ unsigned int callbacks = 1;
/* Get first active entry from list */
d40d = d40_first_active_get(d40c);
@@ -1631,6 +1670,7 @@ static void dma_tc_handle(struct d40_chan *d40c)
d40d->lli_current = 0;
}
+ callbacks = d40_cyclic_periods_elapsed(d40c, d40d);
} else {
d40_lcla_free_all(d40c, d40d);
@@ -1651,7 +1691,7 @@ static void dma_tc_handle(struct d40_chan *d40c)
d40_desc_done(d40c, d40d);
}
- d40c->pending_tx++;
+ d40c->pending_tx += callbacks;
tasklet_schedule(&d40c->tasklet);
}
@@ -2636,6 +2676,8 @@ dma40_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t dma_addr,
desc->cyclic_dma_addr = buf_addr;
desc->cyclic_buf_len = buf_len;
desc->cyclic_residue = buf_len;
+ desc->cyclic_period_len = period_len;
+ desc->cyclic_callback_pos = 0;
}
kfree(sg);
--
2.55.0
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH v4 04/23] dmaengine: ste_dma40: Fix failed start cleanup
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (2 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 05/23] dmaengine: ste_dma40: Fix probe runtime PM disable Linus Walleij
` (18 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 b992471e05f2..4591da863398 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1633,8 +1633,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;
@@ -1681,14 +1688,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 += callbacks;
@@ -1702,20 +1709,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);
/*
@@ -1731,7 +1740,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] 26+ messages in thread
* [PATCH v4 05/23] dmaengine: ste_dma40: Fix probe runtime PM disable
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (3 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 04/23] dmaengine: ste_dma40: Fix failed start cleanup Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 06/23] dmaengine: ste_dma40: Check runtime PM in IRQ Linus Walleij
` (17 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 4591da863398..bf3da3a94d8f 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3643,6 +3643,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;
@@ -3730,12 +3731,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");
@@ -3764,6 +3759,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)
@@ -3799,7 +3801,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] 26+ messages in thread
* [PATCH v4 06/23] dmaengine: ste_dma40: Check runtime PM in IRQ
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (4 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 05/23] dmaengine: ste_dma40: Fix probe runtime PM disable Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 07/23] dmaengine: ste_dma40: Handle runtime PM resume errors Linus Walleij
` (16 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 bf3da3a94d8f..f32c3eee16d9 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1781,6 +1781,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);
@@ -1829,6 +1834,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] 26+ messages in thread
* [PATCH v4 07/23] dmaengine: ste_dma40: Handle runtime PM resume errors
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (5 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 06/23] dmaengine: ste_dma40: Check runtime PM in IRQ Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 08/23] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status Linus Walleij
` (15 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 avoid register access when resume
fails. Acquire the runtime PM reference before allocating a channel so
failure needs no channel-allocation rollback.
If a queued transfer cannot be started because resume failed, retire all
issued descriptors through the normal tasklet path. Since
dma_async_issue_pending() cannot return an error, leaving them queued would
make clients wait indefinitely for callbacks.
Termination and channel release must also clean up software state when the
controller cannot resume. Always release descriptors and the outstanding
busy reference, and release channel allocation state when freeing the
channel. Skip only the hardware stop that requires register access.
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 | 67 ++++++++++++++++++++++++++++++++++++-------------
1 file changed, 50 insertions(+), 17 deletions(-)
diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index f32c3eee16d9..633f6a04fdc1 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1550,11 +1550,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;
}
@@ -1574,13 +1577,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;
}
@@ -1617,8 +1623,20 @@ 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);
+ do {
+ d40_desc_remove(d40d);
+ d40_desc_done(d40c, d40d);
+ d40c->pending_tx++;
+ d40d = d40_first_queued(d40c);
+ } while (d40d);
+ tasklet_schedule(&d40c->tasklet);
+ return ERR_PTR(err);
+ }
d40c->busy = true;
- pm_runtime_get_sync(d40c->base->dev);
}
/* Remove from queue */
@@ -2133,11 +2151,9 @@ static int d40_free_dma(struct d40_chan *d40c)
int res = 0;
u32 event = D40_TYPE_TO_EVENT(d40c->dma_cfg.dev_type);
struct d40_phy_res *phy = d40c->phy_chan;
+ bool pm_acquired = false;
bool is_src;
- /* Terminate all queued and active transfers */
- d40_term_all(d40c);
-
if (phy == NULL) {
chan_err(d40c, "phy == null\n");
return -EINVAL;
@@ -2159,13 +2175,21 @@ static int d40_free_dma(struct d40_chan *d40c)
return -EINVAL;
}
- pm_runtime_get_sync(d40c->base->dev);
+ /* Terminate all queued and active transfers */
+ d40_term_all(d40c);
+
+ res = pm_runtime_resume_and_get(d40c->base->dev);
+ if (res < 0)
+ goto release_channel;
+ pm_acquired = true;
+
res = d40_channel_execute_command(d40c, D40_DMA_STOP);
if (res) {
chan_err(d40c, "stop failed\n");
goto mark_last_busy;
}
+ release_channel:
d40_alloc_mask_free(phy, is_src, chan_is_logical(d40c) ? event : 0);
if (chan_is_logical(d40c))
@@ -2180,7 +2204,8 @@ static int d40_free_dma(struct d40_chan *d40c)
d40c->phy_chan = NULL;
d40c->configured = false;
mark_last_busy:
- pm_runtime_put_autosuspend(d40c->base->dev);
+ if (pm_acquired)
+ pm_runtime_put_autosuspend(d40c->base->dev);
return res;
}
@@ -2555,10 +2580,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");
@@ -2566,8 +2595,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)) {
@@ -2599,6 +2626,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;
}
@@ -2749,6 +2777,7 @@ static int d40_terminate_all(struct dma_chan *chan)
{
unsigned long flags;
struct d40_chan *d40c = container_of(chan, struct d40_chan, chan);
+ bool pm_acquired = false;
int ret;
if (d40c->phy_chan == NULL) {
@@ -2758,19 +2787,23 @@ static int d40_terminate_all(struct dma_chan *chan)
spin_lock_irqsave(&d40c->lock, flags);
- pm_runtime_get_sync(d40c->base->dev);
- ret = d40_channel_execute_command(d40c, D40_DMA_STOP);
- if (ret)
- chan_err(d40c, "Failed to stop channel\n");
+ ret = pm_runtime_resume_and_get(d40c->base->dev);
+ if (ret >= 0) {
+ pm_acquired = true;
+ ret = d40_channel_execute_command(d40c, D40_DMA_STOP);
+ if (ret)
+ chan_err(d40c, "Failed to stop channel\n");
+ }
d40_term_all(d40c);
- pm_runtime_put_autosuspend(d40c->base->dev);
+ if (pm_acquired)
+ pm_runtime_put_autosuspend(d40c->base->dev);
if (d40c->busy)
pm_runtime_put_autosuspend(d40c->base->dev);
d40c->busy = false;
spin_unlock_irqrestore(&d40c->lock, flags);
- return 0;
+ return ret;
}
static int
--
2.55.0
^ permalink raw reply related [flat|nested] 26+ messages in thread
* [PATCH v4 08/23] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (6 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 07/23] dmaengine: ste_dma40: Handle runtime PM resume errors Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:36 ` sashiko-bot
2026-09-19 22:22 ` [PATCH v4 09/23] dmaengine: ste_dma40: Init hardware before registration Linus Walleij
` (14 subsequent siblings)
22 siblings, 1 reply; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 633f6a04fdc1..9f725369721b 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1790,6 +1790,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;
@@ -1838,6 +1839,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);
@@ -1855,7 +1857,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] 26+ messages in thread
* [PATCH v4 09/23] dmaengine: ste_dma40: Init hardware before registration
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (7 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 08/23] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 10/23] dmaengine: ste_dma40: Fix probe IRQ leak Linus Walleij
` (13 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 9f725369721b..73560d482343 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3804,19 +3804,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] 26+ messages in thread
* [PATCH v4 10/23] dmaengine: ste_dma40: Fix probe IRQ leak
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (8 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 09/23] dmaengine: ste_dma40: Init hardware before registration Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 11/23] dmaengine: ste_dma40: Fix DMA registration unwind Linus Walleij
` (12 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 73560d482343..075c9b18d6c2 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3687,6 +3687,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;
@@ -3810,6 +3811,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);
@@ -3846,6 +3848,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] 26+ messages in thread
* [PATCH v4 11/23] dmaengine: ste_dma40: Fix DMA registration unwind
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (9 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 10/23] dmaengine: ste_dma40: Fix probe IRQ leak Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 12/23] dmaengine: ste_dma40: Fix LCLA allocation order Linus Walleij
` (11 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 075c9b18d6c2..5175052f25bb 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3010,6 +3010,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)) {
@@ -3056,6 +3068,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) {
@@ -3071,6 +3088,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) {
@@ -3088,6 +3110,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) {
@@ -3685,6 +3713,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;
@@ -3818,20 +3847,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] 26+ messages in thread
* [PATCH v4 12/23] dmaengine: ste_dma40: Fix LCLA allocation order
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (10 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 11/23] dmaengine: ste_dma40: Fix DMA registration unwind Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 13/23] dmaengine: ste_dma40: Fix probe LCLA free Linus Walleij
` (10 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 5175052f25bb..e781122abce6 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -421,8 +421,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.
*/
@@ -430,7 +430,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;
};
@@ -3574,6 +3574,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;
@@ -3589,20 +3590,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;
}
@@ -3612,7 +3613,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];
@@ -3622,10 +3623,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) {
@@ -3637,8 +3637,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;
@@ -3882,7 +3881,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] 26+ messages in thread
* [PATCH v4 13/23] dmaengine: ste_dma40: Fix probe LCLA free
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (11 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 12/23] dmaengine: ste_dma40: Fix LCLA allocation order Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 14/23] dmaengine: ste_dma40: Put the LCPA SRAM node Linus Walleij
` (9 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 e781122abce6..acbd536cbfdd 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3879,7 +3879,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] 26+ messages in thread
* [PATCH v4 14/23] dmaengine: ste_dma40: Put the LCPA SRAM node
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (12 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 13/23] dmaengine: ste_dma40: Fix probe LCLA free Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 15/23] dmaengine: ste_dma40: Fix memcpy channel parsing Linus Walleij
` (8 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 acbd536cbfdd..9baa677278be 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3744,6 +3744,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] 26+ messages in thread
* [PATCH v4 15/23] dmaengine: ste_dma40: Fix memcpy channel parsing
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (13 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 14/23] dmaengine: ste_dma40: Put the LCPA SRAM node Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 16/23] dmaengine: ste_dma40: Validate disabled channel indexes Linus Walleij
` (7 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 9baa677278be..f3a0cdfe2d7e 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,
@@ -2123,7 +2108,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);
@@ -3405,12 +3391,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,
@@ -3659,6 +3640,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)
@@ -3672,18 +3654,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] 26+ messages in thread
* [PATCH v4 16/23] dmaengine: ste_dma40: Validate disabled channel indexes
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (14 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 15/23] dmaengine: ste_dma40: Fix memcpy channel parsing Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 17/23] dmaengine: ste_dma40: Validate DMA specifier length Linus Walleij
` (6 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 f3a0cdfe2d7e..28e68fbba1a5 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3390,6 +3390,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] 26+ messages in thread
* [PATCH v4 17/23] dmaengine: ste_dma40: Validate DMA specifier length
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (15 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 16/23] dmaengine: ste_dma40: Validate disabled channel indexes Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 18/23] dmaengine: ste_dma40: Reject direction changes after allocation Linus Walleij
` (5 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 28e68fbba1a5..7cd063e81328 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -2516,6 +2516,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] 26+ messages in thread
* [PATCH v4 18/23] dmaengine: ste_dma40: Reject direction changes after allocation
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (16 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 17/23] dmaengine: ste_dma40: Validate DMA specifier length Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 19/23] dmaengine: ste_dma40: Fix logical channel bounds check Linus Walleij
` (4 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 7cd063e81328..35cee6e48558 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -2384,7 +2384,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);
@@ -2857,6 +2863,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;
@@ -2865,13 +2878,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;
@@ -2881,13 +2887,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] 26+ messages in thread
* [PATCH v4 19/23] dmaengine: ste_dma40: Fix logical channel bounds check
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (17 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 18/23] dmaengine: ste_dma40: Reject direction changes after allocation Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 20/23] dmaengine: ste_dma40: Fix event group bounds Linus Walleij
` (3 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 35cee6e48558..fe9759787981 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1850,15 +1850,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] 26+ messages in thread
* [PATCH v4 20/23] dmaengine: ste_dma40: Fix event group bounds
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (18 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 19/23] dmaengine: ste_dma40: Fix logical channel bounds check Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 21/23] dmaengine: ste_dma40: Search all blocks for fixed logical channels Linus Walleij
` (2 subsequent siblings)
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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.
The physical-mode validation also compares dev_type with the number of
physical channels. However, dev_type identifies an event line: DB8500 has
eight physical channels but 64 source and 64 destination event lines. The
event group determines which physical channel pair can serve an event, so
the physical channel count is not a valid dev_type limit.
Reject dev_type values outside the hardware event-group range, remove the
incorrect physical channel count limit, 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 | 26 ++++++++++++++++++++++----
1 file changed, 22 insertions(+), 4 deletions(-)
diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index fe9759787981..1738a37da682 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
@@ -517,6 +521,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
@@ -531,6 +536,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;
@@ -1852,6 +1858,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;
@@ -1868,8 +1879,7 @@ static int d40_validate_conf(struct d40_chan *d40c,
invalid_dev_type = conf->dev_type >= max_dev_type;
}
- if (invalid_dev_type ||
- (!is_log && conf->dev_type > d40c->base->num_phy_chans)) {
+ if (invalid_dev_type) {
chan_err(d40c, "Invalid device type (%d)\n", conf->dev_type);
res = -EINVAL;
}
@@ -2034,8 +2044,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,
@@ -2055,8 +2069,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;
@@ -3438,6 +3454,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;
@@ -3451,6 +3468,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] 26+ messages in thread
* [PATCH v4 21/23] dmaengine: ste_dma40: Search all blocks for fixed logical channels
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (19 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 20/23] dmaengine: ste_dma40: Fix event group bounds Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 22/23] dmaengine: ste_dma40: Validate fixed physical channel indexes Linus Walleij
2026-09-19 22:22 ` [PATCH v4 23/23] dmaengine: ste_dma40: Validate memcpy configuration Linus Walleij
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 1738a37da682..d68828acb186 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -2077,11 +2077,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] 26+ messages in thread
* [PATCH v4 22/23] dmaengine: ste_dma40: Validate fixed physical channel indexes
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (20 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 21/23] dmaengine: ste_dma40: Search all blocks for fixed logical channels Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
2026-09-19 22:22 ` [PATCH v4 23/23] dmaengine: ste_dma40: Validate memcpy configuration Linus Walleij
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 d68828acb186..0fbd77588231 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1884,6 +1884,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] 26+ messages in thread
* [PATCH v4 23/23] dmaengine: ste_dma40: Validate memcpy configuration
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (21 preceding siblings ...)
2026-09-19 22:22 ` [PATCH v4 22/23] dmaengine: ste_dma40: Validate fixed physical channel indexes Linus Walleij
@ 2026-09-19 22:22 ` Linus Walleij
22 siblings, 0 replies; 26+ messages in thread
From: Linus Walleij @ 2026-09-19 22:22 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 0fbd77588231..2152f63cb6bb 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1863,7 +1863,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;
}
@@ -2137,12 +2138,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);
@@ -2150,6 +2156,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] 26+ messages in thread
* Re: [PATCH v4 08/23] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status
2026-09-19 22:22 ` [PATCH v4 08/23] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status Linus Walleij
@ 2026-09-19 22:36 ` sashiko-bot
0 siblings, 0 replies; 26+ messages in thread
From: sashiko-bot @ 2026-09-19 22:36 UTC (permalink / raw)
To: Linus Walleij; +Cc: Frank.Li, dmaengine, vkoul
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] dmaengine: ste_dma40: IRQ disabled due to unhandled DSP interrupts
--
commit 93721f73dfcbd1d64fc59b63723c4f215e77a3b2
Author: Linus Walleij <linusw@kernel.org>
dmaengine: ste_dma40: Return IRQ_NONE without interrupt status
This commit modifies the dma40 interrupt handler to track whether an
interrupt is acknowledged. If no matching status bit is found for a
registered channel, it returns IRQ_NONE to allow the generic spurious
interrupt detector to disable a stuck interrupt line.
> diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
> index 633f6a04fdc14..9f725369721b8 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -1790,6 +1790,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;
[ ... ]
> @@ -1838,6 +1839,7 @@ static irqreturn_t d40_handle_interrupt(int irq, void *data)
>
> /* ACK interrupt */
> writel(BIT(idx), base->virtbase + il[row].clr);
> + handled = IRQ_HANDLED;
[Severity: High]
Does returning IRQ_NONE here risk disabling the DMA interrupt line entirely
when another processor (like a DSP) is using the channel?
If a logical channel is owned by another processor, Linux receives the
hardware interrupt because interrupts are enabled for all logical channels.
In this scenario, d40c will be NULL, and d40_handle_interrupt() skips
acknowledging the interrupt and continues processing, leaving handled as
IRQ_NONE:
if (!d40c) {
/*
* No error because this can happen if something else
* in the system is using the channel.
*/
continue;
}
Since other processors can generate frequent DMA completions, Linux could
rapidly accumulate IRQ_NONE returns. Would the core IRQ subsystem's spurious
detector log "nobody cared" and permanently disable the shared DMA interrupt
line, breaking all Linux DMA transfers?
>
> spin_lock(&d40c->lock);
>
[ ... ]
> @@ -1855,7 +1857,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;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260920-dma40-fixes-v4-0-d751b2d9c23f@kernel.org?part=8
^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH v4 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks
2026-09-19 22:22 ` [PATCH v4 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks Linus Walleij
@ 2026-09-19 22:37 ` sashiko-bot
0 siblings, 0 replies; 26+ messages in thread
From: sashiko-bot @ 2026-09-19 22:37 UTC (permalink / raw)
To: Linus Walleij; +Cc: vkoul, dmaengine, Frank.Li
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] dmaengine: ste_dma40: double-interrupt race causes full buffer spurious callbacks
- [High] dmaengine: ste_dma40: duplicate cyclic callbacks on fallback pointer sampling failure
--
commit a2aa4dd1ba30133f07fa06190999eebead74c31d
Author: Linus Walleij <linusw@kernel.org>
dmaengine: ste_dma40: Recover coalesced cyclic callbacks
This commit addresses lost period notifications in the DMA40 driver caused
by coalesced cyclic callbacks. It calculates elapsed periods by comparing
the current memory-side pointer against the last queued callback position,
accounting for elapsed periods and potential full buffer laps.
> diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
> index c9983e600dafc..b992471e05f29 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
[ ... ]
> @@ -1484,6 +1488,40 @@ static bool d40_cyclic_offset(struct d40_chan *d40c, struct d40_desc *d40d,
> return false;
> }
>
> +static unsigned int d40_cyclic_periods_elapsed(struct d40_chan *d40c,
> + struct d40_desc *d40d)
> +{
> + size_t current_pos;
> + size_t offset;
> + unsigned int periods;
> +
> + if (!d40d->cyclic_period_len ||
> + !d40_cyclic_offset(d40c, d40d, &offset))
> + return 1;
[Severity: High]
Could this fallback path lead to duplicate callbacks on the next interrupt?
If d40_cyclic_offset fails to reliably read the hardware pointer (for
example, returning false after retry failures due to concurrent register
updates), this returns 1 to legitimately report a single period completion.
However, since d40d->cyclic_callback_pos is left unmodified here, won't the
next successful interrupt recalculate the elapsed periods using the stale
cyclic_callback_pos, effectively recounting the period that was already
reported by this fallback path?
> +
> + current_pos = rounddown(offset, d40d->cyclic_period_len);
> + if (!d40_residue(d40c) && current_pos != offset)
> + current_pos += d40d->cyclic_period_len;
> + if (current_pos == d40d->cyclic_buf_len)
> + current_pos = 0;
> +
> + if (current_pos > d40d->cyclic_callback_pos) {
> + periods = (current_pos - d40d->cyclic_callback_pos) /
> + d40d->cyclic_period_len;
> + } else if (current_pos < d40d->cyclic_callback_pos) {
> + periods = (d40d->cyclic_buf_len -
> + d40d->cyclic_callback_pos + current_pos) /
> + d40d->cyclic_period_len;
> + } else {
> + /* At least one interrupt occurred, so assume one buffer lap. */
> + periods = d40d->cyclic_buf_len / d40d->cyclic_period_len;
> + }
[Severity: High]
Is it possible for this else branch to be triggered by a double-interrupt
race rather than a full buffer lap?
If a second or ghost interrupt fires immediately after the first, before the
hardware pointer has advanced to the next boundary, current_pos will exactly
equal cyclic_callback_pos. This branch appears to interpret that condition
strictly as a full buffer lap caused by CPU starvation.
Could this extremely common double-interrupt race cause a massive burst of
spurious callbacks (a full buffer's worth) to be queued to the cyclic DMA
client, potentially leading to severe state desynchronization like ALSA
audio underruns or pointer jumps?
> +
> + d40d->cyclic_callback_pos = current_pos;
> +
> + return periods;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260920-dma40-fixes-v4-0-d751b2d9c23f@kernel.org?part=3
^ permalink raw reply [flat|nested] 26+ messages in thread
end of thread, other threads:[~2026-09-19 22:37 UTC | newest]
Thread overview: 26+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-19 22:22 [PATCH v4 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
2026-09-19 22:22 ` [PATCH v4 01/23] dmaengine: ste_dma40: Fix physical cyclic capability Linus Walleij
2026-09-19 22:22 ` [PATCH v4 02/23] dmaengine: ste_dma40: Fix cyclic transfer residue Linus Walleij
2026-09-19 22:22 ` [PATCH v4 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks Linus Walleij
2026-09-19 22:37 ` sashiko-bot
2026-09-19 22:22 ` [PATCH v4 04/23] dmaengine: ste_dma40: Fix failed start cleanup Linus Walleij
2026-09-19 22:22 ` [PATCH v4 05/23] dmaengine: ste_dma40: Fix probe runtime PM disable Linus Walleij
2026-09-19 22:22 ` [PATCH v4 06/23] dmaengine: ste_dma40: Check runtime PM in IRQ Linus Walleij
2026-09-19 22:22 ` [PATCH v4 07/23] dmaengine: ste_dma40: Handle runtime PM resume errors Linus Walleij
2026-09-19 22:22 ` [PATCH v4 08/23] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status Linus Walleij
2026-09-19 22:36 ` sashiko-bot
2026-09-19 22:22 ` [PATCH v4 09/23] dmaengine: ste_dma40: Init hardware before registration Linus Walleij
2026-09-19 22:22 ` [PATCH v4 10/23] dmaengine: ste_dma40: Fix probe IRQ leak Linus Walleij
2026-09-19 22:22 ` [PATCH v4 11/23] dmaengine: ste_dma40: Fix DMA registration unwind Linus Walleij
2026-09-19 22:22 ` [PATCH v4 12/23] dmaengine: ste_dma40: Fix LCLA allocation order Linus Walleij
2026-09-19 22:22 ` [PATCH v4 13/23] dmaengine: ste_dma40: Fix probe LCLA free Linus Walleij
2026-09-19 22:22 ` [PATCH v4 14/23] dmaengine: ste_dma40: Put the LCPA SRAM node Linus Walleij
2026-09-19 22:22 ` [PATCH v4 15/23] dmaengine: ste_dma40: Fix memcpy channel parsing Linus Walleij
2026-09-19 22:22 ` [PATCH v4 16/23] dmaengine: ste_dma40: Validate disabled channel indexes Linus Walleij
2026-09-19 22:22 ` [PATCH v4 17/23] dmaengine: ste_dma40: Validate DMA specifier length Linus Walleij
2026-09-19 22:22 ` [PATCH v4 18/23] dmaengine: ste_dma40: Reject direction changes after allocation Linus Walleij
2026-09-19 22:22 ` [PATCH v4 19/23] dmaengine: ste_dma40: Fix logical channel bounds check Linus Walleij
2026-09-19 22:22 ` [PATCH v4 20/23] dmaengine: ste_dma40: Fix event group bounds Linus Walleij
2026-09-19 22:22 ` [PATCH v4 21/23] dmaengine: ste_dma40: Search all blocks for fixed logical channels Linus Walleij
2026-09-19 22:22 ` [PATCH v4 22/23] dmaengine: ste_dma40: Validate fixed physical channel indexes Linus Walleij
2026-09-19 22:22 ` [PATCH v4 23/23] 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;
as well as URLs for NNTP newsgroup(s).