2020-02-27 09:52:57

by Marco Felsch

[permalink] [raw]
Subject: [RFC PATCH] USB: hub: fix port suspend/resume

At the momemnt the usb-port driver has only runime_pm hooks.
Suspending the port and turn off the VBUS supply should be triggered by
the hub device suspend callback usb_port_suspend() which calls the
pm_runtime_put_sync() if all pre-conditions are meet. This mechanism
don't work correctly due to the global PM behaviour, for more information
see [1]. According [1] I added the suspend/resume callbacks for the port
device to fix this. While on it I replaced the #ifdef's by
__maybe_unused.

[1] https://www.spinics.net/lists/linux-usb/msg190537.html

Signed-off-by: Marco Felsch <[email protected]>
---
drivers/usb/core/hub.c | 13 -------------
drivers/usb/core/port.c | 39 +++++++++++++++++++++++++++++++--------
2 files changed, 31 insertions(+), 21 deletions(-)

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 3405b146edc9..c294484e478d 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -3323,10 +3323,6 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
usb_set_device_state(udev, USB_STATE_SUSPENDED);
}

- if (status == 0 && !udev->do_remote_wakeup && udev->persist_enabled
- && test_and_clear_bit(port1, hub->child_usage_bits))
- pm_runtime_put_sync(&port_dev->dev);
-
usb_mark_last_busy(hub->hdev);

usb_unlock_port(port_dev);
@@ -3514,15 +3510,6 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
int status;
u16 portchange, portstatus;

- if (!test_and_set_bit(port1, hub->child_usage_bits)) {
- status = pm_runtime_get_sync(&port_dev->dev);
- if (status < 0) {
- dev_dbg(&udev->dev, "can't resume usb port, status %d\n",
- status);
- return status;
- }
- }
-
usb_lock_port(port_dev);

/* Skip the initial Clear-Suspend step for a remote wakeup */
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index bbbb35fa639f..9efa6b2ef31b 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -187,8 +187,7 @@ static void usb_port_device_release(struct device *dev)
kfree(port_dev);
}

-#ifdef CONFIG_PM
-static int usb_port_runtime_resume(struct device *dev)
+static int __maybe_unused usb_port_runtime_resume(struct device *dev)
{
struct usb_port *port_dev = to_usb_port(dev);
struct usb_device *hdev = to_usb_device(dev->parent->parent);
@@ -244,7 +243,7 @@ static int usb_port_runtime_resume(struct device *dev)
return retval;
}

-static int usb_port_runtime_suspend(struct device *dev)
+static int __maybe_unused usb_port_runtime_suspend(struct device *dev)
{
struct usb_port *port_dev = to_usb_port(dev);
struct usb_device *hdev = to_usb_device(dev->parent->parent);
@@ -283,7 +282,33 @@ static int usb_port_runtime_suspend(struct device *dev)

return retval;
}
-#endif
+
+static int __maybe_unused _usb_port_suspend(struct device *dev)
+{
+ struct usb_port *port_dev = to_usb_port(dev);
+ struct usb_device *udev = port_dev->child;
+ int retval;
+
+ if (!udev->do_remote_wakeup && udev->persist_enabled)
+ retval = usb_port_runtime_suspend(dev);
+
+ /* Do not force the user to enable the power-off feature */
+ if (retval && retval != -EAGAIN)
+ return retval;
+
+ return 0;
+}
+
+static int __maybe_unused _usb_port_resume(struct device *dev)
+{
+ struct usb_port *port_dev = to_usb_port(dev);
+ struct usb_device *udev = port_dev->child;
+
+ if (!udev->do_remote_wakeup && udev->persist_enabled)
+ return usb_port_runtime_resume(dev);
+
+ return 0;
+}

static void usb_port_shutdown(struct device *dev)
{
@@ -294,10 +319,8 @@ static void usb_port_shutdown(struct device *dev)
}

static const struct dev_pm_ops usb_port_pm_ops = {
-#ifdef CONFIG_PM
- .runtime_suspend = usb_port_runtime_suspend,
- .runtime_resume = usb_port_runtime_resume,
-#endif
+ SET_SYSTEM_SLEEP_PM_OPS(_usb_port_suspend, _usb_port_resume)
+ SET_RUNTIME_PM_OPS(usb_port_runtime_suspend, usb_port_runtime_resume, NULL)
};

struct device_type usb_port_device_type = {
--
2.20.1