dmaengine: migrate channel tasklets to WQ_BH
Subsystems (37)
v4 cover dmaengine: migrate channel tasklets to WQ_BH
Hi Vinod, Frank and Arnd,
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.
Frank asked about DMAengine users outside drivers/dma/ that still open-code completion tasklets. Those will be converted in a follow-up series once this one has landed, to keep this set from growing further.
Earlier discussion:
https://lore.kernel.org/all/[email protected]/
v2:
https://lore.kernel.org/all/[email protected]/
v3:
https://lore.kernel.org/all/[email protected]/
Changes since v3:
- post as a new thread instead of in reply to v3 (Andy)
- rebase onto v7.4-rc2. The ste_dma40 rework merged for 7.4-rc1 added tasklet_schedule() calls in the d40_queue_start() error paths and a devm action that kills the channel tasklets; both now use the BH helpers. fsldma and mv_xor gained tasklet_kill() calls in their channel remove and error paths, which are converted as well. zynqmp_dma and altera-msgdma only needed context fixes. The new amlogic-dma driver and the new at_hdmac channel cleanup path kill the virt-dma channel tasklet directly and are converted in 01/33.
- drop the DW DMA patch (v3 31/34). Andy considers it unneeded churn ahead of a larger cleanup of that driver.
- pl330: initialize the channel BH at probe time instead of in alloc_chan_resources(). dmaengine_init_bh() warns when called on an already initialized channel, and channels can be re-requested.
- txx9dmac: channels served by the shared controller interrupt keep scanning descriptors from the controller tasklet. v3 scheduled a BH that was never initialized for them.
- timb_dma: recompute TIMBDMA_IER from the channel BH once the channel has been finished, and drain the channel BHs on remove and in the probe error path.
- mpc512x: drain the channel BHs in mpc_dma_remove().
- sf-pdma: reset the retry budget when abandoning a descriptor, and spell out in the changelog how the new error path mirrors the done tasklet (Frank).
- hidma: leave hidma_free() unchanged (Frank). Drain the BH in free_chan_resources() and in remove(), after killing the low-level completion tasklet and before hidma_ll_uninit(), and add a device_synchronize() callback.
- qcom-gpi: synchronize the vchan before the channel ring is freed, and add a device_synchronize() callback.
- collect Frank Li's Reviewed-by from v3. I kept it on patches that only picked up mechanical conversions from the rebase (01, 06, 12, 17, 21, 27, 30). timb_dma, txx9dmac, mpc512x, pl330 and qcom-gpi changed enough that I have not carried his tag forward on those.
The driver changes above came out of going through the Sashiko reports on v3. Most of what it flagged are pre-existing teardown and locking issues in the individual drivers that this series does not make worse; those are out of scope here.
Review tags retained on unchanged standalone patches:
- 09/33 IOAT: Acked-by Dave Jiang
- 17/33 STE DMA40: Reviewed-by Linus Walleij (the rebase only added the mechanical conversions mentioned above)
- 27/33 PLX DMA and 33/33 Switchtec: Reviewed-by Logan Gunthorpe
Review tags received on v2 patches now folded into 01/33:
- 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/33, but those patch-specific trailers are not attached to the substantially broader 37-file patch. Andy and Bartosz are copied here and on 01/33 for visibility.
Testing:
- build-tested on top of v7.4-rc2 with W=1 for drivers/dma/, with no new warnings compared to the base commit:
- arm64, x86_64 and arm allmodconfig
- powerpc 44x/katmai_defconfig + AMCC_PPC440SPE_ADMA, mpc512x_defconfig + MPC512X_DMA, mpc85xx_smp_defconfig + FSL_DMA, and the same with FSL_DMA replaced by FSL_RAID
- mips rbtx49xx_defconfig + TXX9_DMAC
- every patch builds on its own (drivers/dma/) with the arm64 and x86_64 allmodconfig configurations
- the series also applies cleanly on dmaengine/next (currently v7.4-rc1)
- scripts/checkpatch.pl --strict is clean on all 33 patches
- the sf-pdma and hidma changes are compile-tested only; testing on hardware would be appreciated
Allen Pais (33):
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: 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/amlogic-dma.c | 4 +- drivers/dma/apple-admac.c | 16 ++-- drivers/dma/at_hdmac.c | 2 +- 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/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 | 12 +-- 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 | 78 ++++++++++------ drivers/dma/mv_xor.c | 14 +-- 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 | 19 ++-- 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 | 27 +++++- drivers/dma/qcom/hidma.c | 24 ++++- drivers/dma/qcom/qcom_adm.c | 4 +- drivers/dma/sa11x0-dma.c | 2 +- drivers/dma/sf-pdma/sf-pdma.c | 18 +++- drivers/dma/sprd-dma.c | 2 +- drivers/dma/st_fdma.c | 2 +- drivers/dma/ste_dma40.c | 29 +++--- 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 | 41 +++++++-- drivers/dma/txx9dmac.c | 10 +-- 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, 503 insertions(+), 316 deletions(-)
base-commit: 7475ea8b58616b33c04afb976cf0fbaad9dd63fb
Signature · 1 line
2.43.0
PATCH v4 1/33 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/amlogic-dma.c | 4 +- drivers/dma/at_hdmac.c | 2 +- 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 +++++++++++++ 37 files changed, 128 insertions(+), 57 deletions(-)
drivers/dma/amlogic-dma.c +2 -2
@@ -707,10 +707,10 @@ static void aml_dma_remove(struct platform_device *pdev)for (i =0 ; i <MAX_CHAN_ID ; i++) { aml_chan = aml_dma->aml_rch[i];if (aml_chan) -tasklet_kill (&aml_chan->vchan.task); +dmaengine_kill_bh (&aml_chan->vchan.chan); aml_chan = aml_dma->aml_wch[i];if (aml_chan) -tasklet_kill (&aml_chan->vchan.task); +dmaengine_kill_bh (&aml_chan->vchan.chan); }destroy_workqueue (aml_dma->free_wq); }
drivers/dma/at_hdmac.c +1 -1
@@ -1944,7 +1944,7 @@ static void at_dma_cleanup_channels(struct at_dma *atdma) device_node) {/* Disable interrupts */ atc_disable_chan_irq (atdma, i++); -tasklet_kill (&to_at_dma_chan (chan)->vc.task); +dmaengine_kill_bh (chan);list_del (&chan->device_node); } }
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
@@ -1018,7 +1018,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
@@ -1657,7 +1657,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
@@ -1505,7 +1505,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
@@ -1158,7 +1158,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
@@ -1254,7 +1254,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
@@ -499,7 +499,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
@@ -610,14 +610,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
@@ -975,7 +975,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
@@ -547,7 +547,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
@@ -894,7 +894,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
@@ -1032,7 +1032,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
@@ -1050,7 +1050,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
@@ -1216,7 +1216,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
@@ -1389,7 +1389,7 @@ static int bam_dma_probe(struct platform_device *pdev)free_irq (bdev->irq, bdev); 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:
@@ -1416,7 +1416,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
@@ -601,7 +601,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
@@ -1231,7 +1231,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
@@ -1136,7 +1136,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
@@ -2557,7 +2557,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);
@@ -5610,8 +5610,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;
@@ -111,7 +110,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); }/**
@@ -142,7 +141,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); }/**
@@ -239,7 +238,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
@@ -337,6 +342,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;
@@ -375,6 +383,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; };#define DMA_CHAN_ID_STATIC BIT(30)
@@ -1970,6 +1981,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) {
@@ -2017,6 +2031,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
PATCH v4 2/33 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 dmaengine_*_bh() API and its softirq execution context remain unchanged.
Keep the workqueue operations internal to dmaengine. Drain scheduled work in dmaengine_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
@@ -1449,9 +1451,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);dmaengine_bh_work_fn fn =READ_ONCE (chan->bh_work_fn);if (fn)
@@ -1467,7 +1494,7 @@ void dmaengine_init_bh(struct dma_chan *chan, dmaengine_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 (dmaengine_init_bh);
@@ -1477,8 +1504,7 @@ bool dmaengine_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 (dmaengine_schedule_bh);
@@ -1487,7 +1513,7 @@ void dmaengine_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 (dmaengine_kill_bh);
@@ -1687,15 +1713,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> /**
@@ -342,9 +342,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;
@@ -383,7 +383,7 @@ struct dma_chan {void *route_data;void *private; -struct tasklet_struct bh_tasklet; +struct work_struct bh_work;dmaengine_bh_work_fn bh_work_fn;bool bh_work_initialized; };
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v4 02/33] dmaengine: back channel BH helpers with WQ_BH
Thank you for your contribution! Sashiko AI review found 2 potential issues to consider: - [High] dmaengine: dmaengine_kill_bh() returns with bh_work still queued on re-schedule - [Medium]…
Thank you for your contribution! Sashiko AI review found 2 potential issues to consider:
- [High] dmaengine: dmaengine_kill_bh() returns with bh_work still queued on re-schedule
- [Medium] dmaengine: flush_work() in dmaengine_kill_bh() triggers flush dependency warn
--
commit e183b161b65ac0da135cdea21e6916bb3483d703
Author: Allen Pais <[email protected]>
dmaengine: back channel BH helpers with WQ_BH
This commit replaces the per-channel BH tasklet implementation with a dedicated WQ_BH | WQ_PERCPU workqueue while keeping the dmaengine_*_bh() API unchanged.
Quoted 4 lines
drivers/dma/dmaengine.c +0
-0
[ ... ]
Quoted 1 line
drivers/dma/dmaengine.c +0
-0@@ -1449,9 +1451,34 @@ static void dmaengine_destroy_unmap_pool(void)
[ ... ]
Quoted 15 lines
drivers/dma/dmaengine.c +11
-0+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);dmaengine_bh_work_fn fn =READ_ONCE (chan->bh_work_fn);if (fn)@@ -1467,7 +1494,7 @@ void dmaengine_init_bh(struct dma_chan *chan, dmaengine_bh_work_fn fn)
[ ... ]
drivers/dma/dmaengine.c void dmaengine_kill_bh(struct dma_chan *chan) · line 1518
Quoted 8 lines
drivers/dma/dmaengine.c +1
-1@@ -1487,7 +1513,7 @@ void dmaengine_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 (dmaengine_kill_bh);
[Severity: High]
Can dmaengine_kill_bh() return with chan->bh_work still queued on the workqueue?
Unlike tasklet_kill(), which holds TASKLET_STATE_SCHED while waiting for a running tasklet to finish so concurrent tasklet_schedule() calls cannot re-queue it, process_one_work() clears WORK_STRUCT_PENDING_BIT before invoking dma_chan_bh_entry(). When flush_work() runs while chan->bh_work is executing, insert_wq_barrier() places the flush barrier on worker->scheduled, ahead of pool->worklist:
CPU0 CPU1 (bh_worker)
process_one_work(&chan->bh_work)
set_work_pool_and_clear_pending()
dma_chan_bh_entry()
dmaengine_kill_bh(chan)
flush_work(&chan->bh_work)
insert_wq_barrier()
...
dmaengine_schedule_bh(chan)
queue_work(..., &chan->bh_work)
/* dma_chan_bh_entry() returns */
process_scheduled_works()
/* runs barrier from
* worker->scheduled */
/* flush_work() returns */
kfree(chan)
/* processes pool->worklist */
dma_chan_bh_entry()
chan->bh_work_fn <-- use-after-freeThis is a regression when chan->bh_work is re-queued while dma_chan_bh_entry() is running, for example when a BH callback reschedules itself (such as dma_tasklet() in ste_dma40 or mv_chan_slot_cleanup() in mv_xor) or when a driver calls dmaengine_kill_bh() before tearing down its IRQ or timer (such as mtk_cqdma_remove() or tegra_adma_free_chan_resources()). Using cancel_work_sync(&chan->bh_work) or disable_work_sync(&chan->bh_work) depending on whether the channel may be used again would wait for any in-flight execution while preventing or draining concurrent re-schedules.
drivers/dma/dmaengine.c EXPORT_SYMBOL_GPL(dma_run_dependencies); · line 1719
Quoted 9 lines
drivers/dma/dmaengine.c +3
-1@@ -1687,15 +1713,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 );
[Severity: Medium]
Does calling dmaengine_kill_bh() from a WQ_MEM_RECLAIM workqueue or a PF_MEMALLOC task trigger a WARN_ONCE() in check_flush_dependency()?
dmaengine_bh_wq does not have WQ_MEM_RECLAIM set (and WQ_BH workqueues cannot set WQ_MEM_RECLAIM). Because dmaengine_kill_bh() calls flush_work(), which passes from_cancel = false to __flush_work() -> check_flush_dependency(), flushing a pending or running chan->bh_work from a WQ_MEM_RECLAIM context hits the dependency warning:
kernel/workqueue.c:check_flush_dependency() {
if (from_cancel || target_wq->flags & WQ_MEM_RECLAIM)
return;
WARN_ONCE(current->flags & PF_MEMALLOC, ...);
WARN_ONCE((worker && ((worker->current_pwq->wq->flags &
(WQ_MEM_RECLAIM | __WQ_LEGACY)) == WQ_MEM_RECLAIM)), ...);
}For example, sdhci allocates host->complete_wq with WQ_MEM_RECLAIM and runs sdhci_complete_work() -> sdhci_request_done() -> dmaengine_terminate_sync() -> dmaengine_synchronize() -> vchan_synchronize() -> dmaengine_kill_bh(). Using cancel_work_sync() or disable_work_sync() in dmaengine_kill_bh() sets from_cancel = true and avoids this warning.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
PATCH v4 3/33 dmaengine: apple-admac: use dmaengine BH callback
In reply to Allen PaisReplace the per-channel tasklet with the shared dmaengine BH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.
--- drivers/dma/apple-admac.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-)
drivers/dma/apple-admac.c +6 -10
@@ -97,7 +97,6 @@ struct admac_chan {unsigned int no;struct admac_data *host;struct dma_chan chan; -struct tasklet_struct tasklet;u32 carveout;
@@ -530,10 +529,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);
@@ -552,7 +548,7 @@ static void admac_synchronize(struct dma_chan *chan)list_splice_tail_init (&adchan->to_free, &head);spin_unlock_irqrestore (&adchan->lock, flags); -tasklet_kill (&adchan->tasklet); +dmaengine_kill_bh (&adchan->chan);list_for_each_entry_safe (adtx, _adtx, &head, node) {list_del (&adtx->node);
@@ -671,7 +667,7 @@ static void admac_handle_status_desc_done(struct admac_data *ad, int channo) tx->reclaimed_pos %=2 * tx->buf_len;admac_cyclic_write_desc (ad, channo, tx); -tasklet_schedule (&adchan->tasklet); +dmaengine_schedule_bh (&adchan->chan); }spin_unlock_irqrestore (&adchan->lock, flags); }
@@ -721,9 +717,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;
@@ -909,7 +905,7 @@ static int admac_probe(struct platform_device *pdev)INIT_LIST_HEAD (&adchan->issued);INIT_LIST_HEAD (&adchan->to_free);list_add_tail (&adchan->chan.device_node, &dma->channels); -tasklet_setup (&adchan->tasklet, admac_chan_tasklet); +dmaengine_init_bh (&adchan->chan, admac_chan_bh); } err =reset_control_reset (ad->rstc);
Signature · 1 line
2.43.0
PATCH v4 4/33 dmaengine: at_xdmac: move irq bottom half to dmaengine BH
In reply to Allen PaisReplace the per-channel tasklet with the shared dmaengine BH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.
--- 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;
@@ -1754,9 +1753,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;
@@ -1860,7 +1859,7 @@ static irqreturn_t at_xdmac_interrupt(int irq, void *dev_id)if (atchan->irq_status & (AT_XDMAC_CIS_RBEIS |AT_XDMAC_CIS_WBEIS ))at_xdmac_write (atxdmac,AT_XDMAC_GD , atchan->mask); -tasklet_schedule (&atchan->tasklet); +dmaengine_schedule_bh (&atchan->chan); ret =IRQ_HANDLED ; }
@@ -2310,7 +2309,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" );
@@ -2390,7 +2389,7 @@ static int at_xdmac_probe(struct platform_device *pdev)spin_lock_init (&atchan->lock);INIT_LIST_HEAD (&atchan->xfers_list);INIT_LIST_HEAD (&atchan->free_descs_list); -tasklet_setup (&atchan->tasklet, at_xdmac_tasklet); +dmaengine_init_bh (&atchan->chan, at_xdmac_tasklet);/* Clear pending interrupts. */ while (at_xdmac_chan_read (atchan,AT_XDMAC_CIS ))
@@ -2451,7 +2450,7 @@ static void at_xdmac_remove(struct platform_device *pdev)for (i =0 ; i < atxdmac->dma.chancnt; i++) {struct at_xdmac_chan *atchan = &atxdmac->chan[i]; -tasklet_kill (&atchan->tasklet); +dmaengine_kill_bh (&atchan->chan);at_xdmac_free_chan_resources (&atchan->chan); } }
Signature · 1 line
2.43.0
PATCH v4 5/33 dmaengine: ep93xx: hook callbacks via dmaengine BH
In reply to Allen PaisReplace the per-channel tasklet with the shared dmaengine BH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.
--- 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;
@@ -796,9 +794,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);
@@ -853,12 +851,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); +dmaengine_schedule_bh (&edmac->chan);break ;case INTERRUPT_NEXT_BUFFER :if (test_bit (EP93XX_DMA_IS_CYCLIC , &edmac->flags)) -tasklet_schedule (&edmac->tasklet); +dmaengine_schedule_bh (&edmac->chan);break ;default :
@@ -1414,7 +1412,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); +dmaengine_init_bh (&edmac->chan, ep93xx_dma_tasklet);list_add_tail (&edmac->chan.device_node, &dma_dev->channels);
Signature · 1 line
2.43.0
PATCH v4 6/33 dmaengine: fsldma: migrate tasklet to dmaengine BH
In reply to Allen PaisReplace the per-channel tasklet with the shared dmaengine BH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.
--- drivers/dma/fsldma.c | 12 ++++++------ drivers/dma/fsldma.h | 1 - 2 files changed, 6 insertions(+), 7 deletions(-)
drivers/dma/fsldma.c +6 -6
@@ -970,18 +970,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); +dmaengine_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" );
@@ -1154,7 +1154,7 @@ static int fsl_dma_chan_probe(struct fsldma_device *fdev, } fdev->chan[chan->id] = chan; -tasklet_setup (&chan->tasklet, dma_do_tasklet); +dmaengine_init_bh (&chan->common, dma_do_tasklet);snprintf (chan->name,sizeof (chan->name),"chan%d" , chan->id);/* Initialize the channel */
@@ -1207,7 +1207,7 @@ static int fsl_dma_chan_probe(struct fsldma_device *fdev,static void fsl_dma_chan_remove (struct fsldma_chan *chan) { -tasklet_kill (&chan->tasklet); +dmaengine_kill_bh (&chan->common);irq_dispose_mapping (chan->irq);list_del (&chan->common.device_node);iounmap (chan->regs);
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
PATCH v4 7/33 dmaengine: fsl_raid: run completions via dmaengine BH
In reply to Allen PaisReplace the per-channel tasklet with the shared dmaengine BH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.
--- 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;
@@ -235,7 +236,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); +dmaengine_schedule_bh (&re_chan->chan);return IRQ_HANDLED ; }
@@ -678,7 +679,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); +dmaengine_init_bh (&chan->chan, fsl_re_dequeue); ret =request_irq (chan->irq, fsl_re_isr,0 , chan->name, chandev);if (ret) {
@@ -852,7 +853,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); +dmaengine_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 __iomem *jrregs;int irq; -struct tasklet_struct irqtask;u32 alloc_count;/* hw descriptor ring for inbound queue*/
Signature · 1 line
2.43.0
PATCH v4 8/33 dmaengine: imx-dma: flip per-chan tasklet to dmaengine BH
In reply to Allen PaisReplace the per-channel tasklet with the shared dmaengine BH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.
--- 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 */ +dmaengine_schedule_bh (&imxdmac->chan);dev_dbg (imxdma->dev,"channel %d: watchdog timeout!\n" , imxdmac->channel); }
@@ -390,8 +389,8 @@ static irqreturn_t imxdma_err_handler(int irq, void *dev_id)imx_dmav1_writel (imxdma,1 << i,DMA_DBOSR ); errcode |=IMX_DMA_ERR_BUFFER ; } -/* Tasklet error handler */ -tasklet_schedule (&imxdma->channel[i].dma_tasklet); +/* BH error handler */ +dmaengine_schedule_bh (&imxdma->channel[i].chan); dev_warn(imxdma->dev, "DMA timeout on channel %d -%s%s%s%s\n", i,
@@ -448,8 +447,8 @@ static void dma_irq_handle_channel(struct imxdma_channel *imxdmac)imx_dmav1_writel (imxdma, tmp,DMA_CCR (chno));if (imxdma_chan_is_doing_cyclic (imxdmac)) -/* Tasklet progression */ -tasklet_schedule (&imxdmac->dma_tasklet); +/* BH progression */ +dmaengine_schedule_bh (&imxdmac->chan);return ; }
@@ -462,8 +461,8 @@ static void dma_irq_handle_channel(struct imxdma_channel *imxdmac) out:imx_dmav1_writel (imxdma,0 ,DMA_CCR (chno)); -/* Tasklet irq */ -tasklet_schedule (&imxdmac->dma_tasklet); +/* Schedule the IRQ BH */ +dmaengine_schedule_bh (&imxdmac->chan); }static irqreturn_t dma_irq_handler (int irq,void *dev_id)
@@ -592,9 +591,10 @@ static int imxdma_xfer_desc(struct imxdma_desc *d)return 0 ; } -static void imxdma_tasklet (struct tasklet_struct *t) +static void imxdma_tasklet (struct dma_chan *chan) { -struct imxdma_channel *imxdmac =from_tasklet (imxdmac, t, dma_tasklet); + struct imxdma_channel *imxdmac = container_of(chan, struct imxdma_channel, + chan);struct imxdma_engine *imxdma = imxdmac->imxdma;struct imxdma_desc *desc, *next_desc;unsigned long flags;
@@ -1142,7 +1142,7 @@ static int __init imxdma_probe(struct platform_device *pdev)INIT_LIST_HEAD (&imxdmac->ld_free);INIT_LIST_HEAD (&imxdmac->ld_active); -tasklet_setup (&imxdmac->dma_tasklet, imxdma_tasklet); +dmaengine_init_bh (&imxdmac->chan, imxdma_tasklet); imxdmac->chan.device = &imxdma->dma_device;dma_cookie_init (&imxdmac->chan); imxdmac->channel = i;
@@ -1211,7 +1211,7 @@ static void imxdma_free_irq(struct platform_device *pdev, struct imxdma_engine *if (!is_imx1_dma (imxdma))disable_irq (imxdmac->irq); -tasklet_kill (&imxdmac->dma_tasklet); +dmaengine_kill_bh (&imxdmac->chan); } }
Signature · 1 line
2.43.0
PATCH v4 9/33 dmaengine: ioat: convert cleanup tasklet to dmaengine BH
In reply to Allen PaisReplace the per-channel tasklet with the shared dmaengine BH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.
--- 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); +dmaengine_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); +dmaengine_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 */ +dmaengine_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); +dmaengine_init_bh (&ioat_chan->dma_chan, ioat_cleanup_event); }#define IOAT_NUM_SRC_TEST 6/* must be <= 8 */
Signature · 1 line
2.43.0
PATCH v4 10/33 dmaengine: mmp_pdma: replace per-chan tasklet with dmaengine BH
In reply to Allen PaisReplace the per-channel tasklet with the shared dmaengine BH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.
--- drivers/dma/mmp_pdma.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-)
drivers/dma/mmp_pdma.c +5 -8
@@ -115,7 +115,6 @@ struct mmp_pdma_chan {* is in cyclic mode */ /* channel's basic info */ -struct tasklet_struct tasklet;u32 dcmd;u32 drcmr;u32 dev_addr;
@@ -373,7 +372,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); +dmaengine_schedule_bh (&phy->vchan->chan);return IRQ_HANDLED ; }
@@ -1045,13 +1044,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;
@@ -1168,7 +1165,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); +dmaengine_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
PATCH v4 11/33 dmaengine: mmp_tdma: hook completions to dmaengine BH
In reply to Allen PaisReplace the per-channel tasklet with the shared dmaengine BH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.
--- 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); +dmaengine_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); +dmaengine_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
PATCH v4 12/33 dmaengine: mv_xor: convert irq tasklet to dmaengine BH
In reply to Allen PaisReplace the per-channel tasklet with the shared dmaengine BH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.
--- drivers/dma/mv_xor.c | 14 +++++++------- drivers/dma/mv_xor.h | 2 -- 2 files changed, 7 insertions(+), 9 deletions(-)
drivers/dma/mv_xor.c +7 -7
@@ -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); +dmaengine_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); +dmaengine_schedule_bh (&mv_chan->dmachan);return NULL ; }
@@ -742,7 +742,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); +dmaengine_schedule_bh (&chan->dmachan);mv_chan_clear_eoc_cause (chan);
@@ -1112,7 +1112,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); +dmaengine_init_bh (&mv_chan->dmachan, mv_xor_tasklet);/* clear errors before enabling interrupts */ mv_chan_clear_err_status (mv_chan);
@@ -1167,7 +1167,7 @@ mv_xor_channel_add(struct mv_xor_device *xordev, err_free_irq:free_irq (mv_chan->irq, mv_chan); -tasklet_kill (&mv_chan->irq_tasklet); +dmaengine_kill_bh (&mv_chan->dmachan); err_free_dma:dma_free_wc (&pdev->dev,MV_XOR_POOL_SIZE , mv_chan->dma_desc_pool_virt, mv_chan->dma_desc_pool);
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
PATCH v4 13/33 dmaengine: mxs-dma: use dmaengine BH scheduling
In reply to Allen PaisReplace the per-channel tasklet with the shared dmaengine BH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.
--- 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 */ +dmaengine_schedule_bh (&mxs_chan->chan);return IRQ_HANDLED ; }
@@ -781,7 +780,7 @@ static int mxs_dma_probe(struct platform_device *pdev) mxs_chan->chan.device = &mxs_dma->dma_device;dma_cookie_init (&mxs_chan->chan); -tasklet_setup (&mxs_chan->tasklet, mxs_dma_tasklet); +dmaengine_init_bh (&mxs_chan->chan, mxs_dma_tasklet);/* Add the channel to mxs_chan list */
Signature · 1 line
2.43.0
PATCH v4 14/33 dmaengine: nbpfaxi: switch callbacks to dmaengine BH
In reply to Allen PaisReplace the per-channel tasklet with the shared dmaengine BH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.
--- 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); +dmaengine_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); +dmaengine_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);
@@ -1477,7 +1476,7 @@ static void nbpf_remove(struct platform_device *pdev)devm_free_irq (&pdev->dev, chan->irq, chan); -tasklet_kill (&chan->tasklet); +dmaengine_kill_bh (&chan->dma_chan); }of_dma_controller_free (pdev->dev.of_node);
Signature · 1 line
2.43.0
PATCH v4 15/33 dmaengine: pch_dma: convert tasklet to dmaengine BH
In reply to Allen PaisReplace the per-channel tasklet with the shared dmaengine BH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.
--- 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;
@@ -660,9 +659,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)) {
@@ -702,7 +702,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); +dmaengine_schedule_bh (&pd_chan->chan); ret0 =IRQ_HANDLED ; } }else {
@@ -710,7 +710,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); +dmaengine_schedule_bh (&pd_chan->chan); ret2 =IRQ_HANDLED ; } }
@@ -872,7 +872,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); +dmaengine_init_bh (&pd_chan->chan, pdc_tasklet);list_add_tail (&pd_chan->chan.device_node, &pd->dma.channels); }
@@ -925,7 +925,7 @@ static void pch_dma_remove(struct pci_dev *pdev) device_node) { pd_chan =to_pd_chan (chan); -tasklet_kill (&pd_chan->tasklet); +dmaengine_kill_bh (&pd_chan->chan); }dma_pool_destroy (pd->pool);
Signature · 1 line
2.43.0
PATCH v4 16/33 dmaengine: ppc4xx: replace irq tasklet with dmaengine BH
In reply to Allen PaisReplace the per-channel tasklet with the shared dmaengine BH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.
--- 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); +dmaengine_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); +dmaengine_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); +dmaengine_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); +dmaengine_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); +dmaengine_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
PATCH v4 17/33 dmaengine: ste_dma40: convert per-channel tasklet to dmaengine BH
In reply to Allen PaisReplace the per-channel tasklet with the shared dmaengine BH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.
--- drivers/dma/ste_dma40.c | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-)
drivers/dma/ste_dma40.c +13 -16
@@ -461,13 +461,11 @@ struct d40_base;* @lock: A spinlock to protect this struct. * @log_num: The logical number, if any of this channel. * @pending_tx: The number of pending transfers. Used between interrupt handler -* and tasklet. +* and bottom half. * @busy: Set to true when transfer is ongoing on this channel. * @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.
@@ -494,7 +492,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;
@@ -1646,7 +1643,7 @@ static struct d40_desc *d40_queue_start(struct d40_chan *d40c) d40c->pending_tx++; d40d =d40_first_queued (d40c); }while (d40d); -tasklet_schedule (&d40c->tasklet); +dmaengine_schedule_bh (&d40c->chan);return ERR_PTR (err); } d40c->busy = true;
@@ -1670,7 +1667,7 @@ static struct d40_desc *d40_queue_start(struct d40_chan *d40c) d40c->pending_tx++; d40c->busy = false;pm_runtime_put_autosuspend (d40c->base->dev); -tasklet_schedule (&d40c->tasklet); +dmaengine_schedule_bh (&d40c->chan);return ERR_PTR (err); } }
@@ -1730,13 +1727,13 @@ static void dma_tc_handle(struct d40_chan *d40c) } d40c->pending_tx += callbacks; -tasklet_schedule (&d40c->tasklet); +dmaengine_schedule_bh (&d40c->chan); } -static void dma_tasklet (struct tasklet_struct *t) +static void dma_tasklet (struct dma_chan *chan) { -struct d40_chan *d40c =from_tasklet (d40c, t, tasklet); + struct d40_chan *d40c = container_of(chan, struct d40_chan, chan);struct d40_desc *d40d;unsigned long flags;bool callback_active;
@@ -1786,7 +1783,7 @@ static void dma_tasklet(struct tasklet_struct *t) d40c->pending_tx--;if (d40c->pending_tx) -tasklet_schedule (&d40c->tasklet); +dmaengine_schedule_bh (&d40c->chan);spin_unlock_irqrestore (&d40c->lock, flags);
@@ -3076,14 +3073,14 @@ static void __init d40_chan_init(struct d40_base *base, struct dma_device *dma,INIT_LIST_HEAD (&d40c->client);INIT_LIST_HEAD (&d40c->prepare_queue); -tasklet_setup (&d40c->tasklet, dma_tasklet); +dmaengine_init_bh (&d40c->chan, dma_tasklet);list_add_tail (&d40c->chan.device_node, &dma->channels); } } -static void d40_kill_tasklets (void *data) +static void d40_kill_bh (void *data) {struct dma_device *dma = data;struct d40_chan *d40c;
@@ -3091,7 +3088,7 @@ static void d40_kill_tasklets(void *data)list_for_each_entry (chan, &dma->channels, device_node) { d40c = container_of(chan, struct d40_chan, chan); -tasklet_kill (&d40c->tasklet); +dmaengine_kill_bh (&d40c->chan); } }
@@ -3141,7 +3138,7 @@ static int __init d40_dmaengine_init(struct d40_base *base,d40_ops_init (base, &base->dma_slave); - err =devm_add_action (base->dev, d40_kill_tasklets, + err =devm_add_action (base->dev, d40_kill_bh, &base->dma_slave);if (err) goto exit;
@@ -3161,7 +3158,7 @@ static int __init d40_dmaengine_init(struct d40_base *base,d40_ops_init (base, &base->dma_memcpy); - err =devm_add_action (base->dev, d40_kill_tasklets, + err =devm_add_action (base->dev, d40_kill_bh, &base->dma_memcpy);if (err) goto exit;
@@ -3184,7 +3181,7 @@ static int __init d40_dmaengine_init(struct d40_base *base,d40_ops_init (base, &base->dma_both); - err =devm_add_action (base->dev, d40_kill_tasklets, + err =devm_add_action (base->dev, d40_kill_bh, &base->dma_both);if (err) goto exit;
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v4 17/33] dmaengine: ste_dma40: convert per-channel tasklet to dmaengine BH
Thank you for your contribution! Sashiko AI review found 1 potential issue to consider: - [Medium] dmaengine: ste_dma40: incomplete BH teardown in d40_kill_bh() on self-requeue --
Thank you for your contribution! Sashiko AI review found 1 potential issue to consider:
- [Medium] dmaengine: ste_dma40: incomplete BH teardown in d40_kill_bh() on self-requeue
--
commit 04d3037c70d29e8460f8499e686daa8b7686fd6c
Author: Allen Pais <[email protected]>
dmaengine: ste_dma40: convert per-channel tasklet to dmaengine BH
This commit replaces the per-channel tasklet in ste_dma40 with the shared dmaengine bottom-half helper.
Quoted 4 lines
drivers/dma/ste_dma40.c +0
-0
[ ... ]
Quoted 44 lines
drivers/dma/ste_dma40.c +6
-6@@ -1730,13 +1727,13 @@ static void dma_tc_handle(struct d40_chan *d40c) } d40c->pending_tx += callbacks; -tasklet_schedule (&d40c->tasklet); +dmaengine_schedule_bh (&d40c->chan); } -static void dma_tasklet (struct tasklet_struct *t) +static void dma_tasklet (struct dma_chan *chan) { -struct d40_chan *d40c =from_tasklet (d40c, t, tasklet); + struct d40_chan *d40c = container_of(chan, struct d40_chan, chan);struct d40_desc *d40d;unsigned long flags;bool callback_active;@@ -1786,7 +1783,7 @@ static void dma_tasklet(struct tasklet_struct *t) d40c->pending_tx--;if (d40c->pending_tx) -tasklet_schedule (&d40c->tasklet); +dmaengine_schedule_bh (&d40c->chan);spin_unlock_irqrestore (&d40c->lock, flags);@@ -3076,14 +3073,14 @@ static void __init d40_chan_init(struct d40_base *base, struct dma_device *dma,INIT_LIST_HEAD (&d40c->client);INIT_LIST_HEAD (&d40c->prepare_queue); -tasklet_setup (&d40c->tasklet, dma_tasklet); +dmaengine_init_bh (&d40c->chan, dma_tasklet);list_add_tail (&d40c->chan.device_node, &dma->channels); } } -static void d40_kill_tasklets (void *data) +static void d40_kill_bh (void *data) {struct dma_device *dma = data;struct d40_chan *d40c;
drivers/dma/ste_dma40.c static void d40_kill_tasklets(void *data) · line 3093
Quoted 7 lines
drivers/dma/ste_dma40.c +1
-1@@ -3091,7 +3088,7 @@ static void d40_kill_tasklets(void *data)list_for_each_entry (chan, &dma->channels, device_node) { d40c = container_of(chan, struct d40_chan, chan); -tasklet_kill (&d40c->tasklet); +dmaengine_kill_bh (&d40c->chan); } }
[Severity: Medium]
Can d40_kill_bh() return while d40c->chan.bh_work is still queued if dma_tasklet() re-schedules itself?
dma_tasklet() processes a single transfer per invocation and calls dmaengine_schedule_bh(&d40c->chan) when d40c->pending_tx remains non-zero (for instance, after multiple elapsed cyclic periods in dma_tc_handle()). Previously, tasklet_kill() held TASKLET_STATE_SCHED while waiting for TASKLET_STATE_RUN to clear, preventing dma_tasklet() from re-scheduling itself during teardown.
dmaengine_kill_bh() calls dmaengine_flush_bh_work(), which only runs flush_work(&chan->bh_work). Because process_one_work() clears WORK_STRUCT_PENDING_BIT before invoking dma_tasklet(), a self-requeue in dma_tasklet() queues chan->bh_work behind the flush barrier:
CPU0 CPU1 (dmaengine_bh_wq)d40_kill_bh()
dmaengine_kill_bh(&d40c->chan)
flush_work(&chan->bh_work)
process_one_work()
dma_tasklet()
d40c->pending_tx--;
if (d40c->pending_tx)
dmaengine_schedule_bh(...)
/* flush barrier completes */destroy_cache() / devres release
process_one_work()
dma_tasklet() <-- use-after-free
When d40_kill_bh() runs during probe unwind, d40c->chan.bh_work can execute after base->desc_slab, base->lcla_pool, and base have been freed, causing a use-after-free. Using disable_work_sync() or cancel_work_sync() in dmaengine_kill_bh(), or draining all pending transfers inside dma_tasklet() instead of re-scheduling, would avoid leaving bh_work queued after teardown.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=17
PATCH v4 18/33 dmaengine: xgene-dma: wire descriptor cleanup to dmaengine BH
In reply to Allen PaisReplace the per-channel tasklet with the shared dmaengine BH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.
--- 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); +dmaengine_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); +dmaengine_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); +dmaengine_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); +dmaengine_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); +dmaengine_kill_bh (&chan->dma_chan);xgene_dma_delete_chan_rings (chan); }
Signature · 1 line
2.43.0
PATCH v4 19/33 dmaengine: xilinx-dma: use dmaengine BH instead of tasklets
In reply to Allen PaisReplace the per-channel tasklet with the shared dmaengine BH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.
--- drivers/dma/xilinx/xilinx_dma.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-)
drivers/dma/xilinx/xilinx_dma.c +9 -11
@@ -414,7 +414,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
@@ -453,7 +452,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;
@@ -1173,11 +1171,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); }
@@ -1979,7 +1978,7 @@ static irqreturn_t xilinx_mcdma_irq_handler(int irq, void *data)spin_unlock (&chan->lock); } -tasklet_hi_schedule (&chan->tasklet); +dmaengine_schedule_bh (&chan->common);return IRQ_HANDLED ; }
@@ -2038,7 +2037,7 @@ static irqreturn_t xilinx_dma_irq_handler(int irq, void *data)spin_unlock (&chan->lock); } -tasklet_schedule (&chan->tasklet); +dmaengine_schedule_bh (&chan->common);return IRQ_HANDLED ; }
@@ -2740,7 +2739,7 @@ static void xilinx_dma_synchronize(struct dma_chan *dchan) {struct xilinx_dma_chan *chan =to_xilinx_chan (dchan); -tasklet_kill (&chan->tasklet); +dmaengine_kill_bh (&chan->common); }/**
@@ -2831,7 +2830,7 @@ static void xilinx_dma_chan_remove(struct xilinx_dma_chan *chan)if (chan->irq >0 )free_irq (chan->irq, chan); -tasklet_kill (&chan->tasklet); +dmaengine_kill_bh (&chan->common);list_del (&chan->common.device_node); }
@@ -3161,8 +3160,7 @@ static int xilinx_dma_chan_probe(struct xilinx_dma_device *xdev, str_enabled_disabled(chan->has_sg)); } -/* Initialize the tasklet */ -tasklet_setup (&chan->tasklet, xilinx_dma_do_tasklet); +dmaengine_init_bh (&chan->common, xilinx_dma_do_tasklet);/* * Initialize the DMA channel and add it to the DMA engine channels
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v4 19/33] dmaengine: xilinx-dma: use dmaengine BH instead of tasklets
Thank you for your contribution! Sashiko AI review found 1 potential issue to consider: - [Low] dmaengine: xilinx-dma: HI_SOFTIRQ priority loss in xilinx_mcdma_irq_handler() --
Thank you for your contribution! Sashiko AI review found 1 potential issue to consider:
- [Low] dmaengine: xilinx-dma: HI_SOFTIRQ priority loss in xilinx_mcdma_irq_handler()
--
commit 959cca11cf1a0ddb7da4355d351b00c62da10f51
Author: Allen Pais <[email protected]>
dmaengine: xilinx-dma: use dmaengine BH instead of tasklets
This commit replaces the per-channel tasklet in the Xilinx DMA driver with the shared dmaengine bottom-half helper.
Quoted 4 lines
drivers/dma/xilinx/xilinx_dma.c +0
-0
[ ... ]
drivers/dma/xilinx/xilinx_dma.c static irqreturn_t xilinx_mcdma_irq_handler(int irq, void *data) · line 1983
Quoted 8 lines
drivers/dma/xilinx/xilinx_dma.c +1
-1@@ -1979,7 +1978,7 @@ static irqreturn_t xilinx_mcdma_irq_handler(int irq, void *data)spin_unlock (&chan->lock); } -tasklet_hi_schedule (&chan->tasklet); +dmaengine_schedule_bh (&chan->common);return IRQ_HANDLED ; }
[Severity: Low]
Does replacing tasklet_hi_schedule() with dmaengine_schedule_bh() here regress MCDMA completion priority?
dma_bus_init() allocates dmaengine_bh_wq without WQ_HIGHPRI:
drivers/dma/dmaengine.c:dma_bus_init() {
...
dmaengine_bh_wq = alloc_workqueue("dmaengine_bh",
WQ_BH | WQ_PERCPU, 0);
...
}Because WQ_HIGHPRI is not set, dmaengine_schedule_bh() runs the bottom half in TASKLET_SOFTIRQ rather than HI_SOFTIRQ, which silently undoes c77d4c5081aa ("dmaengine: xilinx_dma: Use tasklet_hi_schedule for timing critical usecase") and places MCDMA completions behind network softirqs.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=19
PATCH v4 20/33 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); +dmaengine_kill_bh (&chan->vchan.chan);list_del (&chan->vchan.chan.device_node); }
Signature · 1 line
2.43.0
PATCH v4 21/33 dmaengine: zynqmp-dma: switch completion tasklet to dmaengine BH
In reply to Allen PaisReplace the per-channel tasklet with the shared dmaengine BH helper. The handler continues to run in softirq context while dmaengine owns the common scheduling and teardown mechanism.
--- drivers/dma/xilinx/zynqmp_dma.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-)
drivers/dma/xilinx/zynqmp_dma.c +9 -10
@@ -205,7 +205,6 @@ struct zynqmp_dma_desc_sw {* @dev: The dma device * @irq: Linux IRQ number, or -1 when not registered * @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
@@ -230,7 +229,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;
@@ -743,7 +741,7 @@ static irqreturn_t zynqmp_dma_irq_handler(int irq, void *data)writel (isr, chan->regs +ZYNQMP_DMA_ISR );if (status &ZYNQMP_DMA_INT_DONE ) { -tasklet_schedule (&chan->tasklet); +dmaengine_schedule_bh (&chan->common); ret =IRQ_HANDLED ; }
@@ -752,7 +750,7 @@ static irqreturn_t zynqmp_dma_irq_handler(int irq, void *data)if (status &ZYNQMP_DMA_INT_ERR ) { chan->err = true; -tasklet_schedule (&chan->tasklet); +dmaengine_schedule_bh (&chan->common);dev_err (chan->dev,"Channel %p has errors\n" , chan); ret =IRQ_HANDLED ; }
@@ -768,11 +766,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;
@@ -823,7 +822,7 @@ static void zynqmp_dma_synchronize(struct dma_chan *dchan) {struct zynqmp_dma_chan *chan =to_chan (dchan); -tasklet_kill (&chan->tasklet); +dmaengine_kill_bh (&chan->common); }/**
@@ -899,7 +898,7 @@ static void zynqmp_dma_chan_remove(struct zynqmp_dma_chan *chan)if (chan->irq >=0 )devm_free_irq (chan->zdev->dev, chan->irq, chan); -tasklet_kill (&chan->tasklet); +dmaengine_kill_bh (&chan->common);if (!list_empty (&chan->common.device_node))list_del (&chan->common.device_node); }
@@ -951,7 +950,7 @@ static int zynqmp_dma_chan_probe(struct zynqmp_dma_device *zdev, chan->is_dmacoherent =of_dma_is_coherent (node); zdev->chan = chan; -tasklet_setup (&chan->tasklet, zynqmp_dma_do_tasklet); +dmaengine_init_bh (&chan->common, zynqmp_dma_do_tasklet);spin_lock_init (&chan->lock);INIT_LIST_HEAD (&chan->active_list);INIT_LIST_HEAD (&chan->pending_list);
Signature · 1 line
2.43.0
PATCH v4 22/33 dmaengine: tegra20-apb: use channel BH helpers
In reply to Allen PaisSwitch the per-channel tasklet to dmaengine_*_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); +dmaengine_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); +dmaengine_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); +dmaengine_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); +dmaengine_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); +dmaengine_kill_bh (&tdc->dma_chan);spin_lock_irqsave (&tdc->lock, flags); busy = tdc->busy;
Signature · 1 line
2.43.0
PATCH v4 23/33 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.
The controller tasklet recomputes TIMBDMA_IER before the deferred work has finished the channel, so have the channel BH recompute and rewrite the mask once the channel state has settled. This keeps the interrupt enable mask consistent with what the single tasklet used to produce.
Drain the channel BHs in td_remove() and in the probe error path so no completion work can run after the controller memory is freed.
--- drivers/dma/timb_dma.c | 41 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-)
drivers/dma/timb_dma.c +36 -5
@@ -433,6 +433,8 @@ static void td_free_chan_resources(struct dma_chan *chan)dev_dbg (dmaengine_chan_dev (chan),"%s: Entry\n" , __func__); +dmaengine_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));
@@ -575,17 +577,42 @@ 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); +dmaengine_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); +struct timb_dma *td =tdchantotd (td_chan); +u32 ier; + +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); + +/* +* The controller tasklet re-armed IER before this channel was +* finished. Recompute it now that the channel state has changed. +*/ + ier =__td_ier_mask (td); +iowrite32 (ier, td->membase +TIMBDMA_IER ); +} + +static void td_kill_chan_bh (struct timb_dma *td,int nr_channels) +{ +int i; + +for (i =0 ; i < nr_channels; i++) +dmaengine_kill_bh (&td->channels[i].chan); +} +static irqreturn_t td_irq (int irq,void *devid) {
@@ -692,6 +719,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); +dmaengine_init_bh (&td_chan->chan, timb_dma_chan_bh); td_chan->descs = pchan->descriptors; td_chan->desc_elems = pchan->descriptor_elements;
@@ -724,6 +752,7 @@ static int td_probe(struct platform_device *pdev)free_irq (irq, td); err_tasklet_kill:tasklet_kill (&td->tasklet); +td_kill_chan_bh (td, pdata->nr_channels);iounmap (td->membase); err_free_mem:kfree (td);
@@ -736,6 +765,7 @@ static int td_probe(struct platform_device *pdev)static void td_remove (struct platform_device *pdev) { +struct timb_dma_platform_data *pdata =dev_get_platdata (&pdev->dev);struct timb_dma *td =platform_get_drvdata (pdev);struct resource *iomem =platform_get_resource (pdev,IORESOURCE_MEM ,0 );int irq =platform_get_irq (pdev,0 );
@@ -743,6 +773,7 @@ static void td_remove(struct platform_device *pdev)dma_async_device_unregister (&td->dma);free_irq (irq, td);tasklet_kill (&td->tasklet); +td_kill_chan_bh (td, pdata->nr_channels);iounmap (td->membase);kfree (td);release_mem_region (iomem->start,resource_size (iomem));
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v4 23/33] dmaengine: timb_dma: route callbacks via channel BH
Thank you for your contribution! Sashiko AI review found 2 potential issues to consider: - [High] dmaengine: timb_dma: race in __td_ier_mask() and TIMBDMA_IER updates - [High] dmaengine: timb_dma…
Thank you for your contribution! Sashiko AI review found 2 potential issues to consider:
- [High] dmaengine: timb_dma: race in __td_ier_mask() and TIMBDMA_IER updates
- [High] dmaengine: timb_dma: race between timb_dma_chan_bh() and channel teardown
--
commit dc5312469115907bacab896cc42d4d204804abfc
Author: Allen Pais <[email protected]>
dmaengine: timb_dma: route callbacks via channel BH
This commit moves per-channel DMA completion handling from the controller tasklet to per-channel BH work items and drains the BH work during channel teardown and driver removal.
Quoted 31 lines
drivers/dma/timb_dma.c +5
-5@@ -433,6 +433,8 @@ static void td_free_chan_resources(struct dma_chan *chan)dev_dbg (dmaengine_chan_dev (chan),"%s: Entry\n" , __func__); +dmaengine_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));@@ -575,17 +577,42 @@ 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); +dmaengine_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) +{
drivers/dma/timb_dma.c static void td_tasklet(struct tasklet_struct *t) · line 598
Quoted 10 lines
drivers/dma/timb_dma.c +10
-0+ struct timb_dma_chan *td_chan = container_of(chan, struct timb_dma_chan, + chan); +struct timb_dma *td =tdchantotd (td_chan); +u32 ier; + +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);
[Severity: High]
Can a pending timb_dma_chan_bh() work item prematurely complete a newly submitted transfer after td_terminate_all() or td_issue_pending()?
Previously, td_tasklet() took td_chan->lock and finished the active descriptor immediately. Now, td_tasklet() only schedules the channel BH work, and td_terminate_all() tears down the active descriptor without canceling or flushing that work:
CPU0 (<softirq>) CPU1td_tasklet()
dmaengine_schedule_bh()
td_terminate_all()
spin_lock_bh(&td_chan->lock)
__td_finish(td_chan)
spin_unlock_bh(&td_chan->lock)
td_tx_submit()
spin_lock_bh(&td_chan->lock)
__td_start_dma(td_chan)
spin_unlock_bh(&td_chan->lock)timb_dma_chan_bh()
spin_lock(&td_chan->lock)
__td_finish(td_chan) <-- stops and unmaps the new in-flight transfer
When the stale BH work runs, __td_finish() unmaps and completes the newly started descriptor at the head of td_chan->active_list while hardware DMA is still in flight.
In addition, dmaengine_kill_bh() in td_free_chan_resources() only flushes td_chan->chan.bh_work without waiting for td->tasklet. If td_tasklet() is pending or running on another CPU, it can schedule the channel BH after dmaengine_kill_bh() returns, or leave td_chan->active_list non-empty when td_free_chan_resources() reaches BUG_ON(!list_empty(&td_chan->active_list)).
drivers/dma/timb_dma.c static void td_tasklet(struct tasklet_struct *t) · line 606
Quoted 8 lines
drivers/dma/timb_dma.c +8
-0+ +/* +* The controller tasklet re-armed IER before this channel was +* finished. Recompute it now that the channel state has changed. +*/ + ier =__td_ier_mask (td); +iowrite32 (ier, td->membase +TIMBDMA_IER ); +}
[Severity: High]
Does calling __td_ier_mask(td) and writing TIMBDMA_IER without holding the channel locks race with concurrent channel completions and starts?
Previously, __td_finish() and __td_start_next() ran sequentially inside the single controller tasklet before calling __td_ier_mask(td). Now, both td_tasklet() and each channel's timb_dma_chan_bh() call __td_ier_mask(td) without holding any td_chan->lock:
drivers/dma/timb_dma.c:__td_ier_mask() {
for (i = 0; i < td->dma.chancnt; i++) {
struct timb_dma_chan *td_chan = td->channels + i;
if (td_chan->ongoing) {
struct timb_dma_desc *td_desc =
list_entry(td_chan->active_list.next,
struct timb_dma_desc, desc_node);
if (td_desc->interrupt)
ret |= 1 << i;
}
}
}If another CPU is inside __td_finish() (from another channel's BH, td_terminate_all(), or td_issue_pending()), __td_ier_mask() can observe td_chan->ongoing == true while list_move() empties td_chan->active_list:
CPU0 CPU1timb_dma_chan_bh() __td_finish(td_chan)
spin_unlock(&td_chan->lock)
__td_ier_mask(td)
if (td_chan->ongoing)
td_chan->ongoing = false;
list_move(&td_desc->desc_node,
&td_chan->free_list);
td_desc = list_entry(
td_chan->active_list.next, ...)
if (td_desc->interrupt) <-- out-of-bounds read off list headWhen active_list is empty, active_list.next points to &td_chan->active_list itself, so reading td_desc->interrupt reads past struct timb_dma_chan (and past the end of the struct timb_dma slab allocation on the last channel), or reads a freed descriptor if td_free_chan_resources() frees free_list.
Second, writing ier to TIMBDMA_IER outside any lock races with __td_enable_chan_irq() when another channel starts a transfer, overwriting TIMBDMA_IER with a stale mask and losing that channel's completion interrupt.
Third, td_irq() masks interrupts by writing 0 to TIMBDMA_IER and relies on td_tasklet() to clear TIMBDMA_ISR before re-enabling TIMBDMA_IER. Because td_tasklet() now writes TIMBDMA_IER before timb_dma_chan_bh() runs __td_finish() to stop RX DMA and clear ongoing, and because timb_dma_chan_bh() can also write TIMBDMA_IER after td_irq() masks it for another channel but before td_tasklet() acknowledges TIMBDMA_ISR, an unacknowledged interrupt can be unmasked immediately and re-trigger td_irq().
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=23
PATCH v4 24/33 dmaengine: txx9dmac: route callbacks via channel BH
In reply to Allen PaisUse the per-channel BH helpers for descriptor completion handling on channels that own an interrupt line. The channel interrupt schedules the BH, which scans the descriptors and re-enables the interrupt, exactly as the per-channel tasklet did.
Channels served by the shared controller interrupt do not initialize a channel BH, so the controller tasklet keeps scanning their descriptors directly, as before. Routing that path through a BH that was never set up would trip the WARN_ON() in dmaengine_schedule_bh() and stall the channel.
--- drivers/dma/txx9dmac.c | 10 +++++----- drivers/dma/txx9dmac.h | 1 - 2 files changed, 5 insertions(+), 6 deletions(-)
drivers/dma/txx9dmac.c +5 -5
@@ -596,13 +596,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 (dmaengine_chan_dev (&dc->chan),"tasklet: status=%x\n" , csr);
@@ -623,7 +623,7 @@ static irqreturn_t txx9dmac_chan_interrupt(int irq, void *dev_id)dev_vdbg (dmaengine_chan_dev (&dc->chan),"interrupt: status=%#x\n" ,channel_readl (dc,CSR )); -tasklet_schedule (&dc->tasklet); +dmaengine_schedule_bh (&dc->chan);/* * Just disable the interrupts. We'll turn them back on in the * softirq handler.
@@ -1108,7 +1108,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); +dmaengine_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);
@@ -1154,7 +1154,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); +dmaengine_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
PATCH v4 25/33 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 dmaengine_*_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 */ +dmaengine_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 */ +dmaengine_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); +dmaengine_init_bh (&xor_dev->dmachan, mv_xor_v2_tasklet); xor_dev->desc_size =mv_xor_v2_set_desc_size (xor_dev);
@@ -868,7 +866,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); +dmaengine_kill_bh (&xor_dev->dmachan); }#ifdef CONFIG_OF
Signature · 1 line
2.43.0
PATCH v4 26/33 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.
Drain the per-channel BH work in mpc_dma_remove() after the controller tasklet is killed, so no callback can run once the device memory is released.
--- drivers/dma/mpc512x_dma.c | 78 +++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 27 deletions(-)
drivers/dma/mpc512x_dma.c +51 -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)) +dmaengine_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); +dmaengine_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); +dmaengine_init_bh (&mchan->chan, mpc_dma_chan_bh);list_add_tail (&mchan->chan.device_node, &dma->channels); }
@@ -1088,6 +1108,7 @@ static void mpc_dma_remove(struct platform_device *op) {struct device *dev = &op->dev;struct mpc_dma *mdma =dev_get_drvdata (dev); +int i;if (dev->of_node)of_dma_controller_free (dev->of_node);
@@ -1099,6 +1120,9 @@ static void mpc_dma_remove(struct platform_device *op)free_irq (mdma->irq, mdma);irq_dispose_mapping (mdma->irq);tasklet_kill (&mdma->tasklet); + +for (i =0 ; i <MPC_DMA_CHANNELS ; i++) +dmaengine_kill_bh (&mdma->channels[i].chan); }static const struct of_device_id mpc_dma_match[] = {
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v4 26/33] dmaengine: mpc512x: route callbacks via channel BH
Thank you for your contribution! Sashiko AI review found 3 potential issues to consider: - [High] dmaengine: mpc512x: race with mdma->tasklet in mpc_dma_free_chan_resources() - [High] dmaengine…
Thank you for your contribution! Sashiko AI review found 3 potential issues to consider:
- [High] dmaengine: mpc512x: race with mdma->tasklet in mpc_dma_free_chan_resources()
- [High] dmaengine: mpc512x: unsynchronized completion processing in prep paths
- [Medium] dmaengine: mpc512x: missing tasklet and BH cleanup in mpc_dma_probe()
--
commit d182185a8446cc183109b376f9fcf9d84e3788ed
Author: Allen Pais <[email protected]>
dmaengine: mpc512x: route callbacks via channel BH
This commit moves completed descriptor processing and callback execution from the controller tasklet into per-channel BH work items, and drains those BH work items during channel resource teardown and driver removal.
Quoted 66 lines
drivers/dma/mpc512x_dma.c +21
-24@@ -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;
drivers/dma/mpc512x_dma.c static irqreturn_t mpc_dma_irq(int irq, void *data) · line 416
Quoted 10 lines
drivers/dma/mpc512x_dma.c +7
-2-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]); }
[Severity: High]
Can calling mpc_dma_process_completed() from mpc_dma_prep_memcpy() and mpc_dma_prep_slave_sg() race with mpc_dma_chan_bh()?
When mchan->free is empty, both prep functions call mpc_dma_process_completed(mdma) synchronously across all channels:
drivers/dma/mpc512x_dma.c:mpc_dma_prep_memcpy() {
...
if (!mdesc) {
/* try to free completed descriptors */
mpc_dma_process_completed(mdma);
return NULL;
}
...
}Because mpc_dma_process_chan_completed() drops mchan->lock while running callbacks and only returns descriptors to mchan->free afterwards, a concurrent prep call (or a callback itself) can enter mpc_dma_process_chan_completed() for a second batch of completed descriptors:
CPU0 (kworker) CPU1mpc_dma_chan_bh()
mpc_dma_process_chan_completed()
spin_lock_irqsave(&mchan->lock)
list_splice_tail_init(...)
/* batch 1, cookie N */
spin_unlock_irqrestore(...)
dmaengine_desc_get_callback_invoke()
mpc_dma_prep_memcpy()
/* mchan->free is empty */
mpc_dma_process_completed()
mpc_dma_process_chan_completed()
/* splices batch 2, cookie N+1 */
dmaengine_desc_get_callback_invoke()
/* callbacks run out of order */
mchan->chan.completed_cookie = N+1
spin_lock_irqsave(&mchan->lock)
mchan->chan.completed_cookie = N <-- completed_cookie moves backward
spin_unlock_irqrestore(...)When completed_cookie regresses to N, dma_cookie_status() reports cookie N+1 as DMA_IN_PROGRESS indefinitely. In addition, because mpc_dma_process_completed() iterates over all channels outside the BH workqueue, a prep call on one channel can run mpc_dma_process_chan_completed() on another channel concurrently with mpc_dma_free_chan_resources() after dmaengine_kill_bh() has returned.
[ ... ]
drivers/dma/mpc512x_dma.c static void mpc_dma_free_chan_resources(struct dma_chan *chan) · line 575
Quoted 9 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); +dmaengine_kill_bh (&mchan->chan); +spin_lock_irqsave (&mchan->lock, flags);/* Channel must be idle */
[Severity: High]
Can mpc_dma_free_chan_resources() race with mdma->tasklet because mpc_dma_irq() schedules mdma->tasklet first rather than scheduling mchan->chan.bh_work directly?
When a transfer completes, mpc_dma_irq_process() moves descriptors onto mchan->completed and mpc_dma_irq() calls tasklet_schedule(&mdma->tasklet); mchan->chan.bh_work is only queued later when mpc_dma_tasklet() runs. Note that mpc_dma_device_terminate_all() only splices prepared, queued, and active into mchan->free, leaving mchan->completed untouched.
CPU0 CPU1
<hardirq>
mpc_dma_irq()
mpc_dma_irq_process()
/* moves desc to mchan->completed */
tasklet_schedule(&mdma->tasklet)
mpc_dma_free_chan_resources()
dmaengine_kill_bh(&mchan->chan)
/* bh_work not scheduled yet */
spin_lock_irqsave(&mchan->lock)
BUG_ON(!list_empty(&mchan->completed))
<-- kernel BUGAlternatively, if mpc_dma_tasklet() schedules mchan->chan.bh_work right after dmaengine_kill_bh() returns and mpc_dma_process_chan_completed() splices mchan->completed into its local list before mpc_dma_free_chan_resources() takes mchan->lock, mpc_dma_free_chan_resources() frees mchan->tcd and the descriptors while mpc_dma_process_chan_completed() runs callbacks and splices the freed descriptors back onto mchan->free.
drivers/dma/mpc512x_dma.c static int mpc_dma_probe(struct platform_device *op) · line 1032
Quoted 8 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); +dmaengine_init_bh (&mchan->chan, mpc_dma_chan_bh);list_add_tail (&mchan->chan.device_node, &dma->channels); }
[Severity: Medium]
If dma_async_device_register() fails in mpc_dma_probe(), can a pending tasklet or channel BH work item run after mdma is freed?
mpc_dma_probe() requests mdma->irq and mdma->irq2 before initializing the channels and mdma->tasklet, and dma_async_device_register() can invoke mpc_dma_alloc_chan_resources() (enabling channel error interrupts) before failing on a later step:
drivers/dma/mpc512x_dma.c:mpc_dma_probe() {
...
retval = dma_async_device_register(dma);
if (retval)
goto err_free2;
...
err_free2:
if (mdma->is_mpc8308)
free_irq(mdma->irq2, mdma);
err_free1:
free_irq(mdma->irq, mdma);
err_dispose2:
if (mdma->is_mpc8308)
irq_dispose_mapping(mdma->irq2);
err_dispose1:
irq_dispose_mapping(mdma->irq);
err:
return retval;
}Unlike mpc_dma_remove(), the err_free2 error path does not call tasklet_kill() or dmaengine_kill_bh() before devres unmaps mdma->regs and frees mdma, which can cause a use-after-free if an interrupt scheduled the tasklet or BH work before free_irq() completed.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=26
PATCH v4 27/33 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 dmaengine_*_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); +dmaengine_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); +dmaengine_kill_bh (&plxdev->dma_chan);plx_dma_abort_desc (plxdev);
@@ -505,7 +505,7 @@ static int plx_dma_create(struct pci_dev *pdev)return -ENOMEM ;spin_lock_init (&plxdev->ring_lock); -tasklet_setup (&plxdev->desc_task, plx_dma_desc_task); +dmaengine_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
PATCH v4 28/33 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. That descriptor is never actually
completed: it stays on the issued list and in chan->desc, the cookie is never advanced, and the channel does not move on to the next transfer.
Complete the failed descriptor through virt-dma instead, mirroring what the done tasklet does for a successful transfer: unlink it from the issued list, mark it DMA_TRANS_ABORTED with the untransferred size as residue, hand it to vchan_cookie_complete() so the client callback runs from the channel BH, and start the next issued descriptor if there is one. Reset the retry budget at the same point, as the done tasklet does after a successful retry, so the next descriptor does not inherit an exhausted retry count.
During removal, kill the producer tasklets before the channel BH so they cannot enqueue callback work after it has been drained.
--- drivers/dma/sf-pdma/sf-pdma.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-)
drivers/dma/sf-pdma/sf-pdma.c +15 -3
@@ -326,9 +326,21 @@ static void sf_pdma_errbh_tasklet(struct tasklet_struct *t)spin_lock_irqsave (&chan->lock, flags);if (chan->retries <=0 ) { -/* fail to recover */ +/* fail to recover: abort this descriptor and move on */ + chan->retries =MAX_RETRY ; + chan->xfer_err = false;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--;
@@ -601,9 +613,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); -dmaengine_kill_bh (&ch->vchan.chan);tasklet_kill (&ch->done_tasklet);tasklet_kill (&ch->err_tasklet); +dmaengine_kill_bh (&ch->vchan.chan); }if (pdev->dev.of_node)
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v4 28/33] dmaengine: sf-pdma: route error callbacks through channel BH
Thank you for your contribution! Sashiko AI review found 2 potential issues to consider: - [High] dmaengine: sf-pdma: unlocked chan->desc read in sf_pdma_errbh_tasklet() - [Medium] dmaengine…
Thank you for your contribution! Sashiko AI review found 2 potential issues to consider:
- [High] dmaengine: sf-pdma: unlocked chan->desc read in sf_pdma_errbh_tasklet()
- [Medium] dmaengine: sf-pdma: stale error tx_result on reused descriptors
--
commit cbd0063b16af2f825d62b2888a259c514966e4ac
Author: Allen Pais <[email protected]>
dmaengine: sf-pdma: route error callbacks through channel BH
This commit routes failed transfer completions in sf-pdma through vchan_cookie_complete() with DMA_TRANS_ABORTED instead of invoking the client callback directly from the error tasklet, and kills the driver tasklets before killing the channel BH on removal.
Quoted 7 lines
drivers/dma/sf-pdma/sf-pdma.c +0
-0@@ -326,9 +326,21 @@ static void sf_pdma_errbh_tasklet(struct tasklet_struct *t)spin_lock_irqsave (&chan->lock, flags);
drivers/dma/sf-pdma/sf-pdma.c static void sf_pdma_errbh_tasklet(struct tasklet_struct *t) · line 335
Quoted 10 lines
drivers/dma/sf-pdma/sf-pdma.c +6
-2if (chan->retries <=0 ) { -/* fail to recover */ +/* fail to recover: abort this descriptor and move on */ + chan->retries =MAX_RETRY ; + chan->xfer_err = false;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);
[Severity: High]
Can this cause a use-after-free or NULL pointer dereference on desc?
sf_pdma_errbh_tasklet() reads desc = chan->desc at entry before acquiring chan->vchan.lock, and neither sf_pdma_terminate_all() nor sf_pdma_free_chan_resources() synchronizes with err_tasklet when freeing chan->desc:
CPU0 CPU1sf_pdma_errbh_tasklet()
desc = chan->desc;
sf_pdma_terminate_all()
spin_lock_irqsave(&chan->vchan.lock, ...)
kfree(chan->desc);
chan->desc = NULL;
spin_unlock_irqrestore(&chan->vchan.lock, ...) ...
spin_lock_irqsave(&chan->vchan.lock, ...)
list_del(&desc->vdesc.node); <-- use-after-free
vchan_cookie_complete(&desc->vdesc);
If sf_pdma_terminate_all() or sf_pdma_donebh_tasklet() clears or replaces chan->desc before sf_pdma_errbh_tasklet() runs, desc can also be NULL or already completed, corrupting the virt-dma descriptor lists. Re-reading chan->desc under chan->vchan.lock and checking that it is non-NULL before unlinking and completing it would avoid this.
drivers/dma/sf-pdma/sf-pdma.c static void sf_pdma_errbh_tasklet(struct tasklet_struct *t) · line 338
Quoted 3 lines
drivers/dma/sf-pdma/sf-pdma.c +3
-0+ desc->vdesc.tx_result.result =DMA_TRANS_ABORTED ; + desc->vdesc.tx_result.residue = desc->xfer_size; +vchan_cookie_complete (&desc->vdesc);
[Severity: Medium]
Does a reusable descriptor (DMA_CTRL_REUSE) keep reporting DMA_TRANS_ABORTED on later successful transfers after an abort?
sf_pdma_probe() sets pdma->dma_dev.descriptor_reuse = true, and vchan_tx_prep() only initializes tx_result to DMA_TRANS_NOERROR and residue 0 when the descriptor is first prepared. Once sf_pdma_errbh_tasklet() overwrites desc->vdesc.tx_result here, neither sf_pdma_xfer_desc() nor sf_pdma_donebh_tasklet() resets tx_result back to DMA_TRANS_NOERROR with zero residue, so subsequent successful completions of the reused descriptor will pass the stale error result and residue to the client callback.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=28
PATCH v4 29/33 dmaengine: pl330: route callbacks via channel BH
In reply to Allen PaisConvert the per-channel tasklet to dmaengine_*_bh while keeping the controller tasklet for non-callback events.
Initialize the channel BH once at probe time, alongside the other per-channel state, rather than in pl330_alloc_chan_resources(). Unlike tasklet_setup(), dmaengine_init_bh() must not be called again on an already initialized channel, and a channel can be released and re-requested many times. pl330_free_chan_resources() still drains the BH when the channel is released.
--- drivers/dma/pl330.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-)
drivers/dma/pl330.c +8 -11
@@ -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;
@@ -1575,7 +1572,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); +dmaengine_schedule_bh (&pch->chan); }static void pl330_dotask (struct tasklet_struct *t)
@@ -2061,14 +2058,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); +dmaengine_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;
@@ -2174,8 +2172,6 @@ static int pl330_alloc_chan_resources(struct dma_chan *chan)return -ENOMEM ; } -tasklet_setup (&pch->task, pl330_tasklet); -spin_unlock_irqrestore (&pl330->lock, flags);return 1 ;
@@ -2355,7 +2351,7 @@ static void pl330_free_chan_resources(struct dma_chan *chan)struct pl330_dmac *pl330 = pch->dmac;unsigned long flags; -tasklet_kill (&pch->task); +dmaengine_kill_bh (&pch->chan);pm_runtime_get_sync (pch->dmac->ddma.dev);spin_lock_irqsave (&pl330->lock, flags);
@@ -2491,7 +2487,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); }/*
@@ -3121,6 +3117,7 @@ pl330_probe(struct amba_device *adev, const struct amba_id *id) pch->chan.device = pd; pch->dmac = pl330; pch->dir =DMA_NONE ; +dmaengine_init_bh (&pch->chan, pl330_tasklet);/* Add the channel to the DMAC list */ list_add_tail (&pch->chan.device_node, &pd->channels);
Signature · 1 line
2.43.0
PATCH v4 30/33 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 dmaengine_*_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); +dmaengine_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); +dmaengine_kill_bh (&mdev->dmachan);list_del (&mdev->dmachan.device_node); }
@@ -847,7 +847,7 @@ static int msgdma_probe(struct platform_device *pdev)if (mdev->irq <0 )return -ENXIO ; -tasklet_setup (&mdev->irq_tasklet, msgdma_tasklet); +dmaengine_init_bh (&mdev->dmachan, msgdma_tasklet);dma_cookie_init (&mdev->dmachan);
Signature · 1 line
2.43.0
Re: [PATCH v4 30/33] dmaengine: altera-msgdma: use channel BH helpers
Quoted 5 lines
Replace the device tasklet with the per-channel BH helper so completion handling runs via dmaengine_*_bh.
Signed-off-by Allen Pais <[email protected]>Reviewed-by Frank Li <[email protected]>
Quoted 59 lines
--- 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); +dmaengine_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); +dmaengine_kill_bh (&mdev->dmachan);list_del (&mdev->dmachan.device_node); }@@ -847,7 +847,7 @@ static int msgdma_probe(struct platform_device *pdev)if (mdev->irq <0 )return -ENXIO ; -tasklet_setup (&mdev->irq_tasklet, msgdma_tasklet); +dmaengine_init_bh (&mdev->dmachan, msgdma_tasklet);dma_cookie_init (&mdev->dmachan);
PATCH v4 31/33 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.
Since callbacks no longer run synchronously from hidma_callback(), drain the BH wherever the driver previously relied on that ordering: provide a device_synchronize() callback so dmaengine_terminate_sync() waits for deferred callbacks, drain the BH in hidma_free_chan_resources() before the descriptors are freed, and drain it in hidma_remove() before hidma_ll_uninit() tears down the TRE pool that the completion path reads. Kill the low-level completion tasklet before that drain, since it is what schedules the BH.
--- drivers/dma/qcom/hidma.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-)
drivers/dma/qcom/hidma.c +23 -1
@@ -155,6 +155,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 +188,7 @@ static void hidma_callback(void *data) }spin_unlock_irqrestore (&mchan->lock, irqflags); -hidma_process_completed (mchan); +dmaengine_schedule_bh (&mchan->chan);if (queued) {pm_runtime_mark_last_busy (dmadev->ddev.dev);
@@ -203,6 +210,7 @@ static int hidma_chan_init(struct hidma_dev *dmadev, u32 dma_sig) mchan->dmadev = dmadev; mchan->chan.device = ddev;dma_cookie_init (&mchan->chan); +dmaengine_init_bh (&mchan->chan, hidma_chan_bh);INIT_LIST_HEAD (&mchan->free);INIT_LIST_HEAD (&mchan->prepared);
@@ -529,6 +537,11 @@ static int hidma_terminate_all(struct dma_chan *chan)return rc; } +static void hidma_synchronize (struct dma_chan *dmach) +{ +dmaengine_kill_bh (dmach); +} +static void hidma_free_chan_resources (struct dma_chan *dmach) {struct hidma_chan *mchan =to_hidma_chan (dmach);
@@ -540,6 +553,9 @@ static void hidma_free_chan_resources(struct dma_chan *dmach)/* terminate running transactions and free descriptors */ hidma_terminate_channel (dmach); +/* drain completion work before the descriptors go away */ +dmaengine_kill_bh (dmach); +spin_lock_irqsave (&mchan->lock, irqflags);/* Move data */
@@ -813,6 +829,7 @@ static int hidma_probe(struct platform_device *pdev) dmadev->ddev.device_pause = hidma_pause; dmadev->ddev.device_resume = hidma_resume; dmadev->ddev.device_terminate_all = hidma_terminate_all; + dmadev->ddev.device_synchronize = hidma_synchronize; dmadev->ddev.copy_align =8 ;/*
@@ -913,6 +930,7 @@ static void hidma_shutdown(struct platform_device *pdev)static void hidma_remove (struct platform_device *pdev) {struct hidma_dev *dmadev =platform_get_drvdata (pdev); +struct dma_chan *chan;pm_runtime_get_sync (dmadev->ddev.dev);dma_async_device_unregister (&dmadev->ddev);
@@ -922,6 +940,10 @@ static void hidma_remove(struct platform_device *pdev)hidma_free_msis (dmadev);tasklet_kill (&dmadev->task); +/* the low-level completion tasklet schedules the channel BH */ +tasklet_kill (&dmadev->lldev->task); +list_for_each_entry (chan, &dmadev->ddev.channels, device_node) +dmaengine_kill_bh (chan);hidma_sysfs_uninit (dmadev);hidma_debug_uninit (dmadev);hidma_ll_uninit (dmadev->lldev);
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v4 31/33] dmaengine: hidma: defer callbacks via channel BH
Thank you for your contribution! Sashiko AI review found 3 potential issues to consider: - [High] dmaengine: hidma: missing lldev tasklet kill before dmaengine_kill_bh() - [High] dmaengine: hidma…
Thank you for your contribution! Sashiko AI review found 3 potential issues to consider:
- [High] dmaengine: hidma: missing lldev tasklet kill before dmaengine_kill_bh()
- [High] dmaengine: hidma: race between hidma_chan_bh() and hidma_terminate_channel()
- [Medium] dmaengine: hidma: unconditional BH schedule in hidma_callback()
--
commit 2833f6ace610118cc0f61079464819e2cd18fc0f
Author: Allen Pais <[email protected]>
dmaengine: hidma: defer callbacks via channel BH
This commit defers HIDMA descriptor completion processing from hidma_callback() to per-channel BH work and drains the BH during channel synchronization, resource freeing, and device removal.
Quoted 4 lines
drivers/dma/qcom/hidma.c +0
-0
[ ... ]
drivers/dma/qcom/hidma.c static void hidma_callback(void *data) · line 194
Quoted 9 lines
drivers/dma/qcom/hidma.c +1
-1@@ -181,7 +188,7 @@ static void hidma_callback(void *data) }spin_unlock_irqrestore (&mchan->lock, irqflags); -hidma_process_completed (mchan); +dmaengine_schedule_bh (&mchan->chan);if (queued) {pm_runtime_mark_last_busy (dmadev->ddev.dev);
[Severity: Medium]
Should dmaengine_schedule_bh(&mchan->chan) only be called when queued is true?
During hidma_alloc_chan_resources(), hidma_ll_request() synchronously invokes hidma_callback(mdesc) before mdesc->node is added to any list, so mdesc->node.next is NULL and queued is false. Scheduling the BH unconditionally queues mchan->chan.bh_work during descriptor allocation; if a later iteration in hidma_alloc_chan_resources() fails, the function returns an error without calling dmaengine_kill_bh(), leaving mchan->chan.bh_work pending after dma_chan_get() drops its device and module references.
[ ... ]
drivers/dma/qcom/hidma.c static int hidma_terminate_all(struct dma_chan *chan) · line 547
Quoted 12 lines
drivers/dma/qcom/hidma.c +5
-0@@ -529,6 +537,11 @@ static int hidma_terminate_all(struct dma_chan *chan)return rc; } +static void hidma_synchronize (struct dma_chan *dmach) +{ +dmaengine_kill_bh (dmach); +} +static void hidma_free_chan_resources (struct dma_chan *dmach) {struct hidma_chan *mchan =to_hidma_chan (dmach);
[Severity: High]
Can dmadev->lldev->task schedule the channel BH after dmaengine_kill_bh() returns in hidma_synchronize() and hidma_free_chan_resources()?
In hidma_remove(), tasklet_kill(&dmadev->lldev->task) is called before dmaengine_kill_bh() because the low-level completion tasklet (hidma_ll_tre_complete()) is what invokes hidma_callback() and schedules the BH. However, neither hidma_synchronize() nor hidma_free_chan_resources() kills lldev->task first. If an interrupt fires just before hidma_ll_disable() masks hardware interrupts in hidma_terminate_channel(), lldev->task can still be scheduled:
CPU0 CPU1 (<softirq>)hidma_free_chan_resources()
hidma_terminate_channel()
hidma_ll_disable()
dmaengine_kill_bh(dmach)
spin_lock_irqsave(&mchan->lock)
list_for_each_entry_safe(...) {
hidma_ll_free(..., mdesc->tre_ch)
list_del(&mdesc->node)
kfree(mdesc)
}
spin_unlock_irqrestore(&mchan->lock)
hidma_ll_tre_complete()
tre->callback(tre->data)
hidma_callback(mdesc)
if (mdesc->node.next)
list_move_tail(&mdesc->node,
&mchan->completed)
<-- use-after-free
dmaengine_schedule_bh()Because list_del() sets mdesc->node.next to LIST_POISON1 (non-NULL), hidma_callback() dereferences the freed mdesc, corrupts mchan->completed, and schedules hidma_chan_bh() on the freed descriptor. In the dmaengine_terminate_sync() path, hidma_callback() moves mdesc from mchan->free to mchan->completed and runs completion callbacks after hidma_synchronize() has returned.
drivers/dma/qcom/hidma.c static void hidma_free_chan_resources(struct dma_chan *dmach) · line 561
Quoted 10 lines
drivers/dma/qcom/hidma.c +3
-0@@ -540,6 +553,9 @@ static void hidma_free_chan_resources(struct dma_chan *dmach)/* terminate running transactions and free descriptors */ hidma_terminate_channel (dmach); +/* drain completion work before the descriptors go away */ +dmaengine_kill_bh (dmach); +spin_lock_irqsave (&mchan->lock, irqflags);/* Move data */
[Severity: High]
Can hidma_chan_bh() race with hidma_terminate_channel() and corrupt mchan->free when dmaengine_kill_bh() is only called after hidma_terminate_channel()?
Both dmaengine_terminate_sync() (which calls hidma_terminate_all() before hidma_synchronize()) and hidma_free_chan_resources() enter hidma_terminate_channel() while hidma_chan_bh() can still be running hidma_process_completed():
CPU0 (kworker) CPU1hidma_chan_bh()
hidma_process_completed()
spin_lock_irqsave(&mchan->lock)
list_splice_tail_init(
&mchan->completed, &list)
spin_unlock_irqrestore(&mchan->lock)
hidma_terminate_channel()
...
list_for_each_entry_safe(...) {
...
list_move(&mdesc->node,
&mchan->free)
spin_lock_irqsave(&mchan->lock)
list_move(&mdesc->node,
&mchan->free) <-- corrupts mchan->free
spin_unlock_irqrestore(&mchan->lock)hidma_terminate_channel() moves descriptors onto mchan->free without holding mchan->lock:
drivers/dma/qcom/hidma.c:hidma_terminate_channel() {
...
/* return all user requests */
list_for_each_entry_safe(mdesc, tmp, &list, node) {
struct dma_async_tx_descriptor *txd = &mdesc->desc;
dma_descriptor_unmap(txd);
dmaengine_desc_get_callback_invoke(txd, NULL);
dma_run_dependencies(txd);
/* move myself to free_list */
list_move(&mdesc->node, &mchan->free);
}
...
}Previously hidma_process_completed() ran synchronously inside the tasklet, Whereas now hidma_chan_bh() runs concurrently on the BH workqueue and can also call hidma_process_completed() in parallel with the direct hidma_process_completed() call at the top of hidma_terminate_channel() and overwrite mchan->last_success after hidma_terminate_channel() resets it to
0. Killing lldev->task and draining the channel BH inside hidma_terminate_channel() after hidma_ll_disable() masks interrupts (and before draining lists and moving descriptors to mchan->free) would avoid these races.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=31
PATCH v4 32/33 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.
Because callbacks are now deferred, synchronize the vchan before the channel ring is freed in gpi_free_chan_resources(), so a late callback cannot observe a torn-down ring, and provide a device_synchronize() callback so dmaengine_terminate_sync() waits for pending callbacks.
--- drivers/dma/qcom/gpi.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-)
drivers/dma/qcom/gpi.c +23 -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);
@@ -1983,6 +1991,13 @@ static int gpi_ch_init(struct gchan *gchan) }/* release all channel resources */ +static void gpi_synchronize (struct dma_chan *chan) +{ +struct gchan *gchan =to_gchan (chan); + +vchan_synchronize (&gchan->vc); +} +static void gpi_free_chan_resources (struct dma_chan *chan) {struct gchan *gchan =to_gchan (chan);
@@ -2010,6 +2025,9 @@ static void gpi_free_chan_resources(struct dma_chan *chan)gpi_reset_chan (gchan,GPI_CH_CMD_DE_ALLOC ); } +/* drain deferred completions before the ring goes away */ +vchan_synchronize (&gchan->vc); +/* free all allocated memory */ gpi_free_ring (&gchan->ch_ring, gpii);vchan_free_chan_resources (&gchan->vc);
@@ -2279,6 +2297,7 @@ static int gpi_probe(struct platform_device *pdev) gpi_dev->dma_device.device_prep_slave_sg = gpi_prep_slave_sg; gpi_dev->dma_device.device_config = gpi_peripheral_config; gpi_dev->dma_device.device_terminate_all = gpi_terminate_all; + gpi_dev->dma_device.device_synchronize = gpi_synchronize; gpi_dev->dma_device.dev = gpi_dev->dev; gpi_dev->dma_device.device_pause = gpi_pause; gpi_dev->dma_device.device_resume = gpi_resume;
Signature · 1 line
2.43.0
PATCH v4 33/33 dmaengine: switchtec: use channel BH helpers
In reply to Allen PaisReplace the per-channel descriptor completion tasklet with the shared dmaengine 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; +dmaengine_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); +dmaengine_schedule_bh (&swdma_chan->dma_chan);return IRQ_HANDLED ; }
@@ -1060,6 +1060,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); +dmaengine_kill_bh (chan);switchtec_dma_abort_desc (swdma_chan,0 );switchtec_dma_free_desc (swdma_chan);
@@ -1086,8 +1087,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 +
@@ -1135,6 +1134,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); +dmaengine_init_bh (chan, switchtec_dma_desc_task);list_add_tail (&chan->device_node, &dma->channels);
@@ -1157,7 +1157,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); +dmaengine_kill_bh (&swdma_chan->dma_chan);switchtec_dma_chan_stop (swdma_chan);
Signature · 1 line
2.43.0