mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-07 19:30:30 +09:00
dmaengine: pl330: fix double lock
[ Upstream commit 91539eb1fd ]
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>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
ca08360610
commit
bc42f67b73
@@ -1657,7 +1657,6 @@ static bool _chan_ns(const struct pl330_dmac *pl330, int i)
|
||||
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_request_channel(struct pl330_dmac *pl330)
|
||||
|
||||
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_request_channel(struct pl330_dmac *pl330)
|
||||
thrd = NULL;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&pl330->lock, flags);
|
||||
|
||||
return thrd;
|
||||
}
|
||||
|
||||
@@ -1703,7 +1698,6 @@ static inline void _free_event(struct pl330_thread *thrd, int ev)
|
||||
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_thread *thrd)
|
||||
|
||||
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(struct dma_chan *chan)
|
||||
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 *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(struct dma_chan *chan)
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user