diff options
Diffstat (limited to 'drivers/usb/class/cdc-wdm.c')
-rw-r--r-- | drivers/usb/class/cdc-wdm.c | 127 |
1 files changed, 115 insertions, 12 deletions
diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 731db051070a..7e8e1235e4e5 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c | |||
@@ -28,8 +28,9 @@ | |||
28 | /* | 28 | /* |
29 | * Version Information | 29 | * Version Information |
30 | */ | 30 | */ |
31 | #define DRIVER_VERSION "v0.02" | 31 | #define DRIVER_VERSION "v0.03" |
32 | #define DRIVER_AUTHOR "Oliver Neukum" | 32 | #define DRIVER_AUTHOR "Oliver Neukum" |
33 | #define DRIVER_DESC "USB Abstract Control Model driver for USB WCM Device Management" | ||
33 | 34 | ||
34 | static struct usb_device_id wdm_ids[] = { | 35 | static struct usb_device_id wdm_ids[] = { |
35 | { | 36 | { |
@@ -87,6 +88,7 @@ struct wdm_device { | |||
87 | dma_addr_t ihandle; | 88 | dma_addr_t ihandle; |
88 | struct mutex wlock; | 89 | struct mutex wlock; |
89 | struct mutex rlock; | 90 | struct mutex rlock; |
91 | struct mutex plock; | ||
90 | wait_queue_head_t wait; | 92 | wait_queue_head_t wait; |
91 | struct work_struct rxwork; | 93 | struct work_struct rxwork; |
92 | int werr; | 94 | int werr; |
@@ -205,7 +207,7 @@ static void wdm_int_callback(struct urb *urb) | |||
205 | req->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE; | 207 | req->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE; |
206 | req->wValue = 0; | 208 | req->wValue = 0; |
207 | req->wIndex = desc->inum; | 209 | req->wIndex = desc->inum; |
208 | req->wLength = cpu_to_le16(desc->bMaxPacketSize0); | 210 | req->wLength = cpu_to_le16(desc->wMaxCommand); |
209 | 211 | ||
210 | usb_fill_control_urb( | 212 | usb_fill_control_urb( |
211 | desc->response, | 213 | desc->response, |
@@ -214,7 +216,7 @@ static void wdm_int_callback(struct urb *urb) | |||
214 | usb_rcvctrlpipe(interface_to_usbdev(desc->intf), 0), | 216 | usb_rcvctrlpipe(interface_to_usbdev(desc->intf), 0), |
215 | (unsigned char *)req, | 217 | (unsigned char *)req, |
216 | desc->inbuf, | 218 | desc->inbuf, |
217 | desc->bMaxPacketSize0, | 219 | desc->wMaxCommand, |
218 | wdm_in_callback, | 220 | wdm_in_callback, |
219 | desc | 221 | desc |
220 | ); | 222 | ); |
@@ -247,6 +249,7 @@ exit: | |||
247 | 249 | ||
248 | static void kill_urbs(struct wdm_device *desc) | 250 | static void kill_urbs(struct wdm_device *desc) |
249 | { | 251 | { |
252 | /* the order here is essential */ | ||
250 | usb_kill_urb(desc->command); | 253 | usb_kill_urb(desc->command); |
251 | usb_kill_urb(desc->validity); | 254 | usb_kill_urb(desc->validity); |
252 | usb_kill_urb(desc->response); | 255 | usb_kill_urb(desc->response); |
@@ -266,7 +269,7 @@ static void cleanup(struct wdm_device *desc) | |||
266 | desc->sbuf, | 269 | desc->sbuf, |
267 | desc->validity->transfer_dma); | 270 | desc->validity->transfer_dma); |
268 | usb_buffer_free(interface_to_usbdev(desc->intf), | 271 | usb_buffer_free(interface_to_usbdev(desc->intf), |
269 | desc->wMaxPacketSize, | 272 | desc->wMaxCommand, |
270 | desc->inbuf, | 273 | desc->inbuf, |
271 | desc->response->transfer_dma); | 274 | desc->response->transfer_dma); |
272 | kfree(desc->orq); | 275 | kfree(desc->orq); |
@@ -299,6 +302,9 @@ static ssize_t wdm_write | |||
299 | if (r) | 302 | if (r) |
300 | goto outnl; | 303 | goto outnl; |
301 | 304 | ||
305 | r = usb_autopm_get_interface(desc->intf); | ||
306 | if (r < 0) | ||
307 | goto outnp; | ||
302 | r = wait_event_interruptible(desc->wait, !test_bit(WDM_IN_USE, | 308 | r = wait_event_interruptible(desc->wait, !test_bit(WDM_IN_USE, |
303 | &desc->flags)); | 309 | &desc->flags)); |
304 | if (r < 0) | 310 | if (r < 0) |
@@ -347,11 +353,14 @@ static ssize_t wdm_write | |||
347 | if (rv < 0) { | 353 | if (rv < 0) { |
348 | kfree(buf); | 354 | kfree(buf); |
349 | clear_bit(WDM_IN_USE, &desc->flags); | 355 | clear_bit(WDM_IN_USE, &desc->flags); |
356 | err("Tx URB error: %d", rv); | ||
350 | } else { | 357 | } else { |
351 | dev_dbg(&desc->intf->dev, "Tx URB has been submitted index=%d", | 358 | dev_dbg(&desc->intf->dev, "Tx URB has been submitted index=%d", |
352 | req->wIndex); | 359 | req->wIndex); |
353 | } | 360 | } |
354 | out: | 361 | out: |
362 | usb_autopm_put_interface(desc->intf); | ||
363 | outnp: | ||
355 | mutex_unlock(&desc->wlock); | 364 | mutex_unlock(&desc->wlock); |
356 | outnl: | 365 | outnl: |
357 | return rv < 0 ? rv : count; | 366 | return rv < 0 ? rv : count; |
@@ -376,6 +385,11 @@ retry: | |||
376 | rv = wait_event_interruptible(desc->wait, | 385 | rv = wait_event_interruptible(desc->wait, |
377 | test_bit(WDM_READ, &desc->flags)); | 386 | test_bit(WDM_READ, &desc->flags)); |
378 | 387 | ||
388 | if (test_bit(WDM_DISCONNECTING, &desc->flags)) { | ||
389 | rv = -ENODEV; | ||
390 | goto err; | ||
391 | } | ||
392 | usb_mark_last_busy(interface_to_usbdev(desc->intf)); | ||
379 | if (rv < 0) { | 393 | if (rv < 0) { |
380 | rv = -ERESTARTSYS; | 394 | rv = -ERESTARTSYS; |
381 | goto err; | 395 | goto err; |
@@ -418,6 +432,9 @@ retry: | |||
418 | desc->ubuf[i] = desc->ubuf[i + cntr]; | 432 | desc->ubuf[i] = desc->ubuf[i + cntr]; |
419 | 433 | ||
420 | desc->length -= cntr; | 434 | desc->length -= cntr; |
435 | /* in case we had outstanding data */ | ||
436 | if (!desc->length) | ||
437 | clear_bit(WDM_READ, &desc->flags); | ||
421 | rv = cntr; | 438 | rv = cntr; |
422 | 439 | ||
423 | err: | 440 | err: |
@@ -480,18 +497,28 @@ static int wdm_open(struct inode *inode, struct file *file) | |||
480 | if (test_bit(WDM_DISCONNECTING, &desc->flags)) | 497 | if (test_bit(WDM_DISCONNECTING, &desc->flags)) |
481 | goto out; | 498 | goto out; |
482 | 499 | ||
483 | desc->count++; | 500 | ; |
484 | file->private_data = desc; | 501 | file->private_data = desc; |
485 | 502 | ||
486 | rv = usb_submit_urb(desc->validity, GFP_KERNEL); | 503 | rv = usb_autopm_get_interface(desc->intf); |
487 | |||
488 | if (rv < 0) { | 504 | if (rv < 0) { |
489 | desc->count--; | 505 | err("Error autopm - %d", rv); |
490 | err("Error submitting int urb - %d", rv); | ||
491 | goto out; | 506 | goto out; |
492 | } | 507 | } |
493 | rv = 0; | 508 | intf->needs_remote_wakeup = 1; |
494 | 509 | ||
510 | mutex_lock(&desc->plock); | ||
511 | if (!desc->count++) { | ||
512 | rv = usb_submit_urb(desc->validity, GFP_KERNEL); | ||
513 | if (rv < 0) { | ||
514 | desc->count--; | ||
515 | err("Error submitting int urb - %d", rv); | ||
516 | } | ||
517 | } else { | ||
518 | rv = 0; | ||
519 | } | ||
520 | mutex_unlock(&desc->plock); | ||
521 | usb_autopm_put_interface(desc->intf); | ||
495 | out: | 522 | out: |
496 | mutex_unlock(&wdm_mutex); | 523 | mutex_unlock(&wdm_mutex); |
497 | return rv; | 524 | return rv; |
@@ -502,10 +529,15 @@ static int wdm_release(struct inode *inode, struct file *file) | |||
502 | struct wdm_device *desc = file->private_data; | 529 | struct wdm_device *desc = file->private_data; |
503 | 530 | ||
504 | mutex_lock(&wdm_mutex); | 531 | mutex_lock(&wdm_mutex); |
532 | mutex_lock(&desc->plock); | ||
505 | desc->count--; | 533 | desc->count--; |
534 | mutex_unlock(&desc->plock); | ||
535 | |||
506 | if (!desc->count) { | 536 | if (!desc->count) { |
507 | dev_dbg(&desc->intf->dev, "wdm_release: cleanup"); | 537 | dev_dbg(&desc->intf->dev, "wdm_release: cleanup"); |
508 | kill_urbs(desc); | 538 | kill_urbs(desc); |
539 | if (!test_bit(WDM_DISCONNECTING, &desc->flags)) | ||
540 | desc->intf->needs_remote_wakeup = 0; | ||
509 | } | 541 | } |
510 | mutex_unlock(&wdm_mutex); | 542 | mutex_unlock(&wdm_mutex); |
511 | return 0; | 543 | return 0; |
@@ -597,6 +629,7 @@ next_desc: | |||
597 | goto out; | 629 | goto out; |
598 | mutex_init(&desc->wlock); | 630 | mutex_init(&desc->wlock); |
599 | mutex_init(&desc->rlock); | 631 | mutex_init(&desc->rlock); |
632 | mutex_init(&desc->plock); | ||
600 | spin_lock_init(&desc->iuspin); | 633 | spin_lock_init(&desc->iuspin); |
601 | init_waitqueue_head(&desc->wait); | 634 | init_waitqueue_head(&desc->wait); |
602 | desc->wMaxCommand = maxcom; | 635 | desc->wMaxCommand = maxcom; |
@@ -698,6 +731,7 @@ static void wdm_disconnect(struct usb_interface *intf) | |||
698 | spin_lock_irqsave(&desc->iuspin, flags); | 731 | spin_lock_irqsave(&desc->iuspin, flags); |
699 | set_bit(WDM_DISCONNECTING, &desc->flags); | 732 | set_bit(WDM_DISCONNECTING, &desc->flags); |
700 | set_bit(WDM_READ, &desc->flags); | 733 | set_bit(WDM_READ, &desc->flags); |
734 | /* to terminate pending flushes */ | ||
701 | clear_bit(WDM_IN_USE, &desc->flags); | 735 | clear_bit(WDM_IN_USE, &desc->flags); |
702 | spin_unlock_irqrestore(&desc->iuspin, flags); | 736 | spin_unlock_irqrestore(&desc->iuspin, flags); |
703 | cancel_work_sync(&desc->rxwork); | 737 | cancel_work_sync(&desc->rxwork); |
@@ -708,11 +742,81 @@ static void wdm_disconnect(struct usb_interface *intf) | |||
708 | mutex_unlock(&wdm_mutex); | 742 | mutex_unlock(&wdm_mutex); |
709 | } | 743 | } |
710 | 744 | ||
745 | static int wdm_suspend(struct usb_interface *intf, pm_message_t message) | ||
746 | { | ||
747 | struct wdm_device *desc = usb_get_intfdata(intf); | ||
748 | int rv = 0; | ||
749 | |||
750 | dev_dbg(&desc->intf->dev, "wdm%d_suspend\n", intf->minor); | ||
751 | |||
752 | mutex_lock(&desc->plock); | ||
753 | #ifdef CONFIG_PM | ||
754 | if (interface_to_usbdev(desc->intf)->auto_pm && test_bit(WDM_IN_USE, &desc->flags)) { | ||
755 | rv = -EBUSY; | ||
756 | } else { | ||
757 | #endif | ||
758 | cancel_work_sync(&desc->rxwork); | ||
759 | kill_urbs(desc); | ||
760 | #ifdef CONFIG_PM | ||
761 | } | ||
762 | #endif | ||
763 | mutex_unlock(&desc->plock); | ||
764 | |||
765 | return rv; | ||
766 | } | ||
767 | |||
768 | static int recover_from_urb_loss(struct wdm_device *desc) | ||
769 | { | ||
770 | int rv = 0; | ||
771 | |||
772 | if (desc->count) { | ||
773 | rv = usb_submit_urb(desc->validity, GFP_NOIO); | ||
774 | if (rv < 0) | ||
775 | err("Error resume submitting int urb - %d", rv); | ||
776 | } | ||
777 | return rv; | ||
778 | } | ||
779 | static int wdm_resume(struct usb_interface *intf) | ||
780 | { | ||
781 | struct wdm_device *desc = usb_get_intfdata(intf); | ||
782 | int rv; | ||
783 | |||
784 | dev_dbg(&desc->intf->dev, "wdm%d_resume\n", intf->minor); | ||
785 | mutex_lock(&desc->plock); | ||
786 | rv = recover_from_urb_loss(desc); | ||
787 | mutex_unlock(&desc->plock); | ||
788 | return rv; | ||
789 | } | ||
790 | |||
791 | static int wdm_pre_reset(struct usb_interface *intf) | ||
792 | { | ||
793 | struct wdm_device *desc = usb_get_intfdata(intf); | ||
794 | |||
795 | mutex_lock(&desc->plock); | ||
796 | return 0; | ||
797 | } | ||
798 | |||
799 | static int wdm_post_reset(struct usb_interface *intf) | ||
800 | { | ||
801 | struct wdm_device *desc = usb_get_intfdata(intf); | ||
802 | int rv; | ||
803 | |||
804 | rv = recover_from_urb_loss(desc); | ||
805 | mutex_unlock(&desc->plock); | ||
806 | return 0; | ||
807 | } | ||
808 | |||
711 | static struct usb_driver wdm_driver = { | 809 | static struct usb_driver wdm_driver = { |
712 | .name = "cdc_wdm", | 810 | .name = "cdc_wdm", |
713 | .probe = wdm_probe, | 811 | .probe = wdm_probe, |
714 | .disconnect = wdm_disconnect, | 812 | .disconnect = wdm_disconnect, |
813 | .suspend = wdm_suspend, | ||
814 | .resume = wdm_resume, | ||
815 | .reset_resume = wdm_resume, | ||
816 | .pre_reset = wdm_pre_reset, | ||
817 | .post_reset = wdm_post_reset, | ||
715 | .id_table = wdm_ids, | 818 | .id_table = wdm_ids, |
819 | .supports_autosuspend = 1, | ||
716 | }; | 820 | }; |
717 | 821 | ||
718 | /* --- low level module stuff --- */ | 822 | /* --- low level module stuff --- */ |
@@ -735,6 +839,5 @@ module_init(wdm_init); | |||
735 | module_exit(wdm_exit); | 839 | module_exit(wdm_exit); |
736 | 840 | ||
737 | MODULE_AUTHOR(DRIVER_AUTHOR); | 841 | MODULE_AUTHOR(DRIVER_AUTHOR); |
738 | MODULE_DESCRIPTION("USB Abstract Control Model driver for " | 842 | MODULE_DESCRIPTION(DRIVER_DESC); |
739 | "USB WCM Device Management"); | ||
740 | MODULE_LICENSE("GPL"); | 843 | MODULE_LICENSE("GPL"); |