Skip to content

Commit 32a6958

Browse files
pmladekgregkh
authored andcommitted
usb: hub: convert khubd into workqueue
There is no need to have separate kthread for handling USB hub events. It is more elegant to use the workqueue framework. The workqueue is allocated as freezable because the original thread was freezable as well. Also it is allocated as ordered because the code is not ready for parallel processing of hub events, see choose_devnum(). struct usb_hub is passed via the work item. Therefore we do not need hub_event_list. Also hub_thread() is not longer needed. It would call only hub_event(). The rest of the code did manipulate the kthread and it is handled by the workqueue framework now. kick_khubd is renamed to kick_hub_wq() to make the function clear. And the protection against races is done another way, see below. hub_event_lock has been removed. It cannot longer be used to protect struct usb_hub between hub_event() and hub_disconnect(). Instead we need to get hub->kref already in kick_hub_wq(). The lock is not really needed for the other scenarios as well. queue_work() returns whether it succeeded. We could revert the needed operations accordingly. This is enough to avoid duplicity and inconsistencies. Yes, the removed lock causes that there is not longer such a strong synchronization between scheduling the work and manipulating hub->disconnected. But kick_hub_wq() must never be called together with hub_disconnect() otherwise even the original code would have failed. Any callers are responsible for this. Therefore the only problem is that hub_disconnect() could be called in parallel with hub_event(). But this was possible even in the past. struct usb_hub is still guarded by hub->kref and released in hub_events() when needed. Note that the source file is still full of the obsolete "khubd" strings. Let's remove them in a follow up patch. This patch already is complex enough. Thanks a lot Alan Stern <[email protected]> for code review, many useful tips and guidance. Also thanks to Tejun Heo <[email protected]> for hints how to allocate the workqueue. Signed-off-by: Petr Mladek <[email protected]> Acked-by: Alan Stern <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent eb6e292 commit 32a6958

File tree

2 files changed

+61
-84
lines changed

2 files changed

+61
-84
lines changed

drivers/usb/core/hub.c

Lines changed: 60 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@
2222
#include <linux/usb/hcd.h>
2323
#include <linux/usb/otg.h>
2424
#include <linux/usb/quirks.h>
25-
#include <linux/kthread.h>
25+
#include <linux/workqueue.h>
2626
#include <linux/mutex.h>
27-
#include <linux/freezer.h>
2827
#include <linux/random.h>
2928
#include <linux/pm_qos.h>
3029

@@ -42,14 +41,9 @@
4241
* change to USB_STATE_NOTATTACHED even when the semaphore isn't held. */
4342
static DEFINE_SPINLOCK(device_state_lock);
4443

45-
/* khubd's worklist and its lock */
46-
static DEFINE_SPINLOCK(hub_event_lock);
47-
static LIST_HEAD(hub_event_list); /* List of hubs needing servicing */
48-
49-
/* Wakes up khubd */
50-
static DECLARE_WAIT_QUEUE_HEAD(khubd_wait);
51-
52-
static struct task_struct *khubd_task;
44+
/* workqueue to process hub events */
45+
static struct workqueue_struct *hub_wq;
46+
static void hub_event(struct work_struct *work);
5347

5448
/* synchronize hub-port add/remove and peering operations */
5549
DEFINE_MUTEX(usb_port_peer_mutex);
@@ -105,6 +99,7 @@ EXPORT_SYMBOL_GPL(ehci_cf_port_reset_rwsem);
10599
#define HUB_DEBOUNCE_STEP 25
106100
#define HUB_DEBOUNCE_STABLE 100
107101

102+
static void hub_release(struct kref *kref);
108103
static int usb_reset_and_verify_device(struct usb_device *udev);
109104

110105
static inline char *portspeed(struct usb_hub *hub, int portstatus)
@@ -576,28 +571,39 @@ static int hub_port_status(struct usb_hub *hub, int port1,
576571
return ret;
577572
}
578573

579-
static void kick_khubd(struct usb_hub *hub)
574+
static void kick_hub_wq(struct usb_hub *hub)
580575
{
581-
unsigned long flags;
576+
struct usb_interface *intf;
582577

583-
spin_lock_irqsave(&hub_event_lock, flags);
584-
if (!hub->disconnected && list_empty(&hub->event_list)) {
585-
list_add_tail(&hub->event_list, &hub_event_list);
578+
if (hub->disconnected || work_pending(&hub->events))
579+
return;
586580

587-
/* Suppress autosuspend until khubd runs */
588-
usb_autopm_get_interface_no_resume(
589-
to_usb_interface(hub->intfdev));
590-
wake_up(&khubd_wait);
591-
}
592-
spin_unlock_irqrestore(&hub_event_lock, flags);
581+
/*
582+
* Suppress autosuspend until the event is proceed.
583+
*
584+
* Be careful and make sure that the symmetric operation is
585+
* always called. We are here only when there is no pending
586+
* work for this hub. Therefore put the interface either when
587+
* the new work is called or when it is canceled.
588+
*/
589+
intf = to_usb_interface(hub->intfdev);
590+
usb_autopm_get_interface_no_resume(intf);
591+
kref_get(&hub->kref);
592+
593+
if (queue_work(hub_wq, &hub->events))
594+
return;
595+
596+
/* the work has already been scheduled */
597+
usb_autopm_put_interface_async(intf);
598+
kref_put(&hub->kref, hub_release);
593599
}
594600

