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

USB: make USB-PERSIST work after every system sleep

This patch (as1046) makes USB-PERSIST work more in accordance with
the documentation. Currently it takes effect only in cases where the
root hub has lost power or been reset, but it is supposed to operate
whenever a power session was dropped during a system sleep.

A new hub_restart() routine carries out the duties required during a
reset or a reset-resume. It checks to see whether occupied ports are
still enabled, and if they aren't then it clears the enable-change and
connect-change features (to prevent interference by khubd) and sets
the child device's reset_resume flag. It also checks ports that are
supposed to be unoccupied to verify that the firmware hasn't left the
port in an enabled state.

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
5e6effae 3eb14915

+80 -34
+80 -34
drivers/usb/core/hub.c
··· 644 644 hub_quiesce(hub); 645 645 } 646 646 647 + #define HUB_RESET 1 648 + #define HUB_RESUME 2 649 + #define HUB_RESET_RESUME 3 650 + 651 + #ifdef CONFIG_PM 652 + 653 + static void hub_restart(struct usb_hub *hub, int type) 654 + { 655 + struct usb_device *hdev = hub->hdev; 656 + int port1; 657 + 658 + /* Check each of the children to see if they require 659 + * USB-PERSIST handling or disconnection. Also check 660 + * each unoccupied port to make sure it is still disabled. 661 + */ 662 + for (port1 = 1; port1 <= hdev->maxchild; ++port1) { 663 + struct usb_device *udev = hdev->children[port1-1]; 664 + int status = 0; 665 + u16 portstatus, portchange; 666 + 667 + if (!udev || udev->state == USB_STATE_NOTATTACHED) { 668 + if (type != HUB_RESET) { 669 + status = hub_port_status(hub, port1, 670 + &portstatus, &portchange); 671 + if (status == 0 && (portstatus & 672 + USB_PORT_STAT_ENABLE)) 673 + clear_port_feature(hdev, port1, 674 + USB_PORT_FEAT_ENABLE); 675 + } 676 + continue; 677 + } 678 + 679 + /* Was the power session lost while we were suspended? */ 680 + switch (type) { 681 + case HUB_RESET_RESUME: 682 + portstatus = 0; 683 + portchange = USB_PORT_STAT_C_CONNECTION; 684 + break; 685 + 686 + case HUB_RESET: 687 + case HUB_RESUME: 688 + status = hub_port_status(hub, port1, 689 + &portstatus, &portchange); 690 + break; 691 + } 692 + 693 + /* For "USB_PERSIST"-enabled children we must 694 + * mark the child device for reset-resume and 695 + * turn off the various status changes to prevent 696 + * khubd from disconnecting it later. 697 + */ 698 + if (USB_PERSIST && udev->persist_enabled && status == 0 && 699 + !(portstatus & USB_PORT_STAT_ENABLE)) { 700 + if (portchange & USB_PORT_STAT_C_ENABLE) 701 + clear_port_feature(hub->hdev, port1, 702 + USB_PORT_FEAT_C_ENABLE); 703 + if (portchange & USB_PORT_STAT_C_CONNECTION) 704 + clear_port_feature(hub->hdev, port1, 705 + USB_PORT_FEAT_C_CONNECTION); 706 + udev->reset_resume = 1; 707 + } 708 + 709 + /* Otherwise for a reset_resume we must disconnect the child, 710 + * but as we may not lock the child device here 711 + * we have to do a "logical" disconnect. 712 + */ 713 + else if (type == HUB_RESET_RESUME) 714 + hub_port_logical_disconnect(hub, port1); 715 + } 716 + 717 + hub_activate(hub); 718 + } 719 + 720 + #endif /* CONFIG_PM */ 721 + 647 722 /* caller has locked the hub device */ 648 723 static int hub_pre_reset(struct usb_interface *intf) 649 724 { ··· 2090 2015 2091 2016 static int hub_resume(struct usb_interface *intf) 2092 2017 { 2093 - struct usb_hub *hub = usb_get_intfdata (intf); 2018 + struct usb_hub *hub = usb_get_intfdata(intf); 2094 2019 2095 - dev_dbg(&intf->dev, "%s\n", __FUNCTION__); 2096 - 2097 - /* tell khubd to look for changes on this hub */ 2098 - hub_activate(hub); 2020 + dev_dbg(&intf->dev, "%s\n", __func__); 2021 + hub_restart(hub, HUB_RESUME); 2099 2022 return 0; 2100 2023 } 2101 2024 2102 2025 static int hub_reset_resume(struct usb_interface *intf) 2103 2026 { 2104 2027 struct usb_hub *hub = usb_get_intfdata(intf); 2105 - struct usb_device *hdev = hub->hdev; 2106 - int port1; 2107 2028 2029 + dev_dbg(&intf->dev, "%s\n", __func__); 2108 2030 hub_power_on(hub); 2109 - 2110 - for (port1 = 1; port1 <= hdev->maxchild; ++port1) { 2111 - struct usb_device *child = hdev->children[port1-1]; 2112 - 2113 - if (child) { 2114 - 2115 - /* For "USB_PERSIST"-enabled children we must 2116 - * mark the child device for reset-resume and 2117 - * turn off the connect-change status to prevent 2118 - * khubd from disconnecting it later. 2119 - */ 2120 - if (USB_PERSIST && child->persist_enabled) { 2121 - child->reset_resume = 1; 2122 - clear_port_feature(hdev, port1, 2123 - USB_PORT_FEAT_C_CONNECTION); 2124 - 2125 - /* Otherwise we must disconnect the child, 2126 - * but as we may not lock the child device here 2127 - * we have to do a "logical" disconnect. 2128 - */ 2129 - } else { 2130 - hub_port_logical_disconnect(hub, port1); 2131 - } 2132 - } 2133 - } 2134 - 2135 - hub_activate(hub); 2031 + hub_restart(hub, HUB_RESET_RESUME); 2136 2032 return 0; 2137 2033 } 2138 2034