]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
usb: hub: Fix flushing of delayed work used for post resume purposes
authorMathias Nyman <mathias.nyman@linux.intel.com>
Fri, 27 Jun 2025 16:43:48 +0000 (19:43 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 28 Aug 2025 14:22:29 +0000 (16:22 +0200)
commit 9bd9c8026341f75f25c53104eb7e656e357ca1a2 upstream.

Delayed work that prevents USB3 hubs from runtime-suspending too early
needed to be flushed in hub_quiesce() to resolve issues detected on
QC SC8280XP CRD board during suspend resume testing.

This flushing did however trigger new issues on Raspberry Pi 3B+, which
doesn't have USB3 ports, and doesn't queue any post resume delayed work.

The flushed 'hub->init_work' item is used for several purposes, and
is originally initialized with a 'NULL' work function. The work function
is also changed on the fly, which may contribute to the issue.

Solve this by creating a dedicated delayed work item for post resume work,
and flush that delayed work in hub_quiesce()

Cc: stable <stable@kernel.org>
Fixes: a49e1e2e785f ("usb: hub: Fix flushing and scheduling of delayed work that tunes runtime pm")
Reported-by: Mark Brown <broonie@kernel.org>
Closes: https://lore.kernel.org/linux-usb/aF5rNp1l0LWITnEB@finisterre.sirena.org.uk
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Tested-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com> # SC8280XP CRD
Tested-by: Mark Brown <broonie@kernel.org>
Link: https://lore.kernel.org/r/20250627164348.3982628-2-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/core/hub.c
drivers/usb/core/hub.h

index 72c6e04ad4277e4eac996be1e9d19fb22318aac1..044a7f95f6dc0c023ceaade0737ba75497a9ab0b 100644 (file)
@@ -1031,12 +1031,11 @@ int usb_remove_device(struct usb_device *udev)
 
 enum hub_activation_type {
        HUB_INIT, HUB_INIT2, HUB_INIT3,         /* INITs must come first */
-       HUB_POST_RESET, HUB_RESUME, HUB_RESET_RESUME, HUB_POST_RESUME,
+       HUB_POST_RESET, HUB_RESUME, HUB_RESET_RESUME,
 };
 
 static void hub_init_func2(struct work_struct *ws);
 static void hub_init_func3(struct work_struct *ws);
-static void hub_post_resume(struct work_struct *ws);
 
 static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
 {
@@ -1060,12 +1059,6 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
                goto init3;
        }
 
-       if (type == HUB_POST_RESUME) {
-               usb_autopm_put_interface_async(to_usb_interface(hub->intfdev));
-               hub_put(hub);
-               return;
-       }
-
        hub_get(hub);
 
        /* The superspeed hub except for root hub has to use Hub Depth
@@ -1319,8 +1312,8 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
                usb_autopm_get_interface_no_resume(
                        to_usb_interface(hub->intfdev));
 
-               INIT_DELAYED_WORK(&hub->init_work, hub_post_resume);
-               queue_delayed_work(system_power_efficient_wq, &hub->init_work,
+               queue_delayed_work(system_power_efficient_wq,
+                                  &hub->post_resume_work,
                                   msecs_to_jiffies(USB_SS_PORT_U0_WAKE_TIME));
                return;
        }
@@ -1345,9 +1338,10 @@ static void hub_init_func3(struct work_struct *ws)
 
 static void hub_post_resume(struct work_struct *ws)
 {
-       struct usb_hub *hub = container_of(ws, struct usb_hub, init_work.work);
+       struct usb_hub *hub = container_of(ws, struct usb_hub, post_resume_work.work);
 
-       hub_activate(hub, HUB_POST_RESUME);
+       usb_autopm_put_interface_async(to_usb_interface(hub->intfdev));
+       hub_put(hub);
 }
 
 enum hub_quiescing_type {
@@ -1375,7 +1369,7 @@ static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type)
 
        /* Stop hub_wq and related activity */
        del_timer_sync(&hub->irq_urb_retry);
-       flush_delayed_work(&hub->init_work);
+       flush_delayed_work(&hub->post_resume_work);
        usb_kill_urb(hub->urb);
        if (hub->has_indicators)
                cancel_delayed_work_sync(&hub->leds);
@@ -1932,6 +1926,7 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
        hub->hdev = hdev;
        INIT_DELAYED_WORK(&hub->leds, led_work);
        INIT_DELAYED_WORK(&hub->init_work, NULL);
+       INIT_DELAYED_WORK(&hub->post_resume_work, hub_post_resume);
        INIT_WORK(&hub->events, hub_event);
        spin_lock_init(&hub->irq_urb_lock);
        timer_setup(&hub->irq_urb_retry, hub_retry_irq_urb, 0);
index dd049bc85f88c678e3b7a47086353f174cfe2a3c..9144e02dfe5c55f63e4fa2c2e642fb49b9cc6305 100644 (file)
@@ -69,6 +69,7 @@ struct usb_hub {
        u8                      indicator[USB_MAXCHILDREN];
        struct delayed_work     leds;
        struct delayed_work     init_work;
+       struct delayed_work     post_resume_work;
        struct work_struct      events;
        spinlock_t              irq_urb_lock;
        struct timer_list       irq_urb_retry;