Skip to content

Commit

Permalink
Merge tag 'for-usb-linus-2013-01-03' of git://git.kernel.org/pub/scm/…
Browse files Browse the repository at this point in the history
…linux/kernel/git/sarah/xhci into usb-linus

Sarah says:
	usb-linus: USB core fixes for warm reset

	Hi Greg,

	Happy New Year!  Here's some bug fixes for 3.8.  I have usb-next
	patches that are based on this set, so please merge your usb-linus
	branch into usb-next after this set is applied.

	The bulk of the patchset (patches 2-7) improve the USB core's warm
	reset error handling.

	There's also one patch that fixes an arithmetic error in the xHCI
	driver, and another to avoid the "dead ports" issue caused by
	unhandled port status change events.

	These are all marked for stable.

	Sarah Sharp
  • Loading branch information
Greg Kroah-Hartman committed Jan 7, 2013
2 parents e460239 + c52804a commit 962426e
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 24 deletions.
110 changes: 88 additions & 22 deletions drivers/usb/core/hub.c
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,60 @@ static int hub_hub_status(struct usb_hub *hub,
return ret;
}

static int hub_set_port_link_state(struct usb_hub *hub, int port1,
unsigned int link_status)
{
return set_port_feature(hub->hdev,
port1 | (link_status << 3),
USB_PORT_FEAT_LINK_STATE);
}

/*
* If USB 3.0 ports are placed into the Disabled state, they will no longer
* detect any device connects or disconnects. This is generally not what the
* USB core wants, since it expects a disabled port to produce a port status
* change event when a new device connects.
*
* Instead, set the link state to Disabled, wait for the link to settle into
* that state, clear any change bits, and then put the port into the RxDetect
* state.
*/
static int hub_usb3_port_disable(struct usb_hub *hub, int port1)
{
int ret;
int total_time;
u16 portchange, portstatus;

if (!hub_is_superspeed(hub->hdev))
return -EINVAL;

ret = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_SS_DISABLED);
if (ret) {
dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n",
port1, ret);
return ret;
}

/* Wait for the link to enter the disabled state. */
for (total_time = 0; ; total_time += HUB_DEBOUNCE_STEP) {
ret = hub_port_status(hub, port1, &portstatus, &portchange);
if (ret < 0)
return ret;

if ((portstatus & USB_PORT_STAT_LINK_STATE) ==
USB_SS_PORT_LS_SS_DISABLED)
break;
if (total_time >= HUB_DEBOUNCE_TIMEOUT)
break;
msleep(HUB_DEBOUNCE_STEP);
}
if (total_time >= HUB_DEBOUNCE_TIMEOUT)
dev_warn(hub->intfdev, "Could not disable port %d after %d ms\n",
port1, total_time);

return hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_RX_DETECT);
}

static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)
{
struct usb_device *hdev = hub->hdev;
Expand All @@ -885,8 +939,13 @@ static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)
if (hub->ports[port1 - 1]->child && set_state)
usb_set_device_state(hub->ports[port1 - 1]->child,
USB_STATE_NOTATTACHED);
if (!hub->error && !hub_is_superspeed(hub->hdev))
ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE);
if (!hub->error) {
if (hub_is_superspeed(hub->hdev))
ret = hub_usb3_port_disable(hub, port1);
else
ret = clear_port_feature(hdev, port1,
USB_PORT_FEAT_ENABLE);
}
if (ret)
dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n",
port1, ret);
Expand Down Expand Up @@ -2440,7 +2499,7 @@ static unsigned hub_is_wusb(struct usb_hub *hub)
#define HUB_SHORT_RESET_TIME 10
#define HUB_BH_RESET_TIME 50
#define HUB_LONG_RESET_TIME 200
#define HUB_RESET_TIMEOUT 500
#define HUB_RESET_TIMEOUT 800

static int hub_port_reset(struct usb_hub *hub, int port1,
struct usb_device *udev, unsigned int delay, bool warm);
Expand Down Expand Up @@ -2475,6 +2534,10 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
if (ret < 0)
return ret;

