aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLukas Wunner <lukas@wunner.de>2019-01-23 03:26:00 -0500
committerVinod Koul <vkoul@kernel.org>2019-02-04 02:11:13 -0500
commit9e528c799d17a4ac37d788c81440b50377dd592d (patch)
treed013da3509de89131080bd901a8e1c0c17b1d07a
parentf7da7782aba92593f7b82f03d2409a1c5f4db91b (diff)
dmaengine: bcm2835: Fix abort of transactions
There are multiple issues with bcm2835_dma_abort() (which is called on termination of a transaction): * The algorithm to abort the transaction first pauses the channel by clearing the ACTIVE flag in the CS register, then waits for the PAUSED flag to clear. Page 49 of the spec documents the latter as follows: "Indicates if the DMA is currently paused and not transferring data. This will occur if the active bit has been cleared [...]" https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf So the function is entering an infinite loop because it is waiting for PAUSED to clear which is always set due to the function having cleared the ACTIVE flag. The only thing that's saving it from itself is the upper bound of 10000 loop iterations. The code comment says that the intention is to "wait for any current AXI transfer to complete", so the author probably wanted to check the WAITING_FOR_OUTSTANDING_WRITES flag instead. Amend the function accordingly. * The CS register is only read at the beginning of the function. It needs to be read again after pausing the channel and before checking for outstanding writes, otherwise writes which were issued between the register read at the beginning of the function and pausing the channel may not be waited for. * The function seeks to abort the transfer by writing 0 to the NEXTCONBK register and setting the ABORT and ACTIVE flags. Thereby, the 0 in NEXTCONBK is sought to be loaded into the CONBLK_AD register. However experimentation has shown this approach to not work: The CONBLK_AD register remains the same as before and the CS register contains 0x00000030 (PAUSED | DREQ_STOPS_DMA). In other words, the control block is not aborted but merely paused and it will be resumed once the next DMA transaction is started. That is absolutely not the desired behavior. A simpler approach is to set the channel's RESET flag instead. This reliably zeroes the NEXTCONBK as well as the CS register. It requires less code and only a single MMIO write. This is also what popular user space DMA drivers do, e.g.: https://github.com/metachris/RPIO/blob/master/source/c_pwm/pwm.c Note that the spec is contradictory whether the NEXTCONBK register is writeable at all. On the one hand, page 41 claims: "The value loaded into the NEXTCONBK register can be overwritten so that the linked list of Control Block data structures can be dynamically altered. However it is only safe to do this when the DMA is paused." On the other hand, page 40 specifies: "Only three registers in each channel's register set are directly writeable (CS, CONBLK_AD and DEBUG). The other registers (TI, SOURCE_AD, DEST_AD, TXFR_LEN, STRIDE & NEXTCONBK), are automatically loaded from a Control Block data structure held in external memory." Fixes: 96286b576690 ("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>
-rw-r--r--drivers/dma/bcm2835-dma.c41
1 files changed, 9 insertions, 32 deletions
diff --git a/drivers/dma/bcm2835-dma.c b/drivers/dma/bcm2835-dma.c
index 0c3f5c71bb48..ae10f5614f95 100644
--- a/drivers/dma/bcm2835-dma.c
+++ b/drivers/dma/bcm2835-dma.c
@@ -406,13 +406,11 @@ static void bcm2835_dma_fill_cb_chain_with_sg(
406 } 406 }
407} 407}
408 408
409static int bcm2835_dma_abort(void __iomem *chan_base) 409static int bcm2835_dma_abort(struct bcm2835_chan *c)
410{ 410{
411 unsigned long cs; 411 void __iomem *chan_base = c->chan_base;
412 long int timeout = 10000; 412 long int timeout = 10000;
413 413
414 cs = readl(chan_base + BCM2835_DMA_CS);
415
416 /* 414 /*
417 * A zero control block address means the channel is idle. 415 * A zero control block address means the channel is idle.
418 * (The ACTIVE flag in the CS register is not a reliable indicator.) 416 * (The ACTIVE flag in the CS register is not a reliable indicator.)
@@ -424,25 +422,16 @@ static int bcm2835_dma_abort(void __iomem *chan_base)
424 writel(0, chan_base + BCM2835_DMA_CS); 422 writel(0, chan_base + BCM2835_DMA_CS);
425 423
426 /* Wait for any current AXI transfer to complete */ 424 /* Wait for any current AXI transfer to complete */
427 while ((cs & BCM2835_DMA_ISPAUSED) && --timeout) { 425 while ((readl(chan_base + BCM2835_DMA_CS) &
426 BCM2835_DMA_WAITING_FOR_WRITES) && --timeout)
428 cpu_relax(); 427 cpu_relax();
429 cs = readl(chan_base + BCM2835_DMA_CS);
430 }
431 428
432 /* We'll un-pause when we set of our next DMA */ 429 /* Peripheral might be stuck and fail to signal AXI write responses */
433 if (!timeout) 430 if (!timeout)
434 return -ETIMEDOUT; 431 dev_err(c->vc.chan.device->dev,
435 432 "failed to complete outstanding writes\n");
436 if (!(cs & BCM2835_DMA_ACTIVE))
437 return 0;
438
439 /* Terminate the control block chain */
440 writel(0, chan_base + BCM2835_DMA_NEXTCB);
441
442 /* Abort the whole DMA */
443 writel(BCM2835_DMA_ABORT | BCM2835_DMA_ACTIVE,
444 chan_base + BCM2835_DMA_CS);
445 433
434 writel(BCM2835_DMA_RESET, chan_base + BCM2835_DMA_CS);
446 return 0; 435 return 0;
447} 436}
448 437
@@ -787,7 +776,6 @@ static int bcm2835_dma_terminate_all(struct dma_chan *chan)
787 struct bcm2835_chan *c = to_bcm2835_dma_chan(chan); 776 struct bcm2835_chan *c = to_bcm2835_dma_chan(chan);
788 struct bcm2835_dmadev *d = to_bcm2835_dma_dev(c->vc.chan.device); 777 struct bcm2835_dmadev *d = to_bcm2835_dma_dev(c->vc.chan.device);
789 unsigned long flags; 778 unsigned long flags;
790 int timeout = 10000;
791 LIST_HEAD(head); 779 LIST_HEAD(head);
792 780
793 spin_lock_irqsave(&c->vc.lock, flags); 781 spin_lock_irqsave(&c->vc.lock, flags);
@@ -801,18 +789,7 @@ static int bcm2835_dma_terminate_all(struct dma_chan *chan)
801 if (c->desc) { 789 if (c->desc) {
802 vchan_terminate_vdesc(&c->desc->vd); 790 vchan_terminate_vdesc(&c->desc->vd);
803 c->desc = NULL; 791 c->desc = NULL;
804 bcm2835_dma_abort(c->chan_base); 792 bcm2835_dma_abort(c);
805
806 /* Wait for stopping */
807 while (--timeout) {
808 if (!readl(c->chan_base + BCM2835_DMA_ADDR))
809 break;
810
811 cpu_relax();
812 }
813
814 if (!timeout)
815 dev_err(d->ddev.dev, "DMA transfer could not be terminated\n");
816 } 793 }
817 794
818 vchan_get_all_descriptors(&c->vc, &head); 795 vchan_get_all_descriptors(&c->vc, &head);