On 26.02.2015 18:12, Mathias Nyman wrote:
> 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.ny...@linux.intel.com>
> ---
>  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) {

was supposed to be "else if (event_trb != td->last_trb)" to make sure were in 
the data stage

> +             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);
> 

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to