595601
void usb_kick_khubd(struct usb_device *hdev)
596602
{
597603
struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
598604

599605
if (hub)
600-
kick_khubd(hub);
606+
kick_hub_wq(hub);
601607
}
602608

603609
/*
@@ -619,7 +625,7 @@ void usb_wakeup_notification(struct usb_device *hdev,
619625
hub = usb_hub_to_struct_hub(hdev);
620626
if (hub) {
621627
set_bit(portnum, hub->wakeup_bits);
622-
kick_khubd(hub);
628+
kick_hub_wq(hub);
623629
}
624630
}
625631
EXPORT_SYMBOL_GPL(usb_wakeup_notification);
@@ -659,7 +665,7 @@ static void hub_irq(struct urb *urb)
659665
hub->nerrors = 0;
660666

661667
/* Something happened, let khubd figure it out */
662-
kick_khubd(hub);
668+
kick_hub_wq(hub);
663669

664670
resubmit:
665671
if (hub->quiescing)
@@ -973,7 +979,7 @@ static void hub_port_logical_disconnect(struct usb_hub *hub, int port1)
973979
*/
974980

975981
set_bit(port1, hub->change_bits);
976-
kick_khubd(hub);
982+
kick_hub_wq(hub);
977983
}
978984

979985
/**
@@ -1241,7 +1247,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
12411247
&hub->leds, LED_CYCLE_PERIOD);
12421248

12431249
/* Scan all ports that need attention */
1244-
kick_khubd(hub);
1250+
kick_hub_wq(hub);
12451251

12461252
/* Allow autosuspend if it was suppressed */
12471253
if (type <= HUB_INIT3)
@@ -1648,14 +1654,11 @@ static void hub_disconnect(struct usb_interface *intf)
16481654
struct usb_device *hdev = interface_to_usbdev(intf);
16491655
int port1;
16501656

1651-
/* Take the hub off the event list and don't let it be added again */
1652-
spin_lock_irq(&hub_event_lock);
1653-
if (!list_empty(&hub->event_list)) {
1654-
list_del_init(&hub->event_list);
1655-
usb_autopm_put_interface_no_suspend(intf);
1656-
}
1657+
/*
1658+
* Stop adding new hub events. We do not want to block here and thus
1659+
* will not try to remove any pending work item.
1660+
*/
16571661
hub->disconnected = 1;
1658-
spin_unlock_irq(&hub_event_lock);
16591662

16601663
/* Disconnect all children and quiesce the hub */
16611664
hub->error = 0;
@@ -1795,11 +1798,11 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
17951798
}
17961799

17971800
kref_init(&hub->kref);
1798-
INIT_LIST_HEAD(&hub->event_list);
17991801
hub->intfdev = &intf->dev;
18001802
hub->hdev = hdev;
18011803
INIT_DELAYED_WORK(&hub->leds, led_work);
18021804
INIT_DELAYED_WORK(&hub->init_work, NULL);
1805+
INIT_WORK(&hub->events, hub_event);
18031806
usb_get_intf(intf);
18041807
usb_get_dev(hdev);
18051808

@@ -4996,9 +4999,8 @@ static void port_event(struct usb_hub *hub, int port1)
49964999
hub_port_connect_change(hub, port1, portstatus, portchange);
49975000
}
49985001

