Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 91539eb1 authored by Iago Abal's avatar Iago Abal Committed by Vinod Koul
Browse files

dmaengine: pl330: fix double lock

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: default avatarIago Abal <mail@iagoabal.eu>
Reviewed-by: default avatarMarek Szyprowski <m.szyprowski@samsung.com>
Signed-off-by: default avatarVinod Koul <vinod.koul@intel.com>
parent 6610d0ed
Loading
Loading
Loading
Loading
+6 −13
Original line number Diff line number Diff line
@@ -1699,7 +1699,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)
@@ -1707,8 +1706,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) ||
@@ -1726,8 +1723,6 @@ static struct pl330_thread *pl330_request_channel(struct pl330_dmac *pl330)
		thrd = NULL;
	}

	spin_unlock_irqrestore(&pl330->lock, flags);

	return thrd;
}

@@ -1745,7 +1740,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;
@@ -1757,10 +1751,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
@@ -2122,20 +2114,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;
}
@@ -2238,12 +2230,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;
@@ -2251,7 +2244,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);
}