usb: hub: Fix flushing of delayed work used for post resume purposes

JIRA: https://issues.redhat.com/browse/RHEL-100939

commit 9bd9c8026341f75f25c53104eb7e656e357ca1a2
Author: Mathias Nyman <mathias.nyman@linux.intel.com>
Date: Fri, 27 Jun 2025 19:43:48 +0300

  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>

Signed-off-by: Desnes Nunes <desnesn@redhat.com>
This commit is contained in:
Desnes Nunes 2025-07-10 11:16:55 -03:00
parent 915a7dc4b1
commit ce7fcf91eb
2 changed files with 9 additions and 13 deletions

View File

@ -1074,12 +1074,11 @@ int usb_remove_device(struct usb_device *udev)
enum hub_activation_type { enum hub_activation_type {
HUB_INIT, HUB_INIT2, HUB_INIT3, /* INITs must come first */ 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_func2(struct work_struct *ws);
static void hub_init_func3(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) static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
{ {
@ -1103,12 +1102,6 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
goto init3; goto init3;
} }
if (type == HUB_POST_RESUME) {
usb_autopm_put_interface_async(to_usb_interface(hub->intfdev));
hub_put(hub);
return;
}
hub_get(hub); hub_get(hub);
/* The superspeed hub except for root hub has to use Hub Depth /* The superspeed hub except for root hub has to use Hub Depth
@ -1362,8 +1355,8 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
usb_autopm_get_interface_no_resume( usb_autopm_get_interface_no_resume(
to_usb_interface(hub->intfdev)); to_usb_interface(hub->intfdev));
INIT_DELAYED_WORK(&hub->init_work, hub_post_resume); queue_delayed_work(system_power_efficient_wq,
queue_delayed_work(system_power_efficient_wq, &hub->init_work, &hub->post_resume_work,
msecs_to_jiffies(USB_SS_PORT_U0_WAKE_TIME)); msecs_to_jiffies(USB_SS_PORT_U0_WAKE_TIME));
return; return;
} }
@ -1388,9 +1381,10 @@ static void hub_init_func3(struct work_struct *ws)
static void hub_post_resume(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 { enum hub_quiescing_type {
@ -1418,7 +1412,7 @@ static void hub_quiesce(struct usb_hub *hub, enum hub_quiescing_type type)
/* Stop hub_wq and related activity */ /* Stop hub_wq and related activity */
timer_delete_sync(&hub->irq_urb_retry); timer_delete_sync(&hub->irq_urb_retry);
flush_delayed_work(&hub->init_work); flush_delayed_work(&hub->post_resume_work);
usb_kill_urb(hub->urb); usb_kill_urb(hub->urb);
if (hub->has_indicators) if (hub->has_indicators)
cancel_delayed_work_sync(&hub->leds); cancel_delayed_work_sync(&hub->leds);
@ -1977,6 +1971,7 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
hub->hdev = hdev; hub->hdev = hdev;
INIT_DELAYED_WORK(&hub->leds, led_work); INIT_DELAYED_WORK(&hub->leds, led_work);
INIT_DELAYED_WORK(&hub->init_work, NULL); INIT_DELAYED_WORK(&hub->init_work, NULL);
INIT_DELAYED_WORK(&hub->post_resume_work, hub_post_resume);
INIT_WORK(&hub->events, hub_event); INIT_WORK(&hub->events, hub_event);
INIT_LIST_HEAD(&hub->onboard_devs); INIT_LIST_HEAD(&hub->onboard_devs);
spin_lock_init(&hub->irq_urb_lock); spin_lock_init(&hub->irq_urb_lock);

View File

@ -70,6 +70,7 @@ struct usb_hub {
u8 indicator[USB_MAXCHILDREN]; u8 indicator[USB_MAXCHILDREN];
struct delayed_work leds; struct delayed_work leds;
struct delayed_work init_work; struct delayed_work init_work;
struct delayed_work post_resume_work;
struct work_struct events; struct work_struct events;
spinlock_t irq_urb_lock; spinlock_t irq_urb_lock;
struct timer_list irq_urb_retry; struct timer_list irq_urb_retry;