* [RFC PATCH 0/1] dmaengine: introduce dmaengine_bh_wq and bh helpers
@ 2026-01-08 8:03 Allen Pais
2026-01-08 8:03 ` [RFC PATCH 1/1] " Allen Pais
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
0 siblings, 2 replies; 63+ messages in thread
From: Allen Pais @ 2026-01-08 8:03 UTC (permalink / raw)
To: vkoul; +Cc: arnd, kees, dmaengine, linux-kernel, Allen Pais
Hi Vinod,
This series contains a single patch that introduces a dedicated dmaengine
bottom-half workqueue along with per-channel BH helpers. The change grew
out of the earlier discussion about moving DMA tasklet usage over to a
BH workqueue and I took some time to get the semantics right
before sending this upstream:
Earlier discussion:
https://lore.kernel.org/all/2e9257af-c123-406b-a189-eaebeecc1d71@app.fastmail.com/
I’m intentionally sending only the foundational helper patch for review
to avoid spamming the list. The full set of driver conversions is already
available here: https://github.com/allenpais/dmaengine-bh-work
Once the base helper patch is reviewed, I will follow up with the
remaining driver conversions.
This series is based on v6.19-rc4 (f10c325a345fef0a688a2bcdfab1540d1c924148).
Thanks,
Allen
Allen Pais (1):
dmaengine: introduce dmaengine_bh_wq and bh helpers
drivers/dma/amd/qdma/qdma.c | 1 +
drivers/dma/bcm2835-dma.c | 2 +-
drivers/dma/dmaengine.c | 109 +++++++++++++++++-
.../dma/dw-axi-dmac/dw-axi-dmac-platform.c | 2 +-
drivers/dma/dw-edma/dw-edma-core.c | 2 +-
drivers/dma/hisi_dma.c | 2 +-
drivers/dma/img-mdc-dma.c | 2 +-
drivers/dma/imx-sdma.c | 2 +-
drivers/dma/k3dma.c | 2 +-
drivers/dma/loongson1-apb-dma.c | 2 +-
drivers/dma/mediatek/mtk-cqdma.c | 2 +-
drivers/dma/mediatek/mtk-hsdma.c | 2 +-
drivers/dma/mediatek/mtk-uart-apdma.c | 4 +-
drivers/dma/owl-dma.c | 2 +-
drivers/dma/pxa_dma.c | 2 +-
drivers/dma/qcom/bam_dma.c | 4 +-
drivers/dma/qcom/qcom_adm.c | 2 +-
drivers/dma/sa11x0-dma.c | 2 +-
drivers/dma/sprd-dma.c | 2 +-
drivers/dma/sun6i-dma.c | 2 +-
drivers/dma/tegra186-gpc-dma.c | 2 +-
drivers/dma/tegra210-adma.c | 2 +-
drivers/dma/ti/k3-udma.c | 8 +-
drivers/dma/ti/omap-dma.c | 2 +-
drivers/dma/virt-dma.c | 6 +-
drivers/dma/virt-dma.h | 8 +-
include/linux/dmaengine.h | 50 ++++++++
27 files changed, 189 insertions(+), 39 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 63+ messages in thread
* [RFC PATCH 1/1] dmaengine: introduce dmaengine_bh_wq and bh helpers
2026-01-08 8:03 [RFC PATCH 0/1] dmaengine: introduce dmaengine_bh_wq and bh helpers Allen Pais
@ 2026-01-08 8:03 ` Allen Pais
2026-01-08 10:26 ` Arnd Bergmann
` (4 more replies)
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
1 sibling, 5 replies; 63+ messages in thread
From: Allen Pais @ 2026-01-08 8:03 UTC (permalink / raw)
To: vkoul; +Cc: arnd, kees, dmaengine, linux-kernel, Allen Pais
Create a dedicated dmaengine bottom-half workqueue (WQ_BH | WQ_PERCPU)
and provide helper APIs for queue/flush/cancel of BH work items. Add
per-channel BH helpers in dma_chan so drivers can schedule a BH callback
without maintaining their own tasklets.
Convert virt-dma to use the new per-channel BH helpers and remove the
per-channel tasklet. Update existing drivers that only need tasklet
teardown to use dma_chan_kill_bh().
This provides a common BH execution path for dmaengine and establishes
the base for converting remaining DMA tasklets to workqueue-based BHs.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/amd/qdma/qdma.c | 1 +
drivers/dma/bcm2835-dma.c | 2 +-
drivers/dma/dmaengine.c | 109 +++++++++++++++++-
.../dma/dw-axi-dmac/dw-axi-dmac-platform.c | 2 +-
drivers/dma/dw-edma/dw-edma-core.c | 2 +-
drivers/dma/hisi_dma.c | 2 +-
drivers/dma/img-mdc-dma.c | 2 +-
drivers/dma/imx-sdma.c | 2 +-
drivers/dma/k3dma.c | 2 +-
drivers/dma/loongson1-apb-dma.c | 2 +-
drivers/dma/mediatek/mtk-cqdma.c | 2 +-
drivers/dma/mediatek/mtk-hsdma.c | 2 +-
drivers/dma/mediatek/mtk-uart-apdma.c | 4 +-
drivers/dma/owl-dma.c | 2 +-
drivers/dma/pxa_dma.c | 2 +-
drivers/dma/qcom/bam_dma.c | 4 +-
drivers/dma/qcom/qcom_adm.c | 2 +-
drivers/dma/sa11x0-dma.c | 2 +-
drivers/dma/sprd-dma.c | 2 +-
drivers/dma/sun6i-dma.c | 2 +-
drivers/dma/tegra186-gpc-dma.c | 2 +-
drivers/dma/tegra210-adma.c | 2 +-
drivers/dma/ti/k3-udma.c | 8 +-
drivers/dma/ti/omap-dma.c | 2 +-
drivers/dma/virt-dma.c | 6 +-
drivers/dma/virt-dma.h | 8 +-
include/linux/dmaengine.h | 50 ++++++++
27 files changed, 189 insertions(+), 39 deletions(-)
diff --git a/drivers/dma/amd/qdma/qdma.c b/drivers/dma/amd/qdma/qdma.c
index 8fb2d5e1df20..b57f8ebf2446 100644
--- a/drivers/dma/amd/qdma/qdma.c
+++ b/drivers/dma/amd/qdma/qdma.c
@@ -13,6 +13,7 @@
#include <linux/platform_device.h>
#include <linux/platform_data/amd_qdma.h>
#include <linux/regmap.h>
+#include <linux/interrupt.h>
#include "qdma.h"
diff --git a/drivers/dma/bcm2835-dma.c b/drivers/dma/bcm2835-dma.c
index 321748e2983e..08a206cfbb76 100644
--- a/drivers/dma/bcm2835-dma.c
+++ b/drivers/dma/bcm2835-dma.c
@@ -846,7 +846,7 @@ static void bcm2835_dma_free(struct bcm2835_dmadev *od)
list_for_each_entry_safe(c, next, &od->ddev.channels,
vc.chan.device_node) {
list_del(&c->vc.chan.device_node);
- tasklet_kill(&c->vc.task);
+ dma_chan_kill_bh(&c->vc.chan);
}
dma_unmap_page_attrs(od->ddev.dev, od->zero_page, PAGE_SIZE,
diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c
index ca13cd39330b..d3df4e943d37 100644
--- a/drivers/dma/dmaengine.c
+++ b/drivers/dma/dmaengine.c
@@ -54,6 +54,7 @@
#include <linux/of_dma.h>
#include <linux/mempool.h>
#include <linux/numa.h>
+#include <linux/workqueue.h>
#include "dmaengine.h"
@@ -61,6 +62,7 @@ static DEFINE_MUTEX(dma_list_mutex);
static DEFINE_IDA(dma_ida);
static LIST_HEAD(dma_device_list);
static long dmaengine_ref_count;
+static struct workqueue_struct *dmaengine_bh_wq;
/* --- debugfs implementation --- */
#ifdef CONFIG_DEBUG_FS
@@ -1425,6 +1427,92 @@ static void dmaengine_destroy_unmap_pool(void)
}
}
+static void dmaengine_destroy_bh_wq(void)
+{
+ if (!dmaengine_bh_wq)
+ return;
+
+ destroy_workqueue(dmaengine_bh_wq);
+ dmaengine_bh_wq = NULL;
+}
+
+bool dmaengine_queue_bh_work(struct work_struct *work)
+{
+ if (WARN_ON(!dmaengine_bh_wq))
+ return false;
+
+ return queue_work(dmaengine_bh_wq, work);
+}
+EXPORT_SYMBOL_GPL(dmaengine_queue_bh_work);
+
+void dmaengine_flush_bh_work(struct work_struct *work)
+{
+ if (!work)
+ return;
+
+ flush_work(work);
+}
+EXPORT_SYMBOL_GPL(dmaengine_flush_bh_work);
+
+void dmaengine_cancel_bh_work_sync(struct work_struct *work)
+{
+ if (!work)
+ return;
+
+ cancel_work_sync(work);
+}
+EXPORT_SYMBOL_GPL(dmaengine_cancel_bh_work_sync);
+
+static void dma_chan_bh_entry(struct work_struct *work)
+{
+ struct dma_chan *chan = container_of(work, struct dma_chan, bh_work);
+ dma_chan_bh_work_fn fn = READ_ONCE(chan->bh_work_fn);
+
+ if (fn)
+ fn(chan);
+}
+
+void dma_chan_init_bh(struct dma_chan *chan, dma_chan_bh_work_fn fn)
+{
+ if (WARN_ON(!fn))
+ return;
+
+ if (WARN_ON(chan->bh_work_initialized))
+ return;
+
+ chan->bh_work_fn = fn;
+ INIT_WORK(&chan->bh_work, dma_chan_bh_entry);
+ chan->bh_work_initialized = true;
+}
+EXPORT_SYMBOL_GPL(dma_chan_init_bh);
+
+bool dma_chan_schedule_bh(struct dma_chan *chan)
+{
+ if (WARN_ON(!chan->bh_work_initialized))
+ return false;
+
+ return dmaengine_queue_bh_work(&chan->bh_work);
+}
+EXPORT_SYMBOL_GPL(dma_chan_schedule_bh);
+
+void dma_chan_flush_bh(struct dma_chan *chan)
+{
+ if (!chan->bh_work_initialized)
+ return;
+
+ dmaengine_flush_bh_work(&chan->bh_work);
+}
+EXPORT_SYMBOL_GPL(dma_chan_flush_bh);
+
+void dma_chan_kill_bh(struct dma_chan *chan)
+{
+ if (!chan->bh_work_initialized)
+ return;
+
+ dmaengine_cancel_bh_work_sync(&chan->bh_work);
+}
+EXPORT_SYMBOL_GPL(dma_chan_kill_bh);
+
static int __init dmaengine_init_unmap_pool(void)
{
int i;
@@ -1621,15 +1709,28 @@ EXPORT_SYMBOL_GPL(dma_run_dependencies);
static int __init dma_bus_init(void)
{
- int err = dmaengine_init_unmap_pool();
+ int err;
+ dmaengine_bh_wq = alloc_workqueue("dmaengine_bh",
+ WQ_BH | WQ_PERCPU, 0);
+ if (!dmaengine_bh_wq)
+ return -ENOMEM;
+
+ err = dmaengine_init_unmap_pool();
if (err)
- return err;
+ goto err_destroy_wq;
err = class_register(&dma_devclass);
- if (!err)
- dmaengine_debugfs_init();
+ if (err)
+ goto err_destroy_pool;
+ dmaengine_debugfs_init();
+ return 0;
+
+err_destroy_pool:
+ dmaengine_destroy_unmap_pool();
+err_destroy_wq:
+ dmaengine_destroy_bh_wq();
return err;
}
arch_initcall(dma_bus_init);
diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
index b23536645ff7..42018b21d2ab 100644
--- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
+++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
@@ -1649,7 +1649,7 @@ static void dw_remove(struct platform_device *pdev)
list_for_each_entry_safe(chan, _chan, &dw->dma.channels,
vc.chan.device_node) {
list_del(&chan->vc.chan.device_node);
- tasklet_kill(&chan->vc.task);
+ dma_chan_kill_bh(&chan->vc.chan);
}
}
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 8e5f7defa6b6..378650bcc430 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -1015,7 +1015,7 @@ int dw_edma_remove(struct dw_edma_chip *chip)
dma_async_device_unregister(&dw->dma);
list_for_each_entry_safe(chan, _chan, &dw->dma.channels,
vc.chan.device_node) {
- tasklet_kill(&chan->vc.task);
+ dma_chan_kill_bh(&chan->vc.chan);
list_del(&chan->vc.chan.device_node);
}
diff --git a/drivers/dma/hisi_dma.c b/drivers/dma/hisi_dma.c
index 25a4134be36b..a15491945329 100644
--- a/drivers/dma/hisi_dma.c
+++ b/drivers/dma/hisi_dma.c
@@ -720,7 +720,7 @@ static void hisi_dma_disable_qps(struct hisi_dma_dev *hdma_dev)
for (i = 0; i < hdma_dev->chan_num; i++) {
hisi_dma_disable_qp(hdma_dev, i);
- tasklet_kill(&hdma_dev->chan[i].vc.task);
+ dma_chan_kill_bh(&hdma_dev->chan[i].vc.chan);
}
}
diff --git a/drivers/dma/img-mdc-dma.c b/drivers/dma/img-mdc-dma.c
index fd55bcd060ab..d4f6d839acca 100644
--- a/drivers/dma/img-mdc-dma.c
+++ b/drivers/dma/img-mdc-dma.c
@@ -1031,7 +1031,7 @@ static void mdc_dma_remove(struct platform_device *pdev)
devm_free_irq(&pdev->dev, mchan->irq, mchan);
- tasklet_kill(&mchan->vc.task);
+ dma_chan_kill_bh(&mchan->vc.chan);
}
pm_runtime_disable(&pdev->dev);
diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c
index ed9e56de5a9b..9cbc95fbb4ee 100644
--- a/drivers/dma/imx-sdma.c
+++ b/drivers/dma/imx-sdma.c
@@ -2427,7 +2427,7 @@ static void sdma_remove(struct platform_device *pdev)
for (i = 0; i < MAX_DMA_CHANNELS; i++) {
struct sdma_channel *sdmac = &sdma->channel[i];
- tasklet_kill(&sdmac->vc.task);
+ dma_chan_kill_bh(&sdmac->vc.chan);
sdma_free_chan_resources(&sdmac->vc.chan);
}
diff --git a/drivers/dma/k3dma.c b/drivers/dma/k3dma.c
index 0f9cd7815f88..36d5df545b57 100644
--- a/drivers/dma/k3dma.c
+++ b/drivers/dma/k3dma.c
@@ -981,7 +981,7 @@ static void k3_dma_remove(struct platform_device *op)
list_for_each_entry_safe(c, cn, &d->slave.channels, vc.chan.device_node) {
list_del(&c->vc.chan.device_node);
- tasklet_kill(&c->vc.task);
+ dma_chan_kill_bh(&c->vc.chan);
}
tasklet_kill(&d->task);
clk_disable_unprepare(d->clk);
diff --git a/drivers/dma/loongson1-apb-dma.c b/drivers/dma/loongson1-apb-dma.c
index 255fe7eca212..49a78ea1514f 100644
--- a/drivers/dma/loongson1-apb-dma.c
+++ b/drivers/dma/loongson1-apb-dma.c
@@ -552,7 +552,7 @@ static void ls1x_dma_chan_remove(struct ls1x_dma *dma)
if (chan->vc.chan.device == &dma->ddev) {
list_del(&chan->vc.chan.device_node);
- tasklet_kill(&chan->vc.task);
+ dma_chan_kill_bh(&chan->vc.chan);
}
}
}
diff --git a/drivers/dma/mediatek/mtk-cqdma.c b/drivers/dma/mediatek/mtk-cqdma.c
index 9f0c41ca7770..3ba54df12bae 100644
--- a/drivers/dma/mediatek/mtk-cqdma.c
+++ b/drivers/dma/mediatek/mtk-cqdma.c
@@ -895,7 +895,7 @@ static void mtk_cqdma_remove(struct platform_device *pdev)
vc = &cqdma->vc[i];
list_del(&vc->vc.chan.device_node);
- tasklet_kill(&vc->vc.task);
+ dma_chan_kill_bh(&vc->vc.chan);
}
/* disable interrupt */
diff --git a/drivers/dma/mediatek/mtk-hsdma.c b/drivers/dma/mediatek/mtk-hsdma.c
index fa77bb24a430..0bbf865de24b 100644
--- a/drivers/dma/mediatek/mtk-hsdma.c
+++ b/drivers/dma/mediatek/mtk-hsdma.c
@@ -1020,7 +1020,7 @@ static void mtk_hsdma_remove(struct platform_device *pdev)
vc = &hsdma->vc[i];
list_del(&vc->vc.chan.device_node);
- tasklet_kill(&vc->vc.task);
+ dma_chan_kill_bh(&vc->vc.chan);
}
/* Disable DMA interrupt */
diff --git a/drivers/dma/mediatek/mtk-uart-apdma.c b/drivers/dma/mediatek/mtk-uart-apdma.c
index 08e15177427b..257b9b77cc57 100644
--- a/drivers/dma/mediatek/mtk-uart-apdma.c
+++ b/drivers/dma/mediatek/mtk-uart-apdma.c
@@ -312,7 +312,7 @@ static void mtk_uart_apdma_free_chan_resources(struct dma_chan *chan)
free_irq(c->irq, chan);
- tasklet_kill(&c->vc.task);
+ dma_chan_kill_bh(&c->vc.chan);
vchan_free_chan_resources(&c->vc);
@@ -463,7 +463,7 @@ static void mtk_uart_apdma_free(struct mtk_uart_apdmadev *mtkd)
struct mtk_chan, vc.chan.device_node);
list_del(&c->vc.chan.device_node);
- tasklet_kill(&c->vc.task);
+ dma_chan_kill_bh(&c->vc.chan);
}
}
diff --git a/drivers/dma/owl-dma.c b/drivers/dma/owl-dma.c
index 57cec757d8f5..4e6ebf990688 100644
--- a/drivers/dma/owl-dma.c
+++ b/drivers/dma/owl-dma.c
@@ -1055,7 +1055,7 @@ static inline void owl_dma_free(struct owl_dma *od)
list_for_each_entry_safe(vchan,
next, &od->dma.channels, vc.chan.device_node) {
list_del(&vchan->vc.chan.device_node);
- tasklet_kill(&vchan->vc.task);
+ dma_chan_kill_bh(&vchan->vc.chan);
}
}
diff --git a/drivers/dma/pxa_dma.c b/drivers/dma/pxa_dma.c
index 249296389771..634b723e4891 100644
--- a/drivers/dma/pxa_dma.c
+++ b/drivers/dma/pxa_dma.c
@@ -1218,7 +1218,7 @@ static void pxad_free_channels(struct dma_device *dmadev)
list_for_each_entry_safe(c, cn, &dmadev->channels,
vc.chan.device_node) {
list_del(&c->vc.chan.device_node);
- tasklet_kill(&c->vc.task);
+ dma_chan_kill_bh(&c->vc.chan);
}
}
diff --git a/drivers/dma/qcom/bam_dma.c b/drivers/dma/qcom/bam_dma.c
index 2cf060174795..6ec82adb89ce 100644
--- a/drivers/dma/qcom/bam_dma.c
+++ b/drivers/dma/qcom/bam_dma.c
@@ -1377,7 +1377,7 @@ static int bam_dma_probe(struct platform_device *pdev)
dma_async_device_unregister(&bdev->common);
err_bam_channel_exit:
for (i = 0; i < bdev->num_channels; i++)
- tasklet_kill(&bdev->channels[i].vc.task);
+ dma_chan_kill_bh(&bdev->channels[i].vc.chan);
err_tasklet_kill:
tasklet_kill(&bdev->task);
err_disable_clk:
@@ -1403,7 +1403,7 @@ static void bam_dma_remove(struct platform_device *pdev)
for (i = 0; i < bdev->num_channels; i++) {
bam_dma_terminate_all(&bdev->channels[i].vc.chan);
- tasklet_kill(&bdev->channels[i].vc.task);
+ dma_chan_kill_bh(&bdev->channels[i].vc.chan);
if (!bdev->channels[i].fifo_virt)
continue;
diff --git a/drivers/dma/qcom/qcom_adm.c b/drivers/dma/qcom/qcom_adm.c
index 6be54fddcee1..0f9e906bd463 100644
--- a/drivers/dma/qcom/qcom_adm.c
+++ b/drivers/dma/qcom/qcom_adm.c
@@ -919,7 +919,7 @@ static void adm_dma_remove(struct platform_device *pdev)
/* mask IRQs for this channel/EE pair */
writel(0, adev->regs + ADM_CH_RSLT_CONF(achan->id, adev->ee));
- tasklet_kill(&adev->channels[i].vc.task);
+ dma_chan_kill_bh(&adev->channels[i].vc.chan);
adm_terminate_all(&adev->channels[i].vc.chan);
}
diff --git a/drivers/dma/sa11x0-dma.c b/drivers/dma/sa11x0-dma.c
index dc1a9a05252e..42940079efbf 100644
--- a/drivers/dma/sa11x0-dma.c
+++ b/drivers/dma/sa11x0-dma.c
@@ -893,7 +893,7 @@ static void sa11x0_dma_free_channels(struct dma_device *dmadev)
list_for_each_entry_safe(c, cn, &dmadev->channels, vc.chan.device_node) {
list_del(&c->vc.chan.device_node);
- tasklet_kill(&c->vc.task);
+ dma_chan_kill_bh(&c->vc.chan);
kfree(c);
}
}
diff --git a/drivers/dma/sprd-dma.c b/drivers/dma/sprd-dma.c
index 6207e0b185e1..5124fe0a93bf 100644
--- a/drivers/dma/sprd-dma.c
+++ b/drivers/dma/sprd-dma.c
@@ -1253,7 +1253,7 @@ static void sprd_dma_remove(struct platform_device *pdev)
list_for_each_entry_safe(c, cn, &sdev->dma_dev.channels,
vc.chan.device_node) {
list_del(&c->vc.chan.device_node);
- tasklet_kill(&c->vc.task);
+ dma_chan_kill_bh(&c->vc.chan);
}
of_dma_controller_free(pdev->dev.of_node);
diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
index 2215ff877bf7..6b3ab99272a5 100644
--- a/drivers/dma/sun6i-dma.c
+++ b/drivers/dma/sun6i-dma.c
@@ -1073,7 +1073,7 @@ static inline void sun6i_dma_free(struct sun6i_dma_dev *sdev)
struct sun6i_vchan *vchan = &sdev->vchans[i];
list_del(&vchan->vc.chan.device_node);
- tasklet_kill(&vchan->vc.task);
+ dma_chan_kill_bh(&vchan->vc.chan);
}
}
diff --git a/drivers/dma/tegra186-gpc-dma.c b/drivers/dma/tegra186-gpc-dma.c
index 4d6fe0efa76e..3b9dc64eb635 100644
--- a/drivers/dma/tegra186-gpc-dma.c
+++ b/drivers/dma/tegra186-gpc-dma.c
@@ -1279,7 +1279,7 @@ static void tegra_dma_free_chan_resources(struct dma_chan *dc)
tegra_dma_terminate_all(dc);
synchronize_irq(tdc->irq);
- tasklet_kill(&tdc->vc.task);
+ dma_chan_kill_bh(&tdc->vc.chan);
tdc->config_init = false;
tdc->slave_id = -1;
tdc->sid_dir = DMA_TRANS_NONE;
diff --git a/drivers/dma/tegra210-adma.c b/drivers/dma/tegra210-adma.c
index d0e8bb27a03b..67ccb1d48361 100644
--- a/drivers/dma/tegra210-adma.c
+++ b/drivers/dma/tegra210-adma.c
@@ -793,7 +793,7 @@ static void tegra_adma_free_chan_resources(struct dma_chan *dc)
tegra_adma_terminate_all(dc);
vchan_free_chan_resources(&tdc->vc);
- tasklet_kill(&tdc->vc.task);
+ dma_chan_kill_bh(&tdc->vc.chan);
free_irq(tdc->irq, tdc);
pm_runtime_put(tdc2dev(tdc));
diff --git a/drivers/dma/ti/k3-udma.c b/drivers/dma/ti/k3-udma.c
index aa2dc762140f..89dd7926705d 100644
--- a/drivers/dma/ti/k3-udma.c
+++ b/drivers/dma/ti/k3-udma.c
@@ -4045,9 +4045,9 @@ static void udma_desc_pre_callback(struct virt_dma_chan *vc,
* This tasklet handles the completion of a DMA descriptor by
* calling its callback and freeing it.
*/
-static void udma_vchan_complete(struct tasklet_struct *t)
+static void udma_vchan_complete(struct dma_chan *chan)
{
- struct virt_dma_chan *vc = from_tasklet(vc, t, task);
+ struct virt_dma_chan *vc = to_virt_chan(chan);
struct virt_dma_desc *vd, *_vd;
struct dmaengine_desc_callback cb;
LIST_HEAD(head);
@@ -4112,7 +4112,7 @@ static void udma_free_chan_resources(struct dma_chan *chan)
}
vchan_free_chan_resources(&uc->vc);
- tasklet_kill(&uc->vc.task);
+ dma_chan_kill_bh(&uc->vc.chan);
bcdma_free_bchan_resources(uc);
udma_free_tx_resources(uc);
@@ -5628,7 +5628,7 @@ static int udma_probe(struct platform_device *pdev)
return -ENOMEM;
vchan_init(&uc->vc, &ud->ddev);
/* Use custom vchan completion handling */
- tasklet_setup(&uc->vc.task, udma_vchan_complete);
+ dma_chan_init_bh(&uc->vc.chan, udma_vchan_complete);
init_completion(&uc->teardown_completed);
INIT_DELAYED_WORK(&uc->tx_drain.work, udma_check_tx_completion);
}
diff --git a/drivers/dma/ti/omap-dma.c b/drivers/dma/ti/omap-dma.c
index 8c023c6e623a..f6aee92071e2 100644
--- a/drivers/dma/ti/omap-dma.c
+++ b/drivers/dma/ti/omap-dma.c
@@ -1521,7 +1521,7 @@ static void omap_dma_free(struct omap_dmadev *od)
struct omap_chan, vc.chan.device_node);
list_del(&c->vc.chan.device_node);
- tasklet_kill(&c->vc.task);
+ dma_chan_kill_bh(&c->vc.chan);
kfree(c);
}
}
diff --git a/drivers/dma/virt-dma.c b/drivers/dma/virt-dma.c
index 7961172a780d..c311397f3bd7 100644
--- a/drivers/dma/virt-dma.c
+++ b/drivers/dma/virt-dma.c
@@ -80,9 +80,9 @@ EXPORT_SYMBOL_GPL(vchan_find_desc);
* This tasklet handles the completion of a DMA descriptor by
* calling its callback and freeing it.
*/
-static void vchan_complete(struct tasklet_struct *t)
+static void vchan_complete(struct dma_chan *chan)
{
- struct virt_dma_chan *vc = from_tasklet(vc, t, task);
+ struct virt_dma_chan *vc = to_virt_chan(chan);
struct virt_dma_desc *vd, *_vd;
struct dmaengine_desc_callback cb;
LIST_HEAD(head);
@@ -131,7 +131,7 @@ void vchan_init(struct virt_dma_chan *vc, struct dma_device *dmadev)
INIT_LIST_HEAD(&vc->desc_completed);
INIT_LIST_HEAD(&vc->desc_terminated);
- tasklet_setup(&vc->task, vchan_complete);
+ dma_chan_init_bh(&vc->chan, vchan_complete);
vc->chan.device = dmadev;
list_add_tail(&vc->chan.device_node, &dmadev->channels);
diff --git a/drivers/dma/virt-dma.h b/drivers/dma/virt-dma.h
index 59d9eabc8b67..5299fb7367ca 100644
--- a/drivers/dma/virt-dma.h
+++ b/drivers/dma/virt-dma.h
@@ -8,7 +8,6 @@
#define VIRT_DMA_H
#include <linux/dmaengine.h>
-#include <linux/interrupt.h>
#include "dmaengine.h"
@@ -21,7 +20,6 @@ struct virt_dma_desc {
struct virt_dma_chan {
struct dma_chan chan;
- struct tasklet_struct task;
void (*desc_free)(struct virt_dma_desc *);
spinlock_t lock;
@@ -106,7 +104,7 @@ static inline void vchan_cookie_complete(struct virt_dma_desc *vd)
vd, cookie);
list_add_tail(&vd->node, &vc->desc_completed);
- tasklet_schedule(&vc->task);
+ dma_chan_schedule_bh(&vc->chan);
}
/**
@@ -137,7 +135,7 @@ static inline void vchan_cyclic_callback(struct virt_dma_desc *vd)
struct virt_dma_chan *vc = to_virt_chan(vd->tx.chan);
vc->cyclic = vd;
- tasklet_schedule(&vc->task);
+ dma_chan_schedule_bh(&vc->chan);
}
/**
@@ -223,7 +221,7 @@ static inline void vchan_synchronize(struct virt_dma_chan *vc)
LIST_HEAD(head);
unsigned long flags;
- tasklet_kill(&vc->task);
+ dma_chan_kill_bh(&vc->chan);
spin_lock_irqsave(&vc->lock, flags);
diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h
index 99efe2b9b4ea..00ad92a73c3b 100644
--- a/include/linux/dmaengine.h
+++ b/include/linux/dmaengine.h
@@ -12,6 +12,7 @@
#include <linux/scatterlist.h>
#include <linux/bitmap.h>
#include <linux/types.h>
+#include <linux/workqueue.h>
#include <asm/page.h>
/**
@@ -295,6 +296,10 @@ enum dma_desc_metadata_mode {
DESC_METADATA_ENGINE = BIT(1),
};
+struct dma_chan;
+
+typedef void (*dma_chan_bh_work_fn)(struct dma_chan *chan);
+
/**
* struct dma_chan_percpu - the per-CPU part of struct dma_chan
* @memcpy_count: transaction counter
@@ -334,6 +339,9 @@ struct dma_router {
* @router: pointer to the DMA router structure
* @route_data: channel specific data for the router
* @private: private data for certain client-channel associations
+ * @bh_work: bottom-half work item stored per-channel
+ * @bh_work_fn: callback executed when @bh_work runs
+ * @bh_work_initialized: indicates whether @bh_work has been initialized
*/
struct dma_chan {
struct dma_device *device;
@@ -359,6 +367,9 @@ struct dma_chan {
void *route_data;
void *private;
+ struct work_struct bh_work;
+ dma_chan_bh_work_fn bh_work_fn;
+ bool bh_work_initialized;
};
/**
@@ -1528,6 +1539,14 @@ struct dma_chan *devm_dma_request_chan(struct device *dev, const char *name);
void dma_release_channel(struct dma_chan *chan);
int dma_get_slave_caps(struct dma_chan *chan, struct dma_slave_caps *caps);
+bool dmaengine_queue_bh_work(struct work_struct *work);
+void dmaengine_flush_bh_work(struct work_struct *work);
+void dmaengine_cancel_bh_work_sync(struct work_struct *work);
+
+void dma_chan_init_bh(struct dma_chan *chan, dma_chan_bh_work_fn fn);
+bool dma_chan_schedule_bh(struct dma_chan *chan);
+void dma_chan_flush_bh(struct dma_chan *chan);
+void dma_chan_kill_bh(struct dma_chan *chan);
#else
static inline struct dma_chan *dma_find_channel(enum dma_transaction_type tx_type)
{
@@ -1575,6 +1594,37 @@ static inline int dma_get_slave_caps(struct dma_chan *chan,
{
return -ENXIO;
}
+
+static inline bool dmaengine_queue_bh_work(struct work_struct *work)
+{
+ return false;
+}
+
+static inline void dmaengine_flush_bh_work(struct work_struct *work)
+{
+}
+
+static inline void dmaengine_cancel_bh_work_sync(struct work_struct *work)
+{
+}
+
+static inline void dma_chan_init_bh(struct dma_chan *chan,
+ dma_chan_bh_work_fn fn)
+{
+}
+
+static inline bool dma_chan_schedule_bh(struct dma_chan *chan)
+{
+ return false;
+}
+
+static inline void dma_chan_flush_bh(struct dma_chan *chan)
+{
+}
+
+static inline void dma_chan_kill_bh(struct dma_chan *chan)
+{
+}
#endif
static inline int dmaengine_desc_set_reuse(struct dma_async_tx_descriptor *tx)
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* Re: [RFC PATCH 1/1] dmaengine: introduce dmaengine_bh_wq and bh helpers
2026-01-08 8:03 ` [RFC PATCH 1/1] " Allen Pais
@ 2026-01-08 10:26 ` Arnd Bergmann
2026-01-08 19:22 ` Allen
2026-01-12 6:26 ` kernel test robot
` (3 subsequent siblings)
4 siblings, 1 reply; 63+ messages in thread
From: Arnd Bergmann @ 2026-01-08 10:26 UTC (permalink / raw)
To: Allen, Vinod Koul; +Cc: Kees Cook, dmaengine, linux-kernel
On Thu, Jan 8, 2026, at 09:03, Allen Pais wrote:
> Create a dedicated dmaengine bottom-half workqueue (WQ_BH | WQ_PERCPU)
> and provide helper APIs for queue/flush/cancel of BH work items. Add
> per-channel BH helpers in dma_chan so drivers can schedule a BH callback
> without maintaining their own tasklets.
>
> Convert virt-dma to use the new per-channel BH helpers and remove the
> per-channel tasklet. Update existing drivers that only need tasklet
> teardown to use dma_chan_kill_bh().
>
> This provides a common BH execution path for dmaengine and establishes
> the base for converting remaining DMA tasklets to workqueue-based BHs.
>
> Signed-off-by: Allen Pais <allen.lkml@gmail.com>
Hi Allen,
I agree that the dmaengine code should stop using tasklets here,
but I think the last time we discussed this, we ended up not
going with work queues as a replacement because of the inherent
scheduling overhead.
The use of this tasklet is to invoke the dmaengine_desc_callback(),
which at the moment clearly expects to be called from tasklet
context, but in most cases probably should just be called from
hardirq context, e.g. when all it does is to call complete()
or wake_up(). In particular, I assume this breaks any console
driver that tries to use DMA to send output to a UART.
It may make sense to take the portions of your patch that
abstract the dmaengine drivers away from tasklet and have them
interact with shared functions, but I don't think we should
introduce a workqueue at all here, at least not until we
have identified dmaengine users that want workqueue behavior.
If your goal is to reduce the number of tasklet uses in the
kernel, I would suggest taking this on at one level higher up
the stack: assume that dma_async_tx_descriptor->callback()
is always called at tasklet context, and introduce an
alternative mechanism that is called from hardirq context,
then change over each user of dma_async_tx_descriptor to
use the hardirq method instead of the tasklet method, if
at all possible.
Arnd
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [RFC PATCH 1/1] dmaengine: introduce dmaengine_bh_wq and bh helpers
2026-01-08 10:26 ` Arnd Bergmann
@ 2026-01-08 19:22 ` Allen
2026-01-09 16:42 ` Arnd Bergmann
0 siblings, 1 reply; 63+ messages in thread
From: Allen @ 2026-01-08 19:22 UTC (permalink / raw)
To: Arnd Bergmann; +Cc: Vinod Koul, Kees Cook, dmaengine, linux-kernel
> > Create a dedicated dmaengine bottom-half workqueue (WQ_BH | WQ_PERCPU)
> > and provide helper APIs for queue/flush/cancel of BH work items. Add
> > per-channel BH helpers in dma_chan so drivers can schedule a BH callback
> > without maintaining their own tasklets.
> >
> > Convert virt-dma to use the new per-channel BH helpers and remove the
> > per-channel tasklet. Update existing drivers that only need tasklet
> > teardown to use dma_chan_kill_bh().
> >
> > This provides a common BH execution path for dmaengine and establishes
> > the base for converting remaining DMA tasklets to workqueue-based BHs.
> >
> > Signed-off-by: Allen Pais <allen.lkml@gmail.com>
>
> Hi Allen,
>
> I agree that the dmaengine code should stop using tasklets here,
> but I think the last time we discussed this, we ended up not
> going with work queues as a replacement because of the inherent
> scheduling overhead.
>
> The use of this tasklet is to invoke the dmaengine_desc_callback(),
> which at the moment clearly expects to be called from tasklet
> context, but in most cases probably should just be called from
> hardirq context, e.g. when all it does is to call complete()
> or wake_up(). In particular, I assume this breaks any console
> driver that tries to use DMA to send output to a UART.
>
> It may make sense to take the portions of your patch that
> abstract the dmaengine drivers away from tasklet and have them
> interact with shared functions, but I don't think we should
> introduce a workqueue at all here, at least not until we
> have identified dmaengine users that want workqueue behavior.
>
> If your goal is to reduce the number of tasklet uses in the
> kernel, I would suggest taking this on at one level higher up
> the stack: assume that dma_async_tx_descriptor->callback()
> is always called at tasklet context, and introduce an
> alternative mechanism that is called from hardirq context,
> then change over each user of dma_async_tx_descriptor to
> use the hardirq method instead of the tasklet method, if
> at all possible.
Arnd,
Thanks for the feedback. My intent with WQ_BH was to keep callbacks in
softirq/BH context, but I agree the scheduling overhead and existing tasklet
assumptions are valid concerns.
I can re-spin the RFC to drop the workqueue entirely and keep
tasklet semantics,
while still abstracting tasklet handling into dmaengine helpers so drivers no
longer directly manipulate tasklets. That keeps
dmaengine_desc_callback_invoke()
in tasklet context and avoids breaking DMA users that rely on that behavior.
A hardirq callback path feels like a larger API change, so I’d
prefer to handle
that as a separate follow‑up (e.g. explicit hardirq callback/flag + user
migration where safe). Thoughts?
Here’s a diff on top of v6.19-rc4 that does exactly this.
diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c
index ca13cd39330b..611047763b35 100644
--- a/drivers/dma/dmaengine.c
+++ b/drivers/dma/dmaengine.c
@@ -1425,6 +1425,48 @@ static void dmaengine_destroy_unmap_pool(void)
}
}
+static void dma_chan_bh_entry(struct tasklet_struct *t)
+{
+ struct dma_chan *chan = from_tasklet(chan, t, bh_tasklet);
+ dma_chan_bh_work_fn fn = READ_ONCE(chan->bh_work_fn);
+
+ if (fn)
+ fn(chan);
+}
+
+void dma_chan_init_bh(struct dma_chan *chan, dma_chan_bh_work_fn fn)
+{
+ if (WARN_ON(!fn))
+ return;
+
+ if (WARN_ON(chan->bh_work_initialized))
+ return;
+
+ chan->bh_work_fn = fn;
+ tasklet_setup(&chan->bh_tasklet, dma_chan_bh_entry);
+ chan->bh_work_initialized = true;
+}
+EXPORT_SYMBOL_GPL(dma_chan_init_bh);
+
+bool dma_chan_schedule_bh(struct dma_chan *chan)
+{
+ if (WARN_ON(!chan->bh_work_initialized))
+ return false;
+
+ tasklet_schedule(&chan->bh_tasklet);
+ return true;
+}
+EXPORT_SYMBOL_GPL(dma_chan_schedule_bh);
+
+void dma_chan_kill_bh(struct dma_chan *chan)
+{
+ if (!chan->bh_work_initialized)
+ return;
+
+ tasklet_kill(&chan->bh_tasklet);
+}
+EXPORT_SYMBOL_GPL(dma_chan_kill_bh);
+
static int __init dmaengine_init_unmap_pool(void)
{
int i;
diff --git a/drivers/dma/virt-dma.c b/drivers/dma/virt-dma.c
index 7961172a780d..3d3fadb81f8d 100644
--- a/drivers/dma/virt-dma.c
+++ b/drivers/dma/virt-dma.c
@@ -77,12 +77,12 @@ struct virt_dma_desc *vchan_find_desc(struct
virt_dma_chan *vc,
EXPORT_SYMBOL_GPL(vchan_find_desc);
/*
- * This tasklet handles the completion of a DMA descriptor by
- * calling its callback and freeing it.
+ * This bottom-half handler completes a DMA descriptor by invoking its
+ * callback and freeing it.
*/
-static void vchan_complete(struct tasklet_struct *t)
+static void vchan_complete(struct dma_chan *chan)
{
- struct virt_dma_chan *vc = from_tasklet(vc, t, task);
+ struct virt_dma_chan *vc = to_virt_chan(chan);
struct virt_dma_desc *vd, *_vd;
struct dmaengine_desc_callback cb;
LIST_HEAD(head);
@@ -131,7 +131,7 @@ void vchan_init(struct virt_dma_chan *vc, struct
dma_device *dmadev)
INIT_LIST_HEAD(&vc->desc_completed);
INIT_LIST_HEAD(&vc->desc_terminated);
- tasklet_setup(&vc->task, vchan_complete);
+ dma_chan_init_bh(&vc->chan, vchan_complete);
vc->chan.device = dmadev;
list_add_tail(&vc->chan.device_node, &dmadev->channels);
diff --git a/drivers/dma/virt-dma.h b/drivers/dma/virt-dma.h
index 59d9eabc8b67..75c6fee2f70e 100644
--- a/drivers/dma/virt-dma.h
+++ b/drivers/dma/virt-dma.h
@@ -8,8 +8,6 @@
#define VIRT_DMA_H
#include <linux/dmaengine.h>
-#include <linux/interrupt.h>
-
#include "dmaengine.h"
struct virt_dma_desc {
@@ -21,7 +19,6 @@ struct virt_dma_desc {
struct virt_dma_chan {
struct dma_chan chan;
- struct tasklet_struct task;
void (*desc_free)(struct virt_dma_desc *);
spinlock_t lock;
@@ -106,7 +103,7 @@ static inline void vchan_cookie_complete(struct
virt_dma_desc *vd)
vd, cookie);
list_add_tail(&vd->node, &vc->desc_completed);
- tasklet_schedule(&vc->task);
+ dma_chan_schedule_bh(&vc->chan);
}
@@ -137,7 +134,7 @@ static inline void vchan_cyclic_callback(struct
virt_dma_desc *vd)
struct virt_dma_chan *vc = to_virt_chan(vd->tx.chan);
vc->cyclic = vd;
- tasklet_schedule(&vc->task);
+ dma_chan_schedule_bh(&vc->chan);
}
@@ -223,7 +220,7 @@ static inline void vchan_synchronize(struct
virt_dma_chan *vc)
LIST_HEAD(head);
unsigned long flags;
- tasklet_kill(&vc->task);
+ dma_chan_kill_bh(&vc->chan);
spin_lock_irqsave(&vc->lock, flags);
diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h
index 99efe2b9b4ea..2d2c8ab3764d 100644
--- a/include/linux/dmaengine.h
+++ b/include/linux/dmaengine.h
@@ -12,6 +12,7 @@
#include <linux/scatterlist.h>
#include <linux/bitmap.h>
#include <linux/types.h>
+#include <linux/interrupt.h>
#include <asm/page.h>
@@ -295,6 +296,10 @@ enum dma_desc_metadata_mode {
DESC_METADATA_ENGINE = BIT(1),
};
+struct dma_chan;
+
+typedef void (*dma_chan_bh_work_fn)(struct dma_chan *chan);
+
@@ -334,6 +339,9 @@ struct dma_router {
* @router: pointer to the DMA router structure
* @route_data: channel specific data for the router
* @private: private data for certain client-channel associations
+ * @bh_tasklet: bottom-half tasklet stored per-channel
+ * @bh_work_fn: callback executed when @bh_tasklet runs
+ * @bh_work_initialized: indicates whether @bh_tasklet has been initialized
*/
struct dma_chan {
@@ -359,6 +367,9 @@ struct dma_chan {
void *route_data;
void *private;
+ struct tasklet_struct bh_tasklet;
+ dma_chan_bh_work_fn bh_work_fn;
+ bool bh_work_initialized;
};
@@ -1528,6 +1539,9 @@ struct dma_chan *devm_dma_request_chan(struct
device *dev, const char *name);
void dma_release_channel(struct dma_chan *chan);
int dma_get_slave_caps(struct dma_chan *chan, struct dma_slave_caps *caps);
+void dma_chan_init_bh(struct dma_chan *chan, dma_chan_bh_work_fn fn);
+bool dma_chan_schedule_bh(struct dma_chan *chan);
+void dma_chan_kill_bh(struct dma_chan *chan);
#else
@@ -1575,6 +1589,20 @@ static inline int dma_get_slave_caps(struct
dma_chan *chan,
{
return -ENXIO;
}
+
+static inline void dma_chan_init_bh(struct dma_chan *chan,
+ dma_chan_bh_work_fn fn)
+{
+}
+
+static inline bool dma_chan_schedule_bh(struct dma_chan *chan)
+{
+ return false;
+}
+
+static inline void dma_chan_kill_bh(struct dma_chan *chan)
+{
+}
#endif
>
> Arnd
--
- Allen
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [RFC PATCH 1/1] dmaengine: introduce dmaengine_bh_wq and bh helpers
2026-01-08 19:22 ` Allen
@ 2026-01-09 16:42 ` Arnd Bergmann
2026-01-12 22:20 ` Allen
0 siblings, 1 reply; 63+ messages in thread
From: Arnd Bergmann @ 2026-01-09 16:42 UTC (permalink / raw)
To: Allen; +Cc: Vinod Koul, Kees Cook, dmaengine, linux-kernel
On Thu, Jan 8, 2026, at 20:22, Allen wrote:
>
> Thanks for the feedback. My intent with WQ_BH was to keep callbacks in
> softirq/BH context, but I agree the scheduling overhead and existing tasklet
> assumptions are valid concerns.
Hi Allen,
Sorry about missing the bit about WQ_BH, I forgot about the details
of or previous discussion and this is pretty much what I had
suggested last time,
> I can re-spin the RFC to drop the workqueue entirely and keep
> tasklet semantics,
> while still abstracting tasklet handling into dmaengine helpers so drivers no
> longer directly manipulate tasklets. That keeps
> dmaengine_desc_callback_invoke()
> in tasklet context and avoids breaking DMA users that rely on that behavior.
It's probably fine to do both, but a series of two patches (first introduce
the per-channel API, then move it over to WQ_BH) may be slightly
clearer here.
I'm not sure why the dmaengine_*_bh_wq() functions are exported
interfaces, as far as I can tell, you use them only internally
in the dma_chan_*_bh() functions, so making them static would
let the compiler inline them where possible.
There are of course dmaengine drivers that use tasklets for other
purposes than the channel callback. Was your idea here to use
the same workqueue for these? I would perhaps hold off on that for
the moment and see if there is a better alternative for those,
possibly hardirq context, threaded irq or a regular workqueue
depending on the driver.
> A hardirq callback path feels like a larger API change, so I’d
> prefer to handle that as a separate follow‑up (e.g. explicit hardirq
> callback/flag + user migration where safe). Thoughts?
Yes, definitely keep that separate. I still think that this is
what we want eventually for a bigger improvement, but your
patch seems valuable on its own as well.
Some more thoughts on where that later change could take us:
> /*
> - * This tasklet handles the completion of a DMA descriptor by
> - * calling its callback and freeing it.
> + * This bottom-half handler completes a DMA descriptor by invoking its
> + * callback and freeing it.
> */
> -static void vchan_complete(struct tasklet_struct *t)
> +static void vchan_complete(struct dma_chan *chan)
> {
> - struct virt_dma_chan *vc = from_tasklet(vc, t, task);
> + struct virt_dma_chan *vc = to_virt_chan(chan);
> struct virt_dma_desc *vd, *_vd;
> struct dmaengine_desc_callback cb;
> LIST_HEAD(head);
> @@ -131,7 +131,7 @@ void vchan_init(struct virt_dma_chan *vc, struct
> dma_device *dmadev)
> INIT_LIST_HEAD(&vc->desc_completed);
> INIT_LIST_HEAD(&vc->desc_terminated);
>
> - tasklet_setup(&vc->task, vchan_complete);
> + dma_chan_init_bh(&vc->chan, vchan_complete);
This is where I think it makes sense to start, again for
the vchan imlmenentation. What the dmaengine drivers have
is a per-driver tasklet (or WQ_BH) with a single complete()
function that directly calls into the client drivers for
each completion that has happened.
Since the context we want depends on the type of client
driver, I think a good approach would be to start
by modifying vchan_cookie_complete() to allow it to
call the callback function directly from hardirq context
when the client asks for that, and avoid the round trip
through the tasklet where possible.
A new bit in the dma_ctrl_flags word could be used
to ask for hardirq vs softirq context, and the existing
drivers just fall back to using a tasklet for softirq
context.
Arnd
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [RFC PATCH 1/1] dmaengine: introduce dmaengine_bh_wq and bh helpers
2026-01-08 8:03 ` [RFC PATCH 1/1] " Allen Pais
2026-01-08 10:26 ` Arnd Bergmann
@ 2026-01-12 6:26 ` kernel test robot
2026-01-14 7:13 ` kernel test robot
` (2 subsequent siblings)
4 siblings, 0 replies; 63+ messages in thread
From: kernel test robot @ 2026-01-12 6:26 UTC (permalink / raw)
To: Allen Pais; +Cc: oe-kbuild-all
Hi Allen,
[This is a private test report for your RFC patch.]
kernel test robot noticed the following build errors:
[auto build test ERROR on vkoul-dmaengine/next]
[also build test ERROR on shawnguo/for-next sunxi/sunxi/for-next soc/for-next linus/master v6.16-rc1 next-20260109]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Allen-Pais/dmaengine-introduce-dmaengine_bh_wq-and-bh-helpers/20260108-164201
base: https://git.kernel.org/pub/scm/linux/kernel/git/vkoul/dmaengine.git next
patch link: https://lore.kernel.org/r/20260108080332.2341725-2-allen.lkml%40gmail.com
patch subject: [RFC PATCH 1/1] dmaengine: introduce dmaengine_bh_wq and bh helpers
config: x86_64-rhel-9.4-ltp (https://download.01.org/0day-ci/archive/20260112/202601120727.5ttojW2a-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260112/202601120727.5ttojW2a-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202601120727.5ttojW2a-lkp@intel.com/
All errors (new ones prefixed by >>):
>> drivers/dma/idma64.c:166:8: error: unknown type name 'irqreturn_t'
166 | static irqreturn_t idma64_irq(int irq, void *dev)
| ^~~~~~~~~~~
drivers/dma/idma64.c: In function 'idma64_irq':
>> drivers/dma/idma64.c:176:24: error: 'IRQ_NONE' undeclared (first use in this function)
176 | return IRQ_NONE;
| ^~~~~~~~
drivers/dma/idma64.c:176:24: note: each undeclared identifier is reported only once for each function it appears in
>> drivers/dma/idma64.c:190:16: error: 'IRQ_HANDLED' undeclared (first use in this function)
190 | return IRQ_HANDLED;
| ^~~~~~~~~~~
drivers/dma/idma64.c: In function 'idma64_probe':
>> drivers/dma/idma64.c:561:15: error: implicit declaration of function 'devm_request_irq'; did you mean 'devm_request_region'? [-Wimplicit-function-declaration]
561 | ret = devm_request_irq(chip->dev, chip->irq, idma64_irq, IRQF_SHARED,
| ^~~~~~~~~~~~~~~~
| devm_request_region
>> drivers/dma/idma64.c:561:66: error: 'IRQF_SHARED' undeclared (first use in this function); did you mean 'VM_SHARED'?
561 | ret = devm_request_irq(chip->dev, chip->irq, idma64_irq, IRQF_SHARED,
| ^~~~~~~~~~~
| VM_SHARED
drivers/dma/idma64.c: In function 'idma64_remove':
>> drivers/dma/idma64.c:622:9: error: implicit declaration of function 'devm_free_irq'; did you mean 'devm_free_pages'? [-Wimplicit-function-declaration]
622 | devm_free_irq(chip->dev, chip->irq, idma64);
| ^~~~~~~~~~~~~
| devm_free_pages
>> drivers/dma/idma64.c:627:17: error: implicit declaration of function 'tasklet_kill' [-Wimplicit-function-declaration]
627 | tasklet_kill(&idma64c->vchan.task);
| ^~~~~~~~~~~~
>> drivers/dma/idma64.c:627:45: error: 'struct virt_dma_chan' has no member named 'task'
627 | tasklet_kill(&idma64c->vchan.task);
| ^
--
drivers/dma/hsu/hsu.c: In function 'hsu_dma_remove':
>> drivers/dma/hsu/hsu.c:503:42: error: 'struct virt_dma_chan' has no member named 'task'
503 | tasklet_kill(&hsuc->vchan.task);
| ^
vim +622 drivers/dma/idma64.c
667dfed98615ae Andy Shevchenko 2015-07-27 531
667dfed98615ae Andy Shevchenko 2015-07-27 532 #define IDMA64_BUSWIDTHS \
667dfed98615ae Andy Shevchenko 2015-07-27 533 BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \
667dfed98615ae Andy Shevchenko 2015-07-27 534 BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \
667dfed98615ae Andy Shevchenko 2015-07-27 535 BIT(DMA_SLAVE_BUSWIDTH_4_BYTES)
667dfed98615ae Andy Shevchenko 2015-07-27 536
667dfed98615ae Andy Shevchenko 2015-07-27 537 static int idma64_probe(struct idma64_chip *chip)
667dfed98615ae Andy Shevchenko 2015-07-27 538 {
667dfed98615ae Andy Shevchenko 2015-07-27 539 struct idma64 *idma64;
667dfed98615ae Andy Shevchenko 2015-07-27 540 unsigned short nr_chan = IDMA64_NR_CHAN;
667dfed98615ae Andy Shevchenko 2015-07-27 541 unsigned short i;
667dfed98615ae Andy Shevchenko 2015-07-27 542 int ret;
667dfed98615ae Andy Shevchenko 2015-07-27 543
667dfed98615ae Andy Shevchenko 2015-07-27 544 idma64 = devm_kzalloc(chip->dev, sizeof(*idma64), GFP_KERNEL);
667dfed98615ae Andy Shevchenko 2015-07-27 545 if (!idma64)
667dfed98615ae Andy Shevchenko 2015-07-27 546 return -ENOMEM;
667dfed98615ae Andy Shevchenko 2015-07-27 547
667dfed98615ae Andy Shevchenko 2015-07-27 548 idma64->regs = chip->regs;
667dfed98615ae Andy Shevchenko 2015-07-27 549 chip->idma64 = idma64;
667dfed98615ae Andy Shevchenko 2015-07-27 550
667dfed98615ae Andy Shevchenko 2015-07-27 551 idma64->chan = devm_kcalloc(chip->dev, nr_chan, sizeof(*idma64->chan),
667dfed98615ae Andy Shevchenko 2015-07-27 552 GFP_KERNEL);
667dfed98615ae Andy Shevchenko 2015-07-27 553 if (!idma64->chan)
667dfed98615ae Andy Shevchenko 2015-07-27 554 return -ENOMEM;
667dfed98615ae Andy Shevchenko 2015-07-27 555
667dfed98615ae Andy Shevchenko 2015-07-27 556 idma64->all_chan_mask = (1 << nr_chan) - 1;
667dfed98615ae Andy Shevchenko 2015-07-27 557
667dfed98615ae Andy Shevchenko 2015-07-27 558 /* Turn off iDMA controller */
667dfed98615ae Andy Shevchenko 2015-07-27 559 idma64_off(idma64);
667dfed98615ae Andy Shevchenko 2015-07-27 560
667dfed98615ae Andy Shevchenko 2015-07-27 @561 ret = devm_request_irq(chip->dev, chip->irq, idma64_irq, IRQF_SHARED,
667dfed98615ae Andy Shevchenko 2015-07-27 562 dev_name(chip->dev), idma64);
667dfed98615ae Andy Shevchenko 2015-07-27 563 if (ret)
667dfed98615ae Andy Shevchenko 2015-07-27 564 return ret;
667dfed98615ae Andy Shevchenko 2015-07-27 565
667dfed98615ae Andy Shevchenko 2015-07-27 566 INIT_LIST_HEAD(&idma64->dma.channels);
667dfed98615ae Andy Shevchenko 2015-07-27 567 for (i = 0; i < nr_chan; i++) {
667dfed98615ae Andy Shevchenko 2015-07-27 568 struct idma64_chan *idma64c = &idma64->chan[i];
667dfed98615ae Andy Shevchenko 2015-07-27 569
667dfed98615ae Andy Shevchenko 2015-07-27 570 idma64c->vchan.desc_free = idma64_vdesc_free;
667dfed98615ae Andy Shevchenko 2015-07-27 571 vchan_init(&idma64c->vchan, &idma64->dma);
667dfed98615ae Andy Shevchenko 2015-07-27 572
667dfed98615ae Andy Shevchenko 2015-07-27 573 idma64c->regs = idma64->regs + i * IDMA64_CH_LENGTH;
667dfed98615ae Andy Shevchenko 2015-07-27 574 idma64c->mask = BIT(i);
667dfed98615ae Andy Shevchenko 2015-07-27 575 }
667dfed98615ae Andy Shevchenko 2015-07-27 576
667dfed98615ae Andy Shevchenko 2015-07-27 577 dma_cap_set(DMA_SLAVE, idma64->dma.cap_mask);
667dfed98615ae Andy Shevchenko 2015-07-27 578 dma_cap_set(DMA_PRIVATE, idma64->dma.cap_mask);
667dfed98615ae Andy Shevchenko 2015-07-27 579
667dfed98615ae Andy Shevchenko 2015-07-27 580 idma64->dma.device_alloc_chan_resources = idma64_alloc_chan_resources;
667dfed98615ae Andy Shevchenko 2015-07-27 581 idma64->dma.device_free_chan_resources = idma64_free_chan_resources;
667dfed98615ae Andy Shevchenko 2015-07-27 582
667dfed98615ae Andy Shevchenko 2015-07-27 583 idma64->dma.device_prep_slave_sg = idma64_prep_slave_sg;
667dfed98615ae Andy Shevchenko 2015-07-27 584
667dfed98615ae Andy Shevchenko 2015-07-27 585 idma64->dma.device_issue_pending = idma64_issue_pending;
667dfed98615ae Andy Shevchenko 2015-07-27 586 idma64->dma.device_tx_status = idma64_tx_status;
667dfed98615ae Andy Shevchenko 2015-07-27 587
667dfed98615ae Andy Shevchenko 2015-07-27 588 idma64->dma.device_config = idma64_slave_config;
667dfed98615ae Andy Shevchenko 2015-07-27 589 idma64->dma.device_pause = idma64_pause;
667dfed98615ae Andy Shevchenko 2015-07-27 590 idma64->dma.device_resume = idma64_resume;
667dfed98615ae Andy Shevchenko 2015-07-27 591 idma64->dma.device_terminate_all = idma64_terminate_all;
bbacb8e78a3b29 Andy Shevchenko 2018-07-10 592 idma64->dma.device_synchronize = idma64_synchronize;
667dfed98615ae Andy Shevchenko 2015-07-27 593
667dfed98615ae Andy Shevchenko 2015-07-27 594 idma64->dma.src_addr_widths = IDMA64_BUSWIDTHS;
667dfed98615ae Andy Shevchenko 2015-07-27 595 idma64->dma.dst_addr_widths = IDMA64_BUSWIDTHS;
667dfed98615ae Andy Shevchenko 2015-07-27 596 idma64->dma.directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
667dfed98615ae Andy Shevchenko 2015-07-27 597 idma64->dma.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST;
667dfed98615ae Andy Shevchenko 2015-07-27 598
5ba846b1ee0792 Andy Shevchenko 2019-03-18 599 idma64->dma.dev = chip->sysdev;
667dfed98615ae Andy Shevchenko 2015-07-27 600
334304ac2baca7 Christoph Hellwig 2024-07-19 601 dma_set_max_seg_size(idma64->dma.dev, IDMA64C_CTLH_BLOCK_TS_MASK);
e3fdb1894cfac6 Andy Shevchenko 2015-11-17 602
667dfed98615ae Andy Shevchenko 2015-07-27 603 ret = dma_async_device_register(&idma64->dma);
667dfed98615ae Andy Shevchenko 2015-07-27 604 if (ret)
667dfed98615ae Andy Shevchenko 2015-07-27 605 return ret;
667dfed98615ae Andy Shevchenko 2015-07-27 606
667dfed98615ae Andy Shevchenko 2015-07-27 607 dev_info(chip->dev, "Found Intel integrated DMA 64-bit\n");
667dfed98615ae Andy Shevchenko 2015-07-27 608 return 0;
667dfed98615ae Andy Shevchenko 2015-07-27 609 }
667dfed98615ae Andy Shevchenko 2015-07-27 610
c3b63380f52a5c Uwe Kleine-König 2022-10-14 611 static void idma64_remove(struct idma64_chip *chip)
667dfed98615ae Andy Shevchenko 2015-07-27 612 {
667dfed98615ae Andy Shevchenko 2015-07-27 613 struct idma64 *idma64 = chip->idma64;
667dfed98615ae Andy Shevchenko 2015-07-27 614 unsigned short i;
667dfed98615ae Andy Shevchenko 2015-07-27 615
667dfed98615ae Andy Shevchenko 2015-07-27 616 dma_async_device_unregister(&idma64->dma);
667dfed98615ae Andy Shevchenko 2015-07-27 617
667dfed98615ae Andy Shevchenko 2015-07-27 618 /*
667dfed98615ae Andy Shevchenko 2015-07-27 619 * Explicitly call devm_request_irq() to avoid the side effects with
667dfed98615ae Andy Shevchenko 2015-07-27 620 * the scheduled tasklets.
667dfed98615ae Andy Shevchenko 2015-07-27 621 */
667dfed98615ae Andy Shevchenko 2015-07-27 @622 devm_free_irq(chip->dev, chip->irq, idma64);
667dfed98615ae Andy Shevchenko 2015-07-27 623
667dfed98615ae Andy Shevchenko 2015-07-27 624 for (i = 0; i < idma64->dma.chancnt; i++) {
667dfed98615ae Andy Shevchenko 2015-07-27 625 struct idma64_chan *idma64c = &idma64->chan[i];
667dfed98615ae Andy Shevchenko 2015-07-27 626
667dfed98615ae Andy Shevchenko 2015-07-27 @627 tasklet_kill(&idma64c->vchan.task);
667dfed98615ae Andy Shevchenko 2015-07-27 628 }
667dfed98615ae Andy Shevchenko 2015-07-27 629 }
667dfed98615ae Andy Shevchenko 2015-07-27 630
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [RFC PATCH 1/1] dmaengine: introduce dmaengine_bh_wq and bh helpers
2026-01-09 16:42 ` Arnd Bergmann
@ 2026-01-12 22:20 ` Allen
2026-01-13 7:33 ` Arnd Bergmann
0 siblings, 1 reply; 63+ messages in thread
From: Allen @ 2026-01-12 22:20 UTC (permalink / raw)
To: Arnd Bergmann; +Cc: Vinod Koul, Kees Cook, dmaengine, linux-kernel
> > Thanks for the feedback. My intent with WQ_BH was to keep callbacks in
> > softirq/BH context, but I agree the scheduling overhead and existing tasklet
> > assumptions are valid concerns.
>
> Hi Allen,
>
> Sorry about missing the bit about WQ_BH, I forgot about the details
> of or previous discussion and this is pretty much what I had
> suggested last time,
>
Hi Arnd,
Sorry I missed the WQ_BH point in my reply, I’d forgotten the details of our
earlier discussion, and I should have followed up on this sooner.
> > I can re-spin the RFC to drop the workqueue entirely and keep
> > tasklet semantics,
> > while still abstracting tasklet handling into dmaengine helpers so drivers no
> > longer directly manipulate tasklets. That keeps
> > dmaengine_desc_callback_invoke()
> > in tasklet context and avoids breaking DMA users that rely on that behavior.
>
> It's probably fine to do both, but a series of two patches (first introduce
> the per-channel API, then move it over to WQ_BH) may be slightly
> clearer here.
>
> I'm not sure why the dmaengine_*_bh_wq() functions are exported
> interfaces, as far as I can tell, you use them only internally
> in the dma_chan_*_bh() functions, so making them static would
> let the compiler inline them where possible.
>
> There are of course dmaengine drivers that use tasklets for other
> purposes than the channel callback. Was your idea here to use
> the same workqueue for these? I would perhaps hold off on that for
> the moment and see if there is a better alternative for those,
> possibly hardirq context, threaded irq or a regular workqueue
> depending on the driver.
>
> > A hardirq callback path feels like a larger API change, so I’d
> > prefer to handle that as a separate follow‑up (e.g. explicit hardirq
> > callback/flag + user migration where safe). Thoughts?
>
> Yes, definitely keep that separate. I still think that this is
> what we want eventually for a bigger improvement, but your
> patch seems valuable on its own as well.
>
Thanks for the detailed feedback. I’ll respin along these lines:
- Split the series into two patches: (1) introduce the per‑channel BH API,
(2) switch the vchan implementation over to the WQ_BH backend if we decide
to keep that step. This should make the progression clearer.
- The dmaengine_*_bh_wq() helpers will be made static; only dma_chan_*_bh()
stays exported.
- I won’t try to move the other per‑driver tasklets onto the shared queue in
this series. That feels like a separate discussion, and the right context
(hardirq/threaded/workqueue) may vary by driver.
- I’ll keep the hardirq callback path separate. For that follow‑up, I can plan
to add an explicit “hardirq safe” request bit (e.g. a new dma_ctrl_flags)
and update vchan_cookie_complete() to invoke the callback directly when
requested; otherwise it stays on the tasklet path.
If you’d prefer I drop the WQ_BH conversion entirely for now and keep only the
tasklet‑based per‑channel API, I can do that too.
Thanks,
Allen
> Some more thoughts on where that later change could take us:
>
> > /*
> > - * This tasklet handles the completion of a DMA descriptor by
> > - * calling its callback and freeing it.
> > + * This bottom-half handler completes a DMA descriptor by invoking its
> > + * callback and freeing it.
> > */
> > -static void vchan_complete(struct tasklet_struct *t)
> > +static void vchan_complete(struct dma_chan *chan)
> > {
> > - struct virt_dma_chan *vc = from_tasklet(vc, t, task);
> > + struct virt_dma_chan *vc = to_virt_chan(chan);
> > struct virt_dma_desc *vd, *_vd;
> > struct dmaengine_desc_callback cb;
> > LIST_HEAD(head);
> > @@ -131,7 +131,7 @@ void vchan_init(struct virt_dma_chan *vc, struct
> > dma_device *dmadev)
> > INIT_LIST_HEAD(&vc->desc_completed);
> > INIT_LIST_HEAD(&vc->desc_terminated);
> >
> > - tasklet_setup(&vc->task, vchan_complete);
> > + dma_chan_init_bh(&vc->chan, vchan_complete);
>
> This is where I think it makes sense to start, again for
> the vchan imlmenentation. What the dmaengine drivers have
> is a per-driver tasklet (or WQ_BH) with a single complete()
> function that directly calls into the client drivers for
> each completion that has happened.
>
> Since the context we want depends on the type of client
> driver, I think a good approach would be to start
> by modifying vchan_cookie_complete() to allow it to
> call the callback function directly from hardirq context
> when the client asks for that, and avoid the round trip
> through the tasklet where possible.
>
> A new bit in the dma_ctrl_flags word could be used
> to ask for hardirq vs softirq context, and the existing
> drivers just fall back to using a tasklet for softirq
> context.
>
> Arnd
--
- Allen
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [RFC PATCH 1/1] dmaengine: introduce dmaengine_bh_wq and bh helpers
2026-01-12 22:20 ` Allen
@ 2026-01-13 7:33 ` Arnd Bergmann
2026-01-13 19:31 ` Allen
0 siblings, 1 reply; 63+ messages in thread
From: Arnd Bergmann @ 2026-01-13 7:33 UTC (permalink / raw)
To: Allen; +Cc: Vinod Koul, Kees Cook, dmaengine, linux-kernel
On Mon, Jan 12, 2026, at 23:20, Allen wrote:
>> > A hardirq callback path feels like a larger API change, so I’d
>> > prefer to handle that as a separate follow‑up (e.g. explicit hardirq
>> > callback/flag + user migration where safe). Thoughts?
>>
>> Yes, definitely keep that separate. I still think that this is
>> what we want eventually for a bigger improvement, but your
>> patch seems valuable on its own as well.
>>
>
> Thanks for the detailed feedback. I’ll respin along these lines:
>
> - Split the series into two patches: (1) introduce the per‑channel BH API,
> (2) switch the vchan implementation over to the WQ_BH backend if we decide
> to keep that step. This should make the progression clearer.
>
> - The dmaengine_*_bh_wq() helpers will be made static; only dma_chan_*_bh()
> stays exported.
Sounds good, yes.
> - I won’t try to move the other per‑driver tasklets onto the shared queue in
> this series. That feels like a separate discussion, and the right context
> (hardirq/threaded/workqueue) may vary by driver.
I'm not sure we are talking about the same thing here. I do think we should
try to have all the callbacks in each dmaengine driver go through the same
per-channel deferral mechanism. What I meant to say is that all the tasklets
that are not used for the client callbacks should be separate from those,
e.g. the pl330_dotask() handler is used for unexpected events unrelated
to a channel.
> - I’ll keep the hardirq callback path separate. For that follow‑up, I can plan
> to add an explicit “hardirq safe” request bit (e.g. a new dma_ctrl_flags)
> and update vchan_cookie_complete() to invoke the callback directly when
> requested; otherwise it stays on the tasklet path.
>
> If you’d prefer I drop the WQ_BH conversion entirely for now and keep only the
> tasklet‑based per‑channel API, I can do that too.
No, I think that part is fine.
Arnd
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [RFC PATCH 1/1] dmaengine: introduce dmaengine_bh_wq and bh helpers
2026-01-13 7:33 ` Arnd Bergmann
@ 2026-01-13 19:31 ` Allen
0 siblings, 0 replies; 63+ messages in thread
From: Allen @ 2026-01-13 19:31 UTC (permalink / raw)
To: Arnd Bergmann; +Cc: Vinod Koul, Kees Cook, dmaengine, linux-kernel
> >> > A hardirq callback path feels like a larger API change, so I’d
> >> > prefer to handle that as a separate follow‑up (e.g. explicit hardirq
> >> > callback/flag + user migration where safe). Thoughts?
> >>
> >> Yes, definitely keep that separate. I still think that this is
> >> what we want eventually for a bigger improvement, but your
> >> patch seems valuable on its own as well.
> >>
> >
> > Thanks for the detailed feedback. I’ll respin along these lines:
> >
> > - Split the series into two patches: (1) introduce the per‑channel BH API,
> > (2) switch the vchan implementation over to the WQ_BH backend if we decide
> > to keep that step. This should make the progression clearer.
> >
> > - The dmaengine_*_bh_wq() helpers will be made static; only dma_chan_*_bh()
> > stays exported.
>
> Sounds good, yes.
>
> > - I won’t try to move the other per‑driver tasklets onto the shared queue in
> > this series. That feels like a separate discussion, and the right context
> > (hardirq/threaded/workqueue) may vary by driver.
>
> I'm not sure we are talking about the same thing here. I do think we should
> try to have all the callbacks in each dmaengine driver go through the same
> per-channel deferral mechanism. What I meant to say is that all the tasklets
> that are not used for the client callbacks should be separate from those,
> e.g. the pl330_dotask() handler is used for unexpected events unrelated
> to a channel.
Hi Arnd,
Thanks for clarifying, I understand now. Yes, the intent will be to have
all per‑channel client callbacks go through the shared per‑channel deferral
mechanism (dma_chan_*_bh), while keeping any non‑callback tasklets/work
separate (e.g. pl330_dotask() for unexpected events).
I’ll keep WQ_BH and the per‑channel API as the common callback path, and in
follow‑on patches I’ll start converting driver callback paths to use it.
Thanks,
Allen
>
> > - I’ll keep the hardirq callback path separate. For that follow‑up, I can plan
> > to add an explicit “hardirq safe” request bit (e.g. a new dma_ctrl_flags)
> > and update vchan_cookie_complete() to invoke the callback directly when
> > requested; otherwise it stays on the tasklet path.
> >
> > If you’d prefer I drop the WQ_BH conversion entirely for now and keep only the
> > tasklet‑based per‑channel API, I can do that too.
>
> No, I think that part is fine.
>
> Arnd
--
- Allen
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [RFC PATCH 1/1] dmaengine: introduce dmaengine_bh_wq and bh helpers
2026-01-08 8:03 ` [RFC PATCH 1/1] " Allen Pais
2026-01-08 10:26 ` Arnd Bergmann
2026-01-12 6:26 ` kernel test robot
@ 2026-01-14 7:13 ` kernel test robot
2026-01-14 7:35 ` kernel test robot
2026-01-14 8:49 ` kernel test robot
4 siblings, 0 replies; 63+ messages in thread
From: kernel test robot @ 2026-01-14 7:13 UTC (permalink / raw)
To: Allen Pais; +Cc: oe-kbuild-all
Hi Allen,
[This is a private test report for your RFC patch.]
kernel test robot noticed the following build errors:
[auto build test ERROR on vkoul-dmaengine/next]
[also build test ERROR on shawnguo/for-next sunxi/sunxi/for-next soc/for-next linus/master v6.19-rc5 next-20260113]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Allen-Pais/dmaengine-introduce-dmaengine_bh_wq-and-bh-helpers/20260108-164201
base: https://git.kernel.org/pub/scm/linux/kernel/git/vkoul/dmaengine.git next
patch link: https://lore.kernel.org/r/20260108080332.2341725-2-allen.lkml%40gmail.com
patch subject: [RFC PATCH 1/1] dmaengine: introduce dmaengine_bh_wq and bh helpers
config: csky-allmodconfig (https://download.01.org/0day-ci/archive/20260114/202601141406.igbKhauH-lkp@intel.com/config)
compiler: csky-linux-gcc (GCC) 15.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260114/202601141406.igbKhauH-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202601141406.igbKhauH-lkp@intel.com/
All errors (new ones prefixed by >>):
drivers/dma/hsu/hsu.c: In function 'hsu_dma_remove':
>> drivers/dma/hsu/hsu.c:503:42: error: 'struct virt_dma_chan' has no member named 'task'
503 | tasklet_kill(&hsuc->vchan.task);
| ^
vim +503 drivers/dma/hsu/hsu.c
2b49e0c56741fca Andy Shevchenko 2015-02-23 492
2b49e0c56741fca Andy Shevchenko 2015-02-23 493 int hsu_dma_remove(struct hsu_dma_chip *chip)
2b49e0c56741fca Andy Shevchenko 2015-02-23 494 {
2b49e0c56741fca Andy Shevchenko 2015-02-23 495 struct hsu_dma *hsu = chip->hsu;
2b49e0c56741fca Andy Shevchenko 2015-02-23 496 unsigned short i;
2b49e0c56741fca Andy Shevchenko 2015-02-23 497
2b49e0c56741fca Andy Shevchenko 2015-02-23 498 dma_async_device_unregister(&hsu->dma);
2b49e0c56741fca Andy Shevchenko 2015-02-23 499
4c97ad993d76390 Heikki Krogerus 2015-10-13 500 for (i = 0; i < hsu->nr_channels; i++) {
2b49e0c56741fca Andy Shevchenko 2015-02-23 501 struct hsu_dma_chan *hsuc = &hsu->chan[i];
2b49e0c56741fca Andy Shevchenko 2015-02-23 502
2b49e0c56741fca Andy Shevchenko 2015-02-23 @503 tasklet_kill(&hsuc->vchan.task);
2b49e0c56741fca Andy Shevchenko 2015-02-23 504 }
2b49e0c56741fca Andy Shevchenko 2015-02-23 505
2b49e0c56741fca Andy Shevchenko 2015-02-23 506 return 0;
2b49e0c56741fca Andy Shevchenko 2015-02-23 507 }
2b49e0c56741fca Andy Shevchenko 2015-02-23 508 EXPORT_SYMBOL_GPL(hsu_dma_remove);
2b49e0c56741fca Andy Shevchenko 2015-02-23 509
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [RFC PATCH 1/1] dmaengine: introduce dmaengine_bh_wq and bh helpers
2026-01-08 8:03 ` [RFC PATCH 1/1] " Allen Pais
` (2 preceding siblings ...)
2026-01-14 7:13 ` kernel test robot
@ 2026-01-14 7:35 ` kernel test robot
2026-01-14 8:49 ` kernel test robot
4 siblings, 0 replies; 63+ messages in thread
From: kernel test robot @ 2026-01-14 7:35 UTC (permalink / raw)
To: Allen Pais; +Cc: oe-kbuild-all
Hi Allen,
[This is a private test report for your RFC patch.]
kernel test robot noticed the following build warnings:
[auto build test WARNING on vkoul-dmaengine/next]
[also build test WARNING on shawnguo/for-next sunxi/sunxi/for-next soc/for-next linus/master v6.19-rc5 next-20260114]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Allen-Pais/dmaengine-introduce-dmaengine_bh_wq-and-bh-helpers/20260108-164201
base: https://git.kernel.org/pub/scm/linux/kernel/git/vkoul/dmaengine.git next
patch link: https://lore.kernel.org/r/20260108080332.2341725-2-allen.lkml%40gmail.com
patch subject: [RFC PATCH 1/1] dmaengine: introduce dmaengine_bh_wq and bh helpers
config: um-allyesconfig (https://download.01.org/0day-ci/archive/20260114/202601141510.JE2HHJ51-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260114/202601141510.JE2HHJ51-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202601141510.JE2HHJ51-lkp@intel.com/
All warnings (new ones prefixed by >>):
drivers/dma/mediatek/mtk-hsdma.c:644:8: error: unknown type name 'irqreturn_t'
644 | static irqreturn_t mtk_hsdma_irq(int irq, void *devid)
| ^~~~~~~~~~~
drivers/dma/mediatek/mtk-hsdma.c: In function 'mtk_hsdma_irq':
drivers/dma/mediatek/mtk-hsdma.c:656:16: error: 'IRQ_HANDLED' undeclared (first use in this function)
656 | return IRQ_HANDLED;
| ^~~~~~~~~~~
drivers/dma/mediatek/mtk-hsdma.c:656:16: note: each undeclared identifier is reported only once for each function it appears in
drivers/dma/mediatek/mtk-hsdma.c: In function 'mtk_hsdma_probe':
drivers/dma/mediatek/mtk-hsdma.c:988:15: error: implicit declaration of function 'devm_request_irq'; did you mean 'devm_request_region'? [-Wimplicit-function-declaration]
988 | err = devm_request_irq(&pdev->dev, hsdma->irq,
| ^~~~~~~~~~~~~~~~
| devm_request_region
drivers/dma/mediatek/mtk-hsdma.c: In function 'mtk_hsdma_remove':
drivers/dma/mediatek/mtk-hsdma.c:1030:9: error: implicit declaration of function 'synchronize_irq'; did you mean 'synchronize_srcu'? [-Wimplicit-function-declaration]
1030 | synchronize_irq(hsdma->irq);
| ^~~~~~~~~~~~~~~
| synchronize_srcu
drivers/dma/mediatek/mtk-hsdma.c: In function 'mtk_hsdma_irq':
>> drivers/dma/mediatek/mtk-hsdma.c:657:1: warning: control reaches end of non-void function [-Wreturn-type]
657 | }
| ^
--
In file included from drivers/dma/sf-pdma/sf-pdma.c:26:
drivers/dma/sf-pdma/sf-pdma.h:102:41: error: field 'done_tasklet' has incomplete type
102 | struct tasklet_struct done_tasklet;
| ^~~~~~~~~~~~
drivers/dma/sf-pdma/sf-pdma.h:103:41: error: field 'err_tasklet' has incomplete type
103 | struct tasklet_struct err_tasklet;
| ^~~~~~~~~~~
drivers/dma/sf-pdma/sf-pdma.c: In function 'sf_pdma_donebh_tasklet':
drivers/dma/sf-pdma/sf-pdma.c:300:37: error: implicit declaration of function 'from_tasklet' [-Wimplicit-function-declaration]
300 | struct sf_pdma_chan *chan = from_tasklet(chan, t, done_tasklet);
| ^~~~~~~~~~~~
drivers/dma/sf-pdma/sf-pdma.c:300:59: error: 'done_tasklet' undeclared (first use in this function)
300 | struct sf_pdma_chan *chan = from_tasklet(chan, t, done_tasklet);
| ^~~~~~~~~~~~
drivers/dma/sf-pdma/sf-pdma.c:300:59: note: each undeclared identifier is reported only once for each function it appears in
drivers/dma/sf-pdma/sf-pdma.c: In function 'sf_pdma_errbh_tasklet':
drivers/dma/sf-pdma/sf-pdma.c:324:59: error: 'err_tasklet' undeclared (first use in this function)
324 | struct sf_pdma_chan *chan = from_tasklet(chan, t, err_tasklet);
| ^~~~~~~~~~~
drivers/dma/sf-pdma/sf-pdma.c: At top level:
drivers/dma/sf-pdma/sf-pdma.c:344:8: error: unknown type name 'irqreturn_t'
344 | static irqreturn_t sf_pdma_done_isr(int irq, void *dev_id)
| ^~~~~~~~~~~
drivers/dma/sf-pdma/sf-pdma.c: In function 'sf_pdma_done_isr':
drivers/dma/sf-pdma/sf-pdma.c:355:17: error: implicit declaration of function 'tasklet_hi_schedule' [-Wimplicit-function-declaration]
355 | tasklet_hi_schedule(&chan->done_tasklet);
| ^~~~~~~~~~~~~~~~~~~
drivers/dma/sf-pdma/sf-pdma.c:369:16: error: 'IRQ_HANDLED' undeclared (first use in this function)
369 | return IRQ_HANDLED;
| ^~~~~~~~~~~
drivers/dma/sf-pdma/sf-pdma.c: At top level:
drivers/dma/sf-pdma/sf-pdma.c:372:8: error: unknown type name 'irqreturn_t'
372 | static irqreturn_t sf_pdma_err_isr(int irq, void *dev_id)
| ^~~~~~~~~~~
drivers/dma/sf-pdma/sf-pdma.c: In function 'sf_pdma_err_isr':
drivers/dma/sf-pdma/sf-pdma.c:381:9: error: implicit declaration of function 'tasklet_schedule' [-Wimplicit-function-declaration]
381 | tasklet_schedule(&chan->err_tasklet);
| ^~~~~~~~~~~~~~~~
drivers/dma/sf-pdma/sf-pdma.c:383:16: error: 'IRQ_HANDLED' undeclared (first use in this function)
383 | return IRQ_HANDLED;
| ^~~~~~~~~~~
drivers/dma/sf-pdma/sf-pdma.c: In function 'sf_pdma_irq_init':
drivers/dma/sf-pdma/sf-pdma.c:413:21: error: implicit declaration of function 'devm_request_irq'; did you mean 'devm_request_region'? [-Wimplicit-function-declaration]
413 | r = devm_request_irq(&pdev->dev, irq, sf_pdma_done_isr, 0,
| ^~~~~~~~~~~~~~~~
| devm_request_region
drivers/dma/sf-pdma/sf-pdma.c: In function 'sf_pdma_setup_chans':
drivers/dma/sf-pdma/sf-pdma.c:491:17: error: implicit declaration of function 'tasklet_setup'; did you mean 'timer_setup'? [-Wimplicit-function-declaration]
491 | tasklet_setup(&chan->done_tasklet, sf_pdma_donebh_tasklet);
| ^~~~~~~~~~~~~
| timer_setup
drivers/dma/sf-pdma/sf-pdma.c: In function 'sf_pdma_remove':
drivers/dma/sf-pdma/sf-pdma.c:603:17: error: implicit declaration of function 'devm_free_irq'; did you mean 'devm_free_pages'? [-Wimplicit-function-declaration]
603 | devm_free_irq(&pdev->dev, ch->txirq, ch);
| ^~~~~~~~~~~~~
| devm_free_pages
drivers/dma/sf-pdma/sf-pdma.c:606:17: error: implicit declaration of function 'tasklet_kill' [-Wimplicit-function-declaration]
606 | tasklet_kill(&ch->vchan.task);
| ^~~~~~~~~~~~
drivers/dma/sf-pdma/sf-pdma.c:606:40: error: 'struct virt_dma_chan' has no member named 'task'
606 | tasklet_kill(&ch->vchan.task);
| ^
drivers/dma/sf-pdma/sf-pdma.c: In function 'sf_pdma_err_isr':
>> drivers/dma/sf-pdma/sf-pdma.c:384:1: warning: control reaches end of non-void function [-Wreturn-type]
384 | }
| ^
drivers/dma/sf-pdma/sf-pdma.c: In function 'sf_pdma_done_isr':
drivers/dma/sf-pdma/sf-pdma.c:370:1: warning: control reaches end of non-void function [-Wreturn-type]
370 | }
| ^
--
drivers/dma/stm32/stm32-dma.c:760:8: error: unknown type name 'irqreturn_t'
760 | static irqreturn_t stm32_dma_chan_irq(int irq, void *devid)
| ^~~~~~~~~~~
drivers/dma/stm32/stm32-dma.c: In function 'stm32_dma_chan_irq':
drivers/dma/stm32/stm32-dma.c:813:16: error: 'IRQ_HANDLED' undeclared (first use in this function)
813 | return IRQ_HANDLED;
| ^~~~~~~~~~~
drivers/dma/stm32/stm32-dma.c:813:16: note: each undeclared identifier is reported only once for each function it appears in
drivers/dma/stm32/stm32-dma.c: In function 'stm32_dma_probe':
drivers/dma/stm32/stm32-dma.c:1669:23: error: implicit declaration of function 'devm_request_irq'; did you mean 'devm_request_region'? [-Wimplicit-function-declaration]
1669 | ret = devm_request_irq(&pdev->dev, chan->irq,
| ^~~~~~~~~~~~~~~~
| devm_request_region
drivers/dma/stm32/stm32-dma.c: In function 'stm32_dma_chan_irq':
>> drivers/dma/stm32/stm32-dma.c:814:1: warning: control reaches end of non-void function [-Wreturn-type]
814 | }
| ^
--
drivers/dma/stm32/stm32-mdma.c:1399:8: error: unknown type name 'irqreturn_t'
1399 | static irqreturn_t stm32_mdma_irq_handler(int irq, void *devid)
| ^~~~~~~~~~~
drivers/dma/stm32/stm32-mdma.c: In function 'stm32_mdma_irq_handler':
drivers/dma/stm32/stm32-mdma.c:1409:24: error: 'IRQ_NONE' undeclared (first use in this function)
1409 | return IRQ_NONE;
| ^~~~~~~~
drivers/dma/stm32/stm32-mdma.c:1409:24: note: each undeclared identifier is reported only once for each function it appears in
drivers/dma/stm32/stm32-mdma.c:1478:16: error: 'IRQ_HANDLED' undeclared (first use in this function)
1478 | return IRQ_HANDLED;
| ^~~~~~~~~~~
drivers/dma/stm32/stm32-mdma.c: In function 'stm32_mdma_probe':
drivers/dma/stm32/stm32-mdma.c:1714:15: error: implicit declaration of function 'devm_request_irq'; did you mean 'devm_request_region'? [-Wimplicit-function-declaration]
1714 | ret = devm_request_irq(&pdev->dev, dmadev->irq, stm32_mdma_irq_handler,
| ^~~~~~~~~~~~~~~~
| devm_request_region
drivers/dma/stm32/stm32-mdma.c: In function 'stm32_mdma_irq_handler':
>> drivers/dma/stm32/stm32-mdma.c:1479:1: warning: control reaches end of non-void function [-Wreturn-type]
1479 | }
| ^
--
drivers/dma/stm32/stm32-dma3.c:1012:8: error: unknown type name 'irqreturn_t'
1012 | static irqreturn_t stm32_dma3_chan_irq(int irq, void *devid)
| ^~~~~~~~~~~
drivers/dma/stm32/stm32-dma3.c: In function 'stm32_dma3_chan_irq':
drivers/dma/stm32/stm32-dma3.c:1023:24: error: 'IRQ_NONE' undeclared (first use in this function)
1023 | return IRQ_NONE;
| ^~~~~~~~
drivers/dma/stm32/stm32-dma3.c:1023:24: note: each undeclared identifier is reported only once for each function it appears in
drivers/dma/stm32/stm32-dma3.c:1069:16: error: 'IRQ_HANDLED' undeclared (first use in this function)
1069 | return IRQ_HANDLED;
| ^~~~~~~~~~~
drivers/dma/stm32/stm32-dma3.c: In function 'stm32_dma3_probe':
drivers/dma/stm32/stm32-dma3.c:1894:23: error: implicit declaration of function 'devm_request_irq'; did you mean 'devm_request_region'? [-Wimplicit-function-declaration]
1894 | ret = devm_request_irq(&pdev->dev, chan->irq, stm32_dma3_chan_irq, 0,
| ^~~~~~~~~~~~~~~~
| devm_request_region
drivers/dma/stm32/stm32-dma3.c: In function 'stm32_dma3_chan_irq':
>> drivers/dma/stm32/stm32-dma3.c:1070:1: warning: control reaches end of non-void function [-Wreturn-type]
1070 | }
| ^
vim +657 drivers/dma/mediatek/mtk-hsdma.c
548c4597e984b7 Sean Wang 2018-03-15 643
548c4597e984b7 Sean Wang 2018-03-15 644 static irqreturn_t mtk_hsdma_irq(int irq, void *devid)
548c4597e984b7 Sean Wang 2018-03-15 645 {
548c4597e984b7 Sean Wang 2018-03-15 646 struct mtk_hsdma_device *hsdma = devid;
548c4597e984b7 Sean Wang 2018-03-15 647
548c4597e984b7 Sean Wang 2018-03-15 648 /*
548c4597e984b7 Sean Wang 2018-03-15 649 * Disable interrupt until all completed PDs are cleaned up in
548c4597e984b7 Sean Wang 2018-03-15 650 * mtk_hsdma_free_rooms call.
548c4597e984b7 Sean Wang 2018-03-15 651 */
548c4597e984b7 Sean Wang 2018-03-15 652 mtk_dma_clr(hsdma, MTK_HSDMA_INT_ENABLE, MTK_HSDMA_INT_RXDONE);
548c4597e984b7 Sean Wang 2018-03-15 653
548c4597e984b7 Sean Wang 2018-03-15 654 mtk_hsdma_free_rooms_in_ring(hsdma);
548c4597e984b7 Sean Wang 2018-03-15 655
548c4597e984b7 Sean Wang 2018-03-15 656 return IRQ_HANDLED;
548c4597e984b7 Sean Wang 2018-03-15 @657 }
548c4597e984b7 Sean Wang 2018-03-15 658
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [RFC PATCH 1/1] dmaengine: introduce dmaengine_bh_wq and bh helpers
2026-01-08 8:03 ` [RFC PATCH 1/1] " Allen Pais
` (3 preceding siblings ...)
2026-01-14 7:35 ` kernel test robot
@ 2026-01-14 8:49 ` kernel test robot
4 siblings, 0 replies; 63+ messages in thread
From: kernel test robot @ 2026-01-14 8:49 UTC (permalink / raw)
To: Allen Pais; +Cc: llvm, oe-kbuild-all
Hi Allen,
[This is a private test report for your RFC patch.]
kernel test robot noticed the following build errors:
[auto build test ERROR on vkoul-dmaengine/next]
[also build test ERROR on shawnguo/for-next sunxi/sunxi/for-next soc/for-next linus/master v6.19-rc5 next-20260114]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Allen-Pais/dmaengine-introduce-dmaengine_bh_wq-and-bh-helpers/20260108-164201
base: https://git.kernel.org/pub/scm/linux/kernel/git/vkoul/dmaengine.git next
patch link: https://lore.kernel.org/r/20260108080332.2341725-2-allen.lkml%40gmail.com
patch subject: [RFC PATCH 1/1] dmaengine: introduce dmaengine_bh_wq and bh helpers
config: um-allmodconfig (https://download.01.org/0day-ci/archive/20260114/202601141622.CAk5SXFi-lkp@intel.com/config)
compiler: clang version 19.1.7 (https://github.com/llvm/llvm-project cd708029e0b2869e80abe31ddb175f7c35361f90)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260114/202601141622.CAk5SXFi-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202601141622.CAk5SXFi-lkp@intel.com/
All errors (new ones prefixed by >>):
In file included from drivers/dma/hsu/hsu.c:22:
In file included from include/linux/dmaengine.h:12:
In file included from include/linux/scatterlist.h:9:
In file included from arch/um/include/asm/io.h:24:
include/asm-generic/io.h:1209:55: warning: performing pointer arithmetic on a null pointer has undefined behavior [-Wnull-pointer-arithmetic]
1209 | return (port > MMIO_UPPER_LIMIT) ? NULL : PCI_IOBASE + port;
| ~~~~~~~~~~ ^
>> drivers/dma/hsu/hsu.c:503:29: error: no member named 'task' in 'struct virt_dma_chan'
503 | tasklet_kill(&hsuc->vchan.task);
| ~~~~~~~~~~~ ^
1 warning and 1 error generated.
vim +503 drivers/dma/hsu/hsu.c
2b49e0c56741fc Andy Shevchenko 2015-02-23 492
2b49e0c56741fc Andy Shevchenko 2015-02-23 493 int hsu_dma_remove(struct hsu_dma_chip *chip)
2b49e0c56741fc Andy Shevchenko 2015-02-23 494 {
2b49e0c56741fc Andy Shevchenko 2015-02-23 495 struct hsu_dma *hsu = chip->hsu;
2b49e0c56741fc Andy Shevchenko 2015-02-23 496 unsigned short i;
2b49e0c56741fc Andy Shevchenko 2015-02-23 497
2b49e0c56741fc Andy Shevchenko 2015-02-23 498 dma_async_device_unregister(&hsu->dma);
2b49e0c56741fc Andy Shevchenko 2015-02-23 499
4c97ad993d7639 Heikki Krogerus 2015-10-13 500 for (i = 0; i < hsu->nr_channels; i++) {
2b49e0c56741fc Andy Shevchenko 2015-02-23 501 struct hsu_dma_chan *hsuc = &hsu->chan[i];
2b49e0c56741fc Andy Shevchenko 2015-02-23 502
2b49e0c56741fc Andy Shevchenko 2015-02-23 @503 tasklet_kill(&hsuc->vchan.task);
2b49e0c56741fc Andy Shevchenko 2015-02-23 504 }
2b49e0c56741fc Andy Shevchenko 2015-02-23 505
2b49e0c56741fc Andy Shevchenko 2015-02-23 506 return 0;
2b49e0c56741fc Andy Shevchenko 2015-02-23 507 }
2b49e0c56741fc Andy Shevchenko 2015-02-23 508 EXPORT_SYMBOL_GPL(hsu_dma_remove);
2b49e0c56741fc Andy Shevchenko 2015-02-23 509
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 63+ messages in thread
* [PATCH v2 03/64] dmaengine: apple-admac: use dma_chan BH callback
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
@ 2026-07-27 20:28 ` Allen Pais
2026-07-27 20:28 ` [PATCH v2 04/64] dmaengine: at_xdmac: move irq bottom half to dma_chan BH Allen Pais
` (20 subsequent siblings)
21 siblings, 0 replies; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:28 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Sven Peter, Janne Grunau, Neal Gompa, asahi, linux-arm-kernel
Replace the per-channel tasklet with the shared dma_chan BH helper.
The handler continues to run in softirq context while dmaengine owns
the common scheduling and teardown mechanism.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/apple-admac.c | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
diff --git a/drivers/dma/apple-admac.c b/drivers/dma/apple-admac.c
index 14a5ee14a481..84483247bde6 100644
--- a/drivers/dma/apple-admac.c
+++ b/drivers/dma/apple-admac.c
@@ -89,7 +89,6 @@ struct admac_chan {
unsigned int no;
struct admac_data *host;
struct dma_chan chan;
- struct tasklet_struct tasklet;
u32 carveout;
@@ -521,10 +520,7 @@ static int admac_terminate_all(struct dma_chan *chan)
list_add_tail(&adchan->current_tx->node, &adchan->to_free);
adchan->current_tx = NULL;
}
- /*
- * Descriptors can only be freed after the tasklet
- * has been killed (in admac_synchronize).
- */
+ /* Descriptors are released once the BH is flushed in admac_synchronize */
list_splice_tail_init(&adchan->submitted, &adchan->to_free);
list_splice_tail_init(&adchan->issued, &adchan->to_free);
spin_unlock_irqrestore(&adchan->lock, flags);
@@ -543,7 +539,7 @@ static void admac_synchronize(struct dma_chan *chan)
list_splice_tail_init(&adchan->to_free, &head);
spin_unlock_irqrestore(&adchan->lock, flags);
- tasklet_kill(&adchan->tasklet);
+ dma_chan_kill_bh(&adchan->chan);
list_for_each_entry_safe(adtx, _adtx, &head, node) {
list_del(&adtx->node);
@@ -662,7 +658,7 @@ static void admac_handle_status_desc_done(struct admac_data *ad, int channo)
tx->reclaimed_pos %= 2 * tx->buf_len;
admac_cyclic_write_desc(ad, channo, tx);
- tasklet_schedule(&adchan->tasklet);
+ dma_chan_schedule_bh(&adchan->chan);
}
spin_unlock_irqrestore(&adchan->lock, flags);
}
@@ -712,9 +708,9 @@ static irqreturn_t admac_interrupt(int irq, void *devid)
return IRQ_HANDLED;
}
-static void admac_chan_tasklet(struct tasklet_struct *t)
+static void admac_chan_bh(struct dma_chan *chan)
{
- struct admac_chan *adchan = from_tasklet(adchan, t, tasklet);
+ struct admac_chan *adchan = to_admac_chan(chan);
struct admac_tx *adtx;
struct dmaengine_desc_callback cb;
struct dmaengine_result tx_result;
@@ -886,7 +882,7 @@ static int admac_probe(struct platform_device *pdev)
INIT_LIST_HEAD(&adchan->issued);
INIT_LIST_HEAD(&adchan->to_free);
list_add_tail(&adchan->chan.device_node, &dma->channels);
- tasklet_setup(&adchan->tasklet, admac_chan_tasklet);
+ dma_chan_init_bh(&adchan->chan, admac_chan_bh);
}
err = reset_control_reset(ad->rstc);
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 04/64] dmaengine: at_xdmac: move irq bottom half to dma_chan BH
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
2026-07-27 20:28 ` [PATCH v2 03/64] dmaengine: apple-admac: use dma_chan BH callback Allen Pais
@ 2026-07-27 20:28 ` Allen Pais
2026-07-27 20:28 ` [PATCH v2 08/64] dmaengine: imx-dma: flip per-chan tasklet " Allen Pais
` (19 subsequent siblings)
21 siblings, 0 replies; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:28 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Ludovic Desroches, linux-arm-kernel
Replace the per-channel tasklet with the shared dma_chan BH helper.
The handler continues to run in softirq context while dmaengine owns
the common scheduling and teardown mechanism.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/at_xdmac.c | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/drivers/dma/at_xdmac.c b/drivers/dma/at_xdmac.c
index 901971e8bae6..aa918043e37e 100644
--- a/drivers/dma/at_xdmac.c
+++ b/drivers/dma/at_xdmac.c
@@ -228,7 +228,6 @@ struct at_xdmac_chan {
u32 save_cndc;
u32 irq_status;
unsigned long status;
- struct tasklet_struct tasklet;
struct dma_slave_config sconfig;
spinlock_t lock;
@@ -1759,9 +1758,9 @@ static void at_xdmac_handle_error(struct at_xdmac_chan *atchan)
/* Then continue with usual descriptor management */
}
-static void at_xdmac_tasklet(struct tasklet_struct *t)
+static void at_xdmac_tasklet(struct dma_chan *chan)
{
- struct at_xdmac_chan *atchan = from_tasklet(atchan, t, tasklet);
+ struct at_xdmac_chan *atchan = to_at_xdmac_chan(chan);
struct at_xdmac *atxdmac = to_at_xdmac(atchan->chan.device);
struct at_xdmac_desc *desc;
struct dma_async_tx_descriptor *txd;
@@ -1865,7 +1864,7 @@ static irqreturn_t at_xdmac_interrupt(int irq, void *dev_id)
if (atchan->irq_status & (AT_XDMAC_CIS_RBEIS | AT_XDMAC_CIS_WBEIS))
at_xdmac_write(atxdmac, AT_XDMAC_GD, atchan->mask);
- tasklet_schedule(&atchan->tasklet);
+ dma_chan_schedule_bh(&atchan->chan);
ret = IRQ_HANDLED;
}
@@ -2317,7 +2316,7 @@ static int at_xdmac_probe(struct platform_device *pdev)
return PTR_ERR(atxdmac->clk);
}
- /* Do not use dev res to prevent races with tasklet */
+ /* Do not use devm resources to prevent races with the BH worker */
ret = request_irq(atxdmac->irq, at_xdmac_interrupt, 0, "at_xdmac", atxdmac);
if (ret) {
dev_err(&pdev->dev, "can't request irq\n");
@@ -2397,7 +2396,7 @@ static int at_xdmac_probe(struct platform_device *pdev)
spin_lock_init(&atchan->lock);
INIT_LIST_HEAD(&atchan->xfers_list);
INIT_LIST_HEAD(&atchan->free_descs_list);
- tasklet_setup(&atchan->tasklet, at_xdmac_tasklet);
+ dma_chan_init_bh(&atchan->chan, at_xdmac_tasklet);
/* Clear pending interrupts. */
while (at_xdmac_chan_read(atchan, AT_XDMAC_CIS))
@@ -2458,7 +2457,7 @@ static void at_xdmac_remove(struct platform_device *pdev)
for (i = 0; i < atxdmac->dma.chancnt; i++) {
struct at_xdmac_chan *atchan = &atxdmac->chan[i];
- tasklet_kill(&atchan->tasklet);
+ dma_chan_kill_bh(&atchan->chan);
at_xdmac_free_chan_resources(&atchan->chan);
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 08/64] dmaengine: imx-dma: flip per-chan tasklet to dma_chan BH
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
2026-07-27 20:28 ` [PATCH v2 03/64] dmaengine: apple-admac: use dma_chan BH callback Allen Pais
2026-07-27 20:28 ` [PATCH v2 04/64] dmaengine: at_xdmac: move irq bottom half to dma_chan BH Allen Pais
@ 2026-07-27 20:28 ` Allen Pais
2026-07-27 20:57 ` sashiko-bot
2026-07-27 20:28 ` [PATCH v2 13/64] dmaengine: mxs-dma: use dma_chan BH scheduling Allen Pais
` (18 subsequent siblings)
21 siblings, 1 reply; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:28 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, imx,
linux-arm-kernel
Replace the per-channel tasklet with the shared dma_chan BH helper.
The handler continues to run in softirq context while dmaengine owns
the common scheduling and teardown mechanism.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/imx-dma.c | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/drivers/dma/imx-dma.c b/drivers/dma/imx-dma.c
index 81c6276436f8..3ff0bfbfa5ff 100644
--- a/drivers/dma/imx-dma.c
+++ b/drivers/dma/imx-dma.c
@@ -145,7 +145,6 @@ struct imxdma_channel {
struct imxdma_engine *imxdma;
unsigned int channel;
- struct tasklet_struct dma_tasklet;
struct list_head ld_free;
struct list_head ld_queue;
struct list_head ld_active;
@@ -344,8 +343,8 @@ static void imxdma_watchdog(struct timer_list *t)
imx_dmav1_writel(imxdma, 0, DMA_CCR(channel));
- /* Tasklet watchdog error handler */
- tasklet_schedule(&imxdmac->dma_tasklet);
+ /* BH watchdog error handler */
+ dma_chan_schedule_bh(&imxdmac->chan);
dev_dbg(imxdma->dev, "channel %d: watchdog timeout!\n",
imxdmac->channel);
}
@@ -390,8 +389,8 @@ static irqreturn_t imxdma_err_handler(int irq, void *dev_id)
imx_dmav1_writel(imxdma, 1 << i, DMA_DBOSR);
errcode |= IMX_DMA_ERR_BUFFER;
}
- /* Tasklet error handler */
- tasklet_schedule(&imxdma->channel[i].dma_tasklet);
+ /* BH error handler */
+ dma_chan_schedule_bh(&imxdma->channel[i].chan);
dev_warn(imxdma->dev,
"DMA timeout on channel %d -%s%s%s%s\n", i,
@@ -448,8 +447,8 @@ static void dma_irq_handle_channel(struct imxdma_channel *imxdmac)
imx_dmav1_writel(imxdma, tmp, DMA_CCR(chno));
if (imxdma_chan_is_doing_cyclic(imxdmac))
- /* Tasklet progression */
- tasklet_schedule(&imxdmac->dma_tasklet);
+ /* BH progression */
+ dma_chan_schedule_bh(&imxdmac->chan);
return;
}
@@ -462,8 +461,8 @@ static void dma_irq_handle_channel(struct imxdma_channel *imxdmac)
out:
imx_dmav1_writel(imxdma, 0, DMA_CCR(chno));
- /* Tasklet irq */
- tasklet_schedule(&imxdmac->dma_tasklet);
+ /* Schedule the IRQ BH */
+ dma_chan_schedule_bh(&imxdmac->chan);
}
static irqreturn_t dma_irq_handler(int irq, void *dev_id)
@@ -592,9 +591,10 @@ static int imxdma_xfer_desc(struct imxdma_desc *d)
return 0;
}
-static void imxdma_tasklet(struct tasklet_struct *t)
+static void imxdma_tasklet(struct dma_chan *chan)
{
- struct imxdma_channel *imxdmac = from_tasklet(imxdmac, t, dma_tasklet);
+ struct imxdma_channel *imxdmac = container_of(chan, struct imxdma_channel,
+ chan);
struct imxdma_engine *imxdma = imxdmac->imxdma;
struct imxdma_desc *desc, *next_desc;
unsigned long flags;
@@ -1142,7 +1142,7 @@ static int __init imxdma_probe(struct platform_device *pdev)
INIT_LIST_HEAD(&imxdmac->ld_free);
INIT_LIST_HEAD(&imxdmac->ld_active);
- tasklet_setup(&imxdmac->dma_tasklet, imxdma_tasklet);
+ dma_chan_init_bh(&imxdmac->chan, imxdma_tasklet);
imxdmac->chan.device = &imxdma->dma_device;
dma_cookie_init(&imxdmac->chan);
imxdmac->channel = i;
@@ -1211,7 +1211,7 @@ static void imxdma_free_irq(struct platform_device *pdev, struct imxdma_engine *
if (!is_imx1_dma(imxdma))
disable_irq(imxdmac->irq);
- tasklet_kill(&imxdmac->dma_tasklet);
+ dma_chan_kill_bh(&imxdmac->chan);
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 13/64] dmaengine: mxs-dma: use dma_chan BH scheduling
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
` (2 preceding siblings ...)
2026-07-27 20:28 ` [PATCH v2 08/64] dmaengine: imx-dma: flip per-chan tasklet " Allen Pais
@ 2026-07-27 20:28 ` Allen Pais
2026-07-27 20:59 ` sashiko-bot
2026-07-27 20:28 ` [PATCH v2 17/64] dmaengine: ste_dma40: convert per-channel tasklet to dma_chan BH Allen Pais
` (17 subsequent siblings)
21 siblings, 1 reply; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:28 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, imx,
linux-arm-kernel
Replace the per-channel tasklet with the shared dma_chan BH helper.
The handler continues to run in softirq context while dmaengine owns
the common scheduling and teardown mechanism.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/mxs-dma.c | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/drivers/dma/mxs-dma.c b/drivers/dma/mxs-dma.c
index 7acb3d29dad3..8cecc0add82d 100644
--- a/drivers/dma/mxs-dma.c
+++ b/drivers/dma/mxs-dma.c
@@ -109,7 +109,6 @@ struct mxs_dma_chan {
struct mxs_dma_engine *mxs_dma;
struct dma_chan chan;
struct dma_async_tx_descriptor desc;
- struct tasklet_struct tasklet;
unsigned int chan_irq;
struct mxs_dma_ccw *ccw;
dma_addr_t ccw_phys;
@@ -300,9 +299,9 @@ static dma_cookie_t mxs_dma_tx_submit(struct dma_async_tx_descriptor *tx)
return dma_cookie_assign(tx);
}
-static void mxs_dma_tasklet(struct tasklet_struct *t)
+static void mxs_dma_tasklet(struct dma_chan *chan)
{
- struct mxs_dma_chan *mxs_chan = from_tasklet(mxs_chan, t, tasklet);
+ struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(chan);
dmaengine_desc_get_callback_invoke(&mxs_chan->desc, NULL);
}
@@ -386,8 +385,8 @@ static irqreturn_t mxs_dma_int_handler(int irq, void *dev_id)
dma_cookie_complete(&mxs_chan->desc);
}
- /* schedule tasklet on this channel */
- tasklet_schedule(&mxs_chan->tasklet);
+ /* schedule BH on this channel */
+ dma_chan_schedule_bh(&mxs_chan->chan);
return IRQ_HANDLED;
}
@@ -781,7 +780,7 @@ static int mxs_dma_probe(struct platform_device *pdev)
mxs_chan->chan.device = &mxs_dma->dma_device;
dma_cookie_init(&mxs_chan->chan);
- tasklet_setup(&mxs_chan->tasklet, mxs_dma_tasklet);
+ dma_chan_init_bh(&mxs_chan->chan, mxs_dma_tasklet);
/* Add the channel to mxs_chan list */
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 17/64] dmaengine: ste_dma40: convert per-channel tasklet to dma_chan BH
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
` (3 preceding siblings ...)
2026-07-27 20:28 ` [PATCH v2 13/64] dmaengine: mxs-dma: use dma_chan BH scheduling Allen Pais
@ 2026-07-27 20:28 ` Allen Pais
2026-07-28 19:33 ` Linus Walleij
2026-07-27 20:28 ` [PATCH v2 19/64] dmaengine: xilinx-dma: use dma_chan BH instead of tasklets Allen Pais
` (16 subsequent siblings)
21 siblings, 1 reply; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:28 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Linus Walleij, linux-arm-kernel
Replace the per-channel tasklet with the shared dma_chan BH helper.
The handler continues to run in softirq context while dmaengine owns
the common scheduling and teardown mechanism.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/ste_dma40.c | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 0d9ffa3e2663..f279a093a81f 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -461,8 +461,6 @@ struct d40_base;
* @phy_chan: Pointer to physical channel which this instance runs on. If this
* point is NULL, then the channel is not allocated.
* @chan: DMA engine handle.
- * @tasklet: Tasklet that gets scheduled from interrupt context to complete a
- * transfer and call client callback.
* @client: Cliented owned descriptor list.
* @pending_queue: Submitted jobs, to be issued by issue_pending()
* @active: Active descriptor.
@@ -489,7 +487,6 @@ struct d40_chan {
bool busy;
struct d40_phy_res *phy_chan;
struct dma_chan chan;
- struct tasklet_struct tasklet;
struct list_head client;
struct list_head pending_queue;
struct list_head active;
@@ -1587,13 +1584,13 @@ static void dma_tc_handle(struct d40_chan *d40c)
}
d40c->pending_tx++;
- tasklet_schedule(&d40c->tasklet);
+ dma_chan_schedule_bh(&d40c->chan);
}
-static void dma_tasklet(struct tasklet_struct *t)
+static void dma_tasklet(struct dma_chan *chan)
{
- struct d40_chan *d40c = from_tasklet(d40c, t, tasklet);
+ struct d40_chan *d40c = container_of(chan, struct d40_chan, chan);
struct d40_desc *d40d;
unsigned long flags;
bool callback_active;
@@ -1641,7 +1638,7 @@ static void dma_tasklet(struct tasklet_struct *t)
d40c->pending_tx--;
if (d40c->pending_tx)
- tasklet_schedule(&d40c->tasklet);
+ dma_chan_schedule_bh(&d40c->chan);
spin_unlock_irqrestore(&d40c->lock, flags);
@@ -2815,7 +2812,7 @@ static void __init d40_chan_init(struct d40_base *base, struct dma_device *dma,
INIT_LIST_HEAD(&d40c->client);
INIT_LIST_HEAD(&d40c->prepare_queue);
- tasklet_setup(&d40c->tasklet, dma_tasklet);
+ dma_chan_init_bh(&d40c->chan, dma_tasklet);
list_add_tail(&d40c->chan.device_node,
&dma->channels);
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 19/64] dmaengine: xilinx-dma: use dma_chan BH instead of tasklets
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
` (4 preceding siblings ...)
2026-07-27 20:28 ` [PATCH v2 17/64] dmaengine: ste_dma40: convert per-channel tasklet to dma_chan BH Allen Pais
@ 2026-07-27 20:28 ` Allen Pais
2026-07-27 20:28 ` [PATCH v2 20/64] dmaengine: xilinx-dpdma: kill vchan BH on remove Allen Pais
` (15 subsequent siblings)
21 siblings, 0 replies; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:28 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Michal Simek, Suraj Gupta, Marek Vasut, Folker Schwesinger,
Tomi Valkeinen, Krzysztof Kozlowski, linux-arm-kernel
Replace the per-channel tasklet with the shared dma_chan BH helper.
The handler continues to run in softirq context while dmaengine owns
the common scheduling and teardown mechanism.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/xilinx/xilinx_dma.c | 20 +++++++++-----------
1 file changed, 9 insertions(+), 11 deletions(-)
diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c
index 404235c17353..bf9f22ed1265 100644
--- a/drivers/dma/xilinx/xilinx_dma.c
+++ b/drivers/dma/xilinx/xilinx_dma.c
@@ -402,7 +402,6 @@ struct xilinx_dma_tx_descriptor {
* @err: Channel has errors
* @idle: Check for channel idle
* @terminating: Check for channel being synchronized by user
- * @tasklet: Cleanup work after irq
* @config: Device configuration info
* @flush_on_fsync: Flush on Frame sync
* @desc_pendingcount: Descriptor pending count
@@ -441,7 +440,6 @@ struct xilinx_dma_chan {
bool err;
bool idle;
bool terminating;
- struct tasklet_struct tasklet;
struct xilinx_vdma_config config;
bool flush_on_fsync;
u32 desc_pendingcount;
@@ -1115,11 +1113,12 @@ static void xilinx_dma_chan_desc_cleanup(struct xilinx_dma_chan *chan)
/**
* xilinx_dma_do_tasklet - Schedule completion tasklet
- * @t: Pointer to the Xilinx DMA channel structure
+ * @c: Pointer to the Xilinx DMA channel structure
*/
-static void xilinx_dma_do_tasklet(struct tasklet_struct *t)
+static void xilinx_dma_do_tasklet(struct dma_chan *c)
{
- struct xilinx_dma_chan *chan = from_tasklet(chan, t, tasklet);
+ struct xilinx_dma_chan *chan = container_of(c,
+ struct xilinx_dma_chan, common);
xilinx_dma_chan_desc_cleanup(chan);
}
@@ -1898,7 +1897,7 @@ static irqreturn_t xilinx_mcdma_irq_handler(int irq, void *data)
spin_unlock(&chan->lock);
}
- tasklet_hi_schedule(&chan->tasklet);
+ dma_chan_schedule_bh(&chan->common);
return IRQ_HANDLED;
}
@@ -1955,7 +1954,7 @@ static irqreturn_t xilinx_dma_irq_handler(int irq, void *data)
spin_unlock(&chan->lock);
}
- tasklet_schedule(&chan->tasklet);
+ dma_chan_schedule_bh(&chan->common);
return IRQ_HANDLED;
}
@@ -2654,7 +2653,7 @@ static void xilinx_dma_synchronize(struct dma_chan *dchan)
{
struct xilinx_dma_chan *chan = to_xilinx_chan(dchan);
- tasklet_kill(&chan->tasklet);
+ dma_chan_kill_bh(&chan->common);
}
/**
@@ -2745,7 +2744,7 @@ static void xilinx_dma_chan_remove(struct xilinx_dma_chan *chan)
if (chan->irq > 0)
free_irq(chan->irq, chan);
- tasklet_kill(&chan->tasklet);
+ dma_chan_kill_bh(&chan->common);
list_del(&chan->common.device_node);
}
@@ -3075,8 +3074,7 @@ static int xilinx_dma_chan_probe(struct xilinx_dma_device *xdev,
str_enabled_disabled(chan->has_sg));
}
- /* Initialize the tasklet */
- tasklet_setup(&chan->tasklet, xilinx_dma_do_tasklet);
+ dma_chan_init_bh(&chan->common, xilinx_dma_do_tasklet);
/*
* Initialize the DMA channel and add it to the DMA engine channels
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 20/64] dmaengine: xilinx-dpdma: kill vchan BH on remove
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
` (5 preceding siblings ...)
2026-07-27 20:28 ` [PATCH v2 19/64] dmaengine: xilinx-dma: use dma_chan BH instead of tasklets Allen Pais
@ 2026-07-27 20:28 ` Allen Pais
2026-07-27 20:28 ` [PATCH v2 21/64] dmaengine: zynqmp-dma: switch completion tasklet to dma_chan BH Allen Pais
` (14 subsequent siblings)
21 siblings, 0 replies; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:28 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Laurent Pinchart, Michal Simek, linux-arm-kernel
virt-dma now dispatches completion callbacks through per-channel BH
work. Cancel that work when removing a channel, while retaining the
driver's separate error-handling tasklet.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/xilinx/xilinx_dpdma.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/dma/xilinx/xilinx_dpdma.c b/drivers/dma/xilinx/xilinx_dpdma.c
index d9a3542c4531..2ec82064fd2e 100644
--- a/drivers/dma/xilinx/xilinx_dpdma.c
+++ b/drivers/dma/xilinx/xilinx_dpdma.c
@@ -1685,6 +1685,7 @@ static void xilinx_dpdma_chan_remove(struct xilinx_dpdma_chan *chan)
return;
tasklet_kill(&chan->err_task);
+ dma_chan_kill_bh(&chan->vchan.chan);
list_del(&chan->vchan.chan.device_node);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 21/64] dmaengine: zynqmp-dma: switch completion tasklet to dma_chan BH
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
` (6 preceding siblings ...)
2026-07-27 20:28 ` [PATCH v2 20/64] dmaengine: xilinx-dpdma: kill vchan BH on remove Allen Pais
@ 2026-07-27 20:28 ` Allen Pais
2026-07-27 20:36 ` [PATCH v2 33/64] dmaengine: sun6i: kill vchan BH on teardown Allen Pais
` (13 subsequent siblings)
21 siblings, 0 replies; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:28 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Michal Simek, Abin Joseph, Radhey Shyam Pandey, Sakari Ailus,
linux-arm-kernel
Replace the per-channel tasklet with the shared dma_chan BH helper.
The handler continues to run in softirq context while dmaengine owns
the common scheduling and teardown mechanism.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/xilinx/zynqmp_dma.c | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/drivers/dma/xilinx/zynqmp_dma.c b/drivers/dma/xilinx/zynqmp_dma.c
index f6a812e49ddc..6291126cdc9f 100644
--- a/drivers/dma/xilinx/zynqmp_dma.c
+++ b/drivers/dma/xilinx/zynqmp_dma.c
@@ -207,7 +207,6 @@ struct zynqmp_dma_desc_sw {
* @dev: The dma device
* @irq: Channel IRQ
* @is_dmacoherent: Tells whether dma operations are coherent or not
- * @tasklet: Cleanup work after irq
* @idle : Channel status;
* @desc_size: Size of the low level descriptor
* @err: Channel has errors
@@ -232,7 +231,6 @@ struct zynqmp_dma_chan {
struct device *dev;
int irq;
bool is_dmacoherent;
- struct tasklet_struct tasklet;
bool idle;
size_t desc_size;
bool err;
@@ -735,7 +733,7 @@ static irqreturn_t zynqmp_dma_irq_handler(int irq, void *data)
writel(isr, chan->regs + ZYNQMP_DMA_ISR);
if (status & ZYNQMP_DMA_INT_DONE) {
- tasklet_schedule(&chan->tasklet);
+ dma_chan_schedule_bh(&chan->common);
ret = IRQ_HANDLED;
}
@@ -744,7 +742,7 @@ static irqreturn_t zynqmp_dma_irq_handler(int irq, void *data)
if (status & ZYNQMP_DMA_INT_ERR) {
chan->err = true;
- tasklet_schedule(&chan->tasklet);
+ dma_chan_schedule_bh(&chan->common);
dev_err(chan->dev, "Channel %p has errors\n", chan);
ret = IRQ_HANDLED;
}
@@ -760,11 +758,12 @@ static irqreturn_t zynqmp_dma_irq_handler(int irq, void *data)
/**
* zynqmp_dma_do_tasklet - Schedule completion tasklet
- * @t: Pointer to the ZynqMP DMA channel structure
+ * @c: Pointer to the ZynqMP DMA channel structure
*/
-static void zynqmp_dma_do_tasklet(struct tasklet_struct *t)
+static void zynqmp_dma_do_tasklet(struct dma_chan *c)
{
- struct zynqmp_dma_chan *chan = from_tasklet(chan, t, tasklet);
+ struct zynqmp_dma_chan *chan = container_of(c,
+ struct zynqmp_dma_chan, common);
u32 count;
unsigned long irqflags;
@@ -815,7 +814,7 @@ static void zynqmp_dma_synchronize(struct dma_chan *dchan)
{
struct zynqmp_dma_chan *chan = to_chan(dchan);
- tasklet_kill(&chan->tasklet);
+ dma_chan_kill_bh(&chan->common);
}
/**
@@ -887,7 +886,7 @@ static void zynqmp_dma_chan_remove(struct zynqmp_dma_chan *chan)
if (chan->irq)
devm_free_irq(chan->zdev->dev, chan->irq, chan);
- tasklet_kill(&chan->tasklet);
+ dma_chan_kill_bh(&chan->common);
list_del(&chan->common.device_node);
}
@@ -937,7 +936,7 @@ static int zynqmp_dma_chan_probe(struct zynqmp_dma_device *zdev,
chan->is_dmacoherent = of_property_read_bool(node, "dma-coherent");
zdev->chan = chan;
- tasklet_setup(&chan->tasklet, zynqmp_dma_do_tasklet);
+ dma_chan_init_bh(&chan->common, zynqmp_dma_do_tasklet);
spin_lock_init(&chan->lock);
INIT_LIST_HEAD(&chan->active_list);
INIT_LIST_HEAD(&chan->pending_list);
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 33/64] dmaengine: sun6i: kill vchan BH on teardown
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
` (7 preceding siblings ...)
2026-07-27 20:28 ` [PATCH v2 21/64] dmaengine: zynqmp-dma: switch completion tasklet to dma_chan BH Allen Pais
@ 2026-07-27 20:36 ` Allen Pais
2026-07-27 20:37 ` [PATCH v2 34/64] dmaengine: mtk-cqdma: " Allen Pais
` (12 subsequent siblings)
21 siblings, 0 replies; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:36 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Chen-Yu Tsai, Jernej Skrabec, Samuel Holland, linux-arm-kernel,
linux-sunxi
Use dma_chan_kill_bh() for virt-dma channel cleanup.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/sun6i-dma.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
index a9a254dbf8cb..9289c24dfcfa 100644
--- a/drivers/dma/sun6i-dma.c
+++ b/drivers/dma/sun6i-dma.c
@@ -1097,7 +1097,7 @@ static inline void sun6i_dma_free(struct sun6i_dma_dev *sdev)
struct sun6i_vchan *vchan = &sdev->vchans[i];
list_del(&vchan->vc.chan.device_node);
- tasklet_kill(&vchan->vc.task);
+ dma_chan_kill_bh(&vchan->vc.chan);
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 34/64] dmaengine: mtk-cqdma: kill vchan BH on teardown
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
` (8 preceding siblings ...)
2026-07-27 20:36 ` [PATCH v2 33/64] dmaengine: sun6i: kill vchan BH on teardown Allen Pais
@ 2026-07-27 20:37 ` Allen Pais
2026-07-27 20:39 ` [PATCH v2 39/64] dmaengine: fsl-edma-common: " Allen Pais
` (11 subsequent siblings)
21 siblings, 0 replies; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:37 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Sean Wang, Matthias Brugger, AngeloGioacchino Del Regno,
linux-arm-kernel, linux-mediatek
Use dma_chan_kill_bh() for virt-dma channel cleanup.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/mediatek/mtk-cqdma.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/dma/mediatek/mtk-cqdma.c b/drivers/dma/mediatek/mtk-cqdma.c
index 80791e30aec2..3b0f1d8fa205 100644
--- a/drivers/dma/mediatek/mtk-cqdma.c
+++ b/drivers/dma/mediatek/mtk-cqdma.c
@@ -895,7 +895,7 @@ static void mtk_cqdma_remove(struct platform_device *pdev)
vc = &cqdma->vc[i];
list_del(&vc->vc.chan.device_node);
- tasklet_kill(&vc->vc.task);
+ dma_chan_kill_bh(&vc->vc.chan);
}
/* disable interrupt */
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 39/64] dmaengine: fsl-edma-common: kill vchan BH on teardown
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
` (9 preceding siblings ...)
2026-07-27 20:37 ` [PATCH v2 34/64] dmaengine: mtk-cqdma: " Allen Pais
@ 2026-07-27 20:39 ` Allen Pais
2026-07-27 21:14 ` sashiko-bot
2026-07-27 20:39 ` [PATCH v2 42/64] dmaengine: jz4780: " Allen Pais
` (10 subsequent siblings)
21 siblings, 1 reply; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:39 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Frank Li, imx
Use dma_chan_kill_bh() for virt-dma cleanup.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/fsl-edma-common.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/dma/fsl-edma-common.c b/drivers/dma/fsl-edma-common.c
index bb7531c456df..70b40e60c39a 100644
--- a/drivers/dma/fsl-edma-common.c
+++ b/drivers/dma/fsl-edma-common.c
@@ -915,7 +915,7 @@ void fsl_edma_cleanup_vchan(struct dma_device *dmadev)
list_for_each_entry_safe(chan, _chan,
&dmadev->channels, vchan.chan.device_node) {
list_del(&chan->vchan.chan.device_node);
- tasklet_kill(&chan->vchan.task);
+ dma_chan_kill_bh(&chan->vchan.chan);
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 42/64] dmaengine: jz4780: kill vchan BH on teardown
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
` (10 preceding siblings ...)
2026-07-27 20:39 ` [PATCH v2 39/64] dmaengine: fsl-edma-common: " Allen Pais
@ 2026-07-27 20:39 ` Allen Pais
2026-07-27 20:39 ` [PATCH v2 43/64] dmaengine: pxa_dma: " Allen Pais
` (9 subsequent siblings)
21 siblings, 0 replies; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:39 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Paul Cercueil, linux-mips
Use dma_chan_kill_bh() for virt-dma cleanup.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/dma-jz4780.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/dma/dma-jz4780.c b/drivers/dma/dma-jz4780.c
index 6070dfdb7114..45c1096563e3 100644
--- a/drivers/dma/dma-jz4780.c
+++ b/drivers/dma/dma-jz4780.c
@@ -1019,7 +1019,7 @@ static void jz4780_dma_remove(struct platform_device *pdev)
free_irq(jzdma->irq, jzdma);
for (i = 0; i < jzdma->soc_data->nb_channels; i++)
- tasklet_kill(&jzdma->chan[i].vchan.task);
+ dma_chan_kill_bh(&jzdma->chan[i].vchan.chan);
}
static const struct jz4780_dma_soc_data jz4740_dma_soc_data = {
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 43/64] dmaengine: pxa_dma: kill vchan BH on teardown
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
` (11 preceding siblings ...)
2026-07-27 20:39 ` [PATCH v2 42/64] dmaengine: jz4780: " Allen Pais
@ 2026-07-27 20:39 ` Allen Pais
2026-07-27 20:39 ` [PATCH v2 44/64] dmaengine: mtk-hsdma: " Allen Pais
` (8 subsequent siblings)
21 siblings, 0 replies; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:39 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Daniel Mack, Haojian Zhuang, Robert Jarzmik, linux-arm-kernel
Use dma_chan_kill_bh() for virt-dma cleanup.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/pxa_dma.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/dma/pxa_dma.c b/drivers/dma/pxa_dma.c
index fa2ee0b3e09f..d57e5e0c422c 100644
--- a/drivers/dma/pxa_dma.c
+++ b/drivers/dma/pxa_dma.c
@@ -1215,7 +1215,7 @@ static void pxad_free_channels(struct dma_device *dmadev)
list_for_each_entry_safe(c, cn, &dmadev->channels,
vc.chan.device_node) {
list_del(&c->vc.chan.device_node);
- tasklet_kill(&c->vc.task);
+ dma_chan_kill_bh(&c->vc.chan);
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 44/64] dmaengine: mtk-hsdma: kill vchan BH on teardown
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
` (12 preceding siblings ...)
2026-07-27 20:39 ` [PATCH v2 43/64] dmaengine: pxa_dma: " Allen Pais
@ 2026-07-27 20:39 ` Allen Pais
2026-07-27 20:39 ` [PATCH v2 45/64] dmaengine: mtk-uart-apdma: " Allen Pais
` (7 subsequent siblings)
21 siblings, 0 replies; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:39 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Sean Wang, Matthias Brugger, AngeloGioacchino Del Regno,
linux-arm-kernel, linux-mediatek
Use dma_chan_kill_bh() for virt-dma cleanup.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/mediatek/mtk-hsdma.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/dma/mediatek/mtk-hsdma.c b/drivers/dma/mediatek/mtk-hsdma.c
index a43412ff5edd..1c98fcedf3ac 100644
--- a/drivers/dma/mediatek/mtk-hsdma.c
+++ b/drivers/dma/mediatek/mtk-hsdma.c
@@ -1020,7 +1020,7 @@ static void mtk_hsdma_remove(struct platform_device *pdev)
vc = &hsdma->vc[i];
list_del(&vc->vc.chan.device_node);
- tasklet_kill(&vc->vc.task);
+ dma_chan_kill_bh(&vc->vc.chan);
}
/* Disable DMA interrupt */
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 45/64] dmaengine: mtk-uart-apdma: kill vchan BH on teardown
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
` (13 preceding siblings ...)
2026-07-27 20:39 ` [PATCH v2 44/64] dmaengine: mtk-hsdma: " Allen Pais
@ 2026-07-27 20:39 ` Allen Pais
2026-07-27 20:39 ` [PATCH v2 46/64] dmaengine: imx-sdma: " Allen Pais
` (6 subsequent siblings)
21 siblings, 0 replies; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:39 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Sean Wang, Matthias Brugger, AngeloGioacchino Del Regno,
linux-arm-kernel, linux-mediatek
Use dma_chan_kill_bh() for virt-dma cleanup.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/mediatek/mtk-uart-apdma.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/dma/mediatek/mtk-uart-apdma.c b/drivers/dma/mediatek/mtk-uart-apdma.c
index c269d84d7bd2..4d89927922f5 100644
--- a/drivers/dma/mediatek/mtk-uart-apdma.c
+++ b/drivers/dma/mediatek/mtk-uart-apdma.c
@@ -312,7 +312,7 @@ static void mtk_uart_apdma_free_chan_resources(struct dma_chan *chan)
free_irq(c->irq, chan);
- tasklet_kill(&c->vc.task);
+ dma_chan_kill_bh(&c->vc.chan);
vchan_free_chan_resources(&c->vc);
@@ -463,7 +463,7 @@ static void mtk_uart_apdma_free(struct mtk_uart_apdmadev *mtkd)
struct mtk_chan, vc.chan.device_node);
list_del(&c->vc.chan.device_node);
- tasklet_kill(&c->vc.task);
+ dma_chan_kill_bh(&c->vc.chan);
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 46/64] dmaengine: imx-sdma: kill vchan BH on teardown
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
` (14 preceding siblings ...)
2026-07-27 20:39 ` [PATCH v2 45/64] dmaengine: mtk-uart-apdma: " Allen Pais
@ 2026-07-27 20:39 ` Allen Pais
2026-07-27 21:17 ` sashiko-bot
2026-07-27 20:39 ` [PATCH v2 47/64] dmaengine: loongson1-apb: " Allen Pais
` (5 subsequent siblings)
21 siblings, 1 reply; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:39 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, imx,
linux-arm-kernel
Use dma_chan_kill_bh() for virt-dma cleanup.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/imx-sdma.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c
index 36368835a845..46f66e5f9f30 100644
--- a/drivers/dma/imx-sdma.c
+++ b/drivers/dma/imx-sdma.c
@@ -2399,11 +2399,11 @@ static void sdma_remove(struct platform_device *pdev)
int i;
devm_free_irq(&pdev->dev, sdma->irq, sdma);
- /* Kill the tasklet */
+ /* Kill the channel BH */
for (i = 0; i < MAX_DMA_CHANNELS; i++) {
struct sdma_channel *sdmac = &sdma->channel[i];
- tasklet_kill(&sdmac->vc.task);
+ dma_chan_kill_bh(&sdmac->vc.chan);
sdma_free_chan_resources(&sdmac->vc.chan);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 47/64] dmaengine: loongson1-apb: kill vchan BH on teardown
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
` (15 preceding siblings ...)
2026-07-27 20:39 ` [PATCH v2 46/64] dmaengine: imx-sdma: " Allen Pais
@ 2026-07-27 20:39 ` Allen Pais
2026-07-27 20:39 ` [PATCH v2 48/64] dmaengine: owl-dma: " Allen Pais
` (4 subsequent siblings)
21 siblings, 0 replies; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:39 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Keguang Zhang, linux-mips
Use dma_chan_kill_bh() for virt-dma cleanup.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/loongson/loongson1-apb-dma.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/dma/loongson/loongson1-apb-dma.c b/drivers/dma/loongson/loongson1-apb-dma.c
index 89786cbd20ab..7fc77d8e6db3 100644
--- a/drivers/dma/loongson/loongson1-apb-dma.c
+++ b/drivers/dma/loongson/loongson1-apb-dma.c
@@ -552,7 +552,7 @@ static void ls1x_dma_chan_remove(struct ls1x_dma *dma)
if (chan->vc.chan.device == &dma->ddev) {
list_del(&chan->vc.chan.device_node);
- tasklet_kill(&chan->vc.task);
+ dma_chan_kill_bh(&chan->vc.chan);
}
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 48/64] dmaengine: owl-dma: kill vchan BH on teardown
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
` (16 preceding siblings ...)
2026-07-27 20:39 ` [PATCH v2 47/64] dmaengine: loongson1-apb: " Allen Pais
@ 2026-07-27 20:39 ` Allen Pais
2026-07-27 20:39 ` [PATCH v2 51/64] dmaengine: bcm2835: " Allen Pais
` (3 subsequent siblings)
21 siblings, 0 replies; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:39 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Andreas Färber, Manivannan Sadhasivam, linux-arm-kernel,
linux-actions
Use dma_chan_kill_bh() for virt-dma cleanup.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/owl-dma.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/dma/owl-dma.c b/drivers/dma/owl-dma.c
index 7c80572fc71d..5f2140dc01ce 100644
--- a/drivers/dma/owl-dma.c
+++ b/drivers/dma/owl-dma.c
@@ -1055,7 +1055,7 @@ static inline void owl_dma_free(struct owl_dma *od)
list_for_each_entry_safe(vchan,
next, &od->dma.channels, vc.chan.device_node) {
list_del(&vchan->vc.chan.device_node);
- tasklet_kill(&vchan->vc.task);
+ dma_chan_kill_bh(&vchan->vc.chan);
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 51/64] dmaengine: bcm2835: kill vchan BH on teardown
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
` (17 preceding siblings ...)
2026-07-27 20:39 ` [PATCH v2 48/64] dmaengine: owl-dma: " Allen Pais
@ 2026-07-27 20:39 ` Allen Pais
2026-07-27 20:39 ` [PATCH v2 54/64] dmaengine: st_fdma: use dma_chan_kill_bh Allen Pais
` (2 subsequent siblings)
21 siblings, 0 replies; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:39 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Florian Fainelli, Broadcom internal kernel review list, Ray Jui,
Scott Branden, linux-rpi-kernel, linux-arm-kernel
Use dma_chan_kill_bh() for virt-dma cleanup.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/bcm2835-dma.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/dma/bcm2835-dma.c b/drivers/dma/bcm2835-dma.c
index 06d830d36882..68e86c8d3d43 100644
--- a/drivers/dma/bcm2835-dma.c
+++ b/drivers/dma/bcm2835-dma.c
@@ -829,7 +829,7 @@ static void bcm2835_dma_free(struct bcm2835_dmadev *od)
list_for_each_entry_safe(c, next, &od->ddev.channels,
vc.chan.device_node) {
list_del(&c->vc.chan.device_node);
- tasklet_kill(&c->vc.task);
+ dma_chan_kill_bh(&c->vc.chan);
}
dma_unmap_page_attrs(od->ddev.dev, od->zero_page, PAGE_SIZE,
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 54/64] dmaengine: st_fdma: use dma_chan_kill_bh
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
` (18 preceding siblings ...)
2026-07-27 20:39 ` [PATCH v2 51/64] dmaengine: bcm2835: " Allen Pais
@ 2026-07-27 20:39 ` Allen Pais
2026-07-27 20:39 ` [PATCH v2 62/64] dmaengine: hidma: defer callbacks via channel BH Allen Pais
[not found] ` <cover.1786384168.git.allen.lkml@gmail.com>
21 siblings, 0 replies; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:39 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Patrice Chotard, linux-arm-kernel
virt-dma now dispatches completion callbacks through per-channel BH
work instead of its tasklet. Cancel that work during teardown before
channel storage is released.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/st_fdma.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/dma/st_fdma.c b/drivers/dma/st_fdma.c
index d9547017f3bd..d4cff851e1d5 100644
--- a/drivers/dma/st_fdma.c
+++ b/drivers/dma/st_fdma.c
@@ -733,7 +733,7 @@ static void st_fdma_free(struct st_fdma_dev *fdev)
for (i = 0; i < fdev->nr_channels; i++) {
fchan = &fdev->chans[i];
list_del(&fchan->vchan.chan.device_node);
- tasklet_kill(&fchan->vchan.task);
+ dma_chan_kill_bh(&fchan->vchan.chan);
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v2 62/64] dmaengine: hidma: defer callbacks via channel BH
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
` (19 preceding siblings ...)
2026-07-27 20:39 ` [PATCH v2 54/64] dmaengine: st_fdma: use dma_chan_kill_bh Allen Pais
@ 2026-07-27 20:39 ` Allen Pais
[not found] ` <cover.1786384168.git.allen.lkml@gmail.com>
21 siblings, 0 replies; 63+ messages in thread
From: Allen Pais @ 2026-07-27 20:39 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Sinan Kaya, linux-arm-kernel, linux-arm-msm
Move descriptor callback processing out of the low-level completion
path and schedule it through per-channel BH work. Drain that work while
freeing channels so callbacks cannot outlive channel storage.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/qcom/hidma.c | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/drivers/dma/qcom/hidma.c b/drivers/dma/qcom/hidma.c
index c939635be21d..1f875a499ab3 100644
--- a/drivers/dma/qcom/hidma.c
+++ b/drivers/dma/qcom/hidma.c
@@ -90,7 +90,13 @@ static inline struct hidma_chan *to_hidma_chan(struct dma_chan *dmach)
static void hidma_free(struct hidma_dev *dmadev)
{
- INIT_LIST_HEAD(&dmadev->ddev.channels);
+ struct hidma_chan *mchan, *next;
+
+ list_for_each_entry_safe(mchan, next, &dmadev->ddev.channels,
+ chan.device_node) {
+ dma_chan_kill_bh(&mchan->chan);
+ list_del(&mchan->chan.device_node);
+ }
}
static unsigned int nr_desc_prm;
@@ -155,6 +161,13 @@ static void hidma_process_completed(struct hidma_chan *mchan)
}
}
+static void hidma_chan_bh(struct dma_chan *chan)
+{
+ struct hidma_chan *mchan = to_hidma_chan(chan);
+
+ hidma_process_completed(mchan);
+}
+
/*
* Called once for each submitted descriptor.
* PM is locked once for each descriptor that is currently
@@ -181,7 +194,7 @@ static void hidma_callback(void *data)
}
spin_unlock_irqrestore(&mchan->lock, irqflags);
- hidma_process_completed(mchan);
+ dma_chan_schedule_bh(&mchan->chan);
if (queued) {
pm_runtime_mark_last_busy(dmadev->ddev.dev);
@@ -203,6 +216,7 @@ static int hidma_chan_init(struct hidma_dev *dmadev, u32 dma_sig)
mchan->dmadev = dmadev;
mchan->chan.device = ddev;
dma_cookie_init(&mchan->chan);
+ dma_chan_init_bh(&mchan->chan, hidma_chan_bh);
INIT_LIST_HEAD(&mchan->free);
INIT_LIST_HEAD(&mchan->prepared);
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* Re: [PATCH v2 08/64] dmaengine: imx-dma: flip per-chan tasklet to dma_chan BH
2026-07-27 20:28 ` [PATCH v2 08/64] dmaengine: imx-dma: flip per-chan tasklet " Allen Pais
@ 2026-07-27 20:57 ` sashiko-bot
0 siblings, 0 replies; 63+ messages in thread
From: sashiko-bot @ 2026-07-27 20:57 UTC (permalink / raw)
To: Allen Pais; +Cc: vkoul, Frank.Li, dmaengine, imx
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
Pre-existing issues:
- [High] The per-channel watchdog timer is not synchronously canceled during driver teardown, leading to a Use-After-Free.
- [High] Pending BH works are not canceled on the probe error path, causing a Use-After-Free.
- [High] Early interrupts cause a NULL pointer dereference because IRQs are requested before channel lists are initialized.
- [High] Descriptor state is accessed after being placed on the free list and unlocking the spinlock, leading to a TOCTOU race with prep functions.
- [High] Channel state and lists are read and modified without proper lock protection in descriptor preparation functions.
- [High] The driver frees DMA descriptors in `imxdma_free_chan_resources` without synchronizing with the completion BH, causing a Use-After-Free.
--
--- Patch [8]: [PATCH v2 08/64] dmaengine: imx-dma: flip per-chan tasklet to dma_chan BH ---
commit 6c1578dd7a98a9ceebdc129c266b46d0c2bdf6e3
Author: Allen Pais <allen.lkml@gmail.com>
dmaengine: imx-dma: flip per-chan tasklet to dma_chan BH
Replace the per-channel tasklet with the shared dma_chan BH helper.
The handler continues to run in softirq context while dmaengine owns
the common scheduling and teardown mechanism.
> diff --git a/drivers/dma/imx-dma.c b/drivers/dma/imx-dma.c
> index 81c6276436f81..3ff0bfbfa5ffa 100644
> --- a/drivers/dma/imx-dma.c
> +++ b/drivers/dma/imx-dma.c
[ ... ]
> @@ -592,9 +591,10 @@ static int imxdma_xfer_desc(struct imxdma_desc *d)
> return 0;
> }
>
> -static void imxdma_tasklet(struct tasklet_struct *t)
> +static void imxdma_tasklet(struct dma_chan *chan)
> {
> - struct imxdma_channel *imxdmac = from_tasklet(imxdmac, t, dma_tasklet);
> + struct imxdma_channel *imxdmac = container_of(chan, struct imxdma_channel,
> + chan);
> struct imxdma_engine *imxdma = imxdmac->imxdma;
> struct imxdma_desc *desc, *next_desc;
> unsigned long flags;
[Severity: High]
This is a pre-existing issue, but is it safe to invoke the callback after
releasing the spinlock and moving the descriptor to the free list?
At the end of imxdma_tasklet(), the lock is released and the callback is
subsequently invoked directly from the descriptor:
out:
spin_unlock_irqrestore(&imxdma->lock, flags);
dmaengine_desc_get_callback_invoke(&desc->desc, NULL);
}
Since the descriptor is already on the free list, couldn't a concurrent
call to imxdma_prep_dma_memcpy() pick this exact descriptor from ld_free
and clear desc->desc.callback = NULL, leading to execution of a NULL or
corrupted callback?
[Severity: High]
This isn't a bug introduced by this patch, but how are the channel lists
synchronized against concurrent preparation?
The completion BH actively splices and modifies ld_free and ld_active while
holding imxdma->lock. However, prep functions like imxdma_prep_dma_memcpy()
read ld_free and call list_first_entry() without acquiring the channel lock:
drivers/dma/imx-dma.c:imxdma_prep_dma_memcpy() {
...
if (list_empty(&imxdmac->ld_free) ||
imxdma_chan_is_doing_cyclic(imxdmac))
return NULL;
desc = list_first_entry(&imxdmac->ld_free, struct imxdma_desc, node);
...
}
Could this lead to linked list corruption or NULL pointer dereferences if
clients actively submit transfers while completions fire?
[ ... ]
> @@ -1142,7 +1142,7 @@ static int __init imxdma_probe(struct platform_device *pdev)
> INIT_LIST_HEAD(&imxdmac->ld_free);
> INIT_LIST_HEAD(&imxdmac->ld_active);
>
> - tasklet_setup(&imxdmac->dma_tasklet, imxdma_tasklet);
> + dma_chan_init_bh(&imxdmac->chan, imxdma_tasklet);
> imxdmac->chan.device = &imxdma->dma_device;
> dma_cookie_init(&imxdmac->chan);
> imxdmac->channel = i;
[Severity: High]
This is a pre-existing issue, but what happens if the probe fails after
this point?
Looking at the error path later in imxdma_probe():
err_of_dma_controller:
dma_async_device_unregister(&imxdma->dma_device);
disable_dma_ahb_clk:
clk_disable_unprepare(imxdma->dma_ahb);
disable_dma_ipg_clk:
clk_disable_unprepare(imxdma->dma_ipg);
return ret;
It exits without calling dma_chan_kill_bh() or imxdma_free_irq(). If a
spurious or lingering hardware interrupt triggers before
dma_async_device_register() completes, dma_irq_handler() schedules the BH.
If a subsequent initialization step fails, devres frees the device memory
but the scheduled BH is never canceled, which might lead to a use-after-free.
[Severity: High]
This isn't a bug introduced by this patch, but does the early interrupt
setup risk a NULL pointer dereference?
The IRQ is requested earlier in imxdma_probe():
ret = devm_request_irq(&pdev->dev, irq,
dma_irq_handler, 0, "DMA", imxdma);
This happens well before the list heads like ld_active are initialized in
the loop shown in the diff above. If an interrupt fires early,
dma_irq_handle_channel() checks the list:
if (list_empty(&imxdmac->ld_active)) {
spin_unlock_irqrestore(&imxdma->lock, flags);
goto out;
}
Since memory is 0-initialized, ld_active.next is NULL. This makes
list_empty() evaluate to false, leading directly to a crash via
list_first_entry() right below it.
[ ... ]
> @@ -1211,7 +1211,7 @@ static void imxdma_free_irq(struct platform_device *pdev, struct imxdma_engine *
> if (!is_imx1_dma(imxdma))
> disable_irq(imxdmac->irq);
>
> - tasklet_kill(&imxdmac->dma_tasklet);
> + dma_chan_kill_bh(&imxdmac->chan);
> }
> }
[Severity: High]
This is a pre-existing issue, but is it safe to kill the BH here without
also stopping the watchdog timer?
This function stops the BH but omits timer_shutdown_sync(&imxdmac->watchdog),
leaving it active. If the driver is unbound via sysfs while a transfer is
active, imxdma_remove() invokes imxdma_free_irq() and then unregisters
and frees the engine. Couldn't the still-armed timer fire on freed memory
and schedule the destroyed BH workqueue?
[Severity: High]
This isn't a bug introduced by this patch, but does
imxdma_free_chan_resources() safely synchronize with this BH?
When a channel is released, descriptors are indiscriminately freed without
stopping or waiting for the BH to finish:
drivers/dma/imx-dma.c:imxdma_free_chan_resources() {
...
list_for_each_entry_safe(desc, _desc, &imxdmac->ld_free, node) {
kfree(desc);
imxdmac->descs_allocated--;
}
...
}
Since the driver lacks a device_synchronize callback and does not call
dma_chan_kill_bh() during channel release, could the completion BH be
actively running on another CPU trying to invoke the callback for a
descriptor that is being freed?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1785183549.git.allen.lkml@gmail.com?part=8
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PATCH v2 13/64] dmaengine: mxs-dma: use dma_chan BH scheduling
2026-07-27 20:28 ` [PATCH v2 13/64] dmaengine: mxs-dma: use dma_chan BH scheduling Allen Pais
@ 2026-07-27 20:59 ` sashiko-bot
0 siblings, 0 replies; 63+ messages in thread
From: sashiko-bot @ 2026-07-27 20:59 UTC (permalink / raw)
To: Allen Pais; +Cc: dmaengine, Frank.Li, vkoul, imx
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing synchronization/flushing of bottom-half work during channel teardown leads to a use-after-free of client resources.
- [High] Requesting IRQ before clock enable and descriptor initialization causes a kernel panic on pending interrupt.
- [High] The driver allocates the DMA engine state with devm_kzalloc but lacks a device_release callback, risking UAF on unbind.
--
--- Patch [13]: [PATCH v2 13/64] dmaengine: mxs-dma: use dma_chan BH scheduling ---
commit cc33b3c421743dc24481c221e9955791a7689087
Author: Allen Pais <allen.lkml@gmail.com>
dmaengine: mxs-dma: use dma_chan BH scheduling
Replace the per-channel tasklet with the shared dma_chan BH helper.
The handler continues to run in softirq context while dmaengine owns
the common scheduling and teardown mechanism.
> The handler continues to run in softirq context while dmaengine owns
> the common scheduling and teardown mechanism.
[Severity: High]
This is a pre-existing issue, but since the commit message notes that the
teardown mechanism is being updated, does mxs_dma_free_chan_resources()
need to call dma_chan_kill_bh() to prevent use-after-free conditions?
If a client releases the channel concurrently with an interrupt,
mxs_dma_free_chan_resources() currently returns without flushing pending
work:
drivers/dma/mxs-dma.c:mxs_dma_free_chan_resources() {
...
mxs_dma_disable_chan(chan);
free_irq(mxs_chan->chan_irq, mxs_dma);
}
Could this allow the asynchronous work to outlive the channel allocation
and dereference a freed client callback when mxs_dma_tasklet() runs?
> diff --git a/drivers/dma/mxs-dma.c b/drivers/dma/mxs-dma.c
> index 7acb3d29dad30..8cecc0add82dc 100644
> --- a/drivers/dma/mxs-dma.c
> +++ b/drivers/dma/mxs-dma.c
[ ... ]
> @@ -385,8 +385,8 @@ static irqreturn_t mxs_dma_int_handler(int irq, void *dev_id)
> dma_cookie_complete(&mxs_chan->desc);
> }
>
> - /* schedule tasklet on this channel */
> - tasklet_schedule(&mxs_chan->tasklet);
> + /* schedule BH on this channel */
> + dma_chan_schedule_bh(&mxs_chan->chan);
>
> return IRQ_HANDLED;
> }
[Severity: High]
This is a pre-existing issue, but can this handler be triggered before the
hardware clock is enabled and the descriptor is initialized?
Looking at mxs_dma_alloc_chan_resources(), the IRQ is requested before the
clock is enabled:
drivers/dma/mxs-dma.c:mxs_dma_alloc_chan_resources() {
...
ret = request_irq(mxs_chan->chan_irq, mxs_dma_int_handler,
0, "mxs-dma", mxs_dma);
if (ret)
goto err_irq;
ret = clk_prepare_enable(mxs_dma->clk);
...
}
If a spurious or bootloader-pending interrupt fires immediately, could
mxs_dma_int_handler() process a DMA_COMPLETE status and hit a BUG_ON when
it calls dma_cookie_complete(&mxs_chan->desc) because the cookie is
still 0?
[ ... ]
> @@ -780,7 +780,7 @@ static int mxs_dma_probe(struct platform_device *pdev)
> mxs_chan->chan.device = &mxs_dma->dma_device;
> dma_cookie_init(&mxs_chan->chan);
>
> - tasklet_setup(&mxs_chan->tasklet, mxs_dma_tasklet);
> + dma_chan_init_bh(&mxs_chan->chan, mxs_dma_tasklet);
>
>
> /* Add the channel to mxs_chan list */
[Severity: High]
This is a pre-existing issue, but is the driver state vulnerable to a
use-after-free if the device is unbound?
The driver allocates mxs_dma using devm_kzalloc() but appears to register
the async device without providing a device_release callback:
drivers/dma/mxs-dma.c:mxs_dma_probe() {
...
mxs_dma = devm_kzalloc(dev, sizeof(*mxs_dma), GFP_KERNEL);
if (!mxs_dma)
return -ENOMEM;
...
ret = dmaenginem_async_device_register(&mxs_dma->dma_device);
...
}
If the driver is unbound via sysfs while a client still holds a reference
to a DMA channel, will devres immediately free mxs_dma, causing a panic
when the client later accesses the channel?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1785183549.git.allen.lkml@gmail.com?part=13
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PATCH v2 39/64] dmaengine: fsl-edma-common: kill vchan BH on teardown
2026-07-27 20:39 ` [PATCH v2 39/64] dmaengine: fsl-edma-common: " Allen Pais
@ 2026-07-27 21:14 ` sashiko-bot
0 siblings, 0 replies; 63+ messages in thread
From: sashiko-bot @ 2026-07-27 21:14 UTC (permalink / raw)
To: Allen Pais; +Cc: vkoul, imx, Frank.Li, dmaengine
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Bypassed DMA channel teardown leading to resource leaks and Use-After-Free via dangling sysfs nodes.
--
--- Patch [39]: [PATCH v2 39/64] dmaengine: fsl-edma-common: kill vchan BH on teardown ---
commit 72f422b1de100bcd764808c0c9305bd937347a17
Author: Allen Pais <allen.lkml@gmail.com>
dmaengine: fsl-edma-common: kill vchan BH on teardown
This commit updates virt-dma cleanup to use dma_chan_kill_bh() instead of
tasklet_kill() when tearing down channels in fsl_edma_cleanup_vchan().
> diff --git a/drivers/dma/fsl-edma-common.c b/drivers/dma/fsl-edma-common.c
> index bb7531c456dfa..70b40e60c39a0 100644
> --- a/drivers/dma/fsl-edma-common.c
> +++ b/drivers/dma/fsl-edma-common.c
> @@ -915,7 +915,7 @@ void fsl_edma_cleanup_vchan(struct dma_device *dmadev)
> list_for_each_entry_safe(chan, _chan,
> &dmadev->channels, vchan.chan.device_node) {
> list_del(&chan->vchan.chan.device_node);
[Severity: Critical]
This is a pre-existing issue, but does unlinking the channel here bypass
the core DMA engine channel teardown?
During driver removal (for example, in fsl_edma_remove() or
mcf_edma_remove()), calling list_del() before dma_async_device_unregister()
means the core unregister function will see an empty dmadev->channels list.
If the channels are unlinked prematurely, dma_async_device_unregister()
skips __dma_async_device_channel_unregister() for all channels. Can this
leave dangling sysfs nodes active and leak resources like IDAs or percpu
memory, potentially leading to a use-after-free if devres subsequently
frees the underlying channel structures while the sysfs files remain
accessible?
(Note: This same unlinking pattern before unregister also appears to exist
in mtk_cqdma_remove() and sprd_dma_remove()).
> - tasklet_kill(&chan->vchan.task);
> + dma_chan_kill_bh(&chan->vchan.chan);
> }
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1785183549.git.allen.lkml@gmail.com?part=39
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PATCH v2 46/64] dmaengine: imx-sdma: kill vchan BH on teardown
2026-07-27 20:39 ` [PATCH v2 46/64] dmaengine: imx-sdma: " Allen Pais
@ 2026-07-27 21:17 ` sashiko-bot
0 siblings, 0 replies; 63+ messages in thread
From: sashiko-bot @ 2026-07-27 21:17 UTC (permalink / raw)
To: Allen Pais; +Cc: Frank.Li, imx, vkoul, dmaengine
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Pre-existing issues:
- [High] Kernel OOPS on module removal due to uninitialized list head in channel 0.
- [High] Acquiring an uninitialized spinlock for channel 0 during teardown causes a kernel BUG.
- [High] Accessing hardware registers while clocks are disabled causes a synchronous external abort.
- [High] Unbalanced clock disables in `sdma_remove` trigger refcount underflow warnings.
- [High] Use-After-Free race condition in `sdma_update_channel_loop` during concurrent termination.
--
--- Patch [46]: [PATCH v2 46/64] dmaengine: imx-sdma: kill vchan BH on teardown ---
commit 3996df4e4fdc79870fd89eac795a9295e1141104
Author: Allen Pais <allen.lkml@gmail.com>
dmaengine: imx-sdma: kill vchan BH on teardown
This commit modifies the virt-dma cleanup path to use dma_chan_kill_bh()
during driver teardown.
> --- a/drivers/dma/imx-sdma.c
> +++ b/drivers/dma/imx-sdma.c
> @@ -2399,11 +2399,11 @@ static void sdma_remove(struct platform_device *pdev)
> int i;
>
> devm_free_irq(&pdev->dev, sdma->irq, sdma);
> - /* Kill the tasklet */
> + /* Kill the channel BH */
> for (i = 0; i < MAX_DMA_CHANNELS; i++) {
> struct sdma_channel *sdmac = &sdma->channel[i];
>
> - tasklet_kill(&sdmac->vc.task);
> + dma_chan_kill_bh(&sdmac->vc.chan);
> sdma_free_chan_resources(&sdmac->vc.chan);
[Severity: High]
This is a pre-existing issue, but does iterating over all channels here lead
to a null pointer dereference for channel 0?
Channel 0 intentionally skips vchan_init() during probe, leaving its list
heads 0-initialized. When sdma_free_chan_resources() calls
vchan_synchronize() for channel 0, it executes:
drivers/dma/virt-dma.h:vchan_synchronize() {
...
list_splice_tail_init(&vc->desc_terminated, &head);
Could this evaluate an empty list on the null next pointer and cause a panic?
[Severity: High]
This isn't a bug introduced by this patch, but does calling
sdma_free_chan_resources() on channel 0 also attempt to acquire an
uninitialized spinlock?
During teardown, sdma_terminate_all() executes:
drivers/dma/imx-sdma.c:sdma_terminate_all() {
...
spin_lock_irqsave(&sdmac->vc.lock, flags);
Since vchan_init() is skipped for channel 0, the lock remains uninitialized
from devm_kzalloc. Can this result in a bad magic system crash?
[Severity: High]
This is a pre-existing issue, but does writing to the hardware registers
during this loop cause a synchronous external abort?
When iterating over all channels, unallocated channels (or when all channels
are inactive) will have the ipg and ahb clocks disabled.
sdma_free_chan_resources() calls sdma_terminate_all(), which invokes
sdma_disable_channel() doing:
drivers/dma/imx-sdma.c:sdma_disable_channel() {
...
writel_relaxed(BIT(channel), sdma->regs + SDMA_H_STATSTOP);
Writing to an i.MX SDMA register while the IP block clocks are gated
typically causes a hardware bus fault. Is there a way to verify the clocks
are enabled before this write?
[Severity: High]
This isn't a bug introduced by this patch, but does this cleanup loop
cause clock refcount underflow warnings?
sdma_free_chan_resources() unconditionally calls:
drivers/dma/imx-sdma.c:sdma_free_chan_resources() {
...
clk_disable(sdma->clk_ipg);
clk_disable(sdma->clk_ahb);
Since clk_enable() is only invoked dynamically when a client requests a
channel, calling disable on unallocated channels seems unbalanced. Could this
break clock tracking?
[Severity: High]
This is a pre-existing issue, but is there a use-after-free race condition
in sdma_update_channel_loop() during concurrent termination?
sdma_update_channel_loop() drops the lock to invoke the callback:
drivers/dma/imx-sdma.c:sdma_update_channel_loop() {
...
spin_unlock(&sdmac->vc.lock);
dmaengine_desc_get_callback_invoke(&desc->vd.tx, NULL);
spin_lock(&sdmac->vc.lock);
/* Assign buffer ownership to SDMA */
bd->mode.status |= BD_DONE;
If a concurrent thread calls dmaengine_terminate_sync(), it can take the
lock, move the active descriptor to the terminated list, and schedule the
terminate_worker which frees the descriptor memory.
When the IRQ thread reacquires the lock, it writes to bd->mode.status. Since
bd points inside the freed descriptor, does this overwrite freed memory?
> }
>
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1785183549.git.allen.lkml@gmail.com?part=46
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PATCH v2 17/64] dmaengine: ste_dma40: convert per-channel tasklet to dma_chan BH
2026-07-27 20:28 ` [PATCH v2 17/64] dmaengine: ste_dma40: convert per-channel tasklet to dma_chan BH Allen Pais
@ 2026-07-28 19:33 ` Linus Walleij
0 siblings, 0 replies; 63+ messages in thread
From: Linus Walleij @ 2026-07-28 19:33 UTC (permalink / raw)
To: Allen Pais
Cc: Vinod Koul, Frank Li, dmaengine, linux-kernel, Arnd Bergmann,
Kees Cook, linux-arm-kernel
On Mon, Jul 27, 2026 at 10:32 PM Allen Pais <allen.lkml@gmail.com> wrote:
> Replace the per-channel tasklet with the shared dma_chan BH helper.
> The handler continues to run in softirq context while dmaengine owns
> the common scheduling and teardown mechanism.
>
> Signed-off-by: Allen Pais <allen.lkml@gmail.com>
I like it.
Reviewed-by: Linus Walleij <linusw@kernel.org>
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 63+ messages in thread
* [PATCH v3 01/34] dmaengine: add tasklet-backed channel BH helpers
[not found] ` <cover.1786384168.git.allen.lkml@gmail.com>
@ 2026-08-10 18:09 ` Allen Pais
2026-08-10 18:30 ` sashiko-bot
` (2 more replies)
2026-08-10 18:09 ` [PATCH v3 03/34] dmaengine: apple-admac: use dmaengine BH callback Allen Pais
` (8 subsequent siblings)
9 siblings, 3 replies; 63+ messages in thread
From: Allen Pais @ 2026-08-10 18:09 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Florian Fainelli, Ray Jui, Scott Branden,
Broadcom internal kernel review list, Lars-Peter Clausen,
Paul Cercueil, Eugeniy Paltsev, Manivannan Sadhasivam, Zhou Wang,
Longfang Liu, Andy Shevchenko, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Keguang Zhang, Sean Wang,
Matthias Brugger, AngeloGioacchino Del Regno, Andreas Färber,
Daniel Mack, Haojian Zhuang, Robert Jarzmik, Paul Walmsley,
Samuel Holland, Orson Zhai, Baolin Wang, Chunyan Zhang,
Patrice Chotard, Chen-Yu Tsai, Jernej Skrabec, Laxman Dewangan,
Jon Hunter, Thierry Reding, Vignesh Raghavendra,
Bartosz Golaszewski, Konrad Dybcio, Bjorn Andersson,
Kuldeep Singh, Stephan Gerhold, linux-rpi-kernel,
linux-arm-kernel, linux-mips, imx, linux-mediatek, linux-actions,
linux-arm-msm, linux-riscv, linux-sunxi, linux-tegra
DMAengine drivers commonly use a per-channel tasklet to invoke client
callbacks. Add helpers that initialize, schedule, and kill a channel
bottom half, with an initial tasklet-backed implementation that preserves
the existing execution context.
Convert virt-dma to the new API and remove its private tasklet. Update all
drivers that directly kill or override that tasklet in the same change so
no stale users remain. While touching the completion handler, avoid forming
a result pointer from a NULL cyclic descriptor.
This establishes a backend-independent API before changing how channel
bottom halves are dispatched.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/bcm2835-dma.c | 2 +-
drivers/dma/dma-axi-dmac.c | 10 +++--
drivers/dma/dma-jz4780.c | 2 +-
drivers/dma/dmaengine.c | 42 +++++++++++++++++++
.../dma/dw-axi-dmac/dw-axi-dmac-platform.c | 2 +-
drivers/dma/dw-edma/dw-edma-core.c | 2 +-
drivers/dma/fsl-edma-common.c | 2 +-
drivers/dma/fsl-qdma.c | 2 +-
drivers/dma/hisi_dma.c | 2 +-
drivers/dma/hsu/hsu.c | 2 +-
drivers/dma/idma64.c | 4 +-
drivers/dma/img-mdc-dma.c | 2 +-
drivers/dma/imx-sdma.c | 4 +-
drivers/dma/k3dma.c | 2 +-
drivers/dma/loongson/loongson1-apb-dma.c | 2 +-
drivers/dma/mediatek/mtk-cqdma.c | 2 +-
drivers/dma/mediatek/mtk-hsdma.c | 2 +-
drivers/dma/mediatek/mtk-uart-apdma.c | 4 +-
drivers/dma/owl-dma.c | 2 +-
drivers/dma/pxa_dma.c | 2 +-
drivers/dma/qcom/bam_dma.c | 4 +-
drivers/dma/qcom/qcom_adm.c | 4 +-
drivers/dma/sa11x0-dma.c | 2 +-
drivers/dma/sf-pdma/sf-pdma.c | 2 +-
drivers/dma/sprd-dma.c | 2 +-
drivers/dma/st_fdma.c | 2 +-
drivers/dma/sun6i-dma.c | 2 +-
drivers/dma/tegra186-gpc-dma.c | 2 +-
drivers/dma/tegra210-adma.c | 2 +-
drivers/dma/ti/edma.c | 2 +-
drivers/dma/ti/k3-udma.c | 12 +++---
drivers/dma/ti/omap-dma.c | 2 +-
drivers/dma/virt-dma.c | 12 +++---
drivers/dma/virt-dma.h | 7 ++--
include/linux/dmaengine.h | 28 +++++++++++++
35 files changed, 125 insertions(+), 54 deletions(-)
diff --git a/drivers/dma/bcm2835-dma.c b/drivers/dma/bcm2835-dma.c
index 06d830d36882..c8add249dbfb 100644
--- a/drivers/dma/bcm2835-dma.c
+++ b/drivers/dma/bcm2835-dma.c
@@ -829,7 +829,7 @@ static void bcm2835_dma_free(struct bcm2835_dmadev *od)
list_for_each_entry_safe(c, next, &od->ddev.channels,
vc.chan.device_node) {
list_del(&c->vc.chan.device_node);
- tasklet_kill(&c->vc.task);
+ dmaengine_kill_bh(&c->vc.chan);
}
dma_unmap_page_attrs(od->ddev.dev, od->zero_page, PAGE_SIZE,
diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c
index d47ff27e1408..d245d04c3a27 100644
--- a/drivers/dma/dma-axi-dmac.c
+++ b/drivers/dma/dma-axi-dmac.c
@@ -1195,9 +1195,11 @@ static int axi_dmac_detect_caps(struct axi_dmac *dmac, unsigned int version)
return 0;
}
-static void axi_dmac_tasklet_kill(void *task)
+static void axi_dmac_kill_bh(void *data)
{
- tasklet_kill(task);
+ struct dma_chan *chan = data;
+
+ dmaengine_kill_bh(chan);
}
static void axi_dmac_free_dma_controller(void *of_node)
@@ -1302,8 +1304,8 @@ static int axi_dmac_probe(struct platform_device *pdev)
* Put the action in here so it get's done before unregistering the DMA
* device.
*/
- ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_tasklet_kill,
- &dmac->chan.vchan.task);
+ ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_kill_bh,
+ &dmac->chan.vchan.chan);
if (ret)
return ret;
diff --git a/drivers/dma/dma-jz4780.c b/drivers/dma/dma-jz4780.c
index 6070dfdb7114..738801501e29 100644
--- a/drivers/dma/dma-jz4780.c
+++ b/drivers/dma/dma-jz4780.c
@@ -1019,7 +1019,7 @@ static void jz4780_dma_remove(struct platform_device *pdev)
free_irq(jzdma->irq, jzdma);
for (i = 0; i < jzdma->soc_data->nb_channels; i++)
- tasklet_kill(&jzdma->chan[i].vchan.task);
+ dmaengine_kill_bh(&jzdma->chan[i].vchan.chan);
}
static const struct jz4780_dma_soc_data jz4740_dma_soc_data = {
diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c
index 9049171df857..d8fc7eb71b48 100644
--- a/drivers/dma/dmaengine.c
+++ b/drivers/dma/dmaengine.c
@@ -1428,6 +1428,48 @@ static void dmaengine_destroy_unmap_pool(void)
}
}
+static void dma_chan_bh_entry(struct tasklet_struct *tasklet)
+{
+ struct dma_chan *chan = from_tasklet(chan, tasklet, bh_tasklet);
+ dmaengine_bh_work_fn fn = READ_ONCE(chan->bh_work_fn);
+
+ if (fn)
+ fn(chan);
+}
+
+void dmaengine_init_bh(struct dma_chan *chan, dmaengine_bh_work_fn fn)
+{
+ if (WARN_ON(!fn))
+ return;
+
+ if (WARN_ON(chan->bh_work_initialized))
+ return;
+
+ chan->bh_work_fn = fn;
+ tasklet_setup(&chan->bh_tasklet, dma_chan_bh_entry);
+ chan->bh_work_initialized = true;
+}
+EXPORT_SYMBOL_GPL(dmaengine_init_bh);
+
+bool dmaengine_schedule_bh(struct dma_chan *chan)
+{
+ if (WARN_ON(!chan->bh_work_initialized))
+ return false;
+
+ tasklet_schedule(&chan->bh_tasklet);
+ return true;
+}
+EXPORT_SYMBOL_GPL(dmaengine_schedule_bh);
+
+void dmaengine_kill_bh(struct dma_chan *chan)
+{
+ if (!chan->bh_work_initialized)
+ return;
+
+ tasklet_kill(&chan->bh_tasklet);
+}
+EXPORT_SYMBOL_GPL(dmaengine_kill_bh);
+
static int __init dmaengine_init_unmap_pool(void)
{
int i;
diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
index bcefaff03b5c..a2b688e7f47e 100644
--- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
+++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
@@ -1663,7 +1663,7 @@ static void dw_remove(struct platform_device *pdev)
list_for_each_entry_safe(chan, _chan, &dw->dma.channels,
vc.chan.device_node) {
list_del(&chan->vc.chan.device_node);
- tasklet_kill(&chan->vc.task);
+ dmaengine_kill_bh(&chan->vc.chan);
}
}
diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
index 89a4c498a17b..ade866fba2ad 100644
--- a/drivers/dma/dw-edma/dw-edma-core.c
+++ b/drivers/dma/dw-edma/dw-edma-core.c
@@ -1170,7 +1170,7 @@ int dw_edma_remove(struct dw_edma_chip *chip)
dma_async_device_unregister(&dw->dma);
list_for_each_entry_safe(chan, _chan, &dw->dma.channels,
vc.chan.device_node) {
- tasklet_kill(&chan->vc.task);
+ dmaengine_kill_bh(&chan->vc.chan);
list_del(&chan->vc.chan.device_node);
}
diff --git a/drivers/dma/fsl-edma-common.c b/drivers/dma/fsl-edma-common.c
index bb7531c456df..90ae678c68d4 100644
--- a/drivers/dma/fsl-edma-common.c
+++ b/drivers/dma/fsl-edma-common.c
@@ -915,7 +915,7 @@ void fsl_edma_cleanup_vchan(struct dma_device *dmadev)
list_for_each_entry_safe(chan, _chan,
&dmadev->channels, vchan.chan.device_node) {
list_del(&chan->vchan.chan.device_node);
- tasklet_kill(&chan->vchan.task);
+ dmaengine_kill_bh(&chan->vchan.chan);
}
}
diff --git a/drivers/dma/fsl-qdma.c b/drivers/dma/fsl-qdma.c
index df843fad0ece..b0ace8bad498 100644
--- a/drivers/dma/fsl-qdma.c
+++ b/drivers/dma/fsl-qdma.c
@@ -1255,7 +1255,7 @@ static void fsl_qdma_cleanup_vchan(struct dma_device *dmadev)
list_for_each_entry_safe(chan, _chan,
&dmadev->channels, vchan.chan.device_node) {
list_del(&chan->vchan.chan.device_node);
- tasklet_kill(&chan->vchan.task);
+ dmaengine_kill_bh(&chan->vchan.chan);
}
}
diff --git a/drivers/dma/hisi_dma.c b/drivers/dma/hisi_dma.c
index 28bf818f9aa6..10eb98350b9f 100644
--- a/drivers/dma/hisi_dma.c
+++ b/drivers/dma/hisi_dma.c
@@ -720,7 +720,7 @@ static void hisi_dma_disable_qps(struct hisi_dma_dev *hdma_dev)
for (i = 0; i < hdma_dev->chan_num; i++) {
hisi_dma_disable_qp(hdma_dev, i);
- tasklet_kill(&hdma_dev->chan[i].vc.task);
+ dmaengine_kill_bh(&hdma_dev->chan[i].vc.chan);
}
}
diff --git a/drivers/dma/hsu/hsu.c b/drivers/dma/hsu/hsu.c
index f62d60d7bc6b..315d0ebecd57 100644
--- a/drivers/dma/hsu/hsu.c
+++ b/drivers/dma/hsu/hsu.c
@@ -500,7 +500,7 @@ int hsu_dma_remove(struct hsu_dma_chip *chip)
for (i = 0; i < hsu->nr_channels; i++) {
struct hsu_dma_chan *hsuc = &hsu->chan[i];
- tasklet_kill(&hsuc->vchan.task);
+ dmaengine_kill_bh(&hsuc->vchan.chan);
}
return 0;
diff --git a/drivers/dma/idma64.c b/drivers/dma/idma64.c
index 5fcd1befc92d..1d395fb735ff 100644
--- a/drivers/dma/idma64.c
+++ b/drivers/dma/idma64.c
@@ -617,14 +617,14 @@ static void idma64_remove(struct idma64_chip *chip)
/*
* Explicitly call devm_request_irq() to avoid the side effects with
- * the scheduled tasklets.
+ * scheduled BH work.
*/
devm_free_irq(chip->dev, chip->irq, idma64);
for (i = 0; i < idma64->dma.chancnt; i++) {
struct idma64_chan *idma64c = &idma64->chan[i];
- tasklet_kill(&idma64c->vchan.task);
+ dmaengine_kill_bh(&idma64c->vchan.chan);
}
}
diff --git a/drivers/dma/img-mdc-dma.c b/drivers/dma/img-mdc-dma.c
index b3765ba15803..0c6024088444 100644
--- a/drivers/dma/img-mdc-dma.c
+++ b/drivers/dma/img-mdc-dma.c
@@ -1031,7 +1031,7 @@ static void mdc_dma_remove(struct platform_device *pdev)
devm_free_irq(&pdev->dev, mchan->irq, mchan);
- tasklet_kill(&mchan->vc.task);
+ dmaengine_kill_bh(&mchan->vc.chan);
}
pm_runtime_disable(&pdev->dev);
diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c
index 36368835a845..4d13b9d2880d 100644
--- a/drivers/dma/imx-sdma.c
+++ b/drivers/dma/imx-sdma.c
@@ -2399,11 +2399,11 @@ static void sdma_remove(struct platform_device *pdev)
int i;
devm_free_irq(&pdev->dev, sdma->irq, sdma);
- /* Kill the tasklet */
+ /* Kill the channel BH */
for (i = 0; i < MAX_DMA_CHANNELS; i++) {
struct sdma_channel *sdmac = &sdma->channel[i];
- tasklet_kill(&sdmac->vc.task);
+ dmaengine_kill_bh(&sdmac->vc.chan);
sdma_free_chan_resources(&sdmac->vc.chan);
}
diff --git a/drivers/dma/k3dma.c b/drivers/dma/k3dma.c
index e84f197fea76..3d73b391e42e 100644
--- a/drivers/dma/k3dma.c
+++ b/drivers/dma/k3dma.c
@@ -976,7 +976,7 @@ static void k3_dma_remove(struct platform_device *op)
list_for_each_entry_safe(c, cn, &d->slave.channels, vc.chan.device_node) {
list_del(&c->vc.chan.device_node);
- tasklet_kill(&c->vc.task);
+ dmaengine_kill_bh(&c->vc.chan);
}
tasklet_kill(&d->task);
clk_disable_unprepare(d->clk);
diff --git a/drivers/dma/loongson/loongson1-apb-dma.c b/drivers/dma/loongson/loongson1-apb-dma.c
index 89786cbd20ab..52a360719644 100644
--- a/drivers/dma/loongson/loongson1-apb-dma.c
+++ b/drivers/dma/loongson/loongson1-apb-dma.c
@@ -552,7 +552,7 @@ static void ls1x_dma_chan_remove(struct ls1x_dma *dma)
if (chan->vc.chan.device == &dma->ddev) {
list_del(&chan->vc.chan.device_node);
- tasklet_kill(&chan->vc.task);
+ dmaengine_kill_bh(&chan->vc.chan);
}
}
}
diff --git a/drivers/dma/mediatek/mtk-cqdma.c b/drivers/dma/mediatek/mtk-cqdma.c
index 80791e30aec2..7b44d3358cda 100644
--- a/drivers/dma/mediatek/mtk-cqdma.c
+++ b/drivers/dma/mediatek/mtk-cqdma.c
@@ -895,7 +895,7 @@ static void mtk_cqdma_remove(struct platform_device *pdev)
vc = &cqdma->vc[i];
list_del(&vc->vc.chan.device_node);
- tasklet_kill(&vc->vc.task);
+ dmaengine_kill_bh(&vc->vc.chan);
}
/* disable interrupt */
diff --git a/drivers/dma/mediatek/mtk-hsdma.c b/drivers/dma/mediatek/mtk-hsdma.c
index a43412ff5edd..75555fa41536 100644
--- a/drivers/dma/mediatek/mtk-hsdma.c
+++ b/drivers/dma/mediatek/mtk-hsdma.c
@@ -1020,7 +1020,7 @@ static void mtk_hsdma_remove(struct platform_device *pdev)
vc = &hsdma->vc[i];
list_del(&vc->vc.chan.device_node);
- tasklet_kill(&vc->vc.task);
+ dmaengine_kill_bh(&vc->vc.chan);
}
/* Disable DMA interrupt */
diff --git a/drivers/dma/mediatek/mtk-uart-apdma.c b/drivers/dma/mediatek/mtk-uart-apdma.c
index c269d84d7bd2..8ce206a0ecc5 100644
--- a/drivers/dma/mediatek/mtk-uart-apdma.c
+++ b/drivers/dma/mediatek/mtk-uart-apdma.c
@@ -312,7 +312,7 @@ static void mtk_uart_apdma_free_chan_resources(struct dma_chan *chan)
free_irq(c->irq, chan);
- tasklet_kill(&c->vc.task);
+ dmaengine_kill_bh(&c->vc.chan);
vchan_free_chan_resources(&c->vc);
@@ -463,7 +463,7 @@ static void mtk_uart_apdma_free(struct mtk_uart_apdmadev *mtkd)
struct mtk_chan, vc.chan.device_node);
list_del(&c->vc.chan.device_node);
- tasklet_kill(&c->vc.task);
+ dmaengine_kill_bh(&c->vc.chan);
}
}
diff --git a/drivers/dma/owl-dma.c b/drivers/dma/owl-dma.c
index 7c80572fc71d..0d9f324adba0 100644
--- a/drivers/dma/owl-dma.c
+++ b/drivers/dma/owl-dma.c
@@ -1055,7 +1055,7 @@ static inline void owl_dma_free(struct owl_dma *od)
list_for_each_entry_safe(vchan,
next, &od->dma.channels, vc.chan.device_node) {
list_del(&vchan->vc.chan.device_node);
- tasklet_kill(&vchan->vc.task);
+ dmaengine_kill_bh(&vchan->vc.chan);
}
}
diff --git a/drivers/dma/pxa_dma.c b/drivers/dma/pxa_dma.c
index fa2ee0b3e09f..2cca8c31929c 100644
--- a/drivers/dma/pxa_dma.c
+++ b/drivers/dma/pxa_dma.c
@@ -1215,7 +1215,7 @@ static void pxad_free_channels(struct dma_device *dmadev)
list_for_each_entry_safe(c, cn, &dmadev->channels,
vc.chan.device_node) {
list_del(&c->vc.chan.device_node);
- tasklet_kill(&c->vc.task);
+ dmaengine_kill_bh(&c->vc.chan);
}
}
diff --git a/drivers/dma/qcom/bam_dma.c b/drivers/dma/qcom/bam_dma.c
index 1bb26af0405f..c23496c3ac69 100644
--- a/drivers/dma/qcom/bam_dma.c
+++ b/drivers/dma/qcom/bam_dma.c
@@ -1387,7 +1387,7 @@ static int bam_dma_probe(struct platform_device *pdev)
dma_async_device_unregister(&bdev->common);
err_bam_channel_exit:
for (i = 0; i < bdev->num_channels; i++)
- tasklet_kill(&bdev->channels[i].vc.task);
+ dmaengine_kill_bh(&bdev->channels[i].vc.chan);
err_tasklet_kill:
tasklet_kill(&bdev->task);
err_disable_clk:
@@ -1413,7 +1413,7 @@ static void bam_dma_remove(struct platform_device *pdev)
for (i = 0; i < bdev->num_channels; i++) {
bam_dma_terminate_all(&bdev->channels[i].vc.chan);
- tasklet_kill(&bdev->channels[i].vc.task);
+ dmaengine_kill_bh(&bdev->channels[i].vc.chan);
if (!bdev->channels[i].fifo_virt)
continue;
diff --git a/drivers/dma/qcom/qcom_adm.c b/drivers/dma/qcom/qcom_adm.c
index 07fbe32d31fa..13f5ca8ff808 100644
--- a/drivers/dma/qcom/qcom_adm.c
+++ b/drivers/dma/qcom/qcom_adm.c
@@ -918,8 +918,8 @@ static void adm_dma_remove(struct platform_device *pdev)
/* mask IRQs for this channel/EE pair */
writel(0, adev->regs + ADM_CH_RSLT_CONF(achan->id, adev->ee));
- tasklet_kill(&adev->channels[i].vc.task);
- adm_terminate_all(&adev->channels[i].vc.chan);
+ dmaengine_kill_bh(&achan->vc.chan);
+ adm_terminate_all(&achan->vc.chan);
}
devm_free_irq(adev->dev, adev->irq, adev);
diff --git a/drivers/dma/sa11x0-dma.c b/drivers/dma/sa11x0-dma.c
index a6fa431530e3..e14566fa2d74 100644
--- a/drivers/dma/sa11x0-dma.c
+++ b/drivers/dma/sa11x0-dma.c
@@ -891,7 +891,7 @@ static void sa11x0_dma_free_channels(struct dma_device *dmadev)
list_for_each_entry_safe(c, cn, &dmadev->channels, vc.chan.device_node) {
list_del(&c->vc.chan.device_node);
- tasklet_kill(&c->vc.task);
+ dmaengine_kill_bh(&c->vc.chan);
kfree(c);
}
}
diff --git a/drivers/dma/sf-pdma/sf-pdma.c b/drivers/dma/sf-pdma/sf-pdma.c
index 6f79cc28703e..a08ca355dadb 100644
--- a/drivers/dma/sf-pdma/sf-pdma.c
+++ b/drivers/dma/sf-pdma/sf-pdma.c
@@ -602,7 +602,7 @@ static void sf_pdma_remove(struct platform_device *pdev)
devm_free_irq(&pdev->dev, ch->txirq, ch);
devm_free_irq(&pdev->dev, ch->errirq, ch);
list_del(&ch->vchan.chan.device_node);
- tasklet_kill(&ch->vchan.task);
+ dmaengine_kill_bh(&ch->vchan.chan);
tasklet_kill(&ch->done_tasklet);
tasklet_kill(&ch->err_tasklet);
}
diff --git a/drivers/dma/sprd-dma.c b/drivers/dma/sprd-dma.c
index 087fea3af2e4..f90f5d8d5a1e 100644
--- a/drivers/dma/sprd-dma.c
+++ b/drivers/dma/sprd-dma.c
@@ -1253,7 +1253,7 @@ static void sprd_dma_remove(struct platform_device *pdev)
list_for_each_entry_safe(c, cn, &sdev->dma_dev.channels,
vc.chan.device_node) {
list_del(&c->vc.chan.device_node);
- tasklet_kill(&c->vc.task);
+ dmaengine_kill_bh(&c->vc.chan);
}
of_dma_controller_free(pdev->dev.of_node);
diff --git a/drivers/dma/st_fdma.c b/drivers/dma/st_fdma.c
index d9547017f3bd..cae0a7fe6ceb 100644
--- a/drivers/dma/st_fdma.c
+++ b/drivers/dma/st_fdma.c
@@ -733,7 +733,7 @@ static void st_fdma_free(struct st_fdma_dev *fdev)
for (i = 0; i < fdev->nr_channels; i++) {
fchan = &fdev->chans[i];
list_del(&fchan->vchan.chan.device_node);
- tasklet_kill(&fchan->vchan.task);
+ dmaengine_kill_bh(&fchan->vchan.chan);
}
}
diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
index f47a326dd7ff..4ddea3cdcd66 100644
--- a/drivers/dma/sun6i-dma.c
+++ b/drivers/dma/sun6i-dma.c
@@ -1094,7 +1094,7 @@ static inline void sun6i_dma_free(struct sun6i_dma_dev *sdev)
struct sun6i_vchan *vchan = &sdev->vchans[i];
list_del(&vchan->vc.chan.device_node);
- tasklet_kill(&vchan->vc.task);
+ dmaengine_kill_bh(&vchan->vc.chan);
}
}
diff --git a/drivers/dma/tegra186-gpc-dma.c b/drivers/dma/tegra186-gpc-dma.c
index 64cedef1050a..3a27a83a3288 100644
--- a/drivers/dma/tegra186-gpc-dma.c
+++ b/drivers/dma/tegra186-gpc-dma.c
@@ -1284,7 +1284,7 @@ static void tegra_dma_free_chan_resources(struct dma_chan *dc)
tegra_dma_terminate_all(dc);
synchronize_irq(tdc->irq);
- tasklet_kill(&tdc->vc.task);
+ dmaengine_kill_bh(&tdc->vc.chan);
tdc->config_init = false;
tdc->slave_id = -1;
tdc->sid_dir = DMA_TRANS_NONE;
diff --git a/drivers/dma/tegra210-adma.c b/drivers/dma/tegra210-adma.c
index ceaee1e33e68..780863199224 100644
--- a/drivers/dma/tegra210-adma.c
+++ b/drivers/dma/tegra210-adma.c
@@ -811,7 +811,7 @@ static void tegra_adma_free_chan_resources(struct dma_chan *dc)
tegra_adma_terminate_all(dc);
vchan_free_chan_resources(&tdc->vc);
- tasklet_kill(&tdc->vc.task);
+ dmaengine_kill_bh(&tdc->vc.chan);
free_irq(tdc->irq, tdc);
pm_runtime_put(tdc2dev(tdc));
diff --git a/drivers/dma/ti/edma.c b/drivers/dma/ti/edma.c
index d97db5af3555..c0ad0cdabb87 100644
--- a/drivers/dma/ti/edma.c
+++ b/drivers/dma/ti/edma.c
@@ -2560,7 +2560,7 @@ static void edma_cleanupp_vchan(struct dma_device *dmadev)
list_for_each_entry_safe(echan, _echan,
&dmadev->channels, vchan.chan.device_node) {
list_del(&echan->vchan.chan.device_node);
- tasklet_kill(&echan->vchan.task);
+ dmaengine_kill_bh(&echan->vchan.chan);
}
}
diff --git a/drivers/dma/ti/k3-udma.c b/drivers/dma/ti/k3-udma.c
index 1cf158eb7bdb..fd428c26799a 100644
--- a/drivers/dma/ti/k3-udma.c
+++ b/drivers/dma/ti/k3-udma.c
@@ -4042,12 +4042,12 @@ static void udma_desc_pre_callback(struct virt_dma_chan *vc,
}
/*
- * This tasklet handles the completion of a DMA descriptor by
+ * This BH handles the completion of a DMA descriptor by
* calling its callback and freeing it.
*/
-static void udma_vchan_complete(struct tasklet_struct *t)
+static void udma_vchan_complete(struct dma_chan *chan)
{
- struct virt_dma_chan *vc = from_tasklet(vc, t, task);
+ struct virt_dma_chan *vc = to_virt_chan(chan);
struct virt_dma_desc *vd, *_vd;
struct dmaengine_desc_callback cb;
LIST_HEAD(head);
@@ -4112,7 +4112,7 @@ static void udma_free_chan_resources(struct dma_chan *chan)
}
vchan_free_chan_resources(&uc->vc);
- tasklet_kill(&uc->vc.task);
+ dmaengine_kill_bh(&uc->vc.chan);
bcdma_free_bchan_resources(uc);
udma_free_tx_resources(uc);
@@ -5627,8 +5627,8 @@ static int udma_probe(struct platform_device *pdev)
if (!uc->name)
return -ENOMEM;
vchan_init(&uc->vc, &ud->ddev);
- /* Use custom vchan completion handling */
- tasklet_setup(&uc->vc.task, udma_vchan_complete);
+ /* Override the default vchan completion handler */
+ uc->vc.chan.bh_work_fn = udma_vchan_complete;
init_completion(&uc->teardown_completed);
INIT_DELAYED_WORK(&uc->tx_drain.work, udma_check_tx_completion);
}
diff --git a/drivers/dma/ti/omap-dma.c b/drivers/dma/ti/omap-dma.c
index 55ece7fd0d99..899c5eeeac88 100644
--- a/drivers/dma/ti/omap-dma.c
+++ b/drivers/dma/ti/omap-dma.c
@@ -1521,7 +1521,7 @@ static void omap_dma_free(struct omap_dmadev *od)
struct omap_chan, vc.chan.device_node);
list_del(&c->vc.chan.device_node);
- tasklet_kill(&c->vc.task);
+ dmaengine_kill_bh(&c->vc.chan);
kfree(c);
}
}
diff --git a/drivers/dma/virt-dma.c b/drivers/dma/virt-dma.c
index 7961172a780d..d407af6cc1da 100644
--- a/drivers/dma/virt-dma.c
+++ b/drivers/dma/virt-dma.c
@@ -77,12 +77,12 @@ struct virt_dma_desc *vchan_find_desc(struct virt_dma_chan *vc,
EXPORT_SYMBOL_GPL(vchan_find_desc);
/*
- * This tasklet handles the completion of a DMA descriptor by
- * calling its callback and freeing it.
+ * This bottom-half handler completes a DMA descriptor by invoking its
+ * callback and freeing it.
*/
-static void vchan_complete(struct tasklet_struct *t)
+static void vchan_complete(struct dma_chan *chan)
{
- struct virt_dma_chan *vc = from_tasklet(vc, t, task);
+ struct virt_dma_chan *vc = to_virt_chan(chan);
struct virt_dma_desc *vd, *_vd;
struct dmaengine_desc_callback cb;
LIST_HEAD(head);
@@ -98,7 +98,7 @@ static void vchan_complete(struct tasklet_struct *t)
}
spin_unlock_irq(&vc->lock);
- dmaengine_desc_callback_invoke(&cb, &vd->tx_result);
+ dmaengine_desc_callback_invoke(&cb, vd ? &vd->tx_result : NULL);
list_for_each_entry_safe(vd, _vd, &head, node) {
dmaengine_desc_get_callback(&vd->tx, &cb);
@@ -131,7 +131,7 @@ void vchan_init(struct virt_dma_chan *vc, struct dma_device *dmadev)
INIT_LIST_HEAD(&vc->desc_completed);
INIT_LIST_HEAD(&vc->desc_terminated);
- tasklet_setup(&vc->task, vchan_complete);
+ dmaengine_init_bh(&vc->chan, vchan_complete);
vc->chan.device = dmadev;
list_add_tail(&vc->chan.device_node, &dmadev->channels);
diff --git a/drivers/dma/virt-dma.h b/drivers/dma/virt-dma.h
index 59d9eabc8b67..0ba42fded2cc 100644
--- a/drivers/dma/virt-dma.h
+++ b/drivers/dma/virt-dma.h
@@ -21,7 +21,6 @@ struct virt_dma_desc {
struct virt_dma_chan {
struct dma_chan chan;
- struct tasklet_struct task;
void (*desc_free)(struct virt_dma_desc *);
spinlock_t lock;
@@ -106,7 +105,7 @@ static inline void vchan_cookie_complete(struct virt_dma_desc *vd)
vd, cookie);
list_add_tail(&vd->node, &vc->desc_completed);
- tasklet_schedule(&vc->task);
+ dmaengine_schedule_bh(&vc->chan);
}
/**
@@ -137,7 +136,7 @@ static inline void vchan_cyclic_callback(struct virt_dma_desc *vd)
struct virt_dma_chan *vc = to_virt_chan(vd->tx.chan);
vc->cyclic = vd;
- tasklet_schedule(&vc->task);
+ dmaengine_schedule_bh(&vc->chan);
}
/**
@@ -223,7 +222,7 @@ static inline void vchan_synchronize(struct virt_dma_chan *vc)
LIST_HEAD(head);
unsigned long flags;
- tasklet_kill(&vc->task);
+ dmaengine_kill_bh(&vc->chan);
spin_lock_irqsave(&vc->lock, flags);
diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h
index b3d251c9734e..a1437bdbda9b 100644
--- a/include/linux/dmaengine.h
+++ b/include/linux/dmaengine.h
@@ -12,6 +12,7 @@
#include <linux/scatterlist.h>
#include <linux/bitmap.h>
#include <linux/types.h>
+#include <linux/interrupt.h>
#include <asm/page.h>
/**
@@ -295,6 +296,10 @@ enum dma_desc_metadata_mode {
DESC_METADATA_ENGINE = BIT(1),
};
+struct dma_chan;
+
+typedef void (*dmaengine_bh_work_fn)(struct dma_chan *chan);
+
/**
* struct dma_chan_percpu - the per-CPU part of struct dma_chan
* @memcpy_count: transaction counter
@@ -334,6 +339,9 @@ struct dma_router {
* @router: pointer to the DMA router structure
* @route_data: channel specific data for the router
* @private: private data for certain client-channel associations
+ * @bh_tasklet: bottom-half tasklet stored per-channel
+ * @bh_work_fn: callback executed when @bh_tasklet runs
+ * @bh_work_initialized: indicates whether @bh_tasklet has been initialized
*/
struct dma_chan {
struct dma_device *device;
@@ -359,6 +367,9 @@ struct dma_chan {
void *route_data;
void *private;
+ struct tasklet_struct bh_tasklet;
+ dmaengine_bh_work_fn bh_work_fn;
+ bool bh_work_initialized;
};
/**
@@ -1529,6 +1540,9 @@ struct dma_chan *devm_dma_request_chan(struct device *dev, const char *name);
void dma_release_channel(struct dma_chan *chan);
int dma_get_slave_caps(struct dma_chan *chan, struct dma_slave_caps *caps);
+void dmaengine_init_bh(struct dma_chan *chan, dmaengine_bh_work_fn fn);
+bool dmaengine_schedule_bh(struct dma_chan *chan);
+void dmaengine_kill_bh(struct dma_chan *chan);
#else
static inline struct dma_chan *dma_find_channel(enum dma_transaction_type tx_type)
{
@@ -1576,6 +1590,20 @@ static inline int dma_get_slave_caps(struct dma_chan *chan,
{
return -ENXIO;
}
+
+static inline void dmaengine_init_bh(struct dma_chan *chan,
+ dmaengine_bh_work_fn fn)
+{
+}
+
+static inline bool dmaengine_schedule_bh(struct dma_chan *chan)
+{
+ return false;
+}
+
+static inline void dmaengine_kill_bh(struct dma_chan *chan)
+{
+}
#endif
static inline int dmaengine_desc_set_reuse(struct dma_async_tx_descriptor *tx)
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v3 03/34] dmaengine: apple-admac: use dmaengine BH callback
[not found] ` <cover.1786384168.git.allen.lkml@gmail.com>
2026-08-10 18:09 ` [PATCH v3 01/34] dmaengine: add tasklet-backed channel BH helpers Allen Pais
@ 2026-08-10 18:09 ` Allen Pais
2026-09-02 15:33 ` Frank Li
2026-08-10 18:09 ` [PATCH v3 04/34] dmaengine: at_xdmac: move irq bottom half to dmaengine BH Allen Pais
` (7 subsequent siblings)
9 siblings, 1 reply; 63+ messages in thread
From: Allen Pais @ 2026-08-10 18:09 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Sven Peter, Janne Grunau, Neal Gompa, asahi, linux-arm-kernel
Replace the per-channel tasklet with the shared dmaengine BH helper.
The handler continues to run in softirq context while dmaengine owns
the common scheduling and teardown mechanism.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/apple-admac.c | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
diff --git a/drivers/dma/apple-admac.c b/drivers/dma/apple-admac.c
index 14a5ee14a481..c5d4fb96d3c4 100644
--- a/drivers/dma/apple-admac.c
+++ b/drivers/dma/apple-admac.c
@@ -89,7 +89,6 @@ struct admac_chan {
unsigned int no;
struct admac_data *host;
struct dma_chan chan;
- struct tasklet_struct tasklet;
u32 carveout;
@@ -521,10 +520,7 @@ static int admac_terminate_all(struct dma_chan *chan)
list_add_tail(&adchan->current_tx->node, &adchan->to_free);
adchan->current_tx = NULL;
}
- /*
- * Descriptors can only be freed after the tasklet
- * has been killed (in admac_synchronize).
- */
+ /* Descriptors are released once the BH is flushed in admac_synchronize */
list_splice_tail_init(&adchan->submitted, &adchan->to_free);
list_splice_tail_init(&adchan->issued, &adchan->to_free);
spin_unlock_irqrestore(&adchan->lock, flags);
@@ -543,7 +539,7 @@ static void admac_synchronize(struct dma_chan *chan)
list_splice_tail_init(&adchan->to_free, &head);
spin_unlock_irqrestore(&adchan->lock, flags);
- tasklet_kill(&adchan->tasklet);
+ dmaengine_kill_bh(&adchan->chan);
list_for_each_entry_safe(adtx, _adtx, &head, node) {
list_del(&adtx->node);
@@ -662,7 +658,7 @@ static void admac_handle_status_desc_done(struct admac_data *ad, int channo)
tx->reclaimed_pos %= 2 * tx->buf_len;
admac_cyclic_write_desc(ad, channo, tx);
- tasklet_schedule(&adchan->tasklet);
+ dmaengine_schedule_bh(&adchan->chan);
}
spin_unlock_irqrestore(&adchan->lock, flags);
}
@@ -712,9 +708,9 @@ static irqreturn_t admac_interrupt(int irq, void *devid)
return IRQ_HANDLED;
}
-static void admac_chan_tasklet(struct tasklet_struct *t)
+static void admac_chan_bh(struct dma_chan *chan)
{
- struct admac_chan *adchan = from_tasklet(adchan, t, tasklet);
+ struct admac_chan *adchan = to_admac_chan(chan);
struct admac_tx *adtx;
struct dmaengine_desc_callback cb;
struct dmaengine_result tx_result;
@@ -886,7 +882,7 @@ static int admac_probe(struct platform_device *pdev)
INIT_LIST_HEAD(&adchan->issued);
INIT_LIST_HEAD(&adchan->to_free);
list_add_tail(&adchan->chan.device_node, &dma->channels);
- tasklet_setup(&adchan->tasklet, admac_chan_tasklet);
+ dmaengine_init_bh(&adchan->chan, admac_chan_bh);
}
err = reset_control_reset(ad->rstc);
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v3 04/34] dmaengine: at_xdmac: move irq bottom half to dmaengine BH
[not found] ` <cover.1786384168.git.allen.lkml@gmail.com>
2026-08-10 18:09 ` [PATCH v3 01/34] dmaengine: add tasklet-backed channel BH helpers Allen Pais
2026-08-10 18:09 ` [PATCH v3 03/34] dmaengine: apple-admac: use dmaengine BH callback Allen Pais
@ 2026-08-10 18:09 ` Allen Pais
2026-09-01 22:32 ` Frank Li
2026-08-10 18:09 ` [PATCH v3 08/34] dmaengine: imx-dma: flip per-chan tasklet " Allen Pais
` (6 subsequent siblings)
9 siblings, 1 reply; 63+ messages in thread
From: Allen Pais @ 2026-08-10 18:09 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Ludovic Desroches, linux-arm-kernel
Replace the per-channel tasklet with the shared dmaengine BH helper.
The handler continues to run in softirq context while dmaengine owns
the common scheduling and teardown mechanism.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/at_xdmac.c | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/drivers/dma/at_xdmac.c b/drivers/dma/at_xdmac.c
index 901971e8bae6..5a970f9f3831 100644
--- a/drivers/dma/at_xdmac.c
+++ b/drivers/dma/at_xdmac.c
@@ -228,7 +228,6 @@ struct at_xdmac_chan {
u32 save_cndc;
u32 irq_status;
unsigned long status;
- struct tasklet_struct tasklet;
struct dma_slave_config sconfig;
spinlock_t lock;
@@ -1759,9 +1758,9 @@ static void at_xdmac_handle_error(struct at_xdmac_chan *atchan)
/* Then continue with usual descriptor management */
}
-static void at_xdmac_tasklet(struct tasklet_struct *t)
+static void at_xdmac_tasklet(struct dma_chan *chan)
{
- struct at_xdmac_chan *atchan = from_tasklet(atchan, t, tasklet);
+ struct at_xdmac_chan *atchan = to_at_xdmac_chan(chan);
struct at_xdmac *atxdmac = to_at_xdmac(atchan->chan.device);
struct at_xdmac_desc *desc;
struct dma_async_tx_descriptor *txd;
@@ -1865,7 +1864,7 @@ static irqreturn_t at_xdmac_interrupt(int irq, void *dev_id)
if (atchan->irq_status & (AT_XDMAC_CIS_RBEIS | AT_XDMAC_CIS_WBEIS))
at_xdmac_write(atxdmac, AT_XDMAC_GD, atchan->mask);
- tasklet_schedule(&atchan->tasklet);
+ dmaengine_schedule_bh(&atchan->chan);
ret = IRQ_HANDLED;
}
@@ -2317,7 +2316,7 @@ static int at_xdmac_probe(struct platform_device *pdev)
return PTR_ERR(atxdmac->clk);
}
- /* Do not use dev res to prevent races with tasklet */
+ /* Do not use devm resources to prevent races with the BH worker */
ret = request_irq(atxdmac->irq, at_xdmac_interrupt, 0, "at_xdmac", atxdmac);
if (ret) {
dev_err(&pdev->dev, "can't request irq\n");
@@ -2397,7 +2396,7 @@ static int at_xdmac_probe(struct platform_device *pdev)
spin_lock_init(&atchan->lock);
INIT_LIST_HEAD(&atchan->xfers_list);
INIT_LIST_HEAD(&atchan->free_descs_list);
- tasklet_setup(&atchan->tasklet, at_xdmac_tasklet);
+ dmaengine_init_bh(&atchan->chan, at_xdmac_tasklet);
/* Clear pending interrupts. */
while (at_xdmac_chan_read(atchan, AT_XDMAC_CIS))
@@ -2458,7 +2457,7 @@ static void at_xdmac_remove(struct platform_device *pdev)
for (i = 0; i < atxdmac->dma.chancnt; i++) {
struct at_xdmac_chan *atchan = &atxdmac->chan[i];
- tasklet_kill(&atchan->tasklet);
+ dmaengine_kill_bh(&atchan->chan);
at_xdmac_free_chan_resources(&atchan->chan);
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v3 08/34] dmaengine: imx-dma: flip per-chan tasklet to dmaengine BH
[not found] ` <cover.1786384168.git.allen.lkml@gmail.com>
` (2 preceding siblings ...)
2026-08-10 18:09 ` [PATCH v3 04/34] dmaengine: at_xdmac: move irq bottom half to dmaengine BH Allen Pais
@ 2026-08-10 18:09 ` Allen Pais
2026-08-10 18:34 ` sashiko-bot
` (2 more replies)
2026-08-10 18:09 ` [PATCH v3 13/34] dmaengine: mxs-dma: use dmaengine BH scheduling Allen Pais
` (5 subsequent siblings)
9 siblings, 3 replies; 63+ messages in thread
From: Allen Pais @ 2026-08-10 18:09 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, imx,
linux-arm-kernel
Replace the per-channel tasklet with the shared dmaengine BH helper.
The handler continues to run in softirq context while dmaengine owns
the common scheduling and teardown mechanism.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/imx-dma.c | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/drivers/dma/imx-dma.c b/drivers/dma/imx-dma.c
index 81c6276436f8..308fe407fe16 100644
--- a/drivers/dma/imx-dma.c
+++ b/drivers/dma/imx-dma.c
@@ -145,7 +145,6 @@ struct imxdma_channel {
struct imxdma_engine *imxdma;
unsigned int channel;
- struct tasklet_struct dma_tasklet;
struct list_head ld_free;
struct list_head ld_queue;
struct list_head ld_active;
@@ -344,8 +343,8 @@ static void imxdma_watchdog(struct timer_list *t)
imx_dmav1_writel(imxdma, 0, DMA_CCR(channel));
- /* Tasklet watchdog error handler */
- tasklet_schedule(&imxdmac->dma_tasklet);
+ /* BH watchdog error handler */
+ dmaengine_schedule_bh(&imxdmac->chan);
dev_dbg(imxdma->dev, "channel %d: watchdog timeout!\n",
imxdmac->channel);
}
@@ -390,8 +389,8 @@ static irqreturn_t imxdma_err_handler(int irq, void *dev_id)
imx_dmav1_writel(imxdma, 1 << i, DMA_DBOSR);
errcode |= IMX_DMA_ERR_BUFFER;
}
- /* Tasklet error handler */
- tasklet_schedule(&imxdma->channel[i].dma_tasklet);
+ /* BH error handler */
+ dmaengine_schedule_bh(&imxdma->channel[i].chan);
dev_warn(imxdma->dev,
"DMA timeout on channel %d -%s%s%s%s\n", i,
@@ -448,8 +447,8 @@ static void dma_irq_handle_channel(struct imxdma_channel *imxdmac)
imx_dmav1_writel(imxdma, tmp, DMA_CCR(chno));
if (imxdma_chan_is_doing_cyclic(imxdmac))
- /* Tasklet progression */
- tasklet_schedule(&imxdmac->dma_tasklet);
+ /* BH progression */
+ dmaengine_schedule_bh(&imxdmac->chan);
return;
}
@@ -462,8 +461,8 @@ static void dma_irq_handle_channel(struct imxdma_channel *imxdmac)
out:
imx_dmav1_writel(imxdma, 0, DMA_CCR(chno));
- /* Tasklet irq */
- tasklet_schedule(&imxdmac->dma_tasklet);
+ /* Schedule the IRQ BH */
+ dmaengine_schedule_bh(&imxdmac->chan);
}
static irqreturn_t dma_irq_handler(int irq, void *dev_id)
@@ -592,9 +591,10 @@ static int imxdma_xfer_desc(struct imxdma_desc *d)
return 0;
}
-static void imxdma_tasklet(struct tasklet_struct *t)
+static void imxdma_tasklet(struct dma_chan *chan)
{
- struct imxdma_channel *imxdmac = from_tasklet(imxdmac, t, dma_tasklet);
+ struct imxdma_channel *imxdmac = container_of(chan, struct imxdma_channel,
+ chan);
struct imxdma_engine *imxdma = imxdmac->imxdma;
struct imxdma_desc *desc, *next_desc;
unsigned long flags;
@@ -1142,7 +1142,7 @@ static int __init imxdma_probe(struct platform_device *pdev)
INIT_LIST_HEAD(&imxdmac->ld_free);
INIT_LIST_HEAD(&imxdmac->ld_active);
- tasklet_setup(&imxdmac->dma_tasklet, imxdma_tasklet);
+ dmaengine_init_bh(&imxdmac->chan, imxdma_tasklet);
imxdmac->chan.device = &imxdma->dma_device;
dma_cookie_init(&imxdmac->chan);
imxdmac->channel = i;
@@ -1211,7 +1211,7 @@ static void imxdma_free_irq(struct platform_device *pdev, struct imxdma_engine *
if (!is_imx1_dma(imxdma))
disable_irq(imxdmac->irq);
- tasklet_kill(&imxdmac->dma_tasklet);
+ dmaengine_kill_bh(&imxdmac->chan);
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v3 13/34] dmaengine: mxs-dma: use dmaengine BH scheduling
[not found] ` <cover.1786384168.git.allen.lkml@gmail.com>
` (3 preceding siblings ...)
2026-08-10 18:09 ` [PATCH v3 08/34] dmaengine: imx-dma: flip per-chan tasklet " Allen Pais
@ 2026-08-10 18:09 ` Allen Pais
2026-08-10 18:27 ` sashiko-bot
2026-09-02 15:33 ` Frank Li
2026-08-10 18:09 ` [PATCH v3 17/34] dmaengine: ste_dma40: convert per-channel tasklet to dmaengine BH Allen Pais
` (4 subsequent siblings)
9 siblings, 2 replies; 63+ messages in thread
From: Allen Pais @ 2026-08-10 18:09 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, imx,
linux-arm-kernel
Replace the per-channel tasklet with the shared dmaengine BH helper.
The handler continues to run in softirq context while dmaengine owns
the common scheduling and teardown mechanism.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/mxs-dma.c | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/drivers/dma/mxs-dma.c b/drivers/dma/mxs-dma.c
index 7acb3d29dad3..149ac402f7b4 100644
--- a/drivers/dma/mxs-dma.c
+++ b/drivers/dma/mxs-dma.c
@@ -109,7 +109,6 @@ struct mxs_dma_chan {
struct mxs_dma_engine *mxs_dma;
struct dma_chan chan;
struct dma_async_tx_descriptor desc;
- struct tasklet_struct tasklet;
unsigned int chan_irq;
struct mxs_dma_ccw *ccw;
dma_addr_t ccw_phys;
@@ -300,9 +299,9 @@ static dma_cookie_t mxs_dma_tx_submit(struct dma_async_tx_descriptor *tx)
return dma_cookie_assign(tx);
}
-static void mxs_dma_tasklet(struct tasklet_struct *t)
+static void mxs_dma_tasklet(struct dma_chan *chan)
{
- struct mxs_dma_chan *mxs_chan = from_tasklet(mxs_chan, t, tasklet);
+ struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(chan);
dmaengine_desc_get_callback_invoke(&mxs_chan->desc, NULL);
}
@@ -386,8 +385,8 @@ static irqreturn_t mxs_dma_int_handler(int irq, void *dev_id)
dma_cookie_complete(&mxs_chan->desc);
}
- /* schedule tasklet on this channel */
- tasklet_schedule(&mxs_chan->tasklet);
+ /* schedule BH on this channel */
+ dmaengine_schedule_bh(&mxs_chan->chan);
return IRQ_HANDLED;
}
@@ -781,7 +780,7 @@ static int mxs_dma_probe(struct platform_device *pdev)
mxs_chan->chan.device = &mxs_dma->dma_device;
dma_cookie_init(&mxs_chan->chan);
- tasklet_setup(&mxs_chan->tasklet, mxs_dma_tasklet);
+ dmaengine_init_bh(&mxs_chan->chan, mxs_dma_tasklet);
/* Add the channel to mxs_chan list */
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v3 17/34] dmaengine: ste_dma40: convert per-channel tasklet to dmaengine BH
[not found] ` <cover.1786384168.git.allen.lkml@gmail.com>
` (4 preceding siblings ...)
2026-08-10 18:09 ` [PATCH v3 13/34] dmaengine: mxs-dma: use dmaengine BH scheduling Allen Pais
@ 2026-08-10 18:09 ` Allen Pais
2026-09-02 14:43 ` Frank Li
2026-08-10 18:09 ` [PATCH v3 19/34] dmaengine: xilinx-dma: use dmaengine BH instead of tasklets Allen Pais
` (3 subsequent siblings)
9 siblings, 1 reply; 63+ messages in thread
From: Allen Pais @ 2026-08-10 18:09 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Linus Walleij, linux-arm-kernel
Replace the per-channel tasklet with the shared dmaengine BH helper.
The handler continues to run in softirq context while dmaengine owns
the common scheduling and teardown mechanism.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
---
drivers/dma/ste_dma40.c | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
index 0d9ffa3e2663..e6dfa1fc5e5a 100644
--- a/drivers/dma/ste_dma40.c
+++ b/drivers/dma/ste_dma40.c
@@ -461,8 +461,6 @@ struct d40_base;
* @phy_chan: Pointer to physical channel which this instance runs on. If this
* point is NULL, then the channel is not allocated.
* @chan: DMA engine handle.
- * @tasklet: Tasklet that gets scheduled from interrupt context to complete a
- * transfer and call client callback.
* @client: Cliented owned descriptor list.
* @pending_queue: Submitted jobs, to be issued by issue_pending()
* @active: Active descriptor.
@@ -489,7 +487,6 @@ struct d40_chan {
bool busy;
struct d40_phy_res *phy_chan;
struct dma_chan chan;
- struct tasklet_struct tasklet;
struct list_head client;
struct list_head pending_queue;
struct list_head active;
@@ -1587,13 +1584,13 @@ static void dma_tc_handle(struct d40_chan *d40c)
}
d40c->pending_tx++;
- tasklet_schedule(&d40c->tasklet);
+ dmaengine_schedule_bh(&d40c->chan);
}
-static void dma_tasklet(struct tasklet_struct *t)
+static void dma_tasklet(struct dma_chan *chan)
{
- struct d40_chan *d40c = from_tasklet(d40c, t, tasklet);
+ struct d40_chan *d40c = container_of(chan, struct d40_chan, chan);
struct d40_desc *d40d;
unsigned long flags;
bool callback_active;
@@ -1641,7 +1638,7 @@ static void dma_tasklet(struct tasklet_struct *t)
d40c->pending_tx--;
if (d40c->pending_tx)
- tasklet_schedule(&d40c->tasklet);
+ dmaengine_schedule_bh(&d40c->chan);
spin_unlock_irqrestore(&d40c->lock, flags);
@@ -2815,7 +2812,7 @@ static void __init d40_chan_init(struct d40_base *base, struct dma_device *dma,
INIT_LIST_HEAD(&d40c->client);
INIT_LIST_HEAD(&d40c->prepare_queue);
- tasklet_setup(&d40c->tasklet, dma_tasklet);
+ dmaengine_init_bh(&d40c->chan, dma_tasklet);
list_add_tail(&d40c->chan.device_node,
&dma->channels);
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v3 19/34] dmaengine: xilinx-dma: use dmaengine BH instead of tasklets
[not found] ` <cover.1786384168.git.allen.lkml@gmail.com>
` (5 preceding siblings ...)
2026-08-10 18:09 ` [PATCH v3 17/34] dmaengine: ste_dma40: convert per-channel tasklet to dmaengine BH Allen Pais
@ 2026-08-10 18:09 ` Allen Pais
2026-09-02 15:35 ` Frank Li
2026-08-10 18:09 ` [PATCH v3 20/34] dmaengine: xilinx-dpdma: kill vchan BH on remove Allen Pais
` (2 subsequent siblings)
9 siblings, 1 reply; 63+ messages in thread
From: Allen Pais @ 2026-08-10 18:09 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Michal Simek, Suraj Gupta, Marek Vasut, Folker Schwesinger,
Tomi Valkeinen, Krzysztof Kozlowski, linux-arm-kernel
Replace the per-channel tasklet with the shared dmaengine BH helper.
The handler continues to run in softirq context while dmaengine owns
the common scheduling and teardown mechanism.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/xilinx/xilinx_dma.c | 20 +++++++++-----------
1 file changed, 9 insertions(+), 11 deletions(-)
diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c
index 404235c17353..b142c9c0583c 100644
--- a/drivers/dma/xilinx/xilinx_dma.c
+++ b/drivers/dma/xilinx/xilinx_dma.c
@@ -402,7 +402,6 @@ struct xilinx_dma_tx_descriptor {
* @err: Channel has errors
* @idle: Check for channel idle
* @terminating: Check for channel being synchronized by user
- * @tasklet: Cleanup work after irq
* @config: Device configuration info
* @flush_on_fsync: Flush on Frame sync
* @desc_pendingcount: Descriptor pending count
@@ -441,7 +440,6 @@ struct xilinx_dma_chan {
bool err;
bool idle;
bool terminating;
- struct tasklet_struct tasklet;
struct xilinx_vdma_config config;
bool flush_on_fsync;
u32 desc_pendingcount;
@@ -1115,11 +1113,12 @@ static void xilinx_dma_chan_desc_cleanup(struct xilinx_dma_chan *chan)
/**
* xilinx_dma_do_tasklet - Schedule completion tasklet
- * @t: Pointer to the Xilinx DMA channel structure
+ * @c: Pointer to the Xilinx DMA channel structure
*/
-static void xilinx_dma_do_tasklet(struct tasklet_struct *t)
+static void xilinx_dma_do_tasklet(struct dma_chan *c)
{
- struct xilinx_dma_chan *chan = from_tasklet(chan, t, tasklet);
+ struct xilinx_dma_chan *chan = container_of(c,
+ struct xilinx_dma_chan, common);
xilinx_dma_chan_desc_cleanup(chan);
}
@@ -1898,7 +1897,7 @@ static irqreturn_t xilinx_mcdma_irq_handler(int irq, void *data)
spin_unlock(&chan->lock);
}
- tasklet_hi_schedule(&chan->tasklet);
+ dmaengine_schedule_bh(&chan->common);
return IRQ_HANDLED;
}
@@ -1955,7 +1954,7 @@ static irqreturn_t xilinx_dma_irq_handler(int irq, void *data)
spin_unlock(&chan->lock);
}
- tasklet_schedule(&chan->tasklet);
+ dmaengine_schedule_bh(&chan->common);
return IRQ_HANDLED;
}
@@ -2654,7 +2653,7 @@ static void xilinx_dma_synchronize(struct dma_chan *dchan)
{
struct xilinx_dma_chan *chan = to_xilinx_chan(dchan);
- tasklet_kill(&chan->tasklet);
+ dmaengine_kill_bh(&chan->common);
}
/**
@@ -2745,7 +2744,7 @@ static void xilinx_dma_chan_remove(struct xilinx_dma_chan *chan)
if (chan->irq > 0)
free_irq(chan->irq, chan);
- tasklet_kill(&chan->tasklet);
+ dmaengine_kill_bh(&chan->common);
list_del(&chan->common.device_node);
}
@@ -3075,8 +3074,7 @@ static int xilinx_dma_chan_probe(struct xilinx_dma_device *xdev,
str_enabled_disabled(chan->has_sg));
}
- /* Initialize the tasklet */
- tasklet_setup(&chan->tasklet, xilinx_dma_do_tasklet);
+ dmaengine_init_bh(&chan->common, xilinx_dma_do_tasklet);
/*
* Initialize the DMA channel and add it to the DMA engine channels
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v3 20/34] dmaengine: xilinx-dpdma: kill vchan BH on remove
[not found] ` <cover.1786384168.git.allen.lkml@gmail.com>
` (6 preceding siblings ...)
2026-08-10 18:09 ` [PATCH v3 19/34] dmaengine: xilinx-dma: use dmaengine BH instead of tasklets Allen Pais
@ 2026-08-10 18:09 ` Allen Pais
2026-09-02 15:31 ` Frank Li
2026-08-10 18:09 ` [PATCH v3 21/34] dmaengine: zynqmp-dma: switch completion tasklet to dmaengine BH Allen Pais
2026-08-10 18:09 ` [PATCH v3 32/34] dmaengine: hidma: defer callbacks via channel BH Allen Pais
9 siblings, 1 reply; 63+ messages in thread
From: Allen Pais @ 2026-08-10 18:09 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Laurent Pinchart, Michal Simek, linux-arm-kernel
virt-dma now dispatches completion callbacks through per-channel BH
work. Cancel that work when removing a channel, while retaining the
driver's separate error-handling tasklet.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/xilinx/xilinx_dpdma.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/dma/xilinx/xilinx_dpdma.c b/drivers/dma/xilinx/xilinx_dpdma.c
index d9a3542c4531..7e147ea11368 100644
--- a/drivers/dma/xilinx/xilinx_dpdma.c
+++ b/drivers/dma/xilinx/xilinx_dpdma.c
@@ -1685,6 +1685,7 @@ static void xilinx_dpdma_chan_remove(struct xilinx_dpdma_chan *chan)
return;
tasklet_kill(&chan->err_task);
+ dmaengine_kill_bh(&chan->vchan.chan);
list_del(&chan->vchan.chan.device_node);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v3 21/34] dmaengine: zynqmp-dma: switch completion tasklet to dmaengine BH
[not found] ` <cover.1786384168.git.allen.lkml@gmail.com>
` (7 preceding siblings ...)
2026-08-10 18:09 ` [PATCH v3 20/34] dmaengine: xilinx-dpdma: kill vchan BH on remove Allen Pais
@ 2026-08-10 18:09 ` Allen Pais
2026-09-02 15:30 ` Frank Li
2026-08-10 18:09 ` [PATCH v3 32/34] dmaengine: hidma: defer callbacks via channel BH Allen Pais
9 siblings, 1 reply; 63+ messages in thread
From: Allen Pais @ 2026-08-10 18:09 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Michal Simek, Abin Joseph, Sakari Ailus, linux-arm-kernel
Replace the per-channel tasklet with the shared dmaengine BH helper.
The handler continues to run in softirq context while dmaengine owns
the common scheduling and teardown mechanism.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/xilinx/zynqmp_dma.c | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/drivers/dma/xilinx/zynqmp_dma.c b/drivers/dma/xilinx/zynqmp_dma.c
index f6a812e49ddc..e396a35f0e0d 100644
--- a/drivers/dma/xilinx/zynqmp_dma.c
+++ b/drivers/dma/xilinx/zynqmp_dma.c
@@ -207,7 +207,6 @@ struct zynqmp_dma_desc_sw {
* @dev: The dma device
* @irq: Channel IRQ
* @is_dmacoherent: Tells whether dma operations are coherent or not
- * @tasklet: Cleanup work after irq
* @idle : Channel status;
* @desc_size: Size of the low level descriptor
* @err: Channel has errors
@@ -232,7 +231,6 @@ struct zynqmp_dma_chan {
struct device *dev;
int irq;
bool is_dmacoherent;
- struct tasklet_struct tasklet;
bool idle;
size_t desc_size;
bool err;
@@ -735,7 +733,7 @@ static irqreturn_t zynqmp_dma_irq_handler(int irq, void *data)
writel(isr, chan->regs + ZYNQMP_DMA_ISR);
if (status & ZYNQMP_DMA_INT_DONE) {
- tasklet_schedule(&chan->tasklet);
+ dmaengine_schedule_bh(&chan->common);
ret = IRQ_HANDLED;
}
@@ -744,7 +742,7 @@ static irqreturn_t zynqmp_dma_irq_handler(int irq, void *data)
if (status & ZYNQMP_DMA_INT_ERR) {
chan->err = true;
- tasklet_schedule(&chan->tasklet);
+ dmaengine_schedule_bh(&chan->common);
dev_err(chan->dev, "Channel %p has errors\n", chan);
ret = IRQ_HANDLED;
}
@@ -760,11 +758,12 @@ static irqreturn_t zynqmp_dma_irq_handler(int irq, void *data)
/**
* zynqmp_dma_do_tasklet - Schedule completion tasklet
- * @t: Pointer to the ZynqMP DMA channel structure
+ * @c: Pointer to the ZynqMP DMA channel structure
*/
-static void zynqmp_dma_do_tasklet(struct tasklet_struct *t)
+static void zynqmp_dma_do_tasklet(struct dma_chan *c)
{
- struct zynqmp_dma_chan *chan = from_tasklet(chan, t, tasklet);
+ struct zynqmp_dma_chan *chan = container_of(c,
+ struct zynqmp_dma_chan, common);
u32 count;
unsigned long irqflags;
@@ -815,7 +814,7 @@ static void zynqmp_dma_synchronize(struct dma_chan *dchan)
{
struct zynqmp_dma_chan *chan = to_chan(dchan);
- tasklet_kill(&chan->tasklet);
+ dmaengine_kill_bh(&chan->common);
}
/**
@@ -887,7 +886,7 @@ static void zynqmp_dma_chan_remove(struct zynqmp_dma_chan *chan)
if (chan->irq)
devm_free_irq(chan->zdev->dev, chan->irq, chan);
- tasklet_kill(&chan->tasklet);
+ dmaengine_kill_bh(&chan->common);
list_del(&chan->common.device_node);
}
@@ -937,7 +936,7 @@ static int zynqmp_dma_chan_probe(struct zynqmp_dma_device *zdev,
chan->is_dmacoherent = of_property_read_bool(node, "dma-coherent");
zdev->chan = chan;
- tasklet_setup(&chan->tasklet, zynqmp_dma_do_tasklet);
+ dmaengine_init_bh(&chan->common, zynqmp_dma_do_tasklet);
spin_lock_init(&chan->lock);
INIT_LIST_HEAD(&chan->active_list);
INIT_LIST_HEAD(&chan->pending_list);
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* [PATCH v3 32/34] dmaengine: hidma: defer callbacks via channel BH
[not found] ` <cover.1786384168.git.allen.lkml@gmail.com>
` (8 preceding siblings ...)
2026-08-10 18:09 ` [PATCH v3 21/34] dmaengine: zynqmp-dma: switch completion tasklet to dmaengine BH Allen Pais
@ 2026-08-10 18:09 ` Allen Pais
2026-09-02 15:47 ` Frank Li
9 siblings, 1 reply; 63+ messages in thread
From: Allen Pais @ 2026-08-10 18:09 UTC (permalink / raw)
To: Vinod Koul, Frank Li
Cc: Allen Pais, dmaengine, linux-kernel, Arnd Bergmann, Kees Cook,
Sinan Kaya, linux-arm-kernel, linux-arm-msm
Move descriptor callback processing out of the low-level completion
path and schedule it through per-channel BH work. Drain that work while
freeing channels so callbacks cannot outlive channel storage.
Signed-off-by: Allen Pais <allen.lkml@gmail.com>
---
drivers/dma/qcom/hidma.c | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/drivers/dma/qcom/hidma.c b/drivers/dma/qcom/hidma.c
index c939635be21d..486a2c61a9fb 100644
--- a/drivers/dma/qcom/hidma.c
+++ b/drivers/dma/qcom/hidma.c
@@ -90,7 +90,13 @@ static inline struct hidma_chan *to_hidma_chan(struct dma_chan *dmach)
static void hidma_free(struct hidma_dev *dmadev)
{
- INIT_LIST_HEAD(&dmadev->ddev.channels);
+ struct hidma_chan *mchan, *next;
+
+ list_for_each_entry_safe(mchan, next, &dmadev->ddev.channels,
+ chan.device_node) {
+ dmaengine_kill_bh(&mchan->chan);
+ list_del(&mchan->chan.device_node);
+ }
}
static unsigned int nr_desc_prm;
@@ -155,6 +161,13 @@ static void hidma_process_completed(struct hidma_chan *mchan)
}
}
+static void hidma_chan_bh(struct dma_chan *chan)
+{
+ struct hidma_chan *mchan = to_hidma_chan(chan);
+
+ hidma_process_completed(mchan);
+}
+
/*
* Called once for each submitted descriptor.
* PM is locked once for each descriptor that is currently
@@ -181,7 +194,7 @@ static void hidma_callback(void *data)
}
spin_unlock_irqrestore(&mchan->lock, irqflags);
- hidma_process_completed(mchan);
+ dmaengine_schedule_bh(&mchan->chan);
if (queued) {
pm_runtime_mark_last_busy(dmadev->ddev.dev);
@@ -203,6 +216,7 @@ static int hidma_chan_init(struct hidma_dev *dmadev, u32 dma_sig)
mchan->dmadev = dmadev;
mchan->chan.device = ddev;
dma_cookie_init(&mchan->chan);
+ dmaengine_init_bh(&mchan->chan, hidma_chan_bh);
INIT_LIST_HEAD(&mchan->free);
INIT_LIST_HEAD(&mchan->prepared);
--
2.43.0
^ permalink raw reply related [flat|nested] 63+ messages in thread
* Re: [PATCH v3 13/34] dmaengine: mxs-dma: use dmaengine BH scheduling
2026-08-10 18:09 ` [PATCH v3 13/34] dmaengine: mxs-dma: use dmaengine BH scheduling Allen Pais
@ 2026-08-10 18:27 ` sashiko-bot
2026-09-02 15:33 ` Frank Li
1 sibling, 0 replies; 63+ messages in thread
From: sashiko-bot @ 2026-08-10 18:27 UTC (permalink / raw)
To: Allen Pais; +Cc: Frank.Li, vkoul, dmaengine, imx
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] The driver does not kill the deferred bottom-half work during channel teardown or error paths, nor does it provide a device_synchronize callback, leading to a potential use-after-free.
- [High] Requesting the IRQ before enabling the device clock can result in a synchronous external abort and bus hang.
--
--- Patch [13]: [PATCH v3 13/34] dmaengine: mxs-dma: use dmaengine BH scheduling ---
commit 84e84c7d902dd9157e789fe8d760954306bc43d6
Author: Allen Pais <allen.lkml@gmail.com>
dmaengine: mxs-dma: use dmaengine BH scheduling
Replace the per-channel tasklet with the shared dmaengine BH helper.
The handler continues to run in softirq context while dmaengine owns
the common scheduling and teardown mechanism.
> diff --git a/drivers/dma/mxs-dma.c b/drivers/dma/mxs-dma.c
> index 7acb3d29dad30..149ac402f7b4b 100644
> --- a/drivers/dma/mxs-dma.c
> +++ b/drivers/dma/mxs-dma.c
[ ... ]
> @@ -386,8 +385,8 @@ static irqreturn_t mxs_dma_int_handler(int irq, void *dev_id)
> dma_cookie_complete(&mxs_chan->desc);
> }
>
> - /* schedule tasklet on this channel */
> - tasklet_schedule(&mxs_chan->tasklet);
> + /* schedule BH on this channel */
> + dmaengine_schedule_bh(&mxs_chan->chan);
>
> return IRQ_HANDLED;
> }
[Severity: High]
This is a pre-existing issue, but since the driver is moving to the BH
mechanism, should we also ensure the scheduled work is properly flushed during
teardown and error paths?
Looking at mxs_dma_alloc_chan_resources(), if clk_prepare_enable() fails,
the error path frees the IRQ and the channel ccw structure:
mxs_dma_alloc_chan_resources() {
...
err_clk:
free_irq(mxs_chan->chan_irq, mxs_dma);
err_irq:
dma_free_coherent(mxs_dma->dma_device.dev, CCW_BLOCK_SIZE,
mxs_chan->ccw, mxs_chan->ccw_phys);
...
}
Because free_irq() waits for the hardirq handler but not the scheduled
workqueue, if an early interrupt schedules the BH work, can the workqueue
execute after the channel resources have been freed, leading to a
use-after-free?
Similarly, mxs_dma_free_chan_resources() frees channel resources without
calling dmaengine_kill_bh(). The driver also lacks a device_synchronize
callback, which makes dmaengine_terminate_sync() a no-op.
Should we add dmaengine_kill_bh() and a synchronization callback to prevent
the scheduled work from running after the client frees the channel?
[ ... ]
> @@ -781,7 +780,7 @@ static int mxs_dma_probe(struct platform_device *pdev)
> mxs_chan->chan.device = &mxs_dma->dma_device;
> dma_cookie_init(&mxs_chan->chan);
>
> - tasklet_setup(&mxs_chan->tasklet, mxs_dma_tasklet);
> + dmaengine_init_bh(&mxs_chan->chan, mxs_dma_tasklet);
>
>
> /* Add the channel to mxs_chan list */
[Severity: High]
This isn't a bug introduced by this patch, but I wanted to ask about the
initialization ordering which can trigger the interrupt handler early.
When a channel is allocated in mxs_dma_alloc_chan_resources(), the IRQ is
requested before the clock is enabled:
mxs_dma_alloc_chan_resources() {
...
ret = request_irq(mxs_chan->chan_irq, mxs_dma_int_handler,
0, "mxs-dma", mxs_dma);
if (ret)
goto err_irq;
ret = clk_prepare_enable(mxs_dma->clk);
...
}
If an interrupt line is asserted early (for example, from a previously
aborted transaction where the client didn't call terminate_all),
mxs_dma_int_handler() will fire immediately. It then performs an MMIO read
on the memory-mapped controller registers (mxs_dma->base + HW_APBHX_CTRL1).
Since accessing device registers with a disabled clock results in a bus hang
on this platform, would it be safer to enable the clock before requesting
the IRQ to prevent a synchronous external abort?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1786384168.git.allen.lkml@gmail.com?part=13
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PATCH v3 01/34] dmaengine: add tasklet-backed channel BH helpers
2026-08-10 18:09 ` [PATCH v3 01/34] dmaengine: add tasklet-backed channel BH helpers Allen Pais
@ 2026-08-10 18:30 ` sashiko-bot
2026-09-01 22:13 ` Frank Li
2026-09-02 15:16 ` Frank Li
2 siblings, 0 replies; 63+ messages in thread
From: sashiko-bot @ 2026-08-10 18:30 UTC (permalink / raw)
To: Allen Pais; +Cc: Frank.Li, imx, dmaengine, vkoul, linux-sunxi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Use-After-Free due to channel bottom-half (tasklet) being killed before hardware interrupts are disabled and synchronized during device removal.
--
--- Patch [1]: [PATCH v3 01/34] dmaengine: add tasklet-backed channel BH helpers ---
commit 552ba608049b66637e1d6db5b616db6c9c33204d
Author: Allen Pais <allen.lkml@gmail.com>
dmaengine: add tasklet-backed channel BH helpers
This commit introduces backend-independent helpers to initialize, schedule,
and kill channel bottom halves. It converts virt-dma and updates various
drivers to use the new API instead of directly overriding or killing
tasklets.
> diff --git a/drivers/dma/mediatek/mtk-cqdma.c b/drivers/dma/mediatek/mtk-cqdma.c
> --- a/drivers/dma/mediatek/mtk-cqdma.c
> +++ b/drivers/dma/mediatek/mtk-cqdma.c
> @@ -895,7 +895,7 @@ static void mtk_cqdma_remove(struct platform_device *pdev)
> vc = &cqdma->vc[i];
>
> list_del(&vc->vc.chan.device_node);
> - tasklet_kill(&vc->vc.task);
> + dmaengine_kill_bh(&vc->vc.chan);
> }
>
> /* disable interrupt */
[Severity: High]
This is a pre-existing issue, but could this teardown sequence result in a
use-after-free?
In mtk_cqdma_remove(), the channel bottom-halves are killed before the
physical channel interrupts are masked and synchronized. If an interrupt fires
within this window, the interrupt handler processes descriptors and can
reschedule the BH via dmaengine_schedule_bh().
Since the device is being removed, devres will subsequently free the channel
memory, potentially causing the recently scheduled BH to execute on freed
memory.
[ ... ]
> diff --git a/drivers/dma/mediatek/mtk-hsdma.c b/drivers/dma/mediatek/mtk-hsdma.c
> --- a/drivers/dma/mediatek/mtk-hsdma.c
> +++ b/drivers/dma/mediatek/mtk-hsdma.c
> @@ -1020,7 +1020,7 @@ static void mtk_hsdma_remove(struct platform_device *pdev)
> vc = &hsdma->vc[i];
>
> list_del(&vc->vc.chan.device_node);
> - tasklet_kill(&vc->vc.task);
> + dmaengine_kill_bh(&vc->vc.chan);
> }
>
> /* Disable DMA interrupt */
[Severity: High]
This is a pre-existing issue, but is it safe to kill the BH here before
disabling and synchronizing the DMA interrupts?
Similar to mtk_cqdma_remove() above, this opens a window where an incoming
interrupt in mtk_hsdma_remove() can reschedule the BH just before the device
is torn down.
[ ... ]
> diff --git a/drivers/dma/pxa_dma.c b/drivers/dma/pxa_dma.c
> --- a/drivers/dma/pxa_dma.c
> +++ b/drivers/dma/pxa_dma.c
> @@ -1215,7 +1215,7 @@ static void pxad_free_channels(struct dma_device *dmadev)
> list_for_each_entry_safe(c, cn, &dmadev->channels,
> vc.chan.device_node) {
> list_del(&c->vc.chan.device_node);
> - tasklet_kill(&c->vc.task);
> + dmaengine_kill_bh(&c->vc.chan);
> }
> }
[Severity: High]
This is a pre-existing issue, but does pxad_free_channels() suffer from a
similar race?
This function is called from pxad_remove(). The driver relies entirely on
devres to free the IRQ after the remove function returns, meaning interrupts
remain fully active during and after the BH is killed.
A triggered interrupt could reschedule the BH right before devres tears down
the channel structures.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1786384168.git.allen.lkml@gmail.com?part=1
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PATCH v3 08/34] dmaengine: imx-dma: flip per-chan tasklet to dmaengine BH
2026-08-10 18:09 ` [PATCH v3 08/34] dmaengine: imx-dma: flip per-chan tasklet " Allen Pais
@ 2026-08-10 18:34 ` sashiko-bot
2026-09-02 15:17 ` Frank Li
2026-09-02 15:28 ` Frank Li
2 siblings, 0 replies; 63+ messages in thread
From: sashiko-bot @ 2026-08-10 18:34 UTC (permalink / raw)
To: Allen Pais; +Cc: Frank.Li, vkoul, imx, dmaengine
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Pre-existing issues:
- [High] The watchdog timer is not safely canceled during teardown or error paths, leading to Use-After-Free.
- [High] Completed descriptors are moved to the free list before callback invocation, allowing lockless data races.
- [High] Bottom-half work is not flushed before freeing channel descriptors, leading to Use-After-Free.
- [High] IRQ is requested before initializing channel data structures and masking interrupts, leading to a NULL pointer dereference.
--
--- Patch [8]: [PATCH v3 08/34] dmaengine: imx-dma: flip per-chan tasklet to dmaengine BH ---
commit a54288aab8aa66cb0228a7320efe5075981ac5ce
Author: Allen Pais <allen.lkml@gmail.com>
dmaengine: imx-dma: flip per-chan tasklet to dmaengine BH
Replace the per-channel tasklet with the shared dmaengine BH helper.
The handler continues to run in softirq context while dmaengine owns
the common scheduling and teardown mechanism.
> diff --git a/drivers/dma/imx-dma.c b/drivers/dma/imx-dma.c
> index 81c6276436f81..308fe407fe161 100644
> --- a/drivers/dma/imx-dma.c
> +++ b/drivers/dma/imx-dma.c
[ ... ]
> @@ -592,9 +591,10 @@ static int imxdma_xfer_desc(struct imxdma_desc *d)
> return 0;
> }
>
> -static void imxdma_tasklet(struct tasklet_struct *t)
> +static void imxdma_tasklet(struct dma_chan *chan)
> {
> - struct imxdma_channel *imxdmac = from_tasklet(imxdmac, t, dma_tasklet);
> + struct imxdma_channel *imxdmac = container_of(chan, struct imxdma_channel,
> + chan);
> struct imxdma_engine *imxdma = imxdmac->imxdma;
> struct imxdma_desc *desc, *next_desc;
> unsigned long flags;
[Severity: High]
This isn't a bug introduced by this patch, but could this lockless data race
cause a loss of completion callbacks?
In imxdma_tasklet(), the completed descriptor is placed on the ld_free list
before the channel lock is dropped and the callback is invoked:
imxdma_tasklet()
list_move_tail(imxdmac->ld_active.next, &imxdmac->ld_free);
spin_unlock_irqrestore(&imxdma->lock, flags);
dmaengine_desc_get_callback_invoke(&desc->desc, NULL);
If another thread calls imxdma_prep_dma_memcpy() concurrently, it locklessly
inspects ld_free. If ld_free was empty, it might immediately pick up this
newly freed descriptor and overwrite its fields (desc->desc.callback = NULL)
before imxdma_tasklet() invokes the callback.
Does this need to hold the descriptor off the free list until after the
callback completes?
[ ... ]
> @@ -1142,7 +1142,7 @@ static int __init imxdma_probe(struct platform_device *pdev)
> INIT_LIST_HEAD(&imxdmac->ld_free);
> INIT_LIST_HEAD(&imxdmac->ld_active);
>
> - tasklet_setup(&imxdmac->dma_tasklet, imxdma_tasklet);
> + dmaengine_init_bh(&imxdmac->chan, imxdma_tasklet);
> imxdmac->chan.device = &imxdma->dma_device;
> dma_cookie_init(&imxdmac->chan);
> imxdmac->channel = i;
[Severity: High]
This is a pre-existing issue, but does the interrupt handler get registered
too early in imxdma_probe()?
For IMX1 DMA, devm_request_irq() is called before the interrupt mask register
(DMA_DIMR) is disabled and before channel list heads like ld_active are
initialized.
If an interrupt is pending or asserts immediately, dma_irq_handler() runs
and calls dma_irq_handle_channel(). This evaluates list_empty() on
uninitialized memory, incorrectly returning false, and then dereferences an
invalid pointer from list_first_entry(), causing a crash.
Should the IRQ registration be moved after the hardware masking and list
initializations?
[ ... ]
> @@ -1211,7 +1211,7 @@ static void imxdma_free_irq(struct platform_device *pdev, struct imxdma_engine *
> if (!is_imx1_dma(imxdma))
> disable_irq(imxdmac->irq);
>
> - tasklet_kill(&imxdmac->dma_tasklet);
> + dmaengine_kill_bh(&imxdmac->chan);
> }
> }
[Severity: High]
This isn't a bug introduced by this patch, but could this lead to a
use-after-free with the watchdog timer?
In imxdma_free_irq(), the dmaengine BH is killed, but the imxdmac->watchdog
timer is never safely canceled. When devm frees the imxdma structure, a
still-active timer could fire, executing imxdma_watchdog(). This would
dereference freed memory to call dmaengine_schedule_bh().
Similarly in imxdma_probe(), if devm_request_irq() fails for a later channel,
the error path jumps to disable_dma_ahb_clk and omits timer cleanup for
already initialized channels.
Does this require timer_shutdown_sync() during teardown and error paths?
[Severity: High]
This isn't a bug introduced by this patch, but could descriptors be freed
while the bottom-half work is still running?
When a channel is released, imxdma_free_chan_resources() iterates through
ld_free and frees the descriptors with kfree(). However, the driver does
not implement device_synchronize, and imxdma_free_chan_resources() does
not flush pending bottom-half work.
If imxdma_tasklet() is executing concurrently, imxdma_free_chan_resources()
could acquire the lock, splice the lists, and free the descriptor just as
the tasklet drops the spinlock to invoke the callback. The bottom-half
would then dereference the freed desc->desc to invoke the callback.
Does imxdma_free_chan_resources() need to flush pending BH work before
freeing descriptors?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1786384168.git.allen.lkml@gmail.com?part=8
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PATCH v3 01/34] dmaengine: add tasklet-backed channel BH helpers
2026-08-10 18:09 ` [PATCH v3 01/34] dmaengine: add tasklet-backed channel BH helpers Allen Pais
2026-08-10 18:30 ` sashiko-bot
@ 2026-09-01 22:13 ` Frank Li
2026-09-02 15:16 ` Frank Li
2 siblings, 0 replies; 63+ messages in thread
From: Frank Li @ 2026-09-01 22:13 UTC (permalink / raw)
To: Allen Pais
Cc: Vinod Koul, Frank Li, dmaengine, linux-kernel, Arnd Bergmann,
Kees Cook, Florian Fainelli, Ray Jui, Scott Branden,
Broadcom internal kernel review list, Lars-Peter Clausen,
Paul Cercueil, Eugeniy Paltsev, Manivannan Sadhasivam, Zhou Wang,
Longfang Liu, Andy Shevchenko, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Keguang Zhang, Sean Wang,
Matthias Brugger, AngeloGioacchino Del Regno, Andreas Färber,
Daniel Mack, Haojian Zhuang, Robert Jarzmik, Paul Walmsley,
Samuel Holland, Orson Zhai, Baolin Wang, Chunyan Zhang,
Patrice Chotard, Chen-Yu Tsai, Jernej Skrabec, Laxman Dewangan,
Jon Hunter, Thierry Reding, Vignesh Raghavendra,
Bartosz Golaszewski, Konrad Dybcio, Bjorn Andersson,
Kuldeep Singh, Stephan Gerhold, linux-rpi-kernel,
linux-arm-kernel, linux-mips, imx, linux-mediatek, linux-actions,
linux-arm-msm, linux-riscv, linux-sunxi, linux-tegra
On Mon, Aug 10, 2026 at 11:09:02AM -0700, Allen Pais wrote:
> DMAengine drivers commonly use a per-channel tasklet to invoke client
> callbacks. Add helpers that initialize, schedule, and kill a channel
> bottom half, with an initial tasklet-backed implementation that preserves
> the existing execution context.
>
> Convert virt-dma to the new API and remove its private tasklet. Update all
> drivers that directly kill or override that tasklet in the same change so
> no stale users remain. While touching the completion handler, avoid forming
> a result pointer from a NULL cyclic descriptor.
>
> This establishes a backend-independent API before changing how channel
> bottom halves are dispatched.
>
> Signed-off-by: Allen Pais <allen.lkml@gmail.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/dma/bcm2835-dma.c | 2 +-
> drivers/dma/dma-axi-dmac.c | 10 +++--
> drivers/dma/dma-jz4780.c | 2 +-
> drivers/dma/dmaengine.c | 42 +++++++++++++++++++
> .../dma/dw-axi-dmac/dw-axi-dmac-platform.c | 2 +-
> drivers/dma/dw-edma/dw-edma-core.c | 2 +-
> drivers/dma/fsl-edma-common.c | 2 +-
> drivers/dma/fsl-qdma.c | 2 +-
> drivers/dma/hisi_dma.c | 2 +-
> drivers/dma/hsu/hsu.c | 2 +-
> drivers/dma/idma64.c | 4 +-
> drivers/dma/img-mdc-dma.c | 2 +-
> drivers/dma/imx-sdma.c | 4 +-
> drivers/dma/k3dma.c | 2 +-
> drivers/dma/loongson/loongson1-apb-dma.c | 2 +-
> drivers/dma/mediatek/mtk-cqdma.c | 2 +-
> drivers/dma/mediatek/mtk-hsdma.c | 2 +-
> drivers/dma/mediatek/mtk-uart-apdma.c | 4 +-
> drivers/dma/owl-dma.c | 2 +-
> drivers/dma/pxa_dma.c | 2 +-
> drivers/dma/qcom/bam_dma.c | 4 +-
> drivers/dma/qcom/qcom_adm.c | 4 +-
> drivers/dma/sa11x0-dma.c | 2 +-
> drivers/dma/sf-pdma/sf-pdma.c | 2 +-
> drivers/dma/sprd-dma.c | 2 +-
> drivers/dma/st_fdma.c | 2 +-
> drivers/dma/sun6i-dma.c | 2 +-
> drivers/dma/tegra186-gpc-dma.c | 2 +-
> drivers/dma/tegra210-adma.c | 2 +-
> drivers/dma/ti/edma.c | 2 +-
> drivers/dma/ti/k3-udma.c | 12 +++---
> drivers/dma/ti/omap-dma.c | 2 +-
> drivers/dma/virt-dma.c | 12 +++---
> drivers/dma/virt-dma.h | 7 ++--
> include/linux/dmaengine.h | 28 +++++++++++++
> 35 files changed, 125 insertions(+), 54 deletions(-)
>
> diff --git a/drivers/dma/bcm2835-dma.c b/drivers/dma/bcm2835-dma.c
> index 06d830d36882..c8add249dbfb 100644
> --- a/drivers/dma/bcm2835-dma.c
> +++ b/drivers/dma/bcm2835-dma.c
> @@ -829,7 +829,7 @@ static void bcm2835_dma_free(struct bcm2835_dmadev *od)
> list_for_each_entry_safe(c, next, &od->ddev.channels,
> vc.chan.device_node) {
> list_del(&c->vc.chan.device_node);
> - tasklet_kill(&c->vc.task);
> + dmaengine_kill_bh(&c->vc.chan);
> }
>
> dma_unmap_page_attrs(od->ddev.dev, od->zero_page, PAGE_SIZE,
> diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c
> index d47ff27e1408..d245d04c3a27 100644
> --- a/drivers/dma/dma-axi-dmac.c
> +++ b/drivers/dma/dma-axi-dmac.c
> @@ -1195,9 +1195,11 @@ static int axi_dmac_detect_caps(struct axi_dmac *dmac, unsigned int version)
> return 0;
> }
>
> -static void axi_dmac_tasklet_kill(void *task)
> +static void axi_dmac_kill_bh(void *data)
> {
> - tasklet_kill(task);
> + struct dma_chan *chan = data;
> +
> + dmaengine_kill_bh(chan);
> }
>
> static void axi_dmac_free_dma_controller(void *of_node)
> @@ -1302,8 +1304,8 @@ static int axi_dmac_probe(struct platform_device *pdev)
> * Put the action in here so it get's done before unregistering the DMA
> * device.
> */
> - ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_tasklet_kill,
> - &dmac->chan.vchan.task);
> + ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_kill_bh,
> + &dmac->chan.vchan.chan);
> if (ret)
> return ret;
>
> diff --git a/drivers/dma/dma-jz4780.c b/drivers/dma/dma-jz4780.c
> index 6070dfdb7114..738801501e29 100644
> --- a/drivers/dma/dma-jz4780.c
> +++ b/drivers/dma/dma-jz4780.c
> @@ -1019,7 +1019,7 @@ static void jz4780_dma_remove(struct platform_device *pdev)
> free_irq(jzdma->irq, jzdma);
>
> for (i = 0; i < jzdma->soc_data->nb_channels; i++)
> - tasklet_kill(&jzdma->chan[i].vchan.task);
> + dmaengine_kill_bh(&jzdma->chan[i].vchan.chan);
> }
>
> static const struct jz4780_dma_soc_data jz4740_dma_soc_data = {
> diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c
> index 9049171df857..d8fc7eb71b48 100644
> --- a/drivers/dma/dmaengine.c
> +++ b/drivers/dma/dmaengine.c
> @@ -1428,6 +1428,48 @@ static void dmaengine_destroy_unmap_pool(void)
> }
> }
>
> +static void dma_chan_bh_entry(struct tasklet_struct *tasklet)
> +{
> + struct dma_chan *chan = from_tasklet(chan, tasklet, bh_tasklet);
> + dmaengine_bh_work_fn fn = READ_ONCE(chan->bh_work_fn);
> +
> + if (fn)
> + fn(chan);
> +}
> +
> +void dmaengine_init_bh(struct dma_chan *chan, dmaengine_bh_work_fn fn)
> +{
> + if (WARN_ON(!fn))
> + return;
> +
> + if (WARN_ON(chan->bh_work_initialized))
> + return;
> +
> + chan->bh_work_fn = fn;
> + tasklet_setup(&chan->bh_tasklet, dma_chan_bh_entry);
> + chan->bh_work_initialized = true;
> +}
> +EXPORT_SYMBOL_GPL(dmaengine_init_bh);
> +
> +bool dmaengine_schedule_bh(struct dma_chan *chan)
> +{
> + if (WARN_ON(!chan->bh_work_initialized))
> + return false;
> +
> + tasklet_schedule(&chan->bh_tasklet);
> + return true;
> +}
> +EXPORT_SYMBOL_GPL(dmaengine_schedule_bh);
> +
> +void dmaengine_kill_bh(struct dma_chan *chan)
> +{
> + if (!chan->bh_work_initialized)
> + return;
> +
> + tasklet_kill(&chan->bh_tasklet);
> +}
> +EXPORT_SYMBOL_GPL(dmaengine_kill_bh);
> +
> static int __init dmaengine_init_unmap_pool(void)
> {
> int i;
> diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
> index bcefaff03b5c..a2b688e7f47e 100644
> --- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
> +++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
> @@ -1663,7 +1663,7 @@ static void dw_remove(struct platform_device *pdev)
> list_for_each_entry_safe(chan, _chan, &dw->dma.channels,
> vc.chan.device_node) {
> list_del(&chan->vc.chan.device_node);
> - tasklet_kill(&chan->vc.task);
> + dmaengine_kill_bh(&chan->vc.chan);
> }
> }
>
> diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
> index 89a4c498a17b..ade866fba2ad 100644
> --- a/drivers/dma/dw-edma/dw-edma-core.c
> +++ b/drivers/dma/dw-edma/dw-edma-core.c
> @@ -1170,7 +1170,7 @@ int dw_edma_remove(struct dw_edma_chip *chip)
> dma_async_device_unregister(&dw->dma);
> list_for_each_entry_safe(chan, _chan, &dw->dma.channels,
> vc.chan.device_node) {
> - tasklet_kill(&chan->vc.task);
> + dmaengine_kill_bh(&chan->vc.chan);
> list_del(&chan->vc.chan.device_node);
> }
>
> diff --git a/drivers/dma/fsl-edma-common.c b/drivers/dma/fsl-edma-common.c
> index bb7531c456df..90ae678c68d4 100644
> --- a/drivers/dma/fsl-edma-common.c
> +++ b/drivers/dma/fsl-edma-common.c
> @@ -915,7 +915,7 @@ void fsl_edma_cleanup_vchan(struct dma_device *dmadev)
> list_for_each_entry_safe(chan, _chan,
> &dmadev->channels, vchan.chan.device_node) {
> list_del(&chan->vchan.chan.device_node);
> - tasklet_kill(&chan->vchan.task);
> + dmaengine_kill_bh(&chan->vchan.chan);
> }
> }
>
> diff --git a/drivers/dma/fsl-qdma.c b/drivers/dma/fsl-qdma.c
> index df843fad0ece..b0ace8bad498 100644
> --- a/drivers/dma/fsl-qdma.c
> +++ b/drivers/dma/fsl-qdma.c
> @@ -1255,7 +1255,7 @@ static void fsl_qdma_cleanup_vchan(struct dma_device *dmadev)
> list_for_each_entry_safe(chan, _chan,
> &dmadev->channels, vchan.chan.device_node) {
> list_del(&chan->vchan.chan.device_node);
> - tasklet_kill(&chan->vchan.task);
> + dmaengine_kill_bh(&chan->vchan.chan);
> }
> }
>
> diff --git a/drivers/dma/hisi_dma.c b/drivers/dma/hisi_dma.c
> index 28bf818f9aa6..10eb98350b9f 100644
> --- a/drivers/dma/hisi_dma.c
> +++ b/drivers/dma/hisi_dma.c
> @@ -720,7 +720,7 @@ static void hisi_dma_disable_qps(struct hisi_dma_dev *hdma_dev)
>
> for (i = 0; i < hdma_dev->chan_num; i++) {
> hisi_dma_disable_qp(hdma_dev, i);
> - tasklet_kill(&hdma_dev->chan[i].vc.task);
> + dmaengine_kill_bh(&hdma_dev->chan[i].vc.chan);
> }
> }
>
> diff --git a/drivers/dma/hsu/hsu.c b/drivers/dma/hsu/hsu.c
> index f62d60d7bc6b..315d0ebecd57 100644
> --- a/drivers/dma/hsu/hsu.c
> +++ b/drivers/dma/hsu/hsu.c
> @@ -500,7 +500,7 @@ int hsu_dma_remove(struct hsu_dma_chip *chip)
> for (i = 0; i < hsu->nr_channels; i++) {
> struct hsu_dma_chan *hsuc = &hsu->chan[i];
>
> - tasklet_kill(&hsuc->vchan.task);
> + dmaengine_kill_bh(&hsuc->vchan.chan);
> }
>
> return 0;
> diff --git a/drivers/dma/idma64.c b/drivers/dma/idma64.c
> index 5fcd1befc92d..1d395fb735ff 100644
> --- a/drivers/dma/idma64.c
> +++ b/drivers/dma/idma64.c
> @@ -617,14 +617,14 @@ static void idma64_remove(struct idma64_chip *chip)
>
> /*
> * Explicitly call devm_request_irq() to avoid the side effects with
> - * the scheduled tasklets.
> + * scheduled BH work.
> */
> devm_free_irq(chip->dev, chip->irq, idma64);
>
> for (i = 0; i < idma64->dma.chancnt; i++) {
> struct idma64_chan *idma64c = &idma64->chan[i];
>
> - tasklet_kill(&idma64c->vchan.task);
> + dmaengine_kill_bh(&idma64c->vchan.chan);
> }
> }
>
> diff --git a/drivers/dma/img-mdc-dma.c b/drivers/dma/img-mdc-dma.c
> index b3765ba15803..0c6024088444 100644
> --- a/drivers/dma/img-mdc-dma.c
> +++ b/drivers/dma/img-mdc-dma.c
> @@ -1031,7 +1031,7 @@ static void mdc_dma_remove(struct platform_device *pdev)
>
> devm_free_irq(&pdev->dev, mchan->irq, mchan);
>
> - tasklet_kill(&mchan->vc.task);
> + dmaengine_kill_bh(&mchan->vc.chan);
> }
>
> pm_runtime_disable(&pdev->dev);
> diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c
> index 36368835a845..4d13b9d2880d 100644
> --- a/drivers/dma/imx-sdma.c
> +++ b/drivers/dma/imx-sdma.c
> @@ -2399,11 +2399,11 @@ static void sdma_remove(struct platform_device *pdev)
> int i;
>
> devm_free_irq(&pdev->dev, sdma->irq, sdma);
> - /* Kill the tasklet */
> + /* Kill the channel BH */
> for (i = 0; i < MAX_DMA_CHANNELS; i++) {
> struct sdma_channel *sdmac = &sdma->channel[i];
>
> - tasklet_kill(&sdmac->vc.task);
> + dmaengine_kill_bh(&sdmac->vc.chan);
> sdma_free_chan_resources(&sdmac->vc.chan);
> }
>
> diff --git a/drivers/dma/k3dma.c b/drivers/dma/k3dma.c
> index e84f197fea76..3d73b391e42e 100644
> --- a/drivers/dma/k3dma.c
> +++ b/drivers/dma/k3dma.c
> @@ -976,7 +976,7 @@ static void k3_dma_remove(struct platform_device *op)
>
> list_for_each_entry_safe(c, cn, &d->slave.channels, vc.chan.device_node) {
> list_del(&c->vc.chan.device_node);
> - tasklet_kill(&c->vc.task);
> + dmaengine_kill_bh(&c->vc.chan);
> }
> tasklet_kill(&d->task);
> clk_disable_unprepare(d->clk);
> diff --git a/drivers/dma/loongson/loongson1-apb-dma.c b/drivers/dma/loongson/loongson1-apb-dma.c
> index 89786cbd20ab..52a360719644 100644
> --- a/drivers/dma/loongson/loongson1-apb-dma.c
> +++ b/drivers/dma/loongson/loongson1-apb-dma.c
> @@ -552,7 +552,7 @@ static void ls1x_dma_chan_remove(struct ls1x_dma *dma)
>
> if (chan->vc.chan.device == &dma->ddev) {
> list_del(&chan->vc.chan.device_node);
> - tasklet_kill(&chan->vc.task);
> + dmaengine_kill_bh(&chan->vc.chan);
> }
> }
> }
> diff --git a/drivers/dma/mediatek/mtk-cqdma.c b/drivers/dma/mediatek/mtk-cqdma.c
> index 80791e30aec2..7b44d3358cda 100644
> --- a/drivers/dma/mediatek/mtk-cqdma.c
> +++ b/drivers/dma/mediatek/mtk-cqdma.c
> @@ -895,7 +895,7 @@ static void mtk_cqdma_remove(struct platform_device *pdev)
> vc = &cqdma->vc[i];
>
> list_del(&vc->vc.chan.device_node);
> - tasklet_kill(&vc->vc.task);
> + dmaengine_kill_bh(&vc->vc.chan);
> }
>
> /* disable interrupt */
> diff --git a/drivers/dma/mediatek/mtk-hsdma.c b/drivers/dma/mediatek/mtk-hsdma.c
> index a43412ff5edd..75555fa41536 100644
> --- a/drivers/dma/mediatek/mtk-hsdma.c
> +++ b/drivers/dma/mediatek/mtk-hsdma.c
> @@ -1020,7 +1020,7 @@ static void mtk_hsdma_remove(struct platform_device *pdev)
> vc = &hsdma->vc[i];
>
> list_del(&vc->vc.chan.device_node);
> - tasklet_kill(&vc->vc.task);
> + dmaengine_kill_bh(&vc->vc.chan);
> }
>
> /* Disable DMA interrupt */
> diff --git a/drivers/dma/mediatek/mtk-uart-apdma.c b/drivers/dma/mediatek/mtk-uart-apdma.c
> index c269d84d7bd2..8ce206a0ecc5 100644
> --- a/drivers/dma/mediatek/mtk-uart-apdma.c
> +++ b/drivers/dma/mediatek/mtk-uart-apdma.c
> @@ -312,7 +312,7 @@ static void mtk_uart_apdma_free_chan_resources(struct dma_chan *chan)
>
> free_irq(c->irq, chan);
>
> - tasklet_kill(&c->vc.task);
> + dmaengine_kill_bh(&c->vc.chan);
>
> vchan_free_chan_resources(&c->vc);
>
> @@ -463,7 +463,7 @@ static void mtk_uart_apdma_free(struct mtk_uart_apdmadev *mtkd)
> struct mtk_chan, vc.chan.device_node);
>
> list_del(&c->vc.chan.device_node);
> - tasklet_kill(&c->vc.task);
> + dmaengine_kill_bh(&c->vc.chan);
> }
> }
>
> diff --git a/drivers/dma/owl-dma.c b/drivers/dma/owl-dma.c
> index 7c80572fc71d..0d9f324adba0 100644
> --- a/drivers/dma/owl-dma.c
> +++ b/drivers/dma/owl-dma.c
> @@ -1055,7 +1055,7 @@ static inline void owl_dma_free(struct owl_dma *od)
> list_for_each_entry_safe(vchan,
> next, &od->dma.channels, vc.chan.device_node) {
> list_del(&vchan->vc.chan.device_node);
> - tasklet_kill(&vchan->vc.task);
> + dmaengine_kill_bh(&vchan->vc.chan);
> }
> }
>
> diff --git a/drivers/dma/pxa_dma.c b/drivers/dma/pxa_dma.c
> index fa2ee0b3e09f..2cca8c31929c 100644
> --- a/drivers/dma/pxa_dma.c
> +++ b/drivers/dma/pxa_dma.c
> @@ -1215,7 +1215,7 @@ static void pxad_free_channels(struct dma_device *dmadev)
> list_for_each_entry_safe(c, cn, &dmadev->channels,
> vc.chan.device_node) {
> list_del(&c->vc.chan.device_node);
> - tasklet_kill(&c->vc.task);
> + dmaengine_kill_bh(&c->vc.chan);
> }
> }
>
> diff --git a/drivers/dma/qcom/bam_dma.c b/drivers/dma/qcom/bam_dma.c
> index 1bb26af0405f..c23496c3ac69 100644
> --- a/drivers/dma/qcom/bam_dma.c
> +++ b/drivers/dma/qcom/bam_dma.c
> @@ -1387,7 +1387,7 @@ static int bam_dma_probe(struct platform_device *pdev)
> dma_async_device_unregister(&bdev->common);
> err_bam_channel_exit:
> for (i = 0; i < bdev->num_channels; i++)
> - tasklet_kill(&bdev->channels[i].vc.task);
> + dmaengine_kill_bh(&bdev->channels[i].vc.chan);
> err_tasklet_kill:
> tasklet_kill(&bdev->task);
> err_disable_clk:
> @@ -1413,7 +1413,7 @@ static void bam_dma_remove(struct platform_device *pdev)
>
> for (i = 0; i < bdev->num_channels; i++) {
> bam_dma_terminate_all(&bdev->channels[i].vc.chan);
> - tasklet_kill(&bdev->channels[i].vc.task);
> + dmaengine_kill_bh(&bdev->channels[i].vc.chan);
>
> if (!bdev->channels[i].fifo_virt)
> continue;
> diff --git a/drivers/dma/qcom/qcom_adm.c b/drivers/dma/qcom/qcom_adm.c
> index 07fbe32d31fa..13f5ca8ff808 100644
> --- a/drivers/dma/qcom/qcom_adm.c
> +++ b/drivers/dma/qcom/qcom_adm.c
> @@ -918,8 +918,8 @@ static void adm_dma_remove(struct platform_device *pdev)
> /* mask IRQs for this channel/EE pair */
> writel(0, adev->regs + ADM_CH_RSLT_CONF(achan->id, adev->ee));
>
> - tasklet_kill(&adev->channels[i].vc.task);
> - adm_terminate_all(&adev->channels[i].vc.chan);
> + dmaengine_kill_bh(&achan->vc.chan);
> + adm_terminate_all(&achan->vc.chan);
> }
>
> devm_free_irq(adev->dev, adev->irq, adev);
> diff --git a/drivers/dma/sa11x0-dma.c b/drivers/dma/sa11x0-dma.c
> index a6fa431530e3..e14566fa2d74 100644
> --- a/drivers/dma/sa11x0-dma.c
> +++ b/drivers/dma/sa11x0-dma.c
> @@ -891,7 +891,7 @@ static void sa11x0_dma_free_channels(struct dma_device *dmadev)
>
> list_for_each_entry_safe(c, cn, &dmadev->channels, vc.chan.device_node) {
> list_del(&c->vc.chan.device_node);
> - tasklet_kill(&c->vc.task);
> + dmaengine_kill_bh(&c->vc.chan);
> kfree(c);
> }
> }
> diff --git a/drivers/dma/sf-pdma/sf-pdma.c b/drivers/dma/sf-pdma/sf-pdma.c
> index 6f79cc28703e..a08ca355dadb 100644
> --- a/drivers/dma/sf-pdma/sf-pdma.c
> +++ b/drivers/dma/sf-pdma/sf-pdma.c
> @@ -602,7 +602,7 @@ static void sf_pdma_remove(struct platform_device *pdev)
> devm_free_irq(&pdev->dev, ch->txirq, ch);
> devm_free_irq(&pdev->dev, ch->errirq, ch);
> list_del(&ch->vchan.chan.device_node);
> - tasklet_kill(&ch->vchan.task);
> + dmaengine_kill_bh(&ch->vchan.chan);
> tasklet_kill(&ch->done_tasklet);
> tasklet_kill(&ch->err_tasklet);
> }
> diff --git a/drivers/dma/sprd-dma.c b/drivers/dma/sprd-dma.c
> index 087fea3af2e4..f90f5d8d5a1e 100644
> --- a/drivers/dma/sprd-dma.c
> +++ b/drivers/dma/sprd-dma.c
> @@ -1253,7 +1253,7 @@ static void sprd_dma_remove(struct platform_device *pdev)
> list_for_each_entry_safe(c, cn, &sdev->dma_dev.channels,
> vc.chan.device_node) {
> list_del(&c->vc.chan.device_node);
> - tasklet_kill(&c->vc.task);
> + dmaengine_kill_bh(&c->vc.chan);
> }
>
> of_dma_controller_free(pdev->dev.of_node);
> diff --git a/drivers/dma/st_fdma.c b/drivers/dma/st_fdma.c
> index d9547017f3bd..cae0a7fe6ceb 100644
> --- a/drivers/dma/st_fdma.c
> +++ b/drivers/dma/st_fdma.c
> @@ -733,7 +733,7 @@ static void st_fdma_free(struct st_fdma_dev *fdev)
> for (i = 0; i < fdev->nr_channels; i++) {
> fchan = &fdev->chans[i];
> list_del(&fchan->vchan.chan.device_node);
> - tasklet_kill(&fchan->vchan.task);
> + dmaengine_kill_bh(&fchan->vchan.chan);
> }
> }
>
> diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
> index f47a326dd7ff..4ddea3cdcd66 100644
> --- a/drivers/dma/sun6i-dma.c
> +++ b/drivers/dma/sun6i-dma.c
> @@ -1094,7 +1094,7 @@ static inline void sun6i_dma_free(struct sun6i_dma_dev *sdev)
> struct sun6i_vchan *vchan = &sdev->vchans[i];
>
> list_del(&vchan->vc.chan.device_node);
> - tasklet_kill(&vchan->vc.task);
> + dmaengine_kill_bh(&vchan->vc.chan);
> }
> }
>
> diff --git a/drivers/dma/tegra186-gpc-dma.c b/drivers/dma/tegra186-gpc-dma.c
> index 64cedef1050a..3a27a83a3288 100644
> --- a/drivers/dma/tegra186-gpc-dma.c
> +++ b/drivers/dma/tegra186-gpc-dma.c
> @@ -1284,7 +1284,7 @@ static void tegra_dma_free_chan_resources(struct dma_chan *dc)
> tegra_dma_terminate_all(dc);
> synchronize_irq(tdc->irq);
>
> - tasklet_kill(&tdc->vc.task);
> + dmaengine_kill_bh(&tdc->vc.chan);
> tdc->config_init = false;
> tdc->slave_id = -1;
> tdc->sid_dir = DMA_TRANS_NONE;
> diff --git a/drivers/dma/tegra210-adma.c b/drivers/dma/tegra210-adma.c
> index ceaee1e33e68..780863199224 100644
> --- a/drivers/dma/tegra210-adma.c
> +++ b/drivers/dma/tegra210-adma.c
> @@ -811,7 +811,7 @@ static void tegra_adma_free_chan_resources(struct dma_chan *dc)
>
> tegra_adma_terminate_all(dc);
> vchan_free_chan_resources(&tdc->vc);
> - tasklet_kill(&tdc->vc.task);
> + dmaengine_kill_bh(&tdc->vc.chan);
> free_irq(tdc->irq, tdc);
> pm_runtime_put(tdc2dev(tdc));
>
> diff --git a/drivers/dma/ti/edma.c b/drivers/dma/ti/edma.c
> index d97db5af3555..c0ad0cdabb87 100644
> --- a/drivers/dma/ti/edma.c
> +++ b/drivers/dma/ti/edma.c
> @@ -2560,7 +2560,7 @@ static void edma_cleanupp_vchan(struct dma_device *dmadev)
> list_for_each_entry_safe(echan, _echan,
> &dmadev->channels, vchan.chan.device_node) {
> list_del(&echan->vchan.chan.device_node);
> - tasklet_kill(&echan->vchan.task);
> + dmaengine_kill_bh(&echan->vchan.chan);
> }
> }
>
> diff --git a/drivers/dma/ti/k3-udma.c b/drivers/dma/ti/k3-udma.c
> index 1cf158eb7bdb..fd428c26799a 100644
> --- a/drivers/dma/ti/k3-udma.c
> +++ b/drivers/dma/ti/k3-udma.c
> @@ -4042,12 +4042,12 @@ static void udma_desc_pre_callback(struct virt_dma_chan *vc,
> }
>
> /*
> - * This tasklet handles the completion of a DMA descriptor by
> + * This BH handles the completion of a DMA descriptor by
> * calling its callback and freeing it.
> */
> -static void udma_vchan_complete(struct tasklet_struct *t)
> +static void udma_vchan_complete(struct dma_chan *chan)
> {
> - struct virt_dma_chan *vc = from_tasklet(vc, t, task);
> + struct virt_dma_chan *vc = to_virt_chan(chan);
> struct virt_dma_desc *vd, *_vd;
> struct dmaengine_desc_callback cb;
> LIST_HEAD(head);
> @@ -4112,7 +4112,7 @@ static void udma_free_chan_resources(struct dma_chan *chan)
> }
>
> vchan_free_chan_resources(&uc->vc);
> - tasklet_kill(&uc->vc.task);
> + dmaengine_kill_bh(&uc->vc.chan);
>
> bcdma_free_bchan_resources(uc);
> udma_free_tx_resources(uc);
> @@ -5627,8 +5627,8 @@ static int udma_probe(struct platform_device *pdev)
> if (!uc->name)
> return -ENOMEM;
> vchan_init(&uc->vc, &ud->ddev);
> - /* Use custom vchan completion handling */
> - tasklet_setup(&uc->vc.task, udma_vchan_complete);
> + /* Override the default vchan completion handler */
> + uc->vc.chan.bh_work_fn = udma_vchan_complete;
> init_completion(&uc->teardown_completed);
> INIT_DELAYED_WORK(&uc->tx_drain.work, udma_check_tx_completion);
> }
> diff --git a/drivers/dma/ti/omap-dma.c b/drivers/dma/ti/omap-dma.c
> index 55ece7fd0d99..899c5eeeac88 100644
> --- a/drivers/dma/ti/omap-dma.c
> +++ b/drivers/dma/ti/omap-dma.c
> @@ -1521,7 +1521,7 @@ static void omap_dma_free(struct omap_dmadev *od)
> struct omap_chan, vc.chan.device_node);
>
> list_del(&c->vc.chan.device_node);
> - tasklet_kill(&c->vc.task);
> + dmaengine_kill_bh(&c->vc.chan);
> kfree(c);
> }
> }
> diff --git a/drivers/dma/virt-dma.c b/drivers/dma/virt-dma.c
> index 7961172a780d..d407af6cc1da 100644
> --- a/drivers/dma/virt-dma.c
> +++ b/drivers/dma/virt-dma.c
> @@ -77,12 +77,12 @@ struct virt_dma_desc *vchan_find_desc(struct virt_dma_chan *vc,
> EXPORT_SYMBOL_GPL(vchan_find_desc);
>
> /*
> - * This tasklet handles the completion of a DMA descriptor by
> - * calling its callback and freeing it.
> + * This bottom-half handler completes a DMA descriptor by invoking its
> + * callback and freeing it.
> */
> -static void vchan_complete(struct tasklet_struct *t)
> +static void vchan_complete(struct dma_chan *chan)
> {
> - struct virt_dma_chan *vc = from_tasklet(vc, t, task);
> + struct virt_dma_chan *vc = to_virt_chan(chan);
> struct virt_dma_desc *vd, *_vd;
> struct dmaengine_desc_callback cb;
> LIST_HEAD(head);
> @@ -98,7 +98,7 @@ static void vchan_complete(struct tasklet_struct *t)
> }
> spin_unlock_irq(&vc->lock);
>
> - dmaengine_desc_callback_invoke(&cb, &vd->tx_result);
> + dmaengine_desc_callback_invoke(&cb, vd ? &vd->tx_result : NULL);
>
> list_for_each_entry_safe(vd, _vd, &head, node) {
> dmaengine_desc_get_callback(&vd->tx, &cb);
> @@ -131,7 +131,7 @@ void vchan_init(struct virt_dma_chan *vc, struct dma_device *dmadev)
> INIT_LIST_HEAD(&vc->desc_completed);
> INIT_LIST_HEAD(&vc->desc_terminated);
>
> - tasklet_setup(&vc->task, vchan_complete);
> + dmaengine_init_bh(&vc->chan, vchan_complete);
>
> vc->chan.device = dmadev;
> list_add_tail(&vc->chan.device_node, &dmadev->channels);
> diff --git a/drivers/dma/virt-dma.h b/drivers/dma/virt-dma.h
> index 59d9eabc8b67..0ba42fded2cc 100644
> --- a/drivers/dma/virt-dma.h
> +++ b/drivers/dma/virt-dma.h
> @@ -21,7 +21,6 @@ struct virt_dma_desc {
>
> struct virt_dma_chan {
> struct dma_chan chan;
> - struct tasklet_struct task;
> void (*desc_free)(struct virt_dma_desc *);
>
> spinlock_t lock;
> @@ -106,7 +105,7 @@ static inline void vchan_cookie_complete(struct virt_dma_desc *vd)
> vd, cookie);
> list_add_tail(&vd->node, &vc->desc_completed);
>
> - tasklet_schedule(&vc->task);
> + dmaengine_schedule_bh(&vc->chan);
> }
>
> /**
> @@ -137,7 +136,7 @@ static inline void vchan_cyclic_callback(struct virt_dma_desc *vd)
> struct virt_dma_chan *vc = to_virt_chan(vd->tx.chan);
>
> vc->cyclic = vd;
> - tasklet_schedule(&vc->task);
> + dmaengine_schedule_bh(&vc->chan);
> }
>
> /**
> @@ -223,7 +222,7 @@ static inline void vchan_synchronize(struct virt_dma_chan *vc)
> LIST_HEAD(head);
> unsigned long flags;
>
> - tasklet_kill(&vc->task);
> + dmaengine_kill_bh(&vc->chan);
>
> spin_lock_irqsave(&vc->lock, flags);
>
> diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h
> index b3d251c9734e..a1437bdbda9b 100644
> --- a/include/linux/dmaengine.h
> +++ b/include/linux/dmaengine.h
> @@ -12,6 +12,7 @@
> #include <linux/scatterlist.h>
> #include <linux/bitmap.h>
> #include <linux/types.h>
> +#include <linux/interrupt.h>
> #include <asm/page.h>
>
> /**
> @@ -295,6 +296,10 @@ enum dma_desc_metadata_mode {
> DESC_METADATA_ENGINE = BIT(1),
> };
>
> +struct dma_chan;
> +
> +typedef void (*dmaengine_bh_work_fn)(struct dma_chan *chan);
> +
> /**
> * struct dma_chan_percpu - the per-CPU part of struct dma_chan
> * @memcpy_count: transaction counter
> @@ -334,6 +339,9 @@ struct dma_router {
> * @router: pointer to the DMA router structure
> * @route_data: channel specific data for the router
> * @private: private data for certain client-channel associations
> + * @bh_tasklet: bottom-half tasklet stored per-channel
> + * @bh_work_fn: callback executed when @bh_tasklet runs
> + * @bh_work_initialized: indicates whether @bh_tasklet has been initialized
> */
> struct dma_chan {
> struct dma_device *device;
> @@ -359,6 +367,9 @@ struct dma_chan {
> void *route_data;
>
> void *private;
> + struct tasklet_struct bh_tasklet;
> + dmaengine_bh_work_fn bh_work_fn;
> + bool bh_work_initialized;
> };
>
> /**
> @@ -1529,6 +1540,9 @@ struct dma_chan *devm_dma_request_chan(struct device *dev, const char *name);
>
> void dma_release_channel(struct dma_chan *chan);
> int dma_get_slave_caps(struct dma_chan *chan, struct dma_slave_caps *caps);
> +void dmaengine_init_bh(struct dma_chan *chan, dmaengine_bh_work_fn fn);
> +bool dmaengine_schedule_bh(struct dma_chan *chan);
> +void dmaengine_kill_bh(struct dma_chan *chan);
> #else
> static inline struct dma_chan *dma_find_channel(enum dma_transaction_type tx_type)
> {
> @@ -1576,6 +1590,20 @@ static inline int dma_get_slave_caps(struct dma_chan *chan,
> {
> return -ENXIO;
> }
> +
> +static inline void dmaengine_init_bh(struct dma_chan *chan,
> + dmaengine_bh_work_fn fn)
> +{
> +}
> +
> +static inline bool dmaengine_schedule_bh(struct dma_chan *chan)
> +{
> + return false;
> +}
> +
> +static inline void dmaengine_kill_bh(struct dma_chan *chan)
> +{
> +}
> #endif
>
> static inline int dmaengine_desc_set_reuse(struct dma_async_tx_descriptor *tx)
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PATCH v3 04/34] dmaengine: at_xdmac: move irq bottom half to dmaengine BH
2026-08-10 18:09 ` [PATCH v3 04/34] dmaengine: at_xdmac: move irq bottom half to dmaengine BH Allen Pais
@ 2026-09-01 22:32 ` Frank Li
0 siblings, 0 replies; 63+ messages in thread
From: Frank Li @ 2026-09-01 22:32 UTC (permalink / raw)
To: Allen Pais
Cc: Vinod Koul, Frank Li, dmaengine, linux-kernel, Arnd Bergmann,
Kees Cook, Ludovic Desroches, linux-arm-kernel
On Mon, Aug 10, 2026 at 11:09:05AM -0700, Allen Pais wrote:
> Replace the per-channel tasklet with the shared dmaengine BH helper.
> The handler continues to run in softirq context while dmaengine owns
> the common scheduling and teardown mechanism.
>
> Signed-off-by: Allen Pais <allen.lkml@gmail.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/dma/at_xdmac.c | 13 ++++++-------
> 1 file changed, 6 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/dma/at_xdmac.c b/drivers/dma/at_xdmac.c
> index 901971e8bae6..5a970f9f3831 100644
> --- a/drivers/dma/at_xdmac.c
> +++ b/drivers/dma/at_xdmac.c
> @@ -228,7 +228,6 @@ struct at_xdmac_chan {
> u32 save_cndc;
> u32 irq_status;
> unsigned long status;
> - struct tasklet_struct tasklet;
> struct dma_slave_config sconfig;
>
> spinlock_t lock;
> @@ -1759,9 +1758,9 @@ static void at_xdmac_handle_error(struct at_xdmac_chan *atchan)
> /* Then continue with usual descriptor management */
> }
>
> -static void at_xdmac_tasklet(struct tasklet_struct *t)
> +static void at_xdmac_tasklet(struct dma_chan *chan)
> {
> - struct at_xdmac_chan *atchan = from_tasklet(atchan, t, tasklet);
> + struct at_xdmac_chan *atchan = to_at_xdmac_chan(chan);
> struct at_xdmac *atxdmac = to_at_xdmac(atchan->chan.device);
> struct at_xdmac_desc *desc;
> struct dma_async_tx_descriptor *txd;
> @@ -1865,7 +1864,7 @@ static irqreturn_t at_xdmac_interrupt(int irq, void *dev_id)
> if (atchan->irq_status & (AT_XDMAC_CIS_RBEIS | AT_XDMAC_CIS_WBEIS))
> at_xdmac_write(atxdmac, AT_XDMAC_GD, atchan->mask);
>
> - tasklet_schedule(&atchan->tasklet);
> + dmaengine_schedule_bh(&atchan->chan);
> ret = IRQ_HANDLED;
> }
>
> @@ -2317,7 +2316,7 @@ static int at_xdmac_probe(struct platform_device *pdev)
> return PTR_ERR(atxdmac->clk);
> }
>
> - /* Do not use dev res to prevent races with tasklet */
> + /* Do not use devm resources to prevent races with the BH worker */
> ret = request_irq(atxdmac->irq, at_xdmac_interrupt, 0, "at_xdmac", atxdmac);
> if (ret) {
> dev_err(&pdev->dev, "can't request irq\n");
> @@ -2397,7 +2396,7 @@ static int at_xdmac_probe(struct platform_device *pdev)
> spin_lock_init(&atchan->lock);
> INIT_LIST_HEAD(&atchan->xfers_list);
> INIT_LIST_HEAD(&atchan->free_descs_list);
> - tasklet_setup(&atchan->tasklet, at_xdmac_tasklet);
> + dmaengine_init_bh(&atchan->chan, at_xdmac_tasklet);
>
> /* Clear pending interrupts. */
> while (at_xdmac_chan_read(atchan, AT_XDMAC_CIS))
> @@ -2458,7 +2457,7 @@ static void at_xdmac_remove(struct platform_device *pdev)
> for (i = 0; i < atxdmac->dma.chancnt; i++) {
> struct at_xdmac_chan *atchan = &atxdmac->chan[i];
>
> - tasklet_kill(&atchan->tasklet);
> + dmaengine_kill_bh(&atchan->chan);
> at_xdmac_free_chan_resources(&atchan->chan);
> }
> }
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PATCH v3 17/34] dmaengine: ste_dma40: convert per-channel tasklet to dmaengine BH
2026-08-10 18:09 ` [PATCH v3 17/34] dmaengine: ste_dma40: convert per-channel tasklet to dmaengine BH Allen Pais
@ 2026-09-02 14:43 ` Frank Li
0 siblings, 0 replies; 63+ messages in thread
From: Frank Li @ 2026-09-02 14:43 UTC (permalink / raw)
To: Allen Pais
Cc: Vinod Koul, Frank Li, dmaengine, linux-kernel, Arnd Bergmann,
Kees Cook, Linus Walleij, linux-arm-kernel
On Mon, Aug 10, 2026 at 11:09:18AM -0700, Allen Pais wrote:
> Replace the per-channel tasklet with the shared dmaengine BH helper.
> The handler continues to run in softirq context while dmaengine owns
> the common scheduling and teardown mechanism.
>
> Signed-off-by: Allen Pais <allen.lkml@gmail.com>
> Reviewed-by: Linus Walleij <linusw@kernel.org>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/dma/ste_dma40.c | 13 +++++--------
> 1 file changed, 5 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c
> index 0d9ffa3e2663..e6dfa1fc5e5a 100644
> --- a/drivers/dma/ste_dma40.c
> +++ b/drivers/dma/ste_dma40.c
> @@ -461,8 +461,6 @@ struct d40_base;
> * @phy_chan: Pointer to physical channel which this instance runs on. If this
> * point is NULL, then the channel is not allocated.
> * @chan: DMA engine handle.
> - * @tasklet: Tasklet that gets scheduled from interrupt context to complete a
> - * transfer and call client callback.
> * @client: Cliented owned descriptor list.
> * @pending_queue: Submitted jobs, to be issued by issue_pending()
> * @active: Active descriptor.
> @@ -489,7 +487,6 @@ struct d40_chan {
> bool busy;
> struct d40_phy_res *phy_chan;
> struct dma_chan chan;
> - struct tasklet_struct tasklet;
> struct list_head client;
> struct list_head pending_queue;
> struct list_head active;
> @@ -1587,13 +1584,13 @@ static void dma_tc_handle(struct d40_chan *d40c)
> }
>
> d40c->pending_tx++;
> - tasklet_schedule(&d40c->tasklet);
> + dmaengine_schedule_bh(&d40c->chan);
>
> }
>
> -static void dma_tasklet(struct tasklet_struct *t)
> +static void dma_tasklet(struct dma_chan *chan)
> {
> - struct d40_chan *d40c = from_tasklet(d40c, t, tasklet);
> + struct d40_chan *d40c = container_of(chan, struct d40_chan, chan);
> struct d40_desc *d40d;
> unsigned long flags;
> bool callback_active;
> @@ -1641,7 +1638,7 @@ static void dma_tasklet(struct tasklet_struct *t)
> d40c->pending_tx--;
>
> if (d40c->pending_tx)
> - tasklet_schedule(&d40c->tasklet);
> + dmaengine_schedule_bh(&d40c->chan);
>
> spin_unlock_irqrestore(&d40c->lock, flags);
>
> @@ -2815,7 +2812,7 @@ static void __init d40_chan_init(struct d40_base *base, struct dma_device *dma,
> INIT_LIST_HEAD(&d40c->client);
> INIT_LIST_HEAD(&d40c->prepare_queue);
>
> - tasklet_setup(&d40c->tasklet, dma_tasklet);
> + dmaengine_init_bh(&d40c->chan, dma_tasklet);
>
> list_add_tail(&d40c->chan.device_node,
> &dma->channels);
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PATCH v3 01/34] dmaengine: add tasklet-backed channel BH helpers
2026-08-10 18:09 ` [PATCH v3 01/34] dmaengine: add tasklet-backed channel BH helpers Allen Pais
2026-08-10 18:30 ` sashiko-bot
2026-09-01 22:13 ` Frank Li
@ 2026-09-02 15:16 ` Frank Li
2 siblings, 0 replies; 63+ messages in thread
From: Frank Li @ 2026-09-02 15:16 UTC (permalink / raw)
To: Allen Pais
Cc: Vinod Koul, Frank Li, dmaengine, linux-kernel, Arnd Bergmann,
Kees Cook, Florian Fainelli, Ray Jui, Scott Branden,
Broadcom internal kernel review list, Lars-Peter Clausen,
Paul Cercueil, Eugeniy Paltsev, Manivannan Sadhasivam, Zhou Wang,
Longfang Liu, Andy Shevchenko, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Keguang Zhang, Sean Wang,
Matthias Brugger, AngeloGioacchino Del Regno, Andreas Färber,
Daniel Mack, Haojian Zhuang, Robert Jarzmik, Paul Walmsley,
Samuel Holland, Orson Zhai, Baolin Wang, Chunyan Zhang,
Patrice Chotard, Chen-Yu Tsai, Jernej Skrabec, Laxman Dewangan,
Jon Hunter, Thierry Reding, Vignesh Raghavendra,
Bartosz Golaszewski, Konrad Dybcio, Bjorn Andersson,
Kuldeep Singh, Stephan Gerhold, linux-rpi-kernel,
linux-arm-kernel, linux-mips, imx, linux-mediatek, linux-actions,
linux-arm-msm, linux-riscv, linux-sunxi, linux-tegra
On Mon, Aug 10, 2026 at 11:09:02AM -0700, Allen Pais wrote:
> DMAengine drivers commonly use a per-channel tasklet to invoke client
> callbacks. Add helpers that initialize, schedule, and kill a channel
> bottom half, with an initial tasklet-backed implementation that preserves
> the existing execution context.
>
> Convert virt-dma to the new API and remove its private tasklet. Update all
> drivers that directly kill or override that tasklet in the same change so
> no stale users remain. While touching the completion handler, avoid forming
> a result pointer from a NULL cyclic descriptor.
>
> This establishes a backend-independent API before changing how channel
> bottom halves are dispatched.
>
> Signed-off-by: Allen Pais <allen.lkml@gmail.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/dma/bcm2835-dma.c | 2 +-
> drivers/dma/dma-axi-dmac.c | 10 +++--
> drivers/dma/dma-jz4780.c | 2 +-
> drivers/dma/dmaengine.c | 42 +++++++++++++++++++
> .../dma/dw-axi-dmac/dw-axi-dmac-platform.c | 2 +-
> drivers/dma/dw-edma/dw-edma-core.c | 2 +-
> drivers/dma/fsl-edma-common.c | 2 +-
> drivers/dma/fsl-qdma.c | 2 +-
> drivers/dma/hisi_dma.c | 2 +-
> drivers/dma/hsu/hsu.c | 2 +-
> drivers/dma/idma64.c | 4 +-
> drivers/dma/img-mdc-dma.c | 2 +-
> drivers/dma/imx-sdma.c | 4 +-
> drivers/dma/k3dma.c | 2 +-
> drivers/dma/loongson/loongson1-apb-dma.c | 2 +-
> drivers/dma/mediatek/mtk-cqdma.c | 2 +-
> drivers/dma/mediatek/mtk-hsdma.c | 2 +-
> drivers/dma/mediatek/mtk-uart-apdma.c | 4 +-
> drivers/dma/owl-dma.c | 2 +-
> drivers/dma/pxa_dma.c | 2 +-
> drivers/dma/qcom/bam_dma.c | 4 +-
> drivers/dma/qcom/qcom_adm.c | 4 +-
> drivers/dma/sa11x0-dma.c | 2 +-
> drivers/dma/sf-pdma/sf-pdma.c | 2 +-
> drivers/dma/sprd-dma.c | 2 +-
> drivers/dma/st_fdma.c | 2 +-
> drivers/dma/sun6i-dma.c | 2 +-
> drivers/dma/tegra186-gpc-dma.c | 2 +-
> drivers/dma/tegra210-adma.c | 2 +-
> drivers/dma/ti/edma.c | 2 +-
> drivers/dma/ti/k3-udma.c | 12 +++---
> drivers/dma/ti/omap-dma.c | 2 +-
> drivers/dma/virt-dma.c | 12 +++---
> drivers/dma/virt-dma.h | 7 ++--
> include/linux/dmaengine.h | 28 +++++++++++++
> 35 files changed, 125 insertions(+), 54 deletions(-)
>
> diff --git a/drivers/dma/bcm2835-dma.c b/drivers/dma/bcm2835-dma.c
> index 06d830d36882..c8add249dbfb 100644
> --- a/drivers/dma/bcm2835-dma.c
> +++ b/drivers/dma/bcm2835-dma.c
> @@ -829,7 +829,7 @@ static void bcm2835_dma_free(struct bcm2835_dmadev *od)
> list_for_each_entry_safe(c, next, &od->ddev.channels,
> vc.chan.device_node) {
> list_del(&c->vc.chan.device_node);
> - tasklet_kill(&c->vc.task);
> + dmaengine_kill_bh(&c->vc.chan);
> }
>
> dma_unmap_page_attrs(od->ddev.dev, od->zero_page, PAGE_SIZE,
> diff --git a/drivers/dma/dma-axi-dmac.c b/drivers/dma/dma-axi-dmac.c
> index d47ff27e1408..d245d04c3a27 100644
> --- a/drivers/dma/dma-axi-dmac.c
> +++ b/drivers/dma/dma-axi-dmac.c
> @@ -1195,9 +1195,11 @@ static int axi_dmac_detect_caps(struct axi_dmac *dmac, unsigned int version)
> return 0;
> }
>
> -static void axi_dmac_tasklet_kill(void *task)
> +static void axi_dmac_kill_bh(void *data)
> {
> - tasklet_kill(task);
> + struct dma_chan *chan = data;
> +
> + dmaengine_kill_bh(chan);
> }
>
> static void axi_dmac_free_dma_controller(void *of_node)
> @@ -1302,8 +1304,8 @@ static int axi_dmac_probe(struct platform_device *pdev)
> * Put the action in here so it get's done before unregistering the DMA
> * device.
> */
> - ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_tasklet_kill,
> - &dmac->chan.vchan.task);
> + ret = devm_add_action_or_reset(&pdev->dev, axi_dmac_kill_bh,
> + &dmac->chan.vchan.chan);
> if (ret)
> return ret;
>
> diff --git a/drivers/dma/dma-jz4780.c b/drivers/dma/dma-jz4780.c
> index 6070dfdb7114..738801501e29 100644
> --- a/drivers/dma/dma-jz4780.c
> +++ b/drivers/dma/dma-jz4780.c
> @@ -1019,7 +1019,7 @@ static void jz4780_dma_remove(struct platform_device *pdev)
> free_irq(jzdma->irq, jzdma);
>
> for (i = 0; i < jzdma->soc_data->nb_channels; i++)
> - tasklet_kill(&jzdma->chan[i].vchan.task);
> + dmaengine_kill_bh(&jzdma->chan[i].vchan.chan);
> }
>
> static const struct jz4780_dma_soc_data jz4740_dma_soc_data = {
> diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c
> index 9049171df857..d8fc7eb71b48 100644
> --- a/drivers/dma/dmaengine.c
> +++ b/drivers/dma/dmaengine.c
> @@ -1428,6 +1428,48 @@ static void dmaengine_destroy_unmap_pool(void)
> }
> }
>
> +static void dma_chan_bh_entry(struct tasklet_struct *tasklet)
> +{
> + struct dma_chan *chan = from_tasklet(chan, tasklet, bh_tasklet);
> + dmaengine_bh_work_fn fn = READ_ONCE(chan->bh_work_fn);
> +
> + if (fn)
> + fn(chan);
> +}
> +
> +void dmaengine_init_bh(struct dma_chan *chan, dmaengine_bh_work_fn fn)
> +{
> + if (WARN_ON(!fn))
> + return;
> +
> + if (WARN_ON(chan->bh_work_initialized))
> + return;
> +
> + chan->bh_work_fn = fn;
> + tasklet_setup(&chan->bh_tasklet, dma_chan_bh_entry);
> + chan->bh_work_initialized = true;
> +}
> +EXPORT_SYMBOL_GPL(dmaengine_init_bh);
> +
> +bool dmaengine_schedule_bh(struct dma_chan *chan)
> +{
> + if (WARN_ON(!chan->bh_work_initialized))
> + return false;
> +
> + tasklet_schedule(&chan->bh_tasklet);
> + return true;
> +}
> +EXPORT_SYMBOL_GPL(dmaengine_schedule_bh);
> +
> +void dmaengine_kill_bh(struct dma_chan *chan)
> +{
> + if (!chan->bh_work_initialized)
> + return;
> +
> + tasklet_kill(&chan->bh_tasklet);
> +}
> +EXPORT_SYMBOL_GPL(dmaengine_kill_bh);
> +
> static int __init dmaengine_init_unmap_pool(void)
> {
> int i;
> diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
> index bcefaff03b5c..a2b688e7f47e 100644
> --- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
> +++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
> @@ -1663,7 +1663,7 @@ static void dw_remove(struct platform_device *pdev)
> list_for_each_entry_safe(chan, _chan, &dw->dma.channels,
> vc.chan.device_node) {
> list_del(&chan->vc.chan.device_node);
> - tasklet_kill(&chan->vc.task);
> + dmaengine_kill_bh(&chan->vc.chan);
> }
> }
>
> diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c
> index 89a4c498a17b..ade866fba2ad 100644
> --- a/drivers/dma/dw-edma/dw-edma-core.c
> +++ b/drivers/dma/dw-edma/dw-edma-core.c
> @@ -1170,7 +1170,7 @@ int dw_edma_remove(struct dw_edma_chip *chip)
> dma_async_device_unregister(&dw->dma);
> list_for_each_entry_safe(chan, _chan, &dw->dma.channels,
> vc.chan.device_node) {
> - tasklet_kill(&chan->vc.task);
> + dmaengine_kill_bh(&chan->vc.chan);
> list_del(&chan->vc.chan.device_node);
> }
>
> diff --git a/drivers/dma/fsl-edma-common.c b/drivers/dma/fsl-edma-common.c
> index bb7531c456df..90ae678c68d4 100644
> --- a/drivers/dma/fsl-edma-common.c
> +++ b/drivers/dma/fsl-edma-common.c
> @@ -915,7 +915,7 @@ void fsl_edma_cleanup_vchan(struct dma_device *dmadev)
> list_for_each_entry_safe(chan, _chan,
> &dmadev->channels, vchan.chan.device_node) {
> list_del(&chan->vchan.chan.device_node);
> - tasklet_kill(&chan->vchan.task);
> + dmaengine_kill_bh(&chan->vchan.chan);
> }
> }
>
> diff --git a/drivers/dma/fsl-qdma.c b/drivers/dma/fsl-qdma.c
> index df843fad0ece..b0ace8bad498 100644
> --- a/drivers/dma/fsl-qdma.c
> +++ b/drivers/dma/fsl-qdma.c
> @@ -1255,7 +1255,7 @@ static void fsl_qdma_cleanup_vchan(struct dma_device *dmadev)
> list_for_each_entry_safe(chan, _chan,
> &dmadev->channels, vchan.chan.device_node) {
> list_del(&chan->vchan.chan.device_node);
> - tasklet_kill(&chan->vchan.task);
> + dmaengine_kill_bh(&chan->vchan.chan);
> }
> }
>
> diff --git a/drivers/dma/hisi_dma.c b/drivers/dma/hisi_dma.c
> index 28bf818f9aa6..10eb98350b9f 100644
> --- a/drivers/dma/hisi_dma.c
> +++ b/drivers/dma/hisi_dma.c
> @@ -720,7 +720,7 @@ static void hisi_dma_disable_qps(struct hisi_dma_dev *hdma_dev)
>
> for (i = 0; i < hdma_dev->chan_num; i++) {
> hisi_dma_disable_qp(hdma_dev, i);
> - tasklet_kill(&hdma_dev->chan[i].vc.task);
> + dmaengine_kill_bh(&hdma_dev->chan[i].vc.chan);
> }
> }
>
> diff --git a/drivers/dma/hsu/hsu.c b/drivers/dma/hsu/hsu.c
> index f62d60d7bc6b..315d0ebecd57 100644
> --- a/drivers/dma/hsu/hsu.c
> +++ b/drivers/dma/hsu/hsu.c
> @@ -500,7 +500,7 @@ int hsu_dma_remove(struct hsu_dma_chip *chip)
> for (i = 0; i < hsu->nr_channels; i++) {
> struct hsu_dma_chan *hsuc = &hsu->chan[i];
>
> - tasklet_kill(&hsuc->vchan.task);
> + dmaengine_kill_bh(&hsuc->vchan.chan);
> }
>
> return 0;
> diff --git a/drivers/dma/idma64.c b/drivers/dma/idma64.c
> index 5fcd1befc92d..1d395fb735ff 100644
> --- a/drivers/dma/idma64.c
> +++ b/drivers/dma/idma64.c
> @@ -617,14 +617,14 @@ static void idma64_remove(struct idma64_chip *chip)
>
> /*
> * Explicitly call devm_request_irq() to avoid the side effects with
> - * the scheduled tasklets.
> + * scheduled BH work.
> */
> devm_free_irq(chip->dev, chip->irq, idma64);
>
> for (i = 0; i < idma64->dma.chancnt; i++) {
> struct idma64_chan *idma64c = &idma64->chan[i];
>
> - tasklet_kill(&idma64c->vchan.task);
> + dmaengine_kill_bh(&idma64c->vchan.chan);
> }
> }
>
> diff --git a/drivers/dma/img-mdc-dma.c b/drivers/dma/img-mdc-dma.c
> index b3765ba15803..0c6024088444 100644
> --- a/drivers/dma/img-mdc-dma.c
> +++ b/drivers/dma/img-mdc-dma.c
> @@ -1031,7 +1031,7 @@ static void mdc_dma_remove(struct platform_device *pdev)
>
> devm_free_irq(&pdev->dev, mchan->irq, mchan);
>
> - tasklet_kill(&mchan->vc.task);
> + dmaengine_kill_bh(&mchan->vc.chan);
> }
>
> pm_runtime_disable(&pdev->dev);
> diff --git a/drivers/dma/imx-sdma.c b/drivers/dma/imx-sdma.c
> index 36368835a845..4d13b9d2880d 100644
> --- a/drivers/dma/imx-sdma.c
> +++ b/drivers/dma/imx-sdma.c
> @@ -2399,11 +2399,11 @@ static void sdma_remove(struct platform_device *pdev)
> int i;
>
> devm_free_irq(&pdev->dev, sdma->irq, sdma);
> - /* Kill the tasklet */
> + /* Kill the channel BH */
> for (i = 0; i < MAX_DMA_CHANNELS; i++) {
> struct sdma_channel *sdmac = &sdma->channel[i];
>
> - tasklet_kill(&sdmac->vc.task);
> + dmaengine_kill_bh(&sdmac->vc.chan);
> sdma_free_chan_resources(&sdmac->vc.chan);
> }
>
> diff --git a/drivers/dma/k3dma.c b/drivers/dma/k3dma.c
> index e84f197fea76..3d73b391e42e 100644
> --- a/drivers/dma/k3dma.c
> +++ b/drivers/dma/k3dma.c
> @@ -976,7 +976,7 @@ static void k3_dma_remove(struct platform_device *op)
>
> list_for_each_entry_safe(c, cn, &d->slave.channels, vc.chan.device_node) {
> list_del(&c->vc.chan.device_node);
> - tasklet_kill(&c->vc.task);
> + dmaengine_kill_bh(&c->vc.chan);
> }
> tasklet_kill(&d->task);
> clk_disable_unprepare(d->clk);
> diff --git a/drivers/dma/loongson/loongson1-apb-dma.c b/drivers/dma/loongson/loongson1-apb-dma.c
> index 89786cbd20ab..52a360719644 100644
> --- a/drivers/dma/loongson/loongson1-apb-dma.c
> +++ b/drivers/dma/loongson/loongson1-apb-dma.c
> @@ -552,7 +552,7 @@ static void ls1x_dma_chan_remove(struct ls1x_dma *dma)
>
> if (chan->vc.chan.device == &dma->ddev) {
> list_del(&chan->vc.chan.device_node);
> - tasklet_kill(&chan->vc.task);
> + dmaengine_kill_bh(&chan->vc.chan);
> }
> }
> }
> diff --git a/drivers/dma/mediatek/mtk-cqdma.c b/drivers/dma/mediatek/mtk-cqdma.c
> index 80791e30aec2..7b44d3358cda 100644
> --- a/drivers/dma/mediatek/mtk-cqdma.c
> +++ b/drivers/dma/mediatek/mtk-cqdma.c
> @@ -895,7 +895,7 @@ static void mtk_cqdma_remove(struct platform_device *pdev)
> vc = &cqdma->vc[i];
>
> list_del(&vc->vc.chan.device_node);
> - tasklet_kill(&vc->vc.task);
> + dmaengine_kill_bh(&vc->vc.chan);
> }
>
> /* disable interrupt */
> diff --git a/drivers/dma/mediatek/mtk-hsdma.c b/drivers/dma/mediatek/mtk-hsdma.c
> index a43412ff5edd..75555fa41536 100644
> --- a/drivers/dma/mediatek/mtk-hsdma.c
> +++ b/drivers/dma/mediatek/mtk-hsdma.c
> @@ -1020,7 +1020,7 @@ static void mtk_hsdma_remove(struct platform_device *pdev)
> vc = &hsdma->vc[i];
>
> list_del(&vc->vc.chan.device_node);
> - tasklet_kill(&vc->vc.task);
> + dmaengine_kill_bh(&vc->vc.chan);
> }
>
> /* Disable DMA interrupt */
> diff --git a/drivers/dma/mediatek/mtk-uart-apdma.c b/drivers/dma/mediatek/mtk-uart-apdma.c
> index c269d84d7bd2..8ce206a0ecc5 100644
> --- a/drivers/dma/mediatek/mtk-uart-apdma.c
> +++ b/drivers/dma/mediatek/mtk-uart-apdma.c
> @@ -312,7 +312,7 @@ static void mtk_uart_apdma_free_chan_resources(struct dma_chan *chan)
>
> free_irq(c->irq, chan);
>
> - tasklet_kill(&c->vc.task);
> + dmaengine_kill_bh(&c->vc.chan);
>
> vchan_free_chan_resources(&c->vc);
>
> @@ -463,7 +463,7 @@ static void mtk_uart_apdma_free(struct mtk_uart_apdmadev *mtkd)
> struct mtk_chan, vc.chan.device_node);
>
> list_del(&c->vc.chan.device_node);
> - tasklet_kill(&c->vc.task);
> + dmaengine_kill_bh(&c->vc.chan);
> }
> }
>
> diff --git a/drivers/dma/owl-dma.c b/drivers/dma/owl-dma.c
> index 7c80572fc71d..0d9f324adba0 100644
> --- a/drivers/dma/owl-dma.c
> +++ b/drivers/dma/owl-dma.c
> @@ -1055,7 +1055,7 @@ static inline void owl_dma_free(struct owl_dma *od)
> list_for_each_entry_safe(vchan,
> next, &od->dma.channels, vc.chan.device_node) {
> list_del(&vchan->vc.chan.device_node);
> - tasklet_kill(&vchan->vc.task);
> + dmaengine_kill_bh(&vchan->vc.chan);
> }
> }
>
> diff --git a/drivers/dma/pxa_dma.c b/drivers/dma/pxa_dma.c
> index fa2ee0b3e09f..2cca8c31929c 100644
> --- a/drivers/dma/pxa_dma.c
> +++ b/drivers/dma/pxa_dma.c
> @@ -1215,7 +1215,7 @@ static void pxad_free_channels(struct dma_device *dmadev)
> list_for_each_entry_safe(c, cn, &dmadev->channels,
> vc.chan.device_node) {
> list_del(&c->vc.chan.device_node);
> - tasklet_kill(&c->vc.task);
> + dmaengine_kill_bh(&c->vc.chan);
> }
> }
>
> diff --git a/drivers/dma/qcom/bam_dma.c b/drivers/dma/qcom/bam_dma.c
> index 1bb26af0405f..c23496c3ac69 100644
> --- a/drivers/dma/qcom/bam_dma.c
> +++ b/drivers/dma/qcom/bam_dma.c
> @@ -1387,7 +1387,7 @@ static int bam_dma_probe(struct platform_device *pdev)
> dma_async_device_unregister(&bdev->common);
> err_bam_channel_exit:
> for (i = 0; i < bdev->num_channels; i++)
> - tasklet_kill(&bdev->channels[i].vc.task);
> + dmaengine_kill_bh(&bdev->channels[i].vc.chan);
> err_tasklet_kill:
> tasklet_kill(&bdev->task);
> err_disable_clk:
> @@ -1413,7 +1413,7 @@ static void bam_dma_remove(struct platform_device *pdev)
>
> for (i = 0; i < bdev->num_channels; i++) {
> bam_dma_terminate_all(&bdev->channels[i].vc.chan);
> - tasklet_kill(&bdev->channels[i].vc.task);
> + dmaengine_kill_bh(&bdev->channels[i].vc.chan);
>
> if (!bdev->channels[i].fifo_virt)
> continue;
> diff --git a/drivers/dma/qcom/qcom_adm.c b/drivers/dma/qcom/qcom_adm.c
> index 07fbe32d31fa..13f5ca8ff808 100644
> --- a/drivers/dma/qcom/qcom_adm.c
> +++ b/drivers/dma/qcom/qcom_adm.c
> @@ -918,8 +918,8 @@ static void adm_dma_remove(struct platform_device *pdev)
> /* mask IRQs for this channel/EE pair */
> writel(0, adev->regs + ADM_CH_RSLT_CONF(achan->id, adev->ee));
>
> - tasklet_kill(&adev->channels[i].vc.task);
> - adm_terminate_all(&adev->channels[i].vc.chan);
> + dmaengine_kill_bh(&achan->vc.chan);
> + adm_terminate_all(&achan->vc.chan);
> }
>
> devm_free_irq(adev->dev, adev->irq, adev);
> diff --git a/drivers/dma/sa11x0-dma.c b/drivers/dma/sa11x0-dma.c
> index a6fa431530e3..e14566fa2d74 100644
> --- a/drivers/dma/sa11x0-dma.c
> +++ b/drivers/dma/sa11x0-dma.c
> @@ -891,7 +891,7 @@ static void sa11x0_dma_free_channels(struct dma_device *dmadev)
>
> list_for_each_entry_safe(c, cn, &dmadev->channels, vc.chan.device_node) {
> list_del(&c->vc.chan.device_node);
> - tasklet_kill(&c->vc.task);
> + dmaengine_kill_bh(&c->vc.chan);
> kfree(c);
> }
> }
> diff --git a/drivers/dma/sf-pdma/sf-pdma.c b/drivers/dma/sf-pdma/sf-pdma.c
> index 6f79cc28703e..a08ca355dadb 100644
> --- a/drivers/dma/sf-pdma/sf-pdma.c
> +++ b/drivers/dma/sf-pdma/sf-pdma.c
> @@ -602,7 +602,7 @@ static void sf_pdma_remove(struct platform_device *pdev)
> devm_free_irq(&pdev->dev, ch->txirq, ch);
> devm_free_irq(&pdev->dev, ch->errirq, ch);
> list_del(&ch->vchan.chan.device_node);
> - tasklet_kill(&ch->vchan.task);
> + dmaengine_kill_bh(&ch->vchan.chan);
> tasklet_kill(&ch->done_tasklet);
> tasklet_kill(&ch->err_tasklet);
> }
> diff --git a/drivers/dma/sprd-dma.c b/drivers/dma/sprd-dma.c
> index 087fea3af2e4..f90f5d8d5a1e 100644
> --- a/drivers/dma/sprd-dma.c
> +++ b/drivers/dma/sprd-dma.c
> @@ -1253,7 +1253,7 @@ static void sprd_dma_remove(struct platform_device *pdev)
> list_for_each_entry_safe(c, cn, &sdev->dma_dev.channels,
> vc.chan.device_node) {
> list_del(&c->vc.chan.device_node);
> - tasklet_kill(&c->vc.task);
> + dmaengine_kill_bh(&c->vc.chan);
> }
>
> of_dma_controller_free(pdev->dev.of_node);
> diff --git a/drivers/dma/st_fdma.c b/drivers/dma/st_fdma.c
> index d9547017f3bd..cae0a7fe6ceb 100644
> --- a/drivers/dma/st_fdma.c
> +++ b/drivers/dma/st_fdma.c
> @@ -733,7 +733,7 @@ static void st_fdma_free(struct st_fdma_dev *fdev)
> for (i = 0; i < fdev->nr_channels; i++) {
> fchan = &fdev->chans[i];
> list_del(&fchan->vchan.chan.device_node);
> - tasklet_kill(&fchan->vchan.task);
> + dmaengine_kill_bh(&fchan->vchan.chan);
> }
> }
>
> diff --git a/drivers/dma/sun6i-dma.c b/drivers/dma/sun6i-dma.c
> index f47a326dd7ff..4ddea3cdcd66 100644
> --- a/drivers/dma/sun6i-dma.c
> +++ b/drivers/dma/sun6i-dma.c
> @@ -1094,7 +1094,7 @@ static inline void sun6i_dma_free(struct sun6i_dma_dev *sdev)
> struct sun6i_vchan *vchan = &sdev->vchans[i];
>
> list_del(&vchan->vc.chan.device_node);
> - tasklet_kill(&vchan->vc.task);
> + dmaengine_kill_bh(&vchan->vc.chan);
> }
> }
>
> diff --git a/drivers/dma/tegra186-gpc-dma.c b/drivers/dma/tegra186-gpc-dma.c
> index 64cedef1050a..3a27a83a3288 100644
> --- a/drivers/dma/tegra186-gpc-dma.c
> +++ b/drivers/dma/tegra186-gpc-dma.c
> @@ -1284,7 +1284,7 @@ static void tegra_dma_free_chan_resources(struct dma_chan *dc)
> tegra_dma_terminate_all(dc);
> synchronize_irq(tdc->irq);
>
> - tasklet_kill(&tdc->vc.task);
> + dmaengine_kill_bh(&tdc->vc.chan);
> tdc->config_init = false;
> tdc->slave_id = -1;
> tdc->sid_dir = DMA_TRANS_NONE;
> diff --git a/drivers/dma/tegra210-adma.c b/drivers/dma/tegra210-adma.c
> index ceaee1e33e68..780863199224 100644
> --- a/drivers/dma/tegra210-adma.c
> +++ b/drivers/dma/tegra210-adma.c
> @@ -811,7 +811,7 @@ static void tegra_adma_free_chan_resources(struct dma_chan *dc)
>
> tegra_adma_terminate_all(dc);
> vchan_free_chan_resources(&tdc->vc);
> - tasklet_kill(&tdc->vc.task);
> + dmaengine_kill_bh(&tdc->vc.chan);
> free_irq(tdc->irq, tdc);
> pm_runtime_put(tdc2dev(tdc));
>
> diff --git a/drivers/dma/ti/edma.c b/drivers/dma/ti/edma.c
> index d97db5af3555..c0ad0cdabb87 100644
> --- a/drivers/dma/ti/edma.c
> +++ b/drivers/dma/ti/edma.c
> @@ -2560,7 +2560,7 @@ static void edma_cleanupp_vchan(struct dma_device *dmadev)
> list_for_each_entry_safe(echan, _echan,
> &dmadev->channels, vchan.chan.device_node) {
> list_del(&echan->vchan.chan.device_node);
> - tasklet_kill(&echan->vchan.task);
> + dmaengine_kill_bh(&echan->vchan.chan);
> }
> }
>
> diff --git a/drivers/dma/ti/k3-udma.c b/drivers/dma/ti/k3-udma.c
> index 1cf158eb7bdb..fd428c26799a 100644
> --- a/drivers/dma/ti/k3-udma.c
> +++ b/drivers/dma/ti/k3-udma.c
> @@ -4042,12 +4042,12 @@ static void udma_desc_pre_callback(struct virt_dma_chan *vc,
> }
>
> /*
> - * This tasklet handles the completion of a DMA descriptor by
> + * This BH handles the completion of a DMA descriptor by
> * calling its callback and freeing it.
> */
> -static void udma_vchan_complete(struct tasklet_struct *t)
> +static void udma_vchan_complete(struct dma_chan *chan)
> {
> - struct virt_dma_chan *vc = from_tasklet(vc, t, task);
> + struct virt_dma_chan *vc = to_virt_chan(chan);
> struct virt_dma_desc *vd, *_vd;
> struct dmaengine_desc_callback cb;
> LIST_HEAD(head);
> @@ -4112,7 +4112,7 @@ static void udma_free_chan_resources(struct dma_chan *chan)
> }
>
> vchan_free_chan_resources(&uc->vc);
> - tasklet_kill(&uc->vc.task);
> + dmaengine_kill_bh(&uc->vc.chan);
>
> bcdma_free_bchan_resources(uc);
> udma_free_tx_resources(uc);
> @@ -5627,8 +5627,8 @@ static int udma_probe(struct platform_device *pdev)
> if (!uc->name)
> return -ENOMEM;
> vchan_init(&uc->vc, &ud->ddev);
> - /* Use custom vchan completion handling */
> - tasklet_setup(&uc->vc.task, udma_vchan_complete);
> + /* Override the default vchan completion handler */
> + uc->vc.chan.bh_work_fn = udma_vchan_complete;
> init_completion(&uc->teardown_completed);
> INIT_DELAYED_WORK(&uc->tx_drain.work, udma_check_tx_completion);
> }
> diff --git a/drivers/dma/ti/omap-dma.c b/drivers/dma/ti/omap-dma.c
> index 55ece7fd0d99..899c5eeeac88 100644
> --- a/drivers/dma/ti/omap-dma.c
> +++ b/drivers/dma/ti/omap-dma.c
> @@ -1521,7 +1521,7 @@ static void omap_dma_free(struct omap_dmadev *od)
> struct omap_chan, vc.chan.device_node);
>
> list_del(&c->vc.chan.device_node);
> - tasklet_kill(&c->vc.task);
> + dmaengine_kill_bh(&c->vc.chan);
> kfree(c);
> }
> }
> diff --git a/drivers/dma/virt-dma.c b/drivers/dma/virt-dma.c
> index 7961172a780d..d407af6cc1da 100644
> --- a/drivers/dma/virt-dma.c
> +++ b/drivers/dma/virt-dma.c
> @@ -77,12 +77,12 @@ struct virt_dma_desc *vchan_find_desc(struct virt_dma_chan *vc,
> EXPORT_SYMBOL_GPL(vchan_find_desc);
>
> /*
> - * This tasklet handles the completion of a DMA descriptor by
> - * calling its callback and freeing it.
> + * This bottom-half handler completes a DMA descriptor by invoking its
> + * callback and freeing it.
> */
> -static void vchan_complete(struct tasklet_struct *t)
> +static void vchan_complete(struct dma_chan *chan)
> {
> - struct virt_dma_chan *vc = from_tasklet(vc, t, task);
> + struct virt_dma_chan *vc = to_virt_chan(chan);
> struct virt_dma_desc *vd, *_vd;
> struct dmaengine_desc_callback cb;
> LIST_HEAD(head);
> @@ -98,7 +98,7 @@ static void vchan_complete(struct tasklet_struct *t)
> }
> spin_unlock_irq(&vc->lock);
>
> - dmaengine_desc_callback_invoke(&cb, &vd->tx_result);
> + dmaengine_desc_callback_invoke(&cb, vd ? &vd->tx_result : NULL);
>
> list_for_each_entry_safe(vd, _vd, &head, node) {
> dmaengine_desc_get_callback(&vd->tx, &cb);
> @@ -131,7 +131,7 @@ void vchan_init(struct virt_dma_chan *vc, struct dma_device *dmadev)
> INIT_LIST_HEAD(&vc->desc_completed);
> INIT_LIST_HEAD(&vc->desc_terminated);
>
> - tasklet_setup(&vc->task, vchan_complete);
> + dmaengine_init_bh(&vc->chan, vchan_complete);
>
> vc->chan.device = dmadev;
> list_add_tail(&vc->chan.device_node, &dmadev->channels);
> diff --git a/drivers/dma/virt-dma.h b/drivers/dma/virt-dma.h
> index 59d9eabc8b67..0ba42fded2cc 100644
> --- a/drivers/dma/virt-dma.h
> +++ b/drivers/dma/virt-dma.h
> @@ -21,7 +21,6 @@ struct virt_dma_desc {
>
> struct virt_dma_chan {
> struct dma_chan chan;
> - struct tasklet_struct task;
> void (*desc_free)(struct virt_dma_desc *);
>
> spinlock_t lock;
> @@ -106,7 +105,7 @@ static inline void vchan_cookie_complete(struct virt_dma_desc *vd)
> vd, cookie);
> list_add_tail(&vd->node, &vc->desc_completed);
>
> - tasklet_schedule(&vc->task);
> + dmaengine_schedule_bh(&vc->chan);
> }
>
> /**
> @@ -137,7 +136,7 @@ static inline void vchan_cyclic_callback(struct virt_dma_desc *vd)
> struct virt_dma_chan *vc = to_virt_chan(vd->tx.chan);
>
> vc->cyclic = vd;
> - tasklet_schedule(&vc->task);
> + dmaengine_schedule_bh(&vc->chan);
> }
>
> /**
> @@ -223,7 +222,7 @@ static inline void vchan_synchronize(struct virt_dma_chan *vc)
> LIST_HEAD(head);
> unsigned long flags;
>
> - tasklet_kill(&vc->task);
> + dmaengine_kill_bh(&vc->chan);
>
> spin_lock_irqsave(&vc->lock, flags);
>
> diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h
> index b3d251c9734e..a1437bdbda9b 100644
> --- a/include/linux/dmaengine.h
> +++ b/include/linux/dmaengine.h
> @@ -12,6 +12,7 @@
> #include <linux/scatterlist.h>
> #include <linux/bitmap.h>
> #include <linux/types.h>
> +#include <linux/interrupt.h>
> #include <asm/page.h>
>
> /**
> @@ -295,6 +296,10 @@ enum dma_desc_metadata_mode {
> DESC_METADATA_ENGINE = BIT(1),
> };
>
> +struct dma_chan;
> +
> +typedef void (*dmaengine_bh_work_fn)(struct dma_chan *chan);
> +
> /**
> * struct dma_chan_percpu - the per-CPU part of struct dma_chan
> * @memcpy_count: transaction counter
> @@ -334,6 +339,9 @@ struct dma_router {
> * @router: pointer to the DMA router structure
> * @route_data: channel specific data for the router
> * @private: private data for certain client-channel associations
> + * @bh_tasklet: bottom-half tasklet stored per-channel
> + * @bh_work_fn: callback executed when @bh_tasklet runs
> + * @bh_work_initialized: indicates whether @bh_tasklet has been initialized
> */
> struct dma_chan {
> struct dma_device *device;
> @@ -359,6 +367,9 @@ struct dma_chan {
> void *route_data;
>
> void *private;
> + struct tasklet_struct bh_tasklet;
> + dmaengine_bh_work_fn bh_work_fn;
> + bool bh_work_initialized;
> };
>
> /**
> @@ -1529,6 +1540,9 @@ struct dma_chan *devm_dma_request_chan(struct device *dev, const char *name);
>
> void dma_release_channel(struct dma_chan *chan);
> int dma_get_slave_caps(struct dma_chan *chan, struct dma_slave_caps *caps);
> +void dmaengine_init_bh(struct dma_chan *chan, dmaengine_bh_work_fn fn);
> +bool dmaengine_schedule_bh(struct dma_chan *chan);
> +void dmaengine_kill_bh(struct dma_chan *chan);
> #else
> static inline struct dma_chan *dma_find_channel(enum dma_transaction_type tx_type)
> {
> @@ -1576,6 +1590,20 @@ static inline int dma_get_slave_caps(struct dma_chan *chan,
> {
> return -ENXIO;
> }
> +
> +static inline void dmaengine_init_bh(struct dma_chan *chan,
> + dmaengine_bh_work_fn fn)
> +{
> +}
> +
> +static inline bool dmaengine_schedule_bh(struct dma_chan *chan)
> +{
> + return false;
> +}
> +
> +static inline void dmaengine_kill_bh(struct dma_chan *chan)
> +{
> +}
> #endif
>
> static inline int dmaengine_desc_set_reuse(struct dma_async_tx_descriptor *tx)
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PATCH v3 08/34] dmaengine: imx-dma: flip per-chan tasklet to dmaengine BH
2026-08-10 18:09 ` [PATCH v3 08/34] dmaengine: imx-dma: flip per-chan tasklet " Allen Pais
2026-08-10 18:34 ` sashiko-bot
@ 2026-09-02 15:17 ` Frank Li
2026-09-02 15:28 ` Frank Li
2 siblings, 0 replies; 63+ messages in thread
From: Frank Li @ 2026-09-02 15:17 UTC (permalink / raw)
To: Allen Pais
Cc: Vinod Koul, Frank Li, dmaengine, linux-kernel, Arnd Bergmann,
Kees Cook, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
imx, linux-arm-kernel
On Mon, Aug 10, 2026 at 11:09:09AM -0700, Allen Pais wrote:
> Replace the per-channel tasklet with the shared dmaengine BH helper.
> The handler continues to run in softirq context while dmaengine owns
> the common scheduling and teardown mechanism.
>
> Signed-off-by: Allen Pais <allen.lkml@gmail.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/dma/imx-dma.c | 26 +++++++++++++-------------
> 1 file changed, 13 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/dma/imx-dma.c b/drivers/dma/imx-dma.c
> index 81c6276436f8..308fe407fe16 100644
> --- a/drivers/dma/imx-dma.c
> +++ b/drivers/dma/imx-dma.c
> @@ -145,7 +145,6 @@ struct imxdma_channel {
> struct imxdma_engine *imxdma;
> unsigned int channel;
>
> - struct tasklet_struct dma_tasklet;
> struct list_head ld_free;
> struct list_head ld_queue;
> struct list_head ld_active;
> @@ -344,8 +343,8 @@ static void imxdma_watchdog(struct timer_list *t)
>
> imx_dmav1_writel(imxdma, 0, DMA_CCR(channel));
>
> - /* Tasklet watchdog error handler */
> - tasklet_schedule(&imxdmac->dma_tasklet);
> + /* BH watchdog error handler */
> + dmaengine_schedule_bh(&imxdmac->chan);
> dev_dbg(imxdma->dev, "channel %d: watchdog timeout!\n",
> imxdmac->channel);
> }
> @@ -390,8 +389,8 @@ static irqreturn_t imxdma_err_handler(int irq, void *dev_id)
> imx_dmav1_writel(imxdma, 1 << i, DMA_DBOSR);
> errcode |= IMX_DMA_ERR_BUFFER;
> }
> - /* Tasklet error handler */
> - tasklet_schedule(&imxdma->channel[i].dma_tasklet);
> + /* BH error handler */
> + dmaengine_schedule_bh(&imxdma->channel[i].chan);
>
> dev_warn(imxdma->dev,
> "DMA timeout on channel %d -%s%s%s%s\n", i,
> @@ -448,8 +447,8 @@ static void dma_irq_handle_channel(struct imxdma_channel *imxdmac)
> imx_dmav1_writel(imxdma, tmp, DMA_CCR(chno));
>
> if (imxdma_chan_is_doing_cyclic(imxdmac))
> - /* Tasklet progression */
> - tasklet_schedule(&imxdmac->dma_tasklet);
> + /* BH progression */
> + dmaengine_schedule_bh(&imxdmac->chan);
>
> return;
> }
> @@ -462,8 +461,8 @@ static void dma_irq_handle_channel(struct imxdma_channel *imxdmac)
>
> out:
> imx_dmav1_writel(imxdma, 0, DMA_CCR(chno));
> - /* Tasklet irq */
> - tasklet_schedule(&imxdmac->dma_tasklet);
> + /* Schedule the IRQ BH */
> + dmaengine_schedule_bh(&imxdmac->chan);
> }
>
> static irqreturn_t dma_irq_handler(int irq, void *dev_id)
> @@ -592,9 +591,10 @@ static int imxdma_xfer_desc(struct imxdma_desc *d)
> return 0;
> }
>
> -static void imxdma_tasklet(struct tasklet_struct *t)
> +static void imxdma_tasklet(struct dma_chan *chan)
> {
> - struct imxdma_channel *imxdmac = from_tasklet(imxdmac, t, dma_tasklet);
> + struct imxdma_channel *imxdmac = container_of(chan, struct imxdma_channel,
> + chan);
> struct imxdma_engine *imxdma = imxdmac->imxdma;
> struct imxdma_desc *desc, *next_desc;
> unsigned long flags;
> @@ -1142,7 +1142,7 @@ static int __init imxdma_probe(struct platform_device *pdev)
> INIT_LIST_HEAD(&imxdmac->ld_free);
> INIT_LIST_HEAD(&imxdmac->ld_active);
>
> - tasklet_setup(&imxdmac->dma_tasklet, imxdma_tasklet);
> + dmaengine_init_bh(&imxdmac->chan, imxdma_tasklet);
> imxdmac->chan.device = &imxdma->dma_device;
> dma_cookie_init(&imxdmac->chan);
> imxdmac->channel = i;
> @@ -1211,7 +1211,7 @@ static void imxdma_free_irq(struct platform_device *pdev, struct imxdma_engine *
> if (!is_imx1_dma(imxdma))
> disable_irq(imxdmac->irq);
>
> - tasklet_kill(&imxdmac->dma_tasklet);
> + dmaengine_kill_bh(&imxdmac->chan);
> }
> }
>
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PATCH v3 08/34] dmaengine: imx-dma: flip per-chan tasklet to dmaengine BH
2026-08-10 18:09 ` [PATCH v3 08/34] dmaengine: imx-dma: flip per-chan tasklet " Allen Pais
2026-08-10 18:34 ` sashiko-bot
2026-09-02 15:17 ` Frank Li
@ 2026-09-02 15:28 ` Frank Li
2 siblings, 0 replies; 63+ messages in thread
From: Frank Li @ 2026-09-02 15:28 UTC (permalink / raw)
To: Allen Pais
Cc: Vinod Koul, Frank Li, dmaengine, linux-kernel, Arnd Bergmann,
Kees Cook, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
imx, linux-arm-kernel
On Mon, Aug 10, 2026 at 11:09:09AM -0700, Allen Pais wrote:
> Replace the per-channel tasklet with the shared dmaengine BH helper.
> The handler continues to run in softirq context while dmaengine owns
> the common scheduling and teardown mechanism.
>
> Signed-off-by: Allen Pais <allen.lkml@gmail.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/dma/imx-dma.c | 26 +++++++++++++-------------
> 1 file changed, 13 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/dma/imx-dma.c b/drivers/dma/imx-dma.c
> index 81c6276436f8..308fe407fe16 100644
> --- a/drivers/dma/imx-dma.c
> +++ b/drivers/dma/imx-dma.c
> @@ -145,7 +145,6 @@ struct imxdma_channel {
> struct imxdma_engine *imxdma;
> unsigned int channel;
>
> - struct tasklet_struct dma_tasklet;
> struct list_head ld_free;
> struct list_head ld_queue;
> struct list_head ld_active;
> @@ -344,8 +343,8 @@ static void imxdma_watchdog(struct timer_list *t)
>
> imx_dmav1_writel(imxdma, 0, DMA_CCR(channel));
>
> - /* Tasklet watchdog error handler */
> - tasklet_schedule(&imxdmac->dma_tasklet);
> + /* BH watchdog error handler */
> + dmaengine_schedule_bh(&imxdmac->chan);
> dev_dbg(imxdma->dev, "channel %d: watchdog timeout!\n",
> imxdmac->channel);
> }
> @@ -390,8 +389,8 @@ static irqreturn_t imxdma_err_handler(int irq, void *dev_id)
> imx_dmav1_writel(imxdma, 1 << i, DMA_DBOSR);
> errcode |= IMX_DMA_ERR_BUFFER;
> }
> - /* Tasklet error handler */
> - tasklet_schedule(&imxdma->channel[i].dma_tasklet);
> + /* BH error handler */
> + dmaengine_schedule_bh(&imxdma->channel[i].chan);
>
> dev_warn(imxdma->dev,
> "DMA timeout on channel %d -%s%s%s%s\n", i,
> @@ -448,8 +447,8 @@ static void dma_irq_handle_channel(struct imxdma_channel *imxdmac)
> imx_dmav1_writel(imxdma, tmp, DMA_CCR(chno));
>
> if (imxdma_chan_is_doing_cyclic(imxdmac))
> - /* Tasklet progression */
> - tasklet_schedule(&imxdmac->dma_tasklet);
> + /* BH progression */
> + dmaengine_schedule_bh(&imxdmac->chan);
>
> return;
> }
> @@ -462,8 +461,8 @@ static void dma_irq_handle_channel(struct imxdma_channel *imxdmac)
>
> out:
> imx_dmav1_writel(imxdma, 0, DMA_CCR(chno));
> - /* Tasklet irq */
> - tasklet_schedule(&imxdmac->dma_tasklet);
> + /* Schedule the IRQ BH */
> + dmaengine_schedule_bh(&imxdmac->chan);
> }
>
> static irqreturn_t dma_irq_handler(int irq, void *dev_id)
> @@ -592,9 +591,10 @@ static int imxdma_xfer_desc(struct imxdma_desc *d)
> return 0;
> }
>
> -static void imxdma_tasklet(struct tasklet_struct *t)
> +static void imxdma_tasklet(struct dma_chan *chan)
> {
> - struct imxdma_channel *imxdmac = from_tasklet(imxdmac, t, dma_tasklet);
> + struct imxdma_channel *imxdmac = container_of(chan, struct imxdma_channel,
> + chan);
> struct imxdma_engine *imxdma = imxdmac->imxdma;
> struct imxdma_desc *desc, *next_desc;
> unsigned long flags;
> @@ -1142,7 +1142,7 @@ static int __init imxdma_probe(struct platform_device *pdev)
> INIT_LIST_HEAD(&imxdmac->ld_free);
> INIT_LIST_HEAD(&imxdmac->ld_active);
>
> - tasklet_setup(&imxdmac->dma_tasklet, imxdma_tasklet);
> + dmaengine_init_bh(&imxdmac->chan, imxdma_tasklet);
> imxdmac->chan.device = &imxdma->dma_device;
> dma_cookie_init(&imxdmac->chan);
> imxdmac->channel = i;
> @@ -1211,7 +1211,7 @@ static void imxdma_free_irq(struct platform_device *pdev, struct imxdma_engine *
> if (!is_imx1_dma(imxdma))
> disable_irq(imxdmac->irq);
>
> - tasklet_kill(&imxdmac->dma_tasklet);
> + dmaengine_kill_bh(&imxdmac->chan);
> }
> }
>
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PATCH v3 21/34] dmaengine: zynqmp-dma: switch completion tasklet to dmaengine BH
2026-08-10 18:09 ` [PATCH v3 21/34] dmaengine: zynqmp-dma: switch completion tasklet to dmaengine BH Allen Pais
@ 2026-09-02 15:30 ` Frank Li
0 siblings, 0 replies; 63+ messages in thread
From: Frank Li @ 2026-09-02 15:30 UTC (permalink / raw)
To: Allen Pais
Cc: Vinod Koul, Frank Li, dmaengine, linux-kernel, Arnd Bergmann,
Kees Cook, Michal Simek, Abin Joseph, Sakari Ailus,
linux-arm-kernel
On Mon, Aug 10, 2026 at 11:09:22AM -0700, Allen Pais wrote:
> Replace the per-channel tasklet with the shared dmaengine BH helper.
> The handler continues to run in softirq context while dmaengine owns
> the common scheduling and teardown mechanism.
>
> Signed-off-by: Allen Pais <allen.lkml@gmail.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/dma/xilinx/zynqmp_dma.c | 19 +++++++++----------
> 1 file changed, 9 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/dma/xilinx/zynqmp_dma.c b/drivers/dma/xilinx/zynqmp_dma.c
> index f6a812e49ddc..e396a35f0e0d 100644
> --- a/drivers/dma/xilinx/zynqmp_dma.c
> +++ b/drivers/dma/xilinx/zynqmp_dma.c
> @@ -207,7 +207,6 @@ struct zynqmp_dma_desc_sw {
> * @dev: The dma device
> * @irq: Channel IRQ
> * @is_dmacoherent: Tells whether dma operations are coherent or not
> - * @tasklet: Cleanup work after irq
> * @idle : Channel status;
> * @desc_size: Size of the low level descriptor
> * @err: Channel has errors
> @@ -232,7 +231,6 @@ struct zynqmp_dma_chan {
> struct device *dev;
> int irq;
> bool is_dmacoherent;
> - struct tasklet_struct tasklet;
> bool idle;
> size_t desc_size;
> bool err;
> @@ -735,7 +733,7 @@ static irqreturn_t zynqmp_dma_irq_handler(int irq, void *data)
>
> writel(isr, chan->regs + ZYNQMP_DMA_ISR);
> if (status & ZYNQMP_DMA_INT_DONE) {
> - tasklet_schedule(&chan->tasklet);
> + dmaengine_schedule_bh(&chan->common);
> ret = IRQ_HANDLED;
> }
>
> @@ -744,7 +742,7 @@ static irqreturn_t zynqmp_dma_irq_handler(int irq, void *data)
>
> if (status & ZYNQMP_DMA_INT_ERR) {
> chan->err = true;
> - tasklet_schedule(&chan->tasklet);
> + dmaengine_schedule_bh(&chan->common);
> dev_err(chan->dev, "Channel %p has errors\n", chan);
> ret = IRQ_HANDLED;
> }
> @@ -760,11 +758,12 @@ static irqreturn_t zynqmp_dma_irq_handler(int irq, void *data)
>
> /**
> * zynqmp_dma_do_tasklet - Schedule completion tasklet
> - * @t: Pointer to the ZynqMP DMA channel structure
> + * @c: Pointer to the ZynqMP DMA channel structure
> */
> -static void zynqmp_dma_do_tasklet(struct tasklet_struct *t)
> +static void zynqmp_dma_do_tasklet(struct dma_chan *c)
> {
> - struct zynqmp_dma_chan *chan = from_tasklet(chan, t, tasklet);
> + struct zynqmp_dma_chan *chan = container_of(c,
> + struct zynqmp_dma_chan, common);
> u32 count;
> unsigned long irqflags;
>
> @@ -815,7 +814,7 @@ static void zynqmp_dma_synchronize(struct dma_chan *dchan)
> {
> struct zynqmp_dma_chan *chan = to_chan(dchan);
>
> - tasklet_kill(&chan->tasklet);
> + dmaengine_kill_bh(&chan->common);
> }
>
> /**
> @@ -887,7 +886,7 @@ static void zynqmp_dma_chan_remove(struct zynqmp_dma_chan *chan)
>
> if (chan->irq)
> devm_free_irq(chan->zdev->dev, chan->irq, chan);
> - tasklet_kill(&chan->tasklet);
> + dmaengine_kill_bh(&chan->common);
> list_del(&chan->common.device_node);
> }
>
> @@ -937,7 +936,7 @@ static int zynqmp_dma_chan_probe(struct zynqmp_dma_device *zdev,
>
> chan->is_dmacoherent = of_property_read_bool(node, "dma-coherent");
> zdev->chan = chan;
> - tasklet_setup(&chan->tasklet, zynqmp_dma_do_tasklet);
> + dmaengine_init_bh(&chan->common, zynqmp_dma_do_tasklet);
> spin_lock_init(&chan->lock);
> INIT_LIST_HEAD(&chan->active_list);
> INIT_LIST_HEAD(&chan->pending_list);
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PATCH v3 20/34] dmaengine: xilinx-dpdma: kill vchan BH on remove
2026-08-10 18:09 ` [PATCH v3 20/34] dmaengine: xilinx-dpdma: kill vchan BH on remove Allen Pais
@ 2026-09-02 15:31 ` Frank Li
0 siblings, 0 replies; 63+ messages in thread
From: Frank Li @ 2026-09-02 15:31 UTC (permalink / raw)
To: Allen Pais
Cc: Vinod Koul, Frank Li, dmaengine, linux-kernel, Arnd Bergmann,
Kees Cook, Laurent Pinchart, Michal Simek, linux-arm-kernel
On Mon, Aug 10, 2026 at 11:09:21AM -0700, Allen Pais wrote:
> virt-dma now dispatches completion callbacks through per-channel BH
> work. Cancel that work when removing a channel, while retaining the
> driver's separate error-handling tasklet.
>
> Signed-off-by: Allen Pais <allen.lkml@gmail.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/dma/xilinx/xilinx_dpdma.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/drivers/dma/xilinx/xilinx_dpdma.c b/drivers/dma/xilinx/xilinx_dpdma.c
> index d9a3542c4531..7e147ea11368 100644
> --- a/drivers/dma/xilinx/xilinx_dpdma.c
> +++ b/drivers/dma/xilinx/xilinx_dpdma.c
> @@ -1685,6 +1685,7 @@ static void xilinx_dpdma_chan_remove(struct xilinx_dpdma_chan *chan)
> return;
>
> tasklet_kill(&chan->err_task);
> + dmaengine_kill_bh(&chan->vchan.chan);
> list_del(&chan->vchan.chan.device_node);
> }
>
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PATCH v3 03/34] dmaengine: apple-admac: use dmaengine BH callback
2026-08-10 18:09 ` [PATCH v3 03/34] dmaengine: apple-admac: use dmaengine BH callback Allen Pais
@ 2026-09-02 15:33 ` Frank Li
0 siblings, 0 replies; 63+ messages in thread
From: Frank Li @ 2026-09-02 15:33 UTC (permalink / raw)
To: Allen Pais
Cc: Vinod Koul, Frank Li, dmaengine, linux-kernel, Arnd Bergmann,
Kees Cook, Sven Peter, Janne Grunau, Neal Gompa, asahi,
linux-arm-kernel
On Mon, Aug 10, 2026 at 11:09:04AM -0700, Allen Pais wrote:
> Replace the per-channel tasklet with the shared dmaengine BH helper.
> The handler continues to run in softirq context while dmaengine owns
> the common scheduling and teardown mechanism.
>
> Signed-off-by: Allen Pais <allen.lkml@gmail.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/dma/apple-admac.c | 16 ++++++----------
> 1 file changed, 6 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/dma/apple-admac.c b/drivers/dma/apple-admac.c
> index 14a5ee14a481..c5d4fb96d3c4 100644
> --- a/drivers/dma/apple-admac.c
> +++ b/drivers/dma/apple-admac.c
> @@ -89,7 +89,6 @@ struct admac_chan {
> unsigned int no;
> struct admac_data *host;
> struct dma_chan chan;
> - struct tasklet_struct tasklet;
>
> u32 carveout;
>
> @@ -521,10 +520,7 @@ static int admac_terminate_all(struct dma_chan *chan)
> list_add_tail(&adchan->current_tx->node, &adchan->to_free);
> adchan->current_tx = NULL;
> }
> - /*
> - * Descriptors can only be freed after the tasklet
> - * has been killed (in admac_synchronize).
> - */
> + /* Descriptors are released once the BH is flushed in admac_synchronize */
> list_splice_tail_init(&adchan->submitted, &adchan->to_free);
> list_splice_tail_init(&adchan->issued, &adchan->to_free);
> spin_unlock_irqrestore(&adchan->lock, flags);
> @@ -543,7 +539,7 @@ static void admac_synchronize(struct dma_chan *chan)
> list_splice_tail_init(&adchan->to_free, &head);
> spin_unlock_irqrestore(&adchan->lock, flags);
>
> - tasklet_kill(&adchan->tasklet);
> + dmaengine_kill_bh(&adchan->chan);
>
> list_for_each_entry_safe(adtx, _adtx, &head, node) {
> list_del(&adtx->node);
> @@ -662,7 +658,7 @@ static void admac_handle_status_desc_done(struct admac_data *ad, int channo)
> tx->reclaimed_pos %= 2 * tx->buf_len;
>
> admac_cyclic_write_desc(ad, channo, tx);
> - tasklet_schedule(&adchan->tasklet);
> + dmaengine_schedule_bh(&adchan->chan);
> }
> spin_unlock_irqrestore(&adchan->lock, flags);
> }
> @@ -712,9 +708,9 @@ static irqreturn_t admac_interrupt(int irq, void *devid)
> return IRQ_HANDLED;
> }
>
> -static void admac_chan_tasklet(struct tasklet_struct *t)
> +static void admac_chan_bh(struct dma_chan *chan)
> {
> - struct admac_chan *adchan = from_tasklet(adchan, t, tasklet);
> + struct admac_chan *adchan = to_admac_chan(chan);
> struct admac_tx *adtx;
> struct dmaengine_desc_callback cb;
> struct dmaengine_result tx_result;
> @@ -886,7 +882,7 @@ static int admac_probe(struct platform_device *pdev)
> INIT_LIST_HEAD(&adchan->issued);
> INIT_LIST_HEAD(&adchan->to_free);
> list_add_tail(&adchan->chan.device_node, &dma->channels);
> - tasklet_setup(&adchan->tasklet, admac_chan_tasklet);
> + dmaengine_init_bh(&adchan->chan, admac_chan_bh);
> }
>
> err = reset_control_reset(ad->rstc);
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PATCH v3 13/34] dmaengine: mxs-dma: use dmaengine BH scheduling
2026-08-10 18:09 ` [PATCH v3 13/34] dmaengine: mxs-dma: use dmaengine BH scheduling Allen Pais
2026-08-10 18:27 ` sashiko-bot
@ 2026-09-02 15:33 ` Frank Li
1 sibling, 0 replies; 63+ messages in thread
From: Frank Li @ 2026-09-02 15:33 UTC (permalink / raw)
To: Allen Pais
Cc: Vinod Koul, Frank Li, dmaengine, linux-kernel, Arnd Bergmann,
Kees Cook, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
imx, linux-arm-kernel
On Mon, Aug 10, 2026 at 11:09:14AM -0700, Allen Pais wrote:
> Replace the per-channel tasklet with the shared dmaengine BH helper.
> The handler continues to run in softirq context while dmaengine owns
> the common scheduling and teardown mechanism.
>
> Signed-off-by: Allen Pais <allen.lkml@gmail.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/dma/mxs-dma.c | 11 +++++------
> 1 file changed, 5 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/dma/mxs-dma.c b/drivers/dma/mxs-dma.c
> index 7acb3d29dad3..149ac402f7b4 100644
> --- a/drivers/dma/mxs-dma.c
> +++ b/drivers/dma/mxs-dma.c
> @@ -109,7 +109,6 @@ struct mxs_dma_chan {
> struct mxs_dma_engine *mxs_dma;
> struct dma_chan chan;
> struct dma_async_tx_descriptor desc;
> - struct tasklet_struct tasklet;
> unsigned int chan_irq;
> struct mxs_dma_ccw *ccw;
> dma_addr_t ccw_phys;
> @@ -300,9 +299,9 @@ static dma_cookie_t mxs_dma_tx_submit(struct dma_async_tx_descriptor *tx)
> return dma_cookie_assign(tx);
> }
>
> -static void mxs_dma_tasklet(struct tasklet_struct *t)
> +static void mxs_dma_tasklet(struct dma_chan *chan)
> {
> - struct mxs_dma_chan *mxs_chan = from_tasklet(mxs_chan, t, tasklet);
> + struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(chan);
>
> dmaengine_desc_get_callback_invoke(&mxs_chan->desc, NULL);
> }
> @@ -386,8 +385,8 @@ static irqreturn_t mxs_dma_int_handler(int irq, void *dev_id)
> dma_cookie_complete(&mxs_chan->desc);
> }
>
> - /* schedule tasklet on this channel */
> - tasklet_schedule(&mxs_chan->tasklet);
> + /* schedule BH on this channel */
> + dmaengine_schedule_bh(&mxs_chan->chan);
>
> return IRQ_HANDLED;
> }
> @@ -781,7 +780,7 @@ static int mxs_dma_probe(struct platform_device *pdev)
> mxs_chan->chan.device = &mxs_dma->dma_device;
> dma_cookie_init(&mxs_chan->chan);
>
> - tasklet_setup(&mxs_chan->tasklet, mxs_dma_tasklet);
> + dmaengine_init_bh(&mxs_chan->chan, mxs_dma_tasklet);
>
>
> /* Add the channel to mxs_chan list */
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PATCH v3 19/34] dmaengine: xilinx-dma: use dmaengine BH instead of tasklets
2026-08-10 18:09 ` [PATCH v3 19/34] dmaengine: xilinx-dma: use dmaengine BH instead of tasklets Allen Pais
@ 2026-09-02 15:35 ` Frank Li
0 siblings, 0 replies; 63+ messages in thread
From: Frank Li @ 2026-09-02 15:35 UTC (permalink / raw)
To: Allen Pais
Cc: Vinod Koul, Frank Li, dmaengine, linux-kernel, Arnd Bergmann,
Kees Cook, Michal Simek, Suraj Gupta, Marek Vasut,
Folker Schwesinger, Tomi Valkeinen, Krzysztof Kozlowski,
linux-arm-kernel
On Mon, Aug 10, 2026 at 11:09:20AM -0700, Allen Pais wrote:
> Replace the per-channel tasklet with the shared dmaengine BH helper.
> The handler continues to run in softirq context while dmaengine owns
> the common scheduling and teardown mechanism.
>
> Signed-off-by: Allen Pais <allen.lkml@gmail.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/dma/xilinx/xilinx_dma.c | 20 +++++++++-----------
> 1 file changed, 9 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/dma/xilinx/xilinx_dma.c b/drivers/dma/xilinx/xilinx_dma.c
> index 404235c17353..b142c9c0583c 100644
> --- a/drivers/dma/xilinx/xilinx_dma.c
> +++ b/drivers/dma/xilinx/xilinx_dma.c
> @@ -402,7 +402,6 @@ struct xilinx_dma_tx_descriptor {
> * @err: Channel has errors
> * @idle: Check for channel idle
> * @terminating: Check for channel being synchronized by user
> - * @tasklet: Cleanup work after irq
> * @config: Device configuration info
> * @flush_on_fsync: Flush on Frame sync
> * @desc_pendingcount: Descriptor pending count
> @@ -441,7 +440,6 @@ struct xilinx_dma_chan {
> bool err;
> bool idle;
> bool terminating;
> - struct tasklet_struct tasklet;
> struct xilinx_vdma_config config;
> bool flush_on_fsync;
> u32 desc_pendingcount;
> @@ -1115,11 +1113,12 @@ static void xilinx_dma_chan_desc_cleanup(struct xilinx_dma_chan *chan)
>
> /**
> * xilinx_dma_do_tasklet - Schedule completion tasklet
> - * @t: Pointer to the Xilinx DMA channel structure
> + * @c: Pointer to the Xilinx DMA channel structure
> */
> -static void xilinx_dma_do_tasklet(struct tasklet_struct *t)
> +static void xilinx_dma_do_tasklet(struct dma_chan *c)
> {
> - struct xilinx_dma_chan *chan = from_tasklet(chan, t, tasklet);
> + struct xilinx_dma_chan *chan = container_of(c,
> + struct xilinx_dma_chan, common);
>
> xilinx_dma_chan_desc_cleanup(chan);
> }
> @@ -1898,7 +1897,7 @@ static irqreturn_t xilinx_mcdma_irq_handler(int irq, void *data)
> spin_unlock(&chan->lock);
> }
>
> - tasklet_hi_schedule(&chan->tasklet);
> + dmaengine_schedule_bh(&chan->common);
> return IRQ_HANDLED;
> }
>
> @@ -1955,7 +1954,7 @@ static irqreturn_t xilinx_dma_irq_handler(int irq, void *data)
> spin_unlock(&chan->lock);
> }
>
> - tasklet_schedule(&chan->tasklet);
> + dmaengine_schedule_bh(&chan->common);
> return IRQ_HANDLED;
> }
>
> @@ -2654,7 +2653,7 @@ static void xilinx_dma_synchronize(struct dma_chan *dchan)
> {
> struct xilinx_dma_chan *chan = to_xilinx_chan(dchan);
>
> - tasklet_kill(&chan->tasklet);
> + dmaengine_kill_bh(&chan->common);
> }
>
> /**
> @@ -2745,7 +2744,7 @@ static void xilinx_dma_chan_remove(struct xilinx_dma_chan *chan)
> if (chan->irq > 0)
> free_irq(chan->irq, chan);
>
> - tasklet_kill(&chan->tasklet);
> + dmaengine_kill_bh(&chan->common);
>
> list_del(&chan->common.device_node);
> }
> @@ -3075,8 +3074,7 @@ static int xilinx_dma_chan_probe(struct xilinx_dma_device *xdev,
> str_enabled_disabled(chan->has_sg));
> }
>
> - /* Initialize the tasklet */
> - tasklet_setup(&chan->tasklet, xilinx_dma_do_tasklet);
> + dmaengine_init_bh(&chan->common, xilinx_dma_do_tasklet);
>
> /*
> * Initialize the DMA channel and add it to the DMA engine channels
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 63+ messages in thread
* Re: [PATCH v3 32/34] dmaengine: hidma: defer callbacks via channel BH
2026-08-10 18:09 ` [PATCH v3 32/34] dmaengine: hidma: defer callbacks via channel BH Allen Pais
@ 2026-09-02 15:47 ` Frank Li
0 siblings, 0 replies; 63+ messages in thread
From: Frank Li @ 2026-09-02 15:47 UTC (permalink / raw)
To: Allen Pais
Cc: Vinod Koul, Frank Li, dmaengine, linux-kernel, Arnd Bergmann,
Kees Cook, Sinan Kaya, linux-arm-kernel, linux-arm-msm
On Mon, Aug 10, 2026 at 11:09:33AM -0700, Allen Pais wrote:
> Move descriptor callback processing out of the low-level completion
> path and schedule it through per-channel BH work. Drain that work while
> freeing channels so callbacks cannot outlive channel storage.
>
> Signed-off-by: Allen Pais <allen.lkml@gmail.com>
> ---
> drivers/dma/qcom/hidma.c | 18 ++++++++++++++++--
> 1 file changed, 16 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/dma/qcom/hidma.c b/drivers/dma/qcom/hidma.c
> index c939635be21d..486a2c61a9fb 100644
> --- a/drivers/dma/qcom/hidma.c
> +++ b/drivers/dma/qcom/hidma.c
> @@ -90,7 +90,13 @@ static inline struct hidma_chan *to_hidma_chan(struct dma_chan *dmach)
>
> static void hidma_free(struct hidma_dev *dmadev)
> {
> - INIT_LIST_HEAD(&dmadev->ddev.channels);
> + struct hidma_chan *mchan, *next;
> +
> + list_for_each_entry_safe(mchan, next, &dmadev->ddev.channels,
> + chan.device_node) {
> + dmaengine_kill_bh(&mchan->chan);
> + list_del(&mchan->chan.device_node);
origial code have not list_del(), is it fix other problem?
Frank
> + }
> }
>
> static unsigned int nr_desc_prm;
> @@ -155,6 +161,13 @@ static void hidma_process_completed(struct hidma_chan *mchan)
> }
> }
>
> +static void hidma_chan_bh(struct dma_chan *chan)
> +{
> + struct hidma_chan *mchan = to_hidma_chan(chan);
> +
> + hidma_process_completed(mchan);
> +}
> +
> /*
> * Called once for each submitted descriptor.
> * PM is locked once for each descriptor that is currently
> @@ -181,7 +194,7 @@ static void hidma_callback(void *data)
> }
> spin_unlock_irqrestore(&mchan->lock, irqflags);
>
> - hidma_process_completed(mchan);
> + dmaengine_schedule_bh(&mchan->chan);
>
> if (queued) {
> pm_runtime_mark_last_busy(dmadev->ddev.dev);
> @@ -203,6 +216,7 @@ static int hidma_chan_init(struct hidma_dev *dmadev, u32 dma_sig)
> mchan->dmadev = dmadev;
> mchan->chan.device = ddev;
> dma_cookie_init(&mchan->chan);
> + dmaengine_init_bh(&mchan->chan, hidma_chan_bh);
>
> INIT_LIST_HEAD(&mchan->free);
> INIT_LIST_HEAD(&mchan->prepared);
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 63+ messages in thread
end of thread, other threads:[~2026-09-02 15:48 UTC | newest]
Thread overview: 63+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-08 8:03 [RFC PATCH 0/1] dmaengine: introduce dmaengine_bh_wq and bh helpers Allen Pais
2026-01-08 8:03 ` [RFC PATCH 1/1] " Allen Pais
2026-01-08 10:26 ` Arnd Bergmann
2026-01-08 19:22 ` Allen
2026-01-09 16:42 ` Arnd Bergmann
2026-01-12 22:20 ` Allen
2026-01-13 7:33 ` Arnd Bergmann
2026-01-13 19:31 ` Allen
2026-01-12 6:26 ` kernel test robot
2026-01-14 7:13 ` kernel test robot
2026-01-14 7:35 ` kernel test robot
2026-01-14 8:49 ` kernel test robot
[not found] ` <cover.1785183549.git.allen.lkml@gmail.com>
2026-07-27 20:28 ` [PATCH v2 03/64] dmaengine: apple-admac: use dma_chan BH callback Allen Pais
2026-07-27 20:28 ` [PATCH v2 04/64] dmaengine: at_xdmac: move irq bottom half to dma_chan BH Allen Pais
2026-07-27 20:28 ` [PATCH v2 08/64] dmaengine: imx-dma: flip per-chan tasklet " Allen Pais
2026-07-27 20:57 ` sashiko-bot
2026-07-27 20:28 ` [PATCH v2 13/64] dmaengine: mxs-dma: use dma_chan BH scheduling Allen Pais
2026-07-27 20:59 ` sashiko-bot
2026-07-27 20:28 ` [PATCH v2 17/64] dmaengine: ste_dma40: convert per-channel tasklet to dma_chan BH Allen Pais
2026-07-28 19:33 ` Linus Walleij
2026-07-27 20:28 ` [PATCH v2 19/64] dmaengine: xilinx-dma: use dma_chan BH instead of tasklets Allen Pais
2026-07-27 20:28 ` [PATCH v2 20/64] dmaengine: xilinx-dpdma: kill vchan BH on remove Allen Pais
2026-07-27 20:28 ` [PATCH v2 21/64] dmaengine: zynqmp-dma: switch completion tasklet to dma_chan BH Allen Pais
2026-07-27 20:36 ` [PATCH v2 33/64] dmaengine: sun6i: kill vchan BH on teardown Allen Pais
2026-07-27 20:37 ` [PATCH v2 34/64] dmaengine: mtk-cqdma: " Allen Pais
2026-07-27 20:39 ` [PATCH v2 39/64] dmaengine: fsl-edma-common: " Allen Pais
2026-07-27 21:14 ` sashiko-bot
2026-07-27 20:39 ` [PATCH v2 42/64] dmaengine: jz4780: " Allen Pais
2026-07-27 20:39 ` [PATCH v2 43/64] dmaengine: pxa_dma: " Allen Pais
2026-07-27 20:39 ` [PATCH v2 44/64] dmaengine: mtk-hsdma: " Allen Pais
2026-07-27 20:39 ` [PATCH v2 45/64] dmaengine: mtk-uart-apdma: " Allen Pais
2026-07-27 20:39 ` [PATCH v2 46/64] dmaengine: imx-sdma: " Allen Pais
2026-07-27 21:17 ` sashiko-bot
2026-07-27 20:39 ` [PATCH v2 47/64] dmaengine: loongson1-apb: " Allen Pais
2026-07-27 20:39 ` [PATCH v2 48/64] dmaengine: owl-dma: " Allen Pais
2026-07-27 20:39 ` [PATCH v2 51/64] dmaengine: bcm2835: " Allen Pais
2026-07-27 20:39 ` [PATCH v2 54/64] dmaengine: st_fdma: use dma_chan_kill_bh Allen Pais
2026-07-27 20:39 ` [PATCH v2 62/64] dmaengine: hidma: defer callbacks via channel BH Allen Pais
[not found] ` <cover.1786384168.git.allen.lkml@gmail.com>
2026-08-10 18:09 ` [PATCH v3 01/34] dmaengine: add tasklet-backed channel BH helpers Allen Pais
2026-08-10 18:30 ` sashiko-bot
2026-09-01 22:13 ` Frank Li
2026-09-02 15:16 ` Frank Li
2026-08-10 18:09 ` [PATCH v3 03/34] dmaengine: apple-admac: use dmaengine BH callback Allen Pais
2026-09-02 15:33 ` Frank Li
2026-08-10 18:09 ` [PATCH v3 04/34] dmaengine: at_xdmac: move irq bottom half to dmaengine BH Allen Pais
2026-09-01 22:32 ` Frank Li
2026-08-10 18:09 ` [PATCH v3 08/34] dmaengine: imx-dma: flip per-chan tasklet " Allen Pais
2026-08-10 18:34 ` sashiko-bot
2026-09-02 15:17 ` Frank Li
2026-09-02 15:28 ` Frank Li
2026-08-10 18:09 ` [PATCH v3 13/34] dmaengine: mxs-dma: use dmaengine BH scheduling Allen Pais
2026-08-10 18:27 ` sashiko-bot
2026-09-02 15:33 ` Frank Li
2026-08-10 18:09 ` [PATCH v3 17/34] dmaengine: ste_dma40: convert per-channel tasklet to dmaengine BH Allen Pais
2026-09-02 14:43 ` Frank Li
2026-08-10 18:09 ` [PATCH v3 19/34] dmaengine: xilinx-dma: use dmaengine BH instead of tasklets Allen Pais
2026-09-02 15:35 ` Frank Li
2026-08-10 18:09 ` [PATCH v3 20/34] dmaengine: xilinx-dpdma: kill vchan BH on remove Allen Pais
2026-09-02 15:31 ` Frank Li
2026-08-10 18:09 ` [PATCH v3 21/34] dmaengine: zynqmp-dma: switch completion tasklet to dmaengine BH Allen Pais
2026-09-02 15:30 ` Frank Li
2026-08-10 18:09 ` [PATCH v3 32/34] dmaengine: hidma: defer callbacks via channel BH Allen Pais
2026-09-02 15:47 ` Frank Li
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.