/* The port state is unknown until the reset completes. */
if ((portstatus & USB_PORT_STAT_RESET))
goto delay;

/*
* Some buggy devices require a warm reset to be issued even
* when the port appears not to be connected.
Expand Down Expand Up @@ -2520,11 +2583,7 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
if ((portchange & USB_PORT_STAT_C_CONNECTION))
return -ENOTCONN;

/* if we`ve finished resetting, then break out of
* the loop
*/
if (!(portstatus & USB_PORT_STAT_RESET) &&
(portstatus & USB_PORT_STAT_ENABLE)) {
if ((portstatus & USB_PORT_STAT_ENABLE)) {
if (hub_is_wusb(hub))
udev->speed = USB_SPEED_WIRELESS;
else if (hub_is_superspeed(hub->hdev))
Expand All @@ -2538,10 +2597,15 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
return 0;
}
} else {
if (portchange & USB_PORT_STAT_C_BH_RESET)
return 0;
if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
hub_port_warm_reset_required(hub,
portstatus))
return -ENOTCONN;

return 0;
}

delay:
/* switch to the long delay after two short delay failures */
if (delay_time >= 2 * HUB_SHORT_RESET_TIME)
delay = HUB_LONG_RESET_TIME;
Expand All @@ -2565,31 +2629,28 @@ static void hub_port_finish_reset(struct usb_hub *hub, int port1,
msleep(10 + 40);
update_devnum(udev, 0);
hcd = bus_to_hcd(udev->bus);
if (hcd->driver->reset_device) {
*status = hcd->driver->reset_device(hcd, udev);
if (*status < 0) {
dev_err(&udev->dev, "Cannot reset "
"HCD device state\n");
break;
}
}
/* The xHC may think the device is already reset,
* so ignore the status.
*/
if (hcd->driver->reset_device)
hcd->driver->reset_device(hcd, udev);
}
/* FALL THROUGH */
case -ENOTCONN:
case -ENODEV:
clear_port_feature(hub->hdev,
port1, USB_PORT_FEAT_C_RESET);
/* FIXME need disconnect() for NOTATTACHED device */
if (warm) {
if (hub_is_superspeed(hub->hdev)) {
clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_BH_PORT_RESET);
clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_PORT_LINK_STATE);
} else {
}
if (!warm)
usb_set_device_state(udev, *status
? USB_STATE_NOTATTACHED
: USB_STATE_DEFAULT);
}
break;
}
}
Expand Down Expand Up @@ -4638,9 +4699,14 @@ static void hub_events(void)
* SS.Inactive state.
*/
if (hub_port_warm_reset_required(hub, portstatus)) {
int status;

dev_dbg(hub_dev, "warm reset port %d\n", i);
hub_port_reset(hub, i, NULL,
status = hub_port_reset(hub, i, NULL,
HUB_BH_RESET_TIME, true);
if (status < 0)
hub_port_disable(hub, i, 1);
connect_change = 0;
}

if (connect_change)
Expand Down
38 changes: 36 additions & 2 deletions drivers/usb/host/xhci-hub.c
Original file line number Diff line number Diff line change
Expand Up @@ -761,12 +761,39 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
break;
case USB_PORT_FEAT_LINK_STATE:
temp = xhci_readl(xhci, port_array[wIndex]);

/* Disable port */
if (link_state == USB_SS_PORT_LS_SS_DISABLED) {
xhci_dbg(xhci, "Disable port %d\n", wIndex);
temp = xhci_port_state_to_neutral(temp);
/*
* Clear all change bits, so that we get a new
* connection event.
*/
temp |= PORT_CSC | PORT_PEC | PORT_WRC |
PORT_OCC | PORT_RC | PORT_PLC |
PORT_CEC;
xhci_writel(xhci, temp | PORT_PE,
port_array[wIndex]);
temp = xhci_readl(xhci, port_array[wIndex]);
break;
}

/* Put link in RxDetect (enable port) */
if (link_state == USB_SS_PORT_LS_RX_DETECT) {
xhci_dbg(xhci, "Enable port %d\n", wIndex);
xhci_set_link_state(xhci, port_array, wIndex,
link_state);
temp = xhci_readl(xhci, port_array[wIndex]);
break;
}

/* Software should not attempt to set
* port link state above '5' (Rx.Detect) and the port
* port link state above '3' (U3) and the port
* must be enabled.
*/
if ((temp & PORT_PE) == 0 ||
(link_state > USB_SS_PORT_LS_RX_DETECT)) {
(link_state > USB_SS_PORT_LS_U3)) {
xhci_warn(xhci, "Cannot set link state.\n");
goto error;
}
Expand Down Expand Up @@ -957,6 +984,7 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf)
int max_ports;
__le32 __iomem **port_array;
struct xhci_bus_state *bus_state;
bool reset_change = false;

