diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2007-05-04 11:52:20 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2007-07-12 19:29:47 -0400 |
commit | 0458d5b4c9cc4ca0f62625d0144ddc4b4bc97a3c (patch) | |
tree | 8b1fcb4f063ef4aa6f2e3cd41a60d986a1e432d4 /drivers/usb/core/hub.c | |
parent | ce7cd137fced114d49178b73d468b82096a107fb (diff) |
USB: add USB-Persist facility
This patch (as886) adds the controversial USB-persist facility,
allowing USB devices to persist across a power loss during system
suspend.
The facility is controlled by a new Kconfig option (with appropriate
warnings about the potential dangers); when the option is off the
behavior will remain the same as it is now. But when the option is
on, people will be able to use suspend-to-disk and keep their USB
filesystems intact -- something particularly valuable for small
machines where the root filesystem is on a USB device!
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/core/hub.c')
-rw-r--r-- | drivers/usb/core/hub.c | 196 |
1 files changed, 140 insertions, 56 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 77a6627b18d2..51d2d304568b 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c | |||
@@ -553,45 +553,121 @@ static int hub_hub_status(struct usb_hub *hub, | |||
553 | static int hub_port_disable(struct usb_hub *hub, int port1, int set_state) | 553 | static int hub_port_disable(struct usb_hub *hub, int port1, int set_state) |
554 | { | 554 | { |
555 | struct usb_device *hdev = hub->hdev; | 555 | struct usb_device *hdev = hub->hdev; |
556 | int ret; | 556 | int ret = 0; |
557 | 557 | ||
558 | if (hdev->children[port1-1] && set_state) { | 558 | if (hdev->children[port1-1] && set_state) |
559 | usb_set_device_state(hdev->children[port1-1], | 559 | usb_set_device_state(hdev->children[port1-1], |
560 | USB_STATE_NOTATTACHED); | 560 | USB_STATE_NOTATTACHED); |
561 | } | 561 | if (!hub->error) |
562 | ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE); | 562 | ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE); |
563 | if (ret) | 563 | if (ret) |
564 | dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n", | 564 | dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n", |
565 | port1, ret); | 565 | port1, ret); |
566 | |||
567 | return ret; | 566 | return ret; |
568 | } | 567 | } |
569 | 568 | ||
569 | /* | ||
570 | * Disable a port and mark a logical connnect-change event, so that some | ||
571 | * time later khubd will disconnect() any existing usb_device on the port | ||
572 | * and will re-enumerate if there actually is a device attached. | ||
573 | */ | ||
574 | static void hub_port_logical_disconnect(struct usb_hub *hub, int port1) | ||
575 | { | ||
576 | dev_dbg(hub->intfdev, "logical disconnect on port %d\n", port1); | ||
577 | hub_port_disable(hub, port1, 1); | ||
570 | 578 | ||
571 | /* caller has locked the hub device */ | 579 | /* FIXME let caller ask to power down the port: |
572 | static void hub_pre_reset(struct usb_interface *intf) | 580 | * - some devices won't enumerate without a VBUS power cycle |
581 | * - SRP saves power that way | ||
582 | * - ... new call, TBD ... | ||
583 | * That's easy if this hub can switch power per-port, and | ||
584 | * khubd reactivates the port later (timer, SRP, etc). | ||
585 | * Powerdown must be optional, because of reset/DFU. | ||
586 | */ | ||
587 | |||
588 | set_bit(port1, hub->change_bits); | ||
589 | kick_khubd(hub); | ||
590 | } | ||
591 | |||
592 | static void disconnect_all_children(struct usb_hub *hub, int logical) | ||
573 | { | 593 | { |
574 | struct usb_hub *hub = usb_get_intfdata(intf); | ||
575 | struct usb_device *hdev = hub->hdev; | 594 | struct usb_device *hdev = hub->hdev; |
576 | int port1; | 595 | int port1; |
577 | 596 | ||
578 | for (port1 = 1; port1 <= hdev->maxchild; ++port1) { | 597 | for (port1 = 1; port1 <= hdev->maxchild; ++port1) { |
579 | if (hdev->children[port1 - 1]) { | 598 | if (hdev->children[port1-1]) { |
580 | usb_disconnect(&hdev->children[port1 - 1]); | 599 | if (logical) |
581 | if (hub->error == 0) | 600 | hub_port_logical_disconnect(hub, port1); |
582 | hub_port_disable(hub, port1, 0); | 601 | else |
602 | usb_disconnect(&hdev->children[port1-1]); | ||
603 | } | ||
604 | } | ||
605 | } | ||
606 | |||
607 | #ifdef CONFIG_USB_PERSIST | ||
608 | |||
609 | #define USB_PERSIST 1 | ||
610 | |||
611 | /* For "persistent-device" resets we must mark the child devices for reset | ||
612 | * and turn off a possible connect-change status (so khubd won't disconnect | ||
613 | * them later). | ||
614 | */ | ||
615 | static void mark_children_for_reset_resume(struct usb_hub *hub) | ||
616 | { | ||
617 | struct usb_device *hdev = hub->hdev; | ||
618 | int port1; | ||
619 | |||
620 | for (port1 = 1; port1 <= hdev->maxchild; ++port1) { | ||
621 | struct usb_device *child = hdev->children[port1-1]; | ||
622 | |||
623 | if (child) { | ||
624 | child->reset_resume = 1; | ||
625 | clear_port_feature(hdev, port1, | ||
626 | USB_PORT_FEAT_C_CONNECTION); | ||
583 | } | 627 | } |
584 | } | 628 | } |
629 | } | ||
630 | |||
631 | #else | ||
632 | |||
633 | #define USB_PERSIST 0 | ||
634 | |||
635 | static inline void mark_children_for_reset_resume(struct usb_hub *hub) | ||
636 | { } | ||
637 | |||
638 | #endif /* CONFIG_USB_PERSIST */ | ||
639 | |||
640 | /* caller has locked the hub device */ | ||
641 | static void hub_pre_reset(struct usb_interface *intf) | ||
642 | { | ||
643 | struct usb_hub *hub = usb_get_intfdata(intf); | ||
644 | |||
645 | /* This routine doesn't run as part of a reset-resume, so it's safe | ||
646 | * to disconnect all the drivers below the hub. | ||
647 | */ | ||
648 | disconnect_all_children(hub, 0); | ||
585 | hub_quiesce(hub); | 649 | hub_quiesce(hub); |
586 | } | 650 | } |
587 | 651 | ||
588 | /* caller has locked the hub device */ | 652 | /* caller has locked the hub device */ |
589 | static void hub_post_reset(struct usb_interface *intf) | 653 | static void hub_post_reset(struct usb_interface *intf, int reset_resume) |
590 | { | 654 | { |
591 | struct usb_hub *hub = usb_get_intfdata(intf); | 655 | struct usb_hub *hub = usb_get_intfdata(intf); |
592 | 656 | ||
593 | hub_activate(hub); | ||
594 | hub_power_on(hub); | 657 | hub_power_on(hub); |
658 | if (reset_resume) { | ||
659 | if (USB_PERSIST) | ||
660 | mark_children_for_reset_resume(hub); | ||
661 | else { | ||
662 | /* Reset-resume doesn't call pre_reset, so we have to | ||
663 | * disconnect the children here. But we may not lock | ||
664 | * the child devices, so we have to do a "logical" | ||
665 | * disconnect. | ||
666 | */ | ||
667 | disconnect_all_children(hub, 1); | ||
668 | } | ||
669 | } | ||
670 | hub_activate(hub); | ||
595 | } | 671 | } |
596 | 672 | ||
597 | 673 | ||
@@ -1054,32 +1130,63 @@ void usb_set_device_state(struct usb_device *udev, | |||
1054 | #ifdef CONFIG_PM | 1130 | #ifdef CONFIG_PM |
1055 | 1131 | ||
1056 | /** | 1132 | /** |
1133 | * usb_reset_suspended_device - reset a suspended device instead of resuming it | ||
1134 | * @udev: device to be reset instead of resumed | ||
1135 | * | ||
1136 | * If a host controller doesn't maintain VBUS suspend current during a | ||
1137 | * system sleep or is reset when the system wakes up, all the USB | ||
1138 | * power sessions below it will be broken. This is especially troublesome | ||
1139 | * for mass-storage devices containing mounted filesystems, since the | ||
1140 | * device will appear to have disconnected and all the memory mappings | ||
1141 | * to it will be lost. | ||
1142 | * | ||
1143 | * As an alternative, this routine attempts to recover power sessions for | ||
1144 | * devices that are still present by resetting them instead of resuming | ||
1145 | * them. If all goes well, the devices will appear to persist across the | ||
1146 | * the interruption of the power sessions. | ||
1147 | * | ||
1148 | * This facility is inherently dangerous. Although usb_reset_device() | ||
1149 | * makes every effort to insure that the same device is present after the | ||
1150 | * reset as before, it cannot provide a 100% guarantee. Furthermore it's | ||
1151 | * quite possible for a device to remain unaltered but its media to be | ||
1152 | * changed. If the user replaces a flash memory card while the system is | ||
1153 | * asleep, he will have only himself to blame when the filesystem on the | ||
1154 | * new card is corrupted and the system crashes. | ||
1155 | */ | ||
1156 | int usb_reset_suspended_device(struct usb_device *udev) | ||
1157 | { | ||
1158 | int rc = 0; | ||
1159 | |||
1160 | dev_dbg(&udev->dev, "usb %sresume\n", "reset-"); | ||
1161 | |||
1162 | /* After we're done the device won't be suspended any more. | ||
1163 | * In addition, the reset won't work if udev->state is SUSPENDED. | ||
1164 | */ | ||
1165 | usb_set_device_state(udev, udev->actconfig | ||
1166 | ? USB_STATE_CONFIGURED | ||
1167 | : USB_STATE_ADDRESS); | ||
1168 | |||
1169 | /* Root hubs don't need to be (and can't be) reset */ | ||
1170 | if (udev->parent) | ||
1171 | rc = usb_reset_device(udev); | ||
1172 | return rc; | ||
1173 | } | ||
1174 | |||
1175 | /** | ||
1057 | * usb_root_hub_lost_power - called by HCD if the root hub lost Vbus power | 1176 | * usb_root_hub_lost_power - called by HCD if the root hub lost Vbus power |
1058 | * @rhdev: struct usb_device for the root hub | 1177 | * @rhdev: struct usb_device for the root hub |
1059 | * | 1178 | * |
1060 | * The USB host controller driver calls this function when its root hub | 1179 | * The USB host controller driver calls this function when its root hub |
1061 | * is resumed and Vbus power has been interrupted or the controller | 1180 | * is resumed and Vbus power has been interrupted or the controller |
1062 | * has been reset. The routine marks all the children of the root hub | 1181 | * has been reset. The routine marks @rhdev as having lost power. When |
1063 | * as NOTATTACHED and marks logical connect-change events on their ports. | 1182 | * the hub driver is resumed it will take notice; if CONFIG_USB_PERSIST |
1183 | * is enabled then it will carry out power-session recovery, otherwise | ||
1184 | * it will disconnect all the child devices. | ||
1064 | */ | 1185 | */ |
1065 | void usb_root_hub_lost_power(struct usb_device *rhdev) | 1186 | void usb_root_hub_lost_power(struct usb_device *rhdev) |
1066 | { | 1187 | { |
1067 | struct usb_hub *hub; | ||
1068 | int port1; | ||
1069 | unsigned long flags; | ||
1070 | |||
1071 | dev_warn(&rhdev->dev, "root hub lost power or was reset\n"); | 1188 | dev_warn(&rhdev->dev, "root hub lost power or was reset\n"); |
1072 | 1189 | rhdev->reset_resume = 1; | |
1073 | spin_lock_irqsave(&device_state_lock, flags); | ||
1074 | hub = hdev_to_hub(rhdev); | ||
1075 | for (port1 = 1; port1 <= rhdev->maxchild; ++port1) { | ||
1076 | if (rhdev->children[port1 - 1]) { | ||
1077 | recursively_mark_NOTATTACHED( | ||
1078 | rhdev->children[port1 - 1]); | ||
1079 | set_bit(port1, hub->change_bits); | ||
1080 | } | ||
1081 | } | ||
1082 | spin_unlock_irqrestore(&device_state_lock, flags); | ||
1083 | } | 1190 | } |
1084 | EXPORT_SYMBOL_GPL(usb_root_hub_lost_power); | 1191 | EXPORT_SYMBOL_GPL(usb_root_hub_lost_power); |
1085 | 1192 | ||
@@ -1513,29 +1620,6 @@ static int hub_port_reset(struct usb_hub *hub, int port1, | |||
1513 | return status; | 1620 | return status; |
1514 | } | 1621 | } |
1515 | 1622 | ||
1516 | /* | ||
1517 | * Disable a port and mark a logical connnect-change event, so that some | ||
1518 | * time later khubd will disconnect() any existing usb_device on the port | ||
1519 | * and will re-enumerate if there actually is a device attached. | ||
1520 | */ | ||
1521 | static void hub_port_logical_disconnect(struct usb_hub *hub, int port1) | ||
1522 | { | ||
1523 | dev_dbg(hub->intfdev, "logical disconnect on port %d\n", port1); | ||
1524 | hub_port_disable(hub, port1, 1); | ||
1525 | |||
1526 | /* FIXME let caller ask to power down the port: | ||
1527 | * - some devices won't enumerate without a VBUS power cycle | ||
1528 | * - SRP saves power that way | ||
1529 | * - ... new call, TBD ... | ||
1530 | * That's easy if this hub can switch power per-port, and | ||
1531 | * khubd reactivates the port later (timer, SRP, etc). | ||
1532 | * Powerdown must be optional, because of reset/DFU. | ||
1533 | */ | ||
1534 | |||
1535 | set_bit(port1, hub->change_bits); | ||
1536 | kick_khubd(hub); | ||
1537 | } | ||
1538 | |||
1539 | #ifdef CONFIG_PM | 1623 | #ifdef CONFIG_PM |
1540 | 1624 | ||
1541 | #ifdef CONFIG_USB_SUSPEND | 1625 | #ifdef CONFIG_USB_SUSPEND |
@@ -3018,7 +3102,7 @@ int usb_reset_composite_device(struct usb_device *udev, | |||
3018 | cintf->dev.driver) { | 3102 | cintf->dev.driver) { |
3019 | drv = to_usb_driver(cintf->dev.driver); | 3103 | drv = to_usb_driver(cintf->dev.driver); |
3020 | if (drv->post_reset) | 3104 | if (drv->post_reset) |
3021 | (drv->post_reset)(cintf); | 3105 | (drv->post_reset)(cintf, 0); |
3022 | } | 3106 | } |
3023 | if (cintf != iface) | 3107 | if (cintf != iface) |
3024 | up(&cintf->dev.sem); | 3108 | up(&cintf->dev.sem); |