diff options
author | Oliver Neukum <oliver@neukum.org> | 2010-02-27 14:54:24 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2010-03-19 10:24:14 -0400 |
commit | 860e41a71c1731e79e1920dc42676bafc925af5e (patch) | |
tree | 3374857a0a3da76cf37ccde54c3fe3230223a034 /drivers/usb | |
parent | aa4714560b4ea359bb7830188ebd06bce71bcdea (diff) |
usb: cdc-wdm: Fix race between write and disconnect
Unify mutexes to fix a race between write and disconnect
and shift the test for disconnection to always report it.
Signed-off-by: Oliver Neukum <neukum@b1-systems.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/class/cdc-wdm.c | 84 |
1 files changed, 45 insertions, 39 deletions
diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 18aafcb08fc8..cf1c5fb918dc 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c | |||
@@ -87,9 +87,7 @@ struct wdm_device { | |||
87 | int count; | 87 | int count; |
88 | dma_addr_t shandle; | 88 | dma_addr_t shandle; |
89 | dma_addr_t ihandle; | 89 | dma_addr_t ihandle; |
90 | struct mutex wlock; | 90 | struct mutex lock; |
91 | struct mutex rlock; | ||
92 | struct mutex plock; | ||
93 | wait_queue_head_t wait; | 91 | wait_queue_head_t wait; |
94 | struct work_struct rxwork; | 92 | struct work_struct rxwork; |
95 | int werr; | 93 | int werr; |
@@ -305,14 +303,38 @@ static ssize_t wdm_write | |||
305 | if (we < 0) | 303 | if (we < 0) |
306 | return -EIO; | 304 | return -EIO; |
307 | 305 | ||
308 | r = mutex_lock_interruptible(&desc->wlock); /* concurrent writes */ | 306 | desc->outbuf = buf = kmalloc(count, GFP_KERNEL); |
307 | if (!buf) { | ||
308 | rv = -ENOMEM; | ||
309 | goto outnl; | ||
310 | } | ||
311 | |||
312 | r = copy_from_user(buf, buffer, count); | ||
313 | if (r > 0) { | ||
314 | kfree(buf); | ||
315 | rv = -EFAULT; | ||
316 | goto outnl; | ||
317 | } | ||
318 | |||
319 | /* concurrent writes and disconnect */ | ||
320 | r = mutex_lock_interruptible(&desc->lock); | ||
309 | rv = -ERESTARTSYS; | 321 | rv = -ERESTARTSYS; |
310 | if (r) | 322 | if (r) { |
323 | kfree(buf); | ||
311 | goto outnl; | 324 | goto outnl; |
325 | } | ||
326 | |||
327 | if (test_bit(WDM_DISCONNECTING, &desc->flags)) { | ||
328 | kfree(buf); | ||
329 | rv = -ENODEV; | ||
330 | goto outnp; | ||
331 | } | ||
312 | 332 | ||
313 | r = usb_autopm_get_interface(desc->intf); | 333 | r = usb_autopm_get_interface(desc->intf); |
314 | if (r < 0) | 334 | if (r < 0) { |
335 | kfree(buf); | ||
315 | goto outnp; | 336 | goto outnp; |
337 | } | ||
316 | 338 | ||
317 | if (!file->f_flags && O_NONBLOCK) | 339 | if (!file->f_flags && O_NONBLOCK) |
318 | r = wait_event_interruptible(desc->wait, !test_bit(WDM_IN_USE, | 340 | r = wait_event_interruptible(desc->wait, !test_bit(WDM_IN_USE, |
@@ -320,24 +342,8 @@ static ssize_t wdm_write | |||
320 | else | 342 | else |
321 | if (test_bit(WDM_IN_USE, &desc->flags)) | 343 | if (test_bit(WDM_IN_USE, &desc->flags)) |
322 | r = -EAGAIN; | 344 | r = -EAGAIN; |
323 | if (r < 0) | 345 | if (r < 0) { |
324 | goto out; | ||
325 | |||
326 | if (test_bit(WDM_DISCONNECTING, &desc->flags)) { | ||
327 | rv = -ENODEV; | ||
328 | goto out; | ||
329 | } | ||
330 | |||
331 | desc->outbuf = buf = kmalloc(count, GFP_KERNEL); | ||
332 | if (!buf) { | ||
333 | rv = -ENOMEM; | ||
334 | goto out; | ||
335 | } | ||
336 | |||
337 | r = copy_from_user(buf, buffer, count); | ||
338 | if (r > 0) { | ||
339 | kfree(buf); | 346 | kfree(buf); |
340 | rv = -EFAULT; | ||
341 | goto out; | 347 | goto out; |
342 | } | 348 | } |
343 | 349 | ||
@@ -374,7 +380,7 @@ static ssize_t wdm_write | |||
374 | out: | 380 | out: |
375 | usb_autopm_put_interface(desc->intf); | 381 | usb_autopm_put_interface(desc->intf); |
376 | outnp: | 382 | outnp: |
377 | mutex_unlock(&desc->wlock); | 383 | mutex_unlock(&desc->lock); |
378 | outnl: | 384 | outnl: |
379 | return rv < 0 ? rv : count; | 385 | return rv < 0 ? rv : count; |
380 | } | 386 | } |
@@ -387,7 +393,7 @@ static ssize_t wdm_read | |||
387 | struct wdm_device *desc = file->private_data; | 393 | struct wdm_device *desc = file->private_data; |
388 | 394 | ||
389 | 395 | ||
390 | rv = mutex_lock_interruptible(&desc->rlock); /*concurrent reads */ | 396 | rv = mutex_lock_interruptible(&desc->lock); /*concurrent reads */ |
391 | if (rv < 0) | 397 | if (rv < 0) |
392 | return -ERESTARTSYS; | 398 | return -ERESTARTSYS; |
393 | 399 | ||
@@ -465,7 +471,7 @@ retry: | |||
465 | rv = cntr; | 471 | rv = cntr; |
466 | 472 | ||
467 | err: | 473 | err: |
468 | mutex_unlock(&desc->rlock); | 474 | mutex_unlock(&desc->lock); |
469 | if (rv < 0 && rv != -EAGAIN) | 475 | if (rv < 0 && rv != -EAGAIN) |
470 | dev_err(&desc->intf->dev, "wdm_read: exit error\n"); | 476 | dev_err(&desc->intf->dev, "wdm_read: exit error\n"); |
471 | return rv; | 477 | return rv; |
@@ -533,7 +539,7 @@ static int wdm_open(struct inode *inode, struct file *file) | |||
533 | } | 539 | } |
534 | intf->needs_remote_wakeup = 1; | 540 | intf->needs_remote_wakeup = 1; |
535 | 541 | ||
536 | mutex_lock(&desc->plock); | 542 | mutex_lock(&desc->lock); |
537 | if (!desc->count++) { | 543 | if (!desc->count++) { |
538 | rv = usb_submit_urb(desc->validity, GFP_KERNEL); | 544 | rv = usb_submit_urb(desc->validity, GFP_KERNEL); |
539 | if (rv < 0) { | 545 | if (rv < 0) { |
@@ -544,7 +550,7 @@ static int wdm_open(struct inode *inode, struct file *file) | |||
544 | } else { | 550 | } else { |
545 | rv = 0; | 551 | rv = 0; |
546 | } | 552 | } |
547 | mutex_unlock(&desc->plock); | 553 | mutex_unlock(&desc->lock); |
548 | usb_autopm_put_interface(desc->intf); | 554 | usb_autopm_put_interface(desc->intf); |
549 | out: | 555 | out: |
550 | mutex_unlock(&wdm_mutex); | 556 | mutex_unlock(&wdm_mutex); |
@@ -556,9 +562,9 @@ static int wdm_release(struct inode *inode, struct file *file) | |||
556 | struct wdm_device *desc = file->private_data; | 562 | struct wdm_device *desc = file->private_data; |
557 | 563 | ||
558 | mutex_lock(&wdm_mutex); | 564 | mutex_lock(&wdm_mutex); |
559 | mutex_lock(&desc->plock); | 565 | mutex_lock(&desc->lock); |
560 | desc->count--; | 566 | desc->count--; |
561 | mutex_unlock(&desc->plock); | 567 | mutex_unlock(&desc->lock); |
562 | 568 | ||
563 | if (!desc->count) { | 569 | if (!desc->count) { |
564 | dev_dbg(&desc->intf->dev, "wdm_release: cleanup"); | 570 | dev_dbg(&desc->intf->dev, "wdm_release: cleanup"); |
@@ -655,9 +661,7 @@ next_desc: | |||
655 | desc = kzalloc(sizeof(struct wdm_device), GFP_KERNEL); | 661 | desc = kzalloc(sizeof(struct wdm_device), GFP_KERNEL); |
656 | if (!desc) | 662 | if (!desc) |
657 | goto out; | 663 | goto out; |
658 | mutex_init(&desc->wlock); | 664 | mutex_init(&desc->lock); |
659 | mutex_init(&desc->rlock); | ||
660 | mutex_init(&desc->plock); | ||
661 | spin_lock_init(&desc->iuspin); | 665 | spin_lock_init(&desc->iuspin); |
662 | init_waitqueue_head(&desc->wait); | 666 | init_waitqueue_head(&desc->wait); |
663 | desc->wMaxCommand = maxcom; | 667 | desc->wMaxCommand = maxcom; |
@@ -772,7 +776,9 @@ static void wdm_disconnect(struct usb_interface *intf) | |||
772 | clear_bit(WDM_IN_USE, &desc->flags); | 776 | clear_bit(WDM_IN_USE, &desc->flags); |
773 | spin_unlock_irqrestore(&desc->iuspin, flags); | 777 | spin_unlock_irqrestore(&desc->iuspin, flags); |
774 | cancel_work_sync(&desc->rxwork); | 778 | cancel_work_sync(&desc->rxwork); |
779 | mutex_lock(&desc->lock); | ||
775 | kill_urbs(desc); | 780 | kill_urbs(desc); |
781 | mutex_unlock(&desc->lock); | ||
776 | wake_up_all(&desc->wait); | 782 | wake_up_all(&desc->wait); |
777 | if (!desc->count) | 783 | if (!desc->count) |
778 | cleanup(desc); | 784 | cleanup(desc); |
@@ -786,7 +792,7 @@ static int wdm_suspend(struct usb_interface *intf, pm_message_t message) | |||
786 | 792 | ||
787 | dev_dbg(&desc->intf->dev, "wdm%d_suspend\n", intf->minor); | 793 | dev_dbg(&desc->intf->dev, "wdm%d_suspend\n", intf->minor); |
788 | 794 | ||
789 | mutex_lock(&desc->plock); | 795 | mutex_lock(&desc->lock); |
790 | #ifdef CONFIG_PM | 796 | #ifdef CONFIG_PM |
791 | if ((message.event & PM_EVENT_AUTO) && | 797 | if ((message.event & PM_EVENT_AUTO) && |
792 | test_bit(WDM_IN_USE, &desc->flags)) { | 798 | test_bit(WDM_IN_USE, &desc->flags)) { |
@@ -798,7 +804,7 @@ static int wdm_suspend(struct usb_interface *intf, pm_message_t message) | |||
798 | #ifdef CONFIG_PM | 804 | #ifdef CONFIG_PM |
799 | } | 805 | } |
800 | #endif | 806 | #endif |
801 | mutex_unlock(&desc->plock); | 807 | mutex_unlock(&desc->lock); |
802 | 808 | ||
803 | return rv; | 809 | return rv; |
804 | } | 810 | } |
@@ -821,9 +827,9 @@ static int wdm_resume(struct usb_interface *intf) | |||
821 | int rv; | 827 | int rv; |
822 | 828 | ||
823 | dev_dbg(&desc->intf->dev, "wdm%d_resume\n", intf->minor); | 829 | dev_dbg(&desc->intf->dev, "wdm%d_resume\n", intf->minor); |
824 | mutex_lock(&desc->plock); | 830 | mutex_lock(&desc->lock); |
825 | rv = recover_from_urb_loss(desc); | 831 | rv = recover_from_urb_loss(desc); |
826 | mutex_unlock(&desc->plock); | 832 | mutex_unlock(&desc->lock); |
827 | return rv; | 833 | return rv; |
828 | } | 834 | } |
829 | 835 | ||
@@ -831,7 +837,7 @@ static int wdm_pre_reset(struct usb_interface *intf) | |||
831 | { | 837 | { |
832 | struct wdm_device *desc = usb_get_intfdata(intf); | 838 | struct wdm_device *desc = usb_get_intfdata(intf); |
833 | 839 | ||
834 | mutex_lock(&desc->plock); | 840 | mutex_lock(&desc->lock); |
835 | return 0; | 841 | return 0; |
836 | } | 842 | } |
837 | 843 | ||
@@ -841,7 +847,7 @@ static int wdm_post_reset(struct usb_interface *intf) | |||
841 | int rv; | 847 | int rv; |
842 | 848 | ||
843 | rv = recover_from_urb_loss(desc); | 849 | rv = recover_from_urb_loss(desc); |
844 | mutex_unlock(&desc->plock); | 850 | mutex_unlock(&desc->lock); |
845 | return 0; | 851 | return 0; |
846 | } | 852 | } |
847 | 853 | ||