diff options
Diffstat (limited to 'drivers/usb/core/hub.c')
-rw-r--r-- | drivers/usb/core/hub.c | 396 |
1 files changed, 256 insertions, 140 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 57aeca160f38..eb57fcc701d7 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -30,12 +30,6 @@ #include "hcd.h" #include "hub.h" -#ifdef CONFIG_USB_PERSIST -#define USB_PERSIST 1 -#else -#define USB_PERSIST 0 -#endif - /* if we are in debug mode, always announce new devices */ #ifdef DEBUG #ifndef CONFIG_USB_ANNOUNCE_NEW_DEVICES @@ -333,6 +327,27 @@ static int get_port_status(struct usb_device *hdev, int port1, return status; } +static int hub_port_status(struct usb_hub *hub, int port1, + u16 *status, u16 *change) +{ + int ret; + + mutex_lock(&hub->status_mutex); + ret = get_port_status(hub->hdev, port1, &hub->status->port); + if (ret < 4) { + dev_err(hub->intfdev, + "%s failed (err = %d)\n", __func__, ret); + if (ret >= 0) + ret = -EIO; + } else { + *status = le16_to_cpu(hub->status->port.wPortStatus); + *change = le16_to_cpu(hub->status->port.wPortChange); + ret = 0; + } + mutex_unlock(&hub->status_mutex); + return ret; +} + static void kick_khubd(struct usb_hub *hub) { unsigned long flags; @@ -560,7 +575,7 @@ static int hub_hub_status(struct usb_hub *hub, ret = get_hub_status(hub->hdev, &hub->status->hub); if (ret < 0) dev_err (hub->intfdev, - "%s failed (err = %d)\n", __FUNCTION__, ret); + "%s failed (err = %d)\n", __func__, ret); else { *status = le16_to_cpu(hub->status->hub.wHubStatus); *change = le16_to_cpu(hub->status->hub.wHubChange); @@ -610,9 +625,8 @@ static void hub_port_logical_disconnect(struct usb_hub *hub, int port1) } /* caller has locked the hub device */ -static int hub_pre_reset(struct usb_interface *intf) +static void hub_stop(struct usb_hub *hub) { - struct usb_hub *hub = usb_get_intfdata(intf); struct usb_device *hdev = hub->hdev; int i; @@ -622,6 +636,89 @@ static int hub_pre_reset(struct usb_interface *intf) usb_disconnect(&hdev->children[i]); } hub_quiesce(hub); +} + +#define HUB_RESET 1 +#define HUB_RESUME 2 +#define HUB_RESET_RESUME 3 + +#ifdef CONFIG_PM + +static void hub_restart(struct usb_hub *hub, int type) +{ + struct usb_device *hdev = hub->hdev; + int port1; + + /* Check each of the children to see if they require + * USB-PERSIST handling or disconnection. Also check + * each unoccupied port to make sure it is still disabled. + */ + for (port1 = 1; port1 <= hdev->maxchild; ++port1) { + struct usb_device *udev = hdev->children[port1-1]; + int status = 0; + u16 portstatus, portchange; + + if (!udev || udev->state == USB_STATE_NOTATTACHED) { + if (type != HUB_RESET) { + status = hub_port_status(hub, port1, + &portstatus, &portchange); + if (status == 0 && (portstatus & + USB_PORT_STAT_ENABLE)) + clear_port_feature(hdev, port1, + USB_PORT_FEAT_ENABLE); + } + continue; + } + + /* Was the power session lost while we were suspended? */ + switch (type) { + case HUB_RESET_RESUME: + portstatus = 0; + portchange = USB_PORT_STAT_C_CONNECTION; + break; + + case HUB_RESET: + case HUB_RESUME: + status = hub_port_status(hub, port1, + &portstatus, &portchange); + break; + } + + /* For "USB_PERSIST"-enabled children we must + * mark the child device for reset-resume and + * turn off the various status changes to prevent + * khubd from disconnecting it later. + */ + if (udev->persist_enabled && status == 0 && + !(portstatus & USB_PORT_STAT_ENABLE)) { + if (portchange & USB_PORT_STAT_C_ENABLE) + clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_ENABLE); + if (portchange & USB_PORT_STAT_C_CONNECTION) + clear_port_feature(hub->hdev, port1, + USB_PORT_FEAT_C_CONNECTION); + udev->reset_resume = 1; + } + + /* Otherwise for a reset_resume we must disconnect the child, + * but as we may not lock the child device here + * we have to do a "logical" disconnect. + */ + else if (type == HUB_RESET_RESUME) + hub_port_logical_disconnect(hub, port1); + } + + hub_activate(hub); +} + +#endif /* CONFIG_PM */ + +/* caller has locked the hub device */ +static int hub_pre_reset(struct usb_interface *intf) +{ + struct usb_hub *hub = usb_get_intfdata(intf); + + hub_stop(hub); return 0; } @@ -910,7 +1007,7 @@ static void hub_disconnect(struct usb_interface *intf) /* Disconnect all children and quiesce the hub */ hub->error = 0; - hub_pre_reset(intf); + hub_stop(hub); usb_set_intfdata (intf, NULL); @@ -1098,21 +1195,42 @@ void usb_set_device_state(struct usb_device *udev, spin_unlock_irqrestore(&device_state_lock, flags); } +/* + * WUSB devices are simple: they have no hubs behind, so the mapping + * device <-> virtual port number becomes 1:1. Why? to simplify the + * life of the device connection logic in + * drivers/usb/wusbcore/devconnect.c. When we do the initial secret + * handshake we need to assign a temporary address in the unauthorized + * space. For simplicity we use the first virtual port number found to + * be free [drivers/usb/wusbcore/devconnect.c:wusbhc_devconnect_ack()] + * and that becomes it's address [X < 128] or its unauthorized address + * [X | 0x80]. + * + * We add 1 as an offset to the one-based USB-stack port number + * (zero-based wusb virtual port index) for two reasons: (a) dev addr + * 0 is reserved by USB for default address; (b) Linux's USB stack + * uses always #1 for the root hub of the controller. So USB stack's + * port #1, which is wusb virtual-port #0 has address #2. + */ static void choose_address(struct usb_device *udev) { int devnum; struct usb_bus *bus = udev->bus; /* If khubd ever becomes multithreaded, this will need a lock */ - - /* Try to allocate the next devnum beginning at bus->devnum_next. */ - devnum = find_next_zero_bit(bus->devmap.devicemap, 128, - bus->devnum_next); - if (devnum >= 128) - devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1); - - bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1); - + if (udev->wusb) { + devnum = udev->portnum + 1; + BUG_ON(test_bit(devnum, bus->devmap.devicemap)); + } else { + /* Try to allocate the next devnum beginning at + * bus->devnum_next. */ + devnum = find_next_zero_bit(bus->devmap.devicemap, 128, + bus->devnum_next); + if (devnum >= 128) + devnum = find_next_zero_bit(bus->devmap.devicemap, + 128, 1); + bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1); + } if (devnum < 128) { set_bit(devnum, bus->devmap.devicemap); udev->devnum = devnum; @@ -1127,6 +1245,13 @@ static void release_address(struct usb_device *udev) } } +static void update_address(struct usb_device *udev, int devnum) +{ + /* The address for a WUSB device is managed by wusbcore. */ + if (!udev->wusb) + udev->devnum = devnum; +} + #ifdef CONFIG_USB_SUSPEND static void usb_stop_pm(struct usb_device *udev) @@ -1173,7 +1298,7 @@ void usb_disconnect(struct usb_device **pdev) int i; if (!udev) { - pr_debug ("%s nodev\n", __FUNCTION__); + pr_debug ("%s nodev\n", __func__); return; } @@ -1510,28 +1635,6 @@ out_authorized: } -static int hub_port_status(struct usb_hub *hub, int port1, - u16 *status, u16 *change) -{ - int ret; - - mutex_lock(&hub->status_mutex); - ret = get_port_status(hub->hdev, port1, &hub->status->port); - if (ret < 4) { - dev_err (hub->intfdev, - "%s failed (err = %d)\n", __FUNCTION__, ret); - if (ret >= 0) - ret = -EIO; - } else { - *status = le16_to_cpu(hub->status->port.wPortStatus); - *change = le16_to_cpu(hub->status->port.wPortChange); - ret = 0; - } - mutex_unlock(&hub->status_mutex); - return ret; -} - - /* Returns 1 if @hub is a WUSB root hub, 0 otherwise */ static unsigned hub_is_wusb(struct usb_hub *hub) { @@ -1637,7 +1740,7 @@ static int hub_port_reset(struct usb_hub *hub, int port1, case 0: /* TRSTRCY = 10 ms; plus some extra */ msleep(10 + 40); - udev->devnum = 0; /* Device now at address 0 */ + update_address(udev, 0); /* FALL THROUGH */ case -ENOTCONN: case -ENODEV: @@ -1842,9 +1945,8 @@ static int finish_port_resume(struct usb_device *udev) * the host and the device is the same as it was when the device * suspended. * - * If CONFIG_USB_PERSIST and @udev->reset_resume are both set then this - * routine won't check that the port is still enabled. Furthermore, - * if @udev->reset_resume is set then finish_port_resume() above will + * If @udev->reset_resume is set then this routine won't check that the + * port is still enabled. Furthermore, finish_port_resume() above will * reset @udev. The end result is that a broken power session can be * recovered and @udev will appear to persist across a loss of VBUS power. * @@ -1856,8 +1958,8 @@ static int finish_port_resume(struct usb_device *udev) * to it will be lost. Using the USB_PERSIST facility, the device can be * made to appear as if it had not disconnected. * - * This facility is inherently dangerous. Although usb_reset_device() - * makes every effort to insure that the same device is present after the + * This facility can be dangerous. Although usb_reset_device() makes + * every effort to insure that the same device is present after the * reset as before, it cannot provide a 100% guarantee. Furthermore it's * quite possible for a device to remain unaltered but its media to be * changed. If the user replaces a flash memory card while the system is @@ -1902,7 +2004,7 @@ int usb_port_resume(struct usb_device *udev) status = hub_port_status(hub, port1, &portstatus, &portchange); SuspendCleared: - if (USB_PERSIST && udev->reset_resume) + if (udev->reset_resume) want_flags = USB_PORT_STAT_POWER | USB_PORT_STAT_CONNECTION; else @@ -1927,8 +2029,6 @@ int usb_port_resume(struct usb_device *udev) } clear_bit(port1, hub->busy_bits); - if (!hub->hdev->parent && !hub->busy_bits[0]) - usb_enable_root_hub_irq(hub->hdev->bus); if (status == 0) status = finish_port_resume(udev); @@ -2000,7 +2100,7 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg) } } - dev_dbg(&intf->dev, "%s\n", __FUNCTION__); + dev_dbg(&intf->dev, "%s\n", __func__); /* stop khubd and related activity */ hub_quiesce(hub); @@ -2009,49 +2109,20 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg) static int hub_resume(struct usb_interface *intf) { - struct usb_hub *hub = usb_get_intfdata (intf); - - dev_dbg(&intf->dev, "%s\n", __FUNCTION__); + struct usb_hub *hub = usb_get_intfdata(intf); - /* tell khubd to look for changes on this hub */ - hub_activate(hub); + dev_dbg(&intf->dev, "%s\n", __func__); + hub_restart(hub, HUB_RESUME); return 0; } static int hub_reset_resume(struct usb_interface *intf) { struct usb_hub *hub = usb_get_intfdata(intf); - struct usb_device *hdev = hub->hdev; - int port1; + dev_dbg(&intf->dev, "%s\n", __func__); hub_power_on(hub); - - for (port1 = 1; port1 <= hdev->maxchild; ++port1) { - struct usb_device *child = hdev->children[port1-1]; - - if (child) { - - /* For "USB_PERSIST"-enabled children we must - * mark the child device for reset-resume and - * turn off the connect-change status to prevent - * khubd from disconnecting it later. - */ - if (USB_PERSIST && child->persist_enabled) { - child->reset_resume = 1; - clear_port_feature(hdev, port1, - USB_PORT_FEAT_C_CONNECTION); - - /* Otherwise we must disconnect the child, - * but as we may not lock the child device here - * we have to do a "logical" disconnect. - */ - } else { - hub_port_logical_disconnect(hub, port1); - } - } - } - - hub_activate(hub); + hub_restart(hub, HUB_RESET_RESUME); return 0; } @@ -2061,10 +2132,10 @@ static int hub_reset_resume(struct usb_interface *intf) * * The USB host controller driver calls this function when its root hub * is resumed and Vbus power has been interrupted or the controller - * has been reset. The routine marks @rhdev as having lost power. When - * the hub driver is resumed it will take notice; if CONFIG_USB_PERSIST - * is enabled then it will carry out power-session recovery, otherwise - * it will disconnect all the child devices. + * has been reset. The routine marks @rhdev as having lost power. + * When the hub driver is resumed it will take notice and carry out + * power-session recovery for all the "USB-PERSIST"-enabled child devices; + * the others will be disconnected. */ void usb_root_hub_lost_power(struct usb_device *rhdev) { @@ -2147,12 +2218,13 @@ static int hub_port_debounce(struct usb_hub *hub, int port1) return portstatus; } -static void ep0_reinit(struct usb_device *udev) +void usb_ep0_reinit(struct usb_device *udev) { usb_disable_endpoint(udev, 0 + USB_DIR_IN); usb_disable_endpoint(udev, 0 + USB_DIR_OUT); usb_enable_endpoint(udev, &udev->ep0); } +EXPORT_SYMBOL_GPL(usb_ep0_reinit); #define usb_sndaddr0pipe() (PIPE_CONTROL << 30) #define usb_rcvaddr0pipe() ((PIPE_CONTROL << 30) | USB_DIR_IN) @@ -2171,9 +2243,10 @@ static int hub_set_address(struct usb_device *udev, int devnum) USB_REQ_SET_ADDRESS, 0, devnum, 0, NULL, 0, USB_CTRL_SET_TIMEOUT); if (retval == 0) { - udev->devnum = devnum; /* Device now using proper address */ + /* Device now using proper address. */ + update_address(udev, devnum); usb_set_device_state(udev, USB_STATE_ADDRESS); - ep0_reinit(udev); + usb_ep0_reinit(udev); } return retval; } @@ -2355,26 +2428,33 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, #undef GET_DESCRIPTOR_BUFSIZE } - for (j = 0; j < SET_ADDRESS_TRIES; ++j) { - retval = hub_set_address(udev, devnum); - if (retval >= 0) + /* + * If device is WUSB, we already assigned an + * unauthorized address in the Connect Ack sequence; + * authorization will assign the final address. + */ + if (udev->wusb == 0) { + for (j = 0; j < SET_ADDRESS_TRIES; ++j) { + retval = hub_set_address(udev, devnum); + if (retval >= 0) + break; + msleep(200); + } + if (retval < 0) { + dev_err(&udev->dev, + "device not accepting address %d, error %d\n", + devnum, retval); + goto fail; + } + + /* cope with hardware quirkiness: + * - let SET_ADDRESS settle, some device hardware wants it + * - read ep0 maxpacket even for high and low speed, + */ + msleep(10); + if (USE_NEW_SCHEME(retry_counter)) break; - msleep(200); - } - if (retval < 0) { - dev_err(&udev->dev, - "device not accepting address %d, error %d\n", - devnum, retval); - goto fail; - } - - /* cope with hardware quirkiness: - * - let SET_ADDRESS settle, some device hardware wants it - * - read ep0 maxpacket even for high and low speed, - */ - msleep(10); - if (USE_NEW_SCHEME(retry_counter)) - break; + } retval = usb_get_device_descriptor(udev, 8); if (retval < 8) { @@ -2391,7 +2471,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, if (retval) goto fail; - i = udev->descriptor.bMaxPacketSize0 == 0xff? + i = udev->descriptor.bMaxPacketSize0 == 0xff? /* wusb device? */ 512 : udev->descriptor.bMaxPacketSize0; if (le16_to_cpu(udev->ep0.desc.wMaxPacketSize) != i) { if (udev->speed != USB_SPEED_FULL || @@ -2402,7 +2482,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, } dev_dbg(&udev->dev, "ep0 maxpacket = %d\n", i); udev->ep0.desc.wMaxPacketSize = cpu_to_le16(i); - ep0_reinit(udev); + usb_ep0_reinit(udev); } retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE); @@ -2419,7 +2499,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, fail: if (retval) { hub_port_disable(hub, port1, 0); - udev->devnum = devnum; /* for disconnect processing */ + update_address(udev, devnum); /* for disconnect processing */ } mutex_unlock(&usb_address0_mutex); return retval; @@ -2568,6 +2648,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, udev->speed = USB_SPEED_UNKNOWN; udev->bus_mA = hub->mA_per_port; udev->level = hdev->level + 1; + udev->wusb = hub_is_wusb(hub); /* set the address */ choose_address(udev); @@ -2657,12 +2738,13 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, loop_disable: hub_port_disable(hub, port1, 1); loop: - ep0_reinit(udev); + usb_ep0_reinit(udev); release_address(udev); usb_put_dev(udev); if ((status == -ENOTCONN) || (status == -ENOTSUPP)) break; } + dev_err(hub_dev, "unable to enumerate USB device on port %d\n", port1); done: hub_port_disable(hub, port1, 1); @@ -2726,7 +2808,7 @@ static void hub_events(void) /* If the hub has died, clean up after it */ if (hdev->state == USB_STATE_NOTATTACHED) { hub->error = -ENODEV; - hub_pre_reset(intf); + hub_stop(hub); goto loop; } @@ -2872,11 +2954,6 @@ static void hub_events(void) hub->activating = 0; - /* If this is a root hub, tell the HCD it's okay to - * re-enable port-change interrupts now. */ - if (!hdev->parent && !hub->busy_bits[0]) - usb_enable_root_hub_irq(hdev->bus); - loop_autopm: /* Allow autosuspend if we're not going to run again */ if (list_empty(&hub->event_list)) @@ -2890,7 +2967,13 @@ loop: static int hub_thread(void *__unused) { + /* khubd needs to be freezable to avoid intefering with USB-PERSIST + * port handover. Otherwise it might see that a full-speed device + * was gone before the EHCI controller had handed its port over to + * the companion full-speed controller. + */ set_freezable(); + do { hub_events(); wait_event_freezable(khubd_wait, @@ -2959,16 +3042,36 @@ void usb_hub_cleanup(void) usb_deregister(&hub_driver); } /* usb_hub_cleanup() */ -static int config_descriptors_changed(struct usb_device *udev) +static int descriptors_changed(struct usb_device *udev, + struct usb_device_descriptor *old_device_descriptor) { - unsigned index; - unsigned len = 0; - struct usb_config_descriptor *buf; + int changed = 0; + unsigned index; + unsigned serial_len = 0; + unsigned len; + unsigned old_length; + int length; + char *buf; + + if (memcmp(&udev->descriptor, old_device_descriptor, + sizeof(*old_device_descriptor)) != 0) + return 1; + /* Since the idVendor, idProduct, and bcdDevice values in the + * device descriptor haven't changed, we will assume the + * Manufacturer and Product strings haven't changed either. + * But the SerialNumber string could be different (e.g., a + * different flash card of the same brand). + */ + if (udev->serial) + serial_len = strlen(udev->serial) + 1; + + len = serial_len; for (index = 0; index < udev->descriptor.bNumConfigurations; index++) { - if (len < le16_to_cpu(udev->config[index].desc.wTotalLength)) - len = le16_to_cpu(udev->config[index].desc.wTotalLength); + old_length = le16_to_cpu(udev->config[index].desc.wTotalLength); + len = max(len, old_length); } + buf = kmalloc(len, GFP_NOIO); if (buf == NULL) { dev_err(&udev->dev, "no mem to re-read configs after reset\n"); @@ -2976,25 +3079,41 @@ static int config_descriptors_changed(struct usb_device *udev) return 1; } for (index = 0; index < udev->descriptor.bNumConfigurations; index++) { - int length; - int old_length = le16_to_cpu(udev->config[index].desc.wTotalLength); - + old_length = le16_to_cpu(udev->config[index].desc.wTotalLength); length = usb_get_descriptor(udev, USB_DT_CONFIG, index, buf, old_length); - if (length < old_length) { + if (length != old_length) { dev_dbg(&udev->dev, "config index %d, error %d\n", index, length); + changed = 1; break; } if (memcmp (buf, udev->rawdescriptors[index], old_length) != 0) { dev_dbg(&udev->dev, "config index %d changed (#%d)\n", - index, buf->bConfigurationValue); + index, + ((struct usb_config_descriptor *) buf)-> + bConfigurationValue); + changed = 1; break; } } + + if (!changed && serial_len) { + length = usb_string(udev, udev->descriptor.iSerialNumber, + buf, serial_len); + if (length + 1 != serial_len) { + dev_dbg(&udev->dev, "serial string error %d\n", + length); + changed = 1; + } else if (memcmp(buf, udev->serial, length) != 0) { + dev_dbg(&udev->dev, "serial string changed\n"); + changed = 1; + } + } + kfree(buf); - return index != udev->descriptor.bNumConfigurations; + return changed; } /** @@ -3044,7 +3163,7 @@ int usb_reset_device(struct usb_device *udev) if (!parent_hdev) { /* this requires hcd-specific logic; see OHCI hc_restart() */ - dev_dbg(&udev->dev, "%s for root hub!\n", __FUNCTION__); + dev_dbg(&udev->dev, "%s for root hub!\n", __func__); return -EISDIR; } parent_hub = hdev_to_hub(parent_hdev); @@ -3054,21 +3173,18 @@ int usb_reset_device(struct usb_device *udev) /* ep0 maxpacket size may change; let the HCD know about it. * Other endpoints will be handled by re-enumeration. */ - ep0_reinit(udev); + usb_ep0_reinit(udev); ret = hub_port_init(parent_hub, udev, port1, i); if (ret >= 0 || ret == -ENOTCONN || ret == -ENODEV) break; } clear_bit(port1, parent_hub->busy_bits); - if (!parent_hdev->parent && !parent_hub->busy_bits[0]) - usb_enable_root_hub_irq(parent_hdev->bus); if (ret < 0) goto re_enumerate; /* Device might have changed firmware (DFU or similar) */ - if (memcmp(&udev->descriptor, &descriptor, sizeof descriptor) - || config_descriptors_changed (udev)) { + if (descriptors_changed(udev, &descriptor)) { dev_info(&udev->dev, "device firmware changed\n"); udev->descriptor = descriptor; /* for disconnect() calls */ goto re_enumerate; |