Skip to content

Commit e84b4a0

Browse files
AlanSterngregkh
authored andcommitted
USB: dummy-hcd: Fix erroneous synchronization change
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. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Fixes: f16443a034c7 ("USB: gadgetfs, dummy-hcd, net2280: fix locking for callbacks") Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent d1a0787 commit e84b4a0

1 file changed

Lines changed: 30 additions & 2 deletions

File tree

drivers/usb/gadget/udc/dummy_hcd.c

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,11 +255,13 @@ struct dummy {
255255
*/
256256
struct dummy_ep ep[DUMMY_ENDPOINTS];
257257
int address;
258+
int callback_usage;
258259
struct usb_gadget gadget;
259260
struct usb_gadget_driver *driver;
260261
struct dummy_request fifo_req;
261262
u8 fifo_buf[FIFO_SIZE];
262263
u16 devstatus;
264+
unsigned ints_enabled:1;
263265
unsigned udc_suspended:1;
264266
unsigned pullup:1;
265267

@@ -442,18 +444,27 @@ static void set_link_state(struct dummy_hcd *dum_hcd)
442444
(~dum_hcd->old_status) & dum_hcd->port_status;
443445

444446
/* Report reset and disconnect events to the driver */
445-
if (dum->driver && (disconnect || reset)) {
447+
if (dum->ints_enabled && (disconnect || reset)) {
446448
stop_activity(dum);
449+
++dum->callback_usage;
450+
spin_unlock(&dum->lock);
447451
if (reset)
448452
usb_gadget_udc_reset(&dum->gadget, dum->driver);
449453
else
450454
dum->driver->disconnect(&dum->gadget);
455+
spin_lock(&dum->lock);
456+
--dum->callback_usage;
451457
}
452-
} else if (dum_hcd->active != dum_hcd->old_active) {
458+
} else if (dum_hcd->active != dum_hcd->old_active &&
459+
dum->ints_enabled) {
460+
++dum->callback_usage;
461+
spin_unlock(&dum->lock);
453462
if (dum_hcd->old_active && dum->driver->suspend)
454463
dum->driver->suspend(&dum->gadget);
455464
else if (!dum_hcd->old_active && dum->driver->resume)
456465
dum->driver->resume(&dum->gadget);
466+
spin_lock(&dum->lock);
467+
--dum->callback_usage;
457468
}
458469

459470
dum_hcd->old_status = dum_hcd->port_status;
@@ -969,8 +980,11 @@ static int dummy_udc_start(struct usb_gadget *g,
969980
* can't enumerate without help from the driver we're binding.
970981
*/
971982

983+
spin_lock_irq(&dum->lock);
972984
dum->devstatus = 0;
973985
dum->driver = driver;
986+
dum->ints_enabled = 1;
987+
spin_unlock_irq(&dum->lock);
974988

975989
return 0;
976990
}
@@ -981,6 +995,16 @@ static int dummy_udc_stop(struct usb_gadget *g)
981995
struct dummy *dum = dum_hcd->dum;
982996

983997
spin_lock_irq(&dum->lock);
998+
dum->ints_enabled = 0;
999+
stop_activity(dum);
1000+
1001+
/* emulate synchronize_irq(): wait for callbacks to finish */
1002+
while (dum->callback_usage > 0) {
1003+
spin_unlock_irq(&dum->lock);
1004+
usleep_range(1000, 2000);
1005+
spin_lock_irq(&dum->lock);
1006+
}
1007+
9841008
dum->driver = NULL;
9851009
spin_unlock_irq(&dum->lock);
9861010

@@ -1526,6 +1550,8 @@ static struct dummy_ep *find_endpoint(struct dummy *dum, u8 address)
15261550
if (!is_active((dum->gadget.speed == USB_SPEED_SUPER ?
15271551
dum->ss_hcd : dum->hs_hcd)))
15281552
return NULL;
1553+
if (!dum->ints_enabled)
1554+
return NULL;
15291555
if ((address & ~USB_DIR_IN) == 0)
15301556
return &dum->ep[0];
15311557
for (i = 1; i < DUMMY_ENDPOINTS; i++) {
@@ -1867,10 +1893,12 @@ static void dummy_timer(unsigned long _dum_hcd)
18671893
* until setup() returns; no reentrancy issues etc.
18681894
*/
18691895
if (value > 0) {
1896+
++dum->callback_usage;
18701897
spin_unlock(&dum->lock);
18711898
value = dum->driver->setup(&dum->gadget,
18721899
&setup);
18731900
spin_lock(&dum->lock);
1901+
--dum->callback_usage;
18741902

18751903
if (value >= 0) {
18761904
/* no delays (max 64KB data stage) */

0 commit comments

Comments
 (0)