This is a note to let you know that I've just added the patch titled
dmaengine: pl330: fix double lock
to the 4.4-stable tree which can be found at: http://www.kernel.org/git/?p=linux/kernel/git/stable/stable-queue.git%3Ba=su...
The filename of the patch is: dmaengine-pl330-fix-double-lock.patch and it can be found in the queue-4.4 subdirectory.
If you, or anyone else, feels it should not be added to the stable tree, please let stable@vger.kernel.org know about it.
From foo@baz Wed Dec 6 16:43:17 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 @@ -1657,7 +1657,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) @@ -1665,8 +1664,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) || @@ -1684,8 +1681,6 @@ static struct pl330_thread *pl330_reques thrd = NULL; }
- spin_unlock_irqrestore(&pl330->lock, flags); - return thrd; }
@@ -1703,7 +1698,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; @@ -1715,10 +1709,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 @@ -2085,20 +2077,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; } @@ -2201,12 +2193,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; @@ -2214,7 +2207,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); }
Patches currently in stable-queue which might be from mail@iagoabal.eu are
queue-4.4/dmaengine-pl330-fix-double-lock.patch
linux-stable-mirror@lists.linaro.org