Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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 <stern@rowland.harvard.edu> for code review, many useful
tips and guidance. Also thanks to Tejun Heo <tj@kernel.org> for hints how to
allocate the workqueue.

Signed-off-by: Petr Mladek <pmladek@suse.cz>
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Petr Mladek and committed by
Greg Kroah-Hartman
32a69589 eb6e2924

+61 -84
+60 -83
drivers/usb/core/hub.c
··· 22 22 #include <linux/usb/hcd.h> 23 23 #include <linux/usb/otg.h> 24 24 #include <linux/usb/quirks.h> 25 - #include <linux/kthread.h> 25 + #include <linux/workqueue.h> 26 26 #include <linux/mutex.h> 27 - #include <linux/freezer.h> 28 27 #include <linux/random.h> 29 28 #include <linux/pm_qos.h> 30 29 ··· 41 42 * change to USB_STATE_NOTATTACHED even when the semaphore isn't held. */ 42 43 static DEFINE_SPINLOCK(device_state_lock); 43 44 44 - /* khubd's worklist and its lock */ 45 - static DEFINE_SPINLOCK(hub_event_lock); 46 - static LIST_HEAD(hub_event_list); /* List of hubs needing servicing */ 47 - 48 - /* Wakes up khubd */ 49 - static DECLARE_WAIT_QUEUE_HEAD(khubd_wait); 50 - 51 - static struct task_struct *khubd_task; 45 + /* workqueue to process hub events */ 46 + static struct workqueue_struct *hub_wq; 47 + static void hub_event(struct work_struct *work); 52 48 53 49 /* synchronize hub-port add/remove and peering operations */ 54 50 DEFINE_MUTEX(usb_port_peer_mutex); ··· 99 105 #define HUB_DEBOUNCE_STEP 25 100 106 #define HUB_DEBOUNCE_STABLE 100 101 107 108 + static void hub_release(struct kref *kref); 102 109 static int usb_reset_and_verify_device(struct usb_device *udev); 103 110 104 111 static inline char *portspeed(struct usb_hub *hub, int portstatus) ··· 571 576 return ret; 572 577 } 573 578 574 - static void kick_khubd(struct usb_hub *hub) 579 + static void kick_hub_wq(struct usb_hub *hub) 575 580 { 576 - unsigned long flags; 581 + struct usb_interface *intf; 577 582 578 - spin_lock_irqsave(&hub_event_lock, flags); 579 - if (!hub->disconnected && list_empty(&hub->event_list)) { 580 - list_add_tail(&hub->event_list, &hub_event_list); 583 + if (hub->disconnected || work_pending(&hub->events)) 584 + return; 581 585 582 - /* Suppress autosuspend until khubd runs */ 583 - usb_autopm_get_interface_no_resume( 584 - to_usb_interface(hub->intfdev)); 585 - wake_up(&khubd_wait); 586 - } 587 - spin_unlock_irqrestore(&hub_event_lock, flags); 586 + /* 587 + * Suppress autosuspend until the event is proceed. 588 + * 589 + * Be careful and make sure that the symmetric operation is 590 + * always called. We are here only when there is no pending 591 + * work for this hub. Therefore put the interface either when 592 + * the new work is called or when it is canceled. 593 + */ 594 + intf = to_usb_interface(hub->intfdev); 595 + usb_autopm_get_interface_no_resume(intf); 596 + kref_get(&hub->kref); 597 + 598 + if (queue_work(hub_wq, &hub->events)) 599 + return; 600 + 601 + /* the work has already been scheduled */ 602 + usb_autopm_put_interface_async(intf); 603 + kref_put(&hub->kref, hub_release); 588 604 } 589 605 590 606 void usb_kick_khubd(struct usb_device *hdev) ··· 603 597 struct usb_hub *hub = usb_hub_to_struct_hub(hdev); 604 598 605 599 if (hub) 606 - kick_khubd(hub); 600 + kick_hub_wq(hub); 607 601 } 608 602 609 603 /* ··· 625 619 hub = usb_hub_to_struct_hub(hdev); 626 620 if (hub) { 627 621 set_bit(portnum, hub->wakeup_bits); 628 - kick_khubd(hub); 622 + kick_hub_wq(hub); 629 623 } 630 624 } 631 625 EXPORT_SYMBOL_GPL(usb_wakeup_notification); ··· 665 659 hub->nerrors = 0; 666 660 667 661 /* Something happened, let khubd figure it out */ 668 - kick_khubd(hub); 662 + kick_hub_wq(hub); 669 663 670 664 resubmit: 671 665 if (hub->quiescing) ··· 979 973 */ 980 974 981 975 set_bit(port1, hub->change_bits); 982 - kick_khubd(hub); 976 + kick_hub_wq(hub); 983 977 } 984 978 985 979 /** ··· 1247 1241 &hub->leds, LED_CYCLE_PERIOD); 1248 1242 1249 1243 /* Scan all ports that need attention */ 1250 - kick_khubd(hub); 1244 + kick_hub_wq(hub); 1251 1245 1252 1246 /* Allow autosuspend if it was suppressed */ 1253 1247 if (type <= HUB_INIT3) ··· 1654 1648 struct usb_device *hdev = interface_to_usbdev(intf); 1655 1649 int port1; 1656 1650 1657 - /* Take the hub off the event list and don't let it be added again */ 1658 - spin_lock_irq(&hub_event_lock); 1659 - if (!list_empty(&hub->event_list)) { 1660 - list_del_init(&hub->event_list); 1661 - usb_autopm_put_interface_no_suspend(intf); 1662 - } 1651 + /* 1652 + * Stop adding new hub events. We do not want to block here and thus 1653 + * will not try to remove any pending work item. 1654 + */ 1663 1655 hub->disconnected = 1; 1664 - spin_unlock_irq(&hub_event_lock); 1665 1656 1666 1657 /* Disconnect all children and quiesce the hub */ 1667 1658 hub->error = 0; ··· 1798 1795 } 1799 1796 1800 1797 kref_init(&hub->kref); 1801 - INIT_LIST_HEAD(&hub->event_list); 1802 1798 hub->intfdev = &intf->dev; 1803 1799 hub->hdev = hdev; 1804 1800 INIT_DELAYED_WORK(&hub->leds, led_work); 1805 1801 INIT_DELAYED_WORK(&hub->init_work, NULL); 1802 + INIT_WORK(&hub->events, hub_event); 1806 1803 usb_get_intf(intf); 1807 1804 usb_get_dev(hdev); 1808 1805 ··· 4999 4996 hub_port_connect_change(hub, port1, portstatus, portchange); 5000 4997 } 5001 4998 5002 - static void hub_event(void) 4999 + static void hub_event(struct work_struct *work) 5003 5000 { 5004 - struct list_head *tmp; 5005 5001 struct usb_device *hdev; 5006 5002 struct usb_interface *intf; 5007 5003 struct usb_hub *hub; ··· 5009 5007 u16 hubchange; 5010 5008 int i, ret; 5011 5009 5012 - /* Grab the first entry at the beginning of the list */ 5013 - spin_lock_irq(&hub_event_lock); 5014 - if (list_empty(&hub_event_list)) { 5015 - spin_unlock_irq(&hub_event_lock); 5016 - return; 5017 - } 5018 - 5019 - tmp = hub_event_list.next; 5020 - list_del_init(tmp); 5021 - 5022 - hub = list_entry(tmp, struct usb_hub, event_list); 5023 - kref_get(&hub->kref); 5024 - spin_unlock_irq(&hub_event_lock); 5025 - 5010 + hub = container_of(work, struct usb_hub, events); 5026 5011 hdev = hub->hdev; 5027 5012 hub_dev = hub->intfdev; 5028 5013 intf = to_usb_interface(hub_dev); 5014 + 5029 5015 dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n", 5030 5016 hdev->state, hdev->maxchild, 5031 5017 /* NOTE: expects max 15 ports... */ ··· 5024 5034 * disconnected while waiting for the lock to succeed. */ 5025 5035 usb_lock_device(hdev); 5026 5036 if (unlikely(hub->disconnected)) 5027 - goto out_disconnected; 5037 + goto out_hdev_lock; 5028 5038 5029 5039 /* If the hub has died, clean up after it */ 5030 5040 if (hdev->state == USB_STATE_NOTATTACHED) { 5031 5041 hub->error = -ENODEV; 5032 5042 hub_quiesce(hub, HUB_DISCONNECT); 5033 - goto out; 5043 + goto out_hdev_lock; 5034 5044 } 5035 5045 5036 5046 /* Autoresume */ 5037 5047 ret = usb_autopm_get_interface(intf); 5038 5048 if (ret) { 5039 5049 dev_dbg(hub_dev, "Can't autoresume: %d\n", ret); 5040 - goto out; 5050 + goto out_hdev_lock; 5041 5051 } 5042 5052 5043 5053 /* If this is an inactive hub, do nothing */ ··· 5114 5124 out_autopm: 5115 5125 /* Balance the usb_autopm_get_interface() above */ 5116 5126 usb_autopm_put_interface_no_suspend(intf); 5117 - out: 5118 - /* Balance the usb_autopm_get_interface_no_resume() in 5119 - * kick_khubd() and allow autosuspend. 5120 - */ 5121 - usb_autopm_put_interface(intf); 5122 - out_disconnected: 5127 + out_hdev_lock: 5123 5128 usb_unlock_device(hdev); 5129 + 5130 + /* Balance the stuff in kick_hub_wq() and allow autosuspend */ 5131 + usb_autopm_put_interface(intf); 5124 5132 kref_put(&hub->kref, hub_release); 5125 - } 5126 - 5127 - static int hub_thread(void *__unused) 5128 - { 5129 - /* khubd needs to be freezable to avoid interfering with USB-PERSIST 5130 - * port handover. Otherwise it might see that a full-speed device 5131 - * was gone before the EHCI controller had handed its port over to 5132 - * the companion full-speed controller. 5133 - */ 5134 - set_freezable(); 5135 - 5136 - do { 5137 - hub_event(); 5138 - wait_event_freezable(khubd_wait, 5139 - !list_empty(&hub_event_list) || 5140 - kthread_should_stop()); 5141 - } while (!kthread_should_stop() || !list_empty(&hub_event_list)); 5142 - 5143 - pr_debug("%s: khubd exiting\n", usbcore_name); 5144 - return 0; 5145 5133 } 5146 5134 5147 5135 static const struct usb_device_id hub_id_table[] = { ··· 5159 5191 return -1; 5160 5192 } 5161 5193 5162 - khubd_task = kthread_run(hub_thread, NULL, "khubd"); 5163 - if (!IS_ERR(khubd_task)) 5194 + /* 5195 + * The workqueue needs to be freezable to avoid interfering with 5196 + * USB-PERSIST port handover. Otherwise it might see that a full-speed 5197 + * device was gone before the EHCI controller had handed its port 5198 + * over to the companion full-speed controller. 5199 + * 5200 + * Also we use ordered workqueue because the code is not ready 5201 + * for parallel execution of hub events, see choose_devnum(). 5202 + */ 5203 + hub_wq = alloc_ordered_workqueue("usb_hub_wq", WQ_FREEZABLE); 5204 + if (hub_wq) 5164 5205 return 0; 5165 5206 5166 5207 /* Fall through if kernel_thread failed */ 5167 5208 usb_deregister(&hub_driver); 5168 - printk(KERN_ERR "%s: can't start khubd\n", usbcore_name); 5209 + pr_err("%s: can't allocate workqueue for usb hub\n", usbcore_name); 5169 5210 5170 5211 return -1; 5171 5212 } 5172 5213 5173 5214 void usb_hub_cleanup(void) 5174 5215 { 5175 - kthread_stop(khubd_task); 5216 + destroy_workqueue(hub_wq); 5176 5217 5177 5218 /* 5178 5219 * Hub resources are freed for us by usb_deregister. It calls
+1 -1
drivers/usb/core/hub.h
··· 41 41 int error; /* last reported error */ 42 42 int nerrors; /* track consecutive errors */ 43 43 44 - struct list_head event_list; /* hubs w/data or errs ready */ 45 44 unsigned long event_bits[1]; /* status change bitmask */ 46 45 unsigned long change_bits[1]; /* ports with logical connect 47 46 status change */ ··· 76 77 u8 indicator[USB_MAXCHILDREN]; 77 78 struct delayed_work leds; 78 79 struct delayed_work init_work; 80 + struct work_struct events; 79 81 struct usb_port **ports; 80 82 }; 81 83