diff options
-rw-r--r-- | drivers/usb/core/hcd.c | 2 | ||||
-rw-r--r-- | drivers/usb/core/hub.c | 97 | ||||
-rw-r--r-- | drivers/usb/core/hub.h | 4 | ||||
-rw-r--r-- | drivers/usb/core/port.c | 6 |
4 files changed, 72 insertions, 37 deletions
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index b81407518fd..bec31e2efb8 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -2267,9 +2267,7 @@ static void hcd_resume_work(struct work_struct *work) struct usb_hcd *hcd = container_of(work, struct usb_hcd, wakeup_work); struct usb_device *udev = hcd->self.root_hub; - usb_lock_device(udev); usb_remote_wakeup(udev); - usb_unlock_device(udev); } /** diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 988f227e796..d43054e8e25 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2781,6 +2781,20 @@ static int port_is_power_on(struct usb_hub *hub, unsigned portstatus) return ret; } +static void usb_lock_port(struct usb_port *port_dev) + __acquires(&port_dev->status_lock) +{ + mutex_lock(&port_dev->status_lock); + __acquire(&port_dev->status_lock); +} + +static void usb_unlock_port(struct usb_port *port_dev) + __releases(&port_dev->status_lock) +{ + mutex_unlock(&port_dev->status_lock); + __release(&port_dev->status_lock); +} + #ifdef CONFIG_PM /* Check if a port is suspended(USB2.0 port) or in U3 state(USB3.0 port) */ @@ -3003,6 +3017,8 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) int status; bool really_suspend = true; + usb_lock_port(port_dev); + /* enable remote wakeup when appropriate; this lets the device * wake up the upstream hub (including maybe the root hub). * @@ -3096,6 +3112,8 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) pm_runtime_put_sync(&port_dev->dev); usb_mark_last_busy(hub->hdev); + + usb_unlock_port(port_dev); return status; } @@ -3244,13 +3262,13 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) } } + usb_lock_port(port_dev); + /* Skip the initial Clear-Suspend step for a remote wakeup */ status = hub_port_status(hub, port1, &portstatus, &portchange); if (status == 0 && !port_is_suspended(hub, portstatus)) goto SuspendCleared; - set_bit(port1, hub->busy_bits); - /* see 7.1.7.7; affects power usage, but not budgeting */ if (hub_is_superspeed(hub->hdev)) status = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_U0); @@ -3289,8 +3307,6 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) } } - clear_bit(port1, hub->busy_bits); - status = check_port_resume_type(udev, hub, port1, status, portchange, portstatus); if (status == 0) @@ -3308,16 +3324,18 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) usb_unlocked_enable_lpm(udev); } + usb_unlock_port(port_dev); + return status; } #ifdef CONFIG_PM_RUNTIME -/* caller has locked udev */ int usb_remote_wakeup(struct usb_device *udev) { int status = 0; + usb_lock_device(udev); if (udev->state == USB_STATE_SUSPENDED) { dev_dbg(&udev->dev, "usb %sresume\n", "wakeup-"); status = usb_autoresume_device(udev); @@ -3326,6 +3344,7 @@ int usb_remote_wakeup(struct usb_device *udev) usb_autosuspend_device(udev); } } + usb_unlock_device(udev); return status; } @@ -4030,9 +4049,10 @@ static int hub_enable_device(struct usb_device *udev) * Returns device in USB_STATE_ADDRESS, except on error. * * If this is called for an already-existing device (as part of - * usb_reset_and_verify_device), the caller must own the device lock. For a - * newly detected device that is not accessible through any global - * pointers, it's not necessary to lock the device. + * usb_reset_and_verify_device), the caller must own the device lock and + * the port lock. For a newly detected device that is not accessible + * through any global pointers, it's not necessary to lock the device, + * but it is still necessary to lock the port. */ static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, @@ -4502,7 +4522,9 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus, } /* reset (non-USB 3.0 devices) and get descriptor */ + usb_lock_port(port_dev); status = hub_port_init(hub, udev, port1, i); + usb_unlock_port(port_dev); if (status < 0) goto loop; @@ -4624,6 +4646,7 @@ done: */ static void hub_port_connect_change(struct usb_hub *hub, int port1, u16 portstatus, u16 portchange) + __must_hold(&port_dev->status_lock) { struct usb_port *port_dev = hub->ports[port1 - 1]; struct usb_device *udev = port_dev->child; @@ -4655,26 +4678,29 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, /* For a suspended device, treat this as a * remote wakeup event. */ - usb_lock_device(udev); + usb_unlock_port(port_dev); status = usb_remote_wakeup(udev); - usb_unlock_device(udev); + usb_lock_port(port_dev); #endif } else { /* Don't resuscitate */; } - } clear_bit(port1, hub->change_bits); + /* successfully revalidated the connection */ if (status == 0) return; + usb_unlock_port(port_dev); hub_port_connect(hub, port1, portstatus, portchange); + usb_lock_port(port_dev); } /* 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) + __must_hold(&port_dev->status_lock) { struct usb_port *port_dev = hub->ports[port - 1]; struct usb_device *hdev; @@ -4699,9 +4725,9 @@ static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port, /* TRSMRCY = 10 msec */ msleep(10); - usb_lock_device(udev); + usb_unlock_port(port_dev); ret = usb_remote_wakeup(udev); - usb_unlock_device(udev); + usb_lock_port(port_dev); if (ret < 0) connect_change = 1; } else { @@ -4713,6 +4739,7 @@ static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port, } static void port_event(struct usb_hub *hub, int port1) + __must_hold(&port_dev->status_lock) { int connect_change, reset_device = 0; struct usb_port *port_dev = hub->ports[port1 - 1]; @@ -4820,9 +4847,11 @@ static void port_event(struct usb_hub *hub, int port1) if (reset_device || (udev && hub_is_superspeed(hub->hdev) && (portchange & USB_PORT_STAT_C_LINK_STATE) && (portstatus & USB_PORT_STAT_CONNECTION))) { + usb_unlock_port(port_dev); usb_lock_device(udev); usb_reset_device(udev); usb_unlock_device(udev); + usb_lock_port(port_dev); connect_change = 0; } @@ -4916,10 +4945,9 @@ static void hub_events(void) for (i = 1; i <= hdev->maxchild; i++) { struct usb_port *port_dev = hub->ports[i - 1]; - if (!test_bit(i, hub->busy_bits) - && (test_bit(i, hub->event_bits) - || test_bit(i, hub->change_bits) - || test_bit(i, hub->wakeup_bits))) { + if (test_bit(i, hub->event_bits) + || test_bit(i, hub->change_bits) + || test_bit(i, hub->wakeup_bits)) { /* * The get_noresume and barrier ensure that if * the port was in the process of resuming, we @@ -4931,7 +4959,9 @@ static void hub_events(void) */ pm_runtime_get_noresume(&port_dev->dev); pm_runtime_barrier(&port_dev->dev); + usb_lock_port(port_dev); port_event(hub, i); + usb_unlock_port(port_dev); pm_runtime_put_sync(&port_dev->dev); } } @@ -5169,15 +5199,18 @@ static int descriptors_changed(struct usb_device *udev, * if the reset wasn't even attempted. * * Note: - * The caller must own the device lock. For example, it's safe to use - * this from a driver probe() routine after downloading new firmware. - * For calls that might not occur during probe(), drivers should lock - * the device using usb_lock_device_for_reset(). + * The caller must own the device lock and the port lock, the latter is + * taken by usb_reset_device(). For example, it's safe to use + * usb_reset_device() from a driver probe() routine after downloading + * new firmware. For calls that might not occur during probe(), drivers + * should lock the device using usb_lock_device_for_reset(). * * Locking exception: This routine may also be called from within an * autoresume handler. Such usage won't conflict with other tasks * holding the device lock because these tasks should always call - * usb_autopm_resume_device(), thereby preventing any unwanted autoresume. + * usb_autopm_resume_device(), thereby preventing any unwanted + * autoresume. The autoresume handler is expected to have already + * acquired the port lock before calling this routine. */ static int usb_reset_and_verify_device(struct usb_device *udev) { @@ -5196,11 +5229,9 @@ static int usb_reset_and_verify_device(struct usb_device *udev) return -EINVAL; } - if (!parent_hdev) { - /* this requires hcd-specific logic; see ohci_restart() */ - dev_dbg(&udev->dev, "%s for root hub!\n", __func__); + if (!parent_hdev) return -EISDIR; - } + parent_hub = usb_hub_to_struct_hub(parent_hdev); /* Disable USB2 hardware LPM. @@ -5229,7 +5260,6 @@ static int usb_reset_and_verify_device(struct usb_device *udev) goto re_enumerate; } - set_bit(port1, parent_hub->busy_bits); for (i = 0; i < SET_CONFIG_TRIES; ++i) { /* ep0 maxpacket size may change; let the HCD know about it. @@ -5239,7 +5269,6 @@ static int usb_reset_and_verify_device(struct usb_device *udev) if (ret >= 0 || ret == -ENOTCONN || ret == -ENODEV) break; } - clear_bit(port1, parent_hub->busy_bits); if (ret < 0) goto re_enumerate; @@ -5360,7 +5389,9 @@ int usb_reset_device(struct usb_device *udev) int ret; int i; unsigned int noio_flag; + struct usb_port *port_dev; struct usb_host_config *config = udev->actconfig; + struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent); if (udev->state == USB_STATE_NOTATTACHED || udev->state == USB_STATE_SUSPENDED) { @@ -5369,6 +5400,14 @@ int usb_reset_device(struct usb_device *udev) return -EINVAL; } + if (!udev->parent) { + /* this requires hcd-specific logic; see ohci_restart() */ + dev_dbg(&udev->dev, "%s for root hub!\n", __func__); + return -EISDIR; + } + + port_dev = hub->ports[udev->portnum - 1]; + /* * Don't allocate memory with GFP_KERNEL in current * context to avoid possible deadlock if usb mass @@ -5402,7 +5441,9 @@ int usb_reset_device(struct usb_device *udev) } } + usb_lock_port(port_dev); ret = usb_reset_and_verify_device(udev); + usb_unlock_port(port_dev); if (config) { for (i = config->desc.bNumInterfaces - 1; i >= 0; --i) { diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index 906c355e063..0a7cdc0ef0a 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h @@ -45,8 +45,6 @@ struct usb_hub { unsigned long event_bits[1]; /* status change bitmask */ unsigned long change_bits[1]; /* ports with logical connect status change */ - unsigned long busy_bits[1]; /* ports being reset or - resumed */ unsigned long removed_bits[1]; /* ports with a "removed" device present */ unsigned long wakeup_bits[1]; /* ports that have signaled @@ -88,6 +86,7 @@ struct usb_hub { * @peer: related usb2 and usb3 ports (share the same connector) * @connect_type: port's connect type * @location: opaque representation of platform connector location + * @status_lock: synchronize port_event() vs usb_port_{suspend|resume} * @portnum: port index num based one * @is_superspeed cache super-speed status */ @@ -98,6 +97,7 @@ struct usb_port { struct usb_port *peer; enum usb_port_connect_type connect_type; usb_port_location_t location; + struct mutex status_lock; u8 portnum; unsigned int is_superspeed:1; }; diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index fb83c2c1392..8b165570010 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -95,8 +95,6 @@ static int usb_port_runtime_resume(struct device *dev) pm_runtime_get_sync(&peer->dev); usb_autopm_get_interface(intf); - set_bit(port1, hub->busy_bits); - retval = usb_hub_set_port_power(hdev, hub, port1, true); msleep(hub_power_on_good_delay(hub)); if (port_dev->child && !retval) { @@ -113,7 +111,6 @@ static int usb_port_runtime_resume(struct device *dev) retval = 0; } - clear_bit(port1, hub->busy_bits); usb_autopm_put_interface(intf); return retval; @@ -139,12 +136,10 @@ static int usb_port_runtime_suspend(struct device *dev) return -EAGAIN; usb_autopm_get_interface(intf); - set_bit(port1, hub->busy_bits); retval = usb_hub_set_port_power(hdev, hub, port1, false); usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION); if (!port_dev->is_superspeed) usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE); - clear_bit(port1, hub->busy_bits); usb_autopm_put_interface(intf); /* @@ -400,6 +395,7 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1) port_dev->is_superspeed = 1; dev_set_name(&port_dev->dev, "%s-port%d", dev_name(&hub->hdev->dev), port1); + mutex_init(&port_dev->status_lock); retval = device_register(&port_dev->dev); if (retval) goto error_register; |