diff options
Diffstat (limited to 'drivers/dma/dmaengine.c')
-rw-r--r-- | drivers/dma/dmaengine.c | 205 |
1 files changed, 128 insertions, 77 deletions
diff --git a/drivers/dma/dmaengine.c b/drivers/dma/dmaengine.c index b9008932a8f3..d4d925912c47 100644 --- a/drivers/dma/dmaengine.c +++ b/drivers/dma/dmaengine.c | |||
@@ -74,6 +74,7 @@ | |||
74 | static DEFINE_MUTEX(dma_list_mutex); | 74 | static DEFINE_MUTEX(dma_list_mutex); |
75 | static LIST_HEAD(dma_device_list); | 75 | static LIST_HEAD(dma_device_list); |
76 | static LIST_HEAD(dma_client_list); | 76 | static LIST_HEAD(dma_client_list); |
77 | static long dmaengine_ref_count; | ||
77 | 78 | ||
78 | /* --- sysfs implementation --- */ | 79 | /* --- sysfs implementation --- */ |
79 | 80 | ||
@@ -105,19 +106,8 @@ static ssize_t show_bytes_transferred(struct device *dev, struct device_attribut | |||
105 | static ssize_t show_in_use(struct device *dev, struct device_attribute *attr, char *buf) | 106 | static ssize_t show_in_use(struct device *dev, struct device_attribute *attr, char *buf) |
106 | { | 107 | { |
107 | struct dma_chan *chan = to_dma_chan(dev); | 108 | struct dma_chan *chan = to_dma_chan(dev); |
108 | int in_use = 0; | ||
109 | |||
110 | if (unlikely(chan->slow_ref) && | ||
111 | atomic_read(&chan->refcount.refcount) > 1) | ||
112 | in_use = 1; | ||
113 | else { | ||
114 | if (local_read(&(per_cpu_ptr(chan->local, | ||
115 | get_cpu())->refcount)) > 0) | ||
116 | in_use = 1; | ||
117 | put_cpu(); | ||
118 | } | ||
119 | 109 | ||
120 | return sprintf(buf, "%d\n", in_use); | 110 | return sprintf(buf, "%d\n", chan->client_count); |
121 | } | 111 | } |
122 | 112 | ||
123 | static struct device_attribute dma_attrs[] = { | 113 | static struct device_attribute dma_attrs[] = { |
@@ -155,6 +145,78 @@ __dma_chan_satisfies_mask(struct dma_chan *chan, dma_cap_mask_t *want) | |||
155 | return bitmap_equal(want->bits, has.bits, DMA_TX_TYPE_END); | 145 | return bitmap_equal(want->bits, has.bits, DMA_TX_TYPE_END); |
156 | } | 146 | } |
157 | 147 | ||
148 | static struct module *dma_chan_to_owner(struct dma_chan *chan) | ||
149 | { | ||
150 | return chan->device->dev->driver->owner; | ||
151 | } | ||
152 | |||
153 | /** | ||
154 | * balance_ref_count - catch up the channel reference count | ||
155 | * @chan - channel to balance ->client_count versus dmaengine_ref_count | ||
156 | * | ||
157 | * balance_ref_count must be called under dma_list_mutex | ||
158 | */ | ||
159 | static void balance_ref_count(struct dma_chan *chan) | ||
160 | { | ||
161 | struct module *owner = dma_chan_to_owner(chan); | ||
162 | |||
163 | while (chan->client_count < dmaengine_ref_count) { | ||
164 | __module_get(owner); | ||
165 | chan->client_count++; | ||
166 | } | ||
167 | } | ||
168 | |||
169 | /** | ||
170 | * dma_chan_get - try to grab a dma channel's parent driver module | ||
171 | * @chan - channel to grab | ||
172 | * | ||
173 | * Must be called under dma_list_mutex | ||
174 | */ | ||
175 | static int dma_chan_get(struct dma_chan *chan) | ||
176 | { | ||
177 | int err = -ENODEV; | ||
178 | struct module *owner = dma_chan_to_owner(chan); | ||
179 | |||
180 | if (chan->client_count) { | ||
181 | __module_get(owner); | ||
182 | err = 0; | ||
183 | } else if (try_module_get(owner)) | ||
184 | err = 0; | ||
185 | |||
186 | if (err == 0) | ||
187 | chan->client_count++; | ||
188 | |||
189 | /* allocate upon first client reference */ | ||
190 | if (chan->client_count == 1 && err == 0) { | ||
191 | int desc_cnt = chan->device->device_alloc_chan_resources(chan, NULL); | ||
192 | |||
193 | if (desc_cnt < 0) { | ||
194 | err = desc_cnt; | ||
195 | chan->client_count = 0; | ||
196 | module_put(owner); | ||
197 | } else | ||
198 | balance_ref_count(chan); | ||
199 | } | ||
200 | |||
201 | return err; | ||
202 | } | ||
203 | |||
204 | /** | ||
205 | * dma_chan_put - drop a reference to a dma channel's parent driver module | ||
206 | * @chan - channel to release | ||
207 | * | ||
208 | * Must be called under dma_list_mutex | ||
209 | */ | ||
210 | static void dma_chan_put(struct dma_chan *chan) | ||
211 | { | ||
212 | if (!chan->client_count) | ||
213 | return; /* this channel failed alloc_chan_resources */ | ||
214 | chan->client_count--; | ||
215 | module_put(dma_chan_to_owner(chan)); | ||
216 | if (chan->client_count == 0) | ||
217 | chan->device->device_free_chan_resources(chan); | ||
218 | } | ||
219 | |||
158 | /** | 220 | /** |
159 | * dma_client_chan_alloc - try to allocate channels to a client | 221 | * dma_client_chan_alloc - try to allocate channels to a client |
160 | * @client: &dma_client | 222 | * @client: &dma_client |
@@ -165,7 +227,6 @@ static void dma_client_chan_alloc(struct dma_client *client) | |||
165 | { | 227 | { |
166 | struct dma_device *device; | 228 | struct dma_device *device; |
167 | struct dma_chan *chan; | 229 | struct dma_chan *chan; |
168 | int desc; /* allocated descriptor count */ | ||
169 | enum dma_state_client ack; | 230 | enum dma_state_client ack; |
170 | 231 | ||
171 | /* Find a channel */ | 232 | /* Find a channel */ |
@@ -178,23 +239,16 @@ static void dma_client_chan_alloc(struct dma_client *client) | |||
178 | list_for_each_entry(chan, &device->channels, device_node) { | 239 | list_for_each_entry(chan, &device->channels, device_node) { |
179 | if (!dma_chan_satisfies_mask(chan, client->cap_mask)) | 240 | if (!dma_chan_satisfies_mask(chan, client->cap_mask)) |
180 | continue; | 241 | continue; |
242 | if (!chan->client_count) | ||
243 | continue; | ||
244 | ack = client->event_callback(client, chan, | ||
245 | DMA_RESOURCE_AVAILABLE); | ||
181 | 246 | ||
182 | desc = chan->device->device_alloc_chan_resources( | 247 | /* we are done once this client rejects |
183 | chan, client); | 248 | * an available resource |
184 | if (desc >= 0) { | 249 | */ |
185 | ack = client->event_callback(client, | 250 | if (ack == DMA_NAK) |
186 | chan, | 251 | return; |
187 | DMA_RESOURCE_AVAILABLE); | ||
188 | |||
189 | /* we are done once this client rejects | ||
190 | * an available resource | ||
191 | */ | ||
192 | if (ack == DMA_ACK) { | ||
193 | dma_chan_get(chan); | ||
194 | chan->client_count++; | ||
195 | } else if (ack == DMA_NAK) | ||
196 | return; | ||
197 | } | ||
198 | } | 252 | } |
199 | } | 253 | } |
200 | } | 254 | } |
@@ -224,7 +278,6 @@ EXPORT_SYMBOL(dma_sync_wait); | |||
224 | void dma_chan_cleanup(struct kref *kref) | 278 | void dma_chan_cleanup(struct kref *kref) |
225 | { | 279 | { |
226 | struct dma_chan *chan = container_of(kref, struct dma_chan, refcount); | 280 | struct dma_chan *chan = container_of(kref, struct dma_chan, refcount); |
227 | chan->device->device_free_chan_resources(chan); | ||
228 | kref_put(&chan->device->refcount, dma_async_device_cleanup); | 281 | kref_put(&chan->device->refcount, dma_async_device_cleanup); |
229 | } | 282 | } |
230 | EXPORT_SYMBOL(dma_chan_cleanup); | 283 | EXPORT_SYMBOL(dma_chan_cleanup); |
@@ -232,18 +285,12 @@ EXPORT_SYMBOL(dma_chan_cleanup); | |||
232 | static void dma_chan_free_rcu(struct rcu_head *rcu) | 285 | static void dma_chan_free_rcu(struct rcu_head *rcu) |
233 | { | 286 | { |
234 | struct dma_chan *chan = container_of(rcu, struct dma_chan, rcu); | 287 | struct dma_chan *chan = container_of(rcu, struct dma_chan, rcu); |
235 | int bias = 0x7FFFFFFF; | 288 | |
236 | int i; | ||
237 | for_each_possible_cpu(i) | ||
238 | bias -= local_read(&per_cpu_ptr(chan->local, i)->refcount); | ||
239 | atomic_sub(bias, &chan->refcount.refcount); | ||
240 | kref_put(&chan->refcount, dma_chan_cleanup); | 289 | kref_put(&chan->refcount, dma_chan_cleanup); |
241 | } | 290 | } |
242 | 291 | ||
243 | static void dma_chan_release(struct dma_chan *chan) | 292 | static void dma_chan_release(struct dma_chan *chan) |
244 | { | 293 | { |
245 | atomic_add(0x7FFFFFFF, &chan->refcount.refcount); | ||
246 | chan->slow_ref = 1; | ||
247 | call_rcu(&chan->rcu, dma_chan_free_rcu); | 294 | call_rcu(&chan->rcu, dma_chan_free_rcu); |
248 | } | 295 | } |
249 | 296 | ||
@@ -263,43 +310,36 @@ static void dma_clients_notify_available(void) | |||
263 | } | 310 | } |
264 | 311 | ||
265 | /** | 312 | /** |
266 | * dma_chans_notify_available - tell the clients that a channel is going away | ||
267 | * @chan: channel on its way out | ||
268 | */ | ||
269 | static void dma_clients_notify_removed(struct dma_chan *chan) | ||
270 | { | ||
271 | struct dma_client *client; | ||
272 | enum dma_state_client ack; | ||
273 | |||
274 | mutex_lock(&dma_list_mutex); | ||
275 | |||
276 | list_for_each_entry(client, &dma_client_list, global_node) { | ||
277 | ack = client->event_callback(client, chan, | ||
278 | DMA_RESOURCE_REMOVED); | ||
279 | |||
280 | /* client was holding resources for this channel so | ||
281 | * free it | ||
282 | */ | ||
283 | if (ack == DMA_ACK) { | ||
284 | dma_chan_put(chan); | ||
285 | chan->client_count--; | ||
286 | } | ||
287 | } | ||
288 | |||
289 | mutex_unlock(&dma_list_mutex); | ||
290 | } | ||
291 | |||
292 | /** | ||
293 | * dma_async_client_register - register a &dma_client | 313 | * dma_async_client_register - register a &dma_client |
294 | * @client: ptr to a client structure with valid 'event_callback' and 'cap_mask' | 314 | * @client: ptr to a client structure with valid 'event_callback' and 'cap_mask' |
295 | */ | 315 | */ |
296 | void dma_async_client_register(struct dma_client *client) | 316 | void dma_async_client_register(struct dma_client *client) |
297 | { | 317 | { |
318 | struct dma_device *device, *_d; | ||
319 | struct dma_chan *chan; | ||
320 | int err; | ||
321 | |||
298 | /* validate client data */ | 322 | /* validate client data */ |
299 | BUG_ON(dma_has_cap(DMA_SLAVE, client->cap_mask) && | 323 | BUG_ON(dma_has_cap(DMA_SLAVE, client->cap_mask) && |
300 | !client->slave); | 324 | !client->slave); |
301 | 325 | ||
302 | mutex_lock(&dma_list_mutex); | 326 | mutex_lock(&dma_list_mutex); |
327 | dmaengine_ref_count++; | ||
328 | |||
329 | /* try to grab channels */ | ||
330 | list_for_each_entry_safe(device, _d, &dma_device_list, global_node) | ||
331 | list_for_each_entry(chan, &device->channels, device_node) { | ||
332 | err = dma_chan_get(chan); | ||
333 | if (err == -ENODEV) { | ||
334 | /* module removed before we could use it */ | ||
335 | list_del_init(&device->global_node); | ||
336 | break; | ||
337 | } else if (err) | ||
338 | pr_err("dmaengine: failed to get %s: (%d)\n", | ||
339 | dev_name(&chan->dev), err); | ||
340 | } | ||
341 | |||
342 | |||
303 | list_add_tail(&client->global_node, &dma_client_list); | 343 | list_add_tail(&client->global_node, &dma_client_list); |
304 | mutex_unlock(&dma_list_mutex); | 344 | mutex_unlock(&dma_list_mutex); |
305 | } | 345 | } |
@@ -315,23 +355,17 @@ void dma_async_client_unregister(struct dma_client *client) | |||
315 | { | 355 | { |
316 | struct dma_device *device; | 356 | struct dma_device *device; |
317 | struct dma_chan *chan; | 357 | struct dma_chan *chan; |
318 | enum dma_state_client ack; | ||
319 | 358 | ||
320 | if (!client) | 359 | if (!client) |
321 | return; | 360 | return; |
322 | 361 | ||
323 | mutex_lock(&dma_list_mutex); | 362 | mutex_lock(&dma_list_mutex); |
324 | /* free all channels the client is holding */ | 363 | dmaengine_ref_count--; |
364 | BUG_ON(dmaengine_ref_count < 0); | ||
365 | /* drop channel references */ | ||
325 | list_for_each_entry(device, &dma_device_list, global_node) | 366 | list_for_each_entry(device, &dma_device_list, global_node) |
326 | list_for_each_entry(chan, &device->channels, device_node) { | 367 | list_for_each_entry(chan, &device->channels, device_node) |
327 | ack = client->event_callback(client, chan, | 368 | dma_chan_put(chan); |
328 | DMA_RESOURCE_REMOVED); | ||
329 | |||
330 | if (ack == DMA_ACK) { | ||
331 | dma_chan_put(chan); | ||
332 | chan->client_count--; | ||
333 | } | ||
334 | } | ||
335 | 369 | ||
336 | list_del(&client->global_node); | 370 | list_del(&client->global_node); |
337 | mutex_unlock(&dma_list_mutex); | 371 | mutex_unlock(&dma_list_mutex); |
@@ -423,6 +457,21 @@ int dma_async_device_register(struct dma_device *device) | |||
423 | } | 457 | } |
424 | 458 | ||
425 | mutex_lock(&dma_list_mutex); | 459 | mutex_lock(&dma_list_mutex); |
460 | if (dmaengine_ref_count) | ||
461 | list_for_each_entry(chan, &device->channels, device_node) { | ||
462 | /* if clients are already waiting for channels we need | ||
463 | * to take references on their behalf | ||
464 | */ | ||
465 | if (dma_chan_get(chan) == -ENODEV) { | ||
466 | /* note we can only get here for the first | ||
467 | * channel as the remaining channels are | ||
468 | * guaranteed to get a reference | ||
469 | */ | ||
470 | rc = -ENODEV; | ||
471 | mutex_unlock(&dma_list_mutex); | ||
472 | goto err_out; | ||
473 | } | ||
474 | } | ||
426 | list_add_tail(&device->global_node, &dma_device_list); | 475 | list_add_tail(&device->global_node, &dma_device_list); |
427 | mutex_unlock(&dma_list_mutex); | 476 | mutex_unlock(&dma_list_mutex); |
428 | 477 | ||
@@ -456,7 +505,7 @@ static void dma_async_device_cleanup(struct kref *kref) | |||
456 | } | 505 | } |
457 | 506 | ||
458 | /** | 507 | /** |
459 | * dma_async_device_unregister - unregisters DMA devices | 508 | * dma_async_device_unregister - unregister a DMA device |
460 | * @device: &dma_device | 509 | * @device: &dma_device |
461 | */ | 510 | */ |
462 | void dma_async_device_unregister(struct dma_device *device) | 511 | void dma_async_device_unregister(struct dma_device *device) |
@@ -468,7 +517,9 @@ void dma_async_device_unregister(struct dma_device *device) | |||
468 | mutex_unlock(&dma_list_mutex); | 517 | mutex_unlock(&dma_list_mutex); |
469 | 518 | ||
470 | list_for_each_entry(chan, &device->channels, device_node) { | 519 | list_for_each_entry(chan, &device->channels, device_node) { |
471 | dma_clients_notify_removed(chan); | 520 | WARN_ONCE(chan->client_count, |
521 | "%s called while %d clients hold a reference\n", | ||
522 | __func__, chan->client_count); | ||
472 | device_unregister(&chan->dev); | 523 | device_unregister(&chan->dev); |
473 | dma_chan_release(chan); | 524 | dma_chan_release(chan); |
474 | } | 525 | } |