diff options
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/core/devio.c | 78 |
1 files changed, 77 insertions, 1 deletions
diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 71514be8b715..181f78c84105 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c | |||
@@ -74,6 +74,7 @@ struct dev_state { | |||
74 | void __user *disccontext; | 74 | void __user *disccontext; |
75 | unsigned long ifclaimed; | 75 | unsigned long ifclaimed; |
76 | u32 secid; | 76 | u32 secid; |
77 | u32 disabled_bulk_eps; | ||
77 | }; | 78 | }; |
78 | 79 | ||
79 | struct async { | 80 | struct async { |
@@ -88,6 +89,8 @@ struct async { | |||
88 | struct urb *urb; | 89 | struct urb *urb; |
89 | int status; | 90 | int status; |
90 | u32 secid; | 91 | u32 secid; |
92 | u8 bulk_addr; | ||
93 | u8 bulk_status; | ||
91 | }; | 94 | }; |
92 | 95 | ||
93 | static int usbfs_snoop; | 96 | static int usbfs_snoop; |
@@ -343,6 +346,43 @@ static void snoop_urb(struct usb_device *udev, | |||
343 | } | 346 | } |
344 | } | 347 | } |
345 | 348 | ||
349 | #define AS_CONTINUATION 1 | ||
350 | #define AS_UNLINK 2 | ||
351 | |||
352 | static void cancel_bulk_urbs(struct dev_state *ps, unsigned bulk_addr) | ||
353 | __releases(ps->lock) | ||
354 | __acquires(ps->lock) | ||
355 | { | ||
356 | struct async *as; | ||
357 | |||
358 | /* Mark all the pending URBs that match bulk_addr, up to but not | ||
359 | * including the first one without AS_CONTINUATION. If such an | ||
360 | * URB is encountered then a new transfer has already started so | ||
361 | * the endpoint doesn't need to be disabled; otherwise it does. | ||
362 | */ | ||
363 | list_for_each_entry(as, &ps->async_pending, asynclist) { | ||
364 | if (as->bulk_addr == bulk_addr) { | ||
365 | if (as->bulk_status != AS_CONTINUATION) | ||
366 | goto rescan; | ||
367 | as->bulk_status = AS_UNLINK; | ||
368 | as->bulk_addr = 0; | ||
369 | } | ||
370 | } | ||
371 | ps->disabled_bulk_eps |= (1 << bulk_addr); | ||
372 | |||
373 | /* Now carefully unlink all the marked pending URBs */ | ||
374 | rescan: | ||
375 | list_for_each_entry(as, &ps->async_pending, asynclist) { | ||
376 | if (as->bulk_status == AS_UNLINK) { | ||
377 | as->bulk_status = 0; /* Only once */ | ||
378 | spin_unlock(&ps->lock); /* Allow completions */ | ||
379 | usb_unlink_urb(as->urb); | ||
380 | spin_lock(&ps->lock); | ||
381 | goto rescan; | ||
382 | } | ||
383 | } | ||
384 | } | ||
385 | |||
346 | static void async_completed(struct urb *urb) | 386 | static void async_completed(struct urb *urb) |
347 | { | 387 | { |
348 | struct async *as = urb->context; | 388 | struct async *as = urb->context; |
@@ -371,6 +411,9 @@ static void async_completed(struct urb *urb) | |||
371 | snoop(&urb->dev->dev, "urb complete\n"); | 411 | snoop(&urb->dev->dev, "urb complete\n"); |
372 | snoop_urb(urb->dev, as->userurb, urb->pipe, urb->actual_length, | 412 | snoop_urb(urb->dev, as->userurb, urb->pipe, urb->actual_length, |
373 | as->status, COMPLETE); | 413 | as->status, COMPLETE); |
414 | if (as->status < 0 && as->bulk_addr && as->status != -ECONNRESET && | ||
415 | as->status != -ENOENT) | ||
416 | cancel_bulk_urbs(ps, as->bulk_addr); | ||
374 | spin_unlock(&ps->lock); | 417 | spin_unlock(&ps->lock); |
375 | 418 | ||
376 | if (signr) | 419 | if (signr) |
@@ -993,6 +1036,7 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, | |||
993 | 1036 | ||
994 | if (uurb->flags & ~(USBDEVFS_URB_ISO_ASAP | | 1037 | if (uurb->flags & ~(USBDEVFS_URB_ISO_ASAP | |
995 | USBDEVFS_URB_SHORT_NOT_OK | | 1038 | USBDEVFS_URB_SHORT_NOT_OK | |
1039 | USBDEVFS_URB_BULK_CONTINUATION | | ||
996 | USBDEVFS_URB_NO_FSBR | | 1040 | USBDEVFS_URB_NO_FSBR | |
997 | USBDEVFS_URB_ZERO_PACKET | | 1041 | USBDEVFS_URB_ZERO_PACKET | |
998 | USBDEVFS_URB_NO_INTERRUPT)) | 1042 | USBDEVFS_URB_NO_INTERRUPT)) |
@@ -1194,7 +1238,39 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, | |||
1194 | snoop_urb(ps->dev, as->userurb, as->urb->pipe, | 1238 | snoop_urb(ps->dev, as->userurb, as->urb->pipe, |
1195 | as->urb->transfer_buffer_length, 0, SUBMIT); | 1239 | as->urb->transfer_buffer_length, 0, SUBMIT); |
1196 | async_newpending(as); | 1240 | async_newpending(as); |
1197 | if ((ret = usb_submit_urb(as->urb, GFP_KERNEL))) { | 1241 | |
1242 | if (usb_endpoint_xfer_bulk(&ep->desc)) { | ||
1243 | spin_lock_irq(&ps->lock); | ||
1244 | |||
1245 | /* Not exactly the endpoint address; the direction bit is | ||
1246 | * shifted to the 0x10 position so that the value will be | ||
1247 | * between 0 and 31. | ||
1248 | */ | ||
1249 | as->bulk_addr = usb_endpoint_num(&ep->desc) | | ||
1250 | ((ep->desc.bEndpointAddress & USB_ENDPOINT_DIR_MASK) | ||
1251 | >> 3); | ||
1252 | |||
1253 | /* If this bulk URB is the start of a new transfer, re-enable | ||
1254 | * the endpoint. Otherwise mark it as a continuation URB. | ||
1255 | */ | ||
1256 | if (uurb->flags & USBDEVFS_URB_BULK_CONTINUATION) | ||
1257 | as->bulk_status = AS_CONTINUATION; | ||
1258 | else | ||
1259 | ps->disabled_bulk_eps &= ~(1 << as->bulk_addr); | ||
1260 | |||
1261 | /* Don't accept continuation URBs if the endpoint is | ||
1262 | * disabled because of an earlier error. | ||
1263 | */ | ||
1264 | if (ps->disabled_bulk_eps & (1 << as->bulk_addr)) | ||
1265 | ret = -EREMOTEIO; | ||
1266 | else | ||
1267 | ret = usb_submit_urb(as->urb, GFP_ATOMIC); | ||
1268 | spin_unlock_irq(&ps->lock); | ||
1269 | } else { | ||
1270 | ret = usb_submit_urb(as->urb, GFP_KERNEL); | ||
1271 | } | ||
1272 | |||
1273 | if (ret) { | ||
1198 | dev_printk(KERN_DEBUG, &ps->dev->dev, | 1274 | dev_printk(KERN_DEBUG, &ps->dev->dev, |
1199 | "usbfs: usb_submit_urb returned %d\n", ret); | 1275 | "usbfs: usb_submit_urb returned %d\n", ret); |
1200 | snoop_urb(ps->dev, as->userurb, as->urb->pipe, | 1276 | snoop_urb(ps->dev, as->userurb, as->urb->pipe, |