aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2007-05-30 15:39:33 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2007-07-12 19:34:30 -0400
commitb41a60eca833d76593d4dac8a59f5c38714194ee (patch)
treea7c5cf721d9978503c3c8c88183747cf954b8733
parent54515fe528d8c6f9bfaf7d0b9fffb908deecad78 (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-usb13
-rw-r--r--Documentation/usb/persist.txt38
-rw-r--r--drivers/usb/core/Kconfig13
-rw-r--r--drivers/usb/core/hub.c78
-rw-r--r--drivers/usb/core/sysfs.c75
-rw-r--r--include/linux/usb.h1
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
43What: /sys/bus/usb/devices/.../power/persist
44Date: May 2007
45KernelVersion: 2.6.23
46Contact: Alan Stern <stern@rowland.harvard.edu>
47Description:
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
53On many systems the USB host controllers will get reset after a 53On many systems the USB host controllers will get reset after a
54suspend-to-RAM. On almost all systems, no suspend current is 54suspend-to-RAM. On almost all systems, no suspend current is
55available during suspend-to-disk (also known as swsusp). You can 55available during hibernation (also known as swsusp or suspend-to-disk).
56check the kernel log after resuming to see if either of these has 56You can check the kernel log after resuming to see if either of these
57happened; look for lines saying "root hub lost power or was reset". 57has happened; look for lines saying "root hub lost power or was reset".
58 58
59In practice, people are forced to unmount any filesystems on a USB 59In practice, people are forced to unmount any filesystems on a USB
60device before suspending. If the root filesystem is on a USB device, 60device 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.
71It works like this. If the kernel sees that a USB host controller is 71It works like this. If the kernel sees that a USB host controller is
72not in the expected state during resume (i.e., if the controller was 72not in the expected state during resume (i.e., if the controller was
73reset or otherwise had lost power) then it applies a persistence check 73reset or otherwise had lost power) then it applies a persistence check
74to each of the USB devices below that controller. It doesn't try to 74to each of the USB devices below that controller for which the
75resume 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
76Instead it issues a USB port reset and then re-enumerates the device. 76can'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 77port reset and then re-enumerates the device. (This is exactly the
78reset.) If the re-enumeration shows that the device now attached to 78same thing that happens whenever a USB device is reset.) If the
79that port has the same descriptors as before, including the Vendor and 79re-enumeration shows that the device now attached to that port has the
80Product IDs, then the kernel continues to use the same device 80same descriptors as before, including the Vendor and Product IDs, then
81structure. In effect, the kernel treats the device as though it had 81the kernel continues to use the same device structure. In effect, the
82merely been reset instead of unplugged. 82kernel treats the device as though it had merely been reset instead of
83unplugged.
83 84
84If no device is now attached to the port, or if the descriptors are 85If no device is now attached to the port, or if the descriptors are
85different from what the kernel remembers, then the treatment is what 86different 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.
91Filesystem mounts and memory mappings are unaffected, and the world is 92Filesystem mounts and memory mappings are unaffected, and the world is
92now a good and happy place. 93now a good and happy place.
93 94
95Note that even when CONFIG_USB_PERSIST is set, the "persist" feature
96will be applied only to those devices for which it is enabled. You
97can enable the feature by doing (as root):
98
99 echo 1 >/sys/bus/usb/devices/.../power/persist
100
101where the "..." should be filled in the with the device's ID. Disable
102the feature by writing 0 instead of 1. For hubs the feature is
103automatically and permanently enabled, so you only have to worry about
104setting 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
599static 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 */
615static int hub_pre_reset(struct usb_interface *intf) 600static 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 1866static 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 */
1881static 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
1899static inline void mark_children_for_reset_resume(struct usb_hub *hub)
1900{ }
1901
1902#endif /* CONFIG_USB_PERSIST */
1903
1904static 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}
170static DEVICE_ATTR(quirks, S_IRUGO, show_quirks, NULL); 170static DEVICE_ATTR(quirks, S_IRUGO, show_quirks, NULL);
171 171
172
173#if defined(CONFIG_USB_PERSIST) || defined(CONFIG_USB_SUSPEND)
174static const char power_group[] = "power";
175#endif
176
177#ifdef CONFIG_USB_PERSIST
178
179static ssize_t
180show_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
187static ssize_t
188set_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
206static DEVICE_ATTR(persist, S_IRUGO | S_IWUSR, show_persist, set_persist);
207
208static 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
225static 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
174static ssize_t 241static ssize_t
@@ -276,8 +343,6 @@ set_level(struct device *dev, struct device_attribute *attr,
276 343
277static DEVICE_ATTR(level, S_IRUGO | S_IWUSR, show_level, set_level); 344static DEVICE_ATTR(level, S_IRUGO | S_IWUSR, show_level, set_level);
278 345
279static char power_group[] = "power";
280
281static int add_power_attributes(struct device *dev) 346static 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) \
316static ssize_t \ 382static 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