4999-
static void hub_event(void)
5002+
static void hub_event(struct work_struct *work)
50005003
{
5001-
struct list_head *tmp;
50025004
struct usb_device *hdev;
50035005
struct usb_interface *intf;
50045006
struct usb_hub *hub;
@@ -5007,23 +5009,11 @@ static void hub_event(void)
50075009
u16 hubchange;
50085010
int i, ret;
50095011

5010-
/* Grab the first entry at the beginning of the list */
5011-
spin_lock_irq(&hub_event_lock);
5012-
if (list_empty(&hub_event_list)) {
5013-
spin_unlock_irq(&hub_event_lock);
5014-
return;
5015-
}
5016-
5017-
tmp = hub_event_list.next;
5018-
list_del_init(tmp);
5019-
5020-
hub = list_entry(tmp, struct usb_hub, event_list);
5021-
kref_get(&hub->kref);
5022-
spin_unlock_irq(&hub_event_lock);
5023-
5012+
hub = container_of(work, struct usb_hub, events);
50245013
hdev = hub->hdev;
50255014
hub_dev = hub->intfdev;
50265015
intf = to_usb_interface(hub_dev);
5016+
50275017
dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n",
50285018
hdev->state, hdev->maxchild,
50295019
/* NOTE: expects max 15 ports... */
@@ -5034,20 +5024,20 @@ static void hub_event(void)
50345024
* disconnected while waiting for the lock to succeed. */
50355025
usb_lock_device(hdev);
50365026
if (unlikely(hub->disconnected))
5037-
goto out_disconnected;
5027+
goto out_hdev_lock;
50385028

50395029
/* If the hub has died, clean up after it */
50405030
if (hdev->state == USB_STATE_NOTATTACHED) {
50415031
hub->error = -ENODEV;
50425032
hub_quiesce(hub, HUB_DISCONNECT);
5043-
goto out;
5033+
goto out_hdev_lock;
50445034
}
50455035

50465036
/* Autoresume */
50475037
ret = usb_autopm_get_interface(intf);
50485038
if (ret) {
50495039
dev_dbg(hub_dev, "Can't autoresume: %d\n", ret);
5050-
goto out;
5040+
goto out_hdev_lock;
50515041
}
50525042

50535043
/* If this is an inactive hub, do nothing */
@@ -5124,34 +5114,12 @@ static void hub_event(void)
51245114
out_autopm:
51255115
/* Balance the usb_autopm_get_interface() above */
51265116
usb_autopm_put_interface_no_suspend(intf);
5127-
out:
5128-
/* Balance the usb_autopm_get_interface_no_resume() in
5129-
* kick_khubd() and allow autosuspend.
5130-
*/
5131-
usb_autopm_put_interface(intf);
5132-
out_disconnected:
5117+
out_hdev_lock:
51335118
usb_unlock_device(hdev);
5134-
kref_put(&hub->kref, hub_release);
5135-
}
51365119

5137-
static int hub_thread(void *__unused)
5138-
{
5139-
/* khubd needs to be freezable to avoid interfering with USB-PERSIST
5140-
* port handover. Otherwise it might see that a full-speed device
5141-
* was gone before the EHCI controller had handed its port over to
5142-
* the companion full-speed controller.
5143-
*/
5144-
set_freezable();
5145-
5146-
do {
5147-
hub_event();
5148-
wait_event_freezable(khubd_wait,
5149-
!list_empty(&hub_event_list) ||
5150-
kthread_should_stop());
5151-
} while (!kthread_should_stop() || !list_empty(&hub_event_list));
5152-
5153-
pr_debug("%s: khubd exiting\n", usbcore_name);
5154-
return 0;
5120+
/* Balance the stuff in kick_hub_wq() and allow autosuspend */
5121+
usb_autopm_put_interface(intf);
5122+
kref_put(&hub->kref, hub_release);
51555123
}
51565124

51575125
static const struct usb_device_id hub_id_table[] = {
@@ -5191,20 +5159,29 @@ int usb_hub_init(void)
51915159
return -1;
51925160
}
51935161

5194-
khubd_task = kthread_run(hub_thread, NULL, "khubd");
5195-
if (!IS_ERR(khubd_task))
5162+
/*
5163+
* The workqueue needs to be freezable to avoid interfering with
5164+
* USB-PERSIST port handover. Otherwise it might see that a full-speed
5165+
* device was gone before the EHCI controller had handed its port
5166+
* over to the companion full-speed controller.
5167+
*
5168+
* Also we use ordered workqueue because the code is not ready
5169+
* for parallel execution of hub events, see choose_devnum().
5170+
*/
5171+
hub_wq = alloc_ordered_workqueue("usb_hub_wq", WQ_FREEZABLE);
5172+
if (hub_wq)
51965173
return 0;
51975174

51985175
/* Fall through if kernel_thread failed */
51995176
usb_deregister(&hub_driver);
5200-
printk(KERN_ERR "%s: can't start khubd\n", usbcore_name);
5177+
pr_err("%s: can't allocate workqueue for usb hub\n", usbcore_name);
52015178

52025179
return -1;
52035180
}
52045181

52055182
void usb_hub_cleanup(void)
52065183
{
5207-
kthread_stop(khubd_task);
5184+
destroy_workqueue(hub_wq);
52085185

52095186
/*
52105187
* Hub resources are freed for us by usb_deregister. It calls

drivers/usb/core/hub.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ struct usb_hub {
4141
int error; /* last reported error */
4242
int nerrors; /* track consecutive errors */
4343

44-
struct list_head event_list; /* hubs w/data or errs ready */
4544
unsigned long event_bits[1]; /* status change bitmask */
4645
unsigned long change_bits[1]; /* ports with logical connect
4746
status change */
@@ -77,6 +76,7 @@ struct usb_hub {
7776
u8 indicator[USB_MAXCHILDREN];
7877
struct delayed_work leds;
7978
struct delayed_work init_work;
79+
struct work_struct events;
8080
struct usb_port **ports;
8181
};
8282

0 commit comments

Comments
 (0)