| From foo@baz Wed Dec 6 17:39:55 CET 2017 |
| From: Iago Abal <mail@iagoabal.eu> |
| Date: Wed, 11 Jan 2017 14:00:21 +0100 |
| Subject: dmaengine: pl330: fix double lock |
| |
| From: Iago Abal <mail@iagoabal.eu> |
| |
| |
| [ Upstream commit 91539eb1fda2d530d3b268eef542c5414e54bf1a ] |
| |
| The static bug finder EBA (http://www.iagoabal.eu/eba/) reported the |
| following double-lock bug: |
| |
| Double lock: |
| 1. spin_lock_irqsave(pch->lock, flags) at pl330_free_chan_resources:2236; |
| 2. call to function `pl330_release_channel' immediately after; |
| 3. call to function `dma_pl330_rqcb' in line 1753; |
| 4. spin_lock_irqsave(pch->lock, flags) at dma_pl330_rqcb:1505. |
| |
| I have fixed it as suggested by Marek Szyprowski. |
| |
| First, I have replaced `pch->lock' with `pl330->lock' in functions |
| `pl330_alloc_chan_resources' and `pl330_free_chan_resources'. This avoids |
| the double-lock by acquiring a different lock than `dma_pl330_rqcb'. |
| |
| NOTE that, as a result, `pl330_free_chan_resources' executes |
| `list_splice_tail_init' on `pch->work_list' under lock `pl330->lock', |
| whereas in the rest of the code `pch->work_list' is protected by |
| `pch->lock'. I don't know if this may cause race conditions. Similarly |
| `pch->cyclic' is written by `pl330_alloc_chan_resources' under |
| `pl330->lock' but read by `pl330_tx_submit' under `pch->lock'. |
| |
| Second, I have removed locking from `pl330_request_channel' and |
| `pl330_release_channel' functions. Function `pl330_request_channel' is |
| only called from `pl330_alloc_chan_resources', so the lock is already |
| held. Function `pl330_release_channel' is called from |
| `pl330_free_chan_resources', which already holds the lock, and from |
| `pl330_del'. Function `pl330_del' is called in an error path of |
| `pl330_probe' and at the end of `pl330_remove', but I assume that there |
| cannot be concurrent accesses to the protected data at those points. |
| |
| Signed-off-by: Iago Abal <mail@iagoabal.eu> |
| Reviewed-by: Marek Szyprowski <m.szyprowski@samsung.com> |
| Signed-off-by: Vinod Koul <vinod.koul@intel.com> |
| Signed-off-by: Sasha Levin <alexander.levin@verizon.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| drivers/dma/pl330.c | 19 ++++++------------- |
| 1 file changed, 6 insertions(+), 13 deletions(-) |
| |
| --- a/drivers/dma/pl330.c |
| +++ b/drivers/dma/pl330.c |
| @@ -1694,7 +1694,6 @@ static bool _chan_ns(const struct pl330_ |
| static struct pl330_thread *pl330_request_channel(struct pl330_dmac *pl330) |
| { |
| struct pl330_thread *thrd = NULL; |
| - unsigned long flags; |
| int chans, i; |
| |
| if (pl330->state == DYING) |
| @@ -1702,8 +1701,6 @@ static struct pl330_thread *pl330_reques |
| |
| chans = pl330->pcfg.num_chan; |
| |
| - spin_lock_irqsave(&pl330->lock, flags); |
| - |
| for (i = 0; i < chans; i++) { |
| thrd = &pl330->channels[i]; |
| if ((thrd->free) && (!_manager_ns(thrd) || |
| @@ -1721,8 +1718,6 @@ static struct pl330_thread *pl330_reques |
| thrd = NULL; |
| } |
| |
| - spin_unlock_irqrestore(&pl330->lock, flags); |
| - |
| return thrd; |
| } |
| |
| @@ -1740,7 +1735,6 @@ static inline void _free_event(struct pl |
| static void pl330_release_channel(struct pl330_thread *thrd) |
| { |
| struct pl330_dmac *pl330; |
| - unsigned long flags; |
| |
| if (!thrd || thrd->free) |
| return; |
| @@ -1752,10 +1746,8 @@ static void pl330_release_channel(struct |
| |
| pl330 = thrd->dmac; |
| |
| - spin_lock_irqsave(&pl330->lock, flags); |
| _free_event(thrd, thrd->ev); |
| thrd->free = true; |
| - spin_unlock_irqrestore(&pl330->lock, flags); |
| } |
| |
| /* Initialize the structure for PL330 configuration, that can be used |
| @@ -2120,20 +2112,20 @@ static int pl330_alloc_chan_resources(st |
| struct pl330_dmac *pl330 = pch->dmac; |
| unsigned long flags; |
| |
| - spin_lock_irqsave(&pch->lock, flags); |
| + spin_lock_irqsave(&pl330->lock, flags); |
| |
| dma_cookie_init(chan); |
| pch->cyclic = false; |
| |
| pch->thread = pl330_request_channel(pl330); |
| if (!pch->thread) { |
| - spin_unlock_irqrestore(&pch->lock, flags); |
| + spin_unlock_irqrestore(&pl330->lock, flags); |
| return -ENOMEM; |
| } |
| |
| tasklet_init(&pch->task, pl330_tasklet, (unsigned long) pch); |
| |
| - spin_unlock_irqrestore(&pch->lock, flags); |
| + spin_unlock_irqrestore(&pl330->lock, flags); |
| |
| return 1; |
| } |
| @@ -2236,12 +2228,13 @@ static int pl330_pause(struct dma_chan * |
| static void pl330_free_chan_resources(struct dma_chan *chan) |
| { |
| struct dma_pl330_chan *pch = to_pchan(chan); |
| + struct pl330_dmac *pl330 = pch->dmac; |
| unsigned long flags; |
| |
| tasklet_kill(&pch->task); |
| |
| pm_runtime_get_sync(pch->dmac->ddma.dev); |
| - spin_lock_irqsave(&pch->lock, flags); |
| + spin_lock_irqsave(&pl330->lock, flags); |
| |
| pl330_release_channel(pch->thread); |
| pch->thread = NULL; |
| @@ -2249,7 +2242,7 @@ static void pl330_free_chan_resources(st |
| if (pch->cyclic) |
| list_splice_tail_init(&pch->work_list, &pch->dmac->desc_pool); |
| |
| - spin_unlock_irqrestore(&pch->lock, flags); |
| + spin_unlock_irqrestore(&pl330->lock, flags); |
| pm_runtime_mark_last_busy(pch->dmac->ddma.dev); |
| pm_runtime_put_autosuspend(pch->dmac->ddma.dev); |
| } |