xhci: fix reporting of 0-sized URBs in control endpoint
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 must set 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 rely on a new td->urb_length_set flag, which is set to true when a COMP_SHORT_TX event is received and the URB length updated at that stage. 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. Cc: <stable@vger.kernel.org> Signed-off-by: Aleksander Morgado <aleksander@aleksander.es> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
d3d5389475
commit
45ba2154d1
2 changed files with 11 additions and 2 deletions
|
@ -1946,7 +1946,7 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td,
|
||||||
if (event_trb != ep_ring->dequeue) {
|
if (event_trb != ep_ring->dequeue) {
|
||||||
/* The event was for the status stage */
|
/* The event was for the status stage */
|
||||||
if (event_trb == td->last_trb) {
|
if (event_trb == td->last_trb) {
|
||||||
if (td->urb->actual_length != 0) {
|
if (td->urb_length_set) {
|
||||||
/* Don't overwrite a previously set error code
|
/* Don't overwrite a previously set error code
|
||||||
*/
|
*/
|
||||||
if ((*status == -EINPROGRESS || *status == 0) &&
|
if ((*status == -EINPROGRESS || *status == 0) &&
|
||||||
|
@ -1960,7 +1960,13 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td,
|
||||||
td->urb->transfer_buffer_length;
|
td->urb->transfer_buffer_length;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Maybe the event was for the data stage? */
|
/*
|
||||||
|
* Maybe the event was for the data stage? If so, update
|
||||||
|
* already the actual_length of the URB and flag it as
|
||||||
|
* set, so that it is not overwritten in the event for
|
||||||
|
* the last TRB.
|
||||||
|
*/
|
||||||
|
td->urb_length_set = true;
|
||||||
td->urb->actual_length =
|
td->urb->actual_length =
|
||||||
td->urb->transfer_buffer_length -
|
td->urb->transfer_buffer_length -
|
||||||
EVENT_TRB_LEN(le32_to_cpu(event->transfer_len));
|
EVENT_TRB_LEN(le32_to_cpu(event->transfer_len));
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* xHCI host controller driver
|
* xHCI host controller driver
|
||||||
*
|
*
|
||||||
|
@ -1291,6 +1292,8 @@ struct xhci_td {
|
||||||
struct xhci_segment *start_seg;
|
struct xhci_segment *start_seg;
|
||||||
union xhci_trb *first_trb;
|
union xhci_trb *first_trb;
|
||||||
union xhci_trb *last_trb;
|
union xhci_trb *last_trb;
|
||||||
|
/* actual_length of the URB has already been set */
|
||||||
|
bool urb_length_set;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* xHCI command default timeout value */
|
/* xHCI command default timeout value */
|
||||||
|
|
Loading…
Add table
Reference in a new issue