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 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.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) {

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 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