dmaengine: introduce dmaengine_bh_wq and bh helpers
Subsystems (39)
cover dmaengine: introduce dmaengine_bh_wq and bh helpers
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/[email protected]/
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(-)
Signature · 1 line
2.43.0
RFC PATCH 1/1 dmaengine: introduce dmaengine_bh_wq and bh helpers
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.
--- 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(-)
drivers/dma/amd/qdma/qdma.c +1 -0
@@ -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"
drivers/dma/bcm2835-dma.c +1 -1
@@ -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,
drivers/dma/dmaengine.c +105 -4
138 lines not on this page · raw message
drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c +1 -1
@@ -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); } }
drivers/dma/dw-edma/dw-edma-core.c +1 -1
@@ -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); }
drivers/dma/hisi_dma.c +1 -1
@@ -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); } }
drivers/dma/img-mdc-dma.c +1 -1
@@ -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);
drivers/dma/imx-sdma.c +1 -1
@@ -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); }
drivers/dma/k3dma.c +1 -1
@@ -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);
drivers/dma/loongson1-apb-dma.c +1 -1
@@ -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); } } }
drivers/dma/mediatek/mtk-cqdma.c +1 -1
@@ -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 */
drivers/dma/mediatek/mtk-hsdma.c +1 -1
@@ -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 */
drivers/dma/mediatek/mtk-uart-apdma.c +2 -2
@@ -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); } }
drivers/dma/owl-dma.c +1 -1
@@ -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); } }
drivers/dma/pxa_dma.c +1 -1
@@ -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); } }
drivers/dma/qcom/bam_dma.c +2 -2
@@ -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 ;
drivers/dma/qcom/qcom_adm.c +1 -1
@@ -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); }
drivers/dma/sa11x0-dma.c +1 -1
@@ -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); } }
drivers/dma/sprd-dma.c +1 -1
@@ -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);
drivers/dma/sun6i-dma.c +1 -1
@@ -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); } }
drivers/dma/tegra186-gpc-dma.c +1 -1
@@ -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 ;
drivers/dma/tegra210-adma.c +1 -1
@@ -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));
drivers/dma/ti/k3-udma.c +4 -4
@@ -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); }
drivers/dma/ti/omap-dma.c +1 -1
@@ -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); } }
drivers/dma/virt-dma.c +3 -3
@@ -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);
drivers/dma/virt-dma.h +3 -5
@@ -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);
include/linux/dmaengine.h +50 -0
86 lines not on this page · raw message
Signature · 1 line
2.43.0
Quoted 5 lines · 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 indma_chanso drivers can schedule a BH callback without maintaining their own tasklets.
Quoted 8 lines
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 <[email protected]>
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
Quoted 34 lines
Quoted 5 lines
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 indma_chanso drivers can schedule a BH callback without maintaining their own tasklets.Quoted 8 lines
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 <[email protected]>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 callcomplete()orwake_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.
Quoted 8 lines
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 ofdma_async_tx_descriptorto 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 keepsdmaengine_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.
drivers/dma/dmaengine.c +0 -0
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(structvirt_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(structvirt_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(structvirt_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(structdma_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
Quoted 2 lines
Arnd
Signature · 1 line
- Allen
Quoted 4 lines · Allen
Thanks for the feedback. My intent with
WQ_BHwas 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,
Quoted 6 lines
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 keepsdmaengine_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.
Quoted 3 lines
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:
Quoted 19 lines
/*
- * 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);Quoted 2 lines
-
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
Quoted 10 lines
Quoted 3 lines
Thanks for the feedback. My intent with
WQ_BHwas 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.
Quoted 23 lines
Quoted 6 lines
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 keepsdmaengine_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.
Quoted 8 lines
Quoted 3 lines
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
Quoted 43 lines
Some more thoughts on where that later change could take us:
Quoted 19 lines
/* - * 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);Quoted 2 lines
-
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 singlecomplete()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_flagsword 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
Signature · 1 line
- Allen
Quoted 9 lines · Allen
Quoted 8 lines
Quoted 3 lines
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.
Quoted 8 lines
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 theWQ_BHbackend 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.
Quoted 3 lines
- 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.
Quoted 7 lines
- 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 updatevchan_cookie_complete()to invoke the callback directly when requested; otherwise it stays on the tasklet path.If you’d prefer I drop the
WQ_BHconversion 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
Quoted 24 lines
Quoted 9 lines
Quoted 8 lines
Quoted 3 lines
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.
Quoted 8 lines
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 theWQ_BHbackend 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.
Quoted 3 lines
- 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.
Quoted 6 lines
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
Quoted 12 lines
Quoted 8 lines
- 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 updatevchan_cookie_complete()to invoke the callback directly when requested; otherwise it stays on the tasklet path.If you’d prefer I drop the
WQ_BHconversion 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
Signature · 1 line
- Allen
kernel test robot
build botkernel test robot noticed the following build errors:
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/[email protected]/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/[email protected]/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 <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
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 Signature · 2 lines
0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
kernel test robot
build botkernel test robot noticed the following build errors:
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/[email protected]/config) compiler: csky-linux-gcc (GCC) 15.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260114/[email protected]/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 <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
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 Signature · 2 lines
0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
kernel test robot
build botkernel test robot noticed the following build warnings:
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/[email protected]/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/[email protected]/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 <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
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
Signature · 2 lines
0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
kernel test robot
build botkernel test robot noticed the following build errors:
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/[email protected]/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/[email protected]/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 <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
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 Signature · 2 lines
0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
v2 cover dmaengine: migrate channel tasklets to WQ_BH
In reply to Allen PaisHi Arnd, Vinod and Frank,
This series moves DMAengine client completion bottom halves from private tasklets to a common per-channel API backed by WQ_BH.
The first patch introduces the dma_chan_*_bh() API with a tasklet backend and converts virt-dma to it. This separates the driver-facing API from its implementation without changing callback context.
The second patch switches that backend to a dedicated WQ_BH | WQ_PERCPU workqueue. WQ_BH keeps callbacks in softirq context, while the common API centralizes initialization, scheduling, and synchronization. The workqueue helpers remain internal to DMAengine, and dma_chan_kill_bh() drains scheduled callbacks to preserve tasklet_kill() semantics.
The remaining patches convert driver-owned channel completion tasklets and update virt-dma teardown paths. Tasklets used for controller-level processing, recovery, or other non-client-callback work are deliberately left alone because the appropriate replacement may differ by driver.
As agreed in the RFC discussion, selecting hardirq callback delivery is a separate API change and is not part of this series.
Earlier discussion:
https://lore.kernel.org/all/[email protected]/
Changes since RFC v1:
- split the tasklet-backed channel API from the WQ_BH backend change
- keep the low-level workqueue helpers private to DMAengine
- preserve tasklet teardown semantics by draining scheduled BH work
- convert the remaining driver client-completion tasklets
- retain unrelated controller and recovery tasklets
- route SF-PDMA terminal errors through virt-dma completion handling
- add missing callback synchronization and teardown handling
- fix the build failures reported by the kernel test robot
- rebase onto the current Linus tree
Testing:
- x86_64 allmodconfig
- make -j$(nproc) W=1 drivers/dma/
- scripts/checkpatch.pl --strict --git origin/master..HEAD
- git diff --check origin/master..HEAD
Allen Pais (64):
dmaengine: add tasklet-backed channel BH helpers
dmaengine: back channel BH helpers with WQ_BH
dmaengine: apple-admac: use dma_chan BH callback
dmaengine: at_xdmac: move irq bottom half to dma_chan BH
dmaengine: ep93xx: hook callbacks via dma_chan BH
dmaengine: fsldma: migrate tasklet to dma_chan BH
dmaengine: fsl_raid: run completions via dma_chan BH
dmaengine: imx-dma: flip per-chan tasklet to dma_chan BH
dmaengine: ioat: convert cleanup tasklet to dma_chan BH
dmaengine: mmp_pdma: replace per-chan tasklet with dma_chan BH
dmaengine: mmp_tdma: hook completions to dma_chan BH
dmaengine: mv_xor: convert irq tasklet to dma_chan BH
dmaengine: mxs-dma: use dma_chan BH scheduling
dmaengine: nbpfaxi: switch callbacks to dma_chan BH
dmaengine: pch_dma: convert tasklet to dma_chan BH
dmaengine: ppc4xx: replace irq tasklet with dma_chan BH
dmaengine: ste_dma40: convert per-channel tasklet to dma_chan BH
dmaengine: xgene-dma: wire descriptor cleanup to dma_chan BH
dmaengine: xilinx-dma: use dma_chan BH instead of tasklets
dmaengine: xilinx-dpdma: kill vchan BH on remove
dmaengine: zynqmp-dma: switch completion tasklet to dma_chan BH
dmaengine: tegra20-apb: use channel BH helpers
dmaengine: timb_dma: route callbacks via channel BH
dmaengine: txx9dmac: route callbacks via channel BH
dmaengine: mv_xor_v2: use channel BH helpers
dmaengine: mpc512x: route callbacks via channel BH
dmaengine: k3dma: kill vchan BH on remove
dmaengine: plx_dma: use channel BH helpers
dmaengine: sf-pdma: route error callbacks through channel BH
dmaengine: sa11x0-dma: kill vchan BH on remove
dmaengine: pl330: route callbacks via channel BH
dmaengine: k3-udma: use channel BH for vchan completions
dmaengine: sun6i: kill vchan BH on teardown
dmaengine: mtk-cqdma: kill vchan BH on teardown
dmaengine: altera-msgdma: use channel BH helpers
dmaengine: sprd-dma: kill vchan BH on teardown
dmaengine: idma64: kill vchan BH on teardown
dmaengine: img-mdc-dma: kill vchan BH on teardown
dmaengine: fsl-edma-common: kill vchan BH on teardown
dmaengine: dw-axi-dmac: kill vchan BH on teardown
dmaengine: hsu: kill vchan BH on teardown
dmaengine: jz4780: kill vchan BH on teardown
dmaengine: pxa_dma: kill vchan BH on teardown
dmaengine: mtk-hsdma: kill vchan BH on teardown
dmaengine: mtk-uart-apdma: kill vchan BH on teardown
dmaengine: imx-sdma: kill vchan BH on teardown
dmaengine: loongson1-apb: kill vchan BH on teardown
dmaengine: owl-dma: kill vchan BH on teardown
dmaengine: hisi: kill vchan BH on teardown
dmaengine: dw-edma: kill vchan BH on teardown
dmaengine: bcm2835: kill vchan BH on teardown
dmaengine: tegra210-adma: kill vchan BH on teardown
dmaengine: fsl-qdma: use dma_chan_kill_bh
dmaengine: st_fdma: use dma_chan_kill_bh
dmaengine: dma-axi-dmac: use dma_chan_kill_bh
dmaengine: omap-dma: use dma_chan_kill_bh
dmaengine: edma: use dma_chan_kill_bh
dmaengine: qcom-adm: use dma_chan_kill_bh
dmaengine: tegra186-gpc: use dma_chan_kill_bh
dmaengine: bam-dma: use dma_chan_kill_bh
dmaengine: dw: defer callbacks via channel BH
dmaengine: hidma: defer callbacks via channel BH
dmaengine: qcom-gpi: defer callbacks via vchan
dmaengine: switchtec: use channel BH helpers
drivers/dma/altera-msgdma.c | 14 +-- drivers/dma/apple-admac.c | 16 ++-- drivers/dma/at_xdmac.c | 13 ++- drivers/dma/bcm2835-dma.c | 2 +- drivers/dma/dma-axi-dmac.c | 10 ++- drivers/dma/dma-jz4780.c | 2 +- drivers/dma/dmaengine.c | 89 ++++++++++++++++++- .../dma/dw-axi-dmac/dw-axi-dmac-platform.c | 2 +- drivers/dma/dw-edma/dw-edma-core.c | 2 +- drivers/dma/dw/core.c | 42 +++++++-- drivers/dma/dw/regs.h | 1 + drivers/dma/ep93xx_dma.c | 12 ++- drivers/dma/fsl-edma-common.c | 2 +- drivers/dma/fsl-qdma.c | 2 +- drivers/dma/fsl_raid.c | 11 +-- drivers/dma/fsl_raid.h | 1 - drivers/dma/fsldma.c | 10 +-- drivers/dma/fsldma.h | 1 - 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-dma.c | 26 +++--- drivers/dma/imx-sdma.c | 4 +- drivers/dma/ioat/dma.c | 14 +-- drivers/dma/ioat/dma.h | 3 +- drivers/dma/ioat/init.c | 2 +- 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/mmp_pdma.c | 13 ++- drivers/dma/mmp_tdma.c | 9 +- drivers/dma/mpc512x_dma.c | 74 +++++++++------ drivers/dma/mv_xor.c | 12 +-- drivers/dma/mv_xor.h | 2 - drivers/dma/mv_xor_v2.c | 20 ++--- drivers/dma/mxs-dma.c | 11 ++- drivers/dma/nbpfaxi.c | 13 ++- drivers/dma/owl-dma.c | 2 +- drivers/dma/pch_dma.c | 14 +-- drivers/dma/pl330.c | 18 ++-- drivers/dma/plx_dma.c | 12 +-- drivers/dma/ppc4xx/adma.c | 14 +-- drivers/dma/ppc4xx/adma.h | 2 - drivers/dma/pxa_dma.c | 2 +- drivers/dma/qcom/bam_dma.c | 4 +- drivers/dma/qcom/gpi.c | 17 +++- drivers/dma/qcom/hidma.c | 18 +++- drivers/dma/qcom/qcom_adm.c | 4 +- drivers/dma/sa11x0-dma.c | 2 +- drivers/dma/sf-pdma/sf-pdma.c | 14 ++- drivers/dma/sprd-dma.c | 2 +- drivers/dma/st_fdma.c | 2 +- drivers/dma/ste_dma40.c | 13 ++- drivers/dma/sun6i-dma.c | 2 +- drivers/dma/switchtec_dma.c | 16 ++-- drivers/dma/tegra186-gpc-dma.c | 2 +- drivers/dma/tegra20-apb-dma.c | 18 ++-- 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/timb_dma.c | 21 +++-- drivers/dma/txx9dmac.c | 12 +-- drivers/dma/txx9dmac.h | 1 - drivers/dma/virt-dma.c | 12 +-- drivers/dma/virt-dma.h | 7 +- drivers/dma/xgene-dma.c | 19 ++-- drivers/dma/xilinx/xilinx_dma.c | 20 ++--- drivers/dma/xilinx/xilinx_dpdma.c | 1 + drivers/dma/xilinx/zynqmp_dma.c | 19 ++-- include/linux/dmaengine.h | 28 ++++++ 74 files changed, 482 insertions(+), 311 deletions(-)
base-commit: f5098b6bae761e346ebcd9da7f95622c04733cff
Signature · 1 line
2.43.0
PATCH v2 1/64 dmaengine: add tasklet-backed channel BH helpers
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. 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.
--- drivers/dma/dmaengine.c | 42 +++++++++++++++++++++++++++++++++++++++ drivers/dma/virt-dma.c | 12 +++++------ drivers/dma/virt-dma.h | 7 +++---- include/linux/dmaengine.h | 28 ++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 10 deletions(-)
drivers/dma/dmaengine.c +42 -0
@@ -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); +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;
drivers/dma/virt-dma.c +6 -6
@@ -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); +dma_chan_init_bh (&vc->chan, vchan_complete); vc->chan.device = dmadev;list_add_tail (&vc->chan.device_node, &dmadev->channels);
drivers/dma/virt-dma.h +3 -4
@@ -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); +dma_chan_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); +dma_chan_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); +dma_chan_kill_bh (&vc->chan);spin_lock_irqsave (&vc->lock, flags);
include/linux/dmaengine.h +28 -0
@@ -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); +/** * 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; +dma_chan_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 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 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 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 static inline int dmaengine_desc_set_reuse (struct dma_async_tx_descriptor *tx)
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 01/64] dmaengine: add tasklet-backed channel BH helpers
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Low] API breakage leading to widespread build failures in downstream DMA drivers due to the removal of the task field from struct virt_dma_chan without updating its consumers.
Pre-existing issues:
- [Critical] Race condition causing a Use-After-Free of a cyclic descriptor's tx_result pointer when a channel is concurrently terminated. --
--- Patch [1]: [PATCH v2 01/64] dmaengine: add tasklet-backed channel BH helpers --- commit 9141d715bfba5e26881326cc343ca063eaf8458a
Author: Allen Pais <[email protected]>
dmaengine: add tasklet-backed channel BH helpers
This patch introduces helpers to initialize, schedule, and kill a channel bottom half. It converts virt-dma to use the new API, replacing its private tasklet while preserving the execution context.
Quoted 4 lines
drivers/dma/virt-dma.c +0
-0
[ ... ]
drivers/dma/virt-dma.c static void vchan_complete(struct tasklet_struct *t) · line 101
Quoted 6 lines
drivers/dma/virt-dma.c +1
-1@@ -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 );
[Severity: Critical]
This is a pre-existing issue, but does dropping vc->lock just before referencing vd->tx_result open a race window for a use-after-free?
If a concurrent dmaengine_terminate_sync() calls the driver's terminate_all callback (like bcm2835_dma_terminate_all()), the driver could acquire the lock and free vd via vchan_dma_desc_free_list():
CPU1vchan_complete()
spin_unlock_irq(&vc->lock);
CPU2dmaengine_terminate_sync()
bcm2835_dma_terminate_all()
spin_lock_irqsave(&vc->lock, flags);
vchan_dma_desc_free_list() // frees vd
spin_unlock_irqrestore(&vc->lock, flags);CPU1
dmaengine_desc_callback_invoke(&cb, vd ? &vd->tx_result : NULL);
Could evaluating vd->tx_result here pass a dangling pointer to the client callback if the descriptor was freed by the concurrent termination?
Quoted 2 lines
drivers/dma/virt-dma.c +0
-0list_for_each_entry_safe (vd, _vd, &head, node) {dmaengine_desc_get_callback (&vd->tx, &cb);
[ ... ]
drivers/dma/virt-dma.h struct virt_dma_desc { · line 24
Quoted 9 lines
drivers/dma/virt-dma.h +0
-1@@ -21,7 +21,6 @@ struct virt_dma_desc {struct virt_dma_chan {struct dma_chan chan; -struct tasklet_struct task;
[Severity: Low]
Does removing the task field here cause build failures in downstream drivers?
Several DMA drivers like bcm2835-dma, hisi_dma, idma64, and tegra210-adma directly access chan->vc.task during driver removal or error paths to kill the tasklet. Without updating those consumers, they will fail to compile.
Quoted 3 lines
drivers/dma/virt-dma.h +0
-0void (*desc_free)(struct virt_dma_desc *);spinlock_t lock;
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Vinod Koul
maintainerRe: [PATCH v2 01/64] dmaengine: add tasklet-backed channel BH helpers
Quoted 72 lines · On 27-07-26, 20:56, [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Low] API breakage leading to widespread build failures in downstream DMA drivers due to the removal of thetaskfield fromstruct virt_dma_chanwithout updating its consumers.Pre-existing issues:
- [Critical] Race condition causing a Use-After-Free of a cyclic descriptor'stx_resultpointer when a channel is concurrently terminated. ----- Patch [1]: [PATCH v2 01/64] dmaengine: add tasklet-backed channel BH helpers --- commit
9141d715bfba5e26881326cc343ca063eaf8458a
Author: Allen Pais <[email protected]>dmaengine: add tasklet-backed channel BH helpers
This patch introduces helpers to initialize, schedule, and kill a channel bottom half. It converts virt-dma to use the new API, replacing its private tasklet while preserving the execution context.
Quoted 4 lines
drivers/dma/virt-dma.c +0
-0[ ... ]
drivers/dma/virt-dma.c static void vchan_complete(struct tasklet_struct *t) · line 101
Quoted 6 lines
drivers/dma/virt-dma.c +1
-1@@ -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 );[Severity: Critical]
This is a pre-existing issue, but does droppingvc->lockjust before referencingvd->tx_resultopen a race window for a use-after-free?If a concurrent
dmaengine_terminate_sync()calls the driver'sterminate_allcallback (likebcm2835_dma_terminate_all()), the driver could acquire the lock and free vd viavchan_dma_desc_free_list():CPU1
vchan_complete()spin_unlock_irq(&vc->lock);CPU2
dmaengine_terminate_sync()bcm2835_dma_terminate_all() spin_lock_irqsave(&vc->lock, flags); vchan_dma_desc_free_list() // frees vd spin_unlock_irqrestore(&vc->lock, flags);CPU1
dmaengine_desc_callback_invoke(&cb, vd ? &vd->tx_result : NULL);Could evaluating
vd->tx_resulthere pass a dangling pointer to the client callback if the descriptor was freed by the concurrent termination?Quoted 2 lines
drivers/dma/virt-dma.c +0
-0list_for_each_entry_safe (vd, _vd, &head, node) {dmaengine_desc_get_callback (&vd->tx, &cb);[ ... ]
drivers/dma/virt-dma.h struct virt_dma_desc { · line 24
Quoted 9 lines
drivers/dma/virt-dma.h +0
-1@@ -21,7 +21,6 @@ struct virt_dma_desc {struct virt_dma_chan {struct dma_chan chan; -struct tasklet_struct task;[Severity: Low]
Does removing the task field here cause build failures in downstream drivers?
Quoted 3 lines
Several DMA drivers like bcm2835-dma,
hisi_dma, idma64, and tegra210-adma directly accesschan->vc.taskduring driver removal or error paths to kill the tasklet. Without updating those consumers, they will fail to compile.
This one seems valid.
We need to replace the callsites with dma_chan_kill_bh() here
Signature · 1 line
~Vinod
Vinod Koul
maintainerRe: [PATCH v2 01/64] dmaengine: add tasklet-backed channel BH helpers
In reply to Allen Paisinclude/linux/dmaengine.h struct dma_chan *devm_dma_request_chan(struct device *dev, const char *name); · line 1545
Quoted 5 lines · Allen Pais
void
dma_release_channel(struct dma_chan *chan);
intdma_get_slave_caps(struct dma_chan *chan, struct dma_slave_caps *caps);include/linux/dmaengine.h +3
-0+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);
Can you please name these as dmaengine_xxx. Yes we have mix of these two, but I would like to see new ones move to dmaengine_xx and then progressively drop dma_ everywhere
Signature · 1 line
~Vinod
Re: [PATCH v2 01/64] dmaengine: add tasklet-backed channel BH helpers
Quoted 11 lines
include/linux/dmaengine.h struct dma_chan *devm_dma_request_chan(struct device *dev, const char *name); · line 1545
Quoted 6 lines
void
dma_release_channel(struct dma_chan *chan);
intdma_get_slave_caps(struct dma_chan *chan, struct dma_slave_caps *caps);include/linux/dmaengine.h +3
-0+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);Can you please name these as
dmaengine_xxx. Yes we have mix of these two, but I would like to see new ones move todmaengine_xxand then progressively dropdma_everywhere
Thanks, Vinod.
I will rename the new helpers to dmaengine_* in v3.
I also see that patch 1 removes the virt-dma tasklet before all of its direct users are converted, leaving the intermediate tree unbuildable and breaking bisectability. I will fold those call-site conversions into patch
1. The patches that contain only those conversions will then become empty, which will shorten the series considerably.
Regards,
Allen
Quoted 2 lines
--
~Vinod
PATCH v2 2/64 dmaengine: back channel BH helpers with WQ_BH
In reply to Allen PaisReplace the tasklet implementation of the channel BH helpers with a dedicated WQ_BH | WQ_PERCPU workqueue. The public dma_chan_*_bh() API and its softirq execution context remain unchanged.
Keep the workqueue operations internal to dmaengine. Drain scheduled work in dma_chan_kill_bh() to preserve the completion semantics of tasklet_kill().
--- drivers/dma/dmaengine.c | 59 ++++++++++++++++++++++++++++++++------- include/linux/dmaengine.h | 10 +++---- 2 files changed, 54 insertions(+), 15 deletions(-)
drivers/dma/dmaengine.c +49 -10
@@ -54,6 +54,7 @@#include <linux/rcupdate.h> #include <linux/slab.h> #include <linux/spinlock.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
@@ -1428,9 +1430,34 @@ static void dmaengine_destroy_unmap_pool(void) } } -static void dma_chan_bh_entry (struct tasklet_struct *tasklet) +static void dmaengine_destroy_bh_wq (void ) { -struct dma_chan *chan =from_tasklet (chan, tasklet, bh_tasklet); +if (!dmaengine_bh_wq) +return ; + +destroy_workqueue (dmaengine_bh_wq); + dmaengine_bh_wq =NULL ; +} + +static bool dmaengine_queue_bh_work (struct work_struct *work) +{ +if (WARN_ON (!dmaengine_bh_wq)) +return false; + +return queue_work (dmaengine_bh_wq, work); +} + +static void dmaengine_flush_bh_work (struct work_struct *work) +{ +if (!work) +return ; + +flush_work (work); +} + +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)
@@ -1446,7 +1473,7 @@ void dma_chan_init_bh(struct dma_chan *chan, dma_chan_bh_work_fn fn)return ; chan->bh_work_fn = fn; -tasklet_setup (&chan->bh_tasklet, dma_chan_bh_entry); +INIT_WORK (&chan->bh_work, dma_chan_bh_entry); chan->bh_work_initialized = true; }EXPORT_SYMBOL_GPL (dma_chan_init_bh);
@@ -1456,8 +1483,7 @@ 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; +return dmaengine_queue_bh_work (&chan->bh_work); }EXPORT_SYMBOL_GPL (dma_chan_schedule_bh);
@@ -1466,7 +1492,7 @@ void dma_chan_kill_bh(struct dma_chan *chan)if (!chan->bh_work_initialized)return ; -tasklet_kill (&chan->bh_tasklet); +dmaengine_flush_bh_work (&chan->bh_work); }EXPORT_SYMBOL_GPL (dma_chan_kill_bh);
@@ -1666,15 +1692,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);
include/linux/dmaengine.h +5 -5
@@ -12,7 +12,7 @@#include <linux/scatterlist.h> #include <linux/bitmap.h> #include <linux/types.h> -#include <linux/interrupt.h> +#include <linux/workqueue.h> #include <asm/page.h> /**
@@ -339,9 +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 +* @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;
@@ -367,7 +367,7 @@ struct dma_chan {void *route_data;void *private; -struct tasklet_struct bh_tasklet; +struct work_struct bh_work;dma_chan_bh_work_fn bh_work_fn;bool bh_work_initialized; };
Signature · 1 line
2.43.0
PATCH v2 3/64 dmaengine: apple-admac: use dma_chan BH callback
In reply to Allen PaisReplace 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.
--- drivers/dma/apple-admac.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-)
drivers/dma/apple-admac.c +6 -10
@@ -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);
Signature · 1 line
2.43.0
PATCH v2 4/64 dmaengine: at_xdmac: move irq bottom half to dma_chan BH
In reply to Allen PaisReplace 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.
--- drivers/dma/at_xdmac.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-)
drivers/dma/at_xdmac.c +6 -7
@@ -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); } }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 04/64] dmaengine: at_xdmac: move irq bottom half to dma_chan BH
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but an initialization race condition exists: request_irq() is called before hardware clocks are enabled and DMA channels are initialized, which can cause a kernel panic or infinite loop.
- [High] This problem wasn't introduced by this patch, but there is a resource leak and Use-After-Free due to a bypassed cleanup loop in at_xdmac_remove().
- [High] This problem wasn't introduced by this patch, but the teardown order is flawed: Hardware clocks and PM runtime are disabled before freeing the IRQ and stopping BH workers.
- [High] This problem wasn't introduced by this patch, but out-of-bounds memory accesses and list corruption occur due to an unchecked list_first_entry() in the BH worker.
- [High] This problem wasn't introduced by this patch, but there is a sleeping in atomic bug: The error handler calls pm_runtime_resume_and_get() while holding an IRQ-disabled spinlock.
--
--- Patch [4]: [PATCH v2 04/64] dmaengine: at_xdmac: move irq bottom half to dma_chan BH --- commit 91d37fd9a6c41425b793e9ed1792794d52b35014
Author: Allen Pais <[email protected]>
dmaengine: at_xdmac: move irq bottom half 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.
Quoted 4 lines
drivers/dma/at_xdmac.c +0
-0
[ ... ]
drivers/dma/at_xdmac.c static void at_xdmac_handle_error(struct at_xdmac_chan *atchan) · line 1765
Quoted 11 lines
drivers/dma/at_xdmac.c +2
-2@@ -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;
[Severity: High]
This is a pre-existing issue, but does this function lack a list empty check? Later in at_xdmac_tasklet(), the code does:
desc = list_first_entry(&atchan->xfers_list, struct at_xdmac_desc,
xfer_node);
if (!desc->active_xfer) { ... }
If at_xdmac_device_terminate_all() runs concurrently and empties xfers_list, resuming the BH worker might resolve an invalid pointer for the empty list, read out-of-bounds for desc->active_xfer, and corrupt the list.
[Severity: High]
This is also a pre-existing issue, but can this path sleep while atomic? In at_xdmac_tasklet(), an IRQ-disabled spinlock is acquired:
spin_lock_irq(&atchan->lock);
...
if (atchan->irq_status & error_mask)
at_xdmac_handle_error(atchan);
Inside at_xdmac_handle_error(), it calls pm_runtime_resume_and_get(), which can sleep since the device isn't marked as pm_runtime_irq_safe(). Could this lead to a scheduling while atomic bug if the device's runtime status is not RPM_ACTIVE?
[ ... ]
drivers/dma/at_xdmac.c static int at_xdmac_probe(struct platform_device *pdev) · line 2320
Quoted 7 lines
drivers/dma/at_xdmac.c +1
-1@@ -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);
[Severity: High]
This isn't a bug introduced by this patch, but an initialization race condition appears to exist here. Is it safe to call request_irq() before the hardware clocks are enabled via clk_prepare_enable() and before dma_async_device_register() initializes the channel count?
If an early or pending interrupt fires immediately after request_irq(), the handler at_xdmac_interrupt() will execute while the controller's clock is gated, which could trigger a bus fault.
Even if clocks were enabled, atxdmac->dma.chancnt is still 0 at this point. The handler would skip the channel check loop, fail to clear the interrupt, and spin infinitely in its do-while(pending) loop.
[ ... ]
drivers/dma/at_xdmac.c static void at_xdmac_remove(struct platform_device *pdev) · line 2463
Quoted 9 lines
drivers/dma/at_xdmac.c +1
-1@@ -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); } }
[Severity: High]
This is a pre-existing issue, but does the teardown order in at_xdmac_remove() and the probe error path risk a bus fault?
In at_xdmac_remove(), clk_disable_unprepare() and pm_runtime_disable() are called before free_irq(). If a pending interrupt fires before free_irq() completes, the interrupt handler will attempt to read from the clock-gated device registers, which could trigger a synchronous external abort.
[Severity: High]
This is another pre-existing issue, but will this cleanup loop ever execute?
Earlier in at_xdmac_remove(), dma_async_device_unregister() is called, which iterates over all registered channels and decrements device->chancnt to 0.
When this loop attempts to use atxdmac->dma.chancnt as its limit, it is completely bypassed, meaning dma_chan_kill_bh() and at_xdmac_free_chan_resources() are never called, potentially causing a resource leak and use-after-free for pending BH workers.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
PATCH v2 5/64 dmaengine: ep93xx: hook callbacks via dma_chan BH
In reply to Allen PaisReplace 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.
--- drivers/dma/ep93xx_dma.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-)
drivers/dma/ep93xx_dma.c +5 -7
@@ -167,7 +167,6 @@ struct ep93xx_dma_chan_cfg {* @dma_cfg: channel number, direction * @irq: interrupt number of the channel * @clk: clock used by this channel -* @tasklet: channel specific tasklet used for callbacks * @lock: lock protecting the fields following * @flags: flags for the channel * @buffer: which buffer to use next (0/1)
@@ -196,7 +195,6 @@ struct ep93xx_dma_chan {struct ep93xx_dma_chan_cfg dma_cfg;int irq;struct clk *clk; -struct tasklet_struct tasklet;/* protects the fields following */ spinlock_t lock;unsigned long flags;
@@ -801,9 +799,9 @@ static void ep93xx_dma_advance_work(struct ep93xx_dma_chan *edmac)spin_unlock_irqrestore (&edmac->lock, flags); } -static void ep93xx_dma_tasklet (struct tasklet_struct *t) +static void ep93xx_dma_tasklet (struct dma_chan *chan) { -struct ep93xx_dma_chan *edmac =from_tasklet (edmac, t, tasklet); +struct ep93xx_dma_chan *edmac =to_ep93xx_dma_chan (chan);struct ep93xx_dma_desc *desc, *d;struct dmaengine_desc_callback cb;LIST_HEAD (list);
@@ -858,12 +856,12 @@ static irqreturn_t ep93xx_dma_interrupt(int irq, void *dev_id)switch (edmac->edma->hw_interrupt (edmac)) {case INTERRUPT_DONE : desc->complete = true; -tasklet_schedule (&edmac->tasklet); +dma_chan_schedule_bh (&edmac->chan);break ;case INTERRUPT_NEXT_BUFFER :if (test_bit (EP93XX_DMA_IS_CYCLIC , &edmac->flags)) -tasklet_schedule (&edmac->tasklet); +dma_chan_schedule_bh (&edmac->chan);break ;default :
@@ -1420,7 +1418,7 @@ static struct ep93xx_dma_engine *ep93xx_dma_of_probe(struct platform_device *pdeINIT_LIST_HEAD (&edmac->active);INIT_LIST_HEAD (&edmac->queue);INIT_LIST_HEAD (&edmac->free_list); -tasklet_setup (&edmac->tasklet, ep93xx_dma_tasklet); +dma_chan_init_bh (&edmac->chan, ep93xx_dma_tasklet);list_add_tail (&edmac->chan.device_node, &dma_dev->channels);
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 05/64] dmaengine: ep93xx: hook callbacks via dma_chan BH
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing dma_chan_kill_bh() in ep93xx_dma_synchronize() and ep93xx_dma_free_chan_resources() violates the dmaengine API and leads to premature callback execution for new clients.
- [High] The ep93xx_dma driver is vulnerable to a Use-After-Free and kernel panic if unbound via sysfs, due to the lack of a .remove callback and missing .suppress_bind_attrs = true. --
--- Patch [5]: [PATCH v2 05/64] dmaengine: ep93xx: hook callbacks via dma_chan BH --- commit e03616bfd332cb745207d3436713d688064c9cb5
Author: Allen Pais <[email protected]>
dmaengine: ep93xx: hook callbacks via 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.
Quoted 4 lines
drivers/dma/ep93xx_dma.c +0
-0
[ ... ]
drivers/dma/ep93xx_dma.c static struct ep93xx_dma_engine *ep93xx_dma_of_probe(struct platform_device *pde · line 1424
Quoted 9 lines
drivers/dma/ep93xx_dma.c +1
-1@@ -1420,7 +1418,7 @@ static struct ep93xx_dma_engine *ep93xx_dma_of_probe(struct platform_device *pdeINIT_LIST_HEAD (&edmac->active);INIT_LIST_HEAD (&edmac->queue);INIT_LIST_HEAD (&edmac->free_list); -tasklet_setup (&edmac->tasklet, ep93xx_dma_tasklet); +dma_chan_init_bh (&edmac->chan, ep93xx_dma_tasklet);list_add_tail (&edmac->chan.device_node, &dma_dev->channels);
[Severity: High]
This is a pre-existing issue, but I noticed ep93xx_dma_synchronize() and ep93xx_dma_free_chan_resources() do not cancel the pending BH work.
If a client calls dma_terminate_all(), the active list is emptied, but a scheduled BH is not cancelled because the driver does not call dma_chan_kill_bh() (previously missing tasklet_kill()).
When the channel is reallocated, the pending BH can run, see the new client's descriptor, and unconditionally invoke its callback before completion:
drivers/dma/ep93xx_dma.c:ep93xx_dma_tasklet()
...
desc = ep93xx_dma_get_active(edmac);
if (desc) {
if (desc->complete) {
/* mark descriptor complete for non cyclic case only */
if (!test_bit(EP93XX_DMA_IS_CYCLIC, &edmac->flags))
dma_cookie_complete(&desc->txd);
list_splice_init(&edmac->active, &list);
}
dmaengine_desc_get_callback(&desc->txd, &cb);
}
...Does this need to call dma_chan_kill_bh() during synchronization or channel release to prevent state corruption for new DMA clients?
[Severity: High]
This isn't a bug introduced by this patch, but the ep93xx_dma driver appears vulnerable to a Use-After-Free and kernel panic if unbound via sysfs.
Because the driver lacks a .remove callback and doesn't set .suppress_bind_attrs = true in ep93xx_dma_driver:
drivers/dma/ep93xx_dma.c:
static struct platform_driver ep93xx_dma_driver = {
.driver = {
.name = "ep93xx-dma",
.of_match_table = ep93xx_dma_of_ids,
},
.probe = ep93xx_dma_probe,
};If a privileged user unbinds the driver via sysfs, it never calls dma_async_device_unregister() or frees IRQs. The device core's devres mechanism will free the edma and edmac structures, but any subsequent IRQ firing or scheduled BH execution will dereference the freed memory.
Should this driver include a .remove callback or set .suppress_bind_attrs = true to prevent this?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
PATCH v2 6/64 dmaengine: fsldma: migrate tasklet to dma_chan BH
In reply to Allen PaisReplace 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.
--- drivers/dma/fsldma.c | 10 +++++----- drivers/dma/fsldma.h | 1 - 2 files changed, 5 insertions(+), 6 deletions(-)
drivers/dma/fsldma.c +5 -5
@@ -968,18 +968,18 @@ static irqreturn_t fsldma_chan_irq(int irq, void *data)chan_err (chan,"irq: unhandled sr 0x%08x\n" , stat);/* -* Schedule the tasklet to handle all cleanup of the current +* Queue the BH worker to handle all cleanup of the current * transaction. It will start a new transaction if there is * one pending. */ -tasklet_schedule (&chan->tasklet); +dma_chan_schedule_bh (&chan->common);chan_dbg (chan,"irq: Exit\n" );return IRQ_HANDLED ; } -static void dma_do_tasklet (struct tasklet_struct *t) +static void dma_do_tasklet (struct dma_chan *c) { -struct fsldma_chan *chan =from_tasklet (chan, t, tasklet); +struct fsldma_chan *chan =to_fsl_chan (c);chan_dbg (chan,"tasklet entry\n" );
@@ -1152,7 +1152,7 @@ static int fsl_dma_chan_probe(struct fsldma_device *fdev, } fdev->chan[chan->id] = chan; -tasklet_setup (&chan->tasklet, dma_do_tasklet); +dma_chan_init_bh (&chan->common, dma_do_tasklet);snprintf (chan->name,sizeof (chan->name),"chan%d" , chan->id);/* Initialize the channel */
drivers/dma/fsldma.h +0 -1
@@ -173,7 +173,6 @@ struct fsldma_chan {struct device *dev;/* Channel device */ int irq;/* Channel IRQ */ int id;/* Raw id of this channel */ -struct tasklet_struct tasklet;u32 feature;bool idle;/* DMA controller is idle */ #ifdef CONFIG_PM
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 06/64] dmaengine: fsldma: migrate tasklet to dma_chan BH
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing dma_chan_kill_bh() during channel removal and resource freeing leaves pending bottom-half work queued, leading to use-after-free and NULL pointer dereference crashes.
- [High] Lock inversion deadlock when invoking client callbacks with the channel descriptor lock held.
--
--- Patch [6]: [PATCH v2 06/64] dmaengine: fsldma: migrate tasklet to dma_chan BH --- commit eba223ea472b760cffa1e53fb0bb48d7fa6057d5
Author: Allen Pais <[email protected]>
dmaengine: fsldma: migrate 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.
Quoted 5 lines
drivers/dma/fsldma.c +0
-0@@ -968,18 +968,18 @@ static irqreturn_t fsldma_chan_irq(int irq, void *data)
[ ... ]
drivers/dma/fsldma.c static irqreturn_t fsldma_chan_irq(int irq, void *data) · line 984
Quoted 7 lines
drivers/dma/fsldma.c +2
-2-static void dma_do_tasklet (struct tasklet_struct *t) +static void dma_do_tasklet (struct dma_chan *c) { -struct fsldma_chan *chan =from_tasklet (chan, t, tasklet); +struct fsldma_chan *chan =to_fsl_chan (c);chan_dbg (chan,"tasklet entry\n" );
[Severity: High]
This is a pre-existing issue, but does this cleanup path have a lock inversion deadlock?
dma_do_tasklet() acquires chan->desc_lock before calling fsldma_cleanup_descriptors(). While holding this lock, it invokes fsldma_run_tx_complete_actions(), which executes the client callback via dmaengine_desc_get_callback_invoke() and processes dependencies via dma_run_dependencies().
If the client callback submits a new DMA transaction, or if a dependent transaction is submitted for the same channel, it calls fsl_dma_tx_submit():
drivers/dma/fsldma.c:fsl_dma_tx_submit() {
...
spin_lock_bh(&chan->desc_lock);
...
}Since the non-recursive lock is already held on the same CPU, won't this lead to a deadlock in softirq context?
drivers/dma/fsldma.c static int fsl_dma_chan_probe(struct fsldma_device *fdev, · line 1155
Quoted 6 lines
drivers/dma/fsldma.c +1
-1@@ -1152,7 +1152,7 @@ static int fsl_dma_chan_probe(struct fsldma_device *fdev, } fdev->chan[chan->id] = chan; -tasklet_setup (&chan->tasklet, dma_do_tasklet); +dma_chan_init_bh (&chan->common, dma_do_tasklet);
[Severity: High]
This is a pre-existing issue, but is it possible for pending bottom-half work to run after the channel is removed?
If an IRQ fires and queues the BH right before unbinding, fsl_dma_chan_remove() frees the channel structure and fsl_dma_free_chan_resources() destroys the descriptor pool without calling dma_chan_kill_bh() (or tasklet_kill() previously).
When the pending BH work later executes dma_do_tasklet(), could it dereference the freed channel or pass a NULL desc_pool to dma_pool_free()?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
PATCH v2 7/64 dmaengine: fsl_raid: run completions via dma_chan BH
In reply to Allen PaisReplace 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.
--- drivers/dma/fsl_raid.c | 11 ++++++----- drivers/dma/fsl_raid.h | 1 - 2 files changed, 6 insertions(+), 6 deletions(-)
drivers/dma/fsl_raid.c +6 -5
@@ -155,9 +155,10 @@ static void fsl_re_cleanup_descs(struct fsl_re_chan *re_chan)fsl_re_issue_pending (&re_chan->chan); } -static void fsl_re_dequeue (struct tasklet_struct *t) +static void fsl_re_dequeue (struct dma_chan *chan) { -struct fsl_re_chan *re_chan =from_tasklet (re_chan, t, irqtask); + struct fsl_re_chan *re_chan = container_of(chan, struct fsl_re_chan, + chan);struct fsl_re_desc *desc, *_desc;struct fsl_re_hw_desc *hwdesc;unsigned long flags;
@@ -224,7 +225,7 @@ static irqreturn_t fsl_re_isr(int irq, void *data)/* Clear interrupt */ out_be32 (&re_chan->jrregs->jr_interrupt_status,FSL_RE_CLR_INTR ); -tasklet_schedule (&re_chan->irqtask); +dma_chan_schedule_bh (&re_chan->chan);return IRQ_HANDLED ; }
@@ -670,7 +671,7 @@ static int fsl_re_chan_probe(struct platform_device *ofdev,snprintf (chan->name,sizeof (chan->name),"re_jr%02d" , q); chandev = &chan_ofdev->dev; -tasklet_setup (&chan->irqtask, fsl_re_dequeue); +dma_chan_init_bh (&chan->chan, fsl_re_dequeue); ret =request_irq (chan->irq, fsl_re_isr,0 , chan->name, chandev);if (ret) {
@@ -846,7 +847,7 @@ static int fsl_re_probe(struct platform_device *ofdev)static void fsl_re_remove_chan (struct fsl_re_chan *chan) { -tasklet_kill (&chan->irqtask); +dma_chan_kill_bh (&chan->chan);dma_pool_free (chan->re_dev->hw_desc_pool, chan->inb_ring_virt_addr, chan->inb_phys_addr);
drivers/dma/fsl_raid.h +0 -1
@@ -275,7 +275,6 @@ struct fsl_re_chan {struct dma_chan chan;struct fsl_re_chan_cfg *jrregs;int irq; -struct tasklet_struct irqtask;u32 alloc_count;/* hw descriptor ring for inbound queue*/
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 07/64] dmaengine: fsl_raid: run completions via dma_chan BH
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Pre-existing issues:
- [High] The driver requests hardware interrupts via request_irq() without devres, but completely fails to call free_irq() in both the probe error paths and device removal path.
- [High] The interrupt is requested before the channel's data structures and driver data are fully initialized, leading to a potential NULL pointer dereference upon early interrupts.
- [High] The driver ignores the return value of fsl_re_chan_probe() during initialization, leading to NULL pointer dereferences in the cleanup path when iterating over failed channels.
- [High] Channel memory is freed before the device is unregistered from the DMA engine, allowing concurrent clients to use freed memory.
--
--- Patch [7]: [PATCH v2 07/64] dmaengine: fsl_raid: run completions via dma_chan BH --- commit ab56ab807ba67c4bf376a52b60efd1139b5215d9
Author: Allen Pais <[email protected]>
dmaengine: fsl_raid: run completions via 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.
Quoted 3 lines
drivers/dma/fsl_raid.c +0
-0
[ ... ]
drivers/dma/fsl_raid.c static int fsl_re_chan_probe(struct platform_device *ofdev, · line 676
Quoted 8 lines
drivers/dma/fsl_raid.c +1
-1@@ -670,7 +671,7 @@ static int fsl_re_chan_probe(struct platform_device *ofdev,snprintf (chan->name,sizeof (chan->name),"re_jr%02d" , q); chandev = &chan_ofdev->dev; -tasklet_setup (&chan->irqtask, fsl_re_dequeue); +dma_chan_init_bh (&chan->chan, fsl_re_dequeue); ret =request_irq (chan->irq, fsl_re_isr,0 , chan->name, chandev);
[Severity: High]
This is a pre-existing issue, but is it safe to request the IRQ before the channel's driver data and locks are initialized?
If an interrupt fires immediately after request_irq() succeeds, fsl_re_isr() will be executed:
fsl_re_isr()
re_chan = dev_get_drvdata((struct device *)data);
irqstate = in_be32(&re_chan->jrregs->jr_interrupt_status);Since dev_set_drvdata() hasn't been called yet, dev_get_drvdata() returns NULL, which is immediately dereferenced.
[Severity: High]
This is a pre-existing issue, but does this function leak the requested IRQ on failure?
The driver requests the hardware interrupt without devres. If any subsequent initialization fails, the function jumps to the err_free labels without calling free_irq().
Quoted 1 line
drivers/dma/fsl_raid.c +0
-0if (ret) {
[ ... ]
drivers/dma/fsl_raid.c static int fsl_re_probe(struct platform_device *ofdev) · line 850
Quoted 6 lines
drivers/dma/fsl_raid.c +1
-1@@ -846,7 +847,7 @@ static int fsl_re_probe(struct platform_device *ofdev)static void fsl_re_remove_chan (struct fsl_re_chan *chan) { -tasklet_kill (&chan->irqtask); +dma_chan_kill_bh (&chan->chan);
[Severity: High]
This is a pre-existing issue, but can this lead to a NULL pointer dereference if the probe previously failed?
In fsl_re_probe(), the driver ignores the return value of fsl_re_chan_probe():
fsl_re_probe()
rc = of_device_is_compatible(child, "fsl,raideng-v1.0-job-ring");
if (rc) {
fsl_re_chan_probe(ofdev, child, ridx++, off);
re_priv->total_chans++;
}If a channel fails to probe, its pointer in the re_jrs array remains NULL but total_chans is still incremented. Later, when cleaning up, could passing that NULL pointer into fsl_re_remove_chan() and calling dma_chan_kill_bh() cause a kernel crash?
[Severity: High]
This is a pre-existing issue, but shouldn't there be a free_irq() call here?
Since the IRQ was requested without devres during probe, omitting free_irq() in the teardown path leaves the IRQ active after the driver memory structures are freed. If an interrupt occurs later, could it lead to a use-after-free?
[Severity: High]
This is a pre-existing issue, but does the driver free channel memory while the DMA device is still registered?
Looking at fsl_re_remove(), it iterates over all channels and calls fsl_re_remove_chan() before unregistering the DMA device:
fsl_re_remove()
for (i = 0; i < re_priv->total_chans; i++)
fsl_re_remove_chan(re_priv->re_jrs[i]);
dma_async_device_unregister(&re_priv->dma_dev);Could concurrent clients still submit requests while this is happening, leading fsl_re_issue_pending() to copy data into a freed ring buffer?
Quoted 3 lines
drivers/dma/fsl_raid.c +0
-0dma_pool_free (chan->re_dev->hw_desc_pool, chan->inb_ring_virt_addr, chan->inb_phys_addr);
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
PATCH v2 8/64 dmaengine: imx-dma: flip per-chan tasklet to dma_chan BH
In reply to Allen PaisReplace 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.
--- drivers/dma/imx-dma.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-)
drivers/dma/imx-dma.c +13 -13
@@ -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); } }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 08/64] dmaengine: imx-dma: flip per-chan tasklet to dma_chan BH
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
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 <[email protected]>
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.
Quoted 4 lines
drivers/dma/imx-dma.c +0
-0
[ ... ]
drivers/dma/imx-dma.c static int imxdma_xfer_desc(struct imxdma_desc *d) · line 600
Quoted 13 lines
drivers/dma/imx-dma.c +3
-2@@ -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?
[ ... ]
drivers/dma/imx-dma.c static int __init imxdma_probe(struct platform_device *pdev) · line 1148
Quoted 9 lines
drivers/dma/imx-dma.c +1
-1@@ -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.
[ ... ]
drivers/dma/imx-dma.c static void imxdma_free_irq(struct platform_device *pdev, struct imxdma_engine * · line 1216
Quoted 8 lines
drivers/dma/imx-dma.c +1
-1@@ -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?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8
PATCH v2 9/64 dmaengine: ioat: convert cleanup tasklet to dma_chan BH
In reply to Allen PaisReplace 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.
--- drivers/dma/ioat/dma.c | 14 +++++++------- drivers/dma/ioat/dma.h | 3 +-- drivers/dma/ioat/init.c | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-)
drivers/dma/ioat/dma.c +7 -7
@@ -110,7 +110,7 @@ irqreturn_t ioat_dma_do_interrupt(int irq, void *data)for_each_set_bit (bit, &attnstatus,BITS_PER_LONG ) { ioat_chan =ioat_chan_by_index (instance, bit);if (test_bit (IOAT_RUN , &ioat_chan->state)) -tasklet_schedule (&ioat_chan->cleanup_task); +dma_chan_schedule_bh (&ioat_chan->dma_chan); }writeb (intrctrl, instance->reg_base +IOAT_INTRCTRL_OFFSET );
@@ -127,7 +127,7 @@ irqreturn_t ioat_dma_do_interrupt_msix(int irq, void *data)struct ioatdma_chan *ioat_chan = data;if (test_bit (IOAT_RUN , &ioat_chan->state)) -tasklet_schedule (&ioat_chan->cleanup_task); +dma_chan_schedule_bh (&ioat_chan->dma_chan);return IRQ_HANDLED ; }
@@ -161,11 +161,11 @@ void ioat_stop(struct ioatdma_chan *ioat_chan)/* flush inflight timers */ timer_delete_sync (&ioat_chan->timer); -/* flush inflight tasklet runs */ -tasklet_kill (&ioat_chan->cleanup_task); +/* flush inflight BH runs */ +dma_chan_kill_bh (&ioat_chan->dma_chan);/* final cleanup now that everything is quiesced and can't re-arm */ -ioat_cleanup_event (&ioat_chan->cleanup_task); +ioat_cleanup_event (&ioat_chan->dma_chan); }static void __ioat_issue_pending (struct ioatdma_chan *ioat_chan)
@@ -690,9 +690,9 @@ static void ioat_cleanup(struct ioatdma_chan *ioat_chan)spin_unlock_bh (&ioat_chan->cleanup_lock); } -void ioat_cleanup_event (struct tasklet_struct *t) +void ioat_cleanup_event (struct dma_chan *c) { -struct ioatdma_chan *ioat_chan =from_tasklet (ioat_chan, t, cleanup_task); +struct ioatdma_chan *ioat_chan =to_ioat_chan (c);ioat_cleanup (ioat_chan);if (!test_bit (IOAT_RUN , &ioat_chan->state))
drivers/dma/ioat/dma.h +1 -2
@@ -111,7 +111,6 @@ struct ioatdma_chan {struct ioatdma_device *ioat_dma;dma_addr_t completion_dma;u64 *completion; -struct tasklet_struct cleanup_task;struct kobject kobj;/* ioat v2 / v3 channel attributes
@@ -385,7 +384,7 @@ int ioat_reset_hw(struct ioatdma_chan *ioat_chan);enum dma_status ioat_tx_status (struct dma_chan *c,dma_cookie_t cookie,struct dma_tx_state *txstate); -void ioat_cleanup_event (struct tasklet_struct *t); +void ioat_cleanup_event (struct dma_chan *c);void ioat_timer_event (struct timer_list *t);int ioat_check_space_lock (struct ioatdma_chan *ioat_chan,int num_descs);void ioat_issue_pending (struct dma_chan *chan);
drivers/dma/ioat/init.c +1 -1
@@ -765,7 +765,7 @@ ioat_init_channel(struct ioatdma_device *ioat_dma,list_add_tail (&ioat_chan->dma_chan.device_node, &dma->channels); ioat_dma->idx[idx] = ioat_chan;timer_setup (&ioat_chan->timer, ioat_timer_event,0 ); -tasklet_setup (&ioat_chan->cleanup_task, ioat_cleanup_event); +dma_chan_init_bh (&ioat_chan->dma_chan, ioat_cleanup_event); }#define IOAT_NUM_SRC_TEST 6/* must be <= 8 */
Signature · 1 line
2.43.0
Re: [PATCH v2 09/64] dmaengine: ioat: convert cleanup tasklet to dma_chan BH
Quoted 5 lines · Allen Pais
Replace the per-channel tasklet with the shared
dma_chanBH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.Signed-off-by Allen Pais <[email protected]>
Quoted 89 lines
--- drivers/dma/ioat/dma.c | 14 +++++++------- drivers/dma/ioat/dma.h | 3 +-- drivers/dma/ioat/init.c | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-)drivers/dma/ioat/dma.c +7
-7@@ -110,7 +110,7 @@ irqreturn_t ioat_dma_do_interrupt(int irq, void *data)for_each_set_bit (bit, &attnstatus,BITS_PER_LONG ) { ioat_chan =ioat_chan_by_index (instance, bit);if (test_bit (IOAT_RUN , &ioat_chan->state)) -tasklet_schedule (&ioat_chan->cleanup_task); +dma_chan_schedule_bh (&ioat_chan->dma_chan); }writeb (intrctrl, instance->reg_base +IOAT_INTRCTRL_OFFSET );@@ -127,7 +127,7 @@ irqreturn_t ioat_dma_do_interrupt_msix(int irq, void *data)struct ioatdma_chan *ioat_chan = data;if (test_bit (IOAT_RUN , &ioat_chan->state)) -tasklet_schedule (&ioat_chan->cleanup_task); +dma_chan_schedule_bh (&ioat_chan->dma_chan);return IRQ_HANDLED ; }@@ -161,11 +161,11 @@ void ioat_stop(struct ioatdma_chan *ioat_chan)/* flush inflight timers */ timer_delete_sync (&ioat_chan->timer); -/* flush inflight tasklet runs */ -tasklet_kill (&ioat_chan->cleanup_task); +/* flush inflight BH runs */ +dma_chan_kill_bh (&ioat_chan->dma_chan);/* final cleanup now that everything is quiesced and can't re-arm */ -ioat_cleanup_event (&ioat_chan->cleanup_task); +ioat_cleanup_event (&ioat_chan->dma_chan); }static void __ioat_issue_pending (struct ioatdma_chan *ioat_chan)@@ -690,9 +690,9 @@ static void ioat_cleanup(struct ioatdma_chan *ioat_chan)spin_unlock_bh (&ioat_chan->cleanup_lock); } -void ioat_cleanup_event (struct tasklet_struct *t) +void ioat_cleanup_event (struct dma_chan *c) { -struct ioatdma_chan *ioat_chan =from_tasklet (ioat_chan, t, cleanup_task); +struct ioatdma_chan *ioat_chan =to_ioat_chan (c);ioat_cleanup (ioat_chan);if (!test_bit (IOAT_RUN , &ioat_chan->state))drivers/dma/ioat/dma.h +1
-2@@ -111,7 +111,6 @@ struct ioatdma_chan {struct ioatdma_device *ioat_dma;dma_addr_t completion_dma;u64 *completion; -struct tasklet_struct cleanup_task;struct kobject kobj;/* ioat v2 / v3 channel attributes @@ -385,7 +384,7 @@ int ioat_reset_hw(struct ioatdma_chan *ioat_chan);enum dma_status ioat_tx_status (struct dma_chan *c,dma_cookie_t cookie,struct dma_tx_state *txstate); -void ioat_cleanup_event (struct tasklet_struct *t); +void ioat_cleanup_event (struct dma_chan *c);void ioat_timer_event (struct timer_list *t);int ioat_check_space_lock (struct ioatdma_chan *ioat_chan,int num_descs);void ioat_issue_pending (struct dma_chan *chan);drivers/dma/ioat/init.c +1
-1@@ -765,7 +765,7 @@ ioat_init_channel(struct ioatdma_device *ioat_dma,list_add_tail (&ioat_chan->dma_chan.device_node, &dma->channels); ioat_dma->idx[idx] = ioat_chan;timer_setup (&ioat_chan->timer, ioat_timer_event,0 ); -tasklet_setup (&ioat_chan->cleanup_task, ioat_cleanup_event); +dma_chan_init_bh (&ioat_chan->dma_chan, ioat_cleanup_event); }#define IOAT_NUM_SRC_TEST 6/* must be <= 8 */
[email protected]
AI reviewRe: [PATCH v2 09/64] dmaengine: ioat: convert cleanup tasklet to dma_chan BH
In reply to Allen PaisThank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] The teardown sequence in ioat_stop() cancels the timer using timer_delete_sync(), but it can be subsequently re-armed by the final cleanup sequence, leading to a use-after-free when channel resources are freed.
- [High] ioat_shutdown() does not flush inflight BH runs, which can lead to a use-after-free or crash on module unload.
--
--- Patch [9]: [PATCH v2 09/64] dmaengine: ioat: convert cleanup tasklet to dma_chan BH --- commit 1d64b3893af8e57fe59c7d2f05e40d26e9aec652
Author: Allen Pais <[email protected]>
dmaengine: ioat: convert cleanup 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.
Quoted 4 lines
drivers/dma/ioat/dma.c +0
-0
[ ... ]
drivers/dma/ioat/dma.c void ioat_stop(struct ioatdma_chan *ioat_chan) · line 168
Quoted 12 lines
drivers/dma/ioat/dma.c +3
-3@@ -161,11 +161,11 @@ void ioat_stop(struct ioatdma_chan *ioat_chan)/* flush inflight timers */ timer_delete_sync (&ioat_chan->timer); -/* flush inflight tasklet runs */ -tasklet_kill (&ioat_chan->cleanup_task); +/* flush inflight BH runs */ +dma_chan_kill_bh (&ioat_chan->dma_chan);/* final cleanup now that everything is quiesced and can't re-arm */ -ioat_cleanup_event (&ioat_chan->cleanup_task); +ioat_cleanup_event (&ioat_chan->dma_chan);
[Severity: High]
This is a pre-existing issue, but does this sequence inadvertently re-arm the timer during teardown?
After timer_delete_sync() is called, ioat_cleanup_event() triggers a call chain that can re-arm the timer unconditionally:
ioat_stop()
timer_delete_sync(&ioat_chan->timer);
ioat_cleanup_event()
ioat_cleanup()
ioat_cleanup_preamble()
mod_timer(&ioat_chan->timer, jiffies + COMPLETION_TIMEOUT);If the timer is re-armed here, could the delayed timer callback execute after ioat_free_chan_resources() frees the completion pool memory?
Would upgrading timer_delete_sync() to timer_shutdown_sync() properly prevent the timer from being re-armed?
[Severity: High]
This is also a pre-existing issue, but does ioat_shutdown() in drivers/dma/ioat/init.c have a similar teardown problem with the BH?
In ioat_shutdown(), the driver calls timer_delete_sync() and disables interrupts, but it does not appear to flush inflight BH runs like it does here in ioat_stop().
Since the IOAT_RUN bit is not cleared in ioat_shutdown():
drivers/dma/ioat/init.c:ioat_shutdown() {
...
timer_delete_sync(&ioat_chan->timer);
ioat_reset_hw(ioat_chan);
...
ioat_disable_interrupts(ioat_dma);
}Could an interrupt firing just before ioat_disable_interrupts() successfully schedule the BH via dma_chan_schedule_bh()?
If the module is then unloaded, could the scheduled BH execute after release_ioatdma() has freed the channel structures, resulting in a use-after-free?
Quoted 1 line
drivers/dma/ioat/dma.c +0
-0}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
PATCH v2 10/64 dmaengine: mmp_pdma: replace per-chan tasklet with dma_chan BH
In reply to Allen PaisReplace 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.
--- drivers/dma/mmp_pdma.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-)
drivers/dma/mmp_pdma.c +5 -8
@@ -116,7 +116,6 @@ struct mmp_pdma_chan {* is in cyclic mode */ /* channel's basic info */ -struct tasklet_struct tasklet;u32 dcmd;u32 drcmr;u32 dev_addr;
@@ -374,7 +373,7 @@ static irqreturn_t mmp_pdma_chan_handler(int irq, void *dev_id)if (clear_chan_irq (phy) !=0 )return IRQ_NONE ; -tasklet_schedule (&phy->vchan->tasklet); +dma_chan_schedule_bh (&phy->vchan->chan);return IRQ_HANDLED ; }
@@ -1047,13 +1046,11 @@ static void mmp_pdma_issue_pending(struct dma_chan *dchan) }/* -* dma_do_tasklet -* Do call back -* Start pending list +* dma_do_tasklet - BH worker to process completions and kick pending work */ -static void dma_do_tasklet (struct tasklet_struct *t) +static void dma_do_tasklet (struct dma_chan *c) { -struct mmp_pdma_chan *chan =from_tasklet (chan, t, tasklet); + struct mmp_pdma_chan *chan = container_of(c, struct mmp_pdma_chan, chan);struct mmp_pdma_desc_sw *desc, *_desc;LIST_HEAD (chain_cleanup);unsigned long flags;
@@ -1170,7 +1167,7 @@ static int mmp_pdma_chan_init(struct mmp_pdma_device *pdev, int idx, int irq)spin_lock_init (&chan->desc_lock); chan->dev = pdev->dev; chan->chan.device = &pdev->device; -tasklet_setup (&chan->tasklet, dma_do_tasklet); +dma_chan_init_bh (&chan->chan, dma_do_tasklet);INIT_LIST_HEAD (&chan->chain_pending);INIT_LIST_HEAD (&chan->chain_running);
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 10/64] dmaengine: mmp_pdma: replace per-chan tasklet with dma_chan BH
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] Unconditional dereference of phy->vchan in the interrupt handler races with channel teardown.
- [High] Missing cancellation of scheduled bottom-half work during channel teardown and device removal causes UAF and NULL pointer dereferences.
- [High] Terminating cyclic transfers frees descriptors but leaves a dangling pointer in chan->cyclic_first, causing UAF in the bottom-half handler. --
--- Patch [10]: [PATCH v2 10/64] dmaengine: mmp_pdma: replace per-chan tasklet with dma_chan BH --- commit 7ee6ebc1ebbe207f60ed8c3f6afa81414e0802c3
Author: Allen Pais <[email protected]>
dmaengine: mmp_pdma: replace per-chan tasklet with dma_chan BH
This commit replaces the per-channel tasklet with the shared dma_chan BH helper. The handler continues to run in softirq context while delegating scheduling to the dmaengine's mechanism.
Quoted 4 lines
drivers/dma/mmp_pdma.c +0
-0
[ ... ]
drivers/dma/mmp_pdma.c static irqreturn_t mmp_pdma_chan_handler(int irq, void *dev_id) · line 376
Quoted 6 lines
drivers/dma/mmp_pdma.c +1
-1@@ -374,7 +373,7 @@ static irqreturn_t mmp_pdma_chan_handler(int irq, void *dev_id)if (clear_chan_irq (phy) !=0 )return IRQ_NONE ; -tasklet_schedule (&phy->vchan->tasklet); +dma_chan_schedule_bh (&phy->vchan->chan);
[Severity: High]
This is a pre-existing issue, but does this race with channel teardown?
If mmp_pdma_free_phy() sets phy->vchan to NULL while the shared physical interrupt remains active:
drivers/dma/mmp_pdma.c:mmp_pdma_free_phy() {
...
pchan->phy->vchan = NULL;
...
}Could a pending interrupt be serviced immediately afterwards?
If clear_chan_irq() successfully clears the hardware interrupt and returns zero, it appears this code will unconditionally dereference the NULL pointer in phy->vchan when scheduling the bottom half.
Quoted 2 lines
drivers/dma/mmp_pdma.c +0
-0return IRQ_HANDLED ; }
[ ... ]
Quoted 4 lines
drivers/dma/mmp_pdma.c +0
-0@@ -1047,13 +1046,11 @@ static void mmp_pdma_issue_pending(struct dma_chan *dchan) }/*
drivers/dma/mmp_pdma.c static void mmp_pdma_issue_pending(struct dma_chan *dchan) · line 1053
Quoted 10 lines
drivers/dma/mmp_pdma.c +3
-5-* dma_do_tasklet -* Do call back -* Start pending list +* dma_do_tasklet - BH worker to process completions and kick pending work */ -static void dma_do_tasklet (struct tasklet_struct *t) +static void dma_do_tasklet (struct dma_chan *c) { -struct mmp_pdma_chan *chan =from_tasklet (chan, t, tasklet); + struct mmp_pdma_chan *chan = container_of(c, struct mmp_pdma_chan, chan);
[Severity: High]
This is a pre-existing issue, but is the bottom half properly synchronized against device removal and channel teardown?
Looking at mmp_pdma_free_chan_resources(), the descriptor pool is destroyed and chan->desc_pool is set to NULL:
drivers/dma/mmp_pdma.c:mmp_pdma_free_chan_resources() {
...
dma_pool_destroy(chan->desc_pool);
chan->desc_pool = NULL;
...
}Since there isn't a device_synchronize() or explicit cancellation of the pending bottom-half work during teardown or mmp_pdma_remove(), could this background work execute asynchronously?
If it does, it looks like it would invoke dma_pool_free() on a NULL or destroyed pool further down in this function, or access a chan structure that was already freed by devres.
[Severity: High]
This isn't a bug introduced by this patch, but can this function access a freed descriptor during cyclic transfers?
In mmp_pdma_terminate_all(), the driver correctly frees running descriptors back to the DMA pool by calling mmp_pdma_free_desc_list(), but it doesn't appear to clear chan->cyclic_first:
drivers/dma/mmp_pdma.c:mmp_pdma_terminate_all() {
...
mmp_pdma_free_desc_list(chan, &chan->chain_running);
...
}If a delayed or spurious bottom-half execution triggers this function, could it evaluate chan->cyclic_first as non-null and subsequently use-after-free the memory to invoke the callback?
Quoted 3 lines
drivers/dma/mmp_pdma.c +0
-0struct mmp_pdma_desc_sw *desc, *_desc;LIST_HEAD (chain_cleanup);unsigned long flags;
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=10
PATCH v2 11/64 dmaengine: mmp_tdma: hook completions to dma_chan BH
In reply to Allen PaisReplace 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.
--- drivers/dma/mmp_tdma.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-)
drivers/dma/mmp_tdma.c +4 -5
@@ -102,7 +102,6 @@ struct mmp_tdma_chan {struct device *dev;struct dma_chan chan;struct dma_async_tx_descriptor desc; -struct tasklet_struct tasklet;struct mmp_tdma_desc *desc_arr;dma_addr_t desc_arr_phys;
@@ -320,7 +319,7 @@ static irqreturn_t mmp_tdma_chan_handler(int irq, void *dev_id)struct mmp_tdma_chan *tdmac = dev_id;if (mmp_tdma_clear_chan_irq (tdmac) ==0 ) { -tasklet_schedule (&tdmac->tasklet); +dma_chan_schedule_bh (&tdmac->chan);return IRQ_HANDLED ; }else return IRQ_NONE ;
@@ -346,9 +345,9 @@ static irqreturn_t mmp_tdma_int_handler(int irq, void *dev_id)return IRQ_NONE ; } -static void dma_do_tasklet (struct tasklet_struct *t) +static void dma_do_tasklet (struct dma_chan *c) { -struct mmp_tdma_chan *tdmac =from_tasklet (tdmac, t, tasklet); + struct mmp_tdma_chan *tdmac = container_of(c, struct mmp_tdma_chan, chan);dmaengine_desc_get_callback_invoke (&tdmac->desc,NULL ); }
@@ -583,7 +582,7 @@ static int mmp_tdma_chan_init(struct mmp_tdma_device *tdev, tdmac->pool = pool; tdmac->status =DMA_COMPLETE ; tdev->tdmac[tdmac->idx] = tdmac; -tasklet_setup (&tdmac->tasklet, dma_do_tasklet); +dma_chan_init_bh (&tdmac->chan, dma_do_tasklet);/* add the channel to tdma_chan list */ list_add_tail(&tdmac->chan.device_node,
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 11/64] dmaengine: mmp_tdma: hook completions to dma_chan BH
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing teardown of bottom-half work during channel release, leading to potential use-after-free.
- [High] Incorrect devres initialization ordering for shared interrupts causes NULL pointer dereferences on probe and use-after-free on unbind.
- [High] SRAM descriptors allocated from the gen_pool are leaked when mmp_tdma_prep_dma_cyclic fails or is called multiple times. --
--- Patch [11]: [PATCH v2 11/64] dmaengine: mmp_tdma: hook completions to dma_chan BH --- commit c186dbcb9d7ae1ce6b3473be898cbd442e74d154
Author: Allen Pais <[email protected]>
dmaengine: mmp_tdma: hook completions to dma_chan BH
This commit replaces the per-channel tasklet with the shared dma_chan BH helper. The handler continues to run in softirq context while dmaengine manages the common scheduling and teardown mechanism.
Quoted 4 lines
drivers/dma/mmp_tdma.c +0
-0
[ ... ]
drivers/dma/mmp_tdma.c static irqreturn_t mmp_tdma_int_handler(int irq, void *dev_id) · line 346
Quoted 12 lines
drivers/dma/mmp_tdma.c +1
-1@@ -320,7 +319,7 @@ static irqreturn_t mmp_tdma_chan_handler(int irq, void *dev_id)struct mmp_tdma_chan *tdmac = dev_id;if (mmp_tdma_clear_chan_irq (tdmac) ==0 ) { -tasklet_schedule (&tdmac->tasklet); +dma_chan_schedule_bh (&tdmac->chan);return IRQ_HANDLED ; }else return IRQ_NONE ;@@ -346,9 +345,9 @@ static irqreturn_t mmp_tdma_int_handler(int irq, void *dev_id)return IRQ_NONE ; }
[Severity: High]
This is a pre-existing issue, but is it possible for this interrupt handler to trigger before the channel structures are fully initialized?
Looking at mmp_tdma_probe(), devm_request_irq() is called with IRQF_SHARED before the channel structures are allocated and assigned:
drivers/dma/mmp_tdma.c:mmp_tdma_probe() {
...
ret = devm_request_irq(&pdev->dev, irq,
mmp_tdma_int_handler, IRQF_SHARED, "tdma", tdev);
...
}If the shared IRQ line fires immediately, mmp_tdma_int_handler() iterates over tdev->tdmac[] and unconditionally passes a potentially uninitialized NULL pointer to mmp_tdma_chan_handler(). Could this lead to a NULL pointer dereference on probe, or a use-after-free on unbind since devres reverses the initialization order?
Quoted 9 lines
drivers/dma/mmp_tdma.c +2
-2-static void dma_do_tasklet (struct tasklet_struct *t) +static void dma_do_tasklet (struct dma_chan *c) { -struct mmp_tdma_chan *tdmac =from_tasklet (tdmac, t, tasklet); + struct mmp_tdma_chan *tdmac = container_of(c, struct mmp_tdma_chan, chan);dmaengine_desc_get_callback_invoke (&tdmac->desc,NULL ); }
drivers/dma/mmp_tdma.c static int mmp_tdma_chan_init(struct mmp_tdma_device *tdev, · line 585
Quoted 5 lines
drivers/dma/mmp_tdma.c +1
-1@@ -583,7 +582,7 @@ static int mmp_tdma_chan_init(struct mmp_tdma_device *tdev, tdmac->pool = pool; tdmac->status =DMA_COMPLETE ; tdev->tdmac[tdmac->idx] = tdmac; -tasklet_setup (&tdmac->tasklet, dma_do_tasklet); +dma_chan_init_bh (&tdmac->chan, dma_do_tasklet);
[Severity: High]
This isn't a bug introduced by this patch, but should the bottom-half work be explicitly torn down during channel release?
Currently, mmp_tdma_free_chan_resources() frees channel resources without calling dma_chan_kill_bh():
drivers/dma/mmp_tdma.c:mmp_tdma_free_chan_resources() {
...
}Similarly, when the device unbinds, devres frees the channel structure (tdmac) but mmp_tdma_remove() does not explicitly kill the bottom-half work.
Could this leave pending bottom-half tasks to execute after memory is freed by devres, leading to a use-after-free?
drivers/dma/mmp_tdma.c static int mmp_tdma_chan_init(struct mmp_tdma_device *tdev, · line 588
Quoted 2 lines
drivers/dma/mmp_tdma.c +0
-0/* add the channel to tdma_chan list */ list_add_tail(&tdmac->chan.device_node,
[Severity: High]
This is a pre-existing issue, but can mmp_tdma_prep_dma_cyclic() leak SRAM descriptors allocated from the gen_pool?
If mmp_tdma_config_write() fails, the function branches to err_out without freeing the newly allocated descriptor:
drivers/dma/mmp_tdma.c:mmp_tdma_prep_dma_cyclic() {
...
desc = mmp_tdma_alloc_descriptor(tdmac);
if (!desc)
goto err_out;
if (mmp_tdma_config_write(chan, direction, &tdmac->slave_config))
goto err_out;
...
}Additionally, mmp_tdma_alloc_descriptor() unconditionally assigns the newly allocated memory to tdmac->desc_arr:
drivers/dma/mmp_tdma.c:mmp_tdma_alloc_descriptor() {
...
tdmac->desc_arr = gen_pool_dma_alloc(gpool, size, &tdmac->desc_arr_phys);
...
}If mmp_tdma_prep_dma_cyclic() is called multiple times without an intervening mmp_tdma_free_chan_resources(), the previous descriptor is blindly overwritten and permanently leaked. Could this exhaust the limited device SRAM pool and prevent further DMA operations?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=11
PATCH v2 12/64 dmaengine: mv_xor: convert irq tasklet to dma_chan BH
In reply to Allen PaisReplace 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.
--- drivers/dma/mv_xor.c | 12 ++++++------ drivers/dma/mv_xor.h | 2 -- 2 files changed, 6 insertions(+), 8 deletions(-)
drivers/dma/mv_xor.c +6 -6
@@ -327,7 +327,7 @@ static void mv_chan_slot_cleanup(struct mv_xor_chan *mv_chan)* some descriptors are still waiting * to be cleaned */ -tasklet_schedule (&mv_chan->irq_tasklet); +dma_chan_schedule_bh (&mv_chan->dmachan); } } }
@@ -336,9 +336,9 @@ static void mv_chan_slot_cleanup(struct mv_xor_chan *mv_chan) mv_chan->dmachan.completed_cookie = cookie; } -static void mv_xor_tasklet (struct tasklet_struct *t) +static void mv_xor_tasklet (struct dma_chan *c) { -struct mv_xor_chan *chan =from_tasklet (chan, t, irq_tasklet); +struct mv_xor_chan *chan =to_mv_xor_chan (c);spin_lock (&chan->lock);mv_chan_slot_cleanup (chan);
@@ -372,7 +372,7 @@ mv_chan_alloc_slot(struct mv_xor_chan *mv_chan)spin_unlock_bh (&mv_chan->lock);/* try to free some slots if the allocation fails */ -tasklet_schedule (&mv_chan->irq_tasklet); +dma_chan_schedule_bh (&mv_chan->dmachan);return NULL ; }
@@ -737,7 +737,7 @@ static irqreturn_t mv_xor_interrupt_handler(int irq, void *data)if (intr_cause &XOR_INTR_ERRORS )mv_chan_err_interrupt_handler (chan, intr_cause); -tasklet_schedule (&chan->irq_tasklet); +dma_chan_schedule_bh (&chan->dmachan);mv_chan_clear_eoc_cause (chan);
@@ -1107,7 +1107,7 @@ mv_xor_channel_add(struct mv_xor_device *xordev, mv_chan->mmr_base = xordev->xor_base; mv_chan->mmr_high_base = xordev->xor_high_base; -tasklet_setup (&mv_chan->irq_tasklet, mv_xor_tasklet); +dma_chan_init_bh (&mv_chan->dmachan, mv_xor_tasklet);/* clear errors before enabling interrupts */ mv_chan_clear_err_status (mv_chan);
drivers/dma/mv_xor.h +0 -2
@@ -98,7 +98,6 @@ struct mv_xor_device {* @device: parent device * @common: common dmaengine channel object members * @slots_allocated: records the actual size of the descriptor slot pool -* @irq_tasklet: bottom half where mv_xor_slot_cleanup runs * @op_in_desc: new mode of driver, each op is written to descriptor. */ struct mv_xor_chan {
@@ -118,7 +117,6 @@ struct mv_xor_chan {struct dma_device dmadev;struct dma_chan dmachan;int slots_allocated; -struct tasklet_struct irq_tasklet;int op_in_desc;char dummy_src[MV_XOR_MIN_BYTE_COUNT ];char dummy_dst[MV_XOR_MIN_BYTE_COUNT ];
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 12/64] dmaengine: mv_xor: convert irq tasklet to dma_chan BH
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] The driver fails to explicitly cancel pending bottom-half work during module removal and initialization error paths, leading to a Use-After-Free.
- [High] Interrupts are requested and unmasked before the channel spinlock is initialized, creating a race condition that can lead to locking an uninitialized spinlock.
- [High] Recursive deadlock when submitting dependent operations from the completion handler.
--
--- Patch [12]: [PATCH v2 12/64] dmaengine: mv_xor: convert irq tasklet to dma_chan BH --- commit a6fdd06d107411d5248196d2bc3b3e3a0282f10d
Author: Allen Pais <[email protected]>
Subject: dmaengine: mv_xor: convert irq tasklet to dma_chan BH
This patch replaces 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.
Quoted 4 lines
drivers/dma/mv_xor.c +0
-0
[ ... ]
Quoted 12 lines
drivers/dma/mv_xor.c +2
-2@@ -336,9 +336,9 @@ static void mv_chan_slot_cleanup(struct mv_xor_chan *mv_chan) mv_chan->dmachan.completed_cookie = cookie; } -static void mv_xor_tasklet (struct tasklet_struct *t) +static void mv_xor_tasklet (struct dma_chan *c) { -struct mv_xor_chan *chan =from_tasklet (chan, t, irq_tasklet); +struct mv_xor_chan *chan =to_mv_xor_chan (c);spin_lock (&chan->lock);mv_chan_slot_cleanup (chan);
Quoted 2 lines
spin_unlock(&chan->lock);
}
[Severity: High]
This is a pre-existing issue, but can this sequence lead to a recursive deadlock? Looking at mv_xor_tasklet(), it holds the lock while running cleanup:
mv_xor_tasklet()
spin_lock(&chan->lock);
mv_chan_slot_cleanup(chan);If mv_chan_slot_cleanup() processes completed slots and invokes dma_run_dependencies(), the dependent operations might be submitted to the same channel:
mv_chan_slot_cleanup()
mv_desc_run_tx_complete_actions()
dma_run_dependencies()
mv_xor_tx_submit()
spin_lock_bh(&mv_chan->lock);Would this recursive lock attempt cause a system hang, or is there something preventing dependencies from being queued on the same channel?
[ ... ]
drivers/dma/mv_xor.c mv_xor_channel_add(struct mv_xor_device *xordev, · line 1113
Quoted 9 lines
drivers/dma/mv_xor.c +1
-1@@ -1107,7 +1107,7 @@ mv_xor_channel_add(struct mv_xor_device *xordev, mv_chan->mmr_base = xordev->xor_base; mv_chan->mmr_high_base = xordev->xor_high_base; -tasklet_setup (&mv_chan->irq_tasklet, mv_xor_tasklet); +dma_chan_init_bh (&mv_chan->dmachan, mv_xor_tasklet);/* clear errors before enabling interrupts */ mv_chan_clear_err_status (mv_chan);
[Severity: High]
This is a pre-existing issue, but does the initialization error path properly cancel the bottom half work before the channel structure is freed?
If a failure occurs later in mv_xor_channel_add() (like self-tests failing), the code jumps to err_free_irq:
err_free_irq:
free_irq(mv_chan->irq, mv_chan);
err_free_dma:
...Should dma_chan_kill_bh() be called here and in mv_xor_channel_remove() to ensure scheduled work doesn't execute after mv_chan is freed?
[Severity: High]
This is also a pre-existing issue, but is it possible for an interrupt to fire before the channel spinlock is initialized?
In mv_xor_channel_add(), request_irq() is called and interrupts are unmasked before the lock is initialized:
mv_xor_channel_add()
request_irq(..., mv_xor_interrupt_handler, ...);
mv_chan_unmask_interrupts(mv_chan);
...
spin_lock_init(&mv_chan->lock);If a spurious interrupt fires immediately and schedules the BH work, could mv_xor_tasklet() attempt to acquire an uninitialized lock?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=12
PATCH v2 13/64 dmaengine: mxs-dma: use dma_chan BH scheduling
In reply to Allen PaisReplace 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.
--- drivers/dma/mxs-dma.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-)
drivers/dma/mxs-dma.c +5 -6
@@ -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 */
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 13/64] dmaengine: mxs-dma: use dma_chan BH scheduling
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
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 <[email protected]>
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.
Quoted 2 lines
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?
Quoted 4 lines
drivers/dma/mxs-dma.c +0
-0
[ ... ]
drivers/dma/mxs-dma.c static irqreturn_t mxs_dma_int_handler(int irq, void *dev_id) · line 392
Quoted 11 lines
drivers/dma/mxs-dma.c +2
-2@@ -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?
[ ... ]
drivers/dma/mxs-dma.c static int mxs_dma_probe(struct platform_device *pdev) · line 786
Quoted 9 lines
drivers/dma/mxs-dma.c +1
-1@@ -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?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=13
PATCH v2 14/64 dmaengine: nbpfaxi: switch callbacks to dma_chan BH
In reply to Allen PaisReplace 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.
--- drivers/dma/nbpfaxi.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-)
drivers/dma/nbpfaxi.c +6 -7
@@ -174,7 +174,6 @@ struct nbpf_desc_page {/** * struct nbpf_channel - one DMAC channel * @dma_chan: standard dmaengine channel object -* @tasklet: channel specific tasklet used for callbacks * @base: register address base * @nbpf: DMAC * @name: IRQ name
@@ -200,7 +199,6 @@ struct nbpf_desc_page {*/ struct nbpf_channel {struct dma_chan dma_chan; -struct tasklet_struct tasklet;void __iomem *base;struct nbpf_device *nbpf;char name[16 ];
@@ -1125,9 +1123,10 @@ static struct dma_chan *nbpf_of_xlate(struct of_phandle_args *dma_spec,return dchan; } -static void nbpf_chan_tasklet (struct tasklet_struct *t) +static void nbpf_chan_tasklet (struct dma_chan *c) { -struct nbpf_channel *chan =from_tasklet (chan, t, tasklet); + struct nbpf_channel *chan = container_of(c, struct nbpf_channel, + dma_chan);struct nbpf_desc *desc, *tmp;struct dmaengine_desc_callback cb;
@@ -1228,7 +1227,7 @@ static irqreturn_t nbpf_chan_irq(int irq, void *dev)spin_unlock (&chan->lock);if (bh) -tasklet_schedule (&chan->tasklet); +dma_chan_schedule_bh (&chan->dma_chan);return ret; }
@@ -1272,7 +1271,7 @@ static int nbpf_chan_probe(struct nbpf_device *nbpf, int n)snprintf (chan->name,sizeof (chan->name),"nbpf %d" , n); -tasklet_setup (&chan->tasklet, nbpf_chan_tasklet); +dma_chan_init_bh (&chan->dma_chan, nbpf_chan_tasklet); ret =devm_request_irq (dma_dev->dev, chan->irq, nbpf_chan_irq,IRQF_SHARED , chan->name, chan);
@@ -1478,7 +1477,7 @@ static void nbpf_remove(struct platform_device *pdev)devm_free_irq (&pdev->dev, chan->irq, chan); -tasklet_kill (&chan->tasklet); +dma_chan_kill_bh (&chan->dma_chan); }of_dma_controller_free (pdev->dev.of_node);
Signature · 1 line
2.43.0
PATCH v2 15/64 dmaengine: pch_dma: convert tasklet to dma_chan BH
In reply to Allen PaisReplace 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.
--- drivers/dma/pch_dma.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-)
drivers/dma/pch_dma.c +7 -7
@@ -91,7 +91,6 @@ struct pch_dma_chan {struct dma_chan chan;void __iomem *membase;enum dma_transfer_direction dir; -struct tasklet_struct tasklet;unsigned long err_status;spinlock_t lock;
@@ -665,9 +664,10 @@ static int pd_device_terminate_all(struct dma_chan *chan)return 0 ; } -static void pdc_tasklet (struct tasklet_struct *t) +static void pdc_tasklet (struct dma_chan *c) { -struct pch_dma_chan *pd_chan =from_tasklet (pd_chan, t, tasklet); + struct pch_dma_chan *pd_chan = container_of(c, struct pch_dma_chan, + chan);unsigned long flags;if (!pdc_is_idle (pd_chan)) {
@@ -707,7 +707,7 @@ static irqreturn_t pd_irq(int irq, void *devid)if (sts0 &DMA_STATUS0_ERR (i))set_bit (0 , &pd_chan->err_status); -tasklet_schedule (&pd_chan->tasklet); +dma_chan_schedule_bh (&pd_chan->chan); ret0 =IRQ_HANDLED ; } }else {
@@ -715,7 +715,7 @@ static irqreturn_t pd_irq(int irq, void *devid)if (sts2 &DMA_STATUS2_ERR (i))set_bit (0 , &pd_chan->err_status); -tasklet_schedule (&pd_chan->tasklet); +dma_chan_schedule_bh (&pd_chan->chan); ret2 =IRQ_HANDLED ; } }
@@ -877,7 +877,7 @@ static int pch_dma_probe(struct pci_dev *pdev,INIT_LIST_HEAD (&pd_chan->queue);INIT_LIST_HEAD (&pd_chan->free_list); -tasklet_setup (&pd_chan->tasklet, pdc_tasklet); +dma_chan_init_bh (&pd_chan->chan, pdc_tasklet);list_add_tail (&pd_chan->chan.device_node, &pd->dma.channels); }
@@ -930,7 +930,7 @@ static void pch_dma_remove(struct pci_dev *pdev) device_node) { pd_chan =to_pd_chan (chan); -tasklet_kill (&pd_chan->tasklet); +dma_chan_kill_bh (&pd_chan->chan); }dma_pool_destroy (pd->pool);
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 15/64] dmaengine: pch_dma: convert tasklet to dma_chan BH
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing cleanup of scheduled BH work on the probe error path can lead to a use-after-free.
- [High] Early registration of a shared IRQ before channel count setup leads to an unhandled IRQ storm.
- [High] Client completion callbacks are invoked with the channel spinlock held, leading to a potential self-deadlock.
- [High] Process-context functions acquire the channel spinlock without disabling softirqs, risking a deadlock if preempted by the BH work.
- [High] Checking hardware idle state outside the lock causes a TOCTOU race leading to premature completion and use-after-free. --
--- Patch [15]: [PATCH v2 15/64] dmaengine: pch_dma: convert tasklet to dma_chan BH --- commit a2ed59e859fb44b7074fd4693b53c278ebc94f3d
Author: Allen Pais <[email protected]>
dmaengine: pch_dma: convert 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.
Quoted 4 lines
drivers/dma/pch_dma.c +0
-0
[ ... ]
drivers/dma/pch_dma.c static int pd_device_terminate_all(struct dma_chan *chan) · line 673
Quoted 13 lines
drivers/dma/pch_dma.c +3
-2@@ -665,9 +664,10 @@ static int pd_device_terminate_all(struct dma_chan *chan)return 0 ; } -static void pdc_tasklet (struct tasklet_struct *t) +static void pdc_tasklet (struct dma_chan *c) { -struct pch_dma_chan *pd_chan =from_tasklet (pd_chan, t, tasklet); + struct pch_dma_chan *pd_chan = container_of(c, struct pch_dma_chan, + chan);unsigned long flags;if (!pdc_is_idle (pd_chan)) {
[Severity: High]
This isn't a bug introduced by this patch, but might checking the hardware idle state outside the lock cause a race condition?
If a concurrent pd_issue_pending() acquires the lock first and starts the next request, could the preempted thread later acquire the lock, see a stale idle state, and improperly complete the newly started descriptor?
This same check appears unprotected in pd_issue_pending():
drivers/dma/pch_dma.c:pd_issue_pending() {
...
if (pdc_is_idle(pd_chan)) {
...
}[Severity: High]
This is a pre-existing issue, but would acquiring the channel lock with a plain spin_lock() in process-context functions risk a deadlock?
If a hardware interrupt preempts process context here in pd_tx_submit() or pd_issue_pending():
drivers/dma/pch_dma.c:pd_tx_submit() {
...
spin_lock(&pd_chan->lock);
...
}and schedules this pdc_tasklet() work which attempts to acquire the exact same lock using spin_lock_irqsave(), might this deadlock the CPU?
[Severity: High]
This is a pre-existing issue, but is there a risk of a self-deadlock if the client callback submits a new DMA transaction?
If pdc_tasklet() acquires the lock, and then pdc_chain_complete() invokes the callback:
drivers/dma/pch_dma.c:pdc_chain_complete() {
...
if (callback)
callback(param);
...
}and that callback attempts to submit a new transaction via pd_tx_submit(), it will attempt to acquire the same pd_chan->lock.
[ ... ]
drivers/dma/pch_dma.c static int pch_dma_probe(struct pci_dev *pdev, · line 882
Quoted 8 lines
drivers/dma/pch_dma.c +1
-1@@ -877,7 +877,7 @@ static int pch_dma_probe(struct pci_dev *pdev,INIT_LIST_HEAD (&pd_chan->queue);INIT_LIST_HEAD (&pd_chan->free_list); -tasklet_setup (&pd_chan->tasklet, pdc_tasklet); +dma_chan_init_bh (&pd_chan->chan, pdc_tasklet);list_add_tail (&pd_chan->chan.device_node, &pd->dma.channels); }
[Severity: High]
This isn't a bug introduced by this patch, but can requesting the shared IRQ before dma_async_device_register() initializes the channel count lead to an unhandled IRQ storm?
In pch_dma_probe(), request_irq() is called with IRQF_SHARED before the channel count is initialized.
The interrupt handler relies entirely on pd->dma.chancnt:
drivers/dma/pch_dma.c:pd_irq() {
...
for (i = 0; i < pd->dma.chancnt; i++) {
...
}If pd->dma.chancnt is initially 0, could it loop 0 times and return IRQ_NONE without clearing the hardware interrupt status?
[Severity: High]
This is a pre-existing issue, but does the probe error path fail to kill the scheduled bottom-half work before freeing the device structure?
If dma_async_device_register() fails, the error path frees the memory but appears to skip cancelling pending BH works:
drivers/dma/pch_dma.c:pch_dma_probe() {
...
err_free_pool:
dma_pool_destroy(pd->pool);
err_free_irq:
free_irq(pdev->irq, pd);
...
}If an interrupt fires and schedules the BH work during this window, could it execute on freed memory?
[ ... ]
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=15
PATCH v2 16/64 dmaengine: ppc4xx: replace irq tasklet with dma_chan BH
In reply to Allen PaisReplace 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.
--- drivers/dma/ppc4xx/adma.c | 14 +++++++------- drivers/dma/ppc4xx/adma.h | 2 -- 2 files changed, 7 insertions(+), 9 deletions(-)
drivers/dma/ppc4xx/adma.c +7 -7
@@ -1658,9 +1658,9 @@ static void __ppc440spe_adma_slot_cleanup(struct ppc440spe_adma_chan *chan)/** * ppc440spe_adma_tasklet - clean up watch-dog initiator */ -static void ppc440spe_adma_tasklet (struct tasklet_struct *t) +static void ppc440spe_adma_tasklet (struct dma_chan *c) { -struct ppc440spe_adma_chan *chan =from_tasklet (chan, t, irq_tasklet); +struct ppc440spe_adma_chan *chan =to_ppc440spe_adma_chan (c);spin_lock_nested (&chan->lock,SINGLE_DEPTH_NESTING );__ppc440spe_adma_slot_cleanup (chan);
@@ -1754,7 +1754,7 @@ static struct ppc440spe_adma_desc_slot *ppc440spe_adma_alloc_slots( goto retry;/* try to free some slots if the allocation fails */ -tasklet_schedule (&chan->irq_tasklet); +dma_chan_schedule_bh (&chan->common);return NULL ; }
@@ -3595,7 +3595,7 @@ static irqreturn_t ppc440spe_adma_eot_handler(int irq, void *data)dev_dbg (chan->device->common.dev,"ppc440spe adma%d: %s\n" , chan->device->id, __func__); -tasklet_schedule (&chan->irq_tasklet); +dma_chan_schedule_bh (&chan->common);ppc440spe_adma_device_clear_eot_status (chan);return IRQ_HANDLED ;
@@ -3612,7 +3612,7 @@ static irqreturn_t ppc440spe_adma_err_handler(int irq, void *data)dev_dbg (chan->device->common.dev,"ppc440spe adma%d: %s\n" , chan->device->id, __func__); -tasklet_schedule (&chan->irq_tasklet); +dma_chan_schedule_bh (&chan->common);ppc440spe_adma_device_clear_eot_status (chan);return IRQ_HANDLED ;
@@ -4137,7 +4137,7 @@ static int ppc440spe_adma_probe(struct platform_device *ofdev) chan->common.device = &adev->common;dma_cookie_init (&chan->common);list_add_tail (&chan->common.device_node, &adev->common.channels); -tasklet_setup (&chan->irq_tasklet, ppc440spe_adma_tasklet); +dma_chan_init_bh (&chan->common, ppc440spe_adma_tasklet);/* allocate and map helper pages for async validation or * async_mult/async_sum_product operations on DMA0/1.
@@ -4247,7 +4247,7 @@ static void ppc440spe_adma_remove(struct platform_device *ofdev) device_node) { ppc440spe_chan =to_ppc440spe_adma_chan (chan);ppc440spe_adma_release_irqs (adev, ppc440spe_chan); -tasklet_kill (&ppc440spe_chan->irq_tasklet); +dma_chan_kill_bh (&ppc440spe_chan->common);if (adev->id !=PPC440SPE_XOR_ID ) {dma_unmap_page (&ofdev->dev, ppc440spe_chan->pdest,PAGE_SIZE ,DMA_BIDIRECTIONAL );
drivers/dma/ppc4xx/adma.h +0 -2
@@ -80,7 +80,6 @@ struct ppc440spe_adma_device {* @pending: allows batching of hardware operations * @slots_allocated: records the actual size of the descriptor slot pool * @hw_chain_inited: h/w descriptor chain initialization flag -* @irq_tasklet: bottom half where ppc440spe_adma_slot_cleanup runs * @needs_unmap: if buffers should not be unmapped upon final processing * @pdest_page: P destination page for async validate operation * @qdest_page: Q destination page for async validate operation
@@ -97,7 +96,6 @@ struct ppc440spe_adma_chan {int pending;int slots_allocated;int hw_chain_inited; -struct tasklet_struct irq_tasklet;u8 needs_unmap;struct page *pdest_page;struct page *qdest_page;
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 16/64] dmaengine: ppc4xx: replace irq tasklet with dma_chan BH
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing dma_chan_kill_bh() on probe error paths leads to use-after-free.
- [High] Lockless traversal of chan->chain in hardirq context races with list modification, causing panics.
- [High] Missing channel lock in ppc440spe_adma_issue_pending causes concurrent list/hardware corruption.
- [High] Direct freeing of DMA structures in remove() without waiting for active clients causes Use-After-Free. --
--- Patch [16]: [PATCH v2 16/64] dmaengine: ppc4xx: replace irq tasklet with dma_chan BH --- commit f5f6478c931c241a7fdae2271fa2dee782cc4570
Author: Allen Pais <[email protected]>
Subject: dmaengine: ppc4xx: replace irq tasklet with 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.
Quoted 6 lines
drivers/dma/ppc4xx/adma.c +0
-0@@ -1658,9 +1658,9 @@ static void __ppc440spe_adma_slot_cleanup(struct ppc440spe_adma_chan *chan)/**
drivers/dma/ppc4xx/adma.c static void __ppc440spe_adma_slot_cleanup(struct ppc440spe_adma_chan *chan) · line 1666
Quoted 10 lines
drivers/dma/ppc4xx/adma.c +2
-2* ppc440spe_adma_tasklet - clean up watch-dog initiator */ -static void ppc440spe_adma_tasklet (struct tasklet_struct *t) +static void ppc440spe_adma_tasklet (struct dma_chan *c) { -struct ppc440spe_adma_chan *chan =from_tasklet (chan, t, irq_tasklet); +struct ppc440spe_adma_chan *chan =to_ppc440spe_adma_chan (c);spin_lock_nested (&chan->lock,SINGLE_DEPTH_NESTING );__ppc440spe_adma_slot_cleanup (chan);
[Severity: High]
This is a pre-existing issue, but does this list modification race with hardirq context?
The hardirq handlers (ppc440spe_adma_eot_handler and ppc440spe_adma_err_handler) call ppc440spe_adma_device_clear_eot_status(), which traverses chan->chain locklessly:
drivers/dma/ppc4xx/adma.c:ppc440spe_adma_device_clear_eot_status() {
...
list_for_each_entry(iter, &chan->chain, chain_node) {
if (iter->phys == phys)
break;
}
...
}Meanwhile, this softirq/BH code modifies the list without disabling local interrupts (using spin_lock_nested or spin_lock_bh). If a hardware interrupt fires on the same CPU while the list is being modified, couldn't the hardirq handler traverse a corrupted list and cause memory corruption or a panic?
[ ... ]
drivers/dma/ppc4xx/adma.c static int ppc440spe_adma_probe(struct platform_device *ofdev) · line 4142
Quoted 8 lines
drivers/dma/ppc4xx/adma.c +1
-1@@ -4137,7 +4137,7 @@ static int ppc440spe_adma_probe(struct platform_device *ofdev) chan->common.device = &adev->common;dma_cookie_init (&chan->common);list_add_tail (&chan->common.device_node, &adev->common.channels); -tasklet_setup (&chan->irq_tasklet, ppc440spe_adma_tasklet); +dma_chan_init_bh (&chan->common, ppc440spe_adma_tasklet);/* allocate and map helper pages for async validation or
[Severity: High]
This isn't a bug introduced by this patch, but does the probe error path need to call dma_chan_kill_bh()?
In ppc440spe_adma_probe(), dma_chan_init_bh() initializes the bottom-half work. Later, hardware IRQs are requested. If an interrupt fires and schedules the BH via dma_chan_schedule_bh(), but a subsequent initialization step fails, the error path uses free_irq() and then frees the channel structure without canceling the pending work:
err_page_alloc:
kfree(chan);
When the background work queue executes later, won't it access the freed chan memory and cause a use-after-free?
[ ... ]
drivers/dma/ppc4xx/adma.c static void ppc440spe_adma_remove(struct platform_device *ofdev) · line 4253
Quoted 9 lines
drivers/dma/ppc4xx/adma.c +1
-1@@ -4247,7 +4247,7 @@ static void ppc440spe_adma_remove(struct platform_device *ofdev) device_node) { ppc440spe_chan =to_ppc440spe_adma_chan (chan);ppc440spe_adma_release_irqs (adev, ppc440spe_chan); -tasklet_kill (&ppc440spe_chan->irq_tasklet); +dma_chan_kill_bh (&ppc440spe_chan->common);if (adev->id !=PPC440SPE_XOR_ID ) {dma_unmap_page (&ofdev->dev, ppc440spe_chan->pdest,PAGE_SIZE ,DMA_BIDIRECTIONAL );
[Severity: High]
This is a pre-existing issue, but does this function safely wait for all clients to drop their references?
In ppc440spe_adma_remove(), the driver calls dma_async_device_unregister() which unregisters the device but does not synchronously block for active client references to be dropped.
Immediately after, it frees the channel and device structures:
list_del(&chan->device_node);
kfree(ppc440spe_chan);
...
kfree(adev);If a client (like the network stack or filesystem) still holds a reference and attempts to use the channel or release it later, will it dereference these freed structures and cause a use-after-free?
[ ... ]
[Severity: High]
This is another pre-existing issue in the driver. Is channel locking missing in ppc440spe_adma_issue_pending()?
The DMA engine callback executes without acquiring chan->lock when checking chan->pending and calling ppc440spe_chan_append():
drivers/dma/ppc4xx/adma.c:ppc440spe_adma_issue_pending() {
...
if (ppc440spe_chan->pending) {
ppc440spe_chan->pending = 0;
ppc440spe_chan_append(ppc440spe_chan);
}
...
}Other parts of the driver (like ppc440spe_adma_tx_submit) correctly lock around these calls using spin_lock_bh. Because issue_pending is an asynchronous dmaengine API that can be called concurrently across multiple CPUs, couldn't this race and corrupt hardware MMIO registers and descriptor lists?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=16
PATCH v2 17/64 dmaengine: ste_dma40: convert per-channel tasklet to dma_chan BH
In reply to Allen PaisReplace 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.
--- drivers/dma/ste_dma40.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-)
drivers/dma/ste_dma40.c +5 -8
@@ -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);
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 17/64] dmaengine: ste_dma40: convert per-channel tasklet to dma_chan BH
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing free_irq() on d40_probe error paths leads to a leaked IRQ handler and a potential Use-After-Free of base.
- [High] Missing device_synchronize callback causes a race condition during channel release, potentially invoking callbacks in unloaded client modules.
- [High] Probe failure after exposing channels causes a Use-After-Free because devres forcefully frees memory while clients or scheduled BH work may be active. --
--- Patch [17]: [PATCH v2 17/64] dmaengine: ste_dma40: convert per-channel tasklet to dma_chan BH --- commit 7d6ec649e7f14a8affbdbde4ad9aa8498383284e
Author: Allen Pais <[email protected]>
Subject: dmaengine: ste_dma40: convert per-channel tasklet to dma_chan BH
This commit replaces the per-channel tasklet with the shared dma_chan BH helper. The handler continues to run in softirq context while dmaengine manages the scheduling and teardown mechanism.
Quoted 4 lines
drivers/dma/ste_dma40.c +0
-0
[ ... ]
drivers/dma/ste_dma40.c static void dma_tasklet(struct tasklet_struct *t) · line 1644
Quoted 9 lines
drivers/dma/ste_dma40.c +1
-1@@ -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);
[Severity: High]
This is a pre-existing issue, but does this create a race condition during channel release?
The driver doesn't seem to implement the device_synchronize callback, making dmaengine_synchronize() a no-op. When a DMA client module unloads and releases its channel, it relies on this synchronization to flush any pending async work.
Could dma_release_channel() return and the client module unload while the bh_work is still executing here? If the work is preempted right after dropping the channel lock and before invoking the client callback:
spin_unlock_irqrestore(&d40c->lock, flags); if (callback_active) dmaengine_desc_callback_invoke(&cb, NULL);
wouldn't it resume and attempt to execute the callback residing in the now-freed module's .text segment?
Additionally, I noticed a couple of pre-existing issues in d40_probe():
[Severity: High]
This isn't a bug introduced by this patch, but does d40_probe() leak the IRQ handler on error paths?
drivers/dma/ste_dma40.c:d40_probe() {
...
ret = request_irq(base->irq, d40_handle_interrupt, 0, D40_NAME, base);
if (ret) {
d40_err(dev, "No IRQ defined\n");
goto destroy_cache;
}
...
if (base->plat_data->use_esram_lcla) {
base->lcpa_regulator = regulator_get(base->dev, "lcla_esram");
if (IS_ERR(base->lcpa_regulator)) {
d40_err(dev, "Failed to get lcpa_regulator\n");
ret = PTR_ERR(base->lcpa_regulator);
base->lcpa_regulator = NULL;
goto destroy_cache;
}
...
destroy_cache:
if (base->lcla_pool.dma_addr)
dma_unmap_single(...)
...
report_failure:
d40_err(dev, "probe failed\n");
return ret;
}If d40_probe() fails after request_irq() (for example, in regulator_enable() or of_dma_controller_register()), it branches to the destroy_cache label and returns an error without freeing the registered IRQ.
Because base is automatically freed by the devres framework, if a spurious or lingering interrupt fires afterwards, won't the still-registered d40_handle_interrupt() execute and dereference the freed base pointer?
[Severity: High]
This isn't a bug introduced by this patch, but does a probe failure after exposing channels cause a Use-After-Free?
drivers/dma/ste_dma40.c:d40_probe() {
...
ret = d40_dmaengine_init(base, num_reserved_chans);
if (ret)
goto destroy_cache;
...
ret = of_dma_controller_register(np, d40_xlate, NULL);
if (ret) {
dev_err(dev,
"could not register of_dma_controller\n");
goto destroy_cache;
}
...
}In d40_probe(), the driver exposes channels globally by calling dmaenginem_async_device_register() midway through the function via d40_dmaengine_init().
If a subsequent initialization step fails, like of_dma_controller_register(), the function returns an error. The devres framework then automatically unregisters the DMA device and frees the driver's memory.
In the window between registration and failure, could a concurrent client driver request a channel and start a transfer, or an interrupt fire and schedule the channel's BH work?
Since dma_async_device_unregister() doesn't wait for active clients to drop references when called from devres, will the client or BH work access the forcefully freed structures?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=17
Re: [PATCH v2 17/64] dmaengine: ste_dma40: convert per-channel tasklet to dma_chan BH
In reply to Allen PaisQuoted 5 lines · Allen Pais
Replace the per-channel tasklet with the shared
dma_chanBH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.Signed-off-by Allen Pais <[email protected]>
I like it.
Yours,
Linus Walleij
PATCH v2 18/64 dmaengine: xgene-dma: wire descriptor cleanup to dma_chan BH
In reply to Allen PaisReplace 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.
--- drivers/dma/xgene-dma.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-)
drivers/dma/xgene-dma.c +9 -10
@@ -260,7 +260,6 @@ struct xgene_dma_desc_sw {* These descriptors have already had their cleanup actions run. They * are waiting for the ACK bit to be set by the async tx API. * @desc_pool: descriptor pool for DMA operations -* @tasklet: bottom half where all completed descriptors cleans * @tx_ring: transmit ring descriptor that we use to prepare actual * descriptors for further executions * @rx_ring: receive ring descriptor that we use to get completed DMA
@@ -280,7 +279,6 @@ struct xgene_dma_chan {struct list_head ld_running;struct list_head ld_completed;struct dma_pool *desc_pool; -struct tasklet_struct tasklet;struct xgene_dma_ring tx_ring;struct xgene_dma_ring rx_ring; };
@@ -975,9 +973,10 @@ static enum dma_status xgene_dma_tx_status(struct dma_chan *dchan,return dma_cookie_status (dchan, cookie, txstate); } -static void xgene_dma_tasklet_cb (struct tasklet_struct *t) +static void xgene_dma_tasklet_cb (struct dma_chan *c) { -struct xgene_dma_chan *chan =from_tasklet (chan, t, tasklet); + struct xgene_dma_chan *chan = container_of(c, struct xgene_dma_chan, + dma_chan);/* Run all cleanup for descriptors which have been completed */ xgene_dma_cleanup_descriptors (chan);
@@ -999,11 +998,11 @@ static irqreturn_t xgene_dma_chan_ring_isr(int irq, void *id)disable_irq_nosync (chan->rx_irq);/* -* Schedule the tasklet to handle all cleanup of the current +* Schedule the bottom half to handle all cleanup of the current * transaction. It will start a new transaction if there is * one pending. */ -tasklet_schedule (&chan->tasklet); +dma_chan_schedule_bh (&chan->dma_chan);return IRQ_HANDLED ; }
@@ -1539,7 +1538,7 @@ static int xgene_dma_async_register(struct xgene_dma *pdma, int id)INIT_LIST_HEAD (&chan->ld_pending);INIT_LIST_HEAD (&chan->ld_running);INIT_LIST_HEAD (&chan->ld_completed); -tasklet_setup (&chan->tasklet, xgene_dma_tasklet_cb); +dma_chan_init_bh (&chan->dma_chan, xgene_dma_tasklet_cb); chan->pending =0 ; chan->desc_pool =NULL ;
@@ -1556,7 +1555,7 @@ static int xgene_dma_async_register(struct xgene_dma *pdma, int id) ret =dma_async_device_register (dma_dev);if (ret) {chan_err (chan,"Failed to register async device %d" , ret); -tasklet_kill (&chan->tasklet); +dma_chan_kill_bh (&chan->dma_chan);return ret; }
@@ -1579,7 +1578,7 @@ static int xgene_dma_init_async(struct xgene_dma *pdma)if (ret) {for (j =0 ; j < i; j++) {dma_async_device_unregister (&pdma->dma_dev[j]); -tasklet_kill (&pdma->chan[j].tasklet); +dma_chan_kill_bh (&pdma->chan[j].dma_chan); }return ret;
@@ -1790,7 +1789,7 @@ static void xgene_dma_remove(struct platform_device *pdev)for (i =0 ; i <XGENE_DMA_MAX_CHANNEL ; i++) { chan = &pdma->chan[i]; -tasklet_kill (&chan->tasklet); +dma_chan_kill_bh (&chan->dma_chan);xgene_dma_delete_chan_rings (chan); }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 18/64] dmaengine: xgene-dma: wire descriptor cleanup to dma_chan BH
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Pre-existing issues:
- [High] During device teardown, the driver frees the IRQ before flushing the bottom half workqueue. This results in the bottom half handler calling enable_irq() on a freed IRQ descriptor, which violates the API and can cause warnings or oopses in the IRQ subsystem.
- [High] Potential spinlock recursion deadlock in xgene_dma_cleanup_descriptors() when called from process context via xgene_dma_free_chan_resources().
- [High] Hardware interrupts are unmasked before the bottom halves are initialized in the probe sequence, allowing an early interrupt to permanently hang the channel.
- [High] In the probe error path, bottom halves are flushed before hardware interrupts are masked, leading to a race condition that can result in a Use-After-Free of driver structures when the workqueue later executes.
- [High] The driver returns ring slots to the hardware before clearing the descriptor completion signature, risking permanently lost completions.
--
--- Patch [18]: [PATCH v2 18/64] dmaengine: xgene-dma: wire descriptor cleanup to dma_chan BH --- commit 3558adb2da1479ded074f1f3135c3903cdea9f57
Author: Allen Pais <[email protected]>
dmaengine: xgene-dma: wire descriptor cleanup 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.
Quoted 7 lines
drivers/dma/xgene-dma.c +0
-0@@ -975,9 +973,10 @@ static enum dma_status xgene_dma_tx_status(struct dma_chan *dchan,return dma_cookie_status (dchan, cookie, txstate); }
drivers/dma/xgene-dma.c static enum dma_status xgene_dma_tx_status(struct dma_chan *dchan, · line 982
Quoted 10 lines
drivers/dma/xgene-dma.c +3
-2-static void xgene_dma_tasklet_cb (struct tasklet_struct *t) +static void xgene_dma_tasklet_cb (struct dma_chan *c) { -struct xgene_dma_chan *chan =from_tasklet (chan, t, tasklet); + struct xgene_dma_chan *chan = container_of(c, struct xgene_dma_chan, + dma_chan);/* Run all cleanup for descriptors which have been completed */ xgene_dma_cleanup_descriptors (chan);
[Severity: High]
This is a pre-existing issue, but can this cause a spinlock recursion deadlock?
Looking at xgene_dma_free_chan_resources(), it runs in process context and calls xgene_dma_cleanup_descriptors(), which acquires chan->lock using spin_lock() without disabling softirqs.
If a hardware interrupt occurs on the same CPU, it schedules the bottom half. On interrupt exit, the softirq executes xgene_dma_tasklet_cb(), which also calls xgene_dma_cleanup_descriptors() and attempts to acquire the already-held chan->lock.
[Severity: High]
This is a pre-existing issue, but does this out-of-order execution risk permanently losing DMA completions?
In xgene_dma_cleanup_descriptors(), the driver writes to ring->cmd to notify the hardware that a descriptor slot is free, and then clears the descriptor's completion signature in memory:
xgene_dma_cleanup_descriptors() {
...
iowrite32(-1, ring->cmd);
desc_hw->m0 = cpu_to_le64(XGENE_DMA_DESC_EMPTY_SIGNATURE);
...
}Since the hardware engine is active, could it immediately write a new completion signature into that slot before the CPU executes the assignment, causing the CPU to unconditionally overwrite the valid completion signature with the empty signature?
[ ... ]
drivers/dma/xgene-dma.c static int xgene_dma_async_register(struct xgene_dma *pdma, int id) · line 1541
Quoted 6 lines
drivers/dma/xgene-dma.c +1
-1@@ -1539,7 +1538,7 @@ static int xgene_dma_async_register(struct xgene_dma *pdma, int id)INIT_LIST_HEAD (&chan->ld_pending);INIT_LIST_HEAD (&chan->ld_running);INIT_LIST_HEAD (&chan->ld_completed); -tasklet_setup (&chan->tasklet, xgene_dma_tasklet_cb); +dma_chan_init_bh (&chan->dma_chan, xgene_dma_tasklet_cb);
[Severity: High]
This is a pre-existing issue, but can an early interrupt permanently hang the channel during probe?
In xgene_dma_probe(), xgene_dma_init_hw() enables the DMA engine and unmasks interrupts before xgene_dma_init_async() initializes the bottom halves.
If a pending or spurious interrupt fires, the ISR attempts to schedule the uninitialized BH, which fails. Because the BH never runs, enable_irq() is never called, leaving the channel's IRQ permanently disabled.
[ ... ]
drivers/dma/xgene-dma.c static int xgene_dma_init_async(struct xgene_dma *pdma) · line 1582
Quoted 7 lines
drivers/dma/xgene-dma.c +1
-1@@ -1579,7 +1578,7 @@ static int xgene_dma_init_async(struct xgene_dma *pdma)if (ret) {for (j =0 ; j < i; j++) {dma_async_device_unregister (&pdma->dma_dev[j]); -tasklet_kill (&pdma->chan[j].tasklet); +dma_chan_kill_bh (&pdma->chan[j].dma_chan); }
[Severity: High]
This is a pre-existing issue, but could this error path lead to a use-after-free of the driver structures?
If xgene_dma_init_async() fails, it calls dma_chan_kill_bh() to clean up initialized channels, but does not mask hardware interrupts.
An interrupt could fire immediately after, re-queuing the BH. The probe function then calls xgene_dma_free_irqs(), returns an error, and devm frees the pdma memory.
Could the global workqueue subsequently execute the re-scheduled BH, dereferencing the freed chan structure?
[ ... ]
drivers/dma/xgene-dma.c static void xgene_dma_remove(struct platform_device *pdev) · line 1794
Quoted 8 lines
drivers/dma/xgene-dma.c +1
-1@@ -1790,7 +1789,7 @@ static void xgene_dma_remove(struct platform_device *pdev)for (i =0 ; i <XGENE_DMA_MAX_CHANNEL ; i++) { chan = &pdma->chan[i]; -tasklet_kill (&chan->tasklet); +dma_chan_kill_bh (&chan->dma_chan);xgene_dma_delete_chan_rings (chan); }
[Severity: High]
This is a pre-existing issue, but is there a risk of accessing a freed IRQ descriptor during this teardown sequence?
xgene_dma_remove() calls xgene_dma_free_irqs() which frees chan->rx_irq, and then calls dma_chan_kill_bh().
If the bottom half was already queued, it executes after the IRQ is freed and unconditionally calls enable_irq(chan->rx_irq), which violates the API and could cause an oops in the IRQ subsystem.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=18
PATCH v2 19/64 dmaengine: xilinx-dma: use dma_chan BH instead of tasklets
In reply to Allen PaisReplace 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.
--- drivers/dma/xilinx/xilinx_dma.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-)
drivers/dma/xilinx/xilinx_dma.c +9 -11
@@ -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
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 19/64] dmaengine: xilinx-dma: use dma_chan BH instead of tasklets
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] request_irq() is called before channel initialization is complete, creating a race condition if an early interrupt fires.
- [High] Uninitialized channel access in xilinx_mcdma_irq_handler during probe.
- [High] Recursive spinlock deadlock when running dependencies on the same DMA channel.
--
--- Patch [19]: [PATCH v2 19/64] dmaengine: xilinx-dma: use dma_chan BH instead of tasklets --- commit 2b1e6b23fed3e56575f2e8169196ee83cbca26e8
Author: Allen Pais <[email protected]>
dmaengine: xilinx-dma: use dma_chan BH instead of tasklets
This patch replaces 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.
Quoted 4 lines
drivers/dma/xilinx/xilinx_dma.c +0
-0
[ ... ]
Quoted 6 lines
drivers/dma/xilinx/xilinx_dma.c +1
-1@@ -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
drivers/dma/xilinx/xilinx_dma.c static void xilinx_dma_chan_desc_cleanup(struct xilinx_dma_chan *chan) · line 1124
Quoted 10 lines
drivers/dma/xilinx/xilinx_dma.c +3
-2*/ -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); }
[Severity: High]
This is a pre-existing issue, but can this lead to a recursive spinlock deadlock?
Looking at xilinx_dma_chan_desc_cleanup(), the channel lock is acquired before running dependencies:
xilinx_dma_chan_desc_cleanup() {
...
spin_lock_irqsave(&chan->lock, flags);
...
dma_run_dependencies(&desc->async_tx);
...
}If a dependent transaction belongs to the same channel, dma_run_dependencies() will invoke xilinx_dma_tx_submit(), which unconditionally attempts to acquire the exact same chan->lock. Since standard spinlocks are not recursive, would this cause the CPU to deadlock?
[ ... ]
drivers/dma/xilinx/xilinx_dma.c static irqreturn_t xilinx_mcdma_irq_handler(int irq, void *data) · line 1902
Quoted 8 lines
drivers/dma/xilinx/xilinx_dma.c +1
-1@@ -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 ; }
[Severity: High]
This is a pre-existing issue, but could this handler dereference a NULL pointer during device probe?
If a shared interrupt fires while the driver is still probing channels, xilinx_mcdma_irq_handler() might compute a chan_offset for a channel that has not been probed yet:
xilinx_mcdma_irq_handler() {
...
chan = chan->xdev->chan[chan_offset];
status = dma_ctrl_read(chan, XILINX_MCDMA_CHAN_SR_OFFSET(chan->tdest));
...
}If the channel array entry is still NULL, would dma_ctrl_read() trigger a crash?
[ ... ]
drivers/dma/xilinx/xilinx_dma.c static int xilinx_dma_chan_probe(struct xilinx_dma_device *xdev, · line 3077
Quoted 7 lines
drivers/dma/xilinx/xilinx_dma.c +1
-2@@ -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);
[Severity: High]
This is a pre-existing issue, but does calling request_irq() earlier in xilinx_dma_chan_probe() create a race condition here?
In xilinx_dma_chan_probe(), request_irq() is called with IRQF_SHARED before chan->start_transfer and the bottom-half work are initialized:
xilinx_dma_chan_probe() {
...
err = request_irq(chan->irq, xdev->dma_config->irq_handler,
IRQF_SHARED, "xilinx-dma-controller", chan);
...
chan->start_transfer = xilinx_dma_start_transfer;
...
dma_chan_init_bh(&chan->common, xilinx_dma_do_tasklet);
...
}If an interrupt fires on the shared line immediately after request_irq() succeeds, the handler could try to invoke chan->start_transfer() or dma_chan_schedule_bh() while they are still uninitialized. Should the IRQ registration be moved after the channel is fully initialized?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=19
PATCH v2 20/64 dmaengine: xilinx-dpdma: kill vchan BH on remove
In reply to Allen Paisvirt-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.
--- drivers/dma/xilinx/xilinx_dpdma.c | 1 + 1 file changed, 1 insertion(+)
drivers/dma/xilinx/xilinx_dpdma.c +1 -0
@@ -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); }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 20/64] dmaengine: xilinx-dpdma: kill vchan BH on remove
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] Premature clock disable and delayed IRQ free expose the driver to synchronous external aborts during teardown and probe error paths.
- [High] Hardware registers are accessed during probe before the AXI clock is prepared and enabled.
- [High] Concurrent modification of virt-dma desc_issued list due to missing chan->vchan.lock.
--
--- Patch [20]: [PATCH v2 20/64] dmaengine: xilinx-dpdma: kill vchan BH on remove --- commit b6ee3342e2955cfec74b9591a7918040bbe546f8
Author: Allen Pais <[email protected]>
dmaengine: xilinx-dpdma: kill vchan BH on remove
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.
drivers/dma/xilinx/xilinx_dpdma.c static void xilinx_dpdma_chan_remove(struct xilinx_dpdma_chan *chan) · line 1690
Quoted 11 lines
drivers/dma/xilinx/xilinx_dpdma.c +1
-0@@ -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); }
[Severity: High]
This is a pre-existing issue, but does the driver disable the clock too early during teardown and probe error paths?
Looking at xilinx_dpdma_remove(), the AXI clock is disabled before calling xilinx_dpdma_chan_remove() to kill the tasklets and BHs:
drivers/dma/xilinx/xilinx_dpdma.c:xilinx_dpdma_remove() {
...
clk_disable_unprepare(xdev->axi_clk); for (i = 0; i < ARRAY_SIZE(xdev->chan); i++) xilinx_dpdma_chan_remove(xdev->chan[i]);
...
}
Similarly, in the xilinx_dpdma_probe() error path, the clock is disabled before tasklets are killed and before the shared IRQ is freed:
drivers/dma/xilinx/xilinx_dpdma.c:xilinx_dpdma_probe() {
...error_dma_async:
clk_disable_unprepare(xdev->axi_clk); error: for (i = 0; i < ARRAY_SIZE(xdev->chan); i++) xilinx_dpdma_chan_remove(xdev->chan[i]); free_irq(xdev->irq, xdev);
...
}
Could this cause synchronous external aborts if an active shared IRQ handler, tasklet, or BH attempts to access hardware registers while the clock is disabled?
[Severity: High]
This is a pre-existing issue, but are hardware registers accessed during probe before the AXI clock is prepared and enabled?
In xilinx_dpdma_probe(), dpdma_hw_init() is called early to disable interrupts and clear statuses, which accesses hardware registers:
drivers/dma/xilinx/xilinx_dpdma.c:xilinx_dpdma_probe() {
...
dpdma_hw_init(xdev); xdev->irq = platform_get_irq(pdev, 0);
...
}
However, the clock is enabled much later in the probe sequence:
drivers/dma/xilinx/xilinx_dpdma.c:xilinx_dpdma_probe() {
...
ret = clk_prepare_enable(xdev->axi_clk);
if (ret) {...
}
Will this cause a synchronous external abort on device probe if the bootloader hasn't already enabled the clock?
[Severity: High]
This is a pre-existing issue, but does xilinx_dpdma_chan_handle_err() modify the virt-dma desc_issued list without the correct lock?
It appears to modify chan->vchan.desc_issued without holding chan->vchan.lock:
drivers/dma/xilinx/xilinx_dpdma.c:xilinx_dpdma_chan_handle_err() {
...
if (!chan->desc.pending &&
list_empty(&chan->vchan.desc_issued)) {
active->error = true;
list_add_tail(&active->vdesc.node,
&chan->vchan.desc_issued);...
}
The virt-dma layer expects chan->vchan.lock to protect the desc_issued list, as seen in xilinx_dpdma_terminate_all() where chan->vchan.lock is taken:
drivers/dma/xilinx/xilinx_dpdma.c:xilinx_dpdma_terminate_all() {
...
spin_lock_irqsave(&chan->vchan.lock, flags); vchan_get_all_descriptors(&chan->vchan, &descriptors); spin_unlock_irqrestore(&chan->vchan.lock, flags);
...
}
Could this concurrent modification lead to linked list corruption if a hardware channel error occurs while a client calls dmaengine_terminate_async()?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=20
PATCH v2 21/64 dmaengine: zynqmp-dma: switch completion tasklet to dma_chan BH
In reply to Allen PaisReplace 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.
--- drivers/dma/xilinx/zynqmp_dma.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-)
drivers/dma/xilinx/zynqmp_dma.c +9 -10
@@ -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);
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 21/64] dmaengine: zynqmp-dma: switch completion tasklet to dma_chan BH
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] On channel error, the driver incorrectly marks the currently active descriptor as successful and silently drops all other pending descriptors, causing clients to hang indefinitely.
- [High] The DMA completion handler zynqmp_dma_chan_desc_cleanup drops chan->lock during callback execution while using list_for_each_entry_safe, allowing list corruption if a callback calls dmaengine_terminate_async.
- [High] zynqmp_dma_device_terminate_all fails to actually halt the hardware DMA engine, leading to hardware silently overwriting memory after the client assumes the transfer is stopped. --
--- Patch [21]: [PATCH v2 21/64] dmaengine: zynqmp-dma: switch completion tasklet to dma_chan BH --- commit c7b5b53ae6dbca9c51452b38046b4252492aa34c
Author: Allen Pais <[email protected]>
Subject: dmaengine: zynqmp-dma: switch completion 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.
Quoted 4 lines
drivers/dma/xilinx/zynqmp_dma.c +0
-0
[ ... ]
Quoted 6 lines
drivers/dma/xilinx/zynqmp_dma.c +1
-1@@ -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
drivers/dma/xilinx/zynqmp_dma.c static irqreturn_t zynqmp_dma_irq_handler(int irq, void *data) · line 769
Quoted 10 lines
drivers/dma/xilinx/zynqmp_dma.c +3
-2*/ -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;
[Severity: High]
This isn't a bug introduced by this patch, but I noticed a potential problem in how channel errors are handled when this bottom half runs.
When chan->err is set, we call zynqmp_dma_reset(). Inside zynqmp_dma_reset():
drivers/dma/xilinx/zynqmp_dma.c:zynqmp_dma_reset() {
...
zynqmp_dma_complete_descriptor(chan);
...
zynqmp_dma_free_descriptors(chan);
...
}Does this incorrectly move the active descriptor to done_list and mark it successful?
Also, it appears zynqmp_dma_free_descriptors() silently moves all remaining descriptors from active_list and pending_list to the free_list without invoking their callbacks.
Could this cause clients waiting on these DMA transfers to hang indefinitely?
[Severity: High]
This is a pre-existing issue, but could there be a list corruption risk in zynqmp_dma_chan_desc_cleanup() which is called shortly after in this bottom half?
drivers/dma/xilinx/zynqmp_dma.c:zynqmp_dma_chan_desc_cleanup() {
...
list_for_each_entry_safe(desc, next, &chan->done_list, node) {
...
if (dmaengine_desc_callback_valid(&cb)) {
spin_unlock_irqrestore(&chan->lock, irqflags);
dmaengine_desc_callback_invoke(&cb, NULL);
spin_lock_irqsave(&chan->lock, irqflags);
}
...
}The lock is dropped during callback execution. If the client calls dmaengine_terminate_async() in the callback, all descriptors (including those on done_list) are moved to free_list.
When the lock is reacquired, the loop continues using the next pointer which is now on the free_list.
Could this lead to double-frees and traversing invalid entries?
[Severity: High]
This is a pre-existing issue, but does zynqmp_dma_device_terminate_all() actually halt the hardware DMA engine?
drivers/dma/xilinx/zynqmp_dma.c:zynqmp_dma_device_terminate_all() {
...
writel(ZYNQMP_DMA_IDS_DEFAULT_MASK, chan->regs + ZYNQMP_DMA_IDS);
zynqmp_dma_free_descriptors(chan);
...
}It masks interrupts and frees software descriptors, but doesn't seem to clear the ZYNQMP_DMA_ENABLE bit or issue a hardware reset.
If the hardware controller remains active, could it silently overwrite memory after the client assumes the transfer is stopped and unmaps the DMA buffers?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=21
PATCH v2 22/64 dmaengine: tegra20-apb: use channel BH helpers
In reply to Allen PaisSwitch the per-channel tasklet to dma_chan_*_bh so completion handling runs through the shared per-channel deferral mechanism.
--- drivers/dma/tegra20-apb-dma.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-)
drivers/dma/tegra20-apb-dma.c +9 -9
@@ -194,9 +194,8 @@ struct tegra_dma_channel {struct list_head free_dma_desc;struct list_head cb_desc; -/* ISR handler and tasklet for bottom half of isr handling */ +/* ISR handler for bottom-half processing */ dma_isr_handler isr_handler; -struct tasklet_struct tasklet;/* Channel-slave specific configuration */ unsigned int slave_id;
@@ -632,9 +631,10 @@ static void handle_cont_sngl_cycle_dma_done(struct tegra_dma_channel *tdc, } } -static void tegra_dma_tasklet (struct tasklet_struct *t) +static void tegra_dma_tasklet (struct dma_chan *chan) { -struct tegra_dma_channel *tdc =from_tasklet (tdc, t, tasklet); + struct tegra_dma_channel *tdc = container_of(chan, struct tegra_dma_channel, + dma_chan);struct dmaengine_desc_callback cb;struct tegra_dma_desc *dma_desc;unsigned int cb_count;
@@ -670,7 +670,7 @@ static irqreturn_t tegra_dma_isr(int irq, void *dev_id)if (status &TEGRA_APBDMA_STATUS_ISE_EOC ) {tdc_write (tdc,TEGRA_APBDMA_CHAN_STATUS , status); tdc->isr_handler (tdc, false); -tasklet_schedule (&tdc->tasklet); +dma_chan_schedule_bh (&tdc->dma_chan);wake_up_all (&tdc->wq);spin_unlock (&tdc->lock);return IRQ_HANDLED ;
@@ -819,7 +819,7 @@ static void tegra_dma_synchronize(struct dma_chan *dc)*/ wait_event (tdc->wq,tegra_dma_eoc_interrupt_deasserted (tdc)); -tasklet_kill (&tdc->tasklet); +dma_chan_kill_bh (&tdc->dma_chan);pm_runtime_put (tdc->tdma->dev); }
@@ -1317,7 +1317,7 @@ static void tegra_dma_free_chan_resources(struct dma_chan *dc)dev_dbg (tdc2dev (tdc),"Freeing channel %d\n" , tdc->id);tegra_dma_terminate_all (dc); -tasklet_kill (&tdc->tasklet); +dma_chan_kill_bh (&tdc->dma_chan);list_splice_init (&tdc->pending_sg_req, &sg_req_list);list_splice_init (&tdc->free_sg_req, &sg_req_list);
@@ -1511,7 +1511,7 @@ static int tegra_dma_probe(struct platform_device *pdev) tdc->id = i; tdc->slave_id =TEGRA_APBDMA_SLAVE_ID_INVALID ; -tasklet_setup (&tdc->tasklet, tegra_dma_tasklet); +dma_chan_init_bh (&tdc->dma_chan, tegra_dma_tasklet);spin_lock_init (&tdc->lock);init_waitqueue_head (&tdc->wq);
@@ -1617,7 +1617,7 @@ static int __maybe_unused tegra_dma_dev_suspend(struct device *dev)for (i =0 ; i < tdma->chip_data->nr_channels; i++) {struct tegra_dma_channel *tdc = &tdma->channels[i]; -tasklet_kill (&tdc->tasklet); +dma_chan_kill_bh (&tdc->dma_chan);spin_lock_irqsave (&tdc->lock, flags); busy = tdc->busy;
Signature · 1 line
2.43.0
PATCH v2 23/64 dmaengine: timb_dma: route callbacks via channel BH
In reply to Allen PaisSchedule per-channel BH work for completions so callbacks run through the dma_chan deferral mechanism while keeping the controller tasklet for IRQ handling.
--- drivers/dma/timb_dma.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-)
drivers/dma/timb_dma.c +16 -5
@@ -438,6 +438,8 @@ static void td_free_chan_resources(struct dma_chan *chan)dev_dbg (chan2dev (chan),"%s: Entry\n" , __func__); +dma_chan_kill_bh (&td_chan->chan); +/* check that all descriptors are free */ BUG_ON (!list_empty (&td_chan->active_list));BUG_ON (!list_empty (&td_chan->queue));
@@ -580,17 +582,25 @@ static void td_tasklet(struct tasklet_struct *t)for (i =0 ; i < td->dma.chancnt; i++)if (ipr & (1 << i)) {struct timb_dma_chan *td_chan = td->channels + i; -spin_lock (&td_chan->lock); -__td_finish (td_chan); -if (!list_empty (&td_chan->queue)) -__td_start_next (td_chan); -spin_unlock (&td_chan->lock); +dma_chan_schedule_bh (&td_chan->chan); } ier =__td_ier_mask (td);iowrite32 (ier, td->membase +TIMBDMA_IER ); } +static void timb_dma_chan_bh (struct dma_chan *chan) +{ + struct timb_dma_chan *td_chan = container_of(chan, struct timb_dma_chan, + chan); + +spin_lock (&td_chan->lock); +__td_finish (td_chan); +if (!list_empty (&td_chan->queue)) +__td_start_next (td_chan); +spin_unlock (&td_chan->lock); +} +static irqreturn_t td_irq (int irq,void *devid) {
@@ -697,6 +707,7 @@ static int td_probe(struct platform_device *pdev)INIT_LIST_HEAD (&td_chan->active_list);INIT_LIST_HEAD (&td_chan->queue);INIT_LIST_HEAD (&td_chan->free_list); +dma_chan_init_bh (&td_chan->chan, timb_dma_chan_bh); td_chan->descs = pchan->descriptors; td_chan->desc_elems = pchan->descriptor_elements;
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 23/64] dmaengine: timb_dma: route callbacks via channel BH
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Data race and out-of-bounds read in __td_ier_mask due to lockless traversal of active_list racing with asynchronous workqueue.
Pre-existing issues:
- [High] Premature completion of active transfers due to a race between td_issue_pending and timb_dma_chan_bh. --
--- Patch [23]: [PATCH v2 23/64] dmaengine: timb_dma: route callbacks via channel BH --- commit 2a07cb9b339578f9caa75d42037da515bda38de2
Author: Allen Pais <[email protected]>
dmaengine: timb_dma: route callbacks via channel BH
This patch schedules per-channel BH work for completions so callbacks run through the dma_chan deferral mechanism while keeping the controller tasklet for IRQ handling.
Quoted 3 lines
drivers/dma/timb_dma.c +0
-0
[ ... ]
drivers/dma/timb_dma.c static void td_tasklet(struct tasklet_struct *t) · line 588
Quoted 13 lines
drivers/dma/timb_dma.c +1
-5@@ -580,17 +582,25 @@ static void td_tasklet(struct tasklet_struct *t)for (i =0 ; i < td->dma.chancnt; i++)if (ipr & (1 << i)) {struct timb_dma_chan *td_chan = td->channels + i; -spin_lock (&td_chan->lock); -__td_finish (td_chan); -if (!list_empty (&td_chan->queue)) -__td_start_next (td_chan); -spin_unlock (&td_chan->lock); +dma_chan_schedule_bh (&td_chan->chan); } ier =__td_ier_mask (td);
[Severity: High]
Can this lead to an out-of-bounds memory read?
With completion processing moved to an asynchronous workqueue via dma_chan_schedule_bh(), td_tasklet() and timb_dma_chan_bh() can now run concurrently. The tasklet calls __td_ier_mask() right here, which locklessly evaluates td_chan->ongoing and traverses td_chan->active_list.
If __td_ier_mask() reads ongoing as true right before the workqueue removes the final descriptor from active_list in __td_finish(), wouldn't list_entry() calculate a bogus pointer relative to the empty list head? This could cause a read of unmapped memory or garbage when accessing td_desc->interrupt.
drivers/dma/timb_dma.c static void td_tasklet(struct tasklet_struct *t) · line 598
Quoted 10 lines
drivers/dma/timb_dma.c +7
-0iowrite32 (ier, td->membase +TIMBDMA_IER ); } +static void timb_dma_chan_bh (struct dma_chan *chan) +{ + struct timb_dma_chan *td_chan = container_of(chan, struct timb_dma_chan, + chan); + +spin_lock (&td_chan->lock); +__td_finish (td_chan);
[Severity: High]
This is a pre-existing issue, but does moving to an asynchronous workqueue widen a race condition here?
Currently, if td_issue_pending() races with a hardware interrupt, it can acknowledge the interrupt and complete the transfer before the completion path runs.
If td_issue_pending() also started the next transfer, this newly scheduled timb_dma_chan_bh() workqueue will blindly call __td_finish() and complete whatever is currently in the active_list. By changing the completion from a tasklet (which had a very short race window) to a scheduled workqueue, the delay is much longer. Will this result in erroneously aborting and completing the newly started transfer?
Quoted 4 lines
drivers/dma/timb_dma.c +4
-0+if (!list_empty (&td_chan->queue)) +__td_start_next (td_chan); +spin_unlock (&td_chan->lock); +}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=23
PATCH v2 24/64 dmaengine: txx9dmac: route callbacks via channel BH
In reply to Allen PaisUse the per-channel BH helpers for descriptor completion handling and schedule per-channel work from both channel and controller IRQ paths.
--- drivers/dma/txx9dmac.c | 12 ++++++------ drivers/dma/txx9dmac.h | 1 - 2 files changed, 6 insertions(+), 7 deletions(-)
drivers/dma/txx9dmac.c +6 -6
@@ -601,13 +601,13 @@ static void txx9dmac_scan_descriptors(struct txx9dmac_chan *dc) } } -static void txx9dmac_chan_tasklet (struct tasklet_struct *t) +static void txx9dmac_chan_tasklet (struct dma_chan *chan) {int irq;u32 csr;struct txx9dmac_chan *dc; - dc =from_tasklet (dc, t, tasklet); + dc = container_of(chan, struct txx9dmac_chan, chan); csr =channel_readl (dc,CSR );dev_vdbg (chan2dev (&dc->chan),"tasklet: status=%x\n" , csr);
@@ -628,7 +628,7 @@ static irqreturn_t txx9dmac_chan_interrupt(int irq, void *dev_id)dev_vdbg (chan2dev (&dc->chan),"interrupt: status=%#x\n" ,channel_readl (dc,CSR )); -tasklet_schedule (&dc->tasklet); +dma_chan_schedule_bh (&dc->chan);/* * Just disable the interrupts. We'll turn them back on in the * softirq handler.
@@ -659,7 +659,7 @@ static void txx9dmac_tasklet(struct tasklet_struct *t)spin_lock (&dc->lock);if (csr & (TXX9_DMA_CSR_ABCHC |TXX9_DMA_CSR_NCHNC |TXX9_DMA_CSR_NTRNFC )) -txx9dmac_scan_descriptors (dc); +dma_chan_schedule_bh (&dc->chan);spin_unlock (&dc->lock); } }
@@ -1113,7 +1113,7 @@ static int __init txx9dmac_chan_probe(struct platform_device *pdev) irq =platform_get_irq (pdev,0 );if (irq <0 )return irq; -tasklet_setup (&dc->tasklet, txx9dmac_chan_tasklet); +dma_chan_init_bh (&dc->chan, txx9dmac_chan_tasklet); dc->irq = irq; err =devm_request_irq (&pdev->dev, dc->irq, txx9dmac_chan_interrupt,0 ,dev_name (&pdev->dev), dc);
@@ -1159,7 +1159,7 @@ static void txx9dmac_chan_remove(struct platform_device *pdev)dma_async_device_unregister (&dc->dma);if (dc->irq >=0 ) {devm_free_irq (&pdev->dev, dc->irq, dc); -tasklet_kill (&dc->tasklet); +dma_chan_kill_bh (&dc->chan); } dc->ddev->chan[pdev->id %TXX9_DMA_MAX_NR_CHANNELS ] =NULL ; }
drivers/dma/txx9dmac.h +0 -1
@@ -162,7 +162,6 @@ struct txx9dmac_chan {struct dma_device dma;struct txx9dmac_dev *ddev;void __iomem *ch_regs; -struct tasklet_struct tasklet;int irq;u32 ccr;
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 24/64] dmaengine: txx9dmac: route callbacks via channel BH
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] Uninitialized BH work and missing cleanup in shared IRQ mode - [High] Interrupt storm and livelock…
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Uninitialized BH work and missing cleanup in shared IRQ mode
- [High] Interrupt storm and livelock due to premature IRQ unmasking in shared IRQ mode
- [High] Invalid enable_irq() parameter in shared IRQ mode
--
--- Patch [24]: [PATCH v2 24/64] dmaengine: txx9dmac: route callbacks via channel BH --- commit ba661d7c1d6f85fdbcef7fb4031ee54ec6ee5c5a
Author: Allen Pais <[email protected]>
dmaengine: txx9dmac: route callbacks via channel BH
Use the per-channel BH helpers for descriptor completion handling and schedule per-channel work from both channel and controller IRQ paths.
Quoted 10 lines
drivers/dma/txx9dmac.c +1
-1@@ -601,13 +601,13 @@ static void txx9dmac_scan_descriptors(struct txx9dmac_chan *dc) } } -static void txx9dmac_chan_tasklet (struct tasklet_struct *t) +static void txx9dmac_chan_tasklet (struct dma_chan *chan)
drivers/dma/txx9dmac.c static void txx9dmac_scan_descriptors(struct txx9dmac_chan *dc) · line 613
Quoted 10 lines
drivers/dma/txx9dmac.c +1
-1{int irq;u32 csr;struct txx9dmac_chan *dc; - dc =from_tasklet (dc, t, tasklet); + dc = container_of(chan, struct txx9dmac_chan, chan); csr =channel_readl (dc,CSR );dev_vdbg (chan2dev (&dc->chan),"tasklet: status=%x\n" , csr);
[Severity: High]
Does this code handle shared IRQs correctly?
When running in shared IRQ mode, dc->irq is set to -1. Now that the shared controller path schedules this channel BH, txx9dmac_chan_tasklet() will run and unconditionally call enable_irq(dc->irq) at the end of the function:
irq = dc->irq;
enable_irq(irq);Could this end up calling enable_irq(-1) and causing an issue in the core IRQ code?
[ ... ]
drivers/dma/txx9dmac.c static void txx9dmac_tasklet(struct tasklet_struct *t) · line 665
Quoted 9 lines
drivers/dma/txx9dmac.c +1
-1@@ -659,7 +659,7 @@ static void txx9dmac_tasklet(struct tasklet_struct *t)spin_lock (&dc->lock);if (csr & (TXX9_DMA_CSR_ABCHC |TXX9_DMA_CSR_NCHNC |TXX9_DMA_CSR_NTRNFC )) -txx9dmac_scan_descriptors (dc); +dma_chan_schedule_bh (&dc->chan);spin_unlock (&dc->lock); } }
[Severity: High]
Could this result in an interrupt storm?
Previously, txx9dmac_scan_descriptors() would synchronously clear the hardware interrupt condition before the shared tasklet unmasked the IRQ.
Now, the work is deferred to the async BH handler, but enable_irq(irq) is still called synchronously at the end of txx9dmac_tasklet():
irq = ddev->irq;
enable_irq(irq);Will the unmasked IRQ immediately fire again before the BH has a chance to run and clear the hardware interrupt condition?
[ ... ]
drivers/dma/txx9dmac.c static int __init txx9dmac_chan_probe(struct platform_device *pdev) · line 1119
Quoted 9 lines
drivers/dma/txx9dmac.c +1
-1@@ -1113,7 +1113,7 @@ static int __init txx9dmac_chan_probe(struct platform_device *pdev) irq =platform_get_irq (pdev,0 );if (irq <0 )return irq; -tasklet_setup (&dc->tasklet, txx9dmac_chan_tasklet); +dma_chan_init_bh (&dc->chan, txx9dmac_chan_tasklet); dc->irq = irq; err =devm_request_irq (&pdev->dev, dc->irq, txx9dmac_chan_interrupt,0 ,dev_name (&pdev->dev), dc);
[Severity: High]
Is the BH work properly initialized when operating in shared IRQ mode?
dma_chan_init_bh() is only called if dc->ddev->irq < 0:
if (dc->ddev->irq < 0) {
...
dma_chan_init_bh(&dc->chan, txx9dmac_chan_tasklet);
...
} else {
dc->irq = -1;
}If the controller uses a shared IRQ, the BH work remains uninitialized. When the shared controller tasklet attempts to schedule it, will it trigger a WARN_ON(!chan->bh_work_initialized) and drop the completion request entirely?
[ ... ]
drivers/dma/txx9dmac.c static void txx9dmac_chan_remove(struct platform_device *pdev) · line 1165
Quoted 9 lines
drivers/dma/txx9dmac.c +1
-1@@ -1159,7 +1159,7 @@ static void txx9dmac_chan_remove(struct platform_device *pdev)dma_async_device_unregister (&dc->dma);if (dc->irq >=0 ) {devm_free_irq (&pdev->dev, dc->irq, dc); -tasklet_kill (&dc->tasklet); +dma_chan_kill_bh (&dc->chan); } dc->ddev->chan[pdev->id %TXX9_DMA_MAX_NR_CHANNELS ] =NULL ; }
[Severity: High]
Could we leak resources here in shared IRQ mode?
Similar to the probe function, the cleanup relies on dc->irq >= 0. If the initialization issue above is fixed so the BH work is correctly used in shared IRQ mode, dma_chan_kill_bh() would still be skipped here since dc->irq is -1.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=24
PATCH v2 25/64 dmaengine: mv_xor_v2: use channel BH helpers
In reply to Allen PaisReplace the device tasklet with the per-channel BH helper so descriptor callbacks run through dma_chan_*_bh.
--- drivers/dma/mv_xor_v2.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-)
drivers/dma/mv_xor_v2.c +9 -11
@@ -139,7 +139,6 @@ struct mv_xor_v2_descriptor {* @reg_clk: reference to the 'reg' clock * @dma_base: memory mapped DMA register base * @glob_base: memory mapped global register base -* @irq_tasklet: tasklet used for IRQ handling call-backs * @free_sw_desc: linked list of free SW descriptors * @dmadev: dma device * @dmachan: dma channel
@@ -158,7 +157,6 @@ struct mv_xor_v2_device {void __iomem *glob_base;struct clk *clk;struct clk *reg_clk; -struct tasklet_struct irq_tasklet;struct list_head free_sw_desc;struct dma_device dmadev;struct dma_chan dmachan;
@@ -290,8 +288,8 @@ static irqreturn_t mv_xor_v2_interrupt_handler(int irq, void *data)if (!ndescs)return IRQ_NONE ; -/* schedule a tasklet to handle descriptors callbacks */ -tasklet_schedule (&xor_dev->irq_tasklet); +/* schedule a bottom half to handle descriptors callbacks */ +dma_chan_schedule_bh (&xor_dev->dmachan);return IRQ_HANDLED ; }
@@ -346,8 +344,8 @@ mv_xor_v2_prep_sw_desc(struct mv_xor_v2_device *xor_dev)if (list_empty (&xor_dev->free_sw_desc)) {spin_unlock_bh (&xor_dev->lock); -/* schedule tasklet to free some descriptors */ -tasklet_schedule (&xor_dev->irq_tasklet); +/* schedule bottom half to free some descriptors */ +dma_chan_schedule_bh (&xor_dev->dmachan);return NULL ; }
@@ -553,10 +551,10 @@ int mv_xor_v2_get_pending_params(struct mv_xor_v2_device *xor_dev,/* * handle the descriptors after HW process */ -static void mv_xor_v2_tasklet (struct tasklet_struct *t) +static void mv_xor_v2_tasklet (struct dma_chan *chan) { -struct mv_xor_v2_device *xor_dev =from_tasklet (xor_dev, t, - irq_tasklet); +struct mv_xor_v2_device *xor_dev = + container_of(chan, struct mv_xor_v2_device, dmachan);int pending_ptr, num_of_pending, i;struct mv_xor_v2_sw_desc *next_pending_sw_desc =NULL ;
@@ -760,7 +758,7 @@ static int mv_xor_v2_probe(struct platform_device *pdev)if (ret) goto free_msi_irqs; -tasklet_setup (&xor_dev->irq_tasklet, mv_xor_v2_tasklet); +dma_chan_init_bh (&xor_dev->dmachan, mv_xor_v2_tasklet); xor_dev->desc_size =mv_xor_v2_set_desc_size (xor_dev);
@@ -869,7 +867,7 @@ static void mv_xor_v2_remove(struct platform_device *pdev)platform_device_msi_free_irqs_all (&pdev->dev); -tasklet_kill (&xor_dev->irq_tasklet); +dma_chan_kill_bh (&xor_dev->dmachan); }#ifdef CONFIG_OF
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 25/64] dmaengine: mv_xor_v2: use channel BH helpers
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] Incorrect teardown order in remove() leading to Use-After-Free.
- [High] Missing dma_chan_kill_bh() in probe error path leads to Use-After-Free.
- [High] Early interrupt request in probe allows uninitialized state access. --
--- Patch [25]: [PATCH v2 25/64] dmaengine: mv_xor_v2: use channel BH helpers --- commit 1d8bd96829601184f429fc1fa7947ce7a05990e9
Author: Allen Pais <[email protected]>
dmaengine: mv_xor_v2: use channel BH helpers
This patch replaces the device tasklet with the per-channel BH helper. Descriptor callbacks are updated to run through dma_chan_*_bh instead of the tasklet API.
Quoted 4 lines
drivers/dma/mv_xor_v2.c +0
-0
[ ... ]
drivers/dma/mv_xor_v2.c static int mv_xor_v2_probe(struct platform_device *pdev) · line 761
Quoted 6 lines
drivers/dma/mv_xor_v2.c +1
-1@@ -760,7 +758,7 @@ static int mv_xor_v2_probe(struct platform_device *pdev)if (ret) goto free_msi_irqs; -tasklet_setup (&xor_dev->irq_tasklet, mv_xor_v2_tasklet); +dma_chan_init_bh (&xor_dev->dmachan, mv_xor_v2_tasklet);
[Severity: High]
This is a pre-existing issue, but does requesting the interrupt before initializing the bottom-half allow an early interrupt to crash the system?
In mv_xor_v2_probe(), devm_request_irq() requests and enables the interrupt before dma_chan_init_bh() and dma_alloc_coherent() are called. If the hardware is left in an active state with pending events, an interrupt will fire immediately.
The handler mv_xor_v2_interrupt_handler() calls dma_chan_schedule_bh(). Depending on the timing, this could crash trying to schedule an uninitialized work struct, or the scheduled work could run and dereference a NULL hw_desq_virt pointer.
[Severity: High]
This is a pre-existing issue, but is a call to dma_chan_kill_bh() missing in the probe error paths?
If a setup step fails after initialization, such as dma_alloc_coherent() failing, the probe function jumps to the error labels and returns without calling dma_chan_kill_bh().
If an early interrupt had already fired and scheduled the bottom-half, the work item remains active in the workqueue. When devres automatically frees the xor_dev structure containing the work item, could the executing workqueue corrupt memory?
Quoted 2 lines
drivers/dma/mv_xor_v2.c +0
-0xor_dev->desc_size =mv_xor_v2_set_desc_size (xor_dev);
[ ... ]
drivers/dma/mv_xor_v2.c static void mv_xor_v2_remove(struct platform_device *pdev) · line 871
Quoted 7 lines
drivers/dma/mv_xor_v2.c +1
-1@@ -869,7 +867,7 @@ static void mv_xor_v2_remove(struct platform_device *pdev)platform_device_msi_free_irqs_all (&pdev->dev); -tasklet_kill (&xor_dev->irq_tasklet); +dma_chan_kill_bh (&xor_dev->dmachan); }
[Severity: High]
This is a pre-existing issue, but is the teardown order in mv_xor_v2_remove() safe from use-after-free races?
In mv_xor_v2_remove(), dma_free_coherent() is called to free the hardware descriptor ring (hw_desq_virt) before the device interrupt is disabled via devm_free_irq() and before the bottom-half is killed via dma_chan_kill_bh().
If a pending hardware interrupt fires during this window, or if a previously scheduled bottom-half is running concurrently, the executing bottom-half could dereference the freed hw_desq_virt memory:
mv_xor_v2_tasklet() {
...
struct mv_xor_v2_descriptor *next_pending_hw_desc =
xor_dev->hw_desq_virt + pending_ptr;
...
}Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=25
PATCH v2 26/64 dmaengine: mpc512x: route callbacks via channel BH
In reply to Allen PaisSchedule per-channel BH work for completions and process callbacks there, leaving the controller tasklet for IRQ/error handling.
--- drivers/dma/mpc512x_dma.c | 74 +++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 27 deletions(-)
drivers/dma/mpc512x_dma.c +47 -27
@@ -372,45 +372,47 @@ static irqreturn_t mpc_dma_irq(int irq, void *data)return IRQ_HANDLED ; } -/* process completed descriptors */ -static void mpc_dma_process_completed (struct mpc_dma *mdma) +static void mpc_dma_process_chan_completed (struct mpc_dma_chan *mchan) {dma_cookie_t last_cookie =0 ; -struct mpc_dma_chan *mchan;struct mpc_dma_desc *mdesc;struct dma_async_tx_descriptor *desc;unsigned long flags;LIST_HEAD (list); -int i; -for (i =0 ; i < mdma->dma.chancnt; i++) { - mchan = &mdma->channels[i]; +/* Get all completed descriptors */ +spin_lock_irqsave (&mchan->lock, flags); +if (!list_empty (&mchan->completed)) +list_splice_tail_init (&mchan->completed, &list); +spin_unlock_irqrestore (&mchan->lock, flags); -/* Get all completed descriptors */ -spin_lock_irqsave (&mchan->lock, flags); -if (!list_empty (&mchan->completed)) -list_splice_tail_init (&mchan->completed, &list); -spin_unlock_irqrestore (&mchan->lock, flags); +if (list_empty (&list)) +return ; -if (list_empty (&list)) -continue ; +/* Execute callbacks and run dependencies */ +list_for_each_entry (mdesc, &list, node) { + desc = &mdesc->desc; -/* Execute callbacks and run dependencies */ -list_for_each_entry (mdesc, &list, node) { - desc = &mdesc->desc; +dmaengine_desc_get_callback_invoke (desc,NULL ); -dmaengine_desc_get_callback_invoke (desc,NULL ); + last_cookie = desc->cookie; +dma_run_dependencies (desc); + } - last_cookie = desc->cookie; -dma_run_dependencies (desc); - } +/* Free descriptors */ +spin_lock_irqsave (&mchan->lock, flags); +list_splice_tail_init (&list, &mchan->free); + mchan->chan.completed_cookie = last_cookie; +spin_unlock_irqrestore (&mchan->lock, flags); +} -/* Free descriptors */ -spin_lock_irqsave (&mchan->lock, flags); -list_splice_tail_init (&list, &mchan->free); - mchan->chan.completed_cookie = last_cookie; -spin_unlock_irqrestore (&mchan->lock, flags); - } +/* process completed descriptors */ +static void mpc_dma_process_completed (struct mpc_dma *mdma) +{ +int i; + +for (i =0 ; i < mdma->dma.chancnt; i++) +mpc_dma_process_chan_completed (&mdma->channels[i]); }/* DMA Tasklet */
@@ -419,6 +421,7 @@ static void mpc_dma_tasklet(struct tasklet_struct *t)struct mpc_dma *mdma =from_tasklet (mdma, t, tasklet);unsigned long flags;uint es; +int i;spin_lock_irqsave (&mdma->error_status_lock, flags); es = mdma->error_status;
@@ -453,7 +456,21 @@ static void mpc_dma_tasklet(struct tasklet_struct *t)dev_err (mdma->dma.dev,"- Destination Bus Error\n" ); } -mpc_dma_process_completed (mdma); +for (i =0 ; i < mdma->dma.chancnt; i++) { +struct mpc_dma_chan *mchan = &mdma->channels[i]; + +spin_lock_irqsave (&mchan->lock, flags); +if (!list_empty (&mchan->completed)) +dma_chan_schedule_bh (&mchan->chan); +spin_unlock_irqrestore (&mchan->lock, flags); + } +} + +static void mpc_dma_chan_bh (struct dma_chan *chan) +{ +struct mpc_dma_chan *mchan =dma_chan_to_mpc_dma_chan (chan); + +mpc_dma_process_chan_completed (mchan); }/* Submit descriptor to hardware */
@@ -551,6 +568,8 @@ static void mpc_dma_free_chan_resources(struct dma_chan *chan)unsigned long flags;LIST_HEAD (descs); +dma_chan_kill_bh (&mchan->chan); +spin_lock_irqsave (&mchan->lock, flags);/* Channel must be idle */
@@ -1007,6 +1026,7 @@ static int mpc_dma_probe(struct platform_device *op)INIT_LIST_HEAD (&mchan->completed);spin_lock_init (&mchan->lock); +dma_chan_init_bh (&mchan->chan, mpc_dma_chan_bh);list_add_tail (&mchan->chan.device_node, &dma->channels); }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 26/64] dmaengine: mpc512x: route callbacks via channel BH
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
New issues:
- [High] Missing dma_chan_kill_bh() in mpc_dma_remove() allows per-channel bottom-half workqueue items to outlive the device structures.
Pre-existing issues:
- [High] The error handling path in mpc_dma_probe() fails to cancel the tasklet and BH work after the IRQ is registered.
- [High] Concurrent execution of mpc_dma_process_chan_completed() can cause a channel's completed_cookie to regress.
- [High] request_irq() is called before channel locks, lists, and tasklets are initialized.
- [High] Releasing a channel with pending completions triggers a BUG_ON() panic due to missing device_synchronize().
- [High] mpc_dma_free_chan_resources() can race with the global fallback mpc_dma_process_completed(), leading to a Use-After-Free of DMA descriptors.
- [High] The driver state structure allocated with devm_kzalloc() is freed prematurely if the driver is unbound while clients hold channel references. --
--- Patch [26]: [PATCH v2 26/64] dmaengine: mpc512x: route callbacks via channel BH --- commit 57dd5b81d14732d0a29ee229ec6a57e6e3b79c4c
Author: Allen Pais <[email protected]>
Subject: dmaengine: mpc512x: route callbacks via channel BH
This commit transitions the mpc512x DMA driver from processing completions entirely in the controller tasklet to scheduling per-channel bottom-half (BH) work. This leaves the tasklet strictly for IRQ and error handling.
Quoted 4 lines
drivers/dma/mpc512x_dma.c +0
-0
[ ... ]
Quoted 2 lines
drivers/dma/mpc512x_dma.c +2
-0+static void mpc_dma_process_chan_completed (struct mpc_dma_chan *mchan) +{
[ ... ]
drivers/dma/mpc512x_dma.c static irqreturn_t mpc_dma_irq(int irq, void *data) · line 407
Quoted 6 lines
drivers/dma/mpc512x_dma.c +6
-0+/* Free descriptors */ +spin_lock_irqsave (&mchan->lock, flags); +list_splice_tail_init (&list, &mchan->free); + mchan->chan.completed_cookie = last_cookie; +spin_unlock_irqrestore (&mchan->lock, flags); +}
[Severity: High]
This is a pre-existing issue, but does this unconditional overwrite of the completed cookie leave us vulnerable to race conditions?
Since mpc_dma_process_completed() can be called synchronously from mpc_dma_prep_memcpy(), it can race with the asynchronous processing of the same channel's completions.
If CPU 1 grabs an older batch of completed descriptors and CPU 2 grabs a newer batch, CPU 2 might finish executing callbacks first. When CPU 1 finishes, it overwrites mchan->chan.completed_cookie with its older last_cookie, causing the cookie to regress.
[Severity: High]
This is a pre-existing issue, but could this list splicing race with channel freeing and cause a use-after-free?
When a client runs out of descriptors, mpc_dma_process_completed() iterates globally and extracts completed descriptors for another channel.
While the callbacks are executing outside the lock, another client could concurrently free that channel via mpc_dma_free_chan_resources(), freeing the coherent TCD memory.
After callbacks finish, splicing the local list back into mchan->free puts leaked descriptors pointing to freed memory back into circulation.
[ ... ]
drivers/dma/mpc512x_dma.c
Quoted 13 lines
drivers/dma/mpc512x_dma.c +2
-0@@ -551,6 +568,8 @@ static void mpc_dma_free_chan_resources(struct dma_chan *chan)unsigned long flags;LIST_HEAD (descs); +dma_chan_kill_bh (&mchan->chan); +spin_lock_irqsave (&mchan->lock, flags);/* Channel must be idle */ drivers/dma/mpc512x_dma.c +0
-0BUG_ON (!list_empty (&mchan->prepared));BUG_ON (!list_empty (&mchan->queued));BUG_ON (!list_empty (&mchan->active));BUG_ON (!list_empty (&mchan->completed));
[Severity: High]
This is a pre-existing issue, but can this trigger a kernel panic due to missing synchronization?
When a client terminates a transfer, device_terminate_all() is called, but it ignores mchan->completed. The core expects device_synchronize() to wait for pending async tasks, but this driver doesn't implement it.
If mpc_dma_free_chan_resources() is called with pending completions, the subsequent assertions like BUG_ON(!list_empty(&mchan->completed)) will trigger a panic.
[ ... ]
drivers/dma/mpc512x_dma.c static int mpc_dma_probe(struct platform_device *op) · line 1031
Quoted 7 lines
drivers/dma/mpc512x_dma.c +1
-0@@ -1007,6 +1026,7 @@ static int mpc_dma_probe(struct platform_device *op)INIT_LIST_HEAD (&mchan->completed);spin_lock_init (&mchan->lock); +dma_chan_init_bh (&mchan->chan, mpc_dma_chan_bh);list_add_tail (&mchan->chan.device_node, &dma->channels); }
[Severity: High]
Is there a missing cleanup step for these bottom-half work items when the driver is removed?
The teardown function mpc_dma_remove() correctly calls tasklet_kill(), but does not cancel or flush these newly added BH work items.
If the driver is unbound while active:
mpc_dma_irq()
tasklet_schedule(&mdma->tasklet);
mpc_dma_remove()
tasklet_kill(&mdma->tasklet);
The tasklet's new purpose is to schedule dma_chan_schedule_bh(). These work items will remain queued and execute asynchronously after the function returns. Since mdma is allocated via devm_kzalloc(), devres frees it immediately upon remove returning, causing the orphaned BH work to access freed memory.
[Severity: High]
This is a pre-existing issue, but does tying the controller structure lifecycle to the device binding risk a use-after-free?
In mpc_dma_probe(), the struct mpc_dma is allocated using devm_kzalloc(). If the driver is unbound (e.g., via sysfs), mpc_dma_remove() completes and devres automatically frees the allocation.
If a DMA client driver is still holding a reference to a channel embedded in this structure, any subsequent API calls or asynchronous callbacks on that channel will dereference the freed memory.
[Severity: High]
This is a pre-existing issue, but does the error handling path in mpc_dma_probe() need to cancel the scheduled tasklet and BH?
If a late initialization step (like dma_async_device_register) fails, the code jumps to the error labels and frees the IRQ:
err_free2:
if (mdma->is_mpc8308)
free_irq(mdma->irq2, mdma);
err_free1:
free_irq(mdma->irq, mdma);However, it fails to call tasklet_kill() or dma_chan_kill_bh(). If an interrupt fired and scheduled the tasklet or BH, they will continue to run after probe returns and devres frees the mdma structure, leading to a use-after-free.
[Severity: High]
This is a pre-existing issue, but is it safe to request IRQs before the device structures are fully initialized?
In mpc_dma_probe(), request_irq() is invoked before the channel lists, spinlocks, and tasklets are set up.
If the hardware asserts an interrupt immediately, mpc_dma_irq() will run and attempt to lock mdma->error_status_lock (uninitialized), and schedule mdma->tasklet (uninitialized), crashing the kernel.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=26
PATCH v2 27/64 dmaengine: k3dma: kill vchan BH on remove
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma cleanup now that vchan uses the per-channel BH helper.
--- drivers/dma/k3dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/k3dma.c +1 -1
@@ -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); +dma_chan_kill_bh (&c->vc.chan); }tasklet_kill (&d->task);clk_disable_unprepare (d->clk);
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 27/64] dmaengine: k3dma: kill vchan BH on remove
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Concurrent execution of k3_dma_tasklet and k3_dma_remove leads to list corruption and a LIST_POISON1 kernel panic. --
--- Patch [27]: [PATCH v2 27/64] dmaengine: k3dma: kill vchan BH on remove --- commit 0a41ab53724cb23d95bb02f7dd926541fc428dd1
Author: Allen Pais <[email protected]>
dmaengine: k3dma: kill vchan BH on remove
This commit updates virt-dma cleanup in the k3dma driver. It switches from using tasklet_kill() to dma_chan_kill_bh() during driver removal, reflecting that vchan now uses the per-channel BH helper.
drivers/dma/k3dma.c static void k3_dma_remove(struct platform_device *op) · line 978
Quoted 8 lines
drivers/dma/k3dma.c +0
-0@@ -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);
[Severity: High]
This is a pre-existing issue, but can this sequence cause a list corruption and kernel panic if a hardware tasklet runs concurrently?
If an interrupt fires and schedules d->task just before devm_free_irq() runs, k3_dma_tasklet() could run concurrently and iterate over d->slave.channels locklessly:
drivers/dma/k3dma.c:k3_dma_tasklet() {
...
list_for_each_entry_safe(c, cn, &d->slave.channels, vc.chan.device_node) {
spin_lock_irq(&c->vc.lock);
p = c->phy;
...
}Concurrently, k3_dma_remove() calls list_del() on the channel nodes before calling tasklet_kill(&d->task) below. If k3_dma_tasklet() fetches a deleted node, will it see LIST_POISON1 in the next pointer and panic on the next iteration?
Could tasklet_kill(&d->task) be moved before the channel list modification to ensure the tasklet is stopped first?
Quoted 4 lines
drivers/dma/k3dma.c +1
-1-tasklet_kill (&c->vc.task); +dma_chan_kill_bh (&c->vc.chan); }tasklet_kill (&d->task);
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=27
PATCH v2 28/64 dmaengine: plx_dma: use channel BH helpers
In reply to Allen PaisReplace the device tasklet with the per-channel BH helper so completion callbacks run via dma_chan_*_bh.
--- drivers/dma/plx_dma.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
drivers/dma/plx_dma.c +6 -6
@@ -105,7 +105,6 @@ struct plx_dma_dev {struct dma_chan dma_chan;struct pci_dev __rcu *pdev;void __iomem *bar; -struct tasklet_struct desc_task;spinlock_t ring_lock;bool ring_active;
@@ -241,9 +240,10 @@ static void plx_dma_stop(struct plx_dma_dev *plxdev)rcu_read_unlock (); } -static void plx_dma_desc_task (struct tasklet_struct *t) +static void plx_dma_desc_task (struct dma_chan *chan) { -struct plx_dma_dev *plxdev =from_tasklet (plxdev, t, desc_task); + struct plx_dma_dev *plxdev = container_of(chan, struct plx_dma_dev, + dma_chan);plx_dma_process_desc (plxdev); }
@@ -366,7 +366,7 @@ static irqreturn_t plx_dma_isr(int irq, void *devid)return IRQ_NONE ;if (status &PLX_REG_INTR_STATUS_DESC_DONE && plxdev->ring_active) -tasklet_schedule (&plxdev->desc_task); +dma_chan_schedule_bh (&plxdev->dma_chan);writew (status, plxdev->bar +PLX_REG_INTR_STATUS );
@@ -471,7 +471,7 @@ static void plx_dma_free_chan_resources(struct dma_chan *chan)if (irq >0 )synchronize_irq (irq); -tasklet_kill (&plxdev->desc_task); +dma_chan_kill_bh (&plxdev->dma_chan);plx_dma_abort_desc (plxdev);
@@ -510,7 +510,7 @@ static int plx_dma_create(struct pci_dev *pdev) goto free_plx;spin_lock_init (&plxdev->ring_lock); -tasklet_setup (&plxdev->desc_task, plx_dma_desc_task); +dma_chan_init_bh (&plxdev->dma_chan, plx_dma_desc_task);RCU_INIT_POINTER (plxdev->pdev, pdev); plxdev->bar =pcim_iomap_table (pdev)[0 ];
Signature · 1 line
2.43.0
Re: [PATCH v2 28/64] dmaengine: plx_dma: use channel BH helpers
Quoted 4 lines · Allen Pais
Replace the device tasklet with the per-channel BH helper so completion callbacks run via dma_chan_*_bh.
Signed-off-by Allen Pais <[email protected]>
Looks good to me, thanks.
[email protected]
AI reviewRe: [PATCH v2 28/64] dmaengine: plx_dma: use channel BH helpers
In reply to Allen PaisThank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Pre-existing issues:
- [High] Deadlock risk due to invoking DMA client callbacks with a spinlock held.
- [High] Deadlock risk due to inadequate lock context (plain spin_lock instead of spin_lock_bh) exposing the lock to softirq preemption.
- [High] Unbounded spinning (1000ms busy-wait) inside an RCU read-side critical section.
- [High] plx_dma_prep_memcpy() acquires a spinlock and returns with it held, violating locking and API rules.
- [High] The device IRQ is requested before the MMIO base pointer (plxdev->bar) is initialized, causing a potential NULL pointer dereference. --
--- Patch [28]: [PATCH v2 28/64] dmaengine: plx_dma: use channel BH helpers --- commit 978a06c861c9b53f0d972a684863de23c7d55eb8
Author: Allen Pais <[email protected]>
dmaengine: plx_dma: use channel BH helpers
Replace the device tasklet with the per-channel BH helper so completion callbacks run via dma_chan_*_bh.
Quoted 4 lines
drivers/dma/plx_dma.c +0
-0
[ ... ]
drivers/dma/plx_dma.c static void plx_dma_stop(struct plx_dma_dev *plxdev) · line 241
Quoted 3 lines
drivers/dma/plx_dma.c +0
-0@@ -241,9 +240,10 @@ static void plx_dma_stop(struct plx_dma_dev *plxdev)rcu_read_unlock (); }
[Severity: High]
This isn't a bug introduced by this patch, but can this busy-wait loop cause issues within the RCU read-side critical section?
plx_dma_stop() initiates an RCU read-side critical section and then calls __plx_dma_stop():
plx_dma_stop()
{
rcu_read_lock();
...
__plx_dma_stop(plxdev);
}__plx_dma_stop() then executes a busy-wait loop for up to 1000ms:
__plx_dma_stop()
{
while (!time_after(jiffies, timeout)) {
if (val & PLX_REG_CTRL_GRACEFUL_PAUSE_DONE)
break;
cpu_relax();
}
}Executing a 1-second busy loop inside an RCU read-side critical section could lead to severe scheduling latency or RCU stalls.
drivers/dma/plx_dma.c static void plx_dma_stop(struct plx_dma_dev *plxdev) · line 249
Quoted 9 lines
drivers/dma/plx_dma.c +3
-2-static void plx_dma_desc_task (struct tasklet_struct *t) +static void plx_dma_desc_task (struct dma_chan *chan) { -struct plx_dma_dev *plxdev =from_tasklet (plxdev, t, desc_task); + struct plx_dma_dev *plxdev = container_of(chan, struct plx_dma_dev, + dma_chan);plx_dma_process_desc (plxdev); }
[Severity: High]
This isn't a bug introduced by this patch, but could plx_dma_process_desc() lead to a deadlock?
A client DMA driver submitting a new DMA request from within its completion callback might cause a self-deadlock. The callback is invoked in plx_dma_process_desc() while holding the ring_lock:
plx_dma_process_desc()
{
spin_lock(&plxdev->ring_lock);
...
dmaengine_desc_get_callback_invoke(&desc->txd, &res);
}If the client's callback calls dmaengine_prep_dma_memcpy(), it will invoke plx_dma_prep_memcpy() which attempts to acquire the same ring_lock.
[Severity: High]
This is also a pre-existing issue, but does ring_lock need to be acquired with bottom halves disabled?
If a process context function like plx_dma_tx_status() calls plx_dma_process_desc() and acquires ring_lock without disabling bottom halves:
plx_dma_process_desc()
{
spin_lock(&plxdev->ring_lock);
}A hardware interrupt could fire, and the softirq handler would then execute plx_dma_desc_task() -> plx_dma_process_desc() on the same CPU, attempting to acquire the already-held ring_lock, which could result in a hard deadlock.
[ ... ]
drivers/dma/plx_dma.c static int plx_dma_create(struct pci_dev *pdev) · line 516
Quoted 9 lines
drivers/dma/plx_dma.c +1
-1@@ -510,7 +510,7 @@ static int plx_dma_create(struct pci_dev *pdev) goto free_plx;spin_lock_init (&plxdev->ring_lock); -tasklet_setup (&plxdev->desc_task, plx_dma_desc_task); +dma_chan_init_bh (&plxdev->dma_chan, plx_dma_desc_task);RCU_INIT_POINTER (plxdev->pdev, pdev); plxdev->bar =pcim_iomap_table (pdev)[0 ];
[Severity: High]
This isn't a bug introduced by this patch, but is there a race condition during initialization?
The interrupt handler is registered before the MMIO region pointer is initialized:
plx_dma_create()
{
rc = request_irq(pci_irq_vector(pdev, 0), plx_dma_isr, ...);
...
plxdev->bar = pcim_iomap_table(pdev)[0];
}If an interrupt fires immediately after request_irq(), plx_dma_isr() could read from plxdev->bar, which would still be an uninitialized pointer:
plx_dma_isr()
{
status = readw(plxdev->bar + PLX_REG_INTR_STATUS);
}[Severity: High]
This is another pre-existing issue outside the diff context, but does plx_dma_prep_memcpy() intentionally return with the ring_lock held?
plx_dma_prep_memcpy() acquires the lock but returns without releasing it:
plx_dma_prep_memcpy()
{
spin_lock_bh(&plxdev->ring_lock);
...
return &plxdesc->txd;
}The lock appears to only be released later in plx_dma_tx_submit(). If a client prepares multiple descriptors before submitting, it might self-deadlock trying to acquire the lock a second time. If it sleeps between prep and submit, it would be sleeping in an atomic context.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=28
PATCH v2 29/64 dmaengine: sf-pdma: route error callbacks through channel BH
In reply to Allen Paisvirt-dma now dispatches completion callbacks through per-channel BH work, but SF-PDMA invokes the client callback directly from its error tasklet after exhausting its retries.
Complete failed descriptors through virt-dma with an aborted result and the remaining transfer size, then start the next pending transfer. During removal, kill the producer tasklets before the channel BH so they cannot enqueue callback work after it has been canceled.
--- drivers/dma/sf-pdma/sf-pdma.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-)
drivers/dma/sf-pdma/sf-pdma.c +12 -2
@@ -328,7 +328,17 @@ static void sf_pdma_errbh_tasklet(struct tasklet_struct *t)if (chan->retries <=0 ) {/* fail to recover */ spin_unlock_irqrestore (&chan->lock, flags); -dmaengine_desc_get_callback_invoke (desc->async_tx,NULL ); + +spin_lock_irqsave (&chan->vchan.lock, flags); +list_del (&desc->vdesc.node); + desc->vdesc.tx_result.result =DMA_TRANS_ABORTED ; + desc->vdesc.tx_result.residue = desc->xfer_size; +vchan_cookie_complete (&desc->vdesc); + + chan->desc =sf_pdma_get_first_pending_desc (chan); +if (chan->desc) +sf_pdma_xfer_desc (chan); +spin_unlock_irqrestore (&chan->vchan.lock, flags); }else {/* retry */ chan->retries--;
@@ -602,9 +612,9 @@ 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);tasklet_kill (&ch->done_tasklet);tasklet_kill (&ch->err_tasklet); +dma_chan_kill_bh (&ch->vchan.chan); }if (pdev->dev.of_node)
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 29/64] dmaengine: sf-pdma: route error callbacks through channel BH
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
New issues:
- [Critical] Concurrent execution of errbh_tasklet and donebh_tasklet causes the driver to corrupt the active DMA hardware state.
- [High] Race condition between errbh_tasklet and donebh_tasklet causes a kernel panic via BUG_ON() during descriptor completion.
Pre-existing issues:
- [High] Double free of the active descriptor during channel termination or resource cleanup.
- [High] Memory and resource leak in sf_pdma_remove due to premature channel list removal.
- [High] Lockless read of chan->desc in sf_pdma_errbh_tasklet leads to use-after-free and virt-dma list corruption.
- [High] Missing NULL pointer check on chan->desc causes a kernel panic on concurrent termination or stray interrupts.
- [Medium] Residue reporting always returns 0 for actively running transfers.
--
--- Patch [29]: [PATCH v2 29/64] dmaengine: sf-pdma: route error callbacks through channel BH --- commit 4199e17e8ac9bf5b639472caa463955dc33436b1
Author: Allen Pais <[email protected]>
dmaengine: sf-pdma: route error callbacks through channel BH
This patch modifies the error handling path for SF-PDMA to route completions through the virt-dma per-channel bottom half instead of invoking callbacks directly from the error tasklet. It updates the error tasklet to complete the aborted descriptor using virt-dma primitives and start the next transfer. It also attempts to ensure tasklets are killed before the channel BH during removal.
drivers/dma/sf-pdma/sf-pdma.c static void sf_pdma_errbh_tasklet(struct tasklet_struct *t)
Quoted 5 lines
drivers/dma/sf-pdma/sf-pdma.c +0
-0@@ -328,7 +328,17 @@ static void sf_pdma_errbh_tasklet(struct tasklet_struct *t)
[Severity: High]
This is a pre-existing issue, but is there a risk of a use-after-free here? At the beginning of sf_pdma_errbh_tasklet(), we read the descriptor pointer outside of any lock:
sf_pdma_errbh_tasklet() {
...
struct sf_pdma_desc *desc = chan->desc;
unsigned long flags;
spin_lock_irqsave(&chan->lock, flags);
}If a client concurrently calls sf_pdma_terminate_all(), it frees chan->desc. Since the tasklet later unconditionally dereferences this cached pointer, could this corrupt memory?
drivers/dma/sf-pdma/sf-pdma.c static void sf_pdma_errbh_tasklet(struct tasklet_struct *t) · line 336
Quoted 10 lines
drivers/dma/sf-pdma/sf-pdma.c +6
-1if (chan->retries <=0 ) {/* fail to recover */ spin_unlock_irqrestore (&chan->lock, flags); -dmaengine_desc_get_callback_invoke (desc->async_tx,NULL ); + +spin_lock_irqsave (&chan->vchan.lock, flags); +list_del (&desc->vdesc.node); + desc->vdesc.tx_result.result =DMA_TRANS_ABORTED ; + desc->vdesc.tx_result.residue = desc->xfer_size; +vchan_cookie_complete (&desc->vdesc);
[Severity: High]
If both sf_pdma_donebh_tasklet() and sf_pdma_errbh_tasklet() run concurrently due to rapid sequential interrupts, can they race on this completion?
If the done tasklet acquires the lock first, it completes the descriptor and clears its cookie. When this error tasklet acquires the lock next, it will attempt to complete the exact same descriptor again. Will this double completion trigger a BUG_ON() in the core dmaengine code?
drivers/dma/sf-pdma/sf-pdma.c static void sf_pdma_errbh_tasklet(struct tasklet_struct *t) · line 340
Quoted 4 lines
drivers/dma/sf-pdma/sf-pdma.c +4
-0+ + chan->desc =sf_pdma_get_first_pending_desc (chan); +if (chan->desc) +sf_pdma_xfer_desc (chan);
[Severity: Critical]
Could this hardware re-triggering logic corrupt the DMA state if the done tasklet is executing simultaneously?
If this error tasklet completes the current descriptor and starts the next one in hardware, the done tasklet might still assume the original descriptor is active. Will the done tasklet then incorrectly complete the actively running descriptor and start a third one, corrupting the DMA registers during an active transfer?
Quoted 3 lines
drivers/dma/sf-pdma/sf-pdma.c +1
-0+spin_unlock_irqrestore (&chan->vchan.lock, flags); }else {/* retry */
[ ... ]
drivers/dma/sf-pdma/sf-pdma.c static void sf_pdma_remove(struct platform_device *pdev) · line 614
Quoted 4 lines
drivers/dma/sf-pdma/sf-pdma.c +0
-0@@ -602,9 +612,9 @@ 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);
[Severity: High]
This is a pre-existing issue, but does removing this device node early break the dmaengine core cleanup?
When sf_pdma_remove() calls list_del() here, it empties the channels list. Later in the removal process, dma_async_device_unregister() iterates over the channels list to free resources like per-cpu memory and IDAs. Since the list is already empty, will those resources be permanently leaked?
drivers/dma/sf-pdma/sf-pdma.c static void sf_pdma_remove(struct platform_device *pdev) · line 618
Quoted 5 lines
drivers/dma/sf-pdma/sf-pdma.c +1
-1-tasklet_kill (&ch->vchan.task);tasklet_kill (&ch->done_tasklet);tasklet_kill (&ch->err_tasklet); +dma_chan_kill_bh (&ch->vchan.chan); }
[Severity: High]
This is a pre-existing issue, but can sf_pdma_terminate_all() trigger a double free of the active descriptor?
sf_pdma_terminate_all() explicitly frees the descriptor:
sf_pdma_terminate_all() {
...
kfree(chan->desc);
chan->desc = NULL;
...
vchan_get_all_descriptors(&chan->vchan, &head);
...
vchan_dma_desc_free_list(&chan->vchan, &head);
}Because the active descriptor isn't removed from the virt-dma desc_issued list before being freed, won't vchan_get_all_descriptors() collect it again, causing vchan_dma_desc_free_list() to free the same pointer a second time?
[Severity: High]
This is a pre-existing issue, but is there a missing NULL pointer check when handling partial completions?
If a concurrent client terminates the channel, chan->desc is set to NULL. However, the done ISR unconditionally dereferences it:
sf_pdma_done_isr() {
...
struct sf_pdma_desc *desc = chan->desc;
desc->src_addr += desc->xfer_size - residue;
...
}Could this cause a kernel panic if the ISR fires right after the channel is terminated?
[Severity: Medium]
This is a pre-existing issue, but does sf_pdma_desc_residue() fail to report accurate remaining bytes for actively running transfers?
sf_pdma_desc_residue() only searches the submitted list:
sf_pdma_desc_residue() {
...
list_for_each_entry(vd, &chan->vchan.desc_submitted, node)
if (vd->tx.cookie == cookie)
tx = &vd->tx;
if (!tx)
goto out;
...
}Since actively running descriptors are moved to the desc_issued list, won't this always fail to find the active transfer and incorrectly return 0 remaining bytes?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=29
PATCH v2 30/64 dmaengine: sa11x0-dma: kill vchan BH on remove
In reply to Allen PaisUse dma_chan_kill_bh() when tearing down virt-dma channels.
--- drivers/dma/sa11x0-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/sa11x0-dma.c +1 -1
@@ -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); +dma_chan_kill_bh (&c->vc.chan);kfree (c); } }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 30/64] dmaengine: sa11x0-dma: kill vchan BH on remove
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] The driver's teardown sequence in sa11x0_dma_remove() and the probe error path is critically flawed, freeing DMA channels before disabling hardware interrupts and stopping the device tasklet. --
--- Patch [30]: [PATCH v2 30/64] dmaengine: sa11x0-dma: kill vchan BH on remove --- commit 34b16ea740696f927777e47c145e53481090efcc
Author: Allen Pais <[email protected]>
dmaengine: sa11x0-dma: kill vchan BH on remove
Use dma_chan_kill_bh() when tearing down virt-dma channels.
drivers/dma/sa11x0-dma.c static void sa11x0_dma_free_channels(struct dma_device *dmadev) · line 895
Quoted 11 lines
drivers/dma/sa11x0-dma.c +1
-1@@ -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); +dma_chan_kill_bh (&c->vc.chan);kfree (c);
[Severity: High]
This isn't a bug introduced by this patch, but is a pre-existing structural flaw in the driver's teardown sequence.
In both sa11x0_dma_remove() and the error path of sa11x0_dma_probe(), sa11x0_dma_free_channels() is called before the device interrupts are freed and the main device tasklet is killed:
sa11x0_dma_remove() {
...
sa11x0_dma_free_channels(&d->slave);
for (pch = 0; pch < NR_PHY_CHAN; pch++)
sa11x0_dma_free_irq(pdev, pch, &d->phy[pch]);
tasklet_kill(&d->task);
...
}If an unbind happens while a DMA transfer is active, could an interrupt fire during this window? If so, would sa11x0_dma_irq() dereference a freed channel (p->vchan) and cause a use-after-free when attempting to acquire c->vc.lock?
Additionally, could the lockless traversal of the d->slave.channels list in sa11x0_dma_tasklet() encounter list poisoning or a use-after-free if channels are freed concurrently by the list_del() and kfree() operations here?
Quoted 2 lines
drivers/dma/sa11x0-dma.c +0
-0} }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=30
PATCH v2 31/64 dmaengine: pl330: route callbacks via channel BH
In reply to Allen PaisConvert the per-channel tasklet to dma_chan_*_bh while keeping the controller tasklet for non-callback events.
--- drivers/dma/pl330.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-)
drivers/dma/pl330.c +8 -10
@@ -417,9 +417,6 @@ enum desc_status { };struct dma_pl330_chan { -/* Schedule desc completion */ -struct tasklet_struct task; -/* DMA-Engine Channel */ struct dma_chan chan;
@@ -1574,7 +1571,7 @@ static void dma_pl330_rqcb(struct dma_pl330_desc *desc, enum pl330_op_err err)spin_unlock_irqrestore (&pch->lock, flags); -tasklet_schedule (&pch->task); +dma_chan_schedule_bh (&pch->chan); }static void pl330_dotask (struct tasklet_struct *t)
@@ -2060,14 +2057,15 @@ static inline void fill_queue(struct dma_pl330_chan *pch) desc->status =DONE ;dev_err (pch->dmac->ddma.dev,"%s:%d Bad Desc(%d)\n" , __func__, __LINE__, desc->txd.cookie); -tasklet_schedule (&pch->task); +dma_chan_schedule_bh (&pch->chan); } } } -static void pl330_tasklet (struct tasklet_struct *t) +static void pl330_tasklet (struct dma_chan *chan) { -struct dma_pl330_chan *pch =from_tasklet (pch, t, task); + struct dma_pl330_chan *pch = container_of(chan, struct dma_pl330_chan, + chan);struct dma_pl330_desc *desc, *_dt;unsigned long flags;bool power_down = false;
@@ -2173,7 +2171,7 @@ static int pl330_alloc_chan_resources(struct dma_chan *chan)return -ENOMEM ; } -tasklet_setup (&pch->task, pl330_tasklet); +dma_chan_init_bh (&pch->chan, pl330_tasklet);spin_unlock_irqrestore (&pl330->lock, flags);
@@ -2354,7 +2352,7 @@ static void pl330_free_chan_resources(struct dma_chan *chan)struct pl330_dmac *pl330 = pch->dmac;unsigned long flags; -tasklet_kill (&pch->task); +dma_chan_kill_bh (&pch->chan);pm_runtime_get_sync (pch->dmac->ddma.dev);spin_lock_irqsave (&pl330->lock, flags);
@@ -2490,7 +2488,7 @@ static void pl330_issue_pending(struct dma_chan *chan)list_splice_tail_init (&pch->submitted_list, &pch->work_list);spin_unlock_irqrestore (&pch->lock, flags); -pl330_tasklet (&pch->task); +pl330_tasklet (&pch->chan); }/*
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 31/64] dmaengine: pl330: route callbacks via channel BH
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Calling dma_chan_init_bh() in the dynamic channel allocation path triggers a kernel WARN_ON when a channel is freed and subsequently re-allocated.
Pre-existing issues:
- [High] Concurrent channel error processing can schedule the bottom-half work after the channel thread is set to NULL during channel release, leading to a NULL pointer dereference.
- [High] The desc_pool list in the DMA controller structure is modified without holding pool_lock in pl330_tasklet, leading to linked list corruption.
- [High] Calling pl330_tasklet directly from pl330_issue_pending causes completed DMA callbacks to run synchronously in the caller's context, leading to deadlocks. --
--- Patch [31]: [PATCH v2 31/64] dmaengine: pl330: route callbacks via channel BH --- commit 03f651c688fce11fe6da583209843570b5a94019
Author: Allen Pais <[email protected]>
dmaengine: pl330: route callbacks via channel BH
Convert the per-channel tasklet to dma_chan_*_bh while keeping the controller tasklet for non-callback events.
Quoted 4 lines
drivers/dma/pl330.c +0
-0
[ ... ]
Quoted 7 lines
drivers/dma/pl330.c +1
-1@@ -2060,14 +2057,15 @@ static inline void fill_queue(struct dma_pl330_chan *pch) desc->status =DONE ;dev_err (pch->dmac->ddma.dev,"%s:%d Bad Desc(%d)\n" , __func__, __LINE__, desc->txd.cookie); -tasklet_schedule (&pch->task); +dma_chan_schedule_bh (&pch->chan); }
drivers/dma/pl330.c static inline void fill_queue(struct dma_pl330_chan *pch) · line 2069
Quoted 10 lines
drivers/dma/pl330.c +3
-2} } -static void pl330_tasklet (struct tasklet_struct *t) +static void pl330_tasklet (struct dma_chan *chan) { -struct dma_pl330_chan *pch =from_tasklet (pch, t, task); + struct dma_pl330_chan *pch = container_of(chan, struct dma_pl330_chan, + chan);struct dma_pl330_desc *desc, *_dt;
[Severity: High]
This is a pre-existing issue, but can this function crash due to a NULL pointer dereference if it races with a concurrent channel release?
When pl330_free_chan_resources() kills the bottom-half, it sets pch->thread to NULL:
pl330_free_chan_resources()
dma_chan_kill_bh(&pch->chan);
...
pl330_release_channel(pch->thread);
pch->thread = NULL;If a concurrent DMAC tasklet calls dma_pl330_rqcb(), it unconditionally queues the bottom-half again:
dma_pl330_rqcb()
dma_chan_schedule_bh(&pch->chan);
Since dma_chan_schedule_bh() is not blocked after kill_bh, the workqueue could execute pl330_tasklet() on the freed channel. Later in this function, we acquire a lock via the thread pointer:
pl330_tasklet()
spin_lock(&pch->thread->dmac->lock);
Would this dereference the NULL thread pointer?
[Severity: High]
This is also a pre-existing issue, but does moving descriptors to the global desc_pool here corrupt the linked list?
Later in pl330_tasklet(), completed non-cyclic descriptors are moved back to the global pool:
pl330_tasklet()
list_move_tail(&desc->node, &pch->dmac->desc_pool);
The pch->dmac->desc_pool list is shared across the controller and normally protected by pool_lock, but pl330_tasklet() only holds the channel-specific pch->lock here. Could concurrent modifications to desc_pool from multiple channels or allocations cause list corruption?
[ ... ]
drivers/dma/pl330.c static int pl330_alloc_chan_resources(struct dma_chan *chan) · line 2176
Quoted 8 lines
drivers/dma/pl330.c +1
-1@@ -2173,7 +2171,7 @@ static int pl330_alloc_chan_resources(struct dma_chan *chan)return -ENOMEM ; } -tasklet_setup (&pch->task, pl330_tasklet); +dma_chan_init_bh (&pch->chan, pl330_tasklet);spin_unlock_irqrestore (&pl330->lock, flags);
[Severity: High]
Does initializing the bottom-half work structure here trigger a warning on subsequent channel allocations?
pl330_alloc_chan_resources() is called each time a DMA client dynamically requests a channel. When the client releases the channel, dma_chan_kill_bh() flushes the workqueue but does not clear the bh_work_initialized flag.
If a client requests this same channel again, calling dma_chan_init_bh() will hit the WARN_ON(chan->bh_work_initialized) guard in the DMA engine core. Should this initialization be moved to driver probe time instead?
[ ... ]
drivers/dma/pl330.c static void pl330_issue_pending(struct dma_chan *chan) · line 2492
Quoted 7 lines
drivers/dma/pl330.c +1
-1@@ -2490,7 +2488,7 @@ static void pl330_issue_pending(struct dma_chan *chan)list_splice_tail_init (&pch->submitted_list, &pch->work_list);spin_unlock_irqrestore (&pch->lock, flags); -pl330_tasklet (&pch->task); +pl330_tasklet (&pch->chan); }
[Severity: High]
This is a pre-existing issue, but does calling the bottom-half worker directly from the issue_pending path risk deadlocks?
Clients of the DMA engine API typically call dma_async_issue_pending() while holding their own spinlocks. By invoking pl330_tasklet() synchronously here:
pl330_issue_pending()
pl330_tasklet(&pch->chan);
If any descriptors were marked as DONE by a concurrent IRQ before the scheduled bottom-half could run, pl330_tasklet() will immediately process them and invoke their registered client DMA callbacks inline.
If the client callback attempts to acquire the same spinlock the client already holds, will this cause an AB-BA or AA deadlock?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=31
PATCH v2 32/64 dmaengine: k3-udma: use channel BH for vchan completions
In reply to Allen PaisUse the per-channel BH mechanism for custom vchan completion handling and clean up via dma_chan_kill_bh().
--- drivers/dma/ti/k3-udma.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
drivers/dma/ti/k3-udma.c +6 -6
@@ -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); +dma_chan_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); }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 32/64] dmaengine: k3-udma: use channel BH for vchan completions
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Dangling vc->cyclic pointer causes a Use-After-Free in udma_vchan_complete when a channel running a cyclic transfer is terminated. --
--- Patch [32]: [PATCH v2 32/64] dmaengine: k3-udma: use channel BH for vchan completions --- commit 8a0a241d872268ead0d3f5e593bb261aae3ac2ff
Author: Allen Pais <[email protected]>
dmaengine: k3-udma: use channel BH for vchan completions
This patch converts the custom vchan completion handling in the k3-udma driver from a tasklet to a per-channel bottom-half mechanism. It updates the initialization and teardown paths to use the new dma_chan_kill_bh() API.
Quoted 4 lines
drivers/dma/ti/k3-udma.c +0
-0
[ ... ]
drivers/dma/ti/k3-udma.c static void udma_desc_pre_callback(struct virt_dma_chan *vc, · line 4050
Quoted 13 lines
drivers/dma/ti/k3-udma.c +3
-3@@ -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);
[Severity: High]
This is a pre-existing issue, but could this bottom half handler trigger a use-after-free of the cyclic descriptor?
Looking at the teardown path, if a channel is actively running a cyclic transfer, udma_terminate_all() moves uc->desc to uc->terminated_desc but crucially leaves vc->cyclic pointing to the active descriptor.
When the hardware sends a teardown completion message, the IRQ handler udma_ring_irq_handler() frees the active descriptor via udma_desc_free(&uc->terminated_desc->vd). At this point, vc->cyclic becomes a dangling pointer.
The driver then calls udma_synchronize(), which calls vchan_synchronize(). This flushes the bottom half by calling dma_chan_kill_bh(), which happens before vc->cyclic is checked and cleared:
drivers/dma/virt-dma.c:vchan_synchronize() {
dma_chan_kill_bh(&vc->chan);
spin_lock_irqsave(&vc->lock, flags);
if (vc->cyclic) {
vchan_terminate_vdesc(vc->cyclic);
vc->cyclic = NULL;
}
}When the bottom half executes, it reads the dangling vc->cyclic pointer and dereferences it:
vd = vc->cyclic;
if (vd) {
vc->cyclic = NULL;
dmaengine_desc_get_callback(&vd->tx, &cb);Does this sequence cause a use-after-free when dmaengine_desc_get_callback() accesses the freed descriptor?
Quoted 3 lines
drivers/dma/ti/k3-udma.c +0
-0struct virt_dma_desc *vd, *_vd;struct dmaengine_desc_callback cb;LIST_HEAD (head);
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=32
PATCH v2 33/64 dmaengine: sun6i: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma channel cleanup.
--- drivers/dma/sun6i-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/sun6i-dma.c +1 -1
@@ -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); } }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 33/64] dmaengine: sun6i: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Client callbacks flushed during cleanup can reschedule the driver tasklet after it was killed and clocks were disabled, leading to a hardware fault and list corruption. --
--- Patch [33]: [PATCH v2 33/64] dmaengine: sun6i: kill vchan BH on teardown --- commit 0c78bc25a98ea85ad52645998923d5aa57383201
Author: Allen Pais <[email protected]>
dmaengine: sun6i: kill vchan BH on teardown
Use dma_chan_kill_bh() for virt-dma channel cleanup.
drivers/dma/sun6i-dma.c static inline void sun6i_dma_free(struct sun6i_dma_dev *sdev) · line 1100
Quoted 10 lines
drivers/dma/sun6i-dma.c +1
-1@@ -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);
[Severity: High]
This is a pre-existing issue, but could this cleanup sequence trigger a hardware fault or list corruption if a client callback submits a new transfer?
During device removal or sysfs unbind, sun6i_dma_remove() kills the hardware tasklet and disables controller clocks before calling sun6i_dma_free():
drivers/dma/sun6i-dma.c:sun6i_dma_remove() {
...
sun6i_kill_tasklet(sdc);
clk_disable_unprepare(sdc->clk_mbus);
clk_disable_unprepare(sdc->clk);
reset_control_assert(sdc->rstc);
sun6i_dma_free(sdc);
}sun6i_dma_free() then calls dma_chan_kill_bh() which flushes pending work. If a client callback runs during this flush and submits a new transfer, sun6i_dma_issue_pending() is invoked:
drivers/dma/sun6i-dma.c:sun6i_dma_issue_pending() {
...
list_add_tail(&vchan->node, &sdev->pending);
tasklet_schedule(&sdev->task);
}Because tasklet_schedule() is called unconditionally without checking if the device is tearing down, it resurrects the killed tasklet.
When the scheduled tasklet runs, it accesses hardware registers without clocks enabled, leading to an external abort. Additionally, it concurrently accesses the channel lists while sun6i_dma_free() mutates them via list_del() without locks.
Should the teardown order in sun6i_dma_remove() be adjusted to flush callbacks before disabling clocks, or should sun6i_dma_issue_pending() check a shutdown flag before scheduling the tasklet?
Quoted 2 lines
drivers/dma/sun6i-dma.c +0
-0} }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=33
PATCH v2 34/64 dmaengine: mtk-cqdma: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma channel cleanup.
--- drivers/dma/mediatek/mtk-cqdma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/mediatek/mtk-cqdma.c +1 -1
@@ -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 */
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 34/64] dmaengine: mtk-cqdma: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] Incorrect teardown order in mtk_cqdma_remove() allows physical channel tasklets to reschedule virtual channel bottom halves after they have been killed, leading to a use-after-free.
- [High] Manual removal of channels from the DMA device channel list via list_del() before unregistration bypasses dmaengine cleanup, leaking memory and IDA indexes.
- [High] The DMA device is registered before physical channel tasklets and hardware are initialized, allowing early interrupts to schedule uninitialized tasklets. --
--- Patch [34]: [PATCH v2 34/64] dmaengine: mtk-cqdma: kill vchan BH on teardown --- commit 94dd78bb3e2fc0bc1ffe5fdde7322901f8e3ea2c
Author: Allen Pais <[email protected]>
dmaengine: mtk-cqdma: kill vchan BH on teardown
This patch updates the virt-dma channel cleanup in the MediaTek CQDMA driver to use the standard dma_chan_kill_bh() helper rather than directly calling tasklet_kill().
drivers/dma/mediatek/mtk-cqdma.c static void mtk_cqdma_remove(struct platform_device *pdev) · line 897
Quoted 8 lines
drivers/dma/mediatek/mtk-cqdma.c +0
-0@@ -895,7 +895,7 @@ static void mtk_cqdma_remove(struct platform_device *pdev) vc = &cqdma->vc[i];list_del (&vc->vc.chan.device_node);
[Severity: High]
This isn't a bug introduced by this patch, but does calling list_del() here cause a memory and IDA index leak?
By manually removing the channel from the dmaengine device list before calling dma_async_device_unregister(), the core subsystem's unregistration loop will see an empty list.
This causes __dma_async_device_channel_unregister() to be skipped, which means free_percpu(), sysfs cleanup, and ida_free() are never executed.
drivers/dma/mediatek/mtk-cqdma.c static void mtk_cqdma_remove(struct platform_device *pdev) · line 898
Quoted 2 lines
drivers/dma/mediatek/mtk-cqdma.c +1
-1-tasklet_kill (&vc->vc.task); +dma_chan_kill_bh (&vc->vc.chan);
[Severity: High]
This is a pre-existing issue, but does this teardown order leave a window for a use-after-free?
If a physical channel interrupt fires after dma_chan_kill_bh() is called but before the hardware interrupts are disabled and tasklets are killed below, the interrupt handler will schedule the physical channel tasklet.
This tasklet processes completions and calls vchan_cookie_complete(), which re-queues the virtual channel bottom-half work. After mtk_cqdma_remove() returns, devres frees the cqdma device, leaving the re-queued work item referencing freed memory.
Would it be safer to disable physical interrupts and kill tasklets before killing the virtual channel bottom halves?
drivers/dma/mediatek/mtk-cqdma.c static void mtk_cqdma_remove(struct platform_device *pdev) · line 901
Quoted 3 lines
drivers/dma/mediatek/mtk-cqdma.c +0
-0}/* disable interrupt */
[ ... ]
While reviewing this file, another pre-existing issue was noticed in mtk_cqdma_probe():
[Severity: High]
This is a pre-existing issue, but is the DMA device registered before the physical channel tasklets and hardware are fully initialized?
drivers/dma/mediatek/mtk-cqdma.c:mtk_cqdma_probe() {
...
err = dma_async_device_register(dd);
...
err = mtk_cqdma_hw_init(cqdma);
...
for (i = 0; i < cqdma->dma_channels; ++i)
tasklet_setup(&cqdma->pc[i]->tasklet, mtk_cqdma_tasklet_cb);
...
}Once dma_async_device_register() is called, the controller is globally visible. If a client probes asynchronously and immediately requests a channel, it could trigger an early interrupt before tasklet_setup() completes, potentially dereferencing a NULL pointer for the uninitialized tasklet callback.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=34
PATCH v2 35/64 dmaengine: altera-msgdma: use channel BH helpers
In reply to Allen PaisReplace the device tasklet with the per-channel BH helper so completion handling runs via dma_chan_*_bh.
--- drivers/dma/altera-msgdma.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-)
drivers/dma/altera-msgdma.c +7 -7
@@ -170,7 +170,6 @@ struct msgdma_sw_desc {struct msgdma_device {spinlock_t lock;struct device *dev; -struct tasklet_struct irq_tasklet;struct list_head pending_list;struct list_head free_list;struct list_head active_list;
@@ -684,11 +683,12 @@ static int msgdma_alloc_chan_resources(struct dma_chan *dchan)/** * msgdma_tasklet - Schedule completion tasklet -* @t: Pointer to the Altera sSGDMA channel structure +* @chan: Pointer to the Altera sSGDMA channel structure */ -static void msgdma_tasklet (struct tasklet_struct *t) +static void msgdma_tasklet (struct dma_chan *chan) { -struct msgdma_device *mdev =from_tasklet (mdev, t, irq_tasklet); + struct msgdma_device *mdev = container_of(chan, struct msgdma_device, + dmachan);u32 count;u32 __maybe_unused size;u32 __maybe_unused status;
@@ -748,7 +748,7 @@ static irqreturn_t msgdma_irq_handler(int irq, void *data)spin_unlock (&mdev->lock); } -tasklet_schedule (&mdev->irq_tasklet); +dma_chan_schedule_bh (&mdev->dmachan);/* Clear interrupt in mSGDMA controller */ iowrite32 (MSGDMA_CSR_STAT_IRQ , mdev->csr +MSGDMA_CSR_STATUS );
@@ -766,7 +766,7 @@ static void msgdma_dev_remove(struct msgdma_device *mdev)return ;devm_free_irq (mdev->dev, mdev->irq, mdev); -tasklet_kill (&mdev->irq_tasklet); +dma_chan_kill_bh (&mdev->dmachan);list_del (&mdev->dmachan.device_node); }
@@ -852,7 +852,7 @@ static int msgdma_probe(struct platform_device *pdev)if (ret)return ret; -tasklet_setup (&mdev->irq_tasklet, msgdma_tasklet); +dma_chan_init_bh (&mdev->dmachan, msgdma_tasklet);dma_cookie_init (&mdev->dmachan);
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 35/64] dmaengine: altera-msgdma: use channel BH helpers
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] The device interrupt is requested before essential locks, lists, and bottom-half structures are initialized, risking a crash if an interrupt fires early.
- [High] Missing dma_async_device_unregister() in the probe error path causes Use-After-Free.
--
--- Patch [35]: [PATCH v2 35/64] dmaengine: altera-msgdma: use channel BH helpers ---
Note: The format of this review has been altered to use a free-form summary rather than inline diff quoting due to strict recitation restrictions.
commit bf40fd1cbe95a4c51ca6b86e4123ac5282a06f01
Author: Allen Pais <[email protected]>
Subject: dmaengine: altera-msgdma: use channel BH helpers
Replace the device tasklet with the per-channel BH helper so completion handling runs via dma_chan_*_bh.
[Severity: High]
This isn't a bug introduced by this patch, but looking at msgdma_probe() in drivers/dma/altera-msgdma.c, the interrupt is requested via devm_request_irq() before mdev->lock, the pending_list, and the bottom-half structures are initialized.
If a pending hardware interrupt fires immediately, could msgdma_irq_handler() execute and attempt to acquire the uninitialized mdev->lock? Furthermore, if it then calls msgdma_start_transfer(), it appears list_first_entry_or_null() on the zero-initialized pending_list would return an invalid pointer. Would this cause a page fault during list_splice_tail_init() when it subsequently evaluates list_empty() inside the splice operations? Is it possible to move the devm_request_irq() call after all these structures and lists are fully initialized?
[Severity: High]
This is also a pre-existing issue, but I have a question about the error handling further down in msgdma_probe().
If of_dma_controller_register() fails with an error other than -EINVAL or -ENODEV, the code jumps to the fail label and calls msgdma_dev_remove(). However, msgdma_dev_remove() only cleans up the IRQ and bottom half, without calling dma_async_device_unregister().
Since dma_async_device_register() was already called earlier in the probe function, won't devres subsequently free the mdev structure while the DMA engine core still holds pointers to its embedded dma_dev and channels? Could this lead to a use-after-free if the DMA engine core later traverses its channel lists? Does dma_async_device_unregister() need to be added to the cleanup path?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=35
PATCH v2 36/64 dmaengine: sprd-dma: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma cleanup.
--- drivers/dma/sprd-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/sprd-dma.c +1 -1
@@ -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);
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 36/64] dmaengine: sprd-dma: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] The driver prematurely removes channels from sdev->dma_dev.channels before calling dma_async_device_unregister(), causing the core DMA framework to silently skip unregistering them, which leaks per-CPU memory, IDAs, and sysfs nodes, leading to a Use-After-Free.
- [High] IRQ is requested before the MMIO base is mapped and clocks are enabled, leading to a guaranteed NULL pointer dereference or bus hang if an interrupt is pending.
--
--- Patch [36]: [PATCH v2 36/64] dmaengine: sprd-dma: kill vchan BH on teardown --- commit 90cb440f02a76b9f95f830c9b7cd74c3b47c9d5f
Author: Allen Pais <[email protected]>
dmaengine: sprd-dma: kill vchan BH on teardown
Use dma_chan_kill_bh() for virt-dma cleanup.
drivers/dma/sprd-dma.c static void sprd_dma_remove(struct platform_device *pdev) · line 1255
Quoted 8 lines
drivers/dma/sprd-dma.c +0
-0@@ -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);
[Severity: High]
This isn't a bug introduced by this patch, but removing the channels from sdev->dma_dev.channels here causes the core dma framework to skip unregistering them when dma_async_device_unregister() is called later in this function:
drivers/dma/dmaengine.c:dma_async_device_unregister() {
...
list_for_each_entry_safe(chan, n, &device->channels, device_node)
__dma_async_device_channel_unregister(device, chan);
...
}By emptying the channel list early, the sysfs nodes for the channels are never unregistered.
Since the driver data is devres-managed and freed after unbind, does this leave orphaned sysfs nodes pointing to freed memory, leading to a use-after-free if a user reads the sysfs files?
drivers/dma/sprd-dma.c static void sprd_dma_remove(struct platform_device *pdev) · line 1259
Quoted 5 lines
drivers/dma/sprd-dma.c +1
-1-tasklet_kill (&c->vc.task); +dma_chan_kill_bh (&c->vc.chan); }of_dma_controller_free (pdev->dev.of_node);
[Severity: High]
This is another pre-existing issue, but there appears to be a null pointer dereference risk during probe in this driver.
The interrupt is requested before the mmio base is mapped:
drivers/dma/sprd-dma.c:sprd_dma_probe() {
...
ret = devm_request_irq(&pdev->dev, sdev->irq, dma_irq_handle,
0, "sprd_dma", (void *)sdev);
...
sdev->glb_base = devm_platform_ioremap_resource(pdev, 0);
...
}If an interrupt is already pending when devm_request_irq() unmasks it, the interrupt handler will fire immediately and dereference the uninitialized glb_base pointer:
drivers/dma/sprd-dma.c:dma_irq_handle() {
...
u32 irq_status = readl(sdev->glb_base + SPRD_DMA_GLB_INT_MSK_STS);
...
}Should the memory mapping and clock enablement be done before requesting the interrupt to avoid this panic during boot or module load?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=36
PATCH v2 37/64 dmaengine: idma64: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma cleanup.
--- drivers/dma/idma64.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/dma/idma64.c +2 -2
@@ -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); +dma_chan_kill_bh (&idma64c->vchan.chan); } }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 37/64] dmaengine: idma64: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] The channel cleanup loop in idma64_remove() is dead code, leading to a Use-After-Free of bottom-half work items.
- [High] A race condition during device removal can cause an unhandled interrupt storm.
- [High] Use-After-Free due to missing device_release callback with devres-managed core structure.
--
--- Patch [37]: [PATCH v2 37/64] dmaengine: idma64: kill vchan BH on teardown --- commit e368659298f3842bf78bcfc4c53afeb968ac845c
Author: Allen Pais <[email protected]>
dmaengine: idma64: kill vchan BH on teardown
Use dma_chan_kill_bh() for virt-dma cleanup.
drivers/dma/idma64.c static void idma64_remove(struct idma64_chip *chip) · line 622
Quoted 12 lines
drivers/dma/idma64.c +1
-1@@ -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);
[Severity: High]
This is a pre-existing issue, but does calling devm_free_irq() after dma_async_device_unregister() create a race condition that can cause an interrupt storm?
The earlier call to dma_async_device_unregister() iterates over the channels and decrements idma64->dma.chancnt to 0.
If a hardware interrupt triggers in the narrow window before devm_free_irq() disables it, idma64_irq() executes this loop:
idma64_irq() {
...
for (i = 0; i < idma64->dma.chancnt; i++)
idma64_chan_irq(idma64, i, status_err, status_xfer);
...
}Because chancnt is 0, it skips clearing the active hardware interrupt but still returns IRQ_HANDLED. Will the hardware continually re-assert the uncleared interrupt and hang the CPU?
drivers/dma/idma64.c static void idma64_remove(struct idma64_chip *chip) · line 629
Quoted 8 lines
drivers/dma/idma64.c +1
-1for (i =0 ; i < idma64->dma.chancnt; i++) {struct idma64_chan *idma64c = &idma64->chan[i]; -tasklet_kill (&idma64c->vchan.task); +dma_chan_kill_bh (&idma64c->vchan.chan); } }
[Severity: High]
This is a pre-existing issue, but will this cleanup loop ever execute?
Since the preceding call to dma_async_device_unregister() decrements idma64->dma.chancnt to 0 before we reach this loop, the loop is completely skipped and dma_chan_kill_bh() is never called.
When devres subsequently frees the idma64 structure, won't any pending bottom-half work items execute on freed memory?
[Severity: High]
This is also a pre-existing issue, but is there a use-after-free risk because struct idma64 is allocated with devm_kzalloc() but lacks a dma_device->device_release callback?
When the driver unbinds and idma64_remove() completes, devres will automatically free the idma64 structure:
idma64_probe() {
...
idma64 = devm_kzalloc(chip->dev, sizeof(*idma64), GFP_KERNEL);
...
}If a DMA client still holds a reference to a channel, won't the client dereference the freed idma64 memory when accessing or releasing the channel later, since the DMA core manages reference counts via device->ref?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=37
PATCH v2 38/64 dmaengine: img-mdc-dma: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma cleanup.
--- drivers/dma/img-mdc-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/img-mdc-dma.c +1 -1
@@ -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);
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 38/64] dmaengine: img-mdc-dma: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing pm_runtime_set_suspended() in the driver removal path causes PM state mismatch on subsequent rebinds, leading to hardware faults.
- [High] The PM runtime usage counter is leaked in mdc_alloc_chan_resources() if pm_runtime_get_sync() fails.
- [High] Hardware registers are accessed in mdc_dma_probe() before the device clock is enabled.
- [High] The IRQ handler is registered before its associated virtual channel spinlock is initialized, allowing a spurious interrupt to lock an uninitialized spinlock. --
--- Patch [38]: [PATCH v2 38/64] dmaengine: img-mdc-dma: kill vchan BH on teardown --- commit b4d81ea29e678b96ef67f182eaf0b8fc7065fe66
Author: Allen Pais <[email protected]>
dmaengine: img-mdc-dma: kill vchan BH on teardown
Use dma_chan_kill_bh() for virt-dma cleanup.
Quoted 4 lines
drivers/dma/img-mdc-dma.c +0
-0
[ ... ]
drivers/dma/img-mdc-dma.c
Quoted 11 lines
drivers/dma/img-mdc-dma.c +1
-1@@ -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);drivers/dma/img-mdc-dma.c +0
-0if (!pm_runtime_status_suspended (&pdev->dev))img_mdc_runtime_suspend (&pdev->dev);
[Severity: High]
This is a pre-existing issue, but does mdc_dma_remove() need to call pm_runtime_set_suspended() here?
When the driver is unbound and rebound via sysfs, the hardware is manually suspended but the PM runtime state is left as RPM_ACTIVE. On rebind, mdc_dma_probe() enables runtime PM but sees the device as already active.
When a channel is requested, won't pm_runtime_get_sync() return without enabling the clock, leading to an imprecise external abort (kernel panic) during subsequent register writes?
While reviewing this file, I noticed a few other pre-existing issues:
drivers/dma/img-mdc-dma.c:mdc_alloc_chan_resources()
Quoted 7 lines
static int
mdc_alloc_chan_resources(struct dma_chan *chan){ struct mdc_chan *mchan = to_mdc_chan(chan); struct device *dev = mdma2dev(mchan->mdma); return pm_runtime_get_sync(dev); }
[Severity: High]
This isn't a bug introduced by this patch, but does mdc_alloc_chan_resources() leak the PM runtime usage counter if pm_runtime_get_sync() fails?
If pm_runtime_get_sync() returns an error (e.g., -EACCES), the usage counter is incremented but the error is directly propagated. Since the allocation aborts, the free routine is never called to decrement the counter.
Doesn't this permanently prevent the device (and potentially the parent power domain) from entering low-power suspend states? Should this check the return value and call pm_runtime_put_noidle() on failure?
drivers/dma/img-mdc-dma.c:mdc_dma_probe()
Quoted 3 lines
mdma->clk = devm_clk_get(&pdev->dev, "sys"); if (IS_ERR(mdma->clk)) return PTR_ERR(mdma->clk);
[ ... ]
Quoted 1 line
val = mdc_readl(mdma, MDC_GLOBAL_CONFIG_A);
[Severity: High]
This is a pre-existing issue, but are hardware registers accessed in mdc_dma_probe() before the device clock is enabled?
mdc_readl() reads MDC_GLOBAL_CONFIG_A shortly after getting the "sys" clock, but the clock appears to only be enabled later in img_mdc_runtime_resume().
On systems where the bootloader doesn't leave the clock enabled, wouldn't reading from unclocked peripheral registers cause an imprecise external abort (kernel panic) during device initialization?
drivers/dma/img-mdc-dma.c:mdc_dma_probe()
Quoted 8 lines
ret = devm_request_irq(&pdev->dev, mchan->irq, mdc_chan_irq, IRQ_TYPE_LEVEL_HIGH, dev_name(&pdev->dev), mchan); if (ret < 0) return ret; mchan->vc.desc_free = mdc_desc_free; vchan_init(&mchan->vc, &mdma->dma_dev);
[Severity: High]
This isn't a bug introduced by this patch, but is the IRQ handler registered before its associated virtual channel spinlock is initialized?
devm_request_irq() is called before vchan_init() initializes mchan->vc.lock. If the IRQ line is asserted immediately after registration (e.g. from a warm boot or shared interrupt), mdc_chan_irq() will fire and attempt to acquire a zero-initialized lock.
Could this trigger a kernel panic with lock debugging enabled?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=38
PATCH v2 39/64 dmaengine: fsl-edma-common: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma cleanup.
--- drivers/dma/fsl-edma-common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/fsl-edma-common.c +1 -1
@@ -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); } }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 39/64] dmaengine: fsl-edma-common: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
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 <[email protected]>
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().
drivers/dma/fsl-edma-common.c void fsl_edma_cleanup_vchan(struct dma_device *dmadev) · line 917
Quoted 8 lines
drivers/dma/fsl-edma-common.c +0
-0@@ -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()).
Quoted 4 lines
drivers/dma/fsl-edma-common.c +1
-1-tasklet_kill (&chan->vchan.task); +dma_chan_kill_bh (&chan->vchan.chan); } }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=39
PATCH v2 40/64 dmaengine: dw-axi-dmac: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma cleanup.
--- drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c +1 -1
@@ -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); +dma_chan_kill_bh (&chan->vc.chan); } }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 40/64] dmaengine: dw-axi-dmac: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Manual deletion of DMA channels from the device list in dw_remove() sabotages the DMA engine core's unregistration process, leading to memory, IDA, and sysfs leaks.
- [High] Flushing the completion bottom half (dma_chan_kill_bh) after disabling hardware clocks can cause a system hang if a client callback issues a new transfer. --
--- Patch [40]: [PATCH v2 40/64] dmaengine: dw-axi-dmac: kill vchan BH on teardown --- commit d93e02ecaf0e9550e4241cd3c6ed8d45227cb4a9
Author: Allen Pais <[email protected]>
dmaengine: dw-axi-dmac: kill vchan BH on teardown
This commit updates virt-dma cleanup during teardown to use dma_chan_kill_bh() instead of tasklet_kill().
drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c static void dw_remove(struct platform_device *pdev) · line 1665
Quoted 8 lines
drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c +0
-0@@ -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);
[Severity: High]
This isn't a bug introduced by this patch, but does calling list_del() on the channel here sabotage the DMA engine core's unregistration process?
The DMA core's dma_async_device_unregister() uses the device->channels list to tear down each channel. By manually removing the channels from this list in dw_remove(), the core teardown logic sees an empty list and skips channel unregistration.
Does this leak the channel's IDA, its sysfs device node (dmaXchanY), and its dynamically allocated per-CPU memory (chan->local)?
drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c static void dw_remove(struct platform_device *pdev) · line 1666
Quoted 2 lines
drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c +1
-1-tasklet_kill (&chan->vc.task); +dma_chan_kill_bh (&chan->vc.chan);
[Severity: High]
This is a pre-existing issue, but does flushing the bottom half here risk a system hang?
Earlier in dw_remove(), the hardware clocks are disabled via axi_dma_suspend(). If a client callback (invoked by the bottom half flush) submits a new DMA transfer, the driver will attempt to read or write DMA controller registers to start the transfer.
Can accessing these registers without clocks enabled cause a synchronous external abort?
Quoted 3 lines
drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c +0
-0} }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=40
PATCH v2 41/64 dmaengine: hsu: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma cleanup.
--- drivers/dma/hsu/hsu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/hsu/hsu.c +1 -1
@@ -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); +dma_chan_kill_bh (&hsuc->vchan.chan); }return 0 ;
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 41/64] dmaengine: hsu: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The patch series structure breaks `git bisect` due to a build failure in the `hsu` driver. --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The patch series structure breaks git bisect due to a build failure in the hsu driver. --
--- Patch [41]: [PATCH v2 41/64] dmaengine: hsu: kill vchan BH on teardown --- commit 53bdccad0464307508ef64c4477dc5f1414579fd
Author: Allen Pais <[email protected]>
dmaengine: hsu: kill vchan BH on teardown
Use dma_chan_kill_bh() for virt-dma cleanup.
drivers/dma/hsu/hsu.c int hsu_dma_remove(struct hsu_dma_chip *chip) · line 503
Quoted 9 lines
drivers/dma/hsu/hsu.c +0
-1@@ -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);
[Severity: Medium]
Will this series structure break git bisect?
If the earlier commit "dmaengine: add tasklet-backed channel BH helpers" removes the task member from struct virt_dma_chan, any kernel checkout between that commit and this one will fail to compile if the hsu driver is enabled because it still references hsuc->vchan.task in hsu_dma_remove().
Should the series be reordered so that affected drivers are converted to use the new API before the task member is removed?
Quoted 4 lines
drivers/dma/hsu/hsu.c +1
-0+dma_chan_kill_bh (&hsuc->vchan.chan); }return 0 ;
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=41
Re: [PATCH v2 41/64] dmaengine: hsu: kill vchan BH on teardown
In reply to Allen PaisQuoted 2 lines · Allen Pais
Use
dma_chan_kill_bh()for virt-dma cleanup.
P.S. No need to Bcc, Cc is good enough and allows me to see the replies.
Signature · 2 lines
With Best Regards, Andy Shevchenko
PATCH v2 42/64 dmaengine: jz4780: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma cleanup.
--- drivers/dma/dma-jz4780.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/dma-jz4780.c +1 -1
@@ -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 = {
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 42/64] dmaengine: jz4780: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Use-after-free and arbitrary physical memory corruption during descriptor setup failure
- [High] Hardware teardown race in jz4780_dma_remove due to incorrect devres ordering
- [High] NULL Pointer Dereference in jz4780_dma_tx_status
--
--- Patch [42]: [PATCH v2 42/64] dmaengine: jz4780: kill vchan BH on teardown --- commit 80e35ad454b351671c271c48790a44cd6f63d096
Author: Allen Pais <[email protected]>
dmaengine: jz4780: kill vchan BH on teardown
This commit updates virt-dma cleanup to use dma_chan_kill_bh() instead of tasklet_kill().
drivers/dma/dma-jz4780.c static void jz4780_dma_remove(struct platform_device *pdev) · line 1022
Quoted 10 lines
drivers/dma/dma-jz4780.c +1
-1@@ -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);
[Severity: High]
This is a pre-existing issue, but does disabling the hardware clock here before devres gets a chance to unregister the DMA device create a race condition?
If an interrupt fires or a client accesses the DMA engine (for example, calling dmaengine_terminate_all()) during or immediately after jz4780_dma_remove(), it appears this would trigger a fatal MMIO fault because the device clock is already disabled.
drivers/dma/dma-jz4780.c static void jz4780_dma_remove(struct platform_device *pdev) · line 1025
Quoted 3 lines
drivers/dma/dma-jz4780.c +0
-0}static const struct jz4780_dma_soc_data jz4740_dma_soc_data = {
[Severity: High]
This is a pre-existing issue, but is there a risk of a NULL pointer dereference in jz4780_dma_tx_status() when a transfer isn't active?
drivers/dma/dma-jz4780.c:jz4780_dma_tx_status() {
...
} else if (cookie == jzchan->desc->vdesc.tx.cookie) {
...
}If a client prepares and submits a descriptor, moving it to the desc_submitted list, and then polls its status before it is issued, vchan_find_desc() would return NULL. If no transfer is active, jzchan->desc is NULL, which seems to lead to an unguarded dereference in this branch.
[Severity: Critical]
This is a pre-existing issue, but could the error paths during descriptor setup lead to a use-after-free and potential physical memory corruption?
drivers/dma/dma-jz4780.c:jz4780_dma_prep_slave_sg() {
...
jz4780_dma_desc_free(&jzchan->desc->vdesc);
return NULL;
...
}If jz4780_dma_setup_hwdesc() fails, the error path frees jzchan->desc. However, jzchan->desc appears to hold the currently active, running descriptor rather than the newly allocated one. Does this free the running descriptor while the DMA controller is still executing it? This pattern also appears to exist in jz4780_dma_prep_dma_cyclic().
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=42
PATCH v2 43/64 dmaengine: pxa_dma: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma cleanup.
--- drivers/dma/pxa_dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/pxa_dma.c +1 -1
@@ -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); } }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 43/64] dmaengine: pxa_dma: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Pre-existing issues:
- [High] Prematurely deleting channels from dmadev->channels during driver removal bypasses the dmaengine core's unregistration logic, permanently breaking module reload.
- [High] Critical Use-After-Free vulnerability on driver unbind due to improper devres cleanup ordering and failure to quiesce the hardware.
- [High] Missing of_dma_controller_free() on driver removal leaves a dangling pointer in the OF DMA list.
- [High] Potential Use-After-Free on the probe error path due to missing synchronization of bottom-half work.
--
--- Patch [43]: [PATCH v2 43/64] dmaengine: pxa_dma: kill vchan BH on teardown --- commit 542f54f4e01925d8e48b9a9c649593d23bb50a91
Author: Allen Pais <[email protected]>
dmaengine: pxa_dma: kill vchan BH on teardown
Use dma_chan_kill_bh() for virt-dma cleanup.
drivers/dma/pxa_dma.c static void pxad_free_channels(struct dma_device *dmadev) · line 1217
Quoted 8 lines
drivers/dma/pxa_dma.c +0
-0@@ -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);
[Severity: High]
This is a pre-existing issue, but does this premature list deletion bypass the dmaengine core's unregistration logic?
In pxad_remove(), the driver calls pxad_free_channels(), which uses list_del() to manually remove each channel from dmadev->channels. The core framework's dma_async_device_unregister() is executed subsequently as a devres action and relies on traversing dmadev->channels to unregister each channel.
Because the list has been artificially emptied, __dma_async_device_channel_unregister() is never invoked, skipping device_unregister(), ida_free(), and free_percpu(). Will this leak sysfs nodes (e.g., "dma0chan0") and cause permanent sysfs name collisions on subsequent module loads?
drivers/dma/pxa_dma.c static void pxad_free_channels(struct dma_device *dmadev) · line 1220
Quoted 4 lines
drivers/dma/pxa_dma.c +1
-1-tasklet_kill (&c->vc.task); +dma_chan_kill_bh (&c->vc.chan); } }
[Severity: High]
This is a pre-existing issue, but is there a use-after-free vulnerability on driver unbind due to improper devres cleanup ordering?
During initialization, pxad_init_phys() requests IRQs via devm_request_irq(), and subsequently pxad_init_dmadev() allocates channel memory via devm_kzalloc(). Because devres releases resources in reverse order, the channel memory is freed before the IRQ is released.
Since pxad_remove() fails to mask interrupts or explicitly stop the DMA hardware, if the hardware asserts an interrupt during this window:
drivers/dma/pxa_dma.c:pxad_chan_handler() {
...
struct pxad_chan *chan = phy->vchan;
...
spin_lock(&chan->vc.lock);
...
}Will this dereference the already freed phy->vchan pointer and attempt to acquire a freed spinlock if an interrupt fires during teardown?
[Severity: High]
This is also a pre-existing issue, but does driver removal leave a dangling pointer in the OF DMA list?
In pxad_probe(), the driver registers its DMA controller using of_dma_controller_register(), adding an of_dma structure to the global of_dma_list. However, neither pxad_remove() nor any error path calls of_dma_controller_free().
When the driver is unbound, will the of_dma structure remain in the global list, containing pointers to the freed pdev memory and the unloaded pxad_dma_xlate code, leading to a use-after-free if subsequent DMA requests from other devices traverse the DT?
[Severity: High]
This is a pre-existing issue, but is there a potential use-after-free on the probe error path due to missing synchronization of bottom-half work?
In pxad_probe(), if of_dma_controller_register() fails, the function returns an error without flushing bottom-half work:
drivers/dma/pxa_dma.c:pxad_probe() {
...
if (ret < 0) {
dev_err(pdev->slave.dev,
"of_dma_controller_register failed\n");
return ret;
}
...
}Because dmaenginem_async_device_register() executes prior to this failure, the DMA controller is briefly globally visible. If an external client requests a channel and queues bh_work during this window, will the devres cleanup free the channel memory before the asynchronous work executes?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=43
PATCH v2 44/64 dmaengine: mtk-hsdma: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma cleanup.
--- drivers/dma/mediatek/mtk-hsdma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/mediatek/mtk-hsdma.c +1 -1
@@ -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 */
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 44/64] dmaengine: mtk-hsdma: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Killing channel bottom-halves before disabling hardware interrupts allows pending work to be maliciously re-queued and executed after channel memory is freed.
- [High] Manual removal of virtual channels from the device list bypasses the DMA core's cleanup, leaking per-CPU memory and sysfs resources. --
--- Patch [44]: [PATCH v2 44/64] dmaengine: mtk-hsdma: kill vchan BH on teardown --- commit 2d206408161f8be2e557e668a6b7dd498f1ec43d
Author: Allen Pais <[email protected]>
Subject: dmaengine: mtk-hsdma: kill vchan BH on teardown
This commit replaces the tasklet cleanup with dma_chan_kill_bh() for virt-dma cleanup in the mtk-hsdma driver's teardown path.
drivers/dma/mediatek/mtk-hsdma.c static void mtk_hsdma_remove(struct platform_device *pdev) · line 1022
Quoted 8 lines
drivers/dma/mediatek/mtk-hsdma.c +0
-0@@ -1020,7 +1020,7 @@ static void mtk_hsdma_remove(struct platform_device *pdev) vc = &hsdma->vc[i];list_del (&vc->vc.chan.device_node);
[Severity: High]
This is a pre-existing issue, but does removing the channel from the device list here bypass the DMA core's cleanup?
The core function dma_async_device_unregister() relies on iterating over the device->channels list to execute __dma_async_device_channel_unregister(), which frees the per-CPU memory and unregisters the sysfs devices. Because the list is emptied preemptively before calling dma_async_device_unregister(), the core cleanup loop might be bypassed, which could lead to leaking per-cpu variables and orphaned sysfs nodes.
drivers/dma/mediatek/mtk-hsdma.c static void mtk_hsdma_remove(struct platform_device *pdev) · line 1026
Quoted 5 lines
drivers/dma/mediatek/mtk-hsdma.c +1
-1-tasklet_kill (&vc->vc.task); +dma_chan_kill_bh (&vc->vc.chan); }/* Disable DMA interrupt */
[Severity: High]
This is also a pre-existing issue, but does killing the channel bottom-halves before disabling hardware interrupts allow pending work to be maliciously re-queued?
mtk_hsdma_remove() terminates the asynchronous workers here, but the interrupt is disabled and synchronized later in the function:
/* Disable DMA interrupt */ mtk_dma_write(hsdma, MTK_HSDMA_INT_ENABLE, 0); /* Waits for any pending IRQ handlers to complete */ synchronize_irq(hsdma->irq);
If an interrupt triggers or is handled concurrently during this window, the ISR (mtk_hsdma_irq) could complete descriptors and call vchan_cookie_complete(), which re-schedules the channel's bottom-half onto the workqueue. Since the driver continues unbinding and eventually frees the channel structures, the re-scheduled work might execute later and access freed memory.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=44
PATCH v2 45/64 dmaengine: mtk-uart-apdma: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma cleanup.
--- drivers/dma/mediatek/mtk-uart-apdma.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/dma/mediatek/mtk-uart-apdma.c +2 -2
@@ -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); } }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 45/64] dmaengine: mtk-uart-apdma: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] Resource leak on driver unbind due to prematurely emptying the DMA channels list.
- [High] Use-After-Free of channel context due to orphaned IRQ handler on unbind.
- [High] Double decrement of PM runtime usage counter on failure in channel allocation. --
--- Patch [45]: [PATCH v2 45/64] dmaengine: mtk-uart-apdma: kill vchan BH on teardown --- commit 58283f2545c03ebd5b55eff5e9a5d8fdde273b4d
Author: Allen Pais <[email protected]>
dmaengine: mtk-uart-apdma: kill vchan BH on teardown
Use dma_chan_kill_bh() for virt-dma cleanup.
drivers/dma/mediatek/mtk-uart-apdma.c static void mtk_uart_apdma_free_chan_resources(struct dma_chan *chan) · line 313
Quoted 7 lines
drivers/dma/mediatek/mtk-uart-apdma.c +0
-0@@ -312,7 +312,7 @@ static void mtk_uart_apdma_free_chan_resources(struct dma_chan *chan)free_irq (c->irq, chan);
[Severity: High]
This is a pre-existing issue, but is it possible for a use-after-free to occur on unbind?
The IRQ is requested without devres tracking in mtk_uart_apdma_alloc_chan_resources(). It is only freed here in mtk_uart_apdma_free_chan_resources().
If the driver is unbound while a client still actively holds a channel, mtk_uart_apdma_remove() completes without forcing the channel to be freed and without disabling the active IRQ. Since the mtk_chan context is managed by devres, it is automatically freed upon remove() completion. If a hardware interrupt fires after this, could the orphaned handler dereference the already-freed mtk_chan structure?
drivers/dma/mediatek/mtk-uart-apdma.c static void mtk_uart_apdma_free(struct mtk_uart_apdmadev *mtkd) · line 465
Quoted 9 lines
drivers/dma/mediatek/mtk-uart-apdma.c +1
-1-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);
[Severity: High]
This isn't a bug introduced by this patch, but does this list removal cause a resource leak during driver unbind?
In mtk_uart_apdma_remove(), the driver calls mtk_uart_apdma_free() before dma_async_device_unregister(). This loop explicitly removes each channel via list_del(), leaving the mtkd->ddev.channels list empty.
Immediately after, dma_async_device_unregister() iterates over the now-empty device->channels list to tear down the channels. Because the list is empty, does this cause the teardown to be entirely skipped, permanently leaking the per-CPU chan->local memory, IDR slots, and leaving orphaned sysfs nodes?
drivers/dma/mediatek/mtk-uart-apdma.c static void mtk_uart_apdma_free(struct mtk_uart_apdmadev *mtkd) · line 468
Quoted 4 lines
drivers/dma/mediatek/mtk-uart-apdma.c +1
-1-tasklet_kill (&c->vc.task); +dma_chan_kill_bh (&c->vc.chan); } }
[Severity: High]
This is a pre-existing issue, but does mtk_uart_apdma_alloc_chan_resources() double decrement the PM runtime usage counter on failure?
drivers/dma/mediatek/mtk-uart-apdma.c:mtk_uart_apdma_alloc_chan_resources() {
...
ret = pm_runtime_resume_and_get(mtkd->ddev.dev);
if (ret < 0) {
pm_runtime_put_noidle(chan->device->dev);
return ret;
}
...
}Since pm_runtime_resume_and_get() intrinsically drops the PM runtime usage counter if it fails (returns < 0), does calling pm_runtime_put_noidle() again in the error path result in a double decrement and unbalanced runtime PM state?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=45
PATCH v2 46/64 dmaengine: imx-sdma: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma cleanup.
--- drivers/dma/imx-sdma.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/dma/imx-sdma.c +2 -2
@@ -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); }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 46/64] dmaengine: imx-sdma: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
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 <[email protected]>
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.
Quoted 4 lines
drivers/dma/imx-sdma.c +0
-0@@ -2399,11 +2399,11 @@ static void sdma_remove(struct platform_device *pdev)int i;
drivers/dma/imx-sdma.c static void sdma_remove(struct platform_device *pdev) · line 2407
Quoted 10 lines
drivers/dma/imx-sdma.c +2
-2devm_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?
Quoted 2 lines
drivers/dma/imx-sdma.c +0
-0}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=46
PATCH v2 47/64 dmaengine: loongson1-apb: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma cleanup.
--- drivers/dma/loongson/loongson1-apb-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/loongson/loongson1-apb-dma.c +1 -1
@@ -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); } } }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 47/64] dmaengine: loongson1-apb: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [High] The patch series breaks bisectability and leaves the tree unbuildable by prematurely removing the task member from struct virt_dma_chan before updating all consuming drivers.
Pre-existing issues:
- [High] Premature removal of DMA channels from the dma_device channels list bypasses dmaengine core unregistration, causing massive resource leaks (sysfs nodes, IDA entries, per-CPU memory) and leading to a Use-After-Free vulnerability.
- [High] Flushing the bottom-half work without stopping the hardware and masking interrupts leaves a race window where an active IRQ can reschedule the bottom-half on freed memory, leading to a Use-After-Free. --
--- Patch [47]: [PATCH v2 47/64] dmaengine: loongson1-apb: kill vchan BH on teardown --- commit 3e0d38c61679a38b8f14c3f667abc7411f9f4c83
Author: Allen Pais <[email protected]>
dmaengine: loongson1-apb: kill vchan BH on teardown
Use dma_chan_kill_bh() for virt-dma cleanup.
[Severity: High]
Does this patch series introduce a build regression that breaks bisectability?
If the task member was removed from struct virt_dma_chan earlier in this series, unmodified drivers that still rely on it, such as bcm2835-dma.c in bcm2835_dma_free_chan_resources(), will fail to build.
Should all consuming drivers be updated to the new API before the task member is removed from the core virt_dma_chan structure?
drivers/dma/loongson/loongson1-apb-dma.c static void ls1x_dma_chan_remove(struct ls1x_dma *dma) · line 554
Quoted 6 lines
drivers/dma/loongson/loongson1-apb-dma.c +0
-0@@ -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);
[Severity: High]
This is a pre-existing issue, but does removing the channel from the device_node list here bypass the dmaengine core unregistration?
When the devres-managed dma_async_device_unregister() later runs, the channels list will be empty, causing it to silently skip unregistering the channel devices. This leaks the channel device structures, their IDA entries, and per-CPU memory.
Furthermore, because the channel device is never unregistered, the IRQ handler registered via devm_request_irq() in ls1x_dma_alloc_chan_resources() is never freed and remains active indefinitely. Since the platform device's devres will eventually free the channel memory, can an active IRQ trigger a use-after-free on that freed memory?
drivers/dma/loongson/loongson1-apb-dma.c static void ls1x_dma_chan_remove(struct ls1x_dma *dma) · line 555
Quoted 2 lines
drivers/dma/loongson/loongson1-apb-dma.c +1
-1-tasklet_kill (&chan->vc.task); +dma_chan_kill_bh (&chan->vc.chan);
[Severity: High]
This isn't a bug introduced by this patch, but is there a race condition here since the hardware isn't stopped first?
In ls1x_dma_remove(), the hardware DMA controller is not stopped and its interrupts are not masked before teardown. Because the IRQ is still active, a hardware interrupt can fire immediately after dma_chan_kill_bh() returns.
If the interrupt handler calls vchan_cookie_complete(), which then calls dma_chan_schedule_bh(), it will requeue the work. When the ls1x_dma structure containing the work item is later freed by devres, could the workqueue process freed memory and cause a use-after-free or corrupt the workqueue linked list?
Quoted 3 lines
drivers/dma/loongson/loongson1-apb-dma.c +0
-0} } }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=47
PATCH v2 48/64 dmaengine: owl-dma: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma cleanup.
--- drivers/dma/owl-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/owl-dma.c +1 -1
@@ -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); } }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 48/64] dmaengine: owl-dma: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Pre-existing issues:
- [High] The interrupt handler incorrectly processes cyclic DMA transactions as if they were one-shot transfers, breaking cyclic DMA (e.g., audio playback) by stopping the channel after the first period.
- [High] Virtual channels stall indefinitely if their transactions are queued when all physical DMA channels are busy.
- [High] Missing device_synchronize callback exposes client drivers to Use-After-Free vulnerabilities during channel teardown.
- [High] The driver fails to halt active hardware DMA channels during remove(), causing rogue DMA transfers and potential bus lockups.
--
--- Patch [48]: [PATCH v2 48/64] dmaengine: owl-dma: kill vchan BH on teardown --- commit 2420d60507a6a038239d14db32bd418a2fe41702
Author: Allen Pais <[email protected]>
dmaengine: owl-dma: kill vchan BH on teardown
This commit updates the driver to use dma_chan_kill_bh() for virt-dma cleanup during teardown, replacing the older tasklet_kill() interface.
drivers/dma/owl-dma.c static inline void owl_dma_free(struct owl_dma *od) · line 1060
Quoted 12 lines
drivers/dma/owl-dma.c +1
-1@@ -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); } }
[Severity: High]
This isn't a bug introduced by this patch, but does the interrupt handler incorrectly process cyclic DMA transactions as if they were one-shot transfers?
In drivers/dma/owl-dma.c:owl_dma_interrupt():
txd = vchan->txd;
if (txd) {
vchan->txd = NULL;
vchan_cookie_complete(&txd->vd);
if (vchan_next_desc(&vchan->vc))
owl_dma_start_next_txd(vchan);
else
owl_dma_phy_free(od, vchan);
}It appears this unconditionally completes and removes the descriptor without checking txd->cyclic or calling vchan_cyclic_callback(). Can this break cyclic DMA transfers by stopping the channel after the first period?
[Severity: High]
This is a pre-existing issue, but could virtual channels stall indefinitely if their transactions are queued when all physical DMA channels are busy?
When allocating a physical channel in owl_dma_phy_alloc_and_start():
pchan = owl_dma_get_pchan(od, vchan);
if (!pchan)
return;If no physical channel is available, the request silently remains in the virtual queue. However, when a physical channel is later freed in owl_dma_phy_free():
owl_dma_terminate_pchan(od, vchan->pchan);
vchan->pchan = NULL;The physical channel is released, but it doesn't appear to be reassigned to other waiting virtual channels. Does this cause queued transfers on those unassigned virtual channels to stall indefinitely?
[Severity: High]
While this isn't a bug introduced by your change, is the device_synchronize callback missing from the DMA engine operations?
In drivers/dma/owl-dma.c:owl_dma_probe():
od->dma.device_pause = owl_dma_pause;
od->dma.device_resume = owl_dma_resume;
od->dma.device_terminate_all = owl_dma_terminate_all;
od->dma.src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);Without mapping device_synchronize to vchan_synchronize, dmaengine_terminate_sync() acts as a no-op for synchronization. Could this expose client drivers to use-after-free vulnerabilities if the bottom-half continues to run concurrently after the client assumes the channel is safely halted?
[Severity: High]
This is a pre-existing issue, but does the driver fail to cleanly halt active hardware DMA channels during removal?
In drivers/dma/owl-dma.c:owl_dma_remove():
of_dma_controller_free(pdev->dev.of_node);
dma_async_device_unregister(&od->dma);
/* Mask all interrupts for this execution environment */
dma_writel(od, OWL_DMA_IRQ_EN0, 0x0);
/* Make sure we won't have any further interrupts */
devm_free_irq(od->dma.dev, od->irq, od);
owl_dma_free(od);
clk_disable_unprepare(od->clk);The removal function destroys driver resources and disables clocks, but it doesn't seem to loop over od->pchans to smoothly halt running physical channels via owl_dma_terminate_pchan(). Can disabling the clock while the DMA controller is actively mastering the bus lock up the system, or cause rogue DMA writes to freed memory?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=48
PATCH v2 49/64 dmaengine: hisi: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma cleanup.
--- drivers/dma/hisi_dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/hisi_dma.c +1 -1
@@ -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); } }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 49/64] dmaengine: hisi: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Race condition during device teardown can lead to a use-after-free because the bottom-half work is killed before IRQs are synchronized, allowing a concurrent IRQ handler to reschedule the bottom-half work on freed memory.
- [High] The driver requests shared IRQs before fully initializing virtual channel structures, risking an uninitialized spinlock access and null pointer dereference if a shared or spurious interrupt occurs early. --
--- Patch [49]: [PATCH v2 49/64] dmaengine: hisi: kill vchan BH on teardown --- commit f8a5265e01e8ff281f13c0af8c64babbb3148800
Author: Allen Pais <[email protected]>
dmaengine: hisi: kill vchan BH on teardown
This commit replaces tasklet_kill() with dma_chan_kill_bh() for virt-dma cleanup during device teardown.
drivers/dma/hisi_dma.c static void hisi_dma_disable_qps(struct hisi_dma_dev *hdma_dev) · line 723
Quoted 10 lines
drivers/dma/hisi_dma.c +1
-1@@ -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);
[Severity: High]
This is a pre-existing issue, but does this teardown sequence leave us open to a use-after-free regression?
During device unbind, devres executes hisi_dma_disable_hw_channels() which calls hisi_dma_disable_qps(). However, devm_free_irq() hasn't been called yet.
If hisi_dma_irq() is concurrently executing on another CPU, it can call vchan_cookie_complete():
drivers/dma/hisi_dma.c:hisi_dma_irq() {
...
vchan_cookie_complete(&desc->vd);
...
}This can schedule the bottom half after dma_chan_kill_bh() has already flushed it. When devres subsequently frees the device memory, could the scheduled bottom half execute on the freed channel structure?
drivers/dma/hisi_dma.c static void hisi_dma_disable_qps(struct hisi_dma_dev *hdma_dev) · line 725
Quoted 2 lines
drivers/dma/hisi_dma.c +0
-0} }
[Severity: High]
This isn't a bug introduced by this patch, but is it possible for a shared interrupt to fire before the channel structures are fully initialized?
In hisi_dma_enable_hw_channels(), the driver requests shared IRQs before calling hisi_dma_enable_qps(), which initializes the channels:
drivers/dma/hisi_dma.c:hisi_dma_enable_hw_channels() {
...
ret = hisi_dma_request_qps_irq(hdma_dev);
...
}If a shared or spurious interrupt fires immediately, hisi_dma_irq() would execute and dereference an uninitialized pointer and lock:
drivers/dma/hisi_dma.c:hisi_dma_irq() {
...
struct hisi_dma_dev *hdma_dev = chan->hdma_dev;
...
spin_lock(&chan->vc.lock);
...
}Could this lead to a NULL pointer dereference or uninitialized lock access?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=49
PATCH v2 50/64 dmaengine: dw-edma: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma cleanup.
--- drivers/dma/dw-edma/dw-edma-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/dw-edma/dw-edma-core.c +1 -1
@@ -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); +dma_chan_kill_bh (&chan->vc.chan);list_del (&chan->vc.chan.device_node); }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 50/64] dmaengine: dw-edma: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Virtual DMA descriptors are leaked during channel teardown and release due to missing vchan_free_chan_resources(), leading to memory leaks and critical use-after-free memory corruption on channel reuse. --
--- Patch [50]: [PATCH v2 50/64] dmaengine: dw-edma: kill vchan BH on teardown --- commit 5cb424de4644af7e57a2169fe1e94e001c0d3927
Author: Allen Pais <[email protected]>
dmaengine: dw-edma: kill vchan BH on teardown
This commit modifies the dw-edma driver to use dma_chan_kill_bh() for cleaning up virt-dma channels during teardown, replacing the older tasklet_kill() usage.
drivers/dma/dw-edma/dw-edma-core.c int dw_edma_remove(struct dw_edma_chip *chip) · line 1173
Quoted 10 lines
drivers/dma/dw-edma/dw-edma-core.c +1
-1@@ -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); +dma_chan_kill_bh (&chan->vc.chan);
[Severity: Critical]
This is a pre-existing issue, but does this teardown sequence leak virtual DMA descriptors because it misses a call to vchan_free_chan_resources()?
If a client queues a DMA transfer, aborts it via dw_edma_device_terminate_all(), and releases the channel, the aborted descriptors are not removed from the virt-dma queues because dw_edma_free_chan_resources() also lacks a call to vchan_free_chan_resources() to clear the pending lists.
When the channel is requested again and started, could dw_edma_start_transfer() mistakenly pull a stale descriptor from vchan_next_desc(), leading to memory corruption and a use-after-free?
Quoted 3 lines
drivers/dma/dw-edma/dw-edma-core.c +0
-0list_del (&chan->vc.chan.device_node); }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=50
PATCH v2 51/64 dmaengine: bcm2835: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma cleanup.
--- drivers/dma/bcm2835-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/bcm2835-dma.c +1 -1
@@ -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,
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 51/64] dmaengine: bcm2835: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing of_dma_controller_free() in the probe error path and remove function leaves a dangling pointer in the OF DMA list.
- [High] Force unbinding the driver while a client holds a DMA channel leaks the channel's IRQ and DMA pool, leading to a Use-After-Free when the IRQ fires. --
--- Patch [51]: [PATCH v2 51/64] dmaengine: bcm2835: kill vchan BH on teardown --- commit 922bf2cdbf2470f43f83cc2698c5817aa6b5f545
Author: Allen Pais <[email protected]>
dmaengine: bcm2835: kill vchan BH on teardown
Use dma_chan_kill_bh() for virt-dma cleanup instead of tasklet_kill().
drivers/dma/bcm2835-dma.c static void bcm2835_dma_free(struct bcm2835_dmadev *od) · line 835
Quoted 13 lines
drivers/dma/bcm2835-dma.c +1
-1@@ -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,
[Severity: High]
This is a pre-existing issue, but I noticed a potential leak and use-after-free if dma_async_device_register() fails in bcm2835_dma_probe() or if the driver is forcefully unbound via sysfs.
In bcm2835_dma_probe(), if dma_async_device_register() fails, the error path jumps to err_no_dma:
drivers/dma/bcm2835-dma.c:bcm2835_dma_probe() {
...
rc = dma_async_device_register(&od->ddev);
if (rc) {
dev_err(&pdev->dev,
"Failed to register slave DMA engine device: %d\n", rc);
goto err_no_dma;
}
...
err_no_dma:
bcm2835_dma_free(od);
return rc;
}Since of_dma_controller_register() was called earlier in the probe function, does this error path leave a dangling pointer in the OF DMA list?
If the devm-managed bcm2835_dmadev struct is freed automatically by devres, any subsequent DMA channel request from another driver matching this device tree node might dereference the freed pointer during the bcm2835_dma_xlate callback.
Similarly, in bcm2835_dma_remove(), the OF DMA controller is not freed:
drivers/dma/bcm2835-dma.c:bcm2835_dma_remove() {
struct bcm2835_dmadev *od = platform_get_drvdata(pdev);
dma_async_device_unregister(&od->ddev);
bcm2835_dma_free(od);
}Should of_dma_controller_free() be called in both of these paths?
[Severity: High]
This is also a pre-existing issue, but I noticed another potential use-after-free and memory leak if the driver is forcefully unbound via sysfs while a client still holds a DMA channel.
When a client requests a channel, an IRQ and a DMA pool are dynamically allocated:
drivers/dma/bcm2835-dma.c:bcm2835_dma_alloc_chan_resources() {
...
return request_irq(c->irq_number, bcm2835_dma_callback,
c->irq_flags, "DMA IRQ", c);
}If the driver is unbound via sysfs, bcm2835_dma_remove() executes and returns without tearing down active channel resources, as the DMA engine core skips forcefully releasing channel resources.
Since the IRQ was requested without devm_, it remains registered after the driver memory (struct bcm2835_chan) is automatically freed by devres.
Could a subsequent hardware interrupt execute bcm2835_dma_callback() and dereference the freed channel pointer?
drivers/dma/bcm2835-dma.c:bcm2835_dma_callback() {
...
spin_lock_irqsave(&c->vc.lock, flags);
...
}Should we ensure that free_irq() and dma_pool_destroy() are called when cleaning up these resources during driver removal?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=51
PATCH v2 52/64 dmaengine: tegra210-adma: kill vchan BH on teardown
In reply to Allen PaisUse dma_chan_kill_bh() for virt-dma cleanup.
--- drivers/dma/tegra210-adma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/tegra210-adma.c +1 -1
@@ -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); +dma_chan_kill_bh (&tdc->vc.chan);free_irq (tdc->irq, tdc);pm_runtime_put (tdc2dev (tdc));
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 52/64] dmaengine: tegra210-adma: kill vchan BH on teardown
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Incorrect ordering of bottom-half termination and IRQ freeing leaves a window for spurious execution and use-after-free.
- [High] Requesting the IRQ before powering up the device via Runtime PM causes a kernel panic if a pending or spurious interrupt fires. --
--- Patch [52]: [PATCH v2 52/64] dmaengine: tegra210-adma: kill vchan BH on teardown --- commit acf1e1ab4d7c08f6577663273485ed69a1d42e71
Author: Allen Pais <[email protected]>
dmaengine: tegra210-adma: kill vchan BH on teardown
Use dma_chan_kill_bh() for virt-dma cleanup.
drivers/dma/tegra210-adma.c static void tegra_adma_free_chan_resources(struct dma_chan *dc) · line 815
Quoted 11 lines
drivers/dma/tegra210-adma.c +1
-1@@ -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); +dma_chan_kill_bh (&tdc->vc.chan);free_irq (tdc->irq, tdc);
[Severity: High]
This is a pre-existing issue, but does this ordering leave a window for a use-after-free?
If a delayed or spurious interrupt fires after dma_chan_kill_bh() flushes the bottom-half, but before free_irq() unregisters the interrupt, couldn't the interrupt handler execute and reschedule the bottom half via vchan_cyclic_callback()?
Because free_irq() only waits for hardirq handlers and does not flush pending bottom halves, this rescheduled bottom half could execute asynchronously after the channel teardown completes and the client driver has freed its buffers.
Would it be safer to call free_irq() before dma_chan_kill_bh() to ensure hardware events are disabled before flushing the work?
drivers/dma/tegra210-adma.c static void tegra_adma_free_chan_resources(struct dma_chan *dc) · line 817
Quoted 2 lines
drivers/dma/tegra210-adma.c +0
-0pm_runtime_put (tdc2dev (tdc));
[Severity: High]
This is also a pre-existing issue, but looking at the allocation side in tegra_adma_alloc_chan_resources(), is it safe to request the IRQ before powering up the device?
drivers/dma/tegra210-adma.c:tegra_adma_alloc_chan_resources() {
...
ret = request_irq(tdc->irq, tegra_adma_isr, 0, dma_chan_name(dc), tdc);
if (ret) {
...
}
ret = pm_runtime_resume_and_get(tdc2dev(tdc));
...
}If the interrupt line is already active from a previous session or a spurious hardware event, the CPU could immediately execute tegra_adma_isr() upon IRQ registration.
Since tegra_adma_isr() unconditionally reads the ADMA_CH_INT_STATUS register, and the device clock is disabled before pm_runtime_resume_and_get(), can this access to unclocked hardware cause a synchronous external abort?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=52
PATCH v2 53/64 dmaengine: fsl-qdma: use dma_chan_kill_bh
In reply to Allen Paisvirt-dma now dispatches completion callbacks through per-channel BH work instead of its tasklet. Cancel that work during teardown before channel storage is released.
--- drivers/dma/fsl-qdma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/fsl-qdma.c +1 -1
@@ -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); +dma_chan_kill_bh (&chan->vchan.chan); } }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 53/64] dmaengine: fsl-qdma: use dma_chan_kill_bh
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] Prematurely removing channels from the dmadev->channels list in fsl_qdma_cleanup_vchan() bypasses core DMA engine unregistration, leading to resource leaks and Use-After-Free vulnerabilities.
- [High] The driver fails to halt the DMA engine during device removal and tears down IRQs before unregistering the DMA controller.
- [High] Unconditionally destroying shared DMA pools in fsl_qdma_free_chan_resources() causes a Use-After-Free for other active channels.
--
--- Patch [53]: [PATCH v2 53/64] dmaengine: fsl-qdma: use dma_chan_kill_bh --- commit 705da09605a777fa7dd32343007b9e054bcb5942
Author: Allen Pais <[email protected]>
dmaengine: fsl-qdma: use dma_chan_kill_bh
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.
drivers/dma/fsl-qdma.c static void fsl_qdma_cleanup_vchan(struct dma_device *dmadev) · line 1257
Quoted 8 lines
drivers/dma/fsl-qdma.c +0
-0@@ -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);
[Severity: High]
This is a pre-existing issue, but does removing this channel from the dmadev->channels list here bypass the core DMA engine unregistration?
If the driver empties the channel list before the core DMA framework unregisters it in fsl_qdma_remove():
drivers/dma/fsl-qdma.c:fsl_qdma_remove() {
...
fsl_qdma_cleanup_vchan(&fsl_qdma->dma_dev);
...
dma_async_device_unregister(&fsl_qdma->dma_dev);
}The framework's cleanup loop will be silently skipped:
drivers/dma/dmaengine.c:dma_async_device_unregister() {
...
list_for_each_entry_safe(chan, n, &device->channels, device_node)
__dma_async_device_channel_unregister(device, chan);
...
}Could this leave sysfs entries exposed and cause a use-after-free when the underlying memory is freed?
drivers/dma/fsl-qdma.c static void fsl_qdma_cleanup_vchan(struct dma_device *dmadev) · line 1260
Quoted 4 lines
drivers/dma/fsl-qdma.c +1
-1-tasklet_kill (&chan->vchan.task); +dma_chan_kill_bh (&chan->vchan.chan); } }
[Severity: High]
This is also a pre-existing issue, but does the driver fail to halt the DMA engine during device removal?
Looking at fsl_qdma_remove(), IRQs are destroyed while the controller might still be active:
drivers/dma/fsl-qdma.c:fsl_qdma_remove() {
...
fsl_qdma_irq_exit(pdev, fsl_qdma);
fsl_qdma_cleanup_vchan(&fsl_qdma->dma_dev);
...
}Since fsl_qdma_halt() is not called here, could the hardware continue performing reads or writes against freed memory?
[Severity: High]
This is another pre-existing issue, but does destroying the shared DMA pools in fsl_qdma_free_chan_resources() cause a use-after-free for other active channels?
During initialization, multiple channels can share the same physical queue structure and memory pools:
drivers/dma/fsl-qdma.c:fsl_qdma_probe() {
...
fsl_chan->queue = fsl_qdma->queue + i % (fsl_qdma->n_queues *
fsl_qdma->block_number);
...
}However, when a channel is freed, the shared pools are unconditionally destroyed:
drivers/dma/fsl-qdma.c:fsl_qdma_free_chan_resources() {
...
dma_pool_destroy(fsl_queue->comp_pool);
dma_pool_destroy(fsl_queue->desc_pool);
...
}If another active channel shares this same queue, will it crash or corrupt memory upon subsequent operations?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=53
PATCH v2 54/64 dmaengine: st_fdma: use dma_chan_kill_bh
In reply to Allen Paisvirt-dma now dispatches completion callbacks through per-channel BH work instead of its tasklet. Cancel that work during teardown before channel storage is released.
--- drivers/dma/st_fdma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/st_fdma.c +1 -1
@@ -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); } }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 54/64] dmaengine: st_fdma: use dma_chan_kill_bh
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Pre-existing issues:
- [High] Calling list_del() in st_fdma_free() prematurely empties the channel list, breaking devres-based DMA engine unregistration and silently leaking resources on probe failure.
- [High] The per-channel completion BH work is never cancelled during driver removal, leading to a Use-After-Free if scheduled before devres frees channel memory.
- [High] The driver lacks a device_synchronize callback and destroys the DMA pool before pending BH work is cancelled, leading to a UAF or NULL pointer dereference on channel release.
- [High] Virtual DMA descriptors are permanently leaked because st_fdma_free_chan_res() omits a call to vchan_free_chan_resources().
- [High] Inverted teardown sequence in st_fdma_remove vs devres allows DMA transactions on partially destroyed hardware.
--
--- Patch [54]: [PATCH v2 54/64] dmaengine: st_fdma: use dma_chan_kill_bh --- commit 4cab1e1fac46ef4cc457cee5eab4226a73880829
Author: Allen Pais <[email protected]>
Subject: dmaengine: st_fdma: use dma_chan_kill_bh
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.
drivers/dma/st_fdma.c static void st_fdma_free(struct st_fdma_dev *fdev) · line 735
Quoted 8 lines
drivers/dma/st_fdma.c +0
-0@@ -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);
[Severity: High]
This is a pre-existing issue, but does calling list_del() here prematurely empty the channel list and break devres-based DMA engine unregistration?
If probe fails at of_dma_controller_register(), st_fdma_free() is called and unlinks all channels from fdev->dma_device.channels.
When devres subsequently invokes dma_async_device_unregister(), it iterates over the now-empty list and skips calling __dma_async_device_channel_unregister(). This could permanently leak DMA engine channel structures, IDA allocations, and sysfs nodes.
drivers/dma/st_fdma.c static void st_fdma_free(struct st_fdma_dev *fdev) · line 738
Quoted 4 lines
drivers/dma/st_fdma.c +1
-1-tasklet_kill (&fchan->vchan.task); +dma_chan_kill_bh (&fchan->vchan.chan); } }
[Severity: High]
This is a pre-existing issue, but since this patch updates teardown paths to use dma_chan_kill_bh(), should st_fdma_remove() be updated as well?
drivers/dma/st_fdma.c:st_fdma_remove() {
...
devm_free_irq(&pdev->dev, fdev->irq, fdev);
st_slim_rproc_put(fdev->slim_rproc);
of_dma_controller_free(pdev->dev.of_node);
}The per-channel completion BH work is never cancelled during driver removal. If the driver is unbound and an interrupt schedules the BH work just before devm_free_irq() disables it, devres will free fdev->chans.
The delayed BH (vchan_complete) would then execute on the freed memory, causing a use-after-free.
[Severity: High]
This is a pre-existing issue, but could destroying the DMA pool without synchronizing against pending BH work lead to a use-after-free or null pointer dereference?
drivers/dma/st_fdma.c:st_fdma_free_chan_res() {
...
dma_pool_destroy(fchan->node_pool);
fchan->node_pool = NULL;
...
}The driver lacks a device_synchronize callback (e.g., pointing to vchan_synchronize). Because of this, dmaengine_synchronize() returns immediately without waiting for pending virt-dma BH work.
If vchan_complete() executes concurrently or shortly after, it invokes st_fdma_free_desc():
drivers/dma/st_fdma.c:st_fdma_free_desc() {
...
dma_pool_free(fdesc->fchan->node_pool, fdesc->node[i].desc,
...
}Passing the null node_pool to dma_pool_free() could crash the kernel.
[Severity: High]
This is a pre-existing issue, but are virtual DMA descriptors permanently leaked here because st_fdma_free_chan_res() omits a call to vchan_free_chan_resources()?
drivers/dma/st_fdma.c:st_fdma_free_chan_res() {
...
// missing call to vchan_free_chan_resources(&fchan->vchan)
...
}If a client prepares a DMA transaction (allocating virtual descriptors onto vchan->desc_allocated) but releases the channel without submitting them, st_fdma_free_chan_res() destroys the hardware node pool.
Because vchan_free_chan_resources() isn't called, the virtual descriptors are orphaned on internal lists, leaking their memory.
[Severity: High]
This is a pre-existing issue, but does the teardown sequence in st_fdma_remove() allow DMA transactions on partially destroyed hardware?
drivers/dma/st_fdma.c:st_fdma_remove() {
...
st_slim_rproc_put(fdev->slim_rproc);
...
}Manual hardware power-down occurs here before the devres-managed dma_async_device_unregister() runs.
During this window, clients can still issue DMA requests via dmaengine_submit(), which would write to unclocked or powered-down hardware registers.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=54
PATCH v2 55/64 dmaengine: dma-axi-dmac: use dma_chan_kill_bh
In reply to Allen Paisvirt-dma now dispatches completion callbacks through per-channel BH work instead of its tasklet. Cancel that work during teardown before channel storage is released.
--- drivers/dma/dma-axi-dmac.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-)
drivers/dma/dma-axi-dmac.c +6 -4
@@ -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; + +dma_chan_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;
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 55/64] dmaengine: dma-axi-dmac: use dma_chan_kill_bh
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [Low] Several other DMA drivers fail to build because they still explicitly reference the removed task member in struct virt_dma_chan.
Pre-existing issues:
- [High] axi_dmac_terminate_all() frees active descriptors without clearing the vc->cyclic pointer, leading to a Use-After-Free in the completion bottom half.
- [High] The driver lacks a devm action or a remove callback to explicitly disable the DMA hardware upon device unbind, causing system hangs if the clock is disabled while a transfer is active. --
--- Patch [55]: [PATCH v2 55/64] dmaengine: dma-axi-dmac: use dma_chan_kill_bh --- commit d769925844316e4d839799af39da3b91a09472d0
Author: Allen Pais <[email protected]>
dmaengine: dma-axi-dmac: use dma_chan_kill_bh
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.
Quoted 6 lines
drivers/dma/dma-axi-dmac.c +0
-0@@ -1195,9 +1195,11 @@ static int axi_dmac_detect_caps(struct axi_dmac *dmac, unsigned int version)return 0 ;
drivers/dma/dma-axi-dmac.c static int axi_dmac_detect_caps(struct axi_dmac *dmac, unsigned int version) · line 1203
Quoted 10 lines
drivers/dma/dma-axi-dmac.c +4
-2} -static void axi_dmac_tasklet_kill (void *task) +static void axi_dmac_kill_bh (void *data) { -tasklet_kill (task); +struct dma_chan *chan = data; + +dma_chan_kill_bh (chan); }
[Severity: Low]
Does this patch series miss updates to other drivers?
Several other DMA drivers fail to build because they still explicitly reference the removed task member in struct virt_dma_chan. For example:
drivers/dma/qcom/bam_dma.c:bam_dma_remove() {
...
tasklet_kill(&bdev->channels[i].vc.task);
...
}Should these drivers be updated to use dma_chan_kill_bh() as well?
[ ... ]
drivers/dma/dma-axi-dmac.c static int axi_dmac_probe(struct platform_device *pdev) · line 1308
Quoted 8 lines
drivers/dma/dma-axi-dmac.c +2
-2@@ -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);
[Severity: High]
This is a pre-existing issue, but can the system hang if the driver is unbound while a transfer is active?
The driver relies entirely on devres for teardown and lacks an explicit remove callback or devm action to halt the DMA hardware.
In axi_dmac_probe(), the clock is enabled via devm_clk_get_enabled(). If the driver is forcefully unbound via sysfs, devres unwinding will disable the clock.
Disabling the clock while the AXI DMA controller is actively performing bus transactions could stall the AXI bus.
Should an action be added here to gracefully halt the DMA engine prior to the clock being disabled?
[Severity: High]
This isn't a bug introduced by this patch, but does axi_dmac_terminate_all() safely handle cyclic descriptor termination?
When a cyclic transfer period completes, it sets vc->cyclic to the active descriptor and schedules the bottom half.
If a client concurrently calls dmaengine_terminate_async(), axi_dmac_terminate_all() frees the active descriptors without clearing the vc->cyclic pointer:
drivers/dma/dma-axi-dmac.c:axi_dmac_terminate_all() {
...
list_splice_tail_init(&chan->active_descs, &head);
spin_unlock_irqrestore(&chan->vchan.lock, flags);
vchan_dma_desc_free_list(&chan->vchan, &head);
...
}When the scheduled BH runs, could it dereference the dangling vc->cyclic pointer to fetch the callback, resulting in a use-after-free?
Quoted 3 lines
drivers/dma/dma-axi-dmac.c +0
-0if (ret)return ret;
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=55
PATCH v2 56/64 dmaengine: omap-dma: use dma_chan_kill_bh
In reply to Allen Paisvirt-dma now dispatches completion callbacks through per-channel BH work instead of its tasklet. Cancel that work during teardown before channel storage is released.
--- drivers/dma/ti/omap-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/ti/omap-dma.c +1 -1
@@ -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); } }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 56/64] dmaengine: omap-dma: use dma_chan_kill_bh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Freeing channel memory in omap_dma_free without clearing od->lch_map leaves a dangling pointer that can be dereferenced by the interrupt handler in the probe error path.
- [High] In omap_dma_remove, the shared IRQ is freed before hardware interrupts are disabled, and the wrong interrupt enable register is cleared.
--
--- Patch [56]: [PATCH v2 56/64] dmaengine: omap-dma: use dma_chan_kill_bh --- commit fe56c19ed9d7f6c471a97210acc16fdccedd520f
Author: Allen Pais <[email protected]>
dmaengine: omap-dma: use dma_chan_kill_bh
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.
Quoted 3 lines
drivers/dma/ti/omap-dma.c +0
-0
[ ... ]
drivers/dma/ti/omap-dma.c static void omap_dma_free(struct omap_dmadev *od) · line 1525
Quoted 7 lines
drivers/dma/ti/omap-dma.c +1
-1@@ -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);
[Severity: High]
This is a pre-existing issue, but does freeing the channel memory here leave a dangling pointer in od->lch_map?
If of_dma_controller_register() fails during probe, omap_dma_free() is called to loop through all channels and free them. If a client has already requested a channel after dma_async_device_register() succeeded, od->irq_enable_mask will be non-zero and od->lch_map will still contain pointers to these now-freed channels.
Since devm_request_irq() leaves the interrupt handler active until probe finishes and devres cleans it up, an incoming interrupt could cause omap_dma_irq() to read the mask, index into od->lch_map, and dereference the freed memory.
Should od->lch_map be cleared when freeing the channel?
[ ... ]
[Severity: High]
This is also a pre-existing issue, but does omap_dma_remove() clear the wrong IRQ enable register and free the shared IRQ too early?
Looking at omap_dma_remove() in drivers/dma/ti/omap-dma.c:
omap_dma_remove() {
...
devm_free_irq(&pdev->dev, irq, od);
...
if (!omap_dma_legacy(od)) {
/* Disable all interrupts */
omap_dma_glbl_write(od, IRQENABLE_L0, 0);
}
...
}The shared IRQ is freed before hardware interrupts are disabled. It also writes to IRQENABLE_L0 instead of IRQENABLE_L1, which was used during setup to enable them.
If the hardware asserts an interrupt after the handler is removed, could it assert the shared line indefinitely and cause an unhandled interrupt storm since the source is never cleared?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=56
PATCH v2 57/64 dmaengine: edma: use dma_chan_kill_bh
In reply to Allen Paisvirt-dma now dispatches completion callbacks through per-channel BH work instead of its tasklet. Cancel that work during teardown before channel storage is released.
--- drivers/dma/ti/edma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/ti/edma.c +1 -1
@@ -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); +dma_chan_kill_bh (&echan->vchan.chan); } }
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 57/64] dmaengine: edma: use dma_chan_kill_bh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Channel unregistration bypass leading to memory leak and sysfs Use-After-Free.
- [High] Missing BH cancellation for memcpy channels causing Use-After-Free during teardown. --
--- Patch [57]: [PATCH v2 57/64] dmaengine: edma: use dma_chan_kill_bh --- commit 03c05180f9212195192cc327d33610888eef85fe
Author: Allen Pais <[email protected]>
dmaengine: edma: use dma_chan_kill_bh
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.
drivers/dma/ti/edma.c static void edma_cleanupp_vchan(struct dma_device *dmadev) · line 2562
Quoted 8 lines
drivers/dma/ti/edma.c +0
-0@@ -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);
[Severity: High]
This is a pre-existing issue, but does removing the channel from the device list here cause problems during device unregistration?
When a user unbinds the driver, edma_remove() calls this function which unconditionally removes all slave channels from dmadev->channels. When dma_async_device_unregister() subsequently runs, it iterates over the now-empty list and entirely skips unregistering the channels.
Can this cause sysfs device nodes, IDAs, and per-cpu variables to leak? If the orphaned sysfs attributes (such as in_use or bytes_transferred) are read after the driver is unbound, could it trigger a use-after-free since the underlying chan structures are freed by devres?
drivers/dma/ti/edma.c static void edma_cleanupp_vchan(struct dma_device *dmadev) · line 2565
Quoted 4 lines
drivers/dma/ti/edma.c +1
-1-tasklet_kill (&echan->vchan.task); +dma_chan_kill_bh (&echan->vchan.chan); } }
[Severity: High]
This is a pre-existing issue, but are we missing a cleanup step for the memcpy channels?
Looking at edma_remove(), edma_cleanupp_vchan() is explicitly called for ecc->dma_slave, but it appears to be completely omitted for ecc->dma_memcpy:
drivers/dma/ti/edma.c:edma_remove() {
...
edma_cleanupp_vchan(&ecc->dma_slave);
...
dma_async_device_unregister(&ecc->dma_slave);
if (ecc->dma_memcpy)
dma_async_device_unregister(ecc->dma_memcpy);
...
}Because the cleanup is skipped, the BH cancellation is never performed for dma_memcpy channels. If a background transfer completes and schedules the BH just before the driver is unbound, could the pending BH execute after devres has freed the channel structures and cause a use-after-free?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=57
PATCH v2 58/64 dmaengine: qcom-adm: use dma_chan_kill_bh
In reply to Allen Paisvirt-dma now dispatches completion callbacks through per-channel BH work instead of its tasklet. Cancel that work during teardown before channel storage is released.
--- drivers/dma/qcom/qcom_adm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/dma/qcom/qcom_adm.c +2 -2
@@ -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); +dma_chan_kill_bh (&achan->vc.chan); +adm_terminate_all (&achan->vc.chan); }devm_free_irq (adev->dev, adev->irq, adev);
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 58/64] dmaengine: qcom-adm: use dma_chan_kill_bh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Teardown race condition in adm_dma_remove() leading to use-after-free
- [High] Missing device_synchronize callback exposes DMA clients to use-after-free --
--- Patch [58]: [PATCH v2 58/64] dmaengine: qcom-adm: use dma_chan_kill_bh --- commit be25e9ce5563c30843636df639280bce697e48d5
Author: Allen Pais <[email protected]>
dmaengine: qcom-adm: use dma_chan_kill_bh
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.
drivers/dma/qcom/qcom_adm.c static void adm_dma_remove(struct platform_device *pdev) · line 921
Quoted 11 lines
drivers/dma/qcom/qcom_adm.c +1
-2@@ -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); +dma_chan_kill_bh (&achan->vc.chan);
[Severity: High]
This is a pre-existing issue, but does this sequence leave a window for a use-after-free?
If adm_dma_remove() calls dma_chan_kill_bh() before devm_free_irq() synchronizes with the interrupt handler, a concurrent interrupt can still fire:
cpu1adm_dma_remove()
writel(0, ...)
dma_chan_kill_bh()cpu2adm_dma_irq()
vchan_cookie_complete()
dma_chan_schedule_bh()
Because cpu1 proceeds to devm_free_irq() which waits for the interrupt handler to finish but does not flush the newly queued bottom-half work, the device context is subsequently freed. The workqueue could then execute the scheduled bottom-half work on the freed memory.
Could devm_free_irq() be explicitly called before tearing down the channels?
[Severity: High]
This isn't a bug introduced by this patch, but does this driver also lack a device_synchronize callback during its initialization in adm_dma_probe()?
When clients call dmaengine_terminate_sync() to abort transactions, it relies on device_synchronize (usually mapped to vchan_synchronize) to wait for callbacks to complete.
Without it, dmaengine_terminate_sync() returns immediately, and clients may free their buffers while the virt-dma bottom-half work is still executing.
Can this lead to a use-after-free in the client's completion callback?
Quoted 4 lines
drivers/dma/qcom/qcom_adm.c +1
-0+adm_terminate_all (&achan->vc.chan); }devm_free_irq (adev->dev, adev->irq, adev);
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=58
PATCH v2 59/64 dmaengine: tegra186-gpc: use dma_chan_kill_bh
In reply to Allen Paisvirt-dma now dispatches completion callbacks through per-channel BH work instead of its tasklet. Cancel that work during teardown before channel storage is released.
--- drivers/dma/tegra186-gpc-dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/dma/tegra186-gpc-dma.c +1 -1
@@ -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); +dma_chan_kill_bh (&tdc->vc.chan); tdc->config_init = false; tdc->slave_id =-1 ; tdc->sid_dir =DMA_TRANS_NONE ;
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 59/64] dmaengine: tegra186-gpc: use dma_chan_kill_bh
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Zero-length DMA requests cause an integer underflow when calculating the hardware word count, resulting in an unbounded 4GB DMA transfer.
- [High] TOCTOU race condition in the interrupt handler tegra_dma_isr leading to a Use-After-Free of dma_desc.
- [High] Incorrect teardown order in tegra_dma_free_chan_resources() allows late interrupts to reschedule the channel bottom-half, leading to a Use-After-Free.
- [High] Ignored error return from tegra_dma_terminate_all() leads to descriptor memory leak and active hardware DMA during channel teardown.
- [High] An unsupported DMA slave bus width causes get_transfer_param() to silently propagate an error code (-EINVAL) into the DMA hardware's MMIO sequence register. --
--- Patch [59]: [PATCH v2 59/64] dmaengine: tegra186-gpc: use dma_chan_kill_bh --- commit a9e2657d46f0ef7a89cb50bf6bca1e73ae39ee11
Author: Allen Pais <[email protected]>
dmaengine: tegra186-gpc: use dma_chan_kill_bh
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.
drivers/dma/tegra186-gpc-dma.c static void tegra_dma_free_chan_resources(struct dma_chan *dc) · line 1284
Quoted 6 lines
drivers/dma/tegra186-gpc-dma.c +0
-0@@ -1284,7 +1284,7 @@ static void tegra_dma_free_chan_resources(struct dma_chan *dc)tegra_dma_terminate_all (dc);
[Severity: High]
This isn't a bug introduced by this patch, but should we check the return value of tegra_dma_terminate_all() here?
If tegra_dma_terminate_all() fails due to a hardware timeout, it returns early without moving the active descriptor to the terminated list and without disabling the hardware.
Since the caller ignores the return value, vchan_free_chan_resources() might fail to locate and free the active descriptor. Could this leave the hardware executing a transfer into client memory that may be subsequently freed?
drivers/dma/tegra186-gpc-dma.c static void tegra_dma_free_chan_resources(struct dma_chan *dc) · line 1287
Quoted 4 lines
drivers/dma/tegra186-gpc-dma.c +1
-1synchronize_irq (tdc->irq); -tasklet_kill (&tdc->vc.task); +dma_chan_kill_bh (&tdc->vc.chan);
[Severity: High]
This is a pre-existing issue, but does killing the bottom-half before freeing the IRQ expose a race window?
If the DMA hardware remains active after a timeout, and dma_chan_kill_bh() is called before free_irq(), could a late interrupt fire and invoke vchan_cookie_complete() or vchan_cyclic_callback()?
This would re-queue the bottom-half work. After vchan_free_chan_resources() subsequently frees all descriptors, the queued bottom-half might execute and dereference the freed memory.
drivers/dma/tegra186-gpc-dma.c static void tegra_dma_free_chan_resources(struct dma_chan *dc) · line 1290
Quoted 3 lines
drivers/dma/tegra186-gpc-dma.c +0
-0tdc->config_init = false; tdc->slave_id =-1 ; tdc->sid_dir =DMA_TRANS_NONE ;
While reviewing the file, I noticed a few other pre-existing issues:
[Severity: High]
This isn't a bug introduced by this patch, but does tegra_dma_isr() have a race condition leading to a use-after-free of dma_desc?
drivers/dma/tegra186-gpc-dma.c:tegra_dma_isr() {
...
struct tegra_dma_desc *dma_desc = tdc->dma_desc;
...
spin_lock_irqsave(&tdc->vc.lock, flags);
...
if (!dma_desc)
goto irq_done;
...
}The shared descriptor pointer is cached into a local variable before acquiring the spinlock.
If the ISR is delayed while another CPU calls tegra_dma_terminate_all(), the descriptor is freed. Could a subsequent DMA transfer quickly trigger another interrupt, causing the delayed ISR to acquire the lock, pass the check, and dereference the stale local pointer?
[Severity: High]
This is a pre-existing issue, but do zero-length DMA requests cause an integer underflow when calculating the hardware word count?
In tegra_dma_prep_dma_memset():
drivers/dma/tegra186-gpc-dma.c:tegra_dma_prep_dma_memset() {
...
sg_req[0].wcount = ((len - 4) >> 2);
...
}The same calculation exists in tegra_dma_prep_dma_memcpy() and tegra_dma_prep_slave_sg().
If a client requests a 0-length DMA transfer, the length checks appear to pass. The computation then underflows to 0x3FFFFFFF. Because the hardware interprets this as N+1 words, could this trigger an unbounded 4GB DMA transfer?
[Severity: High]
This isn't a bug introduced by this patch, but does get_transfer_param() silently propagate an error code to the MMIO sequence register?
drivers/dma/tegra186-gpc-dma.c:get_transfer_param() {
...
*mmio_seq = get_bus_width(tdc, tdc->dma_sconfig.dst_addr_width);
*burst_size = tdc->dma_sconfig.dst_maxburst;
*slave_bw = tdc->dma_sconfig.dst_addr_width;
*csr = TEGRA_GPCDMA_CSR_DMA_MEM2IO_FC;
return 0;
}If a client configures an unsupported dst_addr_width, get_bus_width() returns -EINVAL. This negative error code is assigned directly to *mmio_seq, and the function still returns 0 for success.
Could this signed error be bitwise-ORed with other flags and written directly to the hardware register, corrupting the configuration bitfields?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=59
PATCH v2 60/64 dmaengine: bam-dma: use dma_chan_kill_bh
In reply to Allen Paisvirt-dma now dispatches completion callbacks through per-channel BH work instead of its tasklet. Cancel that work during teardown before channel storage is released.
--- drivers/dma/qcom/bam_dma.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/dma/qcom/bam_dma.c +2 -2
@@ -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); +dma_chan_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); +dma_chan_kill_bh (&bdev->channels[i].vc.chan);if (!bdev->channels[i].fifo_virt)continue ;
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 60/64] dmaengine: bam-dma: use dma_chan_kill_bh
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Pre-existing issues:
- [High] In the probe error path, the BAM interrupts are not masked and the IRQ is not freed before disabling clocks and cleaning up state.
- [High] Hardware registers are accessed while the peripheral clock is disabled in bam_dma_remove, leading to bus faults (Data Abort).
- [High] Failing to drop the PM runtime usage counter on error paths prevents the device from ever entering runtime suspend.
- [High] The driver fails to implement the device_synchronize DMA engine callback, rendering dmaengine_synchronize() a no-op.
- [Medium] Calling clk_disable_unprepare in bam_dma_remove underflows the clock enable count.
--
--- Patch [60]: [PATCH v2 60/64] dmaengine: bam-dma: use dma_chan_kill_bh --- commit 66f1d34a1a1de31180ac528ea6a897680d6ee0d9
Author: Allen Pais <[email protected]>
dmaengine: bam-dma: use dma_chan_kill_bh
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.
drivers/dma/qcom/bam_dma.c static int bam_dma_probe(struct platform_device *pdev) · line 1390
Quoted 10 lines
drivers/dma/qcom/bam_dma.c +1
-1@@ -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); +dma_chan_kill_bh (&bdev->channels[i].vc.chan);
[Severity: High]
This isn't a bug introduced by this patch, but the driver fails to implement the device_synchronize DMA engine callback here in bam_dma_probe().
Because bdev->common.device_synchronize is never assigned, dmaengine_synchronize() returns immediately. This could allow DMA clients to free memory while the completion bottom-half work is still executing concurrently.
Should the driver provide a wrapper that calls vchan_synchronize()?
Quoted 4 lines
drivers/dma/qcom/bam_dma.c +0
-0err_tasklet_kill:tasklet_kill (&bdev->task); err_disable_clk:
clk_disable_unprepare(bdev->bamclk);
[Severity: High]
This is a pre-existing issue, but does this error path leave the BAM interrupts active before disabling the clock?
Because devm_request_irq() is used, the interrupt is not freed until after probe() returns. If an interrupt fires during this window, bam_dma_irq() will access BAM hardware registers while the clock is disabled via clk_disable_unprepare(), which can trigger a synchronous external abort on QCOM platforms.
Could we mask interrupts and free the IRQ explicitly before disabling clocks?
drivers/dma/qcom/bam_dma.c static void bam_dma_remove(struct platform_device *pdev) · line 1416
Quoted 6 lines
drivers/dma/qcom/bam_dma.c +1
-1@@ -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); +dma_chan_kill_bh (&bdev->channels[i].vc.chan);
[Severity: High]
This isn't a bug introduced by this patch, but are hardware registers accessed after the clock is disabled?
Looking at bam_dma_remove(), pm_runtime_force_suspend() is called early, which disables the clock. Immediately after, writel_relaxed() is called to mask interrupts, and bam_dma_terminate_all() accesses hardware. Accessing unclocked peripheral registers on QCOM platforms triggers a fatal synchronous external abort.
Should the hardware shutdown logic occur before forcing PM suspend?
[Severity: Medium]
This is a pre-existing issue, but is there a double-disable of the clock in bam_dma_remove()?
Since pm_runtime_force_suspend() has already suspended the device and disabled the clock, the later call to clk_disable_unprepare() at the end of this function will disable it again, causing the clock enable count to drop below zero.
[Severity: High]
This isn't a bug introduced by this patch, but does the pm_runtime_get_sync() failure path in bam_dma_irq() leak the PM usage counter?
If pm_runtime_get_sync() returns a negative error:
drivers/dma/qcom/bam_dma.c:bam_dma_irq() {
...
ret = pm_runtime_get_sync(bdev->dev);
if (ret < 0)
return IRQ_NONE;
...
}Returning IRQ_NONE immediately without calling pm_runtime_put_noidle() permanently leaks the PM reference count.
Additionally, returning IRQ_NONE without clearing the hardware interrupt status leaves the IRQ asserted, potentially causing an IRQ storm that leads to a CPU soft-lockup.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=60
Re: [PATCH v2 60/64] dmaengine: bam-dma: use dma_chan_kill_bh
In reply to Allen PaisQuoted 32 lines · Allen Pais
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 <[email protected]>--- drivers/dma/qcom/bam_dma.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)drivers/dma/qcom/bam_dma.c +2
-3@@ -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); +dma_chan_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); +dma_chan_kill_bh (&bdev->channels[i].vc.chan);if (!bdev->channels[i].fifo_virt)continue ; --
Quoted 3 lines
2.43.0
PATCH v2 61/64 dmaengine: dw: defer callbacks via channel BH
In reply to Allen PaisQueue completed descriptors on the channel and invoke their callbacks from per-channel BH work instead of the controller tasklet. Add a synchronize hook so termination can wait for deferred callbacks before their descriptor storage is released.
--- drivers/dma/dw/core.c | 42 ++++++++++++++++++++++++++++++++++-------- drivers/dma/dw/regs.h | 1 + 2 files changed, 35 insertions(+), 8 deletions(-)
drivers/dma/dw/core.c +34 -8
@@ -236,25 +236,24 @@ dwc_descriptor_complete(struct dw_dma_chan *dwc, struct dw_desc *desc,struct dma_async_tx_descriptor *txd = &desc->txd;struct dw_desc *child;unsigned long flags; -struct dmaengine_desc_callback cb;dev_vdbg (chan2dev (&dwc->chan),"descriptor %u complete\n" , txd->cookie);spin_lock_irqsave (&dwc->lock, flags);dma_cookie_complete (txd); -if (callback_required) -dmaengine_desc_get_callback (txd, &cb); -else -memset (&cb,0 ,sizeof (cb)); +list_del_init (&desc->desc_node);/* async_tx_ack */ list_for_each_entry (child, &desc->tx_list, desc_node)async_tx_ack (&child->txd);async_tx_ack (&desc->txd); -dwc_desc_put (dwc, desc); +if (callback_required) { +list_add_tail (&desc->desc_node, &dwc->completed_list); +dma_chan_schedule_bh (&dwc->chan); + }else { +dwc_desc_put (dwc, desc); + }spin_unlock_irqrestore (&dwc->lock, flags); - -dmaengine_desc_callback_invoke (&cb,NULL ); }static void dwc_complete_all (struct dw_dma *dw,struct dw_dma_chan *dwc)
@@ -285,6 +284,24 @@ static void dwc_complete_all(struct dw_dma *dw, struct dw_dma_chan *dwc)dwc_descriptor_complete (dwc, desc, true); } +static void dwc_chan_bh (struct dma_chan *chan) +{ +struct dw_dma_chan *dwc =to_dw_dma_chan (chan); +struct dw_desc *desc, *_desc; +struct dmaengine_desc_callback cb; +LIST_HEAD (list); + +spin_lock_irq (&dwc->lock); +list_splice_init (&dwc->completed_list, &list); +spin_unlock_irq (&dwc->lock); + +list_for_each_entry_safe (desc, _desc, &list, desc_node) { +dmaengine_desc_get_callback (&desc->txd, &cb); +dmaengine_desc_callback_invoke (&cb,NULL ); +dwc_desc_put (dwc, desc); + } +} +/* Returns how many bytes were already received from source */ static inline u32 dwc_get_sent (struct dw_dma_chan *dwc) {
@@ -965,6 +982,11 @@ static int dwc_terminate_all(struct dma_chan *chan)return 0 ; } +static void dwc_synchronize (struct dma_chan *chan) +{ +dma_chan_kill_bh (chan); +} +static struct dw_desc *dwc_find_desc (struct dw_dma_chan *dwc,dma_cookie_t c) {struct dw_desc *desc;
@@ -1256,6 +1278,8 @@ int do_dma_probe(struct dw_dma_chip *chip)INIT_LIST_HEAD (&dwc->active_list);INIT_LIST_HEAD (&dwc->queue); +INIT_LIST_HEAD (&dwc->completed_list); +dma_chan_init_bh (&dwc->chan, dwc_chan_bh);channel_clear_bit (dw,CH_EN , dwc->mask);
@@ -1321,6 +1345,7 @@ int do_dma_probe(struct dw_dma_chip *chip) dw->dma.device_pause = dwc_pause; dw->dma.device_resume = dwc_resume; dw->dma.device_terminate_all = dwc_terminate_all; + dw->dma.device_synchronize = dwc_synchronize; dw->dma.device_tx_status = dwc_tx_status; dw->dma.device_issue_pending = dwc_issue_pending;
@@ -1374,6 +1399,7 @@ int do_dma_remove(struct dw_dma_chip *chip)list_for_each_entry_safe (dwc, _dwc, &dw->dma.channels, chan.device_node) { +dma_chan_kill_bh (&dwc->chan);list_del (&dwc->chan.device_node);channel_clear_bit (dw,CH_EN , dwc->mask); }
drivers/dma/dw/regs.h +1 -0
@@ -279,6 +279,7 @@ struct dw_dma_chan {unsigned long flags;struct list_head active_list;struct list_head queue; +struct list_head completed_list;unsigned int descs_allocated;
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 61/64] dmaengine: dw: defer callbacks via channel BH
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Concurrent list modification between dwc_scan_descriptors() and dwc_terminate_all() leads to an…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Concurrent list modification between dwc_scan_descriptors() and dwc_terminate_all() leads to an infinite loop and list corruption. --
--- Patch [61]: [PATCH v2 61/64] dmaengine: dw: defer callbacks via channel BH --- commit d2107b5998f3b825a629b786e29b948ab851a743
Author: Allen Pais <[email protected]>
Subject: dmaengine: dw: defer callbacks via channel BH
Queue completed descriptors on the channel and invoke their callbacks from per-channel BH work instead of the controller tasklet. Add a synchronize hook so termination can wait for deferred callbacks before their descriptor storage is released.
Quoted 16 lines
drivers/dma/dw/core.c +0
-3@@ -236,25 +236,24 @@ dwc_descriptor_complete(struct dw_dma_chan *dwc, struct dw_desc *desc,struct dma_async_tx_descriptor *txd = &desc->txd;struct dw_desc *child;unsigned long flags;dev_vdbg (chan2dev (&dwc->chan),"descriptor %u complete\n" , txd->cookie);spin_lock_irqsave (&dwc->lock, flags);dma_cookie_complete (txd); -if (callback_required) -dmaengine_desc_get_callback (txd, &cb); -else
drivers/dma/dw/core.c dwc_descriptor_complete(struct dw_dma_chan *dwc, struct dw_desc *desc, · line 251
Quoted 10 lines
drivers/dma/dw/core.c +3
-2-memset (&cb,0 ,sizeof (cb)); +list_del_init (&desc->desc_node);/* async_tx_ack */ list_for_each_entry (child, &desc->tx_list, desc_node)async_tx_ack (&child->txd);async_tx_ack (&desc->txd); -dwc_desc_put (dwc, desc); +if (callback_required) { +list_add_tail (&desc->desc_node, &dwc->completed_list);
[Severity: High]
Can this concurrent list modification lead to an infinite loop during channel termination?
If dwc_tx_status() races with dwc_terminate_all(), dwc_scan_descriptors() temporarily drops dwc->lock while iterating over dwc->active_list.
If dwc_terminate_all() runs concurrently, it acquires the lock, splices active_list to a local list, drops the lock, and begins locklessly iterating the local list:
drivers/dma/dw/core.c:dwc_terminate_all() {
...
list_splice_init(&dwc->queue, &list);
list_splice_init(&dwc->active_list, &list);
spin_unlock_irqrestore(&dwc->lock, flags);
/* Flush all pending and queued descriptors */
list_for_each_entry_safe(desc, _desc, &list, desc_node)
dwc_descriptor_complete(dwc, desc, false);
}When dwc_scan_descriptors() resumes, it calls dwc_descriptor_complete() which now modifies desc->desc_node by moving it to dwc->completed_list.
Because dwc_terminate_all()'s lockless list_for_each_entry_safe() traversal may have already prefetched a descriptor that is subsequently moved, its iteration could jump into dwc->completed_list.
This would prevent the loop termination condition from ever matching the stack-allocated list head in dwc_terminate_all(), causing an infinite loop. Could this sequence happen in practice?
Quoted 8 lines
drivers/dma/dw/core.c +4
-2+dma_chan_schedule_bh (&dwc->chan); + }else { +dwc_desc_put (dwc, desc); + }spin_unlock_irqrestore (&dwc->lock, flags); - -dmaengine_desc_callback_invoke (&cb,NULL ); }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=61
PATCH v2 62/64 dmaengine: hidma: defer callbacks via channel BH
In reply to Allen PaisMove 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.
--- drivers/dma/qcom/hidma.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-)
drivers/dma/qcom/hidma.c +16 -2
@@ -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);
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 62/64] dmaengine: hidma: defer callbacks via channel BH
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
New issues:
- [Critical] The per-channel bottom-half (BH) is cancelled too late during driver unbind, allowing it to execute after the hardware context is destroyed and dereference a NULL pointer.
- [High] Missing synchronization and cancellation of BH during channel termination leads to use-after-free and memory leaks.
Pre-existing issues:
- [Critical] List manipulation without holding the channel lock in hidma_terminate_channel causes list corruption and data races.
- [High] Sleeping function called in atomic context and unsafe PM hardware access.
- [High] Unbalanced pm_runtime usage counter causing premature device suspension.
- [High] Invalid pointer dereference due to missing list emptiness check in hidma_callback.
--
--- Patch [62]: [PATCH v2 62/64] dmaengine: hidma: defer callbacks via channel BH --- commit 3c9c3e0127e064313b46165b6ad21a91d6575ed1
Author: Allen Pais <[email protected]>
Subject: dmaengine: hidma: defer callbacks via channel BH
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.
Quoted 5 lines
drivers/dma/qcom/hidma.c +0
-0
drivers/dma/qcom/hidma.c static inline struct hidma_chan *to_hidma_chan(struct dma_chan *dmach) · line 97
Quoted 9 lines
drivers/dma/qcom/hidma.c +5
-1@@ -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);
[Severity: Critical]
Does flushing the bottom half here happen too late in the teardown sequence?
During driver unbind, hidma_remove() calls hidma_ll_uninit() before hidma_free().
hidma_ll_uninit() explicitly sets lldev->trepool = NULL. When hidma_free() then calls dma_chan_kill_bh(), it synchronously flushes the pending bottom half:
hidma_chan_bh()
hidma_process_completed()
hidma_ll_status()
tre = &lldev->trepool[tre_ch];Since trepool is already NULL, won't this cause a deterministic NULL pointer dereference?
Quoted 5 lines
drivers/dma/qcom/hidma.c +2
-0+list_del (&mchan->chan.device_node); + } }static unsigned int nr_desc_prm;
[ ... ]
drivers/dma/qcom/hidma.c static void hidma_process_completed(struct hidma_chan *mchan) · line 170
Quoted 11 lines
drivers/dma/qcom/hidma.c +7
-0@@ -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); +} +
[Severity: High]
Does the bottom half work need to be cancelled when a client terminates the channel?
When a client calls dma_release_channel() or dmaengine_terminate_sync(), the framework eventually calls hidma_free_chan_resources(), which frees the descriptors.
If the bottom half executes concurrently or after this teardown:
hidma_chan_bh()
hidma_process_completed()
list_for_each_entry_safe(mdesc, next, &list, node) {
dmaengine_desc_callback_invoke(&cb, &result);
}Won't it dereference descriptors that were just freed, causing a use-after-free, and subsequently leak orphaned descriptors onto mchan->free?
drivers/dma/qcom/hidma.c static void hidma_callback(void *data) · line 195
Quoted 6 lines
drivers/dma/qcom/hidma.c +0
-0/* * 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);
[Severity: Critical]
This is a pre-existing issue, but does hidma_terminate_channel() safely handle concurrent callbacks?
In hidma_terminate_channel(), the lock is dropped before the hardware is paused:
hidma_terminate_channel()
spin_unlock_irqrestore(&mchan->lock, irqflags);
rc = hidma_ll_disable(dmadev->lldev);
During this window, a completed transaction can trigger hidma_callback() which modifies the list without holding a lock on the local list:
hidma_callback()
if (mdesc->node.next) {
list_move_tail(&mdesc->node, &mchan->completed);}
Since hidma_terminate_channel() subsequently modifies the same list locklessly, can this race with hidma_prep_dma_memcpy() and cause severe list corruption?
[Severity: High]
This is a pre-existing issue, but does hidma_callback() correctly handle an empty active list?
When updating the running descriptor, the code uses list_first_entry():
hidma_callback()
mchan->running = list_first_entry(&mchan->active,
struct hidma_desc, node);
If mchan->active is empty, list_first_entry() does not return NULL but rather an invalid pointer computed from the list head itself.
If hidma_tx_status() is subsequently called, won't it dereference this invalid pointer via mchan->running->desc.cookie and cause an out-of-bounds memory read?
drivers/dma/qcom/hidma.c static void hidma_callback(void *data)
Quoted 6 lines
drivers/dma/qcom/hidma.c +1
-1-hidma_process_completed (mchan); +dma_chan_schedule_bh (&mchan->chan);if (queued) {pm_runtime_mark_last_busy (dmadev->ddev.dev);pm_runtime_put_autosuspend (dmadev->ddev.dev);
[Severity: High]
This isn't a bug introduced by this patch, but is the pm_runtime usage counter decrement balanced with the increment?
In hidma_issue_pending(), pm_runtime_get() is called only once per submitted batch of descriptors.
However, the hardware triggers hidma_callback() for each individual descriptor, and pm_runtime_put_autosuspend() is unconditionally called here for every single descriptor.
If a client submits a batch of multiple descriptors, won't this underflow the PM usage count and trigger device auto-suspension while hardware operations are still in flight?
[Severity: High]
This is a pre-existing issue, but does the tasklet safely call sleeping PM functions?
The driver implements hidma_issue_task() as a tasklet, which runs in softirq (atomic) context, but it calls pm_runtime_get_sync():
hidma_issue_task()
pm_runtime_get_sync(dmadev->ddev.dev);
Since pm_runtime_get_sync() will sleep if the device is suspended, doesn't this trigger a sleeping in atomic bug?
Furthermore, in hidma_issue_pending(), if the asynchronous pm_runtime_get() returns 0, the code falls through and immediately calls hidma_ll_start(). Doesn't this access hardware registers while the device is still unclocked or powered off?
Quoted 1 line
}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=62
PATCH v2 63/64 dmaengine: qcom-gpi: defer callbacks via vchan
In reply to Allen PaisComplete descriptors through virt-dma instead of invoking callbacks directly from event processing. This preserves the transfer result for the shared vchan BH, which invokes the callback and frees the descriptor; drain that BH before releasing channel resources.
--- drivers/dma/qcom/gpi.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-)
drivers/dma/qcom/gpi.c +13 -4
@@ -981,8 +981,12 @@ static void gpi_process_imed_data_event(struct gchan *gchan, result.result =DMA_TRANS_NOERROR ; result.residue = gpi_desc->len - imed_event->length; -dma_cookie_complete (&vd->tx); -dmaengine_desc_get_callback_invoke (&vd->tx, &result); +spin_lock_irqsave (&gchan->vc.lock, flags); +list_del (&vd->node); + vd->tx_result = result; +vchan_cookie_complete (vd); +spin_unlock_irqrestore (&gchan->vc.lock, flags); +return ; gpi_free_desc:spin_lock_irqsave (&gchan->vc.lock, flags);
@@ -1060,8 +1064,12 @@ static void gpi_process_xfer_compl_event(struct gchan *gchan, result.residue = gpi_desc->len - compl_event->length;dev_dbg (gpii->gpi_dev->dev,"Residue %d\n" , result.residue); -dma_cookie_complete (&vd->tx); -dmaengine_desc_get_callback_invoke (&vd->tx, &result); +spin_lock_irqsave (&gchan->vc.lock, flags); +list_del (&vd->node); + vd->tx_result = result; +vchan_cookie_complete (vd); +spin_unlock_irqrestore (&gchan->vc.lock, flags); +return ; gpi_free_desc:spin_lock_irqsave (&gchan->vc.lock, flags);
@@ -2009,6 +2017,7 @@ static void gpi_free_chan_resources(struct dma_chan *chan)/* free all allocated memory */ gpi_free_ring (&gchan->ch_ring, gpii); +dma_chan_kill_bh (&gchan->vc.chan);vchan_free_chan_resources (&gchan->vc);kfree (gchan->config);
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v2 63/64] dmaengine: qcom-gpi: defer callbacks via vchan
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] Incorrect teardown sequence in gpi_free_chan_resources(): dma_chan_kill_bh() is called after…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Incorrect teardown sequence in gpi_free_chan_resources(): dma_chan_kill_bh() is called after gpi_free_ring(), exposing a freed ring buffer to concurrent client callbacks, leading to a divide-by-zero crash.
- [High] The driver defers callbacks to a virt-dma BH but fails to implement the device_synchronize callback, breaking dmaengine_terminate_sync().
--
--- Patch [63]: [PATCH v2 63/64] dmaengine: qcom-gpi: defer callbacks via vchan --- commit e55f43a67f69dbf222b5c7eba067eedde92ac98b
Author: Allen Pais <[email protected]>
dmaengine: qcom-gpi: defer callbacks via vchan
Complete descriptors through virt-dma instead of invoking callbacks directly from event processing. This preserves the transfer result for the shared vchan BH, which invokes the callback and frees the descriptor; drain that BH before releasing channel resources.
Quoted 4 lines
drivers/dma/qcom/gpi.c +0
-0
[ ... ]
drivers/dma/qcom/gpi.c static void gpi_process_xfer_compl_event(struct gchan *gchan, · line 1070
Quoted 10 lines
drivers/dma/qcom/gpi.c +4
-2@@ -1060,8 +1064,12 @@ static void gpi_process_xfer_compl_event(struct gchan *gchan, result.residue = gpi_desc->len - compl_event->length;dev_dbg (gpii->gpi_dev->dev,"Residue %d\n" , result.residue); -dma_cookie_complete (&vd->tx); -dmaengine_desc_get_callback_invoke (&vd->tx, &result); +spin_lock_irqsave (&gchan->vc.lock, flags); +list_del (&vd->node); + vd->tx_result = result; +vchan_cookie_complete (vd);
[Severity: High]
Since this patch defers callbacks to the virt-dma bottom-half, does the driver now need to implement the device_synchronize callback?
When a client driver calls dmaengine_terminate_sync() to ensure no callbacks are running before freeing its DMA buffers, the absence of device_synchronize causes it to fall back to dmaengine_terminate_all(), which merely stops the hardware.
Could the newly introduced deferred BH execute a completion callback after dmaengine_terminate_sync() has already returned, potentially leading to a use-after-free in the client driver?
Should gpi_probe() be updated to set gpi_dev->dma_device.device_synchronize to a function that wraps vchan_synchronize()?
Quoted 5 lines
drivers/dma/qcom/gpi.c +2
-0+spin_unlock_irqrestore (&gchan->vc.lock, flags); +return ; gpi_free_desc:spin_lock_irqsave (&gchan->vc.lock, flags);
[ ... ]
drivers/dma/qcom/gpi.c static void gpi_free_chan_resources(struct dma_chan *chan) · line 2020
Quoted 5 lines
drivers/dma/qcom/gpi.c +1
-0@@ -2009,6 +2017,7 @@ static void gpi_free_chan_resources(struct dma_chan *chan)/* free all allocated memory */ gpi_free_ring (&gchan->ch_ring, gpii); +dma_chan_kill_bh (&gchan->vc.chan);
[Severity: High]
Does this cleanup sequence introduce a race condition that could result in a divide-by-zero crash?
Because dma_chan_kill_bh() is called after gpi_free_ring(), the bottom-half is still permitted to run after the channel's ring has been freed and zero-initialized.
If a deferred callback executes in the BH and calls dmaengine_prep_slave_sg() to queue the next transfer, it will eventually call gpi_prep_slave_sg(). That function calls gpi_ring_num_elements_avail(), which blindly accesses the now zeroed ch_ring and divides by ring->el_size.
Could dma_chan_kill_bh() be moved before gpi_free_ring() to ensure the BH is drained before tearing down the hardware ring resources?
Quoted 3 lines
drivers/dma/qcom/gpi.c +0
-0vchan_free_chan_resources (&gchan->vc);kfree (gchan->config);
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=63
PATCH v2 64/64 dmaengine: switchtec: use channel BH helpers
In reply to Allen PaisReplace the per-channel descriptor completion tasklet with the shared dma_chan BH helpers. Client callbacks remain in softirq context while using the common scheduling and teardown mechanism.
--- drivers/dma/switchtec_dma.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-)
drivers/dma/switchtec_dma.c +8 -8
@@ -133,8 +133,6 @@ struct switchtec_dma_chan {/* Serialize hardware control register access */ spinlock_t hw_ctrl_lock; -struct tasklet_struct desc_task; -/* Serialize descriptor preparation */ spinlock_t submit_lock;bool ring_active;
@@ -584,6 +582,7 @@ static void switchtec_dma_synchronize(struct dma_chan *chan)int rc; +dma_chan_kill_bh (chan);switchtec_dma_abort_desc (swdma_chan,1 ); rc =enable_channel (swdma_chan);
@@ -820,9 +819,10 @@ static int switchtec_dma_resume(struct dma_chan *chan)return ret; } -static void switchtec_dma_desc_task (unsigned long data) +static void switchtec_dma_desc_task (struct dma_chan *chan) { -struct switchtec_dma_chan *swdma_chan = (void *)data; +struct switchtec_dma_chan *swdma_chan = + container_of(chan, struct switchtec_dma_chan, dma_chan);switchtec_dma_cleanup_completed (swdma_chan); }
@@ -832,7 +832,7 @@ static irqreturn_t switchtec_dma_isr(int irq, void *chan)struct switchtec_dma_chan *swdma_chan = chan;if (swdma_chan->comp_ring_active) -tasklet_schedule (&swdma_chan->desc_task); +dma_chan_schedule_bh (&swdma_chan->dma_chan);return IRQ_HANDLED ; }
@@ -1041,6 +1041,7 @@ static void switchtec_dma_free_chan_resources(struct dma_chan *chan)spin_unlock_bh (&swdma_chan->complete_lock);switchtec_dma_chan_stop (swdma_chan); +dma_chan_kill_bh (chan);switchtec_dma_abort_desc (swdma_chan,0 );switchtec_dma_free_desc (swdma_chan);
@@ -1067,8 +1068,6 @@ static int switchtec_dma_chan_init(struct switchtec_dma_dev *swdma_dev,spin_lock_init (&swdma_chan->hw_ctrl_lock);spin_lock_init (&swdma_chan->submit_lock);spin_lock_init (&swdma_chan->complete_lock); -tasklet_init (&swdma_chan->desc_task, switchtec_dma_desc_task, - (unsigned long )swdma_chan); swdma_chan->mmio_chan_fw = swdma_dev->bar + SWITCHTEC_DMAC_CHAN_CFG_STS_OFFSET +
@@ -1116,6 +1115,7 @@ static int switchtec_dma_chan_init(struct switchtec_dma_dev *swdma_dev, chan = &swdma_chan->dma_chan; chan->device = dma;dma_cookie_init (chan); +dma_chan_init_bh (chan, switchtec_dma_desc_task);list_add_tail (&chan->device_node, &dma->channels);
@@ -1138,7 +1138,7 @@ static int switchtec_dma_chan_free(struct pci_dev *pdev,spin_unlock_bh (&swdma_chan->complete_lock);pci_free_irq (pdev, swdma_chan->irq, swdma_chan); -tasklet_kill (&swdma_chan->desc_task); +dma_chan_kill_bh (&swdma_chan->dma_chan);switchtec_dma_chan_stop (swdma_chan);
Signature · 1 line
2.43.0
Re: [PATCH v2 64/64] dmaengine: switchtec: use channel BH helpers
Quoted 5 lines · Allen Pais
Replace the per-channel descriptor completion tasklet with the shared
dma_chanBH helpers. Client callbacks remain in softirq context while using the common scheduling and teardown mechanism.Signed-off-by Allen Pais <[email protected]>
This one also looks good to me, thanks.
[email protected]
AI reviewRe: [PATCH v2 64/64] dmaengine: switchtec: use channel BH helpers
In reply to Allen PaisThank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] dmaengine_synchronize() incorrectly aborts pending descriptors instead of waiting, violating the DMA API contract.
- [High] Lost completion results for out-of-order descriptors leads to incorrect callback data.
- [High] Channel reset in switchtec_dma_synchronize fails to zero host memory, causing permanent desynchronization. --
--- Patch [64]: [PATCH v2 64/64] dmaengine: switchtec: use channel BH helpers --- commit ae138df18f2b5dc2645b9652b5d982dbf7b2fde2
Author: Allen Pais <[email protected]>
dmaengine: switchtec: use channel BH helpers
Replace the per-channel descriptor completion tasklet with the shared dma_chan BH helpers. Client callbacks remain in softirq context while using the common scheduling and teardown mechanism.
Quoted 4 lines
drivers/dma/switchtec_dma.c +0
-0
[ ... ]
drivers/dma/switchtec_dma.c static void switchtec_dma_synchronize(struct dma_chan *chan) · line 586
Quoted 6 lines
drivers/dma/switchtec_dma.c +1
-0@@ -584,6 +582,7 @@ static void switchtec_dma_synchronize(struct dma_chan *chan)int rc; +dma_chan_kill_bh (chan);switchtec_dma_abort_desc (swdma_chan,1 );
[Severity: High]
This is a pre-existing issue, but does switchtec_dma_synchronize() violate the DMA API contract here by actively destroying the channel's state?
The DMA API requires dmaengine_synchronize() to merely wait for the completion of previously terminated or submitted transfers. Instead, this forcefully aborts all pending descriptors and skips their callbacks, which would drop pending transfers and leave clients with missing completions.
[Severity: High]
This is also a pre-existing issue, but further down in switchtec_dma_synchronize(), the software ring state is reset:
spin_lock_bh(&swdma_chan->complete_lock); swdma_chan->comp_ring_active = true; swdma_chan->phase_tag = 0; swdma_chan->tail = 0; swdma_chan->cq_tail = 0;
Does this channel reset fail to zero the backing hardware memory (hw_cq)?
Since the memory retains previous phase tags, the software might incorrectly interpret stale data at index 0 as a new hardware completion, causing it to read garbage and permanently desynchronize from the hardware state machine.
Quoted 2 lines
drivers/dma/switchtec_dma.c +0
-0rc =enable_channel (swdma_chan);
[ ... ]
drivers/dma/switchtec_dma.c static int switchtec_dma_resume(struct dma_chan *chan) · line 827
Quoted 12 lines
drivers/dma/switchtec_dma.c +3
-2@@ -820,9 +819,10 @@ static int switchtec_dma_resume(struct dma_chan *chan)return ret; } -static void switchtec_dma_desc_task (unsigned long data) +static void switchtec_dma_desc_task (struct dma_chan *chan) { -struct switchtec_dma_chan *swdma_chan = (void *)data; +struct switchtec_dma_chan *swdma_chan = + container_of(chan, struct switchtec_dma_chan, dma_chan);switchtec_dma_cleanup_completed (swdma_chan);
[Severity: High]
This isn't a bug introduced by this patch, but does switchtec_dma_cleanup_completed() lose the completion results for out-of-order descriptors?
When the hardware completes a descriptor out-of-order, res is computed but immediately discarded by a continue:
/* Out of order CE */
if (se_idx != tail) {
spin_unlock_bh(&swdma_chan->complete_lock);
continue;
}When se_idx == tail is eventually met, the catch-up loop reuses the exact same local res variable for all queued completions:
do {
dma_cookie_complete(&desc->txd);
dma_descriptor_unmap(&desc->txd);
dmaengine_desc_get_callback_invoke(&desc->txd, &res);
...Could out-of-order DMA completions lose their actual result status and residue, causing them to incorrectly inherit the status of the last sequentially completed descriptor?
Quoted 2 lines
drivers/dma/switchtec_dma.c +0
-0}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=64
Re: [PATCH v2 00/64] dmaengine: migrate channel tasklets to WQ_BH
In reply to Allen PaisQuoted 15 lines · Allen Pais
Hi Arnd, Vinod and Frank,
This series moves DMAengine client completion bottom halves from private tasklets to a common per-channel API backed by
WQ_BH.The first patch introduces the dma_chan_*_bh() API with a tasklet backend and converts virt-dma to it. This separates the driver-facing API from its implementation without changing callback context.
The second patch switches that backend to a dedicated
WQ_BH|WQ_PERCPUworkqueue.WQ_BHkeeps callbacks in softirq context, while the common API centralizes initialization, scheduling, and synchronization. The workqueue helpers remain internal to DMAengine, anddma_chan_kill_bh()drains scheduled callbacks to preservetasklet_kill()semantics.
Quoted 7 lines
The remaining patches convert driver-owned channel completion tasklets and update virt-dma teardown paths. Tasklets used for controller-level processing, recovery, or other non-client-callback work are deliberately left alone because the appropriate replacement may differ by driver.
As agreed in the RFC discussion, selecting hardirq callback delivery is a separate API change and is not part of this series.
Hi Allen,
I looked through the series, and this seems all fine to me, nice work!
Arnd
Re: [PATCH v2 00/64] dmaengine: migrate channel tasklets to WQ_BH
Quoted 20 lines
Quoted 20 lines
Hi Arnd, Vinod and Frank,
This series moves DMAengine client completion bottom halves from private tasklets to a common per-channel API backed by
WQ_BH.The first patch introduces the dma_chan_*_bh() API with a tasklet backend and converts virt-dma to it. This separates the driver-facing API from its implementation without changing callback context.
The second patch switches that backend to a dedicated
WQ_BH|WQ_PERCPUworkqueue.WQ_BHkeeps callbacks in softirq context, while the common API centralizes initialization, scheduling, and synchronization. The workqueue helpers remain internal to DMAengine, anddma_chan_kill_bh()drains scheduled callbacks to preservetasklet_kill()semantics.The remaining patches convert driver-owned channel completion tasklets and update virt-dma teardown paths. Tasklets used for controller-level processing, recovery, or other non-client-callback work are deliberately left alone because the appropriate replacement may differ by driver.
Quoted 8 lines
Quoted 2 lines
As agreed in the RFC discussion, selecting hardirq callback delivery is a separate API change and is not part of this series.
Hi Allen,
I looked through the series, and this seems all fine to me, nice work!
Thank you. Could I get your Ack/Review for the series?
Quoted 1 line
Arnd
Thanks,
Allen
v3 cover dmaengine: migrate channel tasklets to WQ_BH
In reply to Allen PaisHi Arnd, Vinod and Frank,
This series moves DMAengine client completion bottom halves from private tasklets to a common per-channel API backed by WQ_BH.
The first patch introduces the dmaengine_*_bh() API with a tasklet backend and converts virt-dma to it. It also updates every driver that directly accesses the removed virt_dma_chan tasklet, keeping the patch buildable on its own. This separates the driver-facing API from its implementation without changing callback context.
The second patch switches that backend to a dedicated WQ_BH | WQ_PERCPU workqueue. WQ_BH keeps callbacks in softirq context, while the common API centralizes initialization, scheduling, and synchronization. The workqueue helpers remain internal to DMAengine, and dmaengine_kill_bh() drains scheduled callbacks to preserve tasklet_kill() semantics.
The remaining patches convert driver-owned channel completion tasklets. Tasklets used for controller-level processing, recovery, or other non-client-callback work are deliberately left alone because the appropriate replacement may differ by driver.
As agreed in the RFC discussion, selecting hardirq callback delivery is a separate API change and is not part of this series.
Earlier discussion:
https://lore.kernel.org/all/[email protected]/
v2:
https://lore.kernel.org/all/[email protected]/
Changes since v2:
- rename the new public helpers and callback type from dma_chan_* to dmaengine_* as requested by Vinod
- update all direct virt_dma_chan tasklet users in patch 1, fixing the intermediate build failures reported by Sashiko
- fold the resulting 30 empty teardown patches, reducing the series from 64 to 34 patches without dropping any driver changes
- rebase from Linux 7.2-rc5 to d58772d8520c (latest Linus master)
Review tags retained on unchanged standalone patches:
- 09/34 IOAT: Acked-by Dave Jiang
- 17/34 STE DMA40: Reviewed-by Linus Walleij
- 27/34 PLX DMA and 34/34 Switchtec: Reviewed-by Logan Gunthorpe
Review tags received on v2 patches now folded into 01/34:
- v2 41/64 HSU: Reviewed-by Andy Shevchenko
- v2 60/64 BAM DMA: Acked-by Bartosz Golaszewski
The HSU and BAM DMA changes remain in 01/34, but those patch-specific trailers are not attached to the substantially broader 35-file patch. Andy and Bartosz are copied here and on 01/34 for visibility.
Testing:
- x86_64 olddefconfig, make -j$(nproc) W=1 drivers/dma/
- the same DMA build with patch 1 applied independently
- scripts/checkpatch.pl --strict on all 34 patches
Allen Pais (34):
dmaengine: add tasklet-backed channel BH helpers
dmaengine: back channel BH helpers with WQ_BH
dmaengine: apple-admac: use dmaengine BH callback
dmaengine: at_xdmac: move irq bottom half to dmaengine BH
dmaengine: ep93xx: hook callbacks via dmaengine BH
dmaengine: fsldma: migrate tasklet to dmaengine BH
dmaengine: fsl_raid: run completions via dmaengine BH
dmaengine: imx-dma: flip per-chan tasklet to dmaengine BH
dmaengine: ioat: convert cleanup tasklet to dmaengine BH
dmaengine: mmp_pdma: replace per-chan tasklet with dmaengine BH
dmaengine: mmp_tdma: hook completions to dmaengine BH
dmaengine: mv_xor: convert irq tasklet to dmaengine BH
dmaengine: mxs-dma: use dmaengine BH scheduling
dmaengine: nbpfaxi: switch callbacks to dmaengine BH
dmaengine: pch_dma: convert tasklet to dmaengine BH
dmaengine: ppc4xx: replace irq tasklet with dmaengine BH
dmaengine: ste_dma40: convert per-channel tasklet to dmaengine BH
dmaengine: xgene-dma: wire descriptor cleanup to dmaengine BH
dmaengine: xilinx-dma: use dmaengine BH instead of tasklets
dmaengine: xilinx-dpdma: kill vchan BH on remove
dmaengine: zynqmp-dma: switch completion tasklet to dmaengine BH
dmaengine: tegra20-apb: use channel BH helpers
dmaengine: timb_dma: route callbacks via channel BH
dmaengine: txx9dmac: route callbacks via channel BH
dmaengine: mv_xor_v2: use channel BH helpers
dmaengine: mpc512x: route callbacks via channel BH
dmaengine: plx_dma: use channel BH helpers
dmaengine: sf-pdma: route error callbacks through channel BH
dmaengine: pl330: route callbacks via channel BH
dmaengine: altera-msgdma: use channel BH helpers
dmaengine: dw: defer callbacks via channel BH
dmaengine: hidma: defer callbacks via channel BH
dmaengine: qcom-gpi: defer callbacks via vchan
dmaengine: switchtec: use channel BH helpers
drivers/dma/altera-msgdma.c | 14 +-- drivers/dma/apple-admac.c | 16 ++-- drivers/dma/at_xdmac.c | 13 ++- drivers/dma/bcm2835-dma.c | 2 +- drivers/dma/dma-axi-dmac.c | 10 ++- drivers/dma/dma-jz4780.c | 2 +- drivers/dma/dmaengine.c | 89 ++++++++++++++++++- .../dma/dw-axi-dmac/dw-axi-dmac-platform.c | 2 +- drivers/dma/dw-edma/dw-edma-core.c | 2 +- drivers/dma/dw/core.c | 42 +++++++-- drivers/dma/dw/regs.h | 1 + drivers/dma/ep93xx_dma.c | 12 ++- drivers/dma/fsl-edma-common.c | 2 +- drivers/dma/fsl-qdma.c | 2 +- drivers/dma/fsl_raid.c | 11 +-- drivers/dma/fsl_raid.h | 1 - drivers/dma/fsldma.c | 10 +-- drivers/dma/fsldma.h | 1 - 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-dma.c | 26 +++--- drivers/dma/imx-sdma.c | 4 +- drivers/dma/ioat/dma.c | 14 +-- drivers/dma/ioat/dma.h | 3 +- drivers/dma/ioat/init.c | 2 +- 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/mmp_pdma.c | 13 ++- drivers/dma/mmp_tdma.c | 9 +- drivers/dma/mpc512x_dma.c | 74 +++++++++------ drivers/dma/mv_xor.c | 12 +-- drivers/dma/mv_xor.h | 2 - drivers/dma/mv_xor_v2.c | 20 ++--- drivers/dma/mxs-dma.c | 11 ++- drivers/dma/nbpfaxi.c | 13 ++- drivers/dma/owl-dma.c | 2 +- drivers/dma/pch_dma.c | 14 +-- drivers/dma/pl330.c | 18 ++-- drivers/dma/plx_dma.c | 12 +-- drivers/dma/ppc4xx/adma.c | 14 +-- drivers/dma/ppc4xx/adma.h | 2 - drivers/dma/pxa_dma.c | 2 +- drivers/dma/qcom/bam_dma.c | 4 +- drivers/dma/qcom/gpi.c | 17 +++- drivers/dma/qcom/hidma.c | 18 +++- drivers/dma/qcom/qcom_adm.c | 4 +- drivers/dma/sa11x0-dma.c | 2 +- drivers/dma/sf-pdma/sf-pdma.c | 14 ++- drivers/dma/sprd-dma.c | 2 +- drivers/dma/st_fdma.c | 2 +- drivers/dma/ste_dma40.c | 13 ++- drivers/dma/sun6i-dma.c | 2 +- drivers/dma/switchtec_dma.c | 16 ++-- drivers/dma/tegra186-gpc-dma.c | 2 +- drivers/dma/tegra20-apb-dma.c | 18 ++-- 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/timb_dma.c | 21 +++-- drivers/dma/txx9dmac.c | 12 +-- drivers/dma/txx9dmac.h | 1 - drivers/dma/virt-dma.c | 12 +-- drivers/dma/virt-dma.h | 7 +- drivers/dma/xgene-dma.c | 19 ++-- drivers/dma/xilinx/xilinx_dma.c | 20 ++--- drivers/dma/xilinx/xilinx_dpdma.c | 1 + drivers/dma/xilinx/zynqmp_dma.c | 19 ++-- include/linux/dmaengine.h | 28 ++++++ 74 files changed, 482 insertions(+), 311 deletions(-)
Signature · 1 line
2.43.0
PATCH v3 1/34 dmaengine: add tasklet-backed channel BH helpers
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.
--- 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(-)
drivers/dma/bcm2835-dma.c +1 -1
@@ -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,
drivers/dma/dma-axi-dmac.c +6 -4
@@ -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;
drivers/dma/dma-jz4780.c +1 -1
@@ -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 = {
drivers/dma/dmaengine.c +42 -0
48 lines not on this page · raw message
drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c +1 -1
@@ -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); } }
drivers/dma/dw-edma/dw-edma-core.c +1 -1
@@ -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); }
drivers/dma/fsl-edma-common.c +1 -1
@@ -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); } }
drivers/dma/fsl-qdma.c +1 -1
@@ -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); } }
drivers/dma/hisi_dma.c +1 -1
@@ -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); } }
drivers/dma/hsu/hsu.c +1 -1
@@ -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 ;
drivers/dma/idma64.c +2 -2
@@ -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); } }
drivers/dma/img-mdc-dma.c +1 -1
@@ -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);
drivers/dma/imx-sdma.c +2 -2
@@ -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); }
drivers/dma/k3dma.c +1 -1
@@ -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);
drivers/dma/loongson/loongson1-apb-dma.c +1 -1
@@ -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); } } }
drivers/dma/mediatek/mtk-cqdma.c +1 -1
@@ -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 */
drivers/dma/mediatek/mtk-hsdma.c +1 -1
@@ -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 */
drivers/dma/mediatek/mtk-uart-apdma.c +2 -2
@@ -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); } }
drivers/dma/owl-dma.c +1 -1
@@ -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); } }
drivers/dma/pxa_dma.c +1 -1
@@ -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); } }
drivers/dma/qcom/bam_dma.c +2 -2
@@ -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 ;
drivers/dma/qcom/qcom_adm.c +2 -2
@@ -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);
drivers/dma/sa11x0-dma.c +1 -1
@@ -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); } }
drivers/dma/sf-pdma/sf-pdma.c +1 -1
@@ -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); }
drivers/dma/sprd-dma.c +1 -1
@@ -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);
drivers/dma/st_fdma.c +1 -1
@@ -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); } }
drivers/dma/sun6i-dma.c +1 -1
@@ -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); } }
drivers/dma/tegra186-gpc-dma.c +1 -1
@@ -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 ;
drivers/dma/tegra210-adma.c +1 -1
@@ -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));
drivers/dma/ti/edma.c +1 -1
@@ -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); } }
drivers/dma/ti/k3-udma.c +6 -6
@@ -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); }
drivers/dma/ti/omap-dma.c +1 -1
@@ -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); } }
drivers/dma/virt-dma.c +6 -6
@@ -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);
drivers/dma/virt-dma.h +3 -4
@@ -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);
include/linux/dmaengine.h +28 -0
@@ -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)
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v3 01/34] dmaengine: add tasklet-backed channel BH helpers
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
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 <[email protected]>
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.
drivers/dma/mediatek/mtk-cqdma.c static void mtk_cqdma_remove(struct platform_device *pdev) · line 901
Quoted 12 lines
drivers/dma/mediatek/mtk-cqdma.c +1
-1@@ -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.
[ ... ]
drivers/dma/mediatek/mtk-hsdma.c static void mtk_hsdma_remove(struct platform_device *pdev) · line 1026
Quoted 12 lines
drivers/dma/mediatek/mtk-hsdma.c +1
-1@@ -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.
[ ... ]
drivers/dma/pxa_dma.c static void pxad_free_channels(struct dma_device *dmadev) · line 1220
Quoted 11 lines
drivers/dma/pxa_dma.c +1
-1@@ -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.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1