Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Alan Stern and committed by
Greg Kroah-Hartman
b41a60ec 54515fe5

+149 -69
+13
Documentation/ABI/testing/sysfs-bus-usb
··· 39 39 If you want to suspend a device immediately but leave it 40 40 free to wake up in response to I/O requests, you should 41 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.
+25 -13
Documentation/usb/persist.txt
··· 2 2 3 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 8 What is the problem? ··· 52 52 53 53 On many systems the USB host controllers will get reset after a 54 54 suspend-to-RAM. On almost all systems, no suspend current is 55 - available during suspend-to-disk (also known as swsusp). You can 56 - check the kernel log after resuming to see if either of these has 57 - happened; look for lines saying "root hub lost power or was reset". 55 + available during hibernation (also known as swsusp or suspend-to-disk). 56 + You can check the kernel log after resuming to see if either of these 57 + has happened; look for lines saying "root hub lost power or was reset". 58 58 59 59 In practice, people are forced to unmount any filesystems on a USB 60 60 device before suspending. If the root filesystem is on a USB device, ··· 71 71 It works like this. If the kernel sees that a USB host controller is 72 72 not in the expected state during resume (i.e., if the controller was 73 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 75 - resume the device; that can't work once the power session is gone. 76 - Instead it issues a USB port reset and then re-enumerates the device. 77 - (This is exactly the same thing that happens whenever a USB device is 78 - reset.) If the re-enumeration shows that the device now attached to 79 - that port has the same descriptors as before, including the Vendor and 80 - Product IDs, then the kernel continues to use the same device 81 - structure. In effect, the kernel treats the device as though it had 82 - merely been reset instead of unplugged. 74 + to each of the USB devices below that controller for which the 75 + "persist" attribute is set. It doesn't try to resume the device; that 76 + can't work once the power session is gone. Instead it issues a USB 77 + port reset and then re-enumerates the device. (This is exactly the 78 + same thing that happens whenever a USB device is reset.) If the 79 + re-enumeration shows that the device now attached to that port has the 80 + same descriptors as before, including the Vendor and Product IDs, then 81 + the kernel continues to use the same device structure. In effect, the 82 + kernel treats the device as though it had merely been reset instead of 83 + unplugged. 83 84 84 85 If no device is now attached to the port, or if the descriptors are 85 86 different from what the kernel remembers, then the treatment is what ··· 91 90 The end result is that the USB device remains available and usable. 92 91 Filesystem mounts and memory mappings are unaffected, and the world is 93 92 now a good and happy place. 93 + 94 + Note that even when CONFIG_USB_PERSIST is set, the "persist" feature 95 + will be applied only to those devices for which it is enabled. You 96 + can enable the feature by doing (as root): 97 + 98 + echo 1 >/sys/bus/usb/devices/.../power/persist 99 + 100 + where the "..." should be filled in the with the device's ID. Disable 101 + the feature by writing 0 instead of 1. For hubs the feature is 102 + automatically and permanently enabled, so you only have to worry about 103 + setting it for devices where it really matters. 94 104 95 105 96 106 Is this the best solution?
+8 -5
drivers/usb/core/Kconfig
··· 91 91 depends on USB && PM && EXPERIMENTAL 92 92 default n 93 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 97 persistent across system suspend, even if the USB bus loses 96 - power. (This includes software-suspend, also known as swsusp, 97 - or suspend-to-disk.) The devices will reappear as if by magic 98 - when the system wakes up, with no need to unmount USB filesystems, 99 - rmmod host-controller drivers, or do anything else. 98 + power. (This includes hibernation, also known as swsusp or 99 + suspend-to-disk.) The devices will reappear as if by magic 100 + when the system wakes up, with no need to unmount USB 101 + filesystems, rmmod host-controller drivers, or do anything 102 + else. 100 103 101 104 WARNING: This option can be dangerous! 102 105
+29 -49
drivers/usb/core/hub.c
··· 596 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 599 /* caller has locked the hub device */ 615 600 static int hub_pre_reset(struct usb_interface *intf) 616 601 { 617 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 611 hub_quiesce(hub); 621 612 return 0; 622 613 } ··· 1863 1872 return 0; 1864 1873 } 1865 1874 1866 - #ifdef CONFIG_USB_PERSIST 1867 - 1868 - /* For "persistent-device" resets we must mark the child devices for reset 1869 - * and turn off a possible connect-change status (so khubd won't disconnect 1870 - * them later). 1871 - */ 1872 - static void mark_children_for_reset_resume(struct usb_hub *hub) 1875 + static int hub_reset_resume(struct usb_interface *intf) 1873 1876 { 1877 + struct usb_hub *hub = usb_get_intfdata(intf); 1874 1878 struct usb_device *hdev = hub->hdev; 1875 1879 int port1; 1880 + 1881 + hub_power_on(hub); 1876 1882 1877 1883 for (port1 = 1; port1 <= hdev->maxchild; ++port1) { 1878 1884 struct usb_device *child = hdev->children[port1-1]; 1879 1885 1880 1886 if (child) { 1881 - child->reset_resume = 1; 1882 - clear_port_feature(hdev, port1, 1883 - USB_PORT_FEAT_C_CONNECTION); 1887 + 1888 + /* For "USB_PERSIST"-enabled children we must 1889 + * mark the child device for reset-resume and 1890 + * turn off the connect-change status to prevent 1891 + * khubd from disconnecting it later. 1892 + */ 1893 + if (USB_PERSIST && child->persist_enabled) { 1894 + child->reset_resume = 1; 1895 + clear_port_feature(hdev, port1, 1896 + USB_PORT_FEAT_C_CONNECTION); 1897 + 1898 + /* Otherwise we must disconnect the child, 1899 + * but as we may not lock the child device here 1900 + * we have to do a "logical" disconnect. 1901 + */ 1902 + } else { 1903 + hub_port_logical_disconnect(hub, port1); 1904 + } 1884 1905 } 1885 1906 } 1886 - } 1887 1907 1888 - #else 1889 - 1890 - static inline void mark_children_for_reset_resume(struct usb_hub *hub) 1891 - { } 1892 - 1893 - #endif /* CONFIG_USB_PERSIST */ 1894 - 1895 - static int hub_reset_resume(struct usb_interface *intf) 1896 - { 1897 - struct usb_hub *hub = usb_get_intfdata(intf); 1898 - 1899 - hub_power_on(hub); 1900 - if (USB_PERSIST) 1901 - mark_children_for_reset_resume(hub); 1902 - else { 1903 - /* Reset-resume doesn't call pre_reset, so we have to 1904 - * disconnect the children here. But we may not lock 1905 - * the child devices, so we have to do a "logical" 1906 - * disconnect. 1907 - */ 1908 - disconnect_all_children(hub, 1); 1909 - } 1910 1908 hub_activate(hub); 1911 1909 return 0; 1912 1910 }
+73 -2
drivers/usb/core/sysfs.c
··· 169 169 } 170 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 239 #ifdef CONFIG_USB_SUSPEND 173 240 174 241 static ssize_t ··· 343 276 344 277 static DEVICE_ATTR(level, S_IRUGO | S_IWUSR, show_level, set_level); 345 278 346 - static char power_group[] = "power"; 347 - 348 279 static int add_power_attributes(struct device *dev) 349 280 { 350 281 int rc = 0; ··· 375 310 #define remove_power_attributes(dev) do {} while (0) 376 311 377 312 #endif /* CONFIG_USB_SUSPEND */ 313 + 378 314 379 315 /* Descriptor fields */ 380 316 #define usb_descriptor_attr_le16(field, format_string) \ ··· 450 384 if (retval) 451 385 return retval; 452 386 387 + retval = add_persist_attributes(dev); 388 + if (retval) 389 + goto error; 390 + 453 391 retval = add_power_attributes(dev); 454 392 if (retval) 455 393 goto error; ··· 491 421 device_remove_file(dev, &dev_attr_product); 492 422 device_remove_file(dev, &dev_attr_serial); 493 423 remove_power_attributes(dev); 424 + remove_persist_attributes(dev); 494 425 sysfs_remove_group(&dev->kobj, &dev_attr_grp); 495 426 } 496 427
+1
include/linux/usb.h
··· 404 404 unsigned auto_pm:1; /* autosuspend/resume in progress */ 405 405 unsigned do_remote_wakeup:1; /* remote wakeup should be enabled */ 406 406 unsigned reset_resume:1; /* needs reset instead of resume */ 407 + unsigned persist_enabled:1; /* USB_PERSIST enabled for this dev */ 407 408 unsigned autosuspend_disabled:1; /* autosuspend and autoresume */ 408 409 unsigned autoresume_disabled:1; /* disabled by the user */ 409 410 #endif