Commit b01b03f3 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

USB: add new routine for checking port-resume type

This patch (as1070) creates a new subroutine to check whether a device
can be resumed.  This code is needed even when CONFIG_USB_SUSPEND
isn't set, because devices do suspend themselves when the root hub
(and hence the entire bus) is suspended, and power sessions can get
lost during a system sleep even without individual port suspends.

The patch also fixes a loose end in USB-Persist reset-resume handling.
When a low- or full-speed device is attached to an EHCI's companion
controller, the port handoff during resume will cause the companion
port's connect-status-change feature to be set.  If that flag isn't
cleared, the port-reset code will think it indicates that the device
has been unplugged and the reset-resume will fail.
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent bd2c7845
...@@ -1821,6 +1821,45 @@ static int hub_port_reset(struct usb_hub *hub, int port1, ...@@ -1821,6 +1821,45 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
#ifdef CONFIG_PM #ifdef CONFIG_PM
#define MASK_BITS (USB_PORT_STAT_POWER | USB_PORT_STAT_CONNECTION | \
USB_PORT_STAT_SUSPEND)
#define WANT_BITS (USB_PORT_STAT_POWER | USB_PORT_STAT_CONNECTION)
/* Determine whether the device on a port is ready for a normal resume,
* is ready for a reset-resume, or should be disconnected.
*/
static int check_port_resume_type(struct usb_device *udev,
struct usb_hub *hub, int port1,
int status, unsigned portchange, unsigned portstatus)
{
/* Is the device still present? */
if (status || (portstatus & MASK_BITS) != WANT_BITS) {
if (status >= 0)
status = -ENODEV;
}
/* Can't do a normal resume if the port isn't enabled */
else if (!(portstatus & USB_PORT_STAT_ENABLE) && !udev->reset_resume)
status = -ENODEV;
if (status) {
dev_dbg(hub->intfdev,
"port %d status %04x.%04x after resume, %d\n",
port1, portchange, portstatus, status);
} else if (udev->reset_resume) {
/* Late port handoff can set status-change bits */
if (portchange & USB_PORT_STAT_C_CONNECTION)
clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_CONNECTION);
if (portchange & USB_PORT_STAT_C_ENABLE)
clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_ENABLE);
}
return status;
}
#ifdef CONFIG_USB_SUSPEND #ifdef CONFIG_USB_SUSPEND
/* /*
...@@ -2025,7 +2064,6 @@ int usb_port_resume(struct usb_device *udev) ...@@ -2025,7 +2064,6 @@ int usb_port_resume(struct usb_device *udev)
int port1 = udev->portnum; int port1 = udev->portnum;
int status; int status;
u16 portchange, portstatus; u16 portchange, portstatus;
unsigned mask_flags, want_flags;
/* Skip the initial Clear-Suspend step for a remote wakeup */ /* Skip the initial Clear-Suspend step for a remote wakeup */
status = hub_port_status(hub, port1, &portstatus, &portchange); status = hub_port_status(hub, port1, &portstatus, &portchange);
...@@ -2054,35 +2092,23 @@ int usb_port_resume(struct usb_device *udev) ...@@ -2054,35 +2092,23 @@ int usb_port_resume(struct usb_device *udev)
*/ */
status = hub_port_status(hub, port1, &portstatus, &portchange); status = hub_port_status(hub, port1, &portstatus, &portchange);
SuspendCleared: /* TRSMRCY = 10 msec */
if (udev->reset_resume) msleep(10);
want_flags = USB_PORT_STAT_POWER }
| USB_PORT_STAT_CONNECTION;
else
want_flags = USB_PORT_STAT_POWER
| USB_PORT_STAT_CONNECTION
| USB_PORT_STAT_ENABLE;
mask_flags = want_flags | USB_PORT_STAT_SUSPEND;
if (status < 0 || (portstatus & mask_flags) != want_flags) { SuspendCleared:
dev_dbg(hub->intfdev, if (status == 0) {
"port %d status %04x.%04x after resume, %d\n",
port1, portchange, portstatus, status);
if (status >= 0)
status = -ENODEV;
} else {
if (portchange & USB_PORT_STAT_C_SUSPEND) if (portchange & USB_PORT_STAT_C_SUSPEND)
clear_port_feature(hub->hdev, port1, clear_port_feature(hub->hdev, port1,
USB_PORT_FEAT_C_SUSPEND); USB_PORT_FEAT_C_SUSPEND);
/* TRSMRCY = 10 msec */
msleep(10);
}
} }
clear_bit(port1, hub->busy_bits); clear_bit(port1, hub->busy_bits);
if (!hub->hdev->parent && !hub->busy_bits[0]) if (!hub->hdev->parent && !hub->busy_bits[0])
usb_enable_root_hub_irq(hub->hdev->bus); usb_enable_root_hub_irq(hub->hdev->bus);
status = check_port_resume_type(udev,
hub, port1, status, portchange, portstatus);
if (status == 0) if (status == 0)
status = finish_port_resume(udev); status = finish_port_resume(udev);
if (status < 0) { if (status < 0) {
...@@ -2115,12 +2141,23 @@ int usb_port_suspend(struct usb_device *udev) ...@@ -2115,12 +2141,23 @@ int usb_port_suspend(struct usb_device *udev)
return 0; return 0;
} }
/* However we may need to do a reset-resume */
int usb_port_resume(struct usb_device *udev) int usb_port_resume(struct usb_device *udev)
{ {
int status = 0; struct usb_hub *hub = hdev_to_hub(udev->parent);
int port1 = udev->portnum;
int status;
u16 portchange, portstatus;
status = hub_port_status(hub, port1, &portstatus, &portchange);
status = check_port_resume_type(udev,
hub, port1, status, portchange, portstatus);
/* However we may need to do a reset-resume */ if (status) {
if (udev->reset_resume) { dev_dbg(&udev->dev, "can't resume, status %d\n", status);
hub_port_logical_disconnect(hub, port1);
} else if (udev->reset_resume) {
dev_dbg(&udev->dev, "reset-resume\n"); dev_dbg(&udev->dev, "reset-resume\n");
status = usb_reset_device(udev); status = usb_reset_device(udev);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment