mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-11-25 12:50:51 +07:00
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 -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.11 (GNU/Linux) iQIcBAABAgAGBQJQ5gLZAAoJEBMGWMLi1Gc57FUP/3PqGOZeHY9O//8tZrJGn4aL N1pLAO6Nz/sVcIT69G1DAFyOthD3iJJHR6yd3vlVwGYVD85M+P4DKM/xMTSeTpR0 75RxTbcphCMKzmLvYjeWkoG2SrUNEIuTRNSRF+GaB2gp6wviNSEBJGHlxdmbXfub SWulb6qlr5KaSz1ecVGsGVRc0PX3m5EMq+Phi8arUyA+a5X6Q2ekcCj2IuZSCk6F aHJo0rcWd6Er3OpjGXM8svdQYeuBsisxQJu4/M40sxFrUJhnMujC02r0EYgSrg15 Xjof82d22v1eFUfOAvMfSuPokMuTMakRVzLF9tCciFafYbMtYEoFUkyWHJH2tGnP Tn++43ps66L/vwVFy7MwRqAgekgQiCmirPaL/mFjVCrSJa3rzq4lU7Nh2+21Ri7x QyeVp5g0O+HufiS0koB1LARsi3M1LOG1AwFAnnf6i2L1S/i5L/cF7qr99QWELnlz cvhmk5w/2bsovJfiR54F4Bhod77P6U1wwjFmsYunsuPsZBRGAmPw11y24humSwll C2xP3tM5D8tEwvZ7rW7fwLCniJPx1aCV94Tz/vu/tuSQ+gzLEqzMyKqIwdnJMthY NX20cduuBMpS+F3kQ4+8yYquK0O1Vq39szPgcm2JB/ijjew10peGXw3Kwg2yBSyQ JuUvdVXjwUze3cDT7OaL =9YSo -----END PGP SIGNATURE----- Merge tag 'for-usb-linus-2013-01-03' of git://git.kernel.org/pub/scm/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
This commit is contained in:
commit
962426e0e2
@ -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;
|
||||
@ -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);
|
||||
@ -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);
|
||||
@ -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.
|
||||
@ -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))
|
||||
@ -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;
|
||||
@ -2565,14 +2629,11 @@ 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:
|
||||
@ -2580,16 +2641,16 @@ static void hub_port_finish_reset(struct usb_hub *hub, int port1,
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
@ -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)];
|
||||
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
@ -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 */
|
||||
|
Loading…
Reference in New Issue
Block a user