diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2007-05-30 15:39:33 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2007-07-12 19:34:30 -0400 |
commit | b41a60eca833d76593d4dac8a59f5c38714194ee (patch) | |
tree | a7c5cf721d9978503c3c8c88183747cf954b8733 /drivers/usb/core | |
parent | 54515fe528d8c6f9bfaf7d0b9fffb908deecad78 (diff) |
USB: add power/persist device attribute
This patch (as920) adds an extra level of protection to the
USB-Persist facility. Now it will apply by default only to hubs; for
all other devices the user must enable it explicitly by setting the
power/persist device attribute.
The disconnect_all_children() routine in hub.c has been removed and
its code placed inline. This is the way it was originally as part of
hub_pre_reset(); the revised usage in hub_reset_resume() is
sufficiently different that the code can no longer be shared.
Likewise, mark_children_for_reset() is now inline as part of
hub_reset_resume(). The end result looks much cleaner than before.
The sysfs interface is updated to add the new attribute file, and
there are corresponding documentation updates.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/core')
-rw-r--r-- | drivers/usb/core/Kconfig | 13 | ||||
-rw-r--r-- | drivers/usb/core/hub.c | 78 | ||||
-rw-r--r-- | drivers/usb/core/sysfs.c | 75 |
3 files changed, 110 insertions, 56 deletions
diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig index 5113ef4cb7f6..97b09f282705 100644 --- a/drivers/usb/core/Kconfig +++ b/drivers/usb/core/Kconfig | |||
@@ -91,12 +91,15 @@ config USB_PERSIST | |||
91 | depends on USB && PM && EXPERIMENTAL | 91 | depends on USB && PM && EXPERIMENTAL |
92 | default n | 92 | default n |
93 | help | 93 | help |
94 | If you say Y here, USB device data structures will remain | 94 | |
95 | If you say Y here and enable the "power/persist" attribute | ||
96 | for a USB device, the device's data structures will remain | ||
95 | persistent across system suspend, even if the USB bus loses | 97 | persistent across system suspend, even if the USB bus loses |
96 | power. (This includes software-suspend, also known as swsusp, | 98 | power. (This includes hibernation, also known as swsusp or |
97 | or suspend-to-disk.) The devices will reappear as if by magic | 99 | suspend-to-disk.) The devices will reappear as if by magic |
98 | when the system wakes up, with no need to unmount USB filesystems, | 100 | when the system wakes up, with no need to unmount USB |
99 | rmmod host-controller drivers, or do anything else. | 101 | filesystems, rmmod host-controller drivers, or do anything |
102 | else. | ||
100 | 103 | ||
101 | WARNING: This option can be dangerous! | 104 | WARNING: This option can be dangerous! |
102 | 105 | ||
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index c4cdb69a6e9e..50e79010401c 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c | |||
@@ -596,27 +596,18 @@ static void hub_port_logical_disconnect(struct usb_hub *hub, int port1) | |||
596 | kick_khubd(hub); | 596 | kick_khubd(hub); |
597 | } | 597 | } |
598 | 598 | ||
599 | static void disconnect_all_children(struct usb_hub *hub, int logical) | ||
600 | { | ||
601 | struct usb_device *hdev = hub->hdev; | ||
602 | int port1; | ||
603 | |||
604 | for (port1 = 1; port1 <= hdev->maxchild; ++port1) { | ||
605 | if (hdev->children[port1-1]) { | ||
606 | if (logical) | ||
607 | hub_port_logical_disconnect(hub, port1); | ||
608 | else | ||
609 | usb_disconnect(&hdev->children[port1-1]); | ||
610 | } | ||
611 | } | ||
612 | } | ||
613 | |||
614 | /* caller has locked the hub device */ | 599 | /* caller has locked the hub device */ |
615 | static int hub_pre_reset(struct usb_interface *intf) | 600 | static int hub_pre_reset(struct usb_interface *intf) |
616 | { | 601 | { |
617 | struct usb_hub *hub = usb_get_intfdata(intf); | 602 | struct usb_hub *hub = usb_get_intfdata(intf); |
603 | struct usb_device *hdev = hub->hdev; | ||
604 | int i; | ||
618 | 605 | ||
619 | disconnect_all_children(hub, 0); | 606 | /* Disconnect all the children */ |
607 | for (i = 0; i < hdev->maxchild; ++i) { | ||
608 | if (hdev->children[i]) | ||
609 | usb_disconnect(&hdev->children[i]); | ||
610 | } | ||
620 | hub_quiesce(hub); | 611 | hub_quiesce(hub); |
621 | return 0; | 612 | return 0; |
622 | } | 613 | } |
@@ -1872,50 +1863,39 @@ static int hub_resume(struct usb_interface *intf) | |||
1872 | return 0; | 1863 | return 0; |
1873 | } | 1864 | } |
1874 | 1865 | ||
1875 | #ifdef CONFIG_USB_PERSIST | 1866 | static int hub_reset_resume(struct usb_interface *intf) |
1876 | |||
1877 | /* For "persistent-device" resets we must mark the child devices for reset | ||
1878 | * and turn off a possible connect-change status (so khubd won't disconnect | ||
1879 | * them later). | ||
1880 | */ | ||
1881 | static void mark_children_for_reset_resume(struct usb_hub *hub) | ||
1882 | { | 1867 | { |
1868 | struct usb_hub *hub = usb_get_intfdata(intf); | ||
1883 | struct usb_device *hdev = hub->hdev; | 1869 | struct usb_device *hdev = hub->hdev; |
1884 | int port1; | 1870 | int port1; |
1885 | 1871 | ||
1872 | hub_power_on(hub); | ||
1873 | |||
1886 | for (port1 = 1; port1 <= hdev->maxchild; ++port1) { | 1874 | for (port1 = 1; port1 <= hdev->maxchild; ++port1) { |
1887 | struct usb_device *child = hdev->children[port1-1]; | 1875 | struct usb_device *child = hdev->children[port1-1]; |
1888 | 1876 | ||
1889 | if (child) { | 1877 | if (child) { |
1890 | child->reset_resume = 1; | 1878 | |
1891 | clear_port_feature(hdev, port1, | 1879 | /* For "USB_PERSIST"-enabled children we must |
1892 | USB_PORT_FEAT_C_CONNECTION); | 1880 | * mark the child device for reset-resume and |
1881 | * turn off the connect-change status to prevent | ||
1882 | * khubd from disconnecting it later. | ||
1883 | */ | ||
1884 | if (USB_PERSIST && child->persist_enabled) { | ||
1885 | child->reset_resume = 1; | ||
1886 | clear_port_feature(hdev, port1, | ||
1887 | USB_PORT_FEAT_C_CONNECTION); | ||
1888 | |||
1889 | /* Otherwise we must disconnect the child, | ||
1890 | * but as we may not lock the child device here | ||
1891 | * we have to do a "logical" disconnect. | ||
1892 | */ | ||
1893 | } else { | ||
1894 | hub_port_logical_disconnect(hub, port1); | ||
1895 | } | ||
1893 | } | 1896 | } |
1894 | } | 1897 | } |
1895 | } | ||
1896 | |||
1897 | #else | ||
1898 | |||
1899 | static inline void mark_children_for_reset_resume(struct usb_hub *hub) | ||
1900 | { } | ||
1901 | |||
1902 | #endif /* CONFIG_USB_PERSIST */ | ||
1903 | |||
1904 | static int hub_reset_resume(struct usb_interface *intf) | ||
1905 | { | ||
1906 | struct usb_hub *hub = usb_get_intfdata(intf); | ||
1907 | 1898 | ||
1908 | hub_power_on(hub); | ||
1909 | if (USB_PERSIST) | ||
1910 | mark_children_for_reset_resume(hub); | ||
1911 | else { | ||
1912 | /* Reset-resume doesn't call pre_reset, so we have to | ||
1913 | * disconnect the children here. But we may not lock | ||
1914 | * the child devices, so we have to do a "logical" | ||
1915 | * disconnect. | ||
1916 | */ | ||
1917 | disconnect_all_children(hub, 1); | ||
1918 | } | ||
1919 | hub_activate(hub); | 1899 | hub_activate(hub); |
1920 | return 0; | 1900 | return 0; |
1921 | } | 1901 | } |
diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index be37c863fdfb..5dfe31bc32ba 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c | |||
@@ -169,6 +169,73 @@ show_quirks(struct device *dev, struct device_attribute *attr, char *buf) | |||
169 | } | 169 | } |
170 | static DEVICE_ATTR(quirks, S_IRUGO, show_quirks, NULL); | 170 | static DEVICE_ATTR(quirks, S_IRUGO, show_quirks, NULL); |
171 | 171 | ||
172 | |||
173 | #if defined(CONFIG_USB_PERSIST) || defined(CONFIG_USB_SUSPEND) | ||
174 | static const char power_group[] = "power"; | ||
175 | #endif | ||
176 | |||
177 | #ifdef CONFIG_USB_PERSIST | ||
178 | |||
179 | static ssize_t | ||
180 | show_persist(struct device *dev, struct device_attribute *attr, char *buf) | ||
181 | { | ||
182 | struct usb_device *udev = to_usb_device(dev); | ||
183 | |||
184 | return sprintf(buf, "%d\n", udev->persist_enabled); | ||
185 | } | ||
186 | |||
187 | static ssize_t | ||
188 | set_persist(struct device *dev, struct device_attribute *attr, | ||
189 | const char *buf, size_t count) | ||
190 | { | ||
191 | struct usb_device *udev = to_usb_device(dev); | ||
192 | int value; | ||
193 | |||
194 | /* Hubs are always enabled for USB_PERSIST */ | ||
195 | if (udev->descriptor.bDeviceClass == USB_CLASS_HUB) | ||
196 | return -EPERM; | ||
197 | |||
198 | if (sscanf(buf, "%d", &value) != 1) | ||
199 | return -EINVAL; | ||
200 | usb_pm_lock(udev); | ||
201 | udev->persist_enabled = !!value; | ||
202 | usb_pm_unlock(udev); | ||
203 | return count; | ||
204 | } | ||
205 | |||
206 | static DEVICE_ATTR(persist, S_IRUGO | S_IWUSR, show_persist, set_persist); | ||
207 | |||
208 | static int add_persist_attributes(struct device *dev) | ||
209 | { | ||
210 | int rc = 0; | ||
211 | |||
212 | if (is_usb_device(dev)) { | ||
213 | struct usb_device *udev = to_usb_device(dev); | ||
214 | |||
215 | /* Hubs are automatically enabled for USB_PERSIST */ | ||
216 | if (udev->descriptor.bDeviceClass == USB_CLASS_HUB) | ||
217 | udev->persist_enabled = 1; | ||
218 | rc = sysfs_add_file_to_group(&dev->kobj, | ||
219 | &dev_attr_persist.attr, | ||
220 | power_group); | ||
221 | } | ||
222 | return rc; | ||
223 | } | ||
224 | |||
225 | static void remove_persist_attributes(struct device *dev) | ||
226 | { | ||
227 | sysfs_remove_file_from_group(&dev->kobj, | ||
228 | &dev_attr_persist.attr, | ||
229 | power_group); | ||
230 | } | ||
231 | |||
232 | #else | ||
233 | |||
234 | #define add_persist_attributes(dev) 0 | ||
235 | #define remove_persist_attributes(dev) do {} while (0) | ||
236 | |||
237 | #endif /* CONFIG_USB_PERSIST */ | ||
238 | |||
172 | #ifdef CONFIG_USB_SUSPEND | 239 | #ifdef CONFIG_USB_SUSPEND |
173 | 240 | ||
174 | static ssize_t | 241 | static ssize_t |
@@ -276,8 +343,6 @@ set_level(struct device *dev, struct device_attribute *attr, | |||
276 | 343 | ||
277 | static DEVICE_ATTR(level, S_IRUGO | S_IWUSR, show_level, set_level); | 344 | static DEVICE_ATTR(level, S_IRUGO | S_IWUSR, show_level, set_level); |
278 | 345 | ||
279 | static char power_group[] = "power"; | ||
280 | |||
281 | static int add_power_attributes(struct device *dev) | 346 | static int add_power_attributes(struct device *dev) |
282 | { | 347 | { |
283 | int rc = 0; | 348 | int rc = 0; |
@@ -311,6 +376,7 @@ static void remove_power_attributes(struct device *dev) | |||
311 | 376 | ||
312 | #endif /* CONFIG_USB_SUSPEND */ | 377 | #endif /* CONFIG_USB_SUSPEND */ |
313 | 378 | ||
379 | |||
314 | /* Descriptor fields */ | 380 | /* Descriptor fields */ |
315 | #define usb_descriptor_attr_le16(field, format_string) \ | 381 | #define usb_descriptor_attr_le16(field, format_string) \ |
316 | static ssize_t \ | 382 | static ssize_t \ |
@@ -384,6 +450,10 @@ int usb_create_sysfs_dev_files(struct usb_device *udev) | |||
384 | if (retval) | 450 | if (retval) |
385 | return retval; | 451 | return retval; |
386 | 452 | ||
453 | retval = add_persist_attributes(dev); | ||
454 | if (retval) | ||
455 | goto error; | ||
456 | |||
387 | retval = add_power_attributes(dev); | 457 | retval = add_power_attributes(dev); |
388 | if (retval) | 458 | if (retval) |
389 | goto error; | 459 | goto error; |
@@ -421,6 +491,7 @@ void usb_remove_sysfs_dev_files(struct usb_device *udev) | |||
421 | device_remove_file(dev, &dev_attr_product); | 491 | device_remove_file(dev, &dev_attr_product); |
422 | device_remove_file(dev, &dev_attr_serial); | 492 | device_remove_file(dev, &dev_attr_serial); |
423 | remove_power_attributes(dev); | 493 | remove_power_attributes(dev); |
494 | remove_persist_attributes(dev); | ||
424 | sysfs_remove_group(&dev->kobj, &dev_attr_grp); | 495 | sysfs_remove_group(&dev->kobj, &dev_attr_grp); |
425 | } | 496 | } |
426 | 497 | ||