diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2008-04-28 11:06:11 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2008-07-21 18:15:47 -0400 |
commit | b01b03f3ad82b4293f6ca4da9b2692b6a377c609 (patch) | |
tree | 9d3ab94600f6fa0491256ab1ac0fd824e55ee880 /drivers | |
parent | bd2c784595e3dd551c2b3aa4167657bcc802f598 (diff) |
USB: add new routine for checking port-resume type
This patch (as1070) creates a new subroutine to check whether a device
can be resumed. This code is needed even when CONFIG_USB_SUSPEND
isn't set, because devices do suspend themselves when the root hub
(and hence the entire bus) is suspended, and power sessions can get
lost during a system sleep even without individual port suspends.
The patch also fixes a loose end in USB-Persist reset-resume handling.
When a low- or full-speed device is attached to an EHCI's companion
controller, the port handoff during resume will cause the companion
port's connect-status-change feature to be set. If that flag isn't
cleared, the port-reset code will think it indicates that the device
has been unplugged and the reset-resume will fail.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/usb/core/hub.c | 89 |
1 files changed, 63 insertions, 26 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 2a5c2833de38..d14da2123eb5 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c | |||
@@ -1821,6 +1821,45 @@ static int hub_port_reset(struct usb_hub *hub, int port1, | |||
1821 | 1821 | ||
1822 | #ifdef CONFIG_PM | 1822 | #ifdef CONFIG_PM |
1823 | 1823 | ||
1824 | #define MASK_BITS (USB_PORT_STAT_POWER | USB_PORT_STAT_CONNECTION | \ | ||
1825 | USB_PORT_STAT_SUSPEND) | ||
1826 | #define WANT_BITS (USB_PORT_STAT_POWER | USB_PORT_STAT_CONNECTION) | ||
1827 | |||
1828 | /* Determine whether the device on a port is ready for a normal resume, | ||
1829 | * is ready for a reset-resume, or should be disconnected. | ||
1830 | */ | ||
1831 | static int check_port_resume_type(struct usb_device *udev, | ||
1832 | struct usb_hub *hub, int port1, | ||
1833 | int status, unsigned portchange, unsigned portstatus) | ||
1834 | { | ||
1835 | /* Is the device still present? */ | ||
1836 | if (status || (portstatus & MASK_BITS) != WANT_BITS) { | ||
1837 | if (status >= 0) | ||
1838 | status = -ENODEV; | ||
1839 | } | ||
1840 | |||
1841 | /* Can't do a normal resume if the port isn't enabled */ | ||
1842 | else if (!(portstatus & USB_PORT_STAT_ENABLE) && !udev->reset_resume) | ||
1843 | status = -ENODEV; | ||
1844 | |||
1845 | if (status) { | ||
1846 | dev_dbg(hub->intfdev, | ||
1847 | "port %d status %04x.%04x after resume, %d\n", | ||
1848 | port1, portchange, portstatus, status); | ||
1849 | } else if (udev->reset_resume) { | ||
1850 | |||
1851 | /* Late port handoff can set status-change bits */ | ||
1852 | if (portchange & USB_PORT_STAT_C_CONNECTION) | ||
1853 | clear_port_feature(hub->hdev, port1, | ||
1854 | USB_PORT_FEAT_C_CONNECTION); | ||
1855 | if (portchange & USB_PORT_STAT_C_ENABLE) | ||
1856 | clear_port_feature(hub->hdev, port1, | ||
1857 | USB_PORT_FEAT_C_ENABLE); | ||
1858 | } | ||
1859 | |||
1860 | return status; | ||
1861 | } | ||
1862 | |||
1824 | #ifdef CONFIG_USB_SUSPEND | 1863 | #ifdef CONFIG_USB_SUSPEND |
1825 | 1864 | ||
1826 | /* | 1865 | /* |
@@ -2025,7 +2064,6 @@ int usb_port_resume(struct usb_device *udev) | |||
2025 | int port1 = udev->portnum; | 2064 | int port1 = udev->portnum; |
2026 | int status; | 2065 | int status; |
2027 | u16 portchange, portstatus; | 2066 | u16 portchange, portstatus; |
2028 | unsigned mask_flags, want_flags; | ||
2029 | 2067 | ||
2030 | /* Skip the initial Clear-Suspend step for a remote wakeup */ | 2068 | /* Skip the initial Clear-Suspend step for a remote wakeup */ |
2031 | status = hub_port_status(hub, port1, &portstatus, &portchange); | 2069 | status = hub_port_status(hub, port1, &portstatus, &portchange); |
@@ -2054,35 +2092,23 @@ int usb_port_resume(struct usb_device *udev) | |||
2054 | */ | 2092 | */ |
2055 | status = hub_port_status(hub, port1, &portstatus, &portchange); | 2093 | status = hub_port_status(hub, port1, &portstatus, &portchange); |
2056 | 2094 | ||
2057 | SuspendCleared: | 2095 | /* TRSMRCY = 10 msec */ |
2058 | if (udev->reset_resume) | 2096 | msleep(10); |
2059 | want_flags = USB_PORT_STAT_POWER | 2097 | } |
2060 | | USB_PORT_STAT_CONNECTION; | ||
2061 | else | ||
2062 | want_flags = USB_PORT_STAT_POWER | ||
2063 | | USB_PORT_STAT_CONNECTION | ||
2064 | | USB_PORT_STAT_ENABLE; | ||
2065 | mask_flags = want_flags | USB_PORT_STAT_SUSPEND; | ||
2066 | 2098 | ||
2067 | if (status < 0 || (portstatus & mask_flags) != want_flags) { | 2099 | SuspendCleared: |
2068 | dev_dbg(hub->intfdev, | 2100 | if (status == 0) { |
2069 | "port %d status %04x.%04x after resume, %d\n", | 2101 | if (portchange & USB_PORT_STAT_C_SUSPEND) |
2070 | port1, portchange, portstatus, status); | 2102 | clear_port_feature(hub->hdev, port1, |
2071 | if (status >= 0) | 2103 | USB_PORT_FEAT_C_SUSPEND); |
2072 | status = -ENODEV; | ||
2073 | } else { | ||
2074 | if (portchange & USB_PORT_STAT_C_SUSPEND) | ||
2075 | clear_port_feature(hub->hdev, port1, | ||
2076 | USB_PORT_FEAT_C_SUSPEND); | ||
2077 | /* TRSMRCY = 10 msec */ | ||
2078 | msleep(10); | ||
2079 | } | ||
2080 | } | 2104 | } |
2081 | 2105 | ||
2082 | clear_bit(port1, hub->busy_bits); | 2106 | clear_bit(port1, hub->busy_bits); |
2083 | if (!hub->hdev->parent && !hub->busy_bits[0]) | 2107 | if (!hub->hdev->parent && !hub->busy_bits[0]) |
2084 | usb_enable_root_hub_irq(hub->hdev->bus); | 2108 | usb_enable_root_hub_irq(hub->hdev->bus); |
2085 | 2109 | ||
2110 | status = check_port_resume_type(udev, | ||
2111 | hub, port1, status, portchange, portstatus); | ||
2086 | if (status == 0) | 2112 | if (status == 0) |
2087 | status = finish_port_resume(udev); | 2113 | status = finish_port_resume(udev); |
2088 | if (status < 0) { | 2114 | if (status < 0) { |
@@ -2115,12 +2141,23 @@ int usb_port_suspend(struct usb_device *udev) | |||
2115 | return 0; | 2141 | return 0; |
2116 | } | 2142 | } |
2117 | 2143 | ||
2144 | /* However we may need to do a reset-resume */ | ||
2145 | |||
2118 | int usb_port_resume(struct usb_device *udev) | 2146 | int usb_port_resume(struct usb_device *udev) |
2119 | { | 2147 | { |
2120 | int status = 0; | 2148 | struct usb_hub *hub = hdev_to_hub(udev->parent); |
2149 | int port1 = udev->portnum; | ||
2150 | int status; | ||
2151 | u16 portchange, portstatus; | ||
2121 | 2152 | ||
2122 | /* However we may need to do a reset-resume */ | 2153 | status = hub_port_status(hub, port1, &portstatus, &portchange); |
2123 | if (udev->reset_resume) { | 2154 | status = check_port_resume_type(udev, |
2155 | hub, port1, status, portchange, portstatus); | ||
2156 | |||
2157 | if (status) { | ||
2158 | dev_dbg(&udev->dev, "can't resume, status %d\n", status); | ||
2159 | hub_port_logical_disconnect(hub, port1); | ||
2160 | } else if (udev->reset_resume) { | ||
2124 | dev_dbg(&udev->dev, "reset-resume\n"); | 2161 | dev_dbg(&udev->dev, "reset-resume\n"); |
2125 | status = usb_reset_device(udev); | 2162 | status = usb_reset_device(udev); |
2126 | } | 2163 | } |