Commit 7a79a60f authored by Mathias Nyman's avatar Mathias Nyman Committed by Greg Kroah-Hartman

usb: xhci: Don't try to recover an endpoint if port is in error state.

commit b8c3b718 upstream.

A USB3 device needs to be reset and re-enumarated if the port it
connects to goes to a error state, with link state inactive.

There is no use in trying to recover failed transactions by resetting
endpoints at this stage. Tests show that in rare cases, after multiple
endpoint resets of a roothub port the whole host controller might stop
completely.

Several retries to recover from transaction error can happen as
it can take a long time before the hub thread discovers the USB3
port error and inactive link.

We can't reliably detect the port error from slot or endpoint context
due to a limitation in xhci, see xhci specs section 4.8.3:
"There are several cases where the EP State field in the Output
Endpoint Context may not reflect the current state of an endpoint"
and
"Software should maintain an accurate value for EP State, by tracking it
with an internal variable that is driven by Events and Doorbell accesses"

Same appears to be true for slot state.

set a flag to the corresponding slot if a USB3 roothub port link goes
inactive to prevent both queueing new URBs and resetting endpoints.
Reported-by: default avatarRapolu Chiranjeevi <chiranjeevi.rapolu@intel.com>
Tested-by: default avatarRapolu Chiranjeevi <chiranjeevi.rapolu@intel.com>
Cc: <stable@vger.kernel.org>
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 avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent ee742005
...@@ -1608,8 +1608,13 @@ static void handle_port_status(struct xhci_hcd *xhci, ...@@ -1608,8 +1608,13 @@ static void handle_port_status(struct xhci_hcd *xhci,
usb_hcd_resume_root_hub(hcd); usb_hcd_resume_root_hub(hcd);
} }
if (hcd->speed >= HCD_USB3 && (portsc & PORT_PLS_MASK) == XDEV_INACTIVE) if (hcd->speed >= HCD_USB3 &&
(portsc & PORT_PLS_MASK) == XDEV_INACTIVE) {
slot_id = xhci_find_slot_id_by_port(hcd, xhci, hcd_portnum + 1);
if (slot_id && xhci->devs[slot_id])
xhci->devs[slot_id]->flags |= VDEV_PORT_ERROR;
bus_state->port_remote_wakeup &= ~(1 << hcd_portnum); bus_state->port_remote_wakeup &= ~(1 << hcd_portnum);
}
if ((portsc & PORT_PLC) && (portsc & PORT_PLS_MASK) == XDEV_RESUME) { if ((portsc & PORT_PLC) && (portsc & PORT_PLS_MASK) == XDEV_RESUME) {
xhci_dbg(xhci, "port resume event for port %d\n", port_id); xhci_dbg(xhci, "port resume event for port %d\n", port_id);
...@@ -1797,6 +1802,14 @@ static void xhci_cleanup_halted_endpoint(struct xhci_hcd *xhci, ...@@ -1797,6 +1802,14 @@ static void xhci_cleanup_halted_endpoint(struct xhci_hcd *xhci,
{ {
struct xhci_virt_ep *ep = &xhci->devs[slot_id]->eps[ep_index]; struct xhci_virt_ep *ep = &xhci->devs[slot_id]->eps[ep_index];
struct xhci_command *command; struct xhci_command *command;
/*
* Avoid resetting endpoint if link is inactive. Can cause host hang.
* Device will be reset soon to recover the link so don't do anything
*/
if (xhci->devs[slot_id]->flags & VDEV_PORT_ERROR)
return;
command = xhci_alloc_command(xhci, false, GFP_ATOMIC); command = xhci_alloc_command(xhci, false, GFP_ATOMIC);
if (!command) if (!command)
return; return;
......
...@@ -1442,6 +1442,10 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag ...@@ -1442,6 +1442,10 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
xhci_dbg(xhci, "urb submitted during PCI suspend\n"); xhci_dbg(xhci, "urb submitted during PCI suspend\n");
return -ESHUTDOWN; return -ESHUTDOWN;
} }
if (xhci->devs[slot_id]->flags & VDEV_PORT_ERROR) {
xhci_dbg(xhci, "Can't queue urb, port error, link inactive\n");
return -ENODEV;
}
if (usb_endpoint_xfer_isoc(&urb->ep->desc)) if (usb_endpoint_xfer_isoc(&urb->ep->desc))
num_tds = urb->number_of_packets; num_tds = urb->number_of_packets;
...@@ -3724,6 +3728,7 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, ...@@ -3724,6 +3728,7 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd,
} }
/* If necessary, update the number of active TTs on this root port */ /* If necessary, update the number of active TTs on this root port */
xhci_update_tt_active_eps(xhci, virt_dev, old_active_eps); xhci_update_tt_active_eps(xhci, virt_dev, old_active_eps);
virt_dev->flags = 0;
ret = 0; ret = 0;
command_cleanup: command_cleanup:
......
...@@ -1010,6 +1010,15 @@ struct xhci_virt_device { ...@@ -1010,6 +1010,15 @@ struct xhci_virt_device {
u8 real_port; u8 real_port;
struct xhci_interval_bw_table *bw_table; struct xhci_interval_bw_table *bw_table;
struct xhci_tt_bw_info *tt_info; struct xhci_tt_bw_info *tt_info;
/*
* flags for state tracking based on events and issued commands.
* Software can not rely on states from output contexts because of
* latency between events and xHC updating output context values.
* See xhci 1.1 section 4.8.3 for more details
*/
unsigned long flags;
#define VDEV_PORT_ERROR BIT(0) /* Port error, link inactive */
/* The current max exit latency for the enabled USB3 link states. */ /* The current max exit latency for the enabled USB3 link states. */
u16 current_mel; u16 current_mel;
/* Used for the debugfs interfaces. */ /* Used for the debugfs interfaces. */
......
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