summaryrefslogtreecommitdiff
path: root/drivers/usb/core
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2026-04-06 09:06:39 +0200
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2026-04-06 09:06:39 +0200
commit3723393cc284e09107c0f55c47458b75b29be2c3 (patch)
tree185380308f3c57ac61e339d73f530639e2241451 /drivers/usb/core
parent1df7a7652f032cf1abe1c102a031c2128e24c31d (diff)
parent591cd656a1bf5ea94a222af5ef2ee76df029c1d2 (diff)
downloadlwn-3723393cc284e09107c0f55c47458b75b29be2c3.tar.gz
lwn-3723393cc284e09107c0f55c47458b75b29be2c3.zip
Merge tag 'v7.0-rc7' into usb-next
We need the USB fixes in here to build on and for testing Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/core')
-rw-r--r--drivers/usb/core/driver.c23
-rw-r--r--drivers/usb/core/hcd.c2
-rw-r--r--drivers/usb/core/offload.c102
-rw-r--r--drivers/usb/core/phy.c12
-rw-r--r--drivers/usb/core/quirks.c3
-rw-r--r--drivers/usb/core/usb.c1
6 files changed, 89 insertions, 54 deletions
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index 2574e65bc640..f63004417058 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -1415,14 +1415,16 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
int status = 0;
int i = 0, n = 0;
struct usb_interface *intf;
+ bool offload_active = false;
if (udev->state == USB_STATE_NOTATTACHED ||
udev->state == USB_STATE_SUSPENDED)
goto done;
+ usb_offload_set_pm_locked(udev, true);
if (msg.event == PM_EVENT_SUSPEND && usb_offload_check(udev)) {
dev_dbg(&udev->dev, "device offloaded, skip suspend.\n");
- udev->offload_at_suspend = 1;
+ offload_active = true;
}
/* Suspend all the interfaces and then udev itself */
@@ -1436,8 +1438,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
* interrupt urbs, allowing interrupt events to be
* handled during system suspend.
*/
- if (udev->offload_at_suspend &&
- intf->needs_remote_wakeup) {
+ if (offload_active && intf->needs_remote_wakeup) {
dev_dbg(&intf->dev,
"device offloaded, skip suspend.\n");
continue;
@@ -1452,7 +1453,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
}
}
if (status == 0) {
- if (!udev->offload_at_suspend)
+ if (!offload_active)
status = usb_suspend_device(udev, msg);
/*
@@ -1498,7 +1499,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
*/
} else {
udev->can_submit = 0;
- if (!udev->offload_at_suspend) {
+ if (!offload_active) {
for (i = 0; i < 16; ++i) {
usb_hcd_flush_endpoint(udev, udev->ep_out[i]);
usb_hcd_flush_endpoint(udev, udev->ep_in[i]);
@@ -1507,6 +1508,8 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
}
done:
+ if (status != 0)
+ usb_offload_set_pm_locked(udev, false);
dev_vdbg(&udev->dev, "%s: status %d\n", __func__, status);
return status;
}
@@ -1536,16 +1539,19 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg)
int status = 0;
int i;
struct usb_interface *intf;
+ bool offload_active = false;
if (udev->state == USB_STATE_NOTATTACHED) {
status = -ENODEV;
goto done;
}
udev->can_submit = 1;
+ if (msg.event == PM_EVENT_RESUME)
+ offload_active = usb_offload_check(udev);
/* Resume the device */
if (udev->state == USB_STATE_SUSPENDED || udev->reset_resume) {
- if (!udev->offload_at_suspend)
+ if (!offload_active)
status = usb_resume_device(udev, msg);
else
dev_dbg(&udev->dev,
@@ -1562,8 +1568,7 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg)
* pending interrupt urbs, allowing interrupt events
* to be handled during system suspend.
*/
- if (udev->offload_at_suspend &&
- intf->needs_remote_wakeup) {
+ if (offload_active && intf->needs_remote_wakeup) {
dev_dbg(&intf->dev,
"device offloaded, skip resume.\n");
continue;
@@ -1572,11 +1577,11 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg)
udev->reset_resume);
}
}
- udev->offload_at_suspend = 0;
usb_mark_last_busy(udev);
done:
dev_vdbg(&udev->dev, "%s: status %d\n", __func__, status);
+ usb_offload_set_pm_locked(udev, false);
if (!status)
udev->reset_resume = 0;
return status;
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index dee842ea6931..89221f1ce769 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -2403,7 +2403,7 @@ void usb_hcd_resume_root_hub (struct usb_hcd *hcd)
if (hcd->rh_registered) {
pm_wakeup_event(&hcd->self.root_hub->dev, 0);
set_bit(HCD_FLAG_WAKEUP_PENDING, &hcd->flags);
- queue_work(pm_wq, &hcd->wakeup_work);
+ queue_work(system_freezable_wq, &hcd->wakeup_work);
}
spin_unlock_irqrestore (&hcd_root_hub_lock, flags);
}
diff --git a/drivers/usb/core/offload.c b/drivers/usb/core/offload.c
index 7c699f1b8d2b..9db3cfedd29c 100644
--- a/drivers/usb/core/offload.c
+++ b/drivers/usb/core/offload.c
@@ -25,33 +25,30 @@
*/
int usb_offload_get(struct usb_device *udev)
{
- int ret;
+ int ret = 0;
- usb_lock_device(udev);
- if (udev->state == USB_STATE_NOTATTACHED) {
- usb_unlock_device(udev);
+ if (!usb_get_dev(udev))
return -ENODEV;
- }
- if (udev->state == USB_STATE_SUSPENDED ||
- udev->offload_at_suspend) {
- usb_unlock_device(udev);
- return -EBUSY;
+ if (pm_runtime_get_if_active(&udev->dev) != 1) {
+ ret = -EBUSY;
+ goto err_rpm;
}
- /*
- * offload_usage could only be modified when the device is active, since
- * it will alter the suspend flow of the device.
- */
- ret = usb_autoresume_device(udev);
- if (ret < 0) {
- usb_unlock_device(udev);
- return ret;
+ spin_lock(&udev->offload_lock);
+
+ if (udev->offload_pm_locked) {
+ ret = -EAGAIN;
+ goto err;
}
udev->offload_usage++;
- usb_autosuspend_device(udev);
- usb_unlock_device(udev);
+
+err:
+ spin_unlock(&udev->offload_lock);
+ pm_runtime_put_autosuspend(&udev->dev);
+err_rpm:
+ usb_put_dev(udev);
return ret;
}
@@ -69,35 +66,32 @@ EXPORT_SYMBOL_GPL(usb_offload_get);
*/
int usb_offload_put(struct usb_device *udev)
{
- int ret;
+ int ret = 0;
- usb_lock_device(udev);
- if (udev->state == USB_STATE_NOTATTACHED) {
- usb_unlock_device(udev);
+ if (!usb_get_dev(udev))
return -ENODEV;
- }
- if (udev->state == USB_STATE_SUSPENDED ||
- udev->offload_at_suspend) {
- usb_unlock_device(udev);
- return -EBUSY;
+ if (pm_runtime_get_if_active(&udev->dev) != 1) {
+ ret = -EBUSY;
+ goto err_rpm;
}
- /*
- * offload_usage could only be modified when the device is active, since
- * it will alter the suspend flow of the device.
- */
- ret = usb_autoresume_device(udev);
- if (ret < 0) {
- usb_unlock_device(udev);
- return ret;
+ spin_lock(&udev->offload_lock);
+
+ if (udev->offload_pm_locked) {
+ ret = -EAGAIN;
+ goto err;
}
/* Drop the count when it wasn't 0, ignore the operation otherwise. */
if (udev->offload_usage)
udev->offload_usage--;
- usb_autosuspend_device(udev);
- usb_unlock_device(udev);
+
+err:
+ spin_unlock(&udev->offload_lock);
+ pm_runtime_put_autosuspend(&udev->dev);
+err_rpm:
+ usb_put_dev(udev);
return ret;
}
@@ -112,25 +106,47 @@ EXPORT_SYMBOL_GPL(usb_offload_put);
* management.
*
* The caller must hold @udev's device lock. In addition, the caller should
- * ensure downstream usb devices are all either suspended or marked as
- * "offload_at_suspend" to ensure the correctness of the return value.
+ * ensure the device itself and the downstream usb devices are all marked as
+ * "offload_pm_locked" to ensure the correctness of the return value.
*
* Returns true on any offload activity, false otherwise.
*/
bool usb_offload_check(struct usb_device *udev) __must_hold(&udev->dev->mutex)
{
struct usb_device *child;
- bool active;
+ bool active = false;
int port1;
+ if (udev->offload_usage)
+ return true;
+
usb_hub_for_each_child(udev, port1, child) {
usb_lock_device(child);
active = usb_offload_check(child);
usb_unlock_device(child);
+
if (active)
- return true;
+ break;
}
- return !!udev->offload_usage;
+ return active;
}
EXPORT_SYMBOL_GPL(usb_offload_check);
+
+/**
+ * usb_offload_set_pm_locked - set the PM lock state of a USB device
+ * @udev: the USB device to modify
+ * @locked: the new lock state
+ *
+ * Setting @locked to true prevents offload_usage from being modified. This
+ * ensures that offload activities cannot be started or stopped during critical
+ * power management transitions, maintaining a stable state for the duration
+ * of the transition.
+ */
+void usb_offload_set_pm_locked(struct usb_device *udev, bool locked)
+{
+ spin_lock(&udev->offload_lock);
+ udev->offload_pm_locked = locked;
+ spin_unlock(&udev->offload_lock);
+}
+EXPORT_SYMBOL_GPL(usb_offload_set_pm_locked);
diff --git a/drivers/usb/core/phy.c b/drivers/usb/core/phy.c
index 4bba1c275740..4d966cc9cdc9 100644
--- a/drivers/usb/core/phy.c
+++ b/drivers/usb/core/phy.c
@@ -114,7 +114,7 @@ EXPORT_SYMBOL_GPL(usb_phy_roothub_alloc);
struct usb_phy_roothub *usb_phy_roothub_alloc_usb3_phy(struct device *dev)
{
struct usb_phy_roothub *phy_roothub;
- int num_phys;
+ int num_phys, usb2_phy_index;
if (!IS_ENABLED(CONFIG_GENERIC_PHY))
return NULL;
@@ -124,6 +124,16 @@ struct usb_phy_roothub *usb_phy_roothub_alloc_usb3_phy(struct device *dev)
if (num_phys <= 0)
return NULL;
+ /*
+ * If 'usb2-phy' is not present, usb_phy_roothub_alloc() added
+ * all PHYs to the primary HCD's phy_roothub already, so skip
+ * adding 'usb3-phy' here to avoid double use of that.
+ */
+ usb2_phy_index = of_property_match_string(dev->of_node, "phy-names",
+ "usb2-phy");
+ if (usb2_phy_index < 0)
+ return NULL;
+
phy_roothub = devm_kzalloc(dev, sizeof(*phy_roothub), GFP_KERNEL);
if (!phy_roothub)
return ERR_PTR(-ENOMEM);
diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c
index 5523a8e29021..0ffdaefba508 100644
--- a/drivers/usb/core/quirks.c
+++ b/drivers/usb/core/quirks.c
@@ -401,6 +401,7 @@ static const struct usb_device_id usb_quirk_list[] = {
/* Silicon Motion Flash Drive */
{ USB_DEVICE(0x090c, 0x1000), .driver_info = USB_QUIRK_DELAY_INIT },
+ { USB_DEVICE(0x090c, 0x2000), .driver_info = USB_QUIRK_DELAY_INIT },
/* Sound Devices USBPre2 */
{ USB_DEVICE(0x0926, 0x0202), .driver_info =
@@ -492,6 +493,8 @@ static const struct usb_device_id usb_quirk_list[] = {
/* Razer - Razer Blade Keyboard */
{ USB_DEVICE(0x1532, 0x0116), .driver_info =
USB_QUIRK_LINEAR_UFRAME_INTR_BINTERVAL },
+ /* Razer - Razer Kiyo Pro Webcam */
+ { USB_DEVICE(0x1532, 0x0e05), .driver_info = USB_QUIRK_NO_LPM },
/* Lenovo ThinkPad OneLink+ Dock twin hub controllers (VIA Labs VL812) */
{ USB_DEVICE(0x17ef, 0x1018), .driver_info = USB_QUIRK_RESET_RESUME },
diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
index e9a10a33534c..df166cafe106 100644
--- a/drivers/usb/core/usb.c
+++ b/drivers/usb/core/usb.c
@@ -671,6 +671,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent,
set_dev_node(&dev->dev, dev_to_node(bus->sysdev));
dev->state = USB_STATE_ATTACHED;
dev->lpm_disable_count = 1;
+ spin_lock_init(&dev->offload_lock);
dev->offload_usage = 0;
atomic_set(&dev->urbnum, 0);