Support for USB 3.0 hub suspend.
This patchset adds support for suspending external USB 3.0 hubs, and fixes the
USB 3.0 device remote wakeup enabling. Hubs are the only USB 3.0 devices on
the market right now that do remote wakeup, and they will only send a remote
wakeup if they are placed into suspend, so it's not necessary to backport this
patchset to stable kernels.
resumed */
unsigned long removed_bits[1]; /* ports with a "removed"
device present */
+ unsigned long wakeup_bits[1]; /* ports that have signaled
+ remote wakeup */
#if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */
#error event_bits[] is too short!
#endif
kick_khubd(hub);
}
+/*
+ * Let the USB core know that a USB 3.0 device has sent a Function Wake Device
+ * Notification, which indicates it had initiated remote wakeup.
+ *
+ * USB 3.0 hubs do not report the port link state change from U3 to U0 when the
+ * device initiates resume, so the USB core will not receive notice of the
+ * resume through the normal hub interrupt URB.
+ */
+void usb_wakeup_notification(struct usb_device *hdev,
+ unsigned int portnum)
+{
+ struct usb_hub *hub;
+
+ if (!hdev)
+ return;
+
+ hub = hdev_to_hub(hdev);
+ if (hub) {
+ set_bit(portnum, hub->wakeup_bits);
+ kick_khubd(hub);
+ }
+}
+EXPORT_SYMBOL_GPL(usb_wakeup_notification);
/* completion function, fires on port status changes and various faults */
static void hub_irq(struct urb *urb)
clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_ENABLE);
}
- if (portchange & USB_PORT_STAT_C_LINK_STATE) {
- need_debounce_delay = true;
- clear_port_feature(hub->hdev, port1,
- USB_PORT_FEAT_C_PORT_LINK_STATE);
- }
-
if ((portchange & USB_PORT_STAT_C_BH_RESET) &&
hub_is_superspeed(hub->hdev)) {
need_debounce_delay = true;
set_bit(port1, hub->change_bits);
} else if (portstatus & USB_PORT_STAT_ENABLE) {
+ bool port_resumed = (portstatus &
+ USB_PORT_STAT_LINK_STATE) ==
+ USB_SS_PORT_LS_U0;
/* The power session apparently survived the resume.
* If there was an overcurrent or suspend change
* (i.e., remote wakeup request), have khubd
- * take care of it.
+ * take care of it. Look at the port link state
+ * for USB 3.0 hubs, since they don't have a suspend
+ * change bit, and they don't set the port link change
+ * bit on device-initiated resume.
*/
- if (portchange)
+ if (portchange || (hub_is_superspeed(hub->hdev) &&
+ port_resumed))
set_bit(port1, hub->change_bits);
} else if (udev->persist_enabled) {
desc = intf->cur_altsetting;
hdev = interface_to_usbdev(intf);
- /* Hubs have proper suspend/resume support. USB 3.0 device suspend is
- * different from USB 2.0/1.1 device suspend, and unfortunately we
- * don't support it yet. So leave autosuspend disabled for USB 3.0
- * external hubs for now. Enable autosuspend for USB 3.0 roothubs,
- * since that isn't a "real" hub.
- */
- if (!hub_is_superspeed(hdev) || !hdev->parent)
- usb_enable_autosuspend(hdev);
+ /* Hubs have proper suspend/resume support. */
+ usb_enable_autosuspend(hdev);
if (hdev->level == MAX_TOPO_LEVEL) {
dev_err(&intf->dev,
* we don't explicitly enable it here.
*/
if (udev->do_remote_wakeup) {
- status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
- USB_REQ_SET_FEATURE, USB_RECIP_DEVICE,
- USB_DEVICE_REMOTE_WAKEUP, 0,
- NULL, 0,
- USB_CTRL_SET_TIMEOUT);
+ if (!hub_is_superspeed(hub->hdev)) {
+ status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ USB_REQ_SET_FEATURE, USB_RECIP_DEVICE,
+ USB_DEVICE_REMOTE_WAKEUP, 0,
+ NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+ } else {
+ /* Assume there's only one function on the USB 3.0
+ * device and enable remote wake for the first
+ * interface. FIXME if the interface association
+ * descriptor shows there's more than one function.
+ */
+ status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ USB_REQ_SET_FEATURE,
+ USB_RECIP_INTERFACE,
+ USB_INTRF_FUNC_SUSPEND,
+ USB_INTRF_FUNC_SUSPEND_RW |
+ USB_INTRF_FUNC_SUSPEND_LP,
+ NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+ }
if (status) {
dev_dbg(&udev->dev, "won't remote wakeup, status %d\n",
status);
struct usb_hub *hub = usb_get_intfdata (intf);
struct usb_device *hdev = hub->hdev;
unsigned port1;
+ int status;
/* Warn if children aren't already suspended */
for (port1 = 1; port1 <= hdev->maxchild; port1++) {
return -EBUSY;
}
}
+ if (hub_is_superspeed(hdev) && hdev->do_remote_wakeup) {
+ /* Enable hub to send remote wakeup for all ports. */
+ for (port1 = 1; port1 <= hdev->maxchild; port1++) {
+ status = set_port_feature(hdev,
+ port1 |
+ USB_PORT_FEAT_REMOTE_WAKE_CONNECT |
+ USB_PORT_FEAT_REMOTE_WAKE_DISCONNECT |
+ USB_PORT_FEAT_REMOTE_WAKE_OVER_CURRENT,
+ USB_PORT_FEAT_REMOTE_WAKE_MASK);
+ }
+ }
dev_dbg(&intf->dev, "%s\n", __func__);
hcd->driver->relinquish_port(hcd, port1);
}
+/* Returns 1 if there was a remote wakeup and a connect status change. */
+static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port,
+ u16 portstatus, u16 portchange)
+{
+ struct usb_device *hdev;
+ struct usb_device *udev;
+ int connect_change = 0;
+ int ret;
+
+ hdev = hub->hdev;
+ udev = hdev->children[port-1];
+ if (!hub_is_superspeed(hdev)) {
+ if (!(portchange & USB_PORT_STAT_C_SUSPEND))
+ return 0;
+ clear_port_feature(hdev, port, USB_PORT_FEAT_C_SUSPEND);
+ } else {
+ if (!udev || udev->state != USB_STATE_SUSPENDED ||
+ (portstatus & USB_PORT_STAT_LINK_STATE) !=
+ USB_SS_PORT_LS_U0)
+ return 0;
+ }
+
+ if (udev) {
+ /* TRSMRCY = 10 msec */
+ msleep(10);
+
+ usb_lock_device(udev);
+ ret = usb_remote_wakeup(udev);
+ usb_unlock_device(udev);
+ if (ret < 0)
+ connect_change = 1;
+ } else {
+ ret = -ENODEV;
+ hub_port_disable(hub, port, 1);
+ }
+ dev_dbg(hub->intfdev, "resume on port %d, status %d\n",
+ port, ret);
+ return connect_change;
+}
+
static void hub_events(void)
{
struct list_head *tmp;
u16 portstatus;
u16 portchange;
int i, ret;
- int connect_change;
+ int connect_change, wakeup_change;
/*
* We restart the list every time to avoid a deadlock with
if (test_bit(i, hub->busy_bits))
continue;
connect_change = test_bit(i, hub->change_bits);
+ wakeup_change = test_and_clear_bit(i, hub->wakeup_bits);
if (!test_and_clear_bit(i, hub->event_bits) &&
- !connect_change)
+ !connect_change && !wakeup_change)
continue;
ret = hub_port_status(hub, i,
}
}
- if (portchange & USB_PORT_STAT_C_SUSPEND) {
- struct usb_device *udev;
+ if (hub_handle_remote_wakeup(hub, i,
+ portstatus, portchange))
+ connect_change = 1;
- clear_port_feature(hdev, i,
- USB_PORT_FEAT_C_SUSPEND);
- udev = hdev->children[i-1];
- if (udev) {
- /* TRSMRCY = 10 msec */
- msleep(10);
-
- usb_lock_device(udev);
- ret = usb_remote_wakeup(hdev->
- children[i-1]);
- usb_unlock_device(udev);
- if (ret < 0)
- connect_change = 1;
- } else {
- ret = -ENODEV;
- hub_port_disable(hub, i, 1);
- }
- dev_dbg (hub_dev,
- "resume on port %d, status %d\n",
- i, ret);
- }
-
if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
u16 status = 0;
u16 unused;
xhci_writel(xhci, temp, port_array[port_id]);
}
+void xhci_set_remote_wake_mask(struct xhci_hcd *xhci,
+ __le32 __iomem **port_array, int port_id, u16 wake_mask)
+{
+ u32 temp;
+
+ temp = xhci_readl(xhci, port_array[port_id]);
+ temp = xhci_port_state_to_neutral(temp);
+
+ if (wake_mask & USB_PORT_FEAT_REMOTE_WAKE_CONNECT)
+ temp |= PORT_WKCONN_E;
+ else
+ temp &= ~PORT_WKCONN_E;
+
+ if (wake_mask & USB_PORT_FEAT_REMOTE_WAKE_DISCONNECT)
+ temp |= PORT_WKDISC_E;
+ else
+ temp &= ~PORT_WKDISC_E;
+
+ if (wake_mask & USB_PORT_FEAT_REMOTE_WAKE_OVER_CURRENT)
+ temp |= PORT_WKOC_E;
+ else
+ temp &= ~PORT_WKOC_E;
+
+ xhci_writel(xhci, temp, port_array[port_id]);
+}
+
/* Test and clear port RWC bit */
void xhci_test_and_clear_bit(struct xhci_hcd *xhci, __le32 __iomem **port_array,
int port_id, u32 port_bit)
int slot_id;
struct xhci_bus_state *bus_state;
u16 link_state = 0;
+ u16 wake_mask = 0;
max_ports = xhci_get_ports(hcd, &port_array);
bus_state = &xhci->bus_state[hcd_index(hcd)];
case SetPortFeature:
if (wValue == USB_PORT_FEAT_LINK_STATE)
link_state = (wIndex & 0xff00) >> 3;
+ if (wValue == USB_PORT_FEAT_REMOTE_WAKE_MASK)
+ wake_mask = wIndex & 0xff00;
wIndex &= 0xff;
if (!wIndex || wIndex > max_ports)
goto error;
temp = xhci_readl(xhci, port_array[wIndex]);
xhci_dbg(xhci, "set port reset, actual port %d status = 0x%x\n", wIndex, temp);
break;
+ case USB_PORT_FEAT_REMOTE_WAKE_MASK:
+ xhci_set_remote_wake_mask(xhci, port_array,
+ wIndex, wake_mask);
+ temp = xhci_readl(xhci, port_array[wIndex]);
+ xhci_dbg(xhci, "set port remote wake mask, "
+ "actual port %d status = 0x%x\n",
+ wIndex, temp);
+ break;
case USB_PORT_FEAT_BH_PORT_RESET:
temp |= PORT_WR;
xhci_writel(xhci, temp, port_array[wIndex]);
t2 |= PORT_LINK_STROBE | XDEV_U3;
set_bit(port_index, &bus_state->bus_suspended);
}
+ /* USB core sets remote wake mask for USB 3.0 hubs,
+ * including the USB 3.0 roothub, but only if CONFIG_USB_SUSPEND
+ * is enabled, so also enable remote wake here.
+ */
if (hcd->self.root_hub->do_remote_wakeup) {
if (t1 & PORT_CONNECT) {
t2 |= PORT_WKOC_E | PORT_WKDISC_E;
unsigned int val, val2;
u64 val_64;
struct xhci_segment *seg;
- u32 page_size;
+ u32 page_size, temp;
int i;
page_size = xhci_readl(xhci, &xhci->op_regs->page_size);
INIT_LIST_HEAD(&xhci->lpm_failed_devs);
+ /* Enable USB 3.0 device notifications for function remote wake, which
+ * is necessary for allowing USB 3.0 devices to do remote wakeup from
+ * U3 (device suspend).
+ */
+ temp = xhci_readl(xhci, &xhci->op_regs->dev_notification);
+ temp &= ~DEV_NOTE_MASK;
+ temp |= DEV_NOTE_FWAKE;
+ xhci_writel(xhci, temp, &xhci->op_regs->dev_notification);
+
return 0;
fail:
return num_similar_speed_ports;
}
+static void handle_device_notification(struct xhci_hcd *xhci,
+ union xhci_trb *event)
+{
+ u32 slot_id;
+ struct usb_device *udev;
+
+ slot_id = TRB_TO_SLOT_ID(event->generic.field[3]);
+ if (!xhci->devs[slot_id]) {
+ xhci_warn(xhci, "Device Notification event for "
+ "unused slot %u\n", slot_id);
+ return;
+ }
+
+ xhci_dbg(xhci, "Device Wake Notification event for slot ID %u\n",
+ slot_id);
+ udev = xhci->devs[slot_id]->udev;
+ if (udev && udev->parent)
+ usb_wakeup_notification(udev->parent, udev->portnum);
+}
+
static void handle_port_status(struct xhci_hcd *xhci,
union xhci_trb *event)
{
}
if (DEV_SUPERSPEED(temp)) {
- xhci_dbg(xhci, "resume SS port %d\n", port_id);
+ xhci_dbg(xhci, "remote wake SS port %d\n", port_id);
+ /* Set a flag to say the port signaled remote wakeup,
+ * so we can tell the difference between the end of
+ * device and host initiated resume.
+ */
+ bus_state->port_remote_wakeup |= 1 << faked_port_index;
+ xhci_test_and_clear_bit(xhci, port_array,
+ faked_port_index, PORT_PLC);
xhci_set_link_state(xhci, port_array, faked_port_index,
XDEV_U0);
- slot_id = xhci_find_slot_id_by_port(hcd, xhci,
- faked_port_index + 1);
- if (!slot_id) {
- xhci_dbg(xhci, "slot_id is zero\n");
- goto cleanup;
- }
- xhci_ring_device(xhci, slot_id);
- xhci_dbg(xhci, "resume SS port %d finished\n", port_id);
- /* Clear PORT_PLC */
- xhci_test_and_clear_bit(xhci, port_array,
- faked_port_index, PORT_PLC);
+ /* Need to wait until the next link state change
+ * indicates the device is actually in U0.
+ */
+ bogus_port_status = true;
+ goto cleanup;
} else {
xhci_dbg(xhci, "resume HS port %d\n", port_id);
bus_state->resume_done[faked_port_index] = jiffies +
}
}
+ if ((temp & PORT_PLC) && (temp & PORT_PLS_MASK) == XDEV_U0 &&
+ DEV_SUPERSPEED(temp)) {
+ xhci_dbg(xhci, "resume SS port %d finished\n", port_id);
+ /* We've just brought the device into U0 through either the
+ * Resume state after a device remote wakeup, or through the
+ * U3Exit state after a host-initiated resume. If it's a device
+ * initiated remote wake, don't pass up the link state change,
+ * so the roothub behavior is consistent with external
+ * USB 3.0 hub behavior.
+ */
+ slot_id = xhci_find_slot_id_by_port(hcd, xhci,
+ faked_port_index + 1);
+ if (slot_id && xhci->devs[slot_id])
+ xhci_ring_device(xhci, slot_id);
+ if (bus_state->port_remote_wakeup && (1 << faked_port_index)) {
+ bus_state->port_remote_wakeup &=
+ ~(1 << faked_port_index);
+ xhci_test_and_clear_bit(xhci, port_array,
+ faked_port_index, PORT_PLC);
+ usb_wakeup_notification(hcd->self.root_hub,
+ faked_port_index + 1);
+ bogus_port_status = true;
+ goto cleanup;
+ }
+ }
+
if (hcd->speed != HCD_USB3)
xhci_test_and_clear_bit(xhci, port_array, faked_port_index,
PORT_PLC);
else
update_ptrs = 0;
break;
+ case TRB_TYPE(TRB_DEV_NOTE):
+ handle_device_notification(xhci, event);
+ break;
default:
if ((le32_to_cpu(event->event_cmd.flags) & TRB_TYPE_BITMASK) >=
TRB_TYPE(48))
/* ports suspend status arrays - max 31 ports for USB2, 15 for USB3 */
u32 port_c_suspend;
u32 suspended_ports;
+ u32 port_remote_wakeup;
unsigned long resume_done[USB_MAXCHILDREN];
};
#define USB_PORT_FEAT_C_BH_PORT_RESET 29
#define USB_PORT_FEAT_FORCE_LINKPM_ACCEPT 30
+/* USB 3.0 hub remote wake mask bits, see table 10-14 */
+#define USB_PORT_FEAT_REMOTE_WAKE_CONNECT (1 << 8)
+#define USB_PORT_FEAT_REMOTE_WAKE_DISCONNECT (1 << 9)
+#define USB_PORT_FEAT_REMOTE_WAKE_OVER_CURRENT (1 << 10)
+
/*
* Hub Status and Hub Change results
* See USB 2.0 spec Table 11-19 and Table 11-20
extern void usb_hc_died(struct usb_hcd *hcd);
extern void usb_hcd_poll_rh_status(struct usb_hcd *hcd);
+extern void usb_wakeup_notification(struct usb_device *hdev,
+ unsigned int portnum);
/* The D0/D1 toggle bits ... USE WITH CAUTION (they're almost hcd-internal) */
#define usb_gettoggle(dev, ep, out) (((dev)->toggle[out] >> (ep)) & 1)