[RFC 1/3] dmaengine: virt-dma: Support for ignoring callback after vc termination

From: Peter Ujfalusi
Date: Wed Feb 10 2016 - 01:51:52 EST


This patch provides means to prevent the race condition between vchan
completion tasklet and the terminate_all function.
The race condition is not easily reproducible, but it has been observed
with RT kernel on uniprocessor systems with high rate DMA activity (UART)
and it follows this pattern:
When terminate_all is called, the tasklet has been already scheduled or
it is running to handle the completion of a vdesc. The client driver might
be freeing up resources after terminating the transfers but the tasklet
when it has a chance to continue to execute will call the provided callback
even after the client driver does not expect this to happen.
The terminated flag for the vchan itself does not provide enough protection
since can still face with a race condition:
in this case when the vchan_complete() tests for the terminated flag it will
see that the vchan is not terminated, but right after the test it is
possible that the terminate thread is set to execute (uniprocessor + RT) and
it will free up the resources. After that the vchan_complete() will resume
and calls the callback. This will lead to crash.

To provide protection, the dma driver need to make sure that the tasklet
will not run while we terminate the channel by:

static int dmadriver_terminate_all(struct dma_chan *chan)
{
unsigned long flags;
LIST_HEAD(head);
...
tasklet_disable(&vc.task);
spin_lock_irqsave(&vc.lock, flags);
vchan_terminate(&vc);
...
vchan_get_all_descriptors(&vc, &head);
spin_unlock_irqrestore(&vc.lock, flags);
vchan_dma_desc_free_list(&vc, &head);
tasklet_enable(&vc.task);

return 0;
}

Signed-off-by: Peter Ujfalusi <peter.ujfalusi@xxxxxx>
---
drivers/dma/virt-dma.c | 6 ++++--
drivers/dma/virt-dma.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/drivers/dma/virt-dma.c b/drivers/dma/virt-dma.c
index a35c211857dd..4317d5d3ef1f 100644
--- a/drivers/dma/virt-dma.c
+++ b/drivers/dma/virt-dma.c
@@ -88,11 +88,13 @@ static void vchan_complete(unsigned long arg)
struct virt_dma_chan *vc = (struct virt_dma_chan *)arg;
struct virt_dma_desc *vd;
dma_async_tx_callback cb = NULL;
+ bool terminated;
void *cb_data = NULL;
LIST_HEAD(head);

spin_lock_irq(&vc->lock);
list_splice_tail_init(&vc->desc_completed, &head);
+ terminated = vc->terminated;
vd = vc->cyclic;
if (vd) {
vc->cyclic = NULL;
@@ -101,7 +103,7 @@ static void vchan_complete(unsigned long arg)
}
spin_unlock_irq(&vc->lock);

- if (cb)
+ if (cb && !terminated)
cb(cb_data);

while (!list_empty(&head)) {
@@ -115,7 +117,7 @@ static void vchan_complete(unsigned long arg)
else
vc->desc_free(vd);

- if (cb)
+ if (cb && !terminated)
cb(cb_data);
}
}
diff --git a/drivers/dma/virt-dma.h b/drivers/dma/virt-dma.h
index d9731ca5e262..4df44b1dafbf 100644
--- a/drivers/dma/virt-dma.h
+++ b/drivers/dma/virt-dma.h
@@ -27,6 +27,7 @@ struct virt_dma_chan {
void (*desc_free)(struct virt_dma_desc *);

spinlock_t lock;
+ bool terminated;

/* protected by vc.lock */
struct list_head desc_allocated;
@@ -80,6 +81,7 @@ static inline struct dma_async_tx_descriptor *vchan_tx_prep(struct virt_dma_chan
static inline bool vchan_issue_pending(struct virt_dma_chan *vc)
{
list_splice_tail_init(&vc->desc_submitted, &vc->desc_issued);
+ vc->terminated = false;
return !list_empty(&vc->desc_issued);
}

@@ -116,6 +118,17 @@ static inline void vchan_cyclic_callback(struct virt_dma_desc *vd)
}

/**
+ * vchan_terminate - set the virtual channel as terminated
+ * vc: virtual channel to update
+ *
+ * vc.lock must be held by caller
+ */
+static inline void vchan_terminate(struct virt_dma_chan *vc)
+{
+ vc->terminated = true;
+}
+
+/**
* vchan_next_desc - peek at the next descriptor to be processed
* @vc: virtual channel to obtain descriptor from
*
--
2.7.1


--------------010201030304020808000908
Content-Type: text/x-patch;
name="0002-dmaengine-omap-dma-Prevent-race-between-vchan_comple.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename*0="0002-dmaengine-omap-dma-Prevent-race-between-vchan_comple.pa";
filename*1="tch"