Re: [PATCH 2/2] dmaengine: at_hdmac: run callback function with no lock held nor interrupts disabled

From: Vinod Koul
Date: Tue Mar 11 2014 - 07:01:23 EST


On Mon, Jan 27, 2014 at 03:23:24PM +0100, Nicolas Ferre wrote:
> Now, submission from callbacks are permitted as per dmaengine framework. So we
> shouldn't hold any spinlock nor disable IRQs while calling callbacks.
> As locks were taken by parent routines, spin_lock_irqsave() has to be called
> inside all routines, wherever they are required.
>
> The little used atc_issue_pending() function is made void.
and why would that be? The client _should_ invoke issues pending to push the
desciptors..?

--
~Vinod
>
> Signed-off-by: Nicolas Ferre <nicolas.ferre@xxxxxxxxx>
> ---
> drivers/dma/at_hdmac.c | 121 +++++++++++++++++++++++++++++--------------------
> 1 file changed, 71 insertions(+), 50 deletions(-)
>
> diff --git a/drivers/dma/at_hdmac.c b/drivers/dma/at_hdmac.c
> index b28759b6d1ca..f7bf4065636c 100644
> --- a/drivers/dma/at_hdmac.c
> +++ b/drivers/dma/at_hdmac.c
> @@ -268,10 +268,14 @@ static struct at_desc *atc_get_current_descriptors(struct at_dma_chan *atchan,
> static int atc_get_bytes_left(struct dma_chan *chan)
> {
> struct at_dma_chan *atchan = to_at_dma_chan(chan);
> - struct at_desc *desc_first = atc_first_active(atchan);
> + struct at_desc *desc_first;
> struct at_desc *desc_cur;
> + unsigned long flags;
> int ret = 0, count = 0;
>
> + spin_lock_irqsave(&atchan->lock, flags);
> + desc_first = atc_first_active(atchan);
> +
> /*
> * Initialize necessary values in the first time.
> * remain_desc record remain desc length.
> @@ -311,6 +315,7 @@ static int atc_get_bytes_left(struct dma_chan *chan)
> }
>
> out:
> + spin_unlock_irqrestore(&atchan->lock, flags);
> return ret;
> }
>
> @@ -318,12 +323,14 @@ out:
> * atc_chain_complete - finish work for one transaction chain
> * @atchan: channel we work on
> * @desc: descriptor at the head of the chain we want do complete
> - *
> - * Called with atchan->lock held and bh disabled */
> + */
> static void
> atc_chain_complete(struct at_dma_chan *atchan, struct at_desc *desc)
> {
> struct dma_async_tx_descriptor *txd = &desc->txd;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&atchan->lock, flags);
>
> dev_vdbg(chan2dev(&atchan->chan_common),
> "descriptor %u complete\n", txd->cookie);
> @@ -337,6 +344,8 @@ atc_chain_complete(struct at_dma_chan *atchan, struct at_desc *desc)
> /* move myself to free_list */
> list_move(&desc->desc_node, &atchan->free_list);
>
> + spin_unlock_irqrestore(&atchan->lock, flags);
> +
> dma_descriptor_unmap(txd);
> /* for cyclic transfers,
> * no need to replay callback function while stopping */
> @@ -344,10 +353,6 @@ atc_chain_complete(struct at_dma_chan *atchan, struct at_desc *desc)
> dma_async_tx_callback callback = txd->callback;
> void *param = txd->callback_param;
>
> - /*
> - * The API requires that no submissions are done from a
> - * callback, so we don't need to drop the lock here
> - */
> if (callback)
> callback(param);
> }
> @@ -362,15 +367,17 @@ atc_chain_complete(struct at_dma_chan *atchan, struct at_desc *desc)
> * Eventually submit queued descriptors if any
> *
> * Assume channel is idle while calling this function
> - * Called with atchan->lock held and bh disabled
> */
> static void atc_complete_all(struct at_dma_chan *atchan)
> {
> struct at_desc *desc, *_desc;
> LIST_HEAD(list);
> + unsigned long flags;
>
> dev_vdbg(chan2dev(&atchan->chan_common), "complete all\n");
>
> + spin_lock_irqsave(&atchan->lock, flags);
> +
> /*
> * Submit queued descriptors ASAP, i.e. before we go through
> * the completed ones.
> @@ -382,6 +389,8 @@ static void atc_complete_all(struct at_dma_chan *atchan)
> /* empty queue list by moving descriptors (if any) to active_list */
> list_splice_init(&atchan->queue, &atchan->active_list);
>
> + spin_unlock_irqrestore(&atchan->lock, flags);
> +
> list_for_each_entry_safe(desc, _desc, &list, desc_node)
> atc_chain_complete(atchan, desc);
> }
> @@ -389,23 +398,35 @@ static void atc_complete_all(struct at_dma_chan *atchan)
> /**
> * atc_advance_work - at the end of a transaction, move forward
> * @atchan: channel where the transaction ended
> - *
> - * Called with atchan->lock held and bh disabled
> */
> static void atc_advance_work(struct at_dma_chan *atchan)
> {
> + unsigned long flags;
> +
> dev_vdbg(chan2dev(&atchan->chan_common), "advance_work\n");
>
> - if (atc_chan_is_enabled(atchan))
> + spin_lock_irqsave(&atchan->lock, flags);
> +
> + if (atc_chan_is_enabled(atchan)) {
> + spin_unlock_irqrestore(&atchan->lock, flags);
> return;
> + }
>
> if (list_empty(&atchan->active_list) ||
> list_is_singular(&atchan->active_list)) {
> + spin_unlock_irqrestore(&atchan->lock, flags);
> atc_complete_all(atchan);
> } else {
> - atc_chain_complete(atchan, atc_first_active(atchan));
> + struct at_desc *desc_first = atc_first_active(atchan);
> +
> + spin_unlock_irqrestore(&atchan->lock, flags);
> + atc_chain_complete(atchan, desc_first);
> + barrier();
> /* advance work */
> - atc_dostart(atchan, atc_first_active(atchan));
> + spin_lock_irqsave(&atchan->lock, flags);
> + desc_first = atc_first_active(atchan);
> + atc_dostart(atchan, desc_first);
> + spin_unlock_irqrestore(&atchan->lock, flags);
> }
> }
>
> @@ -413,13 +434,14 @@ static void atc_advance_work(struct at_dma_chan *atchan)
> /**
> * atc_handle_error - handle errors reported by DMA controller
> * @atchan: channel where error occurs
> - *
> - * Called with atchan->lock held and bh disabled
> */
> static void atc_handle_error(struct at_dma_chan *atchan)
> {
> struct at_desc *bad_desc;
> struct at_desc *child;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&atchan->lock, flags);
>
> /*
> * The descriptor currently at the head of the active list is
> @@ -452,6 +474,8 @@ static void atc_handle_error(struct at_dma_chan *atchan)
> list_for_each_entry(child, &bad_desc->tx_list, desc_node)
> atc_dump_lli(atchan, &child->lli);
>
> + spin_unlock_irqrestore(&atchan->lock, flags);
> +
> /* Pretend the descriptor completed successfully */
> atc_chain_complete(atchan, bad_desc);
> }
> @@ -459,19 +483,27 @@ static void atc_handle_error(struct at_dma_chan *atchan)
> /**
> * atc_handle_cyclic - at the end of a period, run callback function
> * @atchan: channel used for cyclic operations
> - *
> - * Called with atchan->lock held and bh disabled
> */
> static void atc_handle_cyclic(struct at_dma_chan *atchan)
> {
> - struct at_desc *first = atc_first_active(atchan);
> - struct dma_async_tx_descriptor *txd = &first->txd;
> - dma_async_tx_callback callback = txd->callback;
> - void *param = txd->callback_param;
> + struct at_desc *first;
> + struct dma_async_tx_descriptor *txd;
> + dma_async_tx_callback callback;
> + void *param;
> + u32 dscr;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&atchan->lock, flags);
> + first = atc_first_active(atchan);
> + dscr = channel_readl(atchan, DSCR);
> + spin_unlock_irqrestore(&atchan->lock, flags);
> +
> + txd = &first->txd;
> + callback = txd->callback;
> + param = txd->callback_param;
>
> dev_vdbg(chan2dev(&atchan->chan_common),
> - "new cyclic period llp 0x%08x\n",
> - channel_readl(atchan, DSCR));
> + "new cyclic period llp 0x%08x\n", dscr);
>
> if (callback)
> callback(param);
> @@ -482,17 +514,13 @@ static void atc_handle_cyclic(struct at_dma_chan *atchan)
> static void atc_tasklet(unsigned long data)
> {
> struct at_dma_chan *atchan = (struct at_dma_chan *)data;
> - unsigned long flags;
>
> - spin_lock_irqsave(&atchan->lock, flags);
> if (test_and_clear_bit(ATC_IS_ERROR, &atchan->status))
> atc_handle_error(atchan);
> else if (atc_chan_is_cyclic(atchan))
> atc_handle_cyclic(atchan);
> else
> atc_advance_work(atchan);
> -
> - spin_unlock_irqrestore(&atchan->lock, flags);
> }
>
> static irqreturn_t at_dma_interrupt(int irq, void *dev_id)
> @@ -1013,6 +1041,11 @@ static int atc_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
> spin_unlock_irqrestore(&atchan->lock, flags);
> } else if (cmd == DMA_TERMINATE_ALL) {
> struct at_desc *desc, *_desc;
> +
> + /* Disable interrupts */
> + atc_disable_chan_irq(atdma, chan->chan_id);
> + tasklet_disable(&atchan->tasklet);
> +
> /*
> * This is only called when something went wrong elsewhere, so
> * we don't really care about the data. Just disable the
> @@ -1033,14 +1066,22 @@ static int atc_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
> list_splice_init(&atchan->active_list, &list);
>
> /* Flush all pending and queued descriptors */
> - list_for_each_entry_safe(desc, _desc, &list, desc_node)
> + list_for_each_entry_safe(desc, _desc, &list, desc_node) {
> + spin_unlock_irqrestore(&atchan->lock, flags);
> atc_chain_complete(atchan, desc);
> + spin_lock_irqsave(&atchan->lock, flags);
> + }
>
> clear_bit(ATC_IS_PAUSED, &atchan->status);
> /* if channel dedicated to cyclic operations, free it */
> clear_bit(ATC_IS_CYCLIC, &atchan->status);
>
> spin_unlock_irqrestore(&atchan->lock, flags);
> +
> + /* Re-enable channel for future operations */
> + tasklet_enable(&atchan->tasklet);
> + atc_enable_chan_irq(atdma, chan->chan_id);
> +
> } else if (cmd == DMA_SLAVE_CONFIG) {
> return set_runtime_config(chan, (struct dma_slave_config *)arg);
> } else {
> @@ -1065,8 +1106,6 @@ atc_tx_status(struct dma_chan *chan,
> dma_cookie_t cookie,
> struct dma_tx_state *txstate)
> {
> - struct at_dma_chan *atchan = to_at_dma_chan(chan);
> - unsigned long flags;
> enum dma_status ret;
> int bytes = 0;
>
> @@ -1080,13 +1119,9 @@ atc_tx_status(struct dma_chan *chan,
> if (!txstate)
> return DMA_ERROR;
>
> - spin_lock_irqsave(&atchan->lock, flags);
> -
> /* Get number of bytes left in the active transactions */
> bytes = atc_get_bytes_left(chan);
>
> - spin_unlock_irqrestore(&atchan->lock, flags);
> -
> if (unlikely(bytes < 0)) {
> dev_vdbg(chan2dev(chan), "get residual bytes error\n");
> return DMA_ERROR;
> @@ -1101,24 +1136,10 @@ atc_tx_status(struct dma_chan *chan,
> }
>
> /**
> - * atc_issue_pending - try to finish work
> + * atc_issue_pending - void function
> * @chan: target DMA channel
> */
> -static void atc_issue_pending(struct dma_chan *chan)
> -{
> - struct at_dma_chan *atchan = to_at_dma_chan(chan);
> - unsigned long flags;
> -
> - dev_vdbg(chan2dev(chan), "issue_pending\n");
> -
> - /* Not needed for cyclic transfers */
> - if (atc_chan_is_cyclic(atchan))
> - return;
> -
> - spin_lock_irqsave(&atchan->lock, flags);
> - atc_advance_work(atchan);
> - spin_unlock_irqrestore(&atchan->lock, flags);
> -}
> +static void atc_issue_pending(struct dma_chan *chan) {}
>
> /**
> * atc_alloc_chan_resources - allocate resources for DMA channel
> --
> 1.8.2.2
>

--
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/