diff options
author | Thomas Pugliese <thomas.pugliese@gmail.com> | 2014-02-28 15:31:56 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-02-28 19:13:09 -0500 |
commit | acfadcea2adaa52048c6b3c8a3c75105a5540707 (patch) | |
tree | d846a9a1e898e0d0f4d2bdfe08bc5e5abe8ccf38 /drivers/usb/wusbcore/wa-xfer.c | |
parent | 618836cc3478aec3dc9a60488bfd43ca93a322bd (diff) |
usb: wusbcore: fix stranded URB after HWA unplug
This patch adds error checking to the abort request callback to forcibly
clean up the dequeued transfers if the abort request failed. The
wa_complete_remaining_xfer_segs was modified so that it could be used in
this situation as well. This fixes a stranded URB/PNP hang when the HWA
is unplugged while playing audio to a wireless audio device.
Signed-off-by: Thomas Pugliese <thomas.pugliese@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/wusbcore/wa-xfer.c')
-rw-r--r-- | drivers/usb/wusbcore/wa-xfer.c | 56 |
1 files changed, 51 insertions, 5 deletions
diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c index 3ca055555105..cb061915f051 100644 --- a/drivers/usb/wusbcore/wa-xfer.c +++ b/drivers/usb/wusbcore/wa-xfer.c | |||
@@ -167,6 +167,8 @@ struct wa_xfer { | |||
167 | 167 | ||
168 | static void __wa_populate_dto_urb_isoc(struct wa_xfer *xfer, | 168 | static void __wa_populate_dto_urb_isoc(struct wa_xfer *xfer, |
169 | struct wa_seg *seg, int curr_iso_frame); | 169 | struct wa_seg *seg, int curr_iso_frame); |
170 | static void wa_complete_remaining_xfer_segs(struct wa_xfer *xfer, | ||
171 | int starting_index, enum wa_seg_status status); | ||
170 | 172 | ||
171 | static inline void wa_xfer_init(struct wa_xfer *xfer) | 173 | static inline void wa_xfer_init(struct wa_xfer *xfer) |
172 | { | 174 | { |
@@ -416,12 +418,51 @@ out: | |||
416 | 418 | ||
417 | struct wa_xfer_abort_buffer { | 419 | struct wa_xfer_abort_buffer { |
418 | struct urb urb; | 420 | struct urb urb; |
421 | struct wahc *wa; | ||
419 | struct wa_xfer_abort cmd; | 422 | struct wa_xfer_abort cmd; |
420 | }; | 423 | }; |
421 | 424 | ||
422 | static void __wa_xfer_abort_cb(struct urb *urb) | 425 | static void __wa_xfer_abort_cb(struct urb *urb) |
423 | { | 426 | { |
424 | struct wa_xfer_abort_buffer *b = urb->context; | 427 | struct wa_xfer_abort_buffer *b = urb->context; |
428 | struct wahc *wa = b->wa; | ||
429 | |||
430 | /* | ||
431 | * If the abort request URB failed, then the HWA did not get the abort | ||
432 | * command. Forcibly clean up the xfer without waiting for a Transfer | ||
433 | * Result from the HWA. | ||
434 | */ | ||
435 | if (urb->status < 0) { | ||
436 | struct wa_xfer *xfer; | ||
437 | struct device *dev = &wa->usb_iface->dev; | ||
438 | |||
439 | xfer = wa_xfer_get_by_id(wa, le32_to_cpu(b->cmd.dwTransferID)); | ||
440 | dev_err(dev, "%s: Transfer Abort request failed. result: %d\n", | ||
441 | __func__, urb->status); | ||
442 | if (xfer) { | ||
443 | unsigned long flags; | ||
444 | int done; | ||
445 | struct wa_rpipe *rpipe = xfer->ep->hcpriv; | ||
446 | |||
447 | dev_err(dev, "%s: cleaning up xfer %p ID 0x%08X.\n", | ||
448 | __func__, xfer, wa_xfer_id(xfer)); | ||
449 | spin_lock_irqsave(&xfer->lock, flags); | ||
450 | /* mark all segs as aborted. */ | ||
451 | wa_complete_remaining_xfer_segs(xfer, 0, | ||
452 | WA_SEG_ABORTED); | ||
453 | done = __wa_xfer_is_done(xfer); | ||
454 | spin_unlock_irqrestore(&xfer->lock, flags); | ||
455 | if (done) | ||
456 | wa_xfer_completion(xfer); | ||
457 | wa_xfer_delayed_run(rpipe); | ||
458 | wa_xfer_put(xfer); | ||
459 | } else { | ||
460 | dev_err(dev, "%s: xfer ID 0x%08X already gone.\n", | ||
461 | __func__, le32_to_cpu(b->cmd.dwTransferID)); | ||
462 | } | ||
463 | } | ||
464 | |||
465 | wa_put(wa); /* taken in __wa_xfer_abort */ | ||
425 | usb_put_urb(&b->urb); | 466 | usb_put_urb(&b->urb); |
426 | } | 467 | } |
427 | 468 | ||
@@ -449,6 +490,7 @@ static int __wa_xfer_abort(struct wa_xfer *xfer) | |||
449 | b->cmd.bRequestType = WA_XFER_ABORT; | 490 | b->cmd.bRequestType = WA_XFER_ABORT; |
450 | b->cmd.wRPipe = rpipe->descr.wRPipeIndex; | 491 | b->cmd.wRPipe = rpipe->descr.wRPipeIndex; |
451 | b->cmd.dwTransferID = wa_xfer_id_le32(xfer); | 492 | b->cmd.dwTransferID = wa_xfer_id_le32(xfer); |
493 | b->wa = wa_get(xfer->wa); | ||
452 | 494 | ||
453 | usb_init_urb(&b->urb); | 495 | usb_init_urb(&b->urb); |
454 | usb_fill_bulk_urb(&b->urb, xfer->wa->usb_dev, | 496 | usb_fill_bulk_urb(&b->urb, xfer->wa->usb_dev, |
@@ -462,6 +504,7 @@ static int __wa_xfer_abort(struct wa_xfer *xfer) | |||
462 | 504 | ||
463 | 505 | ||
464 | error_submit: | 506 | error_submit: |
507 | wa_put(xfer->wa); | ||
465 | if (printk_ratelimit()) | 508 | if (printk_ratelimit()) |
466 | dev_err(dev, "xfer %p: Can't submit abort request: %d\n", | 509 | dev_err(dev, "xfer %p: Can't submit abort request: %d\n", |
467 | xfer, result); | 510 | xfer, result); |
@@ -2036,15 +2079,17 @@ static int wa_xfer_status_to_errno(u8 status) | |||
2036 | * no other segment transfer results will be returned from the device. | 2079 | * no other segment transfer results will be returned from the device. |
2037 | * Mark the remaining submitted or pending xfers as completed so that | 2080 | * Mark the remaining submitted or pending xfers as completed so that |
2038 | * the xfer will complete cleanly. | 2081 | * the xfer will complete cleanly. |
2082 | * | ||
2083 | * xfer->lock must be held | ||
2084 | * | ||
2039 | */ | 2085 | */ |
2040 | static void wa_complete_remaining_xfer_segs(struct wa_xfer *xfer, | 2086 | static void wa_complete_remaining_xfer_segs(struct wa_xfer *xfer, |
2041 | struct wa_seg *incoming_seg, enum wa_seg_status status) | 2087 | int starting_index, enum wa_seg_status status) |
2042 | { | 2088 | { |
2043 | int index; | 2089 | int index; |
2044 | struct wa_rpipe *rpipe = xfer->ep->hcpriv; | 2090 | struct wa_rpipe *rpipe = xfer->ep->hcpriv; |
2045 | 2091 | ||
2046 | for (index = incoming_seg->index + 1; index < xfer->segs_submitted; | 2092 | for (index = starting_index; index < xfer->segs_submitted; index++) { |
2047 | index++) { | ||
2048 | struct wa_seg *current_seg = xfer->seg[index]; | 2093 | struct wa_seg *current_seg = xfer->seg[index]; |
2049 | 2094 | ||
2050 | BUG_ON(current_seg == NULL); | 2095 | BUG_ON(current_seg == NULL); |
@@ -2202,7 +2247,8 @@ static void wa_xfer_result_chew(struct wahc *wa, struct wa_xfer *xfer, | |||
2202 | * transfers with data or below for no data, the xfer will complete. | 2247 | * transfers with data or below for no data, the xfer will complete. |
2203 | */ | 2248 | */ |
2204 | if (xfer_result->bTransferSegment & 0x80) | 2249 | if (xfer_result->bTransferSegment & 0x80) |
2205 | wa_complete_remaining_xfer_segs(xfer, seg, WA_SEG_DONE); | 2250 | wa_complete_remaining_xfer_segs(xfer, seg->index + 1, |
2251 | WA_SEG_DONE); | ||
2206 | if (usb_pipeisoc(xfer->urb->pipe) | 2252 | if (usb_pipeisoc(xfer->urb->pipe) |
2207 | && (le32_to_cpu(xfer_result->dwNumOfPackets) > 0)) { | 2253 | && (le32_to_cpu(xfer_result->dwNumOfPackets) > 0)) { |
2208 | /* set up WA state to read the isoc packet status next. */ | 2254 | /* set up WA state to read the isoc packet status next. */ |
@@ -2253,7 +2299,7 @@ error_buf_in_populate: | |||
2253 | error_complete: | 2299 | error_complete: |
2254 | xfer->segs_done++; | 2300 | xfer->segs_done++; |
2255 | rpipe_ready = rpipe_avail_inc(rpipe); | 2301 | rpipe_ready = rpipe_avail_inc(rpipe); |
2256 | wa_complete_remaining_xfer_segs(xfer, seg, seg->status); | 2302 | wa_complete_remaining_xfer_segs(xfer, seg->index + 1, seg->status); |
2257 | done = __wa_xfer_is_done(xfer); | 2303 | done = __wa_xfer_is_done(xfer); |
2258 | /* | 2304 | /* |
2259 | * queue work item to clear STALL for control endpoints. | 2305 | * queue work item to clear STALL for control endpoints. |