diff options
author | Dan Williams <dan.j.williams@intel.com> | 2008-04-17 23:17:25 -0400 |
---|---|---|
committer | Dan Williams <dan.j.williams@intel.com> | 2008-04-17 16:25:05 -0400 |
commit | 19242d7233df7d658405d4b7ee1758d21414cfaa (patch) | |
tree | 4bffa2700c30fdb454dfa150115a0607c6cf3d2a /crypto/async_tx | |
parent | 1c62979ed29a8e2bf9fbe1db101c81a0089676f8 (diff) |
async_tx: fix multiple dependency submission
Shrink struct dma_async_tx_descriptor and introduce
async_tx_channel_switch to properly inject a channel switch interrupt in
the descriptor stream. This simplifies the locking model as drivers no
longer need to handle dma_async_tx_descriptor.lock.
Acked-by: Shannon Nelson <shannon.nelson@intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Diffstat (limited to 'crypto/async_tx')
-rw-r--r-- | crypto/async_tx/async_tx.c | 197 |
1 files changed, 162 insertions, 35 deletions
diff --git a/crypto/async_tx/async_tx.c b/crypto/async_tx/async_tx.c index 2be3bae89930..69756164b61d 100644 --- a/crypto/async_tx/async_tx.c +++ b/crypto/async_tx/async_tx.c | |||
@@ -89,13 +89,19 @@ dma_wait_for_async_tx(struct dma_async_tx_descriptor *tx) | |||
89 | iter = tx; | 89 | iter = tx; |
90 | 90 | ||
91 | /* find the root of the unsubmitted dependency chain */ | 91 | /* find the root of the unsubmitted dependency chain */ |
92 | while (iter->cookie == -EBUSY) { | 92 | do { |
93 | parent = iter->parent; | 93 | parent = iter->parent; |
94 | if (parent && parent->cookie == -EBUSY) | 94 | if (!parent) |
95 | iter = iter->parent; | ||
96 | else | ||
97 | break; | 95 | break; |
98 | } | 96 | else |
97 | iter = parent; | ||
98 | } while (parent); | ||
99 | |||
100 | /* there is a small window for ->parent == NULL and | ||
101 | * ->cookie == -EBUSY | ||
102 | */ | ||
103 | while (iter->cookie == -EBUSY) | ||
104 | cpu_relax(); | ||
99 | 105 | ||
100 | status = dma_sync_wait(iter->chan, iter->cookie); | 106 | status = dma_sync_wait(iter->chan, iter->cookie); |
101 | } while (status == DMA_IN_PROGRESS || (iter != tx)); | 107 | } while (status == DMA_IN_PROGRESS || (iter != tx)); |
@@ -111,24 +117,33 @@ EXPORT_SYMBOL_GPL(dma_wait_for_async_tx); | |||
111 | void | 117 | void |
112 | async_tx_run_dependencies(struct dma_async_tx_descriptor *tx) | 118 | async_tx_run_dependencies(struct dma_async_tx_descriptor *tx) |
113 | { | 119 | { |
114 | struct dma_async_tx_descriptor *dep_tx, *_dep_tx; | 120 | struct dma_async_tx_descriptor *next = tx->next; |
115 | struct dma_device *dev; | ||
116 | struct dma_chan *chan; | 121 | struct dma_chan *chan; |
117 | 122 | ||
118 | list_for_each_entry_safe(dep_tx, _dep_tx, &tx->depend_list, | 123 | if (!next) |
119 | depend_node) { | 124 | return; |
120 | chan = dep_tx->chan; | 125 | |
121 | dev = chan->device; | 126 | tx->next = NULL; |
122 | /* we can't depend on ourselves */ | 127 | chan = next->chan; |
123 | BUG_ON(chan == tx->chan); | 128 | |
124 | list_del(&dep_tx->depend_node); | 129 | /* keep submitting up until a channel switch is detected |
125 | tx->tx_submit(dep_tx); | 130 | * in that case we will be called again as a result of |
126 | 131 | * processing the interrupt from async_tx_channel_switch | |
127 | /* we need to poke the engine as client code does not | 132 | */ |
128 | * know about dependency submission events | 133 | while (next && next->chan == chan) { |
129 | */ | 134 | struct dma_async_tx_descriptor *_next; |
130 | dev->device_issue_pending(chan); | 135 | |
136 | spin_lock_bh(&next->lock); | ||
137 | next->parent = NULL; | ||
138 | _next = next->next; | ||
139 | next->next = NULL; | ||
140 | spin_unlock_bh(&next->lock); | ||
141 | |||
142 | next->tx_submit(next); | ||
143 | next = _next; | ||
131 | } | 144 | } |
145 | |||
146 | chan->device->device_issue_pending(chan); | ||
132 | } | 147 | } |
133 | EXPORT_SYMBOL_GPL(async_tx_run_dependencies); | 148 | EXPORT_SYMBOL_GPL(async_tx_run_dependencies); |
134 | 149 | ||
@@ -397,6 +412,92 @@ static void __exit async_tx_exit(void) | |||
397 | } | 412 | } |
398 | #endif | 413 | #endif |
399 | 414 | ||
415 | |||
416 | /** | ||
417 | * async_tx_channel_switch - queue an interrupt descriptor with a dependency | ||
418 | * pre-attached. | ||
419 | * @depend_tx: the operation that must finish before the new operation runs | ||
420 | * @tx: the new operation | ||
421 | */ | ||
422 | static void | ||
423 | async_tx_channel_switch(struct dma_async_tx_descriptor *depend_tx, | ||
424 | struct dma_async_tx_descriptor *tx) | ||
425 | { | ||
426 | struct dma_chan *chan; | ||
427 | struct dma_device *device; | ||
428 | struct dma_async_tx_descriptor *intr_tx = (void *) ~0; | ||
429 | |||
430 | /* first check to see if we can still append to depend_tx */ | ||
431 | spin_lock_bh(&depend_tx->lock); | ||
432 | if (depend_tx->parent && depend_tx->chan == tx->chan) { | ||
433 | tx->parent = depend_tx; | ||
434 | depend_tx->next = tx; | ||
435 | intr_tx = NULL; | ||
436 | } | ||
437 | spin_unlock_bh(&depend_tx->lock); | ||
438 | |||
439 | if (!intr_tx) | ||
440 | return; | ||
441 | |||
442 | chan = depend_tx->chan; | ||
443 | device = chan->device; | ||
444 | |||
445 | /* see if we can schedule an interrupt | ||
446 | * otherwise poll for completion | ||
447 | */ | ||
448 | if (dma_has_cap(DMA_INTERRUPT, device->cap_mask)) | ||
449 | intr_tx = device->device_prep_dma_interrupt(chan); | ||
450 | else | ||
451 | intr_tx = NULL; | ||
452 | |||
453 | if (intr_tx) { | ||
454 | intr_tx->callback = NULL; | ||
455 | intr_tx->callback_param = NULL; | ||
456 | tx->parent = intr_tx; | ||
457 | /* safe to set ->next outside the lock since we know we are | ||
458 | * not submitted yet | ||
459 | */ | ||
460 | intr_tx->next = tx; | ||
461 | |||
462 | /* check if we need to append */ | ||
463 | spin_lock_bh(&depend_tx->lock); | ||
464 | if (depend_tx->parent) { | ||
465 | intr_tx->parent = depend_tx; | ||
466 | depend_tx->next = intr_tx; | ||
467 | async_tx_ack(intr_tx); | ||
468 | intr_tx = NULL; | ||
469 | } | ||
470 | spin_unlock_bh(&depend_tx->lock); | ||
471 | |||
472 | if (intr_tx) { | ||
473 | intr_tx->parent = NULL; | ||
474 | intr_tx->tx_submit(intr_tx); | ||
475 | async_tx_ack(intr_tx); | ||
476 | } | ||
477 | } else { | ||
478 | if (dma_wait_for_async_tx(depend_tx) == DMA_ERROR) | ||
479 | panic("%s: DMA_ERROR waiting for depend_tx\n", | ||
480 | __func__); | ||
481 | tx->tx_submit(tx); | ||
482 | } | ||
483 | } | ||
484 | |||
485 | |||
486 | /** | ||
487 | * submit_disposition - while holding depend_tx->lock we must avoid submitting | ||
488 | * new operations to prevent a circular locking dependency with | ||
489 | * drivers that already hold a channel lock when calling | ||
490 | * async_tx_run_dependencies. | ||
491 | * @ASYNC_TX_SUBMITTED: we were able to append the new operation under the lock | ||
492 | * @ASYNC_TX_CHANNEL_SWITCH: when the lock is dropped schedule a channel switch | ||
493 | * @ASYNC_TX_DIRECT_SUBMIT: when the lock is dropped submit directly | ||
494 | */ | ||
495 | enum submit_disposition { | ||
496 | ASYNC_TX_SUBMITTED, | ||
497 | ASYNC_TX_CHANNEL_SWITCH, | ||
498 | ASYNC_TX_DIRECT_SUBMIT, | ||
499 | }; | ||
500 | |||
400 | void | 501 | void |
401 | async_tx_submit(struct dma_chan *chan, struct dma_async_tx_descriptor *tx, | 502 | async_tx_submit(struct dma_chan *chan, struct dma_async_tx_descriptor *tx, |
402 | enum async_tx_flags flags, struct dma_async_tx_descriptor *depend_tx, | 503 | enum async_tx_flags flags, struct dma_async_tx_descriptor *depend_tx, |
@@ -405,28 +506,54 @@ async_tx_submit(struct dma_chan *chan, struct dma_async_tx_descriptor *tx, | |||
405 | tx->callback = cb_fn; | 506 | tx->callback = cb_fn; |
406 | tx->callback_param = cb_param; | 507 | tx->callback_param = cb_param; |
407 | 508 | ||
408 | /* set this new tx to run after depend_tx if: | 509 | if (depend_tx) { |
409 | * 1/ a dependency exists (depend_tx is !NULL) | 510 | enum submit_disposition s; |
410 | * 2/ the tx can not be submitted to the current channel | 511 | |
411 | */ | 512 | /* sanity check the dependency chain: |
412 | if (depend_tx && depend_tx->chan != chan) { | 513 | * 1/ if ack is already set then we cannot be sure |
413 | /* if ack is already set then we cannot be sure | ||
414 | * we are referring to the correct operation | 514 | * we are referring to the correct operation |
515 | * 2/ dependencies are 1:1 i.e. two transactions can | ||
516 | * not depend on the same parent | ||
415 | */ | 517 | */ |
416 | BUG_ON(depend_tx->ack); | 518 | BUG_ON(depend_tx->ack || depend_tx->next || tx->parent); |
417 | 519 | ||
418 | tx->parent = depend_tx; | 520 | /* the lock prevents async_tx_run_dependencies from missing |
521 | * the setting of ->next when ->parent != NULL | ||
522 | */ | ||
419 | spin_lock_bh(&depend_tx->lock); | 523 | spin_lock_bh(&depend_tx->lock); |
420 | list_add_tail(&tx->depend_node, &depend_tx->depend_list); | 524 | if (depend_tx->parent) { |
421 | if (depend_tx->cookie == 0) { | 525 | /* we have a parent so we can not submit directly |
422 | struct dma_chan *dep_chan = depend_tx->chan; | 526 | * if we are staying on the same channel: append |
423 | struct dma_device *dep_dev = dep_chan->device; | 527 | * else: channel switch |
424 | dep_dev->device_dependency_added(dep_chan); | 528 | */ |
529 | if (depend_tx->chan == chan) { | ||
530 | tx->parent = depend_tx; | ||
531 | depend_tx->next = tx; | ||
532 | s = ASYNC_TX_SUBMITTED; | ||
533 | } else | ||
534 | s = ASYNC_TX_CHANNEL_SWITCH; | ||
535 | } else { | ||
536 | /* we do not have a parent so we may be able to submit | ||
537 | * directly if we are staying on the same channel | ||
538 | */ | ||
539 | if (depend_tx->chan == chan) | ||
540 | s = ASYNC_TX_DIRECT_SUBMIT; | ||
541 | else | ||
542 | s = ASYNC_TX_CHANNEL_SWITCH; | ||
425 | } | 543 | } |
426 | spin_unlock_bh(&depend_tx->lock); | 544 | spin_unlock_bh(&depend_tx->lock); |
427 | 545 | ||
428 | /* schedule an interrupt to trigger the channel switch */ | 546 | switch (s) { |
429 | async_trigger_callback(ASYNC_TX_ACK, depend_tx, NULL, NULL); | 547 | case ASYNC_TX_SUBMITTED: |
548 | break; | ||
549 | case ASYNC_TX_CHANNEL_SWITCH: | ||
550 | async_tx_channel_switch(depend_tx, tx); | ||
551 | break; | ||
552 | case ASYNC_TX_DIRECT_SUBMIT: | ||
553 | tx->parent = NULL; | ||
554 | tx->tx_submit(tx); | ||
555 | break; | ||
556 | } | ||
430 | } else { | 557 | } else { |
431 | tx->parent = NULL; | 558 | tx->parent = NULL; |
432 | tx->tx_submit(tx); | 559 | tx->tx_submit(tx); |