When a control transfer has a short data stage, the xHCI controller generates two transfer events: a COMP_SHORT_TX event that specifies the untransferred amount, and a COMP_SUCCESS event. But when the data stage is not short, only the COMP_SUCCESS event occurs. Therefore, xhci-hcd sets urb->actual_length to urb->transfer_buffer_length while processing the COMP_SUCCESS event, unless urb->actual_length was set already by a previous COMP_SHORT_TX event. The driver checks this by seeing whether urb->actual_length == 0, but this alone is the wrong test, as it is entirely possible for a short transfer to have an urb->actual_length = 0. This patch changes the xhci driver to set the urb->actual_length in advance to the expected value of a successful control transfer. The urb->actual_length is then only adjusted in case of short transfers or other special events, but not on COMP_SUCCESS events. This fixes a bug which affected the HSO plugin, which relies on URBs with urb->actual_length == 0 to halt re-submitting the RX URB in the control endpoint. Signed-off-by: Mathias Nyman <mathias.nyman@xxxxxxxxxxxxxxx> --- drivers/usb/host/xhci-ring.c | 73 ++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index b46b5b9..0e02e79 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -732,7 +732,11 @@ remove_finished_td: /* Clean up the cancelled URB */ /* Doesn't matter what we pass for status, since the core will * just overwrite it (because the URB has been unlinked). + * Control urbs have the urb->actual_length pre-set, clear it + * as well */ + if (usb_endpoint_xfer_control(&cur_td->urb->ep->desc)) + cur_td->urb->actual_length = 0; xhci_giveback_urb_in_irq(xhci, cur_td, 0); /* Stop processing the cancelled list if the watchdog timer is @@ -755,6 +759,7 @@ static void xhci_kill_ring_urbs(struct xhci_hcd *xhci, struct xhci_ring *ring) list_del_init(&cur_td->td_list); if (!list_empty(&cur_td->cancelled_td_list)) list_del_init(&cur_td->cancelled_td_list); + cur_td->urb->actual_length = 0; xhci_giveback_urb_in_irq(xhci, cur_td, -ESHUTDOWN); } } @@ -792,6 +797,7 @@ static void xhci_kill_endpoint_urbs(struct xhci_hcd *xhci, cur_td = list_first_entry(&ep->cancelled_td_list, struct xhci_td, cancelled_td_list); list_del_init(&cur_td->cancelled_td_list); + cur_td->urb->actual_length = 0; xhci_giveback_urb_in_irq(xhci, cur_td, -ESHUTDOWN); } } @@ -1888,6 +1894,7 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td, int ep_index; struct xhci_ep_ctx *ep_ctx; u32 trb_comp_code; + bool force_finish_td = false; slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); xdev = xhci->devs[slot_id]; @@ -1906,7 +1913,8 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td, xhci_warn(xhci, "WARN: Success on ctrl data TRB " "without IOC set??\n"); *status = -ESHUTDOWN; - } else { + } else if (*status == -EINPROGRESS) { + /* only set to 0 if no previous event set it earlier */ *status = 0; } break; @@ -1918,6 +1926,8 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td, break; case COMP_STOP_INVAL: case COMP_STOP: + /* we don't continue stopped TDs, so length can be set to 0 */ + td->urb->actual_length = 0; return finish_td(xhci, td, event_trb, event, ep, status, false); default: if (!xhci_requires_manual_halt_cleanup(xhci, @@ -1928,44 +1938,26 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td, trb_comp_code, ep_index); /* else fall through */ case COMP_STALL: - /* Did we transfer part of the data (middle) phase? */ - if (event_trb != ep_ring->dequeue && - event_trb != td->last_trb) - td->urb->actual_length = - td->urb->transfer_buffer_length - - EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); - else - td->urb->actual_length = 0; - - return finish_td(xhci, td, event_trb, event, ep, status, false); + /* length will be set later below if we stall on data stage */ + td->urb->actual_length = 0; + force_finish_td = true; + break; } + + /* If in setup stage, nothing transferred */ + if (event_trb == ep_ring->dequeue) + td->urb->actual_length = 0; + /* - * Did we transfer any data, despite the errors that might have - * happened? I.e. did we get past the setup stage? + * In data stage, check if we transferred any data despite the possible + * errors that might have happened. Give back the urb if stalled, + * otherwise wait for the status stage event. */ - if (event_trb != ep_ring->dequeue) { - /* The event was for the status stage */ - if (event_trb == td->last_trb) { - if (td->urb->actual_length != 0) { - /* Don't overwrite a previously set error code - */ - if ((*status == -EINPROGRESS || *status == 0) && - (td->urb->transfer_flags - & URB_SHORT_NOT_OK)) - /* Did we already see a short data - * stage? */ - *status = -EREMOTEIO; - } else { - td->urb->actual_length = - td->urb->transfer_buffer_length; - } - } else { - /* Maybe the event was for the data stage? */ - td->urb->actual_length = - td->urb->transfer_buffer_length - - EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); - xhci_dbg(xhci, "Waiting for status " - "stage event\n"); + if (event_trb != td->last_trb) { + td->urb->actual_length = td->urb->transfer_buffer_length - + EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); + if (!force_finish_td) { + xhci_dbg(xhci, "Waiting for status stage event\n"); return 0; } } @@ -3346,6 +3338,15 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, */ if (urb->transfer_buffer_length > 0) num_trbs++; + + /* + * We want to set the urb->actual_length in advance and change it in + * case of error or short transfer. A SHORT_TX event may be followed + * by a SUCCESS event for that same TD, messing up the transfer length. + * So we can't touch urb->actual_length later on successful transfers + */ + urb->actual_length = urb->transfer_buffer_length; + ret = prepare_transfer(xhci, xhci->devs[slot_id], ep_index, urb->stream_id, num_trbs, urb, 0, mem_flags); -- 1.8.3.2 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html