linux/drivers/dma
Lukas Wunner f7da7782ab dmaengine: bcm2835: Fix interrupt race on RT
If IRQ handlers are threaded (either because CONFIG_PREEMPT_RT_BASE is
enabled or "threadirqs" was passed on the command line) and if system
load is sufficiently high that wakeup latency of IRQ threads degrades,
SPI DMA transactions on the BCM2835 occasionally break like this:

ks8851 spi0.0: SPI transfer timed out
bcm2835-dma 3f007000.dma: DMA transfer could not be terminated
ks8851 spi0.0 eth2: ks8851_rdfifo: spi_sync() failed

The root cause is an assumption made by the DMA driver which is
documented in a code comment in bcm2835_dma_terminate_all():

/*
 * Stop DMA activity: we assume the callback will not be called
 * after bcm_dma_abort() returns (even if it does, it will see
 * c->desc is NULL and exit.)
 */

That assumption falls apart if the IRQ handler bcm2835_dma_callback() is
threaded: A client may terminate a descriptor and issue a new one
before the IRQ handler had a chance to run. In fact the IRQ handler may
miss an *arbitrary* number of descriptors. The result is the following
race condition:

1. A descriptor finishes, its interrupt is deferred to the IRQ thread.
2. A client calls dma_terminate_async() which sets channel->desc = NULL.
3. The client issues a new descriptor. Because channel->desc is NULL,
   bcm2835_dma_issue_pending() immediately starts the descriptor.
4. Finally the IRQ thread runs and writes BCM2835_DMA_INT to the CS
   register to acknowledge the interrupt. This clears the ACTIVE flag,
   so the newly issued descriptor is paused in the middle of the
   transaction. Because channel->desc is not NULL, the IRQ thread
   finalizes the descriptor and tries to start the next one.

I see two possible solutions: The first is to call synchronize_irq()
in bcm2835_dma_issue_pending() to wait until the IRQ thread has
finished before issuing a new descriptor. The downside of this approach
is unnecessary latency if clients desire rapidly terminating and
re-issuing descriptors and don't have any use for an IRQ callback.
(The SPI TX DMA channel is a case in point.)

A better alternative is to make the IRQ thread recognize that it has
missed descriptors and avoid finalizing the newly issued descriptor.
So first of all, set the ACTIVE flag when acknowledging the interrupt.
This keeps a newly issued descriptor running.

If the descriptor was finished, the channel remains idle despite the
ACTIVE flag being set. However the ACTIVE flag can then no longer be
used to check whether the channel is idle, so instead check whether
the register containing the current control block address is zero
and finalize the current descriptor only if so.

That way, there is no impact on latency and throughput if the client
doesn't care for the interrupt: Only minimal additional overhead is
introduced for non-cyclic descriptors as one further MMIO read is
necessary per interrupt to check for idleness of the channel. Cyclic
descriptors are sped up slightly by removing one MMIO write per
interrupt.

