diff options
author | Haavard Skinnemoen <hskinnemoen@atmel.com> | 2007-11-14 19:59:27 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-11-14 21:45:39 -0500 |
commit | 348badf1e825323c419dd118f65783db0f7d2ec8 (patch) | |
tree | 126cddb26c14233eaff8ddd6a240fe4ac69204ee | |
parent | 90d8dabf74179e6615bd4688a118e12ec29ab7aa (diff) |
dmaengine: fix broken device refcounting
When a DMA device is unregistered, its reference count is decremented twice
for each channel: Once dma_class_dev_release() and once in
dma_chan_cleanup(). This may result in the DMA device driver's remove()
function completing before all channels have been cleaned up, causing lots
of use-after-free fun.
Fix it by incrementing the device's reference count twice for each
channel during registration.
[dan.j.williams@intel.com: kill unnecessary client refcounting]
Signed-off-by: Haavard Skinnemoen <hskinnemoen@atmel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Signed-off-by: Shannon Nelson <shannon.nelson@intel.com>
Cc: <stable@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | drivers/dma/dmaengine.c | 17 |
1 files changed, 6 insertions, 11 deletions
diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index 82489923af09..d59b2f417306 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c | |||
@@ -182,10 +182,9 @@ static void dma_client_chan_alloc(struct dma_client *client) | |||
182 | /* we are done once this client rejects | 182 | /* we are done once this client rejects |
183 | * an available resource | 183 | * an available resource |
184 | */ | 184 | */ |
185 | if (ack == DMA_ACK) { | 185 | if (ack == DMA_ACK) |
186 | dma_chan_get(chan); | 186 | dma_chan_get(chan); |
187 | kref_get(&device->refcount); | 187 | else if (ack == DMA_NAK) |
188 | } else if (ack == DMA_NAK) | ||
189 | return; | 188 | return; |
190 | } | 189 | } |
191 | } | 190 | } |
@@ -272,11 +271,8 @@ static void dma_clients_notify_removed(struct dma_chan *chan) | |||
272 | /* client was holding resources for this channel so | 271 | /* client was holding resources for this channel so |
273 | * free it | 272 | * free it |
274 | */ | 273 | */ |
275 | if (ack == DMA_ACK) { | 274 | if (ack == DMA_ACK) |
276 | dma_chan_put(chan); | 275 | dma_chan_put(chan); |
277 | kref_put(&chan->device->refcount, | ||
278 | dma_async_device_cleanup); | ||
279 | } | ||
280 | } | 276 | } |
281 | 277 | ||
282 | mutex_unlock(&dma_list_mutex); | 278 | mutex_unlock(&dma_list_mutex); |
@@ -316,11 +312,8 @@ void dma_async_client_unregister(struct dma_client *client) | |||
316 | ack = client->event_callback(client, chan, | 312 | ack = client->event_callback(client, chan, |
317 | DMA_RESOURCE_REMOVED); | 313 | DMA_RESOURCE_REMOVED); |
318 | 314 | ||
319 | if (ack == DMA_ACK) { | 315 | if (ack == DMA_ACK) |
320 | dma_chan_put(chan); | 316 | dma_chan_put(chan); |
321 | kref_put(&chan->device->refcount, | ||
322 | dma_async_device_cleanup); | ||
323 | } | ||
324 | } | 317 | } |
325 | 318 | ||
326 | list_del(&client->global_node); | 319 | list_del(&client->global_node); |
@@ -397,6 +390,8 @@ int dma_async_device_register(struct dma_device *device) | |||
397 | goto err_out; | 390 | goto err_out; |
398 | } | 391 | } |
399 | 392 | ||
393 | /* One for the channel, one of the class device */ | ||
394 | kref_get(&device->refcount); | ||
400 | kref_get(&device->refcount); | 395 | kref_get(&device->refcount); |
401 | kref_init(&chan->refcount); | 396 | kref_init(&chan->refcount); |
402 | chan->slow_ref = 0; | 397 | chan->slow_ref = 0; |