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 | |
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>
-rw-r--r-- | Documentation/ABI/testing/sysfs-bus-usb | 13 | ||||
-rw-r--r-- | Documentation/usb/persist.txt | 38 | ||||
-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 | ||||
-rw-r--r-- | include/linux/usb.h | 1 |
6 files changed, 149 insertions, 69 deletions
diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index f9937add033d..9734577d1711 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb | |||
@@ -39,3 +39,16 @@ Description: | |||
39 | If you want to suspend a device immediately but leave it | 39 | If you want to suspend a device immediately but leave it |
40 | free to wake up in response to I/O requests, you should | 40 | free to wake up in response to I/O requests, you should |
41 | write "0" to power/autosuspend. | 41 | write "0" to power/autosuspend. |
42 | |||
43 | What: /sys/bus/usb/devices/.../power/persist | ||
44 | Date: May 2007 | ||
45 | KernelVersion: 2.6.23 | ||
46 | Contact: Alan Stern <stern@rowland.harvard.edu> | ||
47 | Description: | ||
48 | If CONFIG_USB_PERSIST is set, then each USB device directory | ||
49 | will contain a file named power/persist. The file holds a | ||
50 | boolean value (0 or 1) indicating whether or not the | ||
51 | "USB-Persist" facility is enabled for the device. Since the | ||
52 | facility is inherently dangerous, it is disabled by default | ||
53 | for all devices except hubs. For more information, see | ||
54 | Documentation/usb/persist.txt. | ||
diff --git a/Documentation/usb/persist.txt b/Documentation/usb/persist.txt index 6dcd5f884795..df54d645cbb5 100644 --- a/Documentation/usb/persist.txt +++ b/Documentation/usb/persist.txt | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | Alan Stern <stern@rowland.harvard.edu> | 3 | Alan Stern <stern@rowland.harvard.edu> |
4 | 4 | ||
5 | September 2, 2006 (Updated March 27, 2007) | 5 | September 2, 2006 (Updated May 29, 2007) |
6 | 6 | ||
7 | 7 | ||
8 | What is the problem? | 8 | What is the problem? |
@@ -52,9 +52,9 @@ you can convince the BIOS supplier to fix the problem (lots of luck!). | |||
52 | 52 | ||
53 | On many systems the USB host controllers will get reset after a | 53 | On many systems the USB host controllers will get reset after a |
54 | suspend-to-RAM. On almost all systems, no suspend current is | 54 | suspend-to-RAM. On almost all systems, no suspend current is |
55 | available during suspend-to-disk (also known as swsusp). You can | 55 | available during hibernation (also known as swsusp or suspend-to-disk). |
56 | check the kernel log after resuming to see if either of these has | 56 | You can check the kernel log after resuming to see if either of these |
57 | happened; look for lines saying "root hub lost power or was reset". | 57 | has happened; look for lines saying "root hub lost power or was reset". |
58 | 58 | ||
59 | In practice, people are forced to unmount any filesystems on a USB | 59 | In practice, people are forced to unmount any filesystems on a USB |
60 | device before suspending. If the root filesystem is on a USB device, | 60 | device before suspending. If the root filesystem is on a USB device, |
@@ -71,15 +71,16 @@ structures are allowed to persist across a power-session disruption. | |||
71 | It works like this. If the kernel sees that a USB host controller is | 71 | It works like this. If the kernel sees that a USB host controller is |
72 | not in the expected state during resume (i.e., if the controller was | 72 | not in the expected state during resume (i.e., if the controller was |
73 | reset or otherwise had lost power) then it applies a persistence check | 73 | reset or otherwise had lost power) then it applies a persistence check |
74 | to each of the USB devices below that controller. It doesn't try to | 74 | to each of the USB devices below that controller for which the |
75 | resume the device; that can't work once the power session is gone. | 75 | "persist" attribute is set. It doesn't try to resume the device; that |
76 | Instead it issues a USB port reset and then re-enumerates the device. | 76 | can't work once the power session is gone. Instead it issues a USB |
77 | (This is exactly the same thing that happens whenever a USB device is | 77 | port reset and then re-enumerates the device. (This is exactly the |
78 | reset.) If the re-enumeration shows that the device now attached to | 78 | same thing that happens whenever a USB device is reset.) If the |
79 | that port has the same descriptors as before, including the Vendor and | 79 | re-enumeration shows that the device now attached to that port has the |
80 | Product IDs, then the kernel continues to use the same device | 80 | same descriptors as before, including the Vendor and Product IDs, then |
81 | structure. In effect, the kernel treats the device as though it had | 81 | the kernel continues to use the same device structure. In effect, the |
82 | merely been reset instead of unplugged. | 82 | kernel treats the device as though it had merely been reset instead of |
83 | unplugged. | ||
83 | 84 | ||
84 | If no device is now attached to the port, or if the descriptors are | 85 | If no device is now attached to the port, or if the descriptors are |
85 | different from what the kernel remembers, then the treatment is what | 86 | different from what the kernel remembers, then the treatment is what |
@@ -91,6 +92,17 @@ The end result is that the USB device remains available and usable. | |||
91 | Filesystem mounts and memory mappings are unaffected, and the world is | 92 | Filesystem mounts and memory mappings are unaffected, and the world is |
92 | now a good and happy place. | 93 | now a good and happy place. |
93 | 94 | ||
95 | Note that even when CONFIG_USB_PERSIST is set, the "persist" feature | ||
96 | will be applied only to those devices for which it is enabled. You | ||
97 | can enable the feature by doing (as root): | ||
98 | |||
99 | echo 1 >/sys/bus/usb/devices/.../power/persist | ||
100 | |||
101 | where the "..." should be filled in the with the device's ID. Disable | ||
102 | the feature by writing 0 instead of 1. For hubs the feature is | ||
103 | automatically and permanently enabled, so you only have to worry about | ||
104 | setting it for devices where it really matters. | ||
105 | |||
94 | 106 | ||
95 | Is this the best solution? | 107 | Is this the best solution? |
96 | 108 | ||
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 | ||
diff --git a/include/linux/usb.h b/include/linux/usb.h index bde8c65e2bfc..efce9a4c511c 100644 --- a/include/linux/usb.h +++ b/include/linux/usb.h | |||
@@ -404,6 +404,7 @@ struct usb_device { | |||
404 | unsigned auto_pm:1; /* autosuspend/resume in progress */ | 404 | unsigned auto_pm:1; /* autosuspend/resume in progress */ |
405 | unsigned do_remote_wakeup:1; /* remote wakeup should be enabled */ | 405 | unsigned do_remote_wakeup:1; /* remote wakeup should be enabled */ |
406 | unsigned reset_resume:1; /* needs reset instead of resume */ | 406 | unsigned reset_resume:1; /* needs reset instead of resume */ |
407 | unsigned persist_enabled:1; /* USB_PERSIST enabled for this dev */ | ||
407 | unsigned autosuspend_disabled:1; /* autosuspend and autoresume */ | 408 | unsigned autosuspend_disabled:1; /* autosuspend and autoresume */ |
408 | unsigned autoresume_disabled:1; /* disabled by the user */ | 409 | unsigned autoresume_disabled:1; /* disabled by the user */ |
409 | #endif | 410 | #endif |