* [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs
@ 2026-09-24 8:35 Linus Walleij
2026-09-24 8:35 ` [PATCH v6 01/23] dmaengine: ste_dma40: Fix physical cyclic capability Linus Walleij
` (22 more replies)
0 siblings, 23 replies; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: dmaengine, phone-devel, Linus Walleij, Frank Li, 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 v6:
- Simplify termination and channel-release runtime PM balancing by releasing
temporary references in the successful resume paths. Descriptor cleanup
remains unconditional because it does not access DMA40 registers.
- Name and document the three-attempt cyclic-residue pointer sampling bound,
and remove an unrelated whitespace-only change from that patch.
- Replace probe-local runtime PM bookkeeping with
devm_pm_runtime_set_active_enabled(), keeping IRQ registration after
runtime PM is enabled.
- Link to v5: https://lore.kernel.org/r/20260920-dma40-fixes-v5-0-5c55cd7f92f9@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 when no interrupt is pending
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 | 562 +++++++++++++++++++++++++++++++++++++-----------
1 file changed, 436 insertions(+), 126 deletions(-)
---
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
change-id: 20260820-dma40-fixes-b99af66002bf
Best regards,
--
Linus Walleij <linusw@kernel.org>
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v6 01/23] dmaengine: ste_dma40: Fix physical cyclic capability
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 8:35 ` [PATCH v6 02/23] dmaengine: ste_dma40: Fix cyclic transfer residue Linus Walleij
` (21 subsequent siblings)
22 siblings, 0 replies; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 UTC (permalink / raw)
To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij, Frank Li
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")
Reviewed-by: Frank Li <Frank.Li@nxp.com>
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] 45+ messages in thread
* [PATCH v6 02/23] dmaengine: ste_dma40: Fix cyclic transfer residue
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
2026-09-24 8:35 ` [PATCH v6 01/23] dmaengine: ste_dma40: Fix physical cyclic capability Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 14:37 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks Linus Walleij
` (20 subsequent siblings)
22 siblings, 1 reply; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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.
Make at most three residue sampling attempts: one initial read plus two
retries. The retries let transient split-register updates or LLI relink
windows settle, while the limit prevents unbounded polling if hardware does
not yield a valid pointer.
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 | 106 +++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 101 insertions(+), 5 deletions(-)
diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index e4d689c9eba8..eab9c09b4bfe 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -65,6 +65,9 @@ struct stedma40_platform_data {
/* Maximum iterations taken before giving up suspending a channel */
#define D40_SUSPEND_MAX_IT 500
+/* Maximum attempts to sample a stable cyclic residue position */
+#define D40_RESIDUE_MAX_ATTEMPTS 3
+
/* Milliseconds */
#define DMA40_AUTOSUSPEND_DELAY 100
@@ -378,6 +381,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 +402,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 +1429,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 < D40_RESIDUE_MAX_ATTEMPTS; 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 < D40_RESIDUE_MAX_ATTEMPTS; 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;
@@ -2108,15 +2175,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 +2324,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 +2607,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 +2633,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 +2659,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] 45+ messages in thread
* [PATCH v6 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
2026-09-24 8:35 ` [PATCH v6 01/23] dmaengine: ste_dma40: Fix physical cyclic capability Linus Walleij
2026-09-24 8:35 ` [PATCH v6 02/23] dmaengine: ste_dma40: Fix cyclic transfer residue Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 14:48 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 04/23] dmaengine: ste_dma40: Fix failed start cleanup Linus Walleij
` (19 subsequent siblings)
22 siblings, 1 reply; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 UTC (permalink / raw)
To: Vinod Koul, Frank Li; +Cc: dmaengine, phone-devel, Linus Walleij
DMA40 has one terminal-count status bit per channel, so cyclic period
interrupts can coalesce.
Use the memory-side pointer displacement to queue the minimum observable
number of callbacks. If sampling fails, queue one callback and invalidate
the saved position; the next successful sample resynchronizes without
recounting it.
The pointer is modulo the cyclic buffer: a displacement of d periods can
mean d plus n complete iterations of the buffer. Report d, so the driver
could have missed n iterations of the buffer; DMA40 has no counter to
recover them. For an unchanged pointer, report one callback rather than
risk a spurious full-buffer burst.
This condition has not been seen in practice and is only a product of
review comments that the buffer can miss interrupts.
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 | 72 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 71 insertions(+), 1 deletion(-)
diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index eab9c09b4bfe..7384a50cb895 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -384,11 +384,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.
*/
@@ -405,12 +408,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;
};
/**
@@ -1487,6 +1493,64 @@ 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) {
+ /*
+ * The previous interrupt was reported without a pointer
+ * sample. Resynchronize without using the stale position,
+ * which would count that callback again.
+ */
+ d40d->cyclic_callback_pos = current_pos;
+ d40d->cyclic_callback_pos_valid = true;
+ return 1;
+ }
+
+ /*
+ * The pointer wraps with the cyclic buffer, so its displacement is
+ * only the minimum number of elapsed periods. Complete buffer laps
+ * are not observable.
+ */
+ 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;
+
+ return periods;
+}
+
static bool d40_tx_is_linked(struct d40_chan *d40c)
{
bool is_link;
@@ -1609,6 +1673,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);
@@ -1633,6 +1698,8 @@ static void dma_tc_handle(struct d40_chan *d40c)
if (d40d->lli_current == d40d->lli_len)
d40d->lli_current = 0;
}
+
+ callbacks = d40_cyclic_periods_elapsed(d40c, d40d);
} else {
d40_lcla_free_all(d40c, d40d);
@@ -1653,7 +1720,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);
}
@@ -2638,6 +2705,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] 45+ messages in thread
* [PATCH v6 04/23] dmaengine: ste_dma40: Fix failed start cleanup
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (2 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 8:35 ` [PATCH v6 05/23] dmaengine: ste_dma40: Fix probe runtime PM disable Linus Walleij
` (18 subsequent siblings)
22 siblings, 0 replies; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: dmaengine, phone-devel, Linus Walleij, sashiko-bot, Frank Li
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/
Reviewed-by: Frank Li <Frank.Li@nxp.com>
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 7384a50cb895..621ae9ffcd57 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1662,8 +1662,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;
@@ -1710,14 +1717,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;
@@ -1731,20 +1738,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);
/*
@@ -1760,7 +1769,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] 45+ messages in thread
* [PATCH v6 05/23] dmaengine: ste_dma40: Fix probe runtime PM disable
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (3 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 04/23] dmaengine: ste_dma40: Fix failed start cleanup Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 14:50 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 06/23] dmaengine: ste_dma40: Check runtime PM in IRQ Linus Walleij
` (17 subsequent siblings)
22 siblings, 1 reply; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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.
Use devm_pm_runtime_set_active_enabled() to mark the controller active,
enable runtime PM and register balanced cleanup for probe failure. This
avoids both the disable-depth imbalance and ad-hoc probe state tracking.
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 | 23 +++++++++++++----------
1 file changed, 13 insertions(+), 10 deletions(-)
diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 621ae9ffcd57..de1d5670adec 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3760,12 +3760,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");
@@ -3790,10 +3784,20 @@ static int __init d40_probe(struct platform_device *pdev)
pm_runtime_irq_safe(base->dev);
pm_runtime_set_autosuspend_delay(base->dev, DMA40_AUTOSUSPEND_DELAY);
- pm_runtime_use_autosuspend(base->dev);
pm_runtime_mark_last_busy(base->dev);
- pm_runtime_set_active(base->dev);
- pm_runtime_enable(base->dev);
+
+ ret = devm_pm_runtime_set_active_enabled(base->dev);
+ if (ret) {
+ d40_err(dev, "Failed to enable runtime PM: %d\n", ret);
+ goto destroy_cache;
+ }
+ pm_runtime_use_autosuspend(base->dev);
+
+ 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)
@@ -3829,7 +3833,6 @@ static int __init d40_probe(struct platform_device *pdev)
regulator_disable(base->lcpa_regulator);
regulator_put(base->lcpa_regulator);
}
- pm_runtime_disable(base->dev);
report_failure:
d40_err(dev, "probe failed\n");
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v6 06/23] dmaengine: ste_dma40: Check runtime PM in IRQ
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (4 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 05/23] dmaengine: ste_dma40: Fix probe runtime PM disable Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 8:35 ` [PATCH v6 07/23] dmaengine: ste_dma40: Handle runtime PM resume errors Linus Walleij
` (16 subsequent siblings)
22 siblings, 0 replies; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: dmaengine, phone-devel, Linus Walleij, sashiko-bot, Frank Li
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/
Reviewed-by: Frank Li <Frank.Li@nxp.com>
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 de1d5670adec..690e41ca40f0 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1810,6 +1810,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);
@@ -1858,6 +1863,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] 45+ messages in thread
* [PATCH v6 07/23] dmaengine: ste_dma40: Handle runtime PM resume errors
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (5 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 06/23] dmaengine: ste_dma40: Check runtime PM in IRQ Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 14:54 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 08/23] dmaengine: ste_dma40: Return IRQ_NONE when no interrupt is pending Linus Walleij
` (15 subsequent siblings)
22 siblings, 1 reply; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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. d40_term_all() only releases descriptor state and
does not access DMA40 registers, so it remains unconditional.
Balance each transient runtime PM reference in its successful acquisition
block, without bookkeeping flags. Release the outstanding busy reference
and channel allocation state when freeing a 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 | 74 ++++++++++++++++++++++++++++++++++---------------
1 file changed, 52 insertions(+), 22 deletions(-)
diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 690e41ca40f0..712719f0c4cf 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1579,11 +1579,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;
}
@@ -1603,13 +1606,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;
}
@@ -1646,8 +1652,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 */
@@ -2164,9 +2182,6 @@ static int d40_free_dma(struct d40_chan *d40c)
struct d40_phy_res *phy = d40c->phy_chan;
bool is_src;
- /* Terminate all queued and active transfers */
- d40_term_all(d40c);
-
if (phy == NULL) {
chan_err(d40c, "phy == null\n");
return -EINVAL;
@@ -2188,11 +2203,18 @@ static int d40_free_dma(struct d40_chan *d40c)
return -EINVAL;
}
- pm_runtime_get_sync(d40c->base->dev);
- res = d40_channel_execute_command(d40c, D40_DMA_STOP);
- if (res) {
- chan_err(d40c, "stop failed\n");
- goto mark_last_busy;
+ /* Release descriptor state; this does not access DMA40 registers. */
+ d40_term_all(d40c);
+
+ res = pm_runtime_resume_and_get(d40c->base->dev);
+ if (res >= 0) {
+ res = d40_channel_execute_command(d40c, D40_DMA_STOP);
+ if (res)
+ chan_err(d40c, "stop failed\n");
+
+ pm_runtime_put_autosuspend(d40c->base->dev);
+ if (res)
+ return res;
}
d40_alloc_mask_free(phy, is_src, chan_is_logical(d40c) ? event : 0);
@@ -2208,8 +2230,6 @@ static int d40_free_dma(struct d40_chan *d40c)
d40c->busy = false;
d40c->phy_chan = NULL;
d40c->configured = false;
- mark_last_busy:
- pm_runtime_put_autosuspend(d40c->base->dev);
return res;
}
@@ -2584,10 +2604,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");
@@ -2595,8 +2619,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)) {
@@ -2628,6 +2650,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;
}
@@ -2788,19 +2811,26 @@ 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) {
+ ret = d40_channel_execute_command(d40c, D40_DMA_STOP);
+ if (ret)
+ chan_err(d40c, "Failed to stop channel\n");
+
+ pm_runtime_put_autosuspend(d40c->base->dev);
+ }
+ /*
+ * Always release software state, even when the controller cannot
+ * resume. d40_term_all() does not access DMA40 registers.
+ */
d40_term_all(d40c);
- 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] 45+ messages in thread
* [PATCH v6 08/23] dmaengine: ste_dma40: Return IRQ_NONE when no interrupt is pending
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (6 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 07/23] dmaengine: ste_dma40: Handle runtime PM resume errors Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 14:55 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 09/23] dmaengine: ste_dma40: Init hardware before registration Linus Walleij
` (14 subsequent siblings)
22 siblings, 1 reply; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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 pending.
IRQ_NONE is not only used to identify a device on a shared interrupt.
The generic IRQ core also counts it as unhandled for spurious IRQ
detection. DMA40 does not request this IRQ with IRQF_SHARED, so interrupt
sharing is not the purpose of this change.
If the line remains asserted without matching DMA40 status, returning
IRQ_HANDLED hides the stuck interrupt and can leave the CPU servicing it
indefinitely. Track whether any DMA40 status is pending and return IRQ_NONE
when none is present, allowing the generic IRQ core to diagnose and
eventually disable the faulty line.
DMA40 channels can be owned by other SoC cores. Their status means that
the interrupt is real, 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 712719f0c4cf..271f653a15c4 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1819,6 +1819,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;
@@ -1852,6 +1853,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
@@ -1884,7 +1891,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] 45+ messages in thread
* [PATCH v6 09/23] dmaengine: ste_dma40: Init hardware before registration
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (7 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 08/23] dmaengine: ste_dma40: Return IRQ_NONE when no interrupt is pending Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 14:58 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 10/23] dmaengine: ste_dma40: Fix probe IRQ leak Linus Walleij
` (13 subsequent siblings)
22 siblings, 1 reply; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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 271f653a15c4..85789ee7c0c9 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3838,19 +3838,21 @@ static int __init d40_probe(struct platform_device *pdev)
}
pm_runtime_use_autosuspend(base->dev);
- 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] 45+ messages in thread
* [PATCH v6 10/23] dmaengine: ste_dma40: Fix probe IRQ leak
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (8 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 09/23] dmaengine: ste_dma40: Init hardware before registration Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 15:02 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 11/23] dmaengine: ste_dma40: Fix DMA registration unwind Linus Walleij
` (12 subsequent siblings)
22 siblings, 1 reply; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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 85789ee7c0c9..36f4cbd4da36 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3718,6 +3718,7 @@ static int __init d40_probe(struct platform_device *pdev)
struct resource *res;
struct resource res_lcpa;
int num_reserved_chans;
+ bool irq_requested = false;
u32 val;
int ret;
@@ -3844,6 +3845,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);
@@ -3880,6 +3882,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);
report_failure:
d40_err(dev, "probe failed\n");
--
2.55.0
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v6 11/23] dmaengine: ste_dma40: Fix DMA registration unwind
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (9 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 10/23] dmaengine: ste_dma40: Fix probe IRQ leak Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 9:10 ` sashiko-bot
2026-09-24 15:11 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 12/23] dmaengine: ste_dma40: Fix LCLA allocation order Linus Walleij
` (11 subsequent siblings)
22 siblings, 2 replies; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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 36f4cbd4da36..87b5f1d97ac2 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3042,6 +3042,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)) {
@@ -3088,6 +3100,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) {
@@ -3103,6 +3120,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) {
@@ -3120,6 +3142,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) {
@@ -3717,6 +3745,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 irq_requested = false;
u32 val;
@@ -3852,20 +3881,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] 45+ messages in thread
* [PATCH v6 12/23] dmaengine: ste_dma40: Fix LCLA allocation order
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (10 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 11/23] dmaengine: ste_dma40: Fix DMA registration unwind Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 15:16 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 13/23] dmaengine: ste_dma40: Fix probe LCLA free Linus Walleij
` (10 subsequent siblings)
22 siblings, 1 reply; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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 87b5f1d97ac2..8998466faa76 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -426,8 +426,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.
*/
@@ -435,7 +435,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;
};
@@ -3606,6 +3606,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;
@@ -3621,20 +3622,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;
}
@@ -3644,7 +3645,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];
@@ -3654,10 +3655,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) {
@@ -3669,8 +3669,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;
@@ -3916,7 +3915,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] 45+ messages in thread
* [PATCH v6 13/23] dmaengine: ste_dma40: Fix probe LCLA free
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (11 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 12/23] dmaengine: ste_dma40: Fix LCLA allocation order Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 15:34 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 14/23] dmaengine: ste_dma40: Put the LCPA SRAM node Linus Walleij
` (9 subsequent siblings)
22 siblings, 1 reply; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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 8998466faa76..b25e9ed5fcb9 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3913,7 +3913,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] 45+ messages in thread
* [PATCH v6 14/23] dmaengine: ste_dma40: Put the LCPA SRAM node
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (12 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 13/23] dmaengine: ste_dma40: Fix probe LCLA free Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 15:43 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 15/23] dmaengine: ste_dma40: Fix memcpy channel parsing Linus Walleij
` (8 subsequent siblings)
22 siblings, 1 reply; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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 b25e9ed5fcb9..6599104ecdde 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3775,6 +3775,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] 45+ messages in thread
* [PATCH v6 15/23] dmaengine: ste_dma40: Fix memcpy channel parsing
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (13 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 14/23] dmaengine: ste_dma40: Put the LCPA SRAM node Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 15:49 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 16/23] dmaengine: ste_dma40: Validate disabled channel indexes Linus Walleij
` (7 subsequent siblings)
22 siblings, 1 reply; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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 6599104ecdde..24994e9bfbbb 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;
};
@@ -89,25 +93,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,
@@ -2157,7 +2142,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);
@@ -3437,12 +3423,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,
@@ -3691,6 +3672,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)
@@ -3704,18 +3686,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] 45+ messages in thread
* [PATCH v6 16/23] dmaengine: ste_dma40: Validate disabled channel indexes
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (14 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 15/23] dmaengine: ste_dma40: Fix memcpy channel parsing Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 15:53 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 17/23] dmaengine: ste_dma40: Validate DMA specifier length Linus Walleij
` (6 subsequent siblings)
22 siblings, 1 reply; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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 24994e9bfbbb..ed233676e718 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -3422,6 +3422,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] 45+ messages in thread
* [PATCH v6 17/23] dmaengine: ste_dma40: Validate DMA specifier length
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (15 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 16/23] dmaengine: ste_dma40: Validate disabled channel indexes Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 15:54 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 18/23] dmaengine: ste_dma40: Reject direction changes after allocation Linus Walleij
` (5 subsequent siblings)
22 siblings, 1 reply; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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 ed233676e718..660ac63efb40 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -2545,6 +2545,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] 45+ messages in thread
* [PATCH v6 18/23] dmaengine: ste_dma40: Reject direction changes after allocation
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (16 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 17/23] dmaengine: ste_dma40: Validate DMA specifier length Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 15:57 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 19/23] dmaengine: ste_dma40: Fix logical channel bounds check Linus Walleij
` (4 subsequent siblings)
22 siblings, 1 reply; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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 660ac63efb40..4f5839691b85 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -2413,7 +2413,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);
@@ -2889,6 +2895,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;
@@ -2897,13 +2910,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;
@@ -2913,13 +2919,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] 45+ messages in thread
* [PATCH v6 19/23] dmaengine: ste_dma40: Fix logical channel bounds check
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (17 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 18/23] dmaengine: ste_dma40: Reject direction changes after allocation Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 8:35 ` [PATCH v6 20/23] dmaengine: ste_dma40: Fix event group bounds Linus Walleij
` (3 subsequent siblings)
22 siblings, 0 replies; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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 4f5839691b85..8b13f1360edb 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1884,15 +1884,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] 45+ messages in thread
* [PATCH v6 20/23] dmaengine: ste_dma40: Fix event group bounds
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (18 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 19/23] dmaengine: ste_dma40: Fix logical channel bounds check Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 9:28 ` sashiko-bot
2026-09-24 8:35 ` [PATCH v6 21/23] dmaengine: ste_dma40: Search all blocks for fixed logical channels Linus Walleij
` (2 subsequent siblings)
22 siblings, 1 reply; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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 8b13f1360edb..d3d79e394d02 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -82,6 +82,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
@@ -522,6 +526,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
@@ -536,6 +541,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;
@@ -1886,6 +1892,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;
@@ -1902,8 +1913,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;
}
@@ -2068,8 +2078,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,
@@ -2089,8 +2103,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;
@@ -3470,6 +3486,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;
@@ -3483,6 +3500,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] 45+ messages in thread
* [PATCH v6 21/23] dmaengine: ste_dma40: Search all blocks for fixed logical channels
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (19 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 20/23] dmaengine: ste_dma40: Fix event group bounds Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 16:08 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 22/23] dmaengine: ste_dma40: Validate fixed physical channel indexes Linus Walleij
2026-09-24 8:35 ` [PATCH v6 23/23] dmaengine: ste_dma40: Validate memcpy configuration Linus Walleij
22 siblings, 1 reply; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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 d3d79e394d02..def2416fb1d0 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] 45+ messages in thread
* [PATCH v6 22/23] dmaengine: ste_dma40: Validate fixed physical channel indexes
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (20 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 21/23] dmaengine: ste_dma40: Search all blocks for fixed logical channels Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 16:09 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 23/23] dmaengine: ste_dma40: Validate memcpy configuration Linus Walleij
22 siblings, 1 reply; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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 def2416fb1d0..d65bf6cfea53 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1918,6 +1918,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] 45+ messages in thread
* [PATCH v6 23/23] dmaengine: ste_dma40: Validate memcpy configuration
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
` (21 preceding siblings ...)
2026-09-24 8:35 ` [PATCH v6 22/23] dmaengine: ste_dma40: Validate fixed physical channel indexes Linus Walleij
@ 2026-09-24 8:35 ` Linus Walleij
2026-09-24 16:11 ` Frank Li
22 siblings, 1 reply; 45+ messages in thread
From: Linus Walleij @ 2026-09-24 8:35 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 d65bf6cfea53..c10012026ea0 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -1897,7 +1897,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;
}
@@ -2171,12 +2172,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);
@@ -2184,6 +2190,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] 45+ messages in thread
* Re: [PATCH v6 11/23] dmaengine: ste_dma40: Fix DMA registration unwind
2026-09-24 8:35 ` [PATCH v6 11/23] dmaengine: ste_dma40: Fix DMA registration unwind Linus Walleij
@ 2026-09-24 9:10 ` sashiko-bot
2026-09-24 15:11 ` Frank Li
1 sibling, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-24 9:10 UTC (permalink / raw)
To: Linus Walleij; +Cc: vkoul, Frank.Li, dmaengine
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] dmaengine: ste_dma40: tasklet UAF on devm_add_action_or_reset failure
--
commit 9ae706f948ab2a5c18bdce25be150382cc3a9c26
Author: Linus Walleij <linusw@kernel.org>
dmaengine: ste_dma40: Fix DMA registration unwind
This commit adds tasklet cleanup actions to the DMAengine registration devres
group. It aims to ensure tasklets are drained before freeing probe resources
on registration failure.
Closes: https://lore.kernel.org/dmaengine/20260819225215.5EA2F1F000E9@smtp.kernel.org/
> diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
> index 36f4cbd4da364..87b5f1d97ac23 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
[ ... ]
> @@ -3088,6 +3100,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;
> +
[Severity: High]
If devm_add_action_or_reset() fails here, it will immediately execute
d40_kill_tasklets(). Because the device interrupt was enabled earlier in
the probe routine via enable_irq(), could an interrupt fire immediately
after this premature cleanup and reschedule the tasklet?
> err = dmaenginem_async_device_register(&base->dma_slave);
>
> if (err) {
[ ... ]
> @@ -3852,20 +3881,33 @@ static int __init d40_probe(struct platform_device *pdev)
[ ... ]
> 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);
[Severity: High]
If we reach this error path because devm_add_action_or_reset() failed,
the tasklet cleanup action was never registered.
Since free_irq() is called here but there is no subsequent tasklet drain,
does this leave any tasklet that was rescheduled after the initial failure
still pending?
Could this result in a use-after-free when the tasklet executes on the
memory freed during the destroy_cache path?
> destroy_cache:
> if (base->lcla_pool.dma_addr)
> dma_unmap_single(base->dev, base->lcla_pool.dma_addr,
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260924-dma40-fixes-v6-0-fdb6755020a2@kernel.org?part=11
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v6 20/23] dmaengine: ste_dma40: Fix event group bounds
2026-09-24 8:35 ` [PATCH v6 20/23] dmaengine: ste_dma40: Fix event group bounds Linus Walleij
@ 2026-09-24 9:28 ` sashiko-bot
0 siblings, 0 replies; 45+ messages in thread
From: sashiko-bot @ 2026-09-24 9:28 UTC (permalink / raw)
To: Linus Walleij; +Cc: dmaengine, Frank.Li, vkoul
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] dmaengine: ste_dma40: channel aliasing for event group 4 on V4B
--
commit 48ff5fdacb29b6847b9d51f8ce427281645c1c9b
Author: Linus Walleij <linusw@kernel.org>
dmaengine: ste_dma40: Fix event group bounds
This commit fixes event group bounds validation by properly restricting
device type values to the hardware event-group range and removing incorrect
physical channel limits. It also stops physical channel searches before they
can index past the allocated channel array.
Closes: https://lore.kernel.org/dmaengine/20260819225008.5F9651F000E9@smtp.kernel.org/
> diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
> index 8b13f1360edbe..d3d79e394d028 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
[ ... ]
> @@ -2068,8 +2078,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;
> +
[Severity: High]
Does this cause channel aliasing for event group 4 on V4B hardware?
Since D40_GROUP_SIZE is hardcoded to 8, if event_group is 4 (allowed for V4B)
and j is 0, phy_num evaluates to 8. But if event_group is 0 and j is 8,
phy_num also evaluates to 8.
Could this collision map two different groups to the same physical channel?
Additionally, since D40_PHYS_TO_GROUP is hardcoded as ((phys & 7) / 2),
channel 8 evaluates to group 0. Might this incorrectly configure the GCC clock
and hardware registers for group 0 instead of 4, breaking DMA operations for
event group 4 and channels >= 8?
[ ... ]
> @@ -2089,8 +2103,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;
[Severity: High]
Will this mathematical collision also occur here for logical channel allocation
when event_group is 4?
[ ... ]
> @@ -3470,6 +3486,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;
[Severity: High]
Does setting the max event groups to 5 allow event_group = 4, which
ultimately exposes the aliasing bug against the hardcoded block size of 8
in d40_allocate_channel()?
> 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;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260924-dma40-fixes-v6-0-fdb6755020a2@kernel.org?part=20
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v6 02/23] dmaengine: ste_dma40: Fix cyclic transfer residue
2026-09-24 8:35 ` [PATCH v6 02/23] dmaengine: ste_dma40: Fix cyclic transfer residue Linus Walleij
@ 2026-09-24 14:37 ` Frank Li
0 siblings, 0 replies; 45+ messages in thread
From: Frank Li @ 2026-09-24 14:37 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel
On Thu, Sep 24, 2026 at 10:35:14AM +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.
>
> Make at most three residue sampling attempts: one initial read plus two
> retries. The retries let transient split-register updates or LLI relink
> windows settle, while the limit prevents unbounded polling if hardware does
> not yield a valid pointer.
>
> 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>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/dma/ste_dma40.c | 106 +++++++++++++++++++++++++++++++++++++++++++++---
> 1 file changed, 101 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
> index e4d689c9eba8..eab9c09b4bfe 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -65,6 +65,9 @@ struct stedma40_platform_data {
> /* Maximum iterations taken before giving up suspending a channel */
> #define D40_SUSPEND_MAX_IT 500
>
> +/* Maximum attempts to sample a stable cyclic residue position */
> +#define D40_RESIDUE_MAX_ATTEMPTS 3
> +
> /* Milliseconds */
> #define DMA40_AUTOSUSPEND_DELAY 100
>
> @@ -378,6 +381,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 +402,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 +1429,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 < D40_RESIDUE_MAX_ATTEMPTS; 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 < D40_RESIDUE_MAX_ATTEMPTS; 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;
> @@ -2108,15 +2175,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 +2324,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 +2607,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 +2633,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 +2659,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] 45+ messages in thread
* Re: [PATCH v6 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks
2026-09-24 8:35 ` [PATCH v6 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks Linus Walleij
@ 2026-09-24 14:48 ` Frank Li
0 siblings, 0 replies; 45+ messages in thread
From: Frank Li @ 2026-09-24 14:48 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel
On Thu, Sep 24, 2026 at 10:35:15AM +0200, Linus Walleij wrote:
> DMA40 has one terminal-count status bit per channel, so cyclic period
> interrupts can coalesce.
>
> Use the memory-side pointer displacement to queue the minimum observable
> number of callbacks. If sampling fails, queue one callback and invalidate
> the saved position; the next successful sample resynchronizes without
> recounting it.
>
> The pointer is modulo the cyclic buffer: a displacement of d periods can
> mean d plus n complete iterations of the buffer. Report d, so the driver
> could have missed n iterations of the buffer; DMA40 has no counter to
> recover them. For an unchanged pointer, report one callback rather than
> risk a spurious full-buffer burst.
>
> This condition has not been seen in practice and is only a product of
> review comments that the buffer can miss interrupts.
>
> 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 | 72 ++++++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 71 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
> index eab9c09b4bfe..7384a50cb895 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -384,11 +384,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.
> */
> @@ -405,12 +408,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;
> };
>
> /**
> @@ -1487,6 +1493,64 @@ 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) {
> + /*
> + * The previous interrupt was reported without a pointer
> + * sample. Resynchronize without using the stale position,
> + * which would count that callback again.
> + */
> + d40d->cyclic_callback_pos = current_pos;
> + d40d->cyclic_callback_pos_valid = true;
> + return 1;
> + }
> +
> + /*
> + * The pointer wraps with the cyclic buffer, so its displacement is
> + * only the minimum number of elapsed periods. Complete buffer laps
> + * are not observable.
> + */
> + 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;
typical cyclic buffer can get delta without if branch
delta = (current_pos + d40d->cyclic_buf_len - d40d->cyclic_callback_pos ) % d40d->cyclic_buf_len
periods /= 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;
> + }
above == case result is period is 0.
So if (!period) return 1
Frank
> +
> + d40d->cyclic_callback_pos = current_pos;
> +
> + return periods;
> +}
> +
> static bool d40_tx_is_linked(struct d40_chan *d40c)
> {
> bool is_link;
> @@ -1609,6 +1673,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);
> @@ -1633,6 +1698,8 @@ static void dma_tc_handle(struct d40_chan *d40c)
> if (d40d->lli_current == d40d->lli_len)
> d40d->lli_current = 0;
> }
> +
> + callbacks = d40_cyclic_periods_elapsed(d40c, d40d);
> } else {
> d40_lcla_free_all(d40c, d40d);
>
> @@ -1653,7 +1720,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);
>
> }
> @@ -2638,6 +2705,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] 45+ messages in thread
* Re: [PATCH v6 05/23] dmaengine: ste_dma40: Fix probe runtime PM disable
2026-09-24 8:35 ` [PATCH v6 05/23] dmaengine: ste_dma40: Fix probe runtime PM disable Linus Walleij
@ 2026-09-24 14:50 ` Frank Li
0 siblings, 0 replies; 45+ messages in thread
From: Frank Li @ 2026-09-24 14:50 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel, sashiko-bot
On Thu, Sep 24, 2026 at 10:35:17AM +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.
>
> Use devm_pm_runtime_set_active_enabled() to mark the controller active,
> enable runtime PM and register balanced cleanup for probe failure. This
> avoids both the disable-depth imbalance and ad-hoc probe state tracking.
>
> 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>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/dma/ste_dma40.c | 23 +++++++++++++----------
> 1 file changed, 13 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
> index 621ae9ffcd57..de1d5670adec 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -3760,12 +3760,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");
> @@ -3790,10 +3784,20 @@ static int __init d40_probe(struct platform_device *pdev)
>
> pm_runtime_irq_safe(base->dev);
> pm_runtime_set_autosuspend_delay(base->dev, DMA40_AUTOSUSPEND_DELAY);
> - pm_runtime_use_autosuspend(base->dev);
> pm_runtime_mark_last_busy(base->dev);
> - pm_runtime_set_active(base->dev);
> - pm_runtime_enable(base->dev);
> +
> + ret = devm_pm_runtime_set_active_enabled(base->dev);
> + if (ret) {
> + d40_err(dev, "Failed to enable runtime PM: %d\n", ret);
> + goto destroy_cache;
> + }
> + pm_runtime_use_autosuspend(base->dev);
> +
> + 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)
> @@ -3829,7 +3833,6 @@ static int __init d40_probe(struct platform_device *pdev)
> regulator_disable(base->lcpa_regulator);
> regulator_put(base->lcpa_regulator);
> }
> - pm_runtime_disable(base->dev);
>
> report_failure:
> d40_err(dev, "probe failed\n");
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v6 07/23] dmaengine: ste_dma40: Handle runtime PM resume errors
2026-09-24 8:35 ` [PATCH v6 07/23] dmaengine: ste_dma40: Handle runtime PM resume errors Linus Walleij
@ 2026-09-24 14:54 ` Frank Li
0 siblings, 0 replies; 45+ messages in thread
From: Frank Li @ 2026-09-24 14:54 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel
On Thu, Sep 24, 2026 at 10:35:19AM +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. d40_term_all() only releases descriptor state and
> does not access DMA40 registers, so it remains unconditional.
>
> Balance each transient runtime PM reference in its successful acquisition
> block, without bookkeeping flags. Release the outstanding busy reference
> and channel allocation state when freeing a 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>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/dma/ste_dma40.c | 74 ++++++++++++++++++++++++++++++++++---------------
> 1 file changed, 52 insertions(+), 22 deletions(-)
>
> diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
> index 690e41ca40f0..712719f0c4cf 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -1579,11 +1579,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;
> }
> @@ -1603,13 +1606,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;
> }
> @@ -1646,8 +1652,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 */
> @@ -2164,9 +2182,6 @@ static int d40_free_dma(struct d40_chan *d40c)
> struct d40_phy_res *phy = d40c->phy_chan;
> bool is_src;
>
> - /* Terminate all queued and active transfers */
> - d40_term_all(d40c);
> -
> if (phy == NULL) {
> chan_err(d40c, "phy == null\n");
> return -EINVAL;
> @@ -2188,11 +2203,18 @@ static int d40_free_dma(struct d40_chan *d40c)
> return -EINVAL;
> }
>
> - pm_runtime_get_sync(d40c->base->dev);
> - res = d40_channel_execute_command(d40c, D40_DMA_STOP);
> - if (res) {
> - chan_err(d40c, "stop failed\n");
> - goto mark_last_busy;
> + /* Release descriptor state; this does not access DMA40 registers. */
> + d40_term_all(d40c);
> +
> + res = pm_runtime_resume_and_get(d40c->base->dev);
> + if (res >= 0) {
> + res = d40_channel_execute_command(d40c, D40_DMA_STOP);
> + if (res)
> + chan_err(d40c, "stop failed\n");
> +
> + pm_runtime_put_autosuspend(d40c->base->dev);
> + if (res)
> + return res;
> }
>
> d40_alloc_mask_free(phy, is_src, chan_is_logical(d40c) ? event : 0);
> @@ -2208,8 +2230,6 @@ static int d40_free_dma(struct d40_chan *d40c)
> d40c->busy = false;
> d40c->phy_chan = NULL;
> d40c->configured = false;
> - mark_last_busy:
> - pm_runtime_put_autosuspend(d40c->base->dev);
> return res;
> }
>
> @@ -2584,10 +2604,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");
> @@ -2595,8 +2619,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)) {
> @@ -2628,6 +2650,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;
> }
> @@ -2788,19 +2811,26 @@ 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) {
> + ret = d40_channel_execute_command(d40c, D40_DMA_STOP);
> + if (ret)
> + chan_err(d40c, "Failed to stop channel\n");
> +
> + pm_runtime_put_autosuspend(d40c->base->dev);
> + }
>
> + /*
> + * Always release software state, even when the controller cannot
> + * resume. d40_term_all() does not access DMA40 registers.
> + */
> d40_term_all(d40c);
> - 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 [flat|nested] 45+ messages in thread
* Re: [PATCH v6 08/23] dmaengine: ste_dma40: Return IRQ_NONE when no interrupt is pending
2026-09-24 8:35 ` [PATCH v6 08/23] dmaengine: ste_dma40: Return IRQ_NONE when no interrupt is pending Linus Walleij
@ 2026-09-24 14:55 ` Frank Li
0 siblings, 0 replies; 45+ messages in thread
From: Frank Li @ 2026-09-24 14:55 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel
On Thu, Sep 24, 2026 at 10:35:20AM +0200, Linus Walleij wrote:
> d40_handle_interrupt() returns IRQ_HANDLED even when no terminal-count or
> error status bit is pending.
>
> IRQ_NONE is not only used to identify a device on a shared interrupt.
> The generic IRQ core also counts it as unhandled for spurious IRQ
> detection. DMA40 does not request this IRQ with IRQF_SHARED, so interrupt
> sharing is not the purpose of this change.
>
> If the line remains asserted without matching DMA40 status, returning
> IRQ_HANDLED hides the stuck interrupt and can leave the CPU servicing it
> indefinitely. Track whether any DMA40 status is pending and return IRQ_NONE
> when none is present, allowing the generic IRQ core to diagnose and
> eventually disable the faulty line.
>
> DMA40 channels can be owned by other SoC cores. Their status means that
> the interrupt is real, 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>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> 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 712719f0c4cf..271f653a15c4 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -1819,6 +1819,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;
> @@ -1852,6 +1853,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
> @@ -1884,7 +1891,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] 45+ messages in thread
* Re: [PATCH v6 09/23] dmaengine: ste_dma40: Init hardware before registration
2026-09-24 8:35 ` [PATCH v6 09/23] dmaengine: ste_dma40: Init hardware before registration Linus Walleij
@ 2026-09-24 14:58 ` Frank Li
0 siblings, 0 replies; 45+ messages in thread
From: Frank Li @ 2026-09-24 14:58 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel, sashiko-bot
On Thu, Sep 24, 2026 at 10:35:21AM +0200, Linus Walleij wrote:
> 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 271f653a15c4..85789ee7c0c9 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -3838,19 +3838,21 @@ static int __init d40_probe(struct platform_device *pdev)
> }
> pm_runtime_use_autosuspend(base->dev);
>
> - 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;
why not request_irq() after d40_hw_init(), suppose dw40_hw_init() will
disable and clean all irqs.
Frank
>
> ret = of_dma_controller_register(np, d40_xlate, NULL);
> if (ret) {
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v6 10/23] dmaengine: ste_dma40: Fix probe IRQ leak
2026-09-24 8:35 ` [PATCH v6 10/23] dmaengine: ste_dma40: Fix probe IRQ leak Linus Walleij
@ 2026-09-24 15:02 ` Frank Li
0 siblings, 0 replies; 45+ messages in thread
From: Frank Li @ 2026-09-24 15:02 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel, sashiko-bot
On Thu, Sep 24, 2026 at 10:35:22AM +0200, Linus Walleij wrote:
> 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 85789ee7c0c9..36f4cbd4da36 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -3718,6 +3718,7 @@ static int __init d40_probe(struct platform_device *pdev)
> struct resource *res;
> struct resource res_lcpa;
> int num_reserved_chans;
> + bool irq_requested = false;
> u32 val;
> int ret;
>
> @@ -3844,6 +3845,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);
>
> @@ -3880,6 +3882,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);
suppose irq request should be last steps. use devm_request_irq(), anyways
request_irqs should be last step to avoid suspicious irq.
Frank
>
> report_failure:
> d40_err(dev, "probe failed\n");
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v6 11/23] dmaengine: ste_dma40: Fix DMA registration unwind
2026-09-24 8:35 ` [PATCH v6 11/23] dmaengine: ste_dma40: Fix DMA registration unwind Linus Walleij
2026-09-24 9:10 ` sashiko-bot
@ 2026-09-24 15:11 ` Frank Li
2026-09-27 8:41 ` Linus Walleij
1 sibling, 1 reply; 45+ messages in thread
From: Frank Li @ 2026-09-24 15:11 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel, sashiko-bot
On Thu, Sep 24, 2026 at 10:35:23AM +0200, Linus Walleij wrote:
> 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 36f4cbd4da36..87b5f1d97ac2 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -3042,6 +3042,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)) {
> @@ -3088,6 +3100,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) {
> @@ -3103,6 +3120,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) {
> @@ -3120,6 +3142,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) {
> @@ -3717,6 +3745,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 irq_requested = false;
> u32 val;
> @@ -3852,20 +3881,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 use devm_of_dma_controller_register(), does it avoid use devres group?
Frank
> 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 [flat|nested] 45+ messages in thread
* Re: [PATCH v6 12/23] dmaengine: ste_dma40: Fix LCLA allocation order
2026-09-24 8:35 ` [PATCH v6 12/23] dmaengine: ste_dma40: Fix LCLA allocation order Linus Walleij
@ 2026-09-24 15:16 ` Frank Li
0 siblings, 0 replies; 45+ messages in thread
From: Frank Li @ 2026-09-24 15:16 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel, sashiko-bot
On Thu, Sep 24, 2026 at 10:35:24AM +0200, Linus Walleij wrote:
> 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>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> 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 87b5f1d97ac2..8998466faa76 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -426,8 +426,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.
> */
> @@ -435,7 +435,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;
> };
> @@ -3606,6 +3606,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;
> @@ -3621,20 +3622,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;
> }
>
> @@ -3644,7 +3645,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];
> @@ -3654,10 +3655,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) {
> @@ -3669,8 +3669,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;
> @@ -3916,7 +3915,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 [flat|nested] 45+ messages in thread
* Re: [PATCH v6 13/23] dmaengine: ste_dma40: Fix probe LCLA free
2026-09-24 8:35 ` [PATCH v6 13/23] dmaengine: ste_dma40: Fix probe LCLA free Linus Walleij
@ 2026-09-24 15:34 ` Frank Li
0 siblings, 0 replies; 45+ messages in thread
From: Frank Li @ 2026-09-24 15:34 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel, sashiko-bot
On Thu, Sep 24, 2026 at 10:35:25AM +0200, Linus Walleij wrote:
> 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>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> 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 8998466faa76..b25e9ed5fcb9 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -3913,7 +3913,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 [flat|nested] 45+ messages in thread
* Re: [PATCH v6 14/23] dmaengine: ste_dma40: Put the LCPA SRAM node
2026-09-24 8:35 ` [PATCH v6 14/23] dmaengine: ste_dma40: Put the LCPA SRAM node Linus Walleij
@ 2026-09-24 15:43 ` Frank Li
0 siblings, 0 replies; 45+ messages in thread
From: Frank Li @ 2026-09-24 15:43 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel
On Thu, Sep 24, 2026 at 10:35:26AM +0200, Linus Walleij wrote:
> 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>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> 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 b25e9ed5fcb9..6599104ecdde 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -3775,6 +3775,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 [flat|nested] 45+ messages in thread
* Re: [PATCH v6 15/23] dmaengine: ste_dma40: Fix memcpy channel parsing
2026-09-24 8:35 ` [PATCH v6 15/23] dmaengine: ste_dma40: Fix memcpy channel parsing Linus Walleij
@ 2026-09-24 15:49 ` Frank Li
0 siblings, 0 replies; 45+ messages in thread
From: Frank Li @ 2026-09-24 15:49 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel, sashiko-bot
On Thu, Sep 24, 2026 at 10:35:27AM +0200, Linus Walleij wrote:
> 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>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> 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 6599104ecdde..24994e9bfbbb 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;
> };
>
> @@ -89,25 +93,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,
> @@ -2157,7 +2142,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);
> @@ -3437,12 +3423,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,
> @@ -3691,6 +3672,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)
> @@ -3704,18 +3686,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 [flat|nested] 45+ messages in thread
* Re: [PATCH v6 16/23] dmaengine: ste_dma40: Validate disabled channel indexes
2026-09-24 8:35 ` [PATCH v6 16/23] dmaengine: ste_dma40: Validate disabled channel indexes Linus Walleij
@ 2026-09-24 15:53 ` Frank Li
0 siblings, 0 replies; 45+ messages in thread
From: Frank Li @ 2026-09-24 15:53 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel
On Thu, Sep 24, 2026 at 10:35:28AM +0200, Linus Walleij wrote:
> 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>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> 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 24994e9bfbbb..ed233676e718 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -3422,6 +3422,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 [flat|nested] 45+ messages in thread
* Re: [PATCH v6 17/23] dmaengine: ste_dma40: Validate DMA specifier length
2026-09-24 8:35 ` [PATCH v6 17/23] dmaengine: ste_dma40: Validate DMA specifier length Linus Walleij
@ 2026-09-24 15:54 ` Frank Li
0 siblings, 0 replies; 45+ messages in thread
From: Frank Li @ 2026-09-24 15:54 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel
On Thu, Sep 24, 2026 at 10:35:29AM +0200, Linus Walleij wrote:
> 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>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> 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 ed233676e718..660ac63efb40 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -2545,6 +2545,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 [flat|nested] 45+ messages in thread
* Re: [PATCH v6 18/23] dmaengine: ste_dma40: Reject direction changes after allocation
2026-09-24 8:35 ` [PATCH v6 18/23] dmaengine: ste_dma40: Reject direction changes after allocation Linus Walleij
@ 2026-09-24 15:57 ` Frank Li
0 siblings, 0 replies; 45+ messages in thread
From: Frank Li @ 2026-09-24 15:57 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel, sashiko-bot
On Thu, Sep 24, 2026 at 10:35:30AM +0200, Linus Walleij wrote:
> 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>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> 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 660ac63efb40..4f5839691b85 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -2413,7 +2413,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);
>
> @@ -2889,6 +2895,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;
> @@ -2897,13 +2910,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;
> @@ -2913,13 +2919,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 [flat|nested] 45+ messages in thread
* Re: [PATCH v6 21/23] dmaengine: ste_dma40: Search all blocks for fixed logical channels
2026-09-24 8:35 ` [PATCH v6 21/23] dmaengine: ste_dma40: Search all blocks for fixed logical channels Linus Walleij
@ 2026-09-24 16:08 ` Frank Li
0 siblings, 0 replies; 45+ messages in thread
From: Frank Li @ 2026-09-24 16:08 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel, sashiko-bot
On Thu, Sep 24, 2026 at 10:35:33AM +0200, Linus Walleij wrote:
> 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>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> 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 d3d79e394d02..def2416fb1d0 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 [flat|nested] 45+ messages in thread
* Re: [PATCH v6 22/23] dmaengine: ste_dma40: Validate fixed physical channel indexes
2026-09-24 8:35 ` [PATCH v6 22/23] dmaengine: ste_dma40: Validate fixed physical channel indexes Linus Walleij
@ 2026-09-24 16:09 ` Frank Li
0 siblings, 0 replies; 45+ messages in thread
From: Frank Li @ 2026-09-24 16:09 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel, sashiko-bot
On Thu, Sep 24, 2026 at 10:35:34AM +0200, Linus Walleij wrote:
> 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>
> ---
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 def2416fb1d0..d65bf6cfea53 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -1918,6 +1918,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 [flat|nested] 45+ messages in thread
* Re: [PATCH v6 23/23] dmaengine: ste_dma40: Validate memcpy configuration
2026-09-24 8:35 ` [PATCH v6 23/23] dmaengine: ste_dma40: Validate memcpy configuration Linus Walleij
@ 2026-09-24 16:11 ` Frank Li
0 siblings, 0 replies; 45+ messages in thread
From: Frank Li @ 2026-09-24 16:11 UTC (permalink / raw)
To: Linus Walleij; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel, sashiko-bot
On Thu, Sep 24, 2026 at 10:35:35AM +0200, Linus Walleij wrote:
> 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>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> 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 d65bf6cfea53..c10012026ea0 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -1897,7 +1897,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;
> }
> @@ -2171,12 +2172,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);
>
> @@ -2184,6 +2190,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 [flat|nested] 45+ messages in thread
* Re: [PATCH v6 11/23] dmaengine: ste_dma40: Fix DMA registration unwind
2026-09-24 15:11 ` Frank Li
@ 2026-09-27 8:41 ` Linus Walleij
0 siblings, 0 replies; 45+ messages in thread
From: Linus Walleij @ 2026-09-27 8:41 UTC (permalink / raw)
To: Frank Li; +Cc: Vinod Koul, Frank Li, dmaengine, phone-devel, sashiko-bot
On Thu, Sep 24, 2026 at 5:11 PM Frank Li <Frank.li@oss.nxp.com> wrote:
> > 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 use devm_of_dma_controller_register(), does it avoid use devres group?
No not really.
All of these changes based on Sashiko's comments are starting to feel
a bit overzealous, I think it is commenting on issues that actually exist
in a lot of DMA drivers, are theoretical problems, but never occur in practice.
I don't know if I should stop dropping patches from the series
because they are too fringe...
I managed to clean this up a little bit, I'll repost.
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 45+ messages in thread
end of thread, other threads:[~2026-09-27 8:41 UTC | newest]
Thread overview: 45+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-24 8:35 [PATCH v6 00/23] dmaengine: ste_dma40: Fix numerous accumulated bugs Linus Walleij
2026-09-24 8:35 ` [PATCH v6 01/23] dmaengine: ste_dma40: Fix physical cyclic capability Linus Walleij
2026-09-24 8:35 ` [PATCH v6 02/23] dmaengine: ste_dma40: Fix cyclic transfer residue Linus Walleij
2026-09-24 14:37 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 03/23] dmaengine: ste_dma40: Recover coalesced cyclic callbacks Linus Walleij
2026-09-24 14:48 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 04/23] dmaengine: ste_dma40: Fix failed start cleanup Linus Walleij
2026-09-24 8:35 ` [PATCH v6 05/23] dmaengine: ste_dma40: Fix probe runtime PM disable Linus Walleij
2026-09-24 14:50 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 06/23] dmaengine: ste_dma40: Check runtime PM in IRQ Linus Walleij
2026-09-24 8:35 ` [PATCH v6 07/23] dmaengine: ste_dma40: Handle runtime PM resume errors Linus Walleij
2026-09-24 14:54 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 08/23] dmaengine: ste_dma40: Return IRQ_NONE when no interrupt is pending Linus Walleij
2026-09-24 14:55 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 09/23] dmaengine: ste_dma40: Init hardware before registration Linus Walleij
2026-09-24 14:58 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 10/23] dmaengine: ste_dma40: Fix probe IRQ leak Linus Walleij
2026-09-24 15:02 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 11/23] dmaengine: ste_dma40: Fix DMA registration unwind Linus Walleij
2026-09-24 9:10 ` sashiko-bot
2026-09-24 15:11 ` Frank Li
2026-09-27 8:41 ` Linus Walleij
2026-09-24 8:35 ` [PATCH v6 12/23] dmaengine: ste_dma40: Fix LCLA allocation order Linus Walleij
2026-09-24 15:16 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 13/23] dmaengine: ste_dma40: Fix probe LCLA free Linus Walleij
2026-09-24 15:34 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 14/23] dmaengine: ste_dma40: Put the LCPA SRAM node Linus Walleij
2026-09-24 15:43 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 15/23] dmaengine: ste_dma40: Fix memcpy channel parsing Linus Walleij
2026-09-24 15:49 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 16/23] dmaengine: ste_dma40: Validate disabled channel indexes Linus Walleij
2026-09-24 15:53 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 17/23] dmaengine: ste_dma40: Validate DMA specifier length Linus Walleij
2026-09-24 15:54 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 18/23] dmaengine: ste_dma40: Reject direction changes after allocation Linus Walleij
2026-09-24 15:57 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 19/23] dmaengine: ste_dma40: Fix logical channel bounds check Linus Walleij
2026-09-24 8:35 ` [PATCH v6 20/23] dmaengine: ste_dma40: Fix event group bounds Linus Walleij
2026-09-24 9:28 ` sashiko-bot
2026-09-24 8:35 ` [PATCH v6 21/23] dmaengine: ste_dma40: Search all blocks for fixed logical channels Linus Walleij
2026-09-24 16:08 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 22/23] dmaengine: ste_dma40: Validate fixed physical channel indexes Linus Walleij
2026-09-24 16:09 ` Frank Li
2026-09-24 8:35 ` [PATCH v6 23/23] dmaengine: ste_dma40: Validate memcpy configuration Linus Walleij
2026-09-24 16:11 ` Frank Li
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).