Commit 34a58e7e authored by Aleksander Morgado's avatar Aleksander Morgado Committed by Luis Henriques

xhci: fix reporting of 0-sized URBs in control endpoint

commit 45ba2154 upstream.

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.
Signed-off-by: default avatarAleksander Morgado <aleksander@aleksander.es>
Signed-off-by: default avatarMathias Nyman <mathias.nyman@linux.intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: default avatarLuis Henriques <luis.henriques@canonical.com>
parent 028a0a83
...@@ -1981,7 +1981,7 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td, ...@@ -1981,7 +1981,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) &&
...@@ -1995,7 +1995,13 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td, ...@@ -1995,7 +1995,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));
......
/* /*
* xHCI host controller driver * xHCI host controller driver
* *
...@@ -1289,6 +1290,8 @@ struct xhci_td { ...@@ -1289,6 +1290,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 */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment