Commit 8bea2bd3 authored by Stanislaw Ledwon's avatar Stanislaw Ledwon Committed by Sarah Sharp

usb: Add support for root hub port status CAS

The host controller port status register supports CAS (Cold Attach
Status) bit. This bit could be set when USB3.0 device is connected
when system is in Sx state. When the system wakes to S0 this port
status with CAS bit is reported and this port can't be used by any
device.

When CAS bit is set the port should be reset by warm reset. This
was not supported by xhci driver.

The issue was found when pendrive was connected to suspended
platform. The link state of "Compliance Mode" was reported together
with CAS bit. This link state was also not supported by xhci and
core/hub.c.

The CAS bit is defined only for xhci root hub port and it is
not supported on regular hubs. The link status is used to force
warm reset on port. Make the USB core issue a warm reset when port
is in ether the 'inactive' or 'compliance mode'. Change the xHCI driver
to report 'compliance mode' when the CAS is set. This force warm reset
on the root hub port.

This patch should be backported to stable kernels as old as 3.2, that
contain the commit 10d674a8 "USB: When
hot reset for USB3 fails, try warm reset."
Signed-off-by: default avatarStanislaw Ledwon <staszek.ledwon@linux.intel.com>
Signed-off-by: default avatarSarah Sharp <sarah.a.sharp@linux.intel.com>
Acked-by: default avatarAndiry Xu <andiry.xu@amd.com>
Cc: stable@vger.kernel.org
parent 6887a413
...@@ -2324,12 +2324,16 @@ static unsigned hub_is_wusb(struct usb_hub *hub) ...@@ -2324,12 +2324,16 @@ static unsigned hub_is_wusb(struct usb_hub *hub)
static int hub_port_reset(struct usb_hub *hub, int port1, static int hub_port_reset(struct usb_hub *hub, int port1,
struct usb_device *udev, unsigned int delay, bool warm); struct usb_device *udev, unsigned int delay, bool warm);
/* Is a USB 3.0 port in the Inactive state? */ /* Is a USB 3.0 port in the Inactive or Complinance Mode state?
static bool hub_port_inactive(struct usb_hub *hub, u16 portstatus) * Port worm reset is required to recover
*/
static bool hub_port_warm_reset_required(struct usb_hub *hub, u16 portstatus)
{ {
return hub_is_superspeed(hub->hdev) && return hub_is_superspeed(hub->hdev) &&
(portstatus & USB_PORT_STAT_LINK_STATE) == (((portstatus & USB_PORT_STAT_LINK_STATE) ==
USB_SS_PORT_LS_SS_INACTIVE; USB_SS_PORT_LS_SS_INACTIVE) ||
((portstatus & USB_PORT_STAT_LINK_STATE) ==
USB_SS_PORT_LS_COMP_MOD)) ;
} }
static int hub_port_wait_reset(struct usb_hub *hub, int port1, static int hub_port_wait_reset(struct usb_hub *hub, int port1,
...@@ -2365,7 +2369,7 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1, ...@@ -2365,7 +2369,7 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
* *
* See https://bugzilla.kernel.org/show_bug.cgi?id=41752 * See https://bugzilla.kernel.org/show_bug.cgi?id=41752
*/ */
if (hub_port_inactive(hub, portstatus)) { if (hub_port_warm_reset_required(hub, portstatus)) {
int ret; int ret;
if ((portchange & USB_PORT_STAT_C_CONNECTION)) if ((portchange & USB_PORT_STAT_C_CONNECTION))
...@@ -4408,9 +4412,7 @@ static void hub_events(void) ...@@ -4408,9 +4412,7 @@ static void hub_events(void)
/* Warm reset a USB3 protocol port if it's in /* Warm reset a USB3 protocol port if it's in
* SS.Inactive state. * SS.Inactive state.
*/ */
if (hub_is_superspeed(hub->hdev) && if (hub_port_warm_reset_required(hub, portstatus)) {
(portstatus & USB_PORT_STAT_LINK_STATE)
== USB_SS_PORT_LS_SS_INACTIVE) {
dev_dbg(hub_dev, "warm reset port %d\n", i); dev_dbg(hub_dev, "warm reset port %d\n", i);
hub_port_reset(hub, i, NULL, hub_port_reset(hub, i, NULL,
HUB_BH_RESET_TIME, true); HUB_BH_RESET_TIME, true);
......
...@@ -462,6 +462,42 @@ void xhci_test_and_clear_bit(struct xhci_hcd *xhci, __le32 __iomem **port_array, ...@@ -462,6 +462,42 @@ void xhci_test_and_clear_bit(struct xhci_hcd *xhci, __le32 __iomem **port_array,
} }
} }
/* Updates Link Status for super Speed port */
static void xhci_hub_report_link_state(u32 *status, u32 status_reg)
{
u32 pls = status_reg & PORT_PLS_MASK;
/* resume state is a xHCI internal state.
* Do not report it to usb core.
*/
if (pls == XDEV_RESUME)
return;
/* When the CAS bit is set then warm reset
* should be performed on port
*/
if (status_reg & PORT_CAS) {
/* The CAS bit can be set while the port is
* in any link state.
* Only roothubs have CAS bit, so we
* pretend to be in compliance mode
* unless we're already in compliance
* or the inactive state.
*/
if (pls != USB_SS_PORT_LS_COMP_MOD &&
pls != USB_SS_PORT_LS_SS_INACTIVE) {
pls = USB_SS_PORT_LS_COMP_MOD;
}
/* Return also connection bit -
* hub state machine resets port
* when this bit is set.
*/
pls |= USB_PORT_STAT_CONNECTION;
}
/* update status field */
*status |= pls;
}
int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
u16 wIndex, char *buf, u16 wLength) u16 wIndex, char *buf, u16 wLength)
{ {
...@@ -606,13 +642,9 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, ...@@ -606,13 +642,9 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
else else
status |= USB_PORT_STAT_POWER; status |= USB_PORT_STAT_POWER;
} }
/* Port Link State */ /* Update Port Link State for super speed ports*/
if (hcd->speed == HCD_USB3) { if (hcd->speed == HCD_USB3) {
/* resume state is a xHCI internal state. xhci_hub_report_link_state(&status, temp);
* Do not report it to usb core.
*/
if ((temp & PORT_PLS_MASK) != XDEV_RESUME)
status |= (temp & PORT_PLS_MASK);
} }
if (bus_state->port_c_suspend & (1 << wIndex)) if (bus_state->port_c_suspend & (1 << wIndex))
status |= 1 << USB_PORT_FEAT_C_SUSPEND; status |= 1 << USB_PORT_FEAT_C_SUSPEND;
......
...@@ -341,7 +341,11 @@ struct xhci_op_regs { ...@@ -341,7 +341,11 @@ struct xhci_op_regs {
#define PORT_PLC (1 << 22) #define PORT_PLC (1 << 22)
/* port configure error change - port failed to configure its link partner */ /* port configure error change - port failed to configure its link partner */
#define PORT_CEC (1 << 23) #define PORT_CEC (1 << 23)
/* bit 24 reserved */ /* Cold Attach Status - xHC can set this bit to report device attached during
* Sx state. Warm port reset should be perfomed to clear this bit and move port
* to connected state.
*/
#define PORT_CAS (1 << 24)
/* wake on connect (enable) */ /* wake on connect (enable) */
#define PORT_WKCONN_E (1 << 25) #define PORT_WKCONN_E (1 << 25)
/* wake on disconnect (enable) */ /* wake on disconnect (enable) */
......
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