diff options
author | Iago Abal <mail@iagoabal.eu> | 2017-01-11 08:00:21 -0500 |
---|---|---|
committer | Vinod Koul <vinod.koul@intel.com> | 2017-01-25 05:05:11 -0500 |
commit | 91539eb1fda2d530d3b268eef542c5414e54bf1a (patch) | |
tree | 960f5ca6342ad20837aff18aad6e8ecd7da32fd6 | |
parent | 6610d0edf6dc7ee97e46ab3a538a565c79d26199 (diff) |
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: Iago Abal <mail@iagoabal.eu>
Reviewed-by: Marek Szyprowski <m.szyprowski@samsung.com>
Signed-off-by: Vinod Koul <vinod.koul@intel.com>
-rw-r--r-- | drivers/dma/pl330.c | 19 |
1 files changed, 6 insertions, 13 deletions
diff --git a/drivers/dma/pl330.c b/drivers/dma/pl330.c index 740bbb942594..7539f73df9e0 100644 --- a/drivers/dma/pl330.c +++ b/drivers/dma/pl330.c | |||
@@ -1699,7 +1699,6 @@ static bool _chan_ns(const struct pl330_dmac *pl330, int i) | |||
1699 | static struct pl330_thread *pl330_request_channel(struct pl330_dmac *pl330) | 1699 | static struct pl330_thread *pl330_request_channel(struct pl330_dmac *pl330) |
1700 | { | 1700 | { |
1701 | struct pl330_thread *thrd = NULL; | 1701 | struct pl330_thread *thrd = NULL; |
1702 | unsigned long flags; | ||
1703 | int chans, i; | 1702 | int chans, i; |
1704 | 1703 | ||
1705 | if (pl330->state == DYING) | 1704 | if (pl330->state == DYING) |
@@ -1707,8 +1706,6 @@ static struct pl330_thread *pl330_request_channel(struct pl330_dmac *pl330) | |||
1707 | 1706 | ||
1708 | chans = pl330->pcfg.num_chan; | 1707 | chans = pl330->pcfg.num_chan; |
1709 | 1708 | ||
1710 | spin_lock_irqsave(&pl330->lock, flags); | ||
1711 | |||
1712 | for (i = 0; i < chans; i++) { | 1709 | for (i = 0; i < chans; i++) { |
1713 | thrd = &pl330->channels[i]; | 1710 | thrd = &pl330->channels[i]; |
1714 | if ((thrd->free) && (!_manager_ns(thrd) || | 1711 | if ((thrd->free) && (!_manager_ns(thrd) || |
@@ -1726,8 +1723,6 @@ static struct pl330_thread *pl330_request_channel(struct pl330_dmac *pl330) | |||
1726 | thrd = NULL; | 1723 | thrd = NULL; |
1727 | } | 1724 | } |
1728 | 1725 | ||
1729 | spin_unlock_irqrestore(&pl330->lock, flags); | ||
1730 | |||
1731 | return thrd; | 1726 | return thrd; |
1732 | } | 1727 | } |
1733 | 1728 | ||
@@ -1745,7 +1740,6 @@ static inline void _free_event(struct pl330_thread *thrd, int ev) | |||
1745 | static void pl330_release_channel(struct pl330_thread *thrd) | 1740 | static void pl330_release_channel(struct pl330_thread *thrd) |
1746 | { | 1741 | { |
1747 | struct pl330_dmac *pl330; | 1742 | struct pl330_dmac *pl330; |
1748 | unsigned long flags; | ||
1749 | 1743 | ||
1750 | if (!thrd || thrd->free) | 1744 | if (!thrd || thrd->free) |
1751 | return; | 1745 | return; |
@@ -1757,10 +1751,8 @@ static void pl330_release_channel(struct pl330_thread *thrd) | |||
1757 | 1751 | ||
1758 | pl330 = thrd->dmac; | 1752 | pl330 = thrd->dmac; |
1759 | 1753 | ||
1760 | spin_lock_irqsave(&pl330->lock, flags); | ||
1761 | _free_event(thrd, thrd->ev); | 1754 | _free_event(thrd, thrd->ev); |
1762 | thrd->free = true; | 1755 | thrd->free = true; |
1763 | spin_unlock_irqrestore(&pl330->lock, flags); | ||
1764 | } | 1756 | } |
1765 | 1757 | ||
1766 | /* Initialize the structure for PL330 configuration, that can be used | 1758 | /* Initialize the structure for PL330 configuration, that can be used |
@@ -2122,20 +2114,20 @@ static int pl330_alloc_chan_resources(struct dma_chan *chan) | |||
2122 | struct pl330_dmac *pl330 = pch->dmac; | 2114 | struct pl330_dmac *pl330 = pch->dmac; |
2123 | unsigned long flags; | 2115 | unsigned long flags; |
2124 | 2116 | ||
2125 | spin_lock_irqsave(&pch->lock, flags); | 2117 | spin_lock_irqsave(&pl330->lock, flags); |
2126 | 2118 | ||
2127 | dma_cookie_init(chan); | 2119 | dma_cookie_init(chan); |
2128 | pch->cyclic = false; | 2120 | pch->cyclic = false; |
2129 | 2121 | ||
2130 | pch->thread = pl330_request_channel(pl330); | 2122 | pch->thread = pl330_request_channel(pl330); |
2131 | if (!pch->thread) { | 2123 | if (!pch->thread) { |
2132 | spin_unlock_irqrestore(&pch->lock, flags); | 2124 | spin_unlock_irqrestore(&pl330->lock, flags); |
2133 | return -ENOMEM; | 2125 | return -ENOMEM; |
2134 | } | 2126 | } |
2135 | 2127 | ||
2136 | tasklet_init(&pch->task, pl330_tasklet, (unsigned long) pch); | 2128 | tasklet_init(&pch->task, pl330_tasklet, (unsigned long) pch); |
2137 | 2129 | ||
2138 | spin_unlock_irqrestore(&pch->lock, flags); | 2130 | spin_unlock_irqrestore(&pl330->lock, flags); |
2139 | 2131 | ||
2140 | return 1; | 2132 | return 1; |
2141 | } | 2133 | } |
@@ -2238,12 +2230,13 @@ static int pl330_pause(struct dma_chan *chan) | |||
2238 | static void pl330_free_chan_resources(struct dma_chan *chan) | 2230 | static void pl330_free_chan_resources(struct dma_chan *chan) |
2239 | { | 2231 | { |
2240 | struct dma_pl330_chan *pch = to_pchan(chan); | 2232 | struct dma_pl330_chan *pch = to_pchan(chan); |
2233 | struct pl330_dmac *pl330 = pch->dmac; | ||
2241 | unsigned long flags; | 2234 | unsigned long flags; |
2242 | 2235 | ||
2243 | tasklet_kill(&pch->task); | 2236 | tasklet_kill(&pch->task); |
2244 | 2237 | ||
2245 | pm_runtime_get_sync(pch->dmac->ddma.dev); | 2238 | pm_runtime_get_sync(pch->dmac->ddma.dev); |
2246 | spin_lock_irqsave(&pch->lock, flags); | 2239 | spin_lock_irqsave(&pl330->lock, flags); |
2247 | 2240 | ||
2248 | pl330_release_channel(pch->thread); | 2241 | pl330_release_channel(pch->thread); |
2249 | pch->thread = NULL; | 2242 | pch->thread = NULL; |
@@ -2251,7 +2244,7 @@ static void pl330_free_chan_resources(struct dma_chan *chan) | |||
2251 | if (pch->cyclic) | 2244 | if (pch->cyclic) |
2252 | list_splice_tail_init(&pch->work_list, &pch->dmac->desc_pool); | 2245 | list_splice_tail_init(&pch->work_list, &pch->dmac->desc_pool); |
2253 | 2246 | ||
2254 | spin_unlock_irqrestore(&pch->lock, flags); | 2247 | spin_unlock_irqrestore(&pl330->lock, flags); |
2255 | pm_runtime_mark_last_busy(pch->dmac->ddma.dev); | 2248 | pm_runtime_mark_last_busy(pch->dmac->ddma.dev); |
2256 | pm_runtime_put_autosuspend(pch->dmac->ddma.dev); | 2249 | pm_runtime_put_autosuspend(pch->dmac->ddma.dev); |
2257 | } | 2250 | } |