max_ports = xhci_get_ports(hcd, &port_array);
bus_state = &xhci->bus_state[hcd_index(hcd)];
Expand Down Expand Up @@ -988,6 +1016,12 @@ int xhci_hub_status_data(struct usb_hcd *hcd, char *buf)
buf[(i + 1) / 8] |= 1 << (i + 1) % 8;
status = 1;
}
if ((temp & PORT_RC))
reset_change = true;
}
if (!status && !reset_change) {
xhci_dbg(xhci, "%s: stopping port polling.\n", __func__);
clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
}
spin_unlock_irqrestore(&xhci->lock, flags);
return status ? retval : 0;
Expand Down
2 changes: 2 additions & 0 deletions drivers/usb/host/xhci-mem.c
Original file line number Diff line number Diff line change
Expand Up @@ -1250,6 +1250,8 @@ static unsigned int xhci_microframes_to_exponent(struct usb_device *udev,
static unsigned int xhci_parse_microframe_interval(struct usb_device *udev,
struct usb_host_endpoint *ep)
{
if (ep->desc.bInterval == 0)
return 0;
return xhci_microframes_to_exponent(udev, ep,
ep->desc.bInterval, 0, 15);
}
Expand Down
9 changes: 9 additions & 0 deletions drivers/usb/host/xhci-ring.c
Original file line number Diff line number Diff line change
Expand Up @@ -1725,6 +1725,15 @@ static void handle_port_status(struct xhci_hcd *xhci,
if (bogus_port_status)
return;

/*
* xHCI port-status-change events occur when the "or" of all the
* status-change bits in the portsc register changes from 0 to 1.
* New status changes won't cause an event if any other change
* bits are still set. When an event occurs, switch over to
* polling to avoid losing status changes.
*/
xhci_dbg(xhci, "%s: starting port polling.\n", __func__);
set_bit(HCD_FLAG_POLL_RH, &hcd->flags);
spin_unlock(&xhci->lock);
/* Pass this up to the core */
usb_hcd_poll_rh_status(hcd);
Expand Down
10 changes: 10 additions & 0 deletions drivers/usb/host/xhci.c
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,11 @@ int xhci_suspend(struct xhci_hcd *xhci)
xhci->shared_hcd->state != HC_STATE_SUSPENDED)
return -EINVAL;

/* Don't poll the roothubs on bus suspend. */
xhci_dbg(xhci, "%s: stopping port polling.\n", __func__);
clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
del_timer_sync(&hcd->rh_timer);

spin_lock_irq(&xhci->lock);
clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
clear_bit(HCD_FLAG_HW_ACCESSIBLE, &xhci->shared_hcd->flags);
Expand Down Expand Up @@ -1069,6 +1074,11 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated)
if (xhci->quirks & XHCI_COMP_MODE_QUIRK)
compliance_mode_recovery_timer_init(xhci);

/* Re-enable port polling. */
xhci_dbg(xhci, "%s: starting port polling.\n", __func__);
set_bit(HCD_FLAG_POLL_RH, &hcd->flags);
usb_hcd_poll_rh_status(hcd);

return retval;
}
#endif /* CONFIG_PM */
Expand Down

0 comments on commit 962426e

Please sign in to comment.