Patch "USB: dummy-hcd: Fix erroneous synchronization change" has been added to the 4.4-stable tree
Subsystems (2)
This is a note to let you know that I've just added the patch titled
USB: dummy-hcd: Fix erroneous synchronization change
to the 4.4-stable tree which can be found at:
http://www.kernel.org/git/?p=linux/kernel/git/stable/stable-queue.git;a=summary
The filename of the patch is:
usb-dummy-hcd-fix-erroneous-synchronization-change.patch
and it can be found in the queue-4.4 subdirectory.
If you, or anyone else, feels it should not be added to the stable tree, please let <[email protected]> know about it.
Quoted 1 line
From
7dbd8f4cabd96db5a50513de9d83a8105a5ffc81Mon Sep 17 00:00:00 2001
From: Alan Stern <[email protected]>
Date: Tue, 26 Sep 2017 15:15:49 -0400
Subject: USB: dummy-hcd: Fix erroneous synchronization change
From: Alan Stern <[email protected]>
commit 7dbd8f4cabd96db5a50513de9d83a8105a5ffc81 upstream.
A recent change to the synchronization in dummy-hcd was incorrect. The issue was that dummy_udc_stop() contained no locking and therefore could race with various gadget driver callbacks, and the fix was to add locking and issue the callbacks with the private spinlock held.
UDC drivers aren't supposed to do this. Gadget driver callback routines are allowed to invoke functions in the UDC driver, and these functions will generally try to acquire the private spinlock. This would deadlock the driver.
The correct solution is to drop the spinlock before issuing callbacks, and avoid races by emulating the synchronize_irq() call that all real UDC drivers must perform in their ->udc_stop() routines after disabling interrupts. This involves adding a flag to dummy-hcd's private structure to keep track of whether interrupts are supposed to be enabled, and adding a counter to keep track of ongoing callbacks so that dummy_udc_stop() can wait for them all to finish.
A real UDC driver won't receive disconnect, reset, suspend, resume, or setup events once it has disabled interrupts. dummy-hcd will receive them but won't try to issue any gadget driver callbacks, which should be just as good.
--- drivers/usb/gadget/udc/dummy_hcd.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-)
drivers/usb/gadget/udc/dummy_hcd.c +30 -2
@@ -255,11 +255,13 @@ struct dummy {*/ struct dummy_ep ep[DUMMY_ENDPOINTS ];int address; +int callback_usage;struct usb_gadget gadget;struct usb_gadget_driver *driver;struct dummy_request fifo_req;u8 fifo_buf[FIFO_SIZE ];u16 devstatus; + unsigned ints_enabled:1; unsigned udc_suspended:1; unsigned pullup:1;
@@ -442,18 +444,27 @@ static void set_link_state(struct dummy_ (~dum_hcd->old_status) & dum_hcd->port_status;/* Report reset and disconnect events to the driver */ -if (dum->driver && (disconnect || reset)) { +if (dum->ints_enabled && (disconnect || reset)) {stop_activity (dum); + ++dum->callback_usage; +spin_unlock (&dum->lock);if (reset)usb_gadget_udc_reset (&dum->gadget, dum->driver);else dum->driver->disconnect (&dum->gadget); +spin_lock (&dum->lock); + --dum->callback_usage; } - }else if (dum_hcd->active != dum_hcd->old_active) { + }else if (dum_hcd->active != dum_hcd->old_active && + dum->ints_enabled) { + ++dum->callback_usage; +spin_unlock (&dum->lock);if (dum_hcd->old_active && dum->driver->suspend) dum->driver->suspend (&dum->gadget);else if (!dum_hcd->old_active && dum->driver->resume) dum->driver->resume (&dum->gadget); +spin_lock (&dum->lock); + --dum->callback_usage; } dum_hcd->old_status = dum_hcd->port_status;
@@ -969,8 +980,11 @@ static int dummy_udc_start(struct usb_ga* can't enumerate without help from the driver we're binding. */ +spin_lock_irq (&dum->lock); dum->devstatus =0 ; dum->driver = driver; + dum->ints_enabled =1 ; +spin_unlock_irq (&dum->lock);return 0 ; }
@@ -981,6 +995,16 @@ static int dummy_udc_stop(struct usb_gadstruct dummy *dum = dum_hcd->dum;spin_lock_irq (&dum->lock); + dum->ints_enabled =0 ; +stop_activity (dum); + +/* emulate synchronize_irq(): wait for callbacks to finish */ +while (dum->callback_usage >0 ) { +spin_unlock_irq (&dum->lock); +usleep_range (1000 ,2000 ); +spin_lock_irq (&dum->lock); + } + dum->driver =NULL ;spin_unlock_irq (&dum->lock);
@@ -1526,6 +1550,8 @@ static struct dummy_ep *find_endpoint(stif (!is_active ((dum->gadget.speed ==USB_SPEED_SUPER ? dum->ss_hcd : dum->hs_hcd)))return NULL ; +if (!dum->ints_enabled) +return NULL ;if ((address & ~USB_DIR_IN ) ==0 )return &dum->ep[0 ];for (i =1 ; i <DUMMY_ENDPOINTS ; i++) {
@@ -1867,10 +1893,12 @@ restart:* until setup() returns; no reentrancy issues etc. */ if (value >0 ) { + ++dum->callback_usage;spin_unlock (&dum->lock); value = dum->driver->setup (&dum->gadget, &setup);spin_lock (&dum->lock); + --dum->callback_usage;if (value >=0 ) {/* no delays (max 64KB data stage) */
Patches currently in stable-queue which might be from [email protected] are
queue-4.4/usb-dummy-hcd-fix-infinite-loop-resubmission-bug.patch queue-4.4/usb-storage-unusual_devs-entry-to-fix-write-access-regression-for-seagate-external-drives.patch queue-4.4/usb-gadgetfs-fix-copy_to_user-while-holding-spinlock.patch queue-4.4/usb-devio-don-t-corrupt-user-memory.patch queue-4.4/usb-gadgetfs-fix-crash-caused-by-inadequate-synchronization.patch queue-4.4/usb-dummy-hcd-fix-connection-failures-wrong-speed.patch queue-4.4/usb-dummy-hcd-fix-erroneous-synchronization-change.patch