Fixes: 96286b5766 ("dmaengine: Add support for BCM2835")
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Cc: stable@vger.kernel.org # v3.14+
Cc: Frank Pavlic <f.pavlic@kunbus.de>
Cc: Martin Sperl <kernel@martin.sperl.org>
Cc: Florian Meier <florian.meier@koalo.de>
Cc: Clive Messer <clive.m.messer@gmail.com>
Cc: Matthias Reichl <hias@horus.com>
Tested-by: Stefan Wahren <stefan.wahren@i2se.com>
Acked-by: Florian Kauer <florian.kauer@koalo.de>
Signed-off-by: Vinod Koul <vkoul@kernel.org>
2019-02-04 12:40:45 +05:30
..
bestcomm treewide: kmalloc() -> kmalloc_array() 2018-06-12 16:19:22 -07:00
dw dmaengine-4.21-rc1 2019-01-01 15:45:48 -08:00
dw-axi-dmac dmaengine: dw-axi-dmac: use dmaenginem_async_device_register to simplify the code 2018-08-29 21:43:17 +05:30
hsu dmaengine: hsu: remove dma_slave_config direction usage 2018-10-07 19:25:09 +05:30
ioat pci-v4.20-changes 2018-10-25 06:50:48 -07:00
ipu treewide: Use array_size() in vmalloc() 2018-06-12 16:19:22 -07:00
mediatek dmaengine: mediatek: Add MediaTek Command-Queue DMA controller for MT6765 SoC 2018-11-11 15:15:18 +05:30
ppc4xx dmaengine: ppc4xx: fix off-by-one build failure 2018-10-16 20:08:30 +05:30
qcom dmaengine: qcom_hidma: convert to DEFINE_SHOW_ATTRIBUTE 2018-12-17 11:49:41 +05:30
sh IOMMU Updates for Linux v4.21 2019-01-01 15:55:29 -08:00
ti dmaengine: cppi41: delete channel from pending list when stop channel 2018-12-05 14:01:59 +05:30
xilinx Merge branch 'topic/xilinx' into for-linus 2018-12-31 19:32:32 +05:30
acpi-dma.c dmaengine: acpi-dma: align debug message with flow 2016-02-22 09:06:09 +05:30
altera-msgdma.c dmaengine: altera: Use IRQ-safe spinlock calls in the error paths as well 2017-10-20 11:51:10 +05:30
amba-pl08x.c dmaengine: amba-pl08x: convert to DEFINE_SHOW_ATTRIBUTE 2018-12-17 11:49:41 +05:30
at_hdmac_regs.h dmaengine: at_hdmac: Remove unnecessary 0x prefixes before %pad 2017-11-08 10:47:04 +05:30
at_hdmac.c dmaengine: at_hdmac: fix module unloading 2018-11-29 19:49:41 +05:30
at_xdmac.c dmaengine: at_xdmac: move spin_lock_bh to spin_lock in tasklet 2018-09-03 16:24:05 +05:30
bcm2835-dma.c dmaengine: bcm2835: Fix interrupt race on RT 2019-02-04 12:40:45 +05:30
bcm-sba-raid.c treewide: Use struct_size() for devm_kmalloc() and friends 2018-06-06 11:15:43 -07:00
coh901318_lli.c dmaengine: coh901318: use NULL for pointer initialization 2016-09-26 22:28:24 +05:30
coh901318.c dmaengine: coh901318: Remove unused variable 2018-11-26 13:35:52 +05:30
coh901318.h
dma-axi-dmac.c dmaengine: axi-dmac: Request IRQ with IRQF_SHARED 2018-05-02 10:06:42 +05:30
dma-jz4740.c dmaengine: jz4740: remove dma_slave_config direction usage 2018-10-07 19:20:14 +05:30
dma-jz4780.c Merge branch 'topic/jz' into for-linus 2018-10-24 09:16:04 +01:00
dmaengine.c Merge branch 'ida-4.19' of git://git.infradead.org/users/willy/linux-dax 2018-08-26 11:48:42 -07:00
dmaengine.h License cleanup: add SPDX GPL-2.0 license identifier to files with no license 2017-11-02 11:10:55 +01:00
dmatest.c dmaengine: dmatest: Add transfer_size parameter 2018-12-17 11:45:11 +05:30
ep93xx_dma.c dmaengine: ep93xx: fix some typo 2018-11-11 14:56:49 +05:30
fsl_raid.c dmaengine: fsl_raid: make of_device_ids const. 2017-06-29 09:25:28 +05:30
fsl_raid.h
fsl-edma-common.c dmaengine: fsl-edma: remove dma_slave_config direction usage 2018-10-09 14:29:58 +05:30
fsl-edma-common.h dmaengine: fsl-edma: remove dma_slave_config direction usage 2018-10-09 14:29:58 +05:30
fsl-edma.c dmaengine: fsl-edma: add edma version and configurable registers 2018-09-11 12:06:39 +05:30
fsldma.c dmaengine: fsldma: move spin_lock_bh to spin_lock in tasklet 2018-09-03 16:23:00 +05:30
fsldma.h dmaengine: fsldma: set BWC, DAHTS and SAHTS values correctly 2017-06-22 18:31:35 +05:30
idma64.c Merge branch 'topic/intel' into for-linus 2018-10-24 09:15:59 +01:00
idma64.h asm-generic changes for 4.6 2016-03-24 23:13:48 -07:00
img-mdc-dma.c dmaengine: img-mdc-dma: Use vchan_terminate_vdesc() instead of desc_free 2017-12-04 22:33:51 +05:30
imx-dma.c dmaengine: imx-dma: remove dma_slave_config direction usage 2018-10-07 19:25:10 +05:30
imx-sdma.c dmaengine-4.21-rc1 2019-01-01 15:45:48 -08:00
iop-adma.c dmaengine: iop-adma: convert callback to helper function 2016-08-08 08:11:39 +05:30
k3dma.c dmaengine: k3dma: remove dma_slave_config direction usage 2018-10-07 19:25:10 +05:30
Kconfig dmaengine: uniphier-mdmac: add UniPhier MIO DMAC driver 2018-11-24 19:42:59 +05:30
lpc18xx-dmamux.c dmaengine: add driver for lpc18xx dmamux 2015-08-18 22:12:14 +05:30
Makefile dmaengine: uniphier-mdmac: add UniPhier MIO DMAC driver 2018-11-24 19:42:59 +05:30
mcf-edma.c dmaengine: mcf-edma: avoid warning for wrong pointer cast 2018-09-18 12:15:45 -07:00
mic_x100_dma.c dmaengine: mic_x100_dma: convert to DEFINE_SHOW_ATTRIBUTE 2018-12-17 11:49:41 +05:30
mic_x100_dma.h dmaengine: Add an enum for the dmaengine alignment constraints 2015-08-05 10:53:52 +05:30
mmp_pdma.c dmaengine: mmp_pdma: remove dma_slave_config direction usage 2018-11-05 10:32:46 +05:30
mmp_tdma.c Merge branch 'topic/dmam' into for-linus 2018-10-24 09:15:43 +01:00
moxart-dma.c treewide: Use struct_size() for kmalloc()-family 2018-06-06 11:15:43 -07:00
mpc512x_dma.c Merge branch 'topic/err_reporting' into for-linus 2016-10-03 09:17:33 +05:30
mv_xor_v2.c dmaengine: mv_xor_v2: use {lower,upper}_32_bits to configure HW descriptor address 2018-07-25 17:53:22 +05:30
mv_xor.c dmaengine: mv_xor: move spin_lock_bh to spin_lock in tasklet 2018-09-03 16:23:31 +05:30
mv_xor.h dmaengine: mv_xor: Add support for scatter-gather DMA mode 2016-11-25 11:16:36 +05:30
mxs-dma.c dmaengine: mxs-dma: use dmaenginem_async_device_register to simplify the code 2018-08-29 21:43:18 +05:30
nbpfaxi.c Merge branch 'topic/renesas' into for-linus 2018-10-24 09:16:22 +01:00
of-dma.c dmaengine: Convert to using %pOF instead of full_name 2017-07-19 09:30:44 +05:30
owl-dma.c dmaengine: owl: Fix warnings generated during build 2018-10-15 22:39:16 +05:30
pch_dma.c dmaengine: pch_dma: Replace PCI pool old API 2017-10-31 17:01:06 +05:30
pl330.c dmaengine: pl330: remove dma_slave_config direction usage 2018-11-24 20:22:21 +05:30
pxa_dma.c dmaengine-4.21-rc1 2019-01-01 15:45:48 -08:00
s3c24xx-dma.c headers: separate linux/mod_devicetable.h from linux/platform_device.h 2018-07-07 17:52:26 +02:00
sa11x0-dma.c dmaengine: sa11x0: unexport sa11x0_dma_filter_fn and clean up 2018-11-11 14:54:08 +05:30
sirf-dma.c dmaengine: sirf-dma: remove unused ‘sdesc’ 2016-12-12 22:25:22 +05:30
sprd-dma.c dmaengine: sprd: Add me as one of the module authors 2018-12-05 14:27:12 +05:30
st_fdma.c dmaengine: st_fdma: use dmaenginem_async_device_register to simplify the code 2018-08-29 21:43:17 +05:30
st_fdma.h dmaengine: st_fdma: Add STMicroelectronics FDMA driver header file 2016-10-18 20:12:06 +05:30
ste_dma40_ll.c dmaengine: ste_dma40_ll: make d40_width_to_bits static 2016-06-08 08:59:55 +05:30
ste_dma40_ll.h
ste_dma40.c dmaengine: ste_dma40: remove dma_slave_config direction usage 2018-11-24 20:22:21 +05:30
stm32-dma.c dmaengine: stm32-dma: check whether length is aligned on FIFO threshold 2018-10-02 20:32:15 +05:30
stm32-dmamux.c dmaengine: stm32-dmamux: fix a potential buffer overflow 2018-03-22 10:51:35 +05:30
stm32-mdma.c dmaengine: stm32-mdma: use dmaenginem_async_device_register to simplify the code 2018-08-29 21:43:17 +05:30
sun4i-dma.c dmaengine: sun4i: fix invalid argument 2017-04-24 09:50:05 +05:30
sun6i-dma.c dmaengine: sun6i: Retrieve channel count/max request from devicetree 2017-10-23 11:44:03 +05:30
tegra20-apb-dma.c dmaengine: tegra-apb: Support non-flow controlled slave configuration 2017-11-29 19:35:05 +05:30
tegra210-adma.c dmaengine: tegra210-adma: fix of_irq_get() error check 2017-08-09 11:39:16 +05:30
timb_dma.c dmaengine: timb_dma: Use proper enum in td_prep_slave_sg 2018-10-02 20:11:09 +05:30
TODO
txx9dmac.c dmaengine: txx9dmac: simplify getting .drvdata 2018-04-22 21:38:06 +05:30
txx9dmac.h
uniphier-mdmac.c dmaengine: uniphier-mdmac: add UniPhier MIO DMAC driver 2018-11-24 19:42:59 +05:30
virt-dma.c dmaengine: virt-dma: Add helper to free/reuse a descriptor 2017-12-04 22:33:51 +05:30
virt-dma.h dmaengine: virt-dma: Support for race free transfer termination 2017-12-04 22:33:51 +05:30
xgene-dma.c dmaengine: xgene-dma: remove unused xgene_dma_invalidate_buffer 2017-08-22 22:13:44 +05:30
zx_dma.c treewide: devm_kzalloc() -> devm_kcalloc() 2018-06-12 16:19:22 -07:00