* [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs
@ 2026-09-20 18:59 Linus Walleij
2026-09-20 18:59 ` [PATCH v5 01/23] dmaengine: ste_dma40: Fix physical cyclic capability Linus Walleij
` (22 more replies)
0 siblings, 23 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 v5:
- Respin to fix some further corner cases found by Sashiko as well
as can be done.
- Resynchronize cyclic callback tracking after failed hardware-pointer samples
and limit unchanged-pointer interrupts to one callback. DMA40 has only one
latched terminal-count bit, so extra periods coalesced while the pointer is
unavailable and exact one-or-more full-buffer laps cannot be recovered
without risking spurious callbacks.
- Count DMA40 status from channels owned by other SoC cores as handled
without acknowledging it, so only status-less interrupts return IRQ_NONE
and foreign DMA traffic cannot trigger spurious-IRQ disabling.
This is REALLY FRINGE but let's do our best anyway!
- Link to v4: https://lore.kernel.org/r/20260920-dma40-fixes-v4-0-d751b2d9c23f@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 | 537 +++++++++++++++++++++++++++++++++++++-----------
1 file changed, 419 insertions(+), 118 deletions(-)
---
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
change-id: 20260820-dma40-fixes-b99af66002bf
Best regards,
--
Linus Walleij <linusw@kernel.org>
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v5 01/23] dmaengine: ste_dma40: Fix physical cyclic capability
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-21 16:39 ` Frank Li
2026-09-20 18:59 ` [PATCH v5 02/23] dmaengine: ste_dma40: Fix cyclic transfer residue Linus Walleij
` (21 subsequent siblings)
22 siblings, 1 reply; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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] 37+ messages in thread
* [PATCH v5 02/23] dmaengine: ste_dma40: Fix cyclic transfer residue
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
2026-09-20 18:59 ` [PATCH v5 01/23] dmaengine: ste_dma40: Fix physical cyclic capability Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-21 16:57 ` Frank Li
2026-09-20 18:59 ` [PATCH v5 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks Linus Walleij
` (20 subsequent siblings)
22 siblings, 1 reply; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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] 37+ messages in thread
* [PATCH v5 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
2026-09-20 18:59 ` [PATCH v5 01/23] dmaengine: ste_dma40: Fix physical cyclic capability Linus Walleij
2026-09-20 18:59 ` [PATCH v5 02/23] dmaengine: ste_dma40: Fix cyclic transfer residue Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-21 21:51 ` Frank Li
2026-09-20 18:59 ` [PATCH v5 04/23] dmaengine: ste_dma40: Fix failed start cleanup Linus Walleij
` (19 subsequent siblings)
22 siblings, 1 reply; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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.
If the pointer cannot be sampled, report one callback and mark the callback
position invalid. At the next successful sample, resynchronize instead of
deriving a count from stale state. Any additional periods coalesced while
the pointer is unavailable cannot be recovered reliably.
DMA40 exposes one latched terminal-count status bit, not an event counter.
If the pointer remains at the same boundary, hardware cannot distinguish a
repeated status from an exact full-buffer lap. In this ambiguous case,
report one callback rather than potentially enqueueing a whole buffer of
spurious callbacks. Exact full laps, including multiple laps, therefore
cannot be recovered by software.
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 | 62 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 61 insertions(+), 1 deletion(-)
diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index c9983e600daf..1f9e8c7249b1 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -381,11 +381,14 @@ 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.
* @is_in_client_list: true if the client owns this descriptor.
* @cyclic: true if this is a cyclic job
+ * @cyclic_callback_pos_valid: Whether cyclic_callback_pos is reliable.
*
* This descriptor is used for both logical and physical transfers.
*/
@@ -402,12 +405,15 @@ 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;
bool is_in_client_list;
bool cyclic;
+ bool cyclic_callback_pos_valid;
};
/**
@@ -1484,6 +1490,55 @@ 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)
+ return 1;
+
+ if (!d40_cyclic_offset(d40c, d40d, &offset)) {
+ d40d->cyclic_callback_pos_valid = false;
+ 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 (!d40d->cyclic_callback_pos_valid) {
+ /*
+ * One callback was reported without a reliable pointer.
+ * Resynchronize instead of deriving periods from stale state.
+ */
+ periods = 1;
+ } else 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 {
+ /*
+ * The TC status is a single latched bit. An unchanged pointer
+ * cannot distinguish a complete lap from a repeated interrupt,
+ * so do not amplify it into a buffer's worth of callbacks.
+ */
+ periods = 1;
+ }
+
+ d40d->cyclic_callback_pos = current_pos;
+ d40d->cyclic_callback_pos_valid = true;
+
+ return periods;
+}
+
static bool d40_tx_is_linked(struct d40_chan *d40c)
{
bool is_link;
@@ -1606,6 +1661,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 +1687,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 +1708,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 +2693,9 @@ 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;
+ desc->cyclic_callback_pos_valid = true;
}
kfree(sg);
--
2.55.0
^ permalink raw reply related [flat|nested] 37+ messages in thread
* [PATCH v5 04/23] dmaengine: ste_dma40: Fix failed start cleanup
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (2 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-21 22:10 ` Frank Li
2026-09-20 18:59 ` [PATCH v5 05/23] dmaengine: ste_dma40: Fix probe runtime PM disable Linus Walleij
` (18 subsequent siblings)
22 siblings, 1 reply; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 1f9e8c7249b1..280a0d2f16c5 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1650,8 +1650,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;
@@ -1698,14 +1705,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;
@@ -1719,20 +1726,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);
/*
@@ -1748,7 +1757,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] 37+ messages in thread
* [PATCH v5 05/23] dmaengine: ste_dma40: Fix probe runtime PM disable
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (3 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 04/23] dmaengine: ste_dma40: Fix failed start cleanup Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-21 22:16 ` Frank Li
2026-09-20 18:59 ` [PATCH v5 06/23] dmaengine: ste_dma40: Check runtime PM in IRQ Linus Walleij
` (17 subsequent siblings)
22 siblings, 1 reply; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 280a0d2f16c5..1d02226ab5ff 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3661,6 +3661,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;
@@ -3748,12 +3749,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");
@@ -3782,6 +3777,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)
@@ -3817,7 +3819,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] 37+ messages in thread
* [PATCH v5 06/23] dmaengine: ste_dma40: Check runtime PM in IRQ
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (4 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 05/23] dmaengine: ste_dma40: Fix probe runtime PM disable Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-21 22:19 ` Frank Li
2026-09-20 18:59 ` [PATCH v5 07/23] dmaengine: ste_dma40: Handle runtime PM resume errors Linus Walleij
` (16 subsequent siblings)
22 siblings, 1 reply; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 1d02226ab5ff..58128a980847 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1798,6 +1798,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);
@@ -1846,6 +1851,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] 37+ messages in thread
* [PATCH v5 07/23] dmaengine: ste_dma40: Handle runtime PM resume errors
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (5 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 06/23] dmaengine: ste_dma40: Check runtime PM in IRQ Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-22 19:23 ` Frank Li
2026-09-20 18:59 ` [PATCH v5 08/23] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status Linus Walleij
` (15 subsequent siblings)
22 siblings, 1 reply; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 58128a980847..bb052d3028cc 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1567,11 +1567,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;
}
@@ -1591,13 +1594,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;
}
@@ -1634,8 +1640,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 */
@@ -2150,11 +2168,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;
@@ -2176,13 +2192,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))
@@ -2197,7 +2221,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;
}
@@ -2572,10 +2597,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");
@@ -2583,8 +2612,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)) {
@@ -2616,6 +2643,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;
}
@@ -2767,6 +2795,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) {
@@ -2776,19 +2805,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] 37+ messages in thread
* [PATCH v5 08/23] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (6 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 07/23] dmaengine: ste_dma40: Handle runtime PM resume errors Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-20 19:10 ` sashiko-bot
2026-09-22 19:37 ` Frank Li
2026-09-20 18:59 ` [PATCH v5 09/23] dmaengine: ste_dma40: Init hardware before registration Linus Walleij
` (14 subsequent siblings)
22 siblings, 2 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 UTC (permalink / raw)
To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij
d40_handle_interrupt() returns IRQ_HANDLED even when no terminal-count or
error status bit is set. If the line remains asserted without matching
status, reporting it as handled prevents the generic interrupt code from
detecting the stuck interrupt.
Track whether the handler observes any DMA40 status. Return IRQ_NONE only
when none is present, allowing the generic spurious interrupt detector to
disable a faulty status-less interrupt line.
DMA40 channels can be owned by other SoC cores. Their status still explains
why the interrupt fired, but Linux must not acknowledge it. Treat foreign
status as handled and leave its acknowledgment to the owning core, while
acknowledging and dispatching only registered Linux channels.
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 | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index bb052d3028cc..4380f9a1b035 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1807,6 +1807,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;
@@ -1840,6 +1841,12 @@ static irqreturn_t d40_handle_interrupt(int irq, void *data)
row = chan / BITS_PER_LONG;
idx = chan & (BITS_PER_LONG - 1);
+ /*
+ * Status for a channel owned by another core still explains
+ * the interrupt, but only ACK Linux-owned channels below.
+ */
+ handled = IRQ_HANDLED;
+
if (il[row].offset == D40_PHY_CHAN)
d40c = base->lookup_phy_chans[idx];
else
@@ -1872,7 +1879,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] 37+ messages in thread
* [PATCH v5 09/23] dmaengine: ste_dma40: Init hardware before registration
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (7 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 08/23] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-20 18:59 ` [PATCH v5 10/23] dmaengine: ste_dma40: Fix probe IRQ leak Linus Walleij
` (13 subsequent siblings)
22 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 4380f9a1b035..7cbcf8433fe0 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3827,19 +3827,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] 37+ messages in thread
* [PATCH v5 10/23] dmaengine: ste_dma40: Fix probe IRQ leak
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (8 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 09/23] dmaengine: ste_dma40: Init hardware before registration Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-20 18:59 ` [PATCH v5 11/23] dmaengine: ste_dma40: Fix DMA registration unwind Linus Walleij
` (12 subsequent siblings)
22 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 7cbcf8433fe0..ba529bd91752 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3710,6 +3710,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;
@@ -3833,6 +3834,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);
@@ -3869,6 +3871,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] 37+ messages in thread
* [PATCH v5 11/23] dmaengine: ste_dma40: Fix DMA registration unwind
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (9 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 10/23] dmaengine: ste_dma40: Fix probe IRQ leak Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-20 18:59 ` [PATCH v5 12/23] dmaengine: ste_dma40: Fix LCLA allocation order Linus Walleij
` (11 subsequent siblings)
22 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 ba529bd91752..52d39883da51 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3033,6 +3033,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)) {
@@ -3079,6 +3091,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) {
@@ -3094,6 +3111,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) {
@@ -3111,6 +3133,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) {
@@ -3708,6 +3736,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;
@@ -3841,20 +3870,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] 37+ messages in thread
* [PATCH v5 12/23] dmaengine: ste_dma40: Fix LCLA allocation order
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (10 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 11/23] dmaengine: ste_dma40: Fix DMA registration unwind Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-20 18:59 ` [PATCH v5 13/23] dmaengine: ste_dma40: Fix probe LCLA free Linus Walleij
` (10 subsequent siblings)
22 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 52d39883da51..33cd93e90bc4 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -423,8 +423,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.
*/
@@ -432,7 +432,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;
};
@@ -3597,6 +3597,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;
@@ -3612,20 +3613,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;
}
@@ -3635,7 +3636,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];
@@ -3645,10 +3646,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) {
@@ -3660,8 +3660,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;
@@ -3905,7 +3904,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] 37+ messages in thread
* [PATCH v5 13/23] dmaengine: ste_dma40: Fix probe LCLA free
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (11 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 12/23] dmaengine: ste_dma40: Fix LCLA allocation order Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-20 18:59 ` [PATCH v5 14/23] dmaengine: ste_dma40: Put the LCPA SRAM node Linus Walleij
` (9 subsequent siblings)
22 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 33cd93e90bc4..77ae6b28610b 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3902,7 +3902,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] 37+ messages in thread
* [PATCH v5 14/23] dmaengine: ste_dma40: Put the LCPA SRAM node
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (12 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 13/23] dmaengine: ste_dma40: Fix probe LCLA free Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-20 18:59 ` [PATCH v5 15/23] dmaengine: ste_dma40: Fix memcpy channel parsing Linus Walleij
` (8 subsequent siblings)
22 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 77ae6b28610b..02ccad255a75 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3767,6 +3767,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] 37+ messages in thread
* [PATCH v5 15/23] dmaengine: ste_dma40: Fix memcpy channel parsing
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (13 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 14/23] dmaengine: ste_dma40: Put the LCPA SRAM node Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-20 18:59 ` [PATCH v5 16/23] dmaengine: ste_dma40: Validate disabled channel indexes Linus Walleij
` (7 subsequent siblings)
22 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 02ccad255a75..48dccd432ff4 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,
@@ -2145,7 +2130,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);
@@ -3428,12 +3414,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,
@@ -3682,6 +3663,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)
@@ -3695,18 +3677,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] 37+ messages in thread
* [PATCH v5 16/23] dmaengine: ste_dma40: Validate disabled channel indexes
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (14 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 15/23] dmaengine: ste_dma40: Fix memcpy channel parsing Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-20 18:59 ` [PATCH v5 17/23] dmaengine: ste_dma40: Validate DMA specifier length Linus Walleij
` (6 subsequent siblings)
22 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 48dccd432ff4..8ae236d8270f 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3413,6 +3413,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] 37+ messages in thread
* [PATCH v5 17/23] dmaengine: ste_dma40: Validate DMA specifier length
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (15 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 16/23] dmaengine: ste_dma40: Validate disabled channel indexes Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-20 18:59 ` [PATCH v5 18/23] dmaengine: ste_dma40: Reject direction changes after allocation Linus Walleij
` (5 subsequent siblings)
22 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 8ae236d8270f..9db4a325b36e 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -2538,6 +2538,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] 37+ messages in thread
* [PATCH v5 18/23] dmaengine: ste_dma40: Reject direction changes after allocation
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (16 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 17/23] dmaengine: ste_dma40: Validate DMA specifier length Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-20 18:59 ` [PATCH v5 19/23] dmaengine: ste_dma40: Fix logical channel bounds check Linus Walleij
` (4 subsequent siblings)
22 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 9db4a325b36e..2966a8167c22 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -2406,7 +2406,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);
@@ -2880,6 +2886,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;
@@ -2888,13 +2901,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;
@@ -2904,13 +2910,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] 37+ messages in thread
* [PATCH v5 19/23] dmaengine: ste_dma40: Fix logical channel bounds check
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (17 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 18/23] dmaengine: ste_dma40: Reject direction changes after allocation Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-20 18:59 ` [PATCH v5 20/23] dmaengine: ste_dma40: Fix event group bounds Linus Walleij
` (3 subsequent siblings)
22 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 2966a8167c22..b4dff597fa7c 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1872,15 +1872,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] 37+ messages in thread
* [PATCH v5 20/23] dmaengine: ste_dma40: Fix event group bounds
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (18 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 19/23] dmaengine: ste_dma40: Fix logical channel bounds check Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-20 18:59 ` [PATCH v5 21/23] dmaengine: ste_dma40: Search all blocks for fixed logical channels Linus Walleij
` (2 subsequent siblings)
22 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 b4dff597fa7c..13d1592bb3a4 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
@@ -519,6 +523,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
@@ -533,6 +538,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;
@@ -1874,6 +1880,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;
@@ -1890,8 +1901,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;
}
@@ -2056,8 +2066,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,
@@ -2077,8 +2091,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;
@@ -3461,6 +3477,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;
@@ -3474,6 +3491,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] 37+ messages in thread
* [PATCH v5 21/23] dmaengine: ste_dma40: Search all blocks for fixed logical channels
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (19 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 20/23] dmaengine: ste_dma40: Fix event group bounds Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-20 18:59 ` [PATCH v5 22/23] dmaengine: ste_dma40: Validate fixed physical channel indexes Linus Walleij
2026-09-20 18:59 ` [PATCH v5 23/23] dmaengine: ste_dma40: Validate memcpy configuration Linus Walleij
22 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 13d1592bb3a4..41c7561314c1 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -2099,11 +2099,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] 37+ messages in thread
* [PATCH v5 22/23] dmaengine: ste_dma40: Validate fixed physical channel indexes
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (20 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 21/23] dmaengine: ste_dma40: Search all blocks for fixed logical channels Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
2026-09-20 18:59 ` [PATCH v5 23/23] dmaengine: ste_dma40: Validate memcpy configuration Linus Walleij
22 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 41c7561314c1..a3159a657040 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1906,6 +1906,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] 37+ messages in thread
* [PATCH v5 23/23] dmaengine: ste_dma40: Validate memcpy configuration
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (21 preceding siblings ...)
2026-09-20 18:59 ` [PATCH v5 22/23] dmaengine: ste_dma40: Validate fixed physical channel indexes Linus Walleij
@ 2026-09-20 18:59 ` Linus Walleij
22 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 18:59 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 a3159a657040..57d556e46402 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1885,7 +1885,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;
}
@@ -2159,12 +2160,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);
@@ -2172,6 +2178,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] 37+ messages in thread
* Re: [PATCH v5 08/23] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status
2026-09-20 18:59 ` [PATCH v5 08/23] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status Linus Walleij
@ 2026-09-20 19:10 ` sashiko-bot
2026-09-20 21:26 ` Linus Walleij
2026-09-22 19:37 ` Frank Li
1 sibling, 1 reply; 37+ messages in thread
From: sashiko-bot @ 2026-09-20 19:10 UTC (permalink / raw)
To: Linus Walleij; +Cc: vkoul, dmaengine, Frank.Li
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] dmaengine: ste_dma40: IRQ_NONE on AMP-shared line disables IRQ
--
commit 2ed162fa364e4bf7289280c1ba5e44ef8ed65fc3
Author: Linus Walleij <linusw@kernel.org>
Subject: dmaengine: ste_dma40: Return IRQ_NONE without interrupt status
This commit changes the DMA40 interrupt handler to return IRQ_NONE when no
status bits are set. It aims to allow the generic spurious interrupt detector
to disable a faulty status-less interrupt line while still treating foreign
status as handled.
> diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
> index bb052d3028cc2..4380f9a1b035c 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -1807,6 +1807,7 @@ static void dma_tasklet(struct tasklet_struct *t)
>
> static irqreturn_t d40_handle_interrupt(int irq, void *data)
> {
> + irqreturn_t handled = IRQ_NONE;
[ ... ]
> @@ -1872,7 +1879,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;
[Severity: High]
Does returning IRQ_NONE here risk disabling the DMA40 interrupt line in an
Asymmetric Multi-Processing (AMP) setup?
In an AMP setup where both Linux and another SoC core share the DMA40
interrupt line and status registers, both cores receive the IRQ when an
interrupt fires for the other core's channel. If the faster core (e.g.,
an RTOS) clears the status register before the slower Linux handler reads
it, Linux will read 0 bits set, and handled will remain IRQ_NONE.
If the other core processes many interrupts, such as audio streams, Linux
could quickly accumulate 99,900 'ghost' interrupts within a 100,000
interrupt window. This would cause the generic Linux spurious interrupt
detector to permanently disable the shared IRQ line, breaking all
Linux-owned DMA channels. Returning IRQ_NONE for status-less interrupts
appears to be incompatible with AMP-shared interrupt lines.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260920-dma40-fixes-v5-0-5c55cd7f92f9@kernel.org?part=8
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v5 08/23] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status
2026-09-20 19:10 ` sashiko-bot
@ 2026-09-20 21:26 ` Linus Walleij
0 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-20 21:26 UTC (permalink / raw)
To: sashiko-reviews; +Cc: vkoul, dmaengine, Frank.Li
Hello Sashiko,
On Sun, Sep 20, 2026 at 9:10 PM <sashiko-bot@kernel.org> wrote:
> [Severity: High]
> Does returning IRQ_NONE here risk disabling the DMA40 interrupt line in an
> Asymmetric Multi-Processing (AMP) setup?
You didn't really read the cover letter did you?
This is FRINGE, the other CPU cores on the system (ux500)
where this is used do *NOT* use the DMA40 engine.
Nevertheless I have done my best to circumvent this.
I think the series is good to go.
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v5 01/23] dmaengine: ste_dma40: Fix physical cyclic capability
2026-09-20 18:59 ` [PATCH v5 01/23] dmaengine: ste_dma40: Fix physical cyclic capability Linus Walleij
@ 2026-09-21 16:39 ` Frank Li
0 siblings, 0 replies; 37+ messages in thread
From: Frank Li @ 2026-09-21 16:39 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel
On Sun, Sep 20, 2026 at 08:59:36PM +0200, Linus Walleij wrote:
> d40_dmaengine_init() configures dma_both for physical channels that can
> handle both slave and memcpy transfers. Physical DMA40 channel setup has
> supported cyclic LLIs since cyclic transfer support was added, but the
> DMA_CYCLIC capability is set on dma_slave a second time instead of
> dma_both.
>
> Set DMA_CYCLIC on dma_both so d40_ops_init() installs
> device_prep_dma_cyclic and physical channels advertise cyclic support.
>
> Fixes: 0c842b551063 ("dma40: cyclic xfer support")
> Assisted-by: LLM
> Signed-off-by: Linus Walleij <linusw@kernel.org>
> ---
Missed my review by tags at v3
https://lore.kernel.org/all/aq2XrwzJDF8yZCbz@SMW015318/
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/dma/ste_dma40.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
> index 0d9ffa3e2663..e4d689c9eba8 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -2897,7 +2897,7 @@ static int __init d40_dmaengine_init(struct d40_base *base,
> dma_cap_zero(base->dma_both.cap_mask);
> dma_cap_set(DMA_SLAVE, base->dma_both.cap_mask);
> dma_cap_set(DMA_MEMCPY, base->dma_both.cap_mask);
> - dma_cap_set(DMA_CYCLIC, base->dma_slave.cap_mask);
> + dma_cap_set(DMA_CYCLIC, base->dma_both.cap_mask);
>
> d40_ops_init(base, &base->dma_both);
> err = dmaenginem_async_device_register(&base->dma_both);
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v5 02/23] dmaengine: ste_dma40: Fix cyclic transfer residue
2026-09-20 18:59 ` [PATCH v5 02/23] dmaengine: ste_dma40: Fix cyclic transfer residue Linus Walleij
@ 2026-09-21 16:57 ` Frank Li
0 siblings, 0 replies; 37+ messages in thread
From: Frank Li @ 2026-09-21 16:57 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel
On Sun, Sep 20, 2026 at 08:59:37PM +0200, Linus Walleij wrote:
> DMA40 reads residue from the element count of the currently active LLI.
> For a cyclic transfer this reports at most one period, not the bytes
> remaining until the cyclic buffer wraps.
>
> Once DMA40 advertises burst granularity, DMAengine PCM uses this residue
> directly. For a four-period PCM buffer it consequently reports the
> hardware pointer near three periods after every period interrupt. ALSA
> eventually stops playback with -EIO although DMA period callbacks
> continue.
>
> 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++) {
what's means of 3, retry counter? can you define macro for it.
> + 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;
> }
> +
Nit: clean this unnessary changes.
Frank
> } 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 [flat|nested] 37+ messages in thread
* Re: [PATCH v5 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks
2026-09-20 18:59 ` [PATCH v5 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks Linus Walleij
@ 2026-09-21 21:51 ` Frank Li
2026-09-22 21:13 ` Linus Walleij
0 siblings, 1 reply; 37+ messages in thread
From: Frank Li @ 2026-09-21 21:51 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel
On Sun, Sep 20, 2026 at 08:59:38PM +0200, Linus Walleij wrote:
> 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.
>
> If the pointer cannot be sampled, report one callback and mark the callback
> position invalid. At the next successful sample, resynchronize instead of
> deriving a count from stale state. Any additional periods coalesced while
> the pointer is unavailable cannot be recovered reliably.
>
> DMA40 exposes one latched terminal-count status bit, not an event counter.
> If the pointer remains at the same boundary, hardware cannot distinguish a
> repeated status from an exact full-buffer lap. In this ambiguous case,
> report one callback rather than potentially enqueueing a whole buffer of
> spurious callbacks. Exact full laps, including multiple laps, therefore
> cannot be recovered by software.
>
> 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 | 62 ++++++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 61 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
> index c9983e600daf..1f9e8c7249b1 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -381,11 +381,14 @@ 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.
> * @is_in_client_list: true if the client owns this descriptor.
> * @cyclic: true if this is a cyclic job
> + * @cyclic_callback_pos_valid: Whether cyclic_callback_pos is reliable.
> *
> * This descriptor is used for both logical and physical transfers.
> */
> @@ -402,12 +405,15 @@ 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;
>
> bool is_in_client_list;
> bool cyclic;
> + bool cyclic_callback_pos_valid;
> };
>
> /**
> @@ -1484,6 +1490,55 @@ 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)
> + return 1;
> +
> + if (!d40_cyclic_offset(d40c, d40d, &offset)) {
> + d40d->cyclic_callback_pos_valid = false;
looks like cyclic_callback_pos_valid is not useful. if d40_cyclic_offset()
failure, always return 1.
Logically, can't reconfigized n*period_len. assume period is 4.
if there are not additional register to recorder counter, you can't
distringiush, 1, 5, 9, ... irq elapsed.
So we have to assume never elapse more than peroid.
Frank
> + 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 (!d40d->cyclic_callback_pos_valid) {
> + /*
> + * One callback was reported without a reliable pointer.
> + * Resynchronize instead of deriving periods from stale state.
> + */
> + periods = 1;
> + } else 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 {
> + /*
> + * The TC status is a single latched bit. An unchanged pointer
> + * cannot distinguish a complete lap from a repeated interrupt,
> + * so do not amplify it into a buffer's worth of callbacks.
> + */
> + periods = 1;
> + }
> +
> + d40d->cyclic_callback_pos = current_pos;
> + d40d->cyclic_callback_pos_valid = true;
> +
> + return periods;
> +}
> +
> static bool d40_tx_is_linked(struct d40_chan *d40c)
> {
> bool is_link;
> @@ -1606,6 +1661,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 +1687,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 +1708,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 +2693,9 @@ 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;
> + desc->cyclic_callback_pos_valid = true;
> }
>
> kfree(sg);
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v5 04/23] dmaengine: ste_dma40: Fix failed start cleanup
2026-09-20 18:59 ` [PATCH v5 04/23] dmaengine: ste_dma40: Fix failed start cleanup Linus Walleij
@ 2026-09-21 22:10 ` Frank Li
0 siblings, 0 replies; 37+ messages in thread
From: Frank Li @ 2026-09-21 22:10 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel, sashiko-bot
On Sun, Sep 20, 2026 at 08:59:39PM +0200, Linus Walleij wrote:
> 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>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> 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 1f9e8c7249b1..280a0d2f16c5 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -1650,8 +1650,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;
> @@ -1698,14 +1705,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;
> @@ -1719,20 +1726,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);
>
> /*
> @@ -1748,7 +1757,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 [flat|nested] 37+ messages in thread
* Re: [PATCH v5 05/23] dmaengine: ste_dma40: Fix probe runtime PM disable
2026-09-20 18:59 ` [PATCH v5 05/23] dmaengine: ste_dma40: Fix probe runtime PM disable Linus Walleij
@ 2026-09-21 22:16 ` Frank Li
0 siblings, 0 replies; 37+ messages in thread
From: Frank Li @ 2026-09-21 22:16 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel, sashiko-bot
On Sun, Sep 20, 2026 at 08:59:40PM +0200, Linus Walleij wrote:
> 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 280a0d2f16c5..1d02226ab5ff 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -3661,6 +3661,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;
>
> @@ -3748,12 +3749,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");
> @@ -3782,6 +3777,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;
Does devm_pm_runtime_set_active_enabled() help your case? I have not other
driver need this kindle varible.
Frank
> +
> + 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)
> @@ -3817,7 +3819,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 [flat|nested] 37+ messages in thread
* Re: [PATCH v5 06/23] dmaengine: ste_dma40: Check runtime PM in IRQ
2026-09-20 18:59 ` [PATCH v5 06/23] dmaengine: ste_dma40: Check runtime PM in IRQ Linus Walleij
@ 2026-09-21 22:19 ` Frank Li
0 siblings, 0 replies; 37+ messages in thread
From: Frank Li @ 2026-09-21 22:19 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel, sashiko-bot
On Sun, Sep 20, 2026 at 08:59:41PM +0200, Linus Walleij wrote:
> 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>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> 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 1d02226ab5ff..58128a980847 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -1798,6 +1798,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);
>
> @@ -1846,6 +1851,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 [flat|nested] 37+ messages in thread
* Re: [PATCH v5 07/23] dmaengine: ste_dma40: Handle runtime PM resume errors
2026-09-20 18:59 ` [PATCH v5 07/23] dmaengine: ste_dma40: Handle runtime PM resume errors Linus Walleij
@ 2026-09-22 19:23 ` Frank Li
0 siblings, 0 replies; 37+ messages in thread
From: Frank Li @ 2026-09-22 19:23 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel
On Sun, Sep 20, 2026 at 08:59:42PM +0200, Linus Walleij wrote:
> 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 58128a980847..bb052d3028cc 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -1567,11 +1567,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;
> }
> @@ -1591,13 +1594,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;
> }
> @@ -1634,8 +1640,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 */
> @@ -2150,11 +2168,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;
> @@ -2176,13 +2192,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))
> @@ -2197,7 +2221,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;
> }
>
> @@ -2572,10 +2597,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");
> @@ -2583,8 +2612,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)) {
> @@ -2616,6 +2643,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;
> }
> @@ -2767,6 +2795,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) {
> @@ -2776,19 +2805,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;
pm_acquired is not necesary
ret = pm_runtime_resume_and_get(d40c->base->dev);
if (ret >= ) {
ret = d40_channel_execute_command(d40c, D40_DMA_STOP);
if (ret)
...
pm_runtime_put_autosuspend(d40c->base->dev);
}
d40_term_all(d40c); /* In your patch, d40_term_all(d40c) can be call
without acquire runtime pm
...
Frank
> }
>
> static int
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v5 08/23] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status
2026-09-20 18:59 ` [PATCH v5 08/23] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status Linus Walleij
2026-09-20 19:10 ` sashiko-bot
@ 2026-09-22 19:37 ` Frank Li
2026-09-22 23:17 ` Linus Walleij
1 sibling, 1 reply; 37+ messages in thread
From: Frank Li @ 2026-09-22 19:37 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel
On Sun, Sep 20, 2026 at 08:59:43PM +0200, Linus Walleij wrote:
suggested subject:
return IRQ_NONE when no interrupt is pending
> d40_handle_interrupt() returns IRQ_HANDLED even when no terminal-count or
> error status bit is set. If the line remains asserted without matching
> status, reporting it as handled prevents the generic interrupt code from
> detecting the stuck interrupt.
Looks like it is debug function. The major purpose is return IRQ_NONE should
be for irq sharing?
Frank
>
> Track whether the handler observes any DMA40 status. Return IRQ_NONE only
> when none is present, allowing the generic spurious interrupt detector to
> disable a faulty status-less interrupt line.
>
> DMA40 channels can be owned by other SoC cores. Their status still explains
> why the interrupt fired, but Linux must not acknowledge it. Treat foreign
> status as handled and leave its acknowledgment to the owning core, while
> acknowledging and dispatching only registered Linux channels.
>
> 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 | 9 ++++++++-
> 1 file changed, 8 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
> index bb052d3028cc..4380f9a1b035 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -1807,6 +1807,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;
> @@ -1840,6 +1841,12 @@ static irqreturn_t d40_handle_interrupt(int irq, void *data)
> row = chan / BITS_PER_LONG;
> idx = chan & (BITS_PER_LONG - 1);
>
> + /*
> + * Status for a channel owned by another core still explains
> + * the interrupt, but only ACK Linux-owned channels below.
> + */
> + handled = IRQ_HANDLED;
> +
> if (il[row].offset == D40_PHY_CHAN)
> d40c = base->lookup_phy_chans[idx];
> else
> @@ -1872,7 +1879,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 [flat|nested] 37+ messages in thread
* Re: [PATCH v5 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks
2026-09-21 21:51 ` Frank Li
@ 2026-09-22 21:13 ` Linus Walleij
0 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-22 21:13 UTC (permalink / raw)
To: Frank Li; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel
On Mon, Sep 21, 2026 at 11:52 PM Frank Li <Frank.li@oss.nxp.com> wrote:
> > +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)
> > + return 1;
> > +
> > + if (!d40_cyclic_offset(d40c, d40d, &offset)) {
> > + d40d->cyclic_callback_pos_valid = false;
>
> looks like cyclic_callback_pos_valid is not useful. if d40_cyclic_offset()
> failure, always return 1.
>
> Logically, can't reconfigized n*period_len. assume period is 4.
> if there are not additional register to recorder counter, you can't
> distringiush, 1, 5, 9, ... irq elapsed.
>
> So we have to assume never elapse more than peroid.
I'll try to make it clearer in the code, but I think it can detect
the minimum buffer counts that we can see, if we have for
example 4 buffer counts (we should see 4) if we are one period
off that means we could have had 1, 5, 9 .. etc periods.
If we are two periods off we could have had 2, 6, 10 ... etc
periods.
If we wrapped around the whoe buffer one or several
times there is no way we can see that :/
We report 2 missed periods even if it was actually 6 we
missed for example, I'm just hoping it will mostly be 2 if
it ever happens. It's not like this is a problem I have seen
in practice, it's driven by Sashiko reviews.
Maybe this patch should be dropped altogether,
it's just me chatting with Sashiko about things I don't
think happens much.
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v5 08/23] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status
2026-09-22 19:37 ` Frank Li
@ 2026-09-22 23:17 ` Linus Walleij
0 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-22 23:17 UTC (permalink / raw)
To: Frank Li; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel
Hi Frank,
On Tue, Sep 22, 2026 at 9:38 PM Frank Li <Frank.li@oss.nxp.com> wrote:
> On Sun, Sep 20, 2026 at 08:59:43PM +0200, Linus Walleij wrote:
>
> suggested subject:
>
> return IRQ_NONE when no interrupt is pending
>
> > d40_handle_interrupt() returns IRQ_HANDLED even when no terminal-count or
> > error status bit is set. If the line remains asserted without matching
> > status, reporting it as handled prevents the generic interrupt code from
> > detecting the stuck interrupt.
>
> Looks like it is debug function. The major purpose is return IRQ_NONE should
> be for irq sharing?
That's the most usual case, but it just generally means "that IRQ
was not for me / caused by my hardware" so it is something you
can/should emit even for non-shared IRQs in case you realized
you are certain the hardware didn't raise it.
Then it will appear in dmesg as "spurious IRQ" I think.
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v5 21/23] dmaengine: ste_dma40: Search all blocks for fixed logical channels
2026-09-22 23:30 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
@ 2026-09-22 23:31 ` Linus Walleij
0 siblings, 0 replies; 37+ messages in thread
From: Linus Walleij @ 2026-09-22 23:31 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 9ba5e57a9923..e0f3f4ac0e3a 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -2111,11 +2111,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] 37+ messages in thread
end of thread, other threads:[~2026-09-22 23:31 UTC | newest]
Thread overview: 37+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-20 18:59 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
2026-09-20 18:59 ` [PATCH v5 01/23] dmaengine: ste_dma40: Fix physical cyclic capability Linus Walleij
2026-09-21 16:39 ` Frank Li
2026-09-20 18:59 ` [PATCH v5 02/23] dmaengine: ste_dma40: Fix cyclic transfer residue Linus Walleij
2026-09-21 16:57 ` Frank Li
2026-09-20 18:59 ` [PATCH v5 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks Linus Walleij
2026-09-21 21:51 ` Frank Li
2026-09-22 21:13 ` Linus Walleij
2026-09-20 18:59 ` [PATCH v5 04/23] dmaengine: ste_dma40: Fix failed start cleanup Linus Walleij
2026-09-21 22:10 ` Frank Li
2026-09-20 18:59 ` [PATCH v5 05/23] dmaengine: ste_dma40: Fix probe runtime PM disable Linus Walleij
2026-09-21 22:16 ` Frank Li
2026-09-20 18:59 ` [PATCH v5 06/23] dmaengine: ste_dma40: Check runtime PM in IRQ Linus Walleij
2026-09-21 22:19 ` Frank Li
2026-09-20 18:59 ` [PATCH v5 07/23] dmaengine: ste_dma40: Handle runtime PM resume errors Linus Walleij
2026-09-22 19:23 ` Frank Li
2026-09-20 18:59 ` [PATCH v5 08/23] dmaengine: ste_dma40: Return IRQ_NONE without interrupt status Linus Walleij
2026-09-20 19:10 ` sashiko-bot
2026-09-20 21:26 ` Linus Walleij
2026-09-22 19:37 ` Frank Li
2026-09-22 23:17 ` Linus Walleij
2026-09-20 18:59 ` [PATCH v5 09/23] dmaengine: ste_dma40: Init hardware before registration Linus Walleij
2026-09-20 18:59 ` [PATCH v5 10/23] dmaengine: ste_dma40: Fix probe IRQ leak Linus Walleij
2026-09-20 18:59 ` [PATCH v5 11/23] dmaengine: ste_dma40: Fix DMA registration unwind Linus Walleij
2026-09-20 18:59 ` [PATCH v5 12/23] dmaengine: ste_dma40: Fix LCLA allocation order Linus Walleij
2026-09-20 18:59 ` [PATCH v5 13/23] dmaengine: ste_dma40: Fix probe LCLA free Linus Walleij
2026-09-20 18:59 ` [PATCH v5 14/23] dmaengine: ste_dma40: Put the LCPA SRAM node Linus Walleij
2026-09-20 18:59 ` [PATCH v5 15/23] dmaengine: ste_dma40: Fix memcpy channel parsing Linus Walleij
2026-09-20 18:59 ` [PATCH v5 16/23] dmaengine: ste_dma40: Validate disabled channel indexes Linus Walleij
2026-09-20 18:59 ` [PATCH v5 17/23] dmaengine: ste_dma40: Validate DMA specifier length Linus Walleij
2026-09-20 18:59 ` [PATCH v5 18/23] dmaengine: ste_dma40: Reject direction changes after allocation Linus Walleij
2026-09-20 18:59 ` [PATCH v5 19/23] dmaengine: ste_dma40: Fix logical channel bounds check Linus Walleij
2026-09-20 18:59 ` [PATCH v5 20/23] dmaengine: ste_dma40: Fix event group bounds Linus Walleij
2026-09-20 18:59 ` [PATCH v5 21/23] dmaengine: ste_dma40: Search all blocks for fixed logical channels Linus Walleij
2026-09-20 18:59 ` [PATCH v5 22/23] dmaengine: ste_dma40: Validate fixed physical channel indexes Linus Walleij
2026-09-20 18:59 ` [PATCH v5 23/23] dmaengine: ste_dma40: Validate memcpy configuration Linus Walleij
-- strict thread matches above, loose matches on Subject: below --
2026-09-22 23:30 [PATCH v5 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
2026-09-22 23:31 ` [PATCH v5 21/23] dmaengine: ste_dma40: Search all blocks for fixed logical channels Linus Walleij
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.