Re: [RFC PATCH] xhci: fix reporting of 0-sized URBs in control endpoints

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Feb 26, 2015 at 5:12 PM, Mathias Nyman
<mathias.nyman@xxxxxxxxxxxxxxx> 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.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;
>                 }
>         }

Now that I see it... before finishing the td and if the
urb->actual_length < urb->transfer_buffer_length (i.e. short tx),
shouldn't we also check if (td->urb->transfer_flags &
URB_SHORT_NOT_OK) in order to return a status of -EREMOTEIO?

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


-- 
Aleksander
https://aleksander.es
--
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




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux