* [PATCH v3 1/4] dmaengine: Fix possible use after free
2026-04-08 12:42 [PATCH v3 0/4] dmaengine: dma-axi-dmac: Some memory related fixes Nuno Sá via B4 Relay
@ 2026-04-08 12:42 ` Nuno Sá via B4 Relay
2026-04-10 3:00 ` Frank Li
2026-04-08 12:42 ` [PATCH v3 2/4] dmaengine: dma-axi-dmac: Properly free struct axi_dmac_desc Nuno Sá via B4 Relay
` (2 subsequent siblings)
3 siblings, 1 reply; 9+ messages in thread
From: Nuno Sá via B4 Relay @ 2026-04-08 12:42 UTC (permalink / raw)
To: dmaengine, linux-kernel; +Cc: Lars-Peter Clausen, Vinod Koul, Frank Li
From: Nuno Sá <nuno.sa@analog.com>
In dma_release_channel(), we first called dma_chan_put() and then
checked chan->device->privatecnt for possibly clearing DMA_PRIVATE.
However, dma_chan_put() will call dma_device_put() which could,
potentially (if the DMA provider is already gone for example),
release the last reference of the device and hence freeing
the it.
Fix it, by doing the check before calling dma_chan_put().
Fixes: 0f571515c332 ("dmaengine: Add privatecnt to revert DMA_PRIVATE property")
Signed-off-by: Nuno Sá <nuno.sa@analog.com>
---
drivers/dma/dmaengine.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c
index 405bd2fbb4a3..9049171df857 100644
--- a/drivers/dma/dmaengine.c
+++ b/drivers/dma/dmaengine.c
@@ -905,11 +905,12 @@ void dma_release_channel(struct dma_chan *chan)
mutex_lock(&dma_list_mutex);
WARN_ONCE(chan->client_count != 1,
"chan reference count %d != 1\n", chan->client_count);
- dma_chan_put(chan);
/* drop PRIVATE cap enabled by __dma_request_channel() */
if (--chan->device->privatecnt == 0)
dma_cap_clear(DMA_PRIVATE, chan->device->cap_mask);
+ dma_chan_put(chan);
+
if (chan->slave) {
sysfs_remove_link(&chan->dev->device.kobj, DMA_SLAVE_NAME);
sysfs_remove_link(&chan->slave->kobj, chan->name);
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v3 2/4] dmaengine: dma-axi-dmac: Properly free struct axi_dmac_desc
2026-04-08 12:42 [PATCH v3 0/4] dmaengine: dma-axi-dmac: Some memory related fixes Nuno Sá via B4 Relay
2026-04-08 12:42 ` [PATCH v3 1/4] dmaengine: Fix possible use after free Nuno Sá via B4 Relay
@ 2026-04-08 12:42 ` Nuno Sá via B4 Relay
2026-04-10 3:02 ` Frank Li
2026-04-08 12:42 ` [PATCH v3 3/4] dmaengine: dma-axi-dmac: Drop struct clk from main struct Nuno Sá via B4 Relay
2026-04-08 12:42 ` [PATCH v3 4/4] dmaengine: dma-axi-dmac: Fig BUG() on vunmap() Nuno Sá via B4 Relay
3 siblings, 1 reply; 9+ messages in thread
From: Nuno Sá via B4 Relay @ 2026-04-08 12:42 UTC (permalink / raw)
To: dmaengine, linux-kernel; +Cc: Lars-Peter Clausen, Vinod Koul, Frank Li
From: Nuno Sá <nuno.sa@analog.com>
In axi_dmac_prep_peripheral_dma_vec() if we fail after calling
axi_dmac_alloc_desc(), we need to use axi_dmac_free_desc() to fully free
the descriptor.
Fixes: 74609e568670 ("dmaengine: dma-axi-dmac: Implement device_prep_peripheral_dma_vec")
Signed-off-by: Nuno Sá <nuno.sa@analog.com>
---
drivers/dma/dma-axi-dmac.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c
index 45c2c8e4bc45..127c3cf80a0e 100644
--- a/drivers/dma/dma-axi-dmac.c
+++ b/drivers/dma/dma-axi-dmac.c
@@ -769,7 +769,7 @@ axi_dmac_prep_peripheral_dma_vec(struct dma_chan *c, const struct dma_vec *vecs,
for (i = 0; i < nb; i++) {
if (!axi_dmac_check_addr(chan, vecs[i].addr) ||
!axi_dmac_check_len(chan, vecs[i].len)) {
- kfree(desc);
+ axi_dmac_free_desc(desc);
return NULL;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v3 3/4] dmaengine: dma-axi-dmac: Drop struct clk from main struct
2026-04-08 12:42 [PATCH v3 0/4] dmaengine: dma-axi-dmac: Some memory related fixes Nuno Sá via B4 Relay
2026-04-08 12:42 ` [PATCH v3 1/4] dmaengine: Fix possible use after free Nuno Sá via B4 Relay
2026-04-08 12:42 ` [PATCH v3 2/4] dmaengine: dma-axi-dmac: Properly free struct axi_dmac_desc Nuno Sá via B4 Relay
@ 2026-04-08 12:42 ` Nuno Sá via B4 Relay
2026-04-10 3:03 ` Frank Li
2026-04-08 12:42 ` [PATCH v3 4/4] dmaengine: dma-axi-dmac: Fig BUG() on vunmap() Nuno Sá via B4 Relay
3 siblings, 1 reply; 9+ messages in thread
From: Nuno Sá via B4 Relay @ 2026-04-08 12:42 UTC (permalink / raw)
To: dmaengine, linux-kernel; +Cc: Lars-Peter Clausen, Vinod Koul, Frank Li
From: Nuno Sá <nuno.sa@analog.com>
There's no reason to keep struct clk in struct axi_dmac. Hence, use a
local clk variable in .probe() and be done with it.
Signed-off-by: Nuno Sá <nuno.sa@analog.com>
---
drivers/dma/dma-axi-dmac.c | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c
index 127c3cf80a0e..41898d594be7 100644
--- a/drivers/dma/dma-axi-dmac.c
+++ b/drivers/dma/dma-axi-dmac.c
@@ -170,8 +170,6 @@ struct axi_dmac {
void __iomem *base;
int irq;
- struct clk *clk;
-
struct dma_device dma_dev;
struct axi_dmac_chan chan;
};
@@ -1198,6 +1196,7 @@ static int axi_dmac_probe(struct platform_device *pdev)
{
struct dma_device *dma_dev;
struct axi_dmac *dmac;
+ struct clk *clk;
struct regmap *regmap;
unsigned int version;
u32 irq_mask = 0;
@@ -1217,9 +1216,9 @@ static int axi_dmac_probe(struct platform_device *pdev)
if (IS_ERR(dmac->base))
return PTR_ERR(dmac->base);
- dmac->clk = devm_clk_get_enabled(&pdev->dev, NULL);
- if (IS_ERR(dmac->clk))
- return PTR_ERR(dmac->clk);
+ clk = devm_clk_get_enabled(&pdev->dev, NULL);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
version = axi_dmac_read(dmac, ADI_AXI_REG_VERSION);
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v3 4/4] dmaengine: dma-axi-dmac: Fig BUG() on vunmap()
2026-04-08 12:42 [PATCH v3 0/4] dmaengine: dma-axi-dmac: Some memory related fixes Nuno Sá via B4 Relay
` (2 preceding siblings ...)
2026-04-08 12:42 ` [PATCH v3 3/4] dmaengine: dma-axi-dmac: Drop struct clk from main struct Nuno Sá via B4 Relay
@ 2026-04-08 12:42 ` Nuno Sá via B4 Relay
2026-04-10 3:10 ` Frank Li
3 siblings, 1 reply; 9+ messages in thread
From: Nuno Sá via B4 Relay @ 2026-04-08 12:42 UTC (permalink / raw)
To: dmaengine, linux-kernel; +Cc: Lars-Peter Clausen, Vinod Koul, Frank Li
From: Nuno Sá <nuno.sa@analog.com>
For architectures like Microblaze or arm64 (where this IP is used),
DMA_DIRECT_REMAP is set which means that dma_alloc_coherent() might
remap (and hence vmalloc()) some memory. This became visible in a design
where dma_direct_use_pool() is not possible.
With the above, when calling dma_free_coherent(), vunmap() would be
called from softirq context and thus leading to a BUG().
To fix it, use a dma pool that is allocated in
.device_alloc_chan_resources() and allocate blocks from it. The key
point is that now dma_pool_free() is used in axi_dmac_free_desc() to
free the blocks and that just frees the blocks from the pool in the
sense they can be used again. In other words, no actual call to
dma_free_coherent() happens. That only happens when destroying the pool
in axi_dmac_free_chan_resources() which does not happen in any interrupt
context.
Signed-off-by: Nuno Sá <nuno.sa@analog.com>
---
drivers/dma/dma-axi-dmac.c | 66 ++++++++++++++++++++++++++++------------------
1 file changed, 40 insertions(+), 26 deletions(-)
diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c
index 41898d594be7..d47ff27e1408 100644
--- a/drivers/dma/dma-axi-dmac.c
+++ b/drivers/dma/dma-axi-dmac.c
@@ -13,6 +13,7 @@
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
+#include <linux/dmapool.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
@@ -147,6 +148,7 @@ struct axi_dmac_chan {
struct virt_dma_chan vchan;
struct axi_dmac_desc *next_desc;
+ void *pool;
struct list_head active_descs;
enum dma_transfer_direction direction;
@@ -648,11 +650,17 @@ static void axi_dmac_issue_pending(struct dma_chan *c)
spin_unlock_irqrestore(&chan->vchan.lock, flags);
}
+static void axi_dmac_free_desc(struct axi_dmac_desc *desc)
+{
+ for (unsigned int i = 0; i < desc->num_sgs; i++)
+ dma_pool_free(desc->chan->pool, desc->sg[i].hw, desc->sg[i].hw_phys);
+
+ kfree(desc);
+}
+
static struct axi_dmac_desc *
axi_dmac_alloc_desc(struct axi_dmac_chan *chan, unsigned int num_sgs)
{
- struct axi_dmac *dmac = chan_to_axi_dmac(chan);
- struct device *dev = dmac->dma_dev.dev;
struct axi_dmac_hw_desc *hws;
struct axi_dmac_desc *desc;
dma_addr_t hw_phys;
@@ -664,22 +672,22 @@ axi_dmac_alloc_desc(struct axi_dmac_chan *chan, unsigned int num_sgs)
desc->num_sgs = num_sgs;
desc->chan = chan;
- hws = dma_alloc_coherent(dev, PAGE_ALIGN(num_sgs * sizeof(*hws)),
- &hw_phys, GFP_ATOMIC);
- if (!hws) {
- kfree(desc);
- return NULL;
- }
-
for (i = 0; i < num_sgs; i++) {
- desc->sg[i].hw = &hws[i];
- desc->sg[i].hw_phys = hw_phys + i * sizeof(*hws);
+ hws = dma_pool_zalloc(chan->pool, GFP_NOWAIT, &hw_phys);
+ if (!hws) {
+ desc->num_sgs = i;
+ axi_dmac_free_desc(desc);
+ return NULL;
+ }
- hws[i].id = AXI_DMAC_SG_UNUSED;
- hws[i].flags = 0;
+ desc->sg[i].hw = hws;
+ desc->sg[i].hw_phys = hw_phys;
+
+ hws->id = AXI_DMAC_SG_UNUSED;
/* Link hardware descriptors */
- hws[i].next_sg_addr = hw_phys + (i + 1) * sizeof(*hws);
+ if (i)
+ desc->sg[i - 1].hw->next_sg_addr = hw_phys;
}
/* The last hardware descriptor will trigger an interrupt */
@@ -688,18 +696,6 @@ axi_dmac_alloc_desc(struct axi_dmac_chan *chan, unsigned int num_sgs)
return desc;
}
-static void axi_dmac_free_desc(struct axi_dmac_desc *desc)
-{
- struct axi_dmac *dmac = chan_to_axi_dmac(desc->chan);
- struct device *dev = dmac->dma_dev.dev;
- struct axi_dmac_hw_desc *hw = desc->sg[0].hw;
- dma_addr_t hw_phys = desc->sg[0].hw_phys;
-
- dma_free_coherent(dev, PAGE_ALIGN(desc->num_sgs * sizeof(*hw)),
- hw, hw_phys);
- kfree(desc);
-}
-
static struct axi_dmac_sg *axi_dmac_fill_linear_sg(struct axi_dmac_chan *chan,
enum dma_transfer_direction direction, dma_addr_t addr,
unsigned int num_periods, unsigned int period_len,
@@ -933,9 +929,26 @@ static struct dma_async_tx_descriptor *axi_dmac_prep_interleaved(
return vchan_tx_prep(&chan->vchan, &desc->vdesc, flags);
}
+static int axi_dmac_alloc_chan_resources(struct dma_chan *c)
+{
+ struct axi_dmac_chan *chan = to_axi_dmac_chan(c);
+ struct device *dev = c->device->dev;
+
+ chan->pool = dma_pool_create(dev_name(dev), dev,
+ sizeof(struct axi_dmac_hw_desc),
+ __alignof__(struct axi_dmac_hw_desc), 0);
+ if (!chan->pool)
+ return -ENOMEM;
+
+ return 0;
+}
+
static void axi_dmac_free_chan_resources(struct dma_chan *c)
{
+ struct axi_dmac_chan *chan = to_axi_dmac_chan(c);
+
vchan_free_chan_resources(to_virt_chan(c));
+ dma_pool_destroy(chan->pool);
}
static void axi_dmac_desc_free(struct virt_dma_desc *vdesc)
@@ -1238,6 +1251,7 @@ static int axi_dmac_probe(struct platform_device *pdev)
dma_cap_set(DMA_SLAVE, dma_dev->cap_mask);
dma_cap_set(DMA_CYCLIC, dma_dev->cap_mask);
dma_cap_set(DMA_INTERLEAVE, dma_dev->cap_mask);
+ dma_dev->device_alloc_chan_resources = axi_dmac_alloc_chan_resources;
dma_dev->device_free_chan_resources = axi_dmac_free_chan_resources;
dma_dev->device_tx_status = dma_cookie_status;
dma_dev->device_issue_pending = axi_dmac_issue_pending;
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread