diff options
Diffstat (limited to 'drivers/usb/class/cdc-wdm.c')
-rw-r--r-- | drivers/usb/class/cdc-wdm.c | 134 |
1 files changed, 78 insertions, 56 deletions
diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 18aafcb08fc..189141ca4e0 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -52,7 +52,8 @@ MODULE_DEVICE_TABLE (usb, wdm_ids); #define WDM_READ 4 #define WDM_INT_STALL 5 #define WDM_POLL_RUNNING 6 - +#define WDM_RESPONDING 7 +#define WDM_SUSPENDING 8 #define WDM_MAX 16 @@ -87,9 +88,7 @@ struct wdm_device { int count; dma_addr_t shandle; dma_addr_t ihandle; - struct mutex wlock; - struct mutex rlock; - struct mutex plock; + struct mutex lock; wait_queue_head_t wait; struct work_struct rxwork; int werr; @@ -117,21 +116,22 @@ static void wdm_in_callback(struct urb *urb) int status = urb->status; spin_lock(&desc->iuspin); + clear_bit(WDM_RESPONDING, &desc->flags); if (status) { switch (status) { case -ENOENT: dev_dbg(&desc->intf->dev, "nonzero urb status received: -ENOENT"); - break; + goto skip_error; case -ECONNRESET: dev_dbg(&desc->intf->dev, "nonzero urb status received: -ECONNRESET"); - break; + goto skip_error; case -ESHUTDOWN: dev_dbg(&desc->intf->dev, "nonzero urb status received: -ESHUTDOWN"); - break; + goto skip_error; case -EPIPE: dev_err(&desc->intf->dev, "nonzero urb status received: -EPIPE\n"); @@ -147,6 +147,7 @@ static void wdm_in_callback(struct urb *urb) desc->reslength = urb->actual_length; memmove(desc->ubuf + desc->length, desc->inbuf, desc->reslength); desc->length += desc->reslength; +skip_error: wake_up(&desc->wait); set_bit(WDM_READ, &desc->flags); @@ -229,13 +230,16 @@ static void wdm_int_callback(struct urb *urb) desc->response->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; spin_lock(&desc->iuspin); clear_bit(WDM_READ, &desc->flags); - if (!test_bit(WDM_DISCONNECTING, &desc->flags)) { + set_bit(WDM_RESPONDING, &desc->flags); + if (!test_bit(WDM_DISCONNECTING, &desc->flags) + && !test_bit(WDM_SUSPENDING, &desc->flags)) { rv = usb_submit_urb(desc->response, GFP_ATOMIC); dev_dbg(&desc->intf->dev, "%s: usb_submit_urb %d", __func__, rv); } spin_unlock(&desc->iuspin); if (rv < 0) { + clear_bit(WDM_RESPONDING, &desc->flags); if (rv == -EPERM) return; if (rv == -ENOMEM) { @@ -305,14 +309,38 @@ static ssize_t wdm_write if (we < 0) return -EIO; - r = mutex_lock_interruptible(&desc->wlock); /* concurrent writes */ + desc->outbuf = buf = kmalloc(count, GFP_KERNEL); + if (!buf) { + rv = -ENOMEM; + goto outnl; + } + + r = copy_from_user(buf, buffer, count); + if (r > 0) { + kfree(buf); + rv = -EFAULT; + goto outnl; + } + + /* concurrent writes and disconnect */ + r = mutex_lock_interruptible(&desc->lock); rv = -ERESTARTSYS; - if (r) + if (r) { + kfree(buf); goto outnl; + } + + if (test_bit(WDM_DISCONNECTING, &desc->flags)) { + kfree(buf); + rv = -ENODEV; + goto outnp; + } r = usb_autopm_get_interface(desc->intf); - if (r < 0) + if (r < 0) { + kfree(buf); goto outnp; + } if (!file->f_flags && O_NONBLOCK) r = wait_event_interruptible(desc->wait, !test_bit(WDM_IN_USE, @@ -320,24 +348,8 @@ static ssize_t wdm_write else if (test_bit(WDM_IN_USE, &desc->flags)) r = -EAGAIN; - if (r < 0) - goto out; - - if (test_bit(WDM_DISCONNECTING, &desc->flags)) { - rv = -ENODEV; - goto out; - } - - desc->outbuf = buf = kmalloc(count, GFP_KERNEL); - if (!buf) { - rv = -ENOMEM; - goto out; - } - - r = copy_from_user(buf, buffer, count); - if (r > 0) { + if (r < 0) { kfree(buf); - rv = -EFAULT; goto out; } @@ -374,7 +386,7 @@ static ssize_t wdm_write out: usb_autopm_put_interface(desc->intf); outnp: - mutex_unlock(&desc->wlock); + mutex_unlock(&desc->lock); outnl: return rv < 0 ? rv : count; } @@ -387,7 +399,7 @@ static ssize_t wdm_read struct wdm_device *desc = file->private_data; - rv = mutex_lock_interruptible(&desc->rlock); /*concurrent reads */ + rv = mutex_lock_interruptible(&desc->lock); /*concurrent reads */ if (rv < 0) return -ERESTARTSYS; @@ -424,11 +436,8 @@ retry: spin_lock_irq(&desc->iuspin); if (desc->rerr) { /* read completed, error happened */ - int t = desc->rerr; desc->rerr = 0; spin_unlock_irq(&desc->iuspin); - dev_err(&desc->intf->dev, - "reading had resulted in %d\n", t); rv = -EIO; goto err; } @@ -465,9 +474,7 @@ retry: rv = cntr; err: - mutex_unlock(&desc->rlock); - if (rv < 0 && rv != -EAGAIN) - dev_err(&desc->intf->dev, "wdm_read: exit error\n"); + mutex_unlock(&desc->lock); return rv; } @@ -533,7 +540,7 @@ static int wdm_open(struct inode *inode, struct file *file) } intf->needs_remote_wakeup = 1; - mutex_lock(&desc->plock); + mutex_lock(&desc->lock); if (!desc->count++) { rv = usb_submit_urb(desc->validity, GFP_KERNEL); if (rv < 0) { @@ -544,7 +551,7 @@ static int wdm_open(struct inode *inode, struct file *file) } else { rv = 0; } - mutex_unlock(&desc->plock); + mutex_unlock(&desc->lock); usb_autopm_put_interface(desc->intf); out: mutex_unlock(&wdm_mutex); @@ -556,9 +563,9 @@ static int wdm_release(struct inode *inode, struct file *file) struct wdm_device *desc = file->private_data; mutex_lock(&wdm_mutex); - mutex_lock(&desc->plock); + mutex_lock(&desc->lock); desc->count--; - mutex_unlock(&desc->plock); + mutex_unlock(&desc->lock); if (!desc->count) { dev_dbg(&desc->intf->dev, "wdm_release: cleanup"); @@ -655,9 +662,7 @@ next_desc: desc = kzalloc(sizeof(struct wdm_device), GFP_KERNEL); if (!desc) goto out; - mutex_init(&desc->wlock); - mutex_init(&desc->rlock); - mutex_init(&desc->plock); + mutex_init(&desc->lock); spin_lock_init(&desc->iuspin); init_waitqueue_head(&desc->wait); desc->wMaxCommand = maxcom; @@ -771,14 +776,17 @@ static void wdm_disconnect(struct usb_interface *intf) /* to terminate pending flushes */ clear_bit(WDM_IN_USE, &desc->flags); spin_unlock_irqrestore(&desc->iuspin, flags); - cancel_work_sync(&desc->rxwork); + mutex_lock(&desc->lock); kill_urbs(desc); + cancel_work_sync(&desc->rxwork); + mutex_unlock(&desc->lock); wake_up_all(&desc->wait); if (!desc->count) cleanup(desc); mutex_unlock(&wdm_mutex); } +#ifdef CONFIG_PM static int wdm_suspend(struct usb_interface *intf, pm_message_t message) { struct wdm_device *desc = usb_get_intfdata(intf); @@ -786,22 +794,30 @@ static int wdm_suspend(struct usb_interface *intf, pm_message_t message) dev_dbg(&desc->intf->dev, "wdm%d_suspend\n", intf->minor); - mutex_lock(&desc->plock); -#ifdef CONFIG_PM + /* if this is an autosuspend the caller does the locking */ + if (!(message.event & PM_EVENT_AUTO)) + mutex_lock(&desc->lock); + spin_lock_irq(&desc->iuspin); + if ((message.event & PM_EVENT_AUTO) && - test_bit(WDM_IN_USE, &desc->flags)) { + (test_bit(WDM_IN_USE, &desc->flags) + || test_bit(WDM_RESPONDING, &desc->flags))) { + spin_unlock_irq(&desc->iuspin); rv = -EBUSY; } else { -#endif - cancel_work_sync(&desc->rxwork); + + set_bit(WDM_SUSPENDING, &desc->flags); + spin_unlock_irq(&desc->iuspin); + /* callback submits work - order is essential */ kill_urbs(desc); -#ifdef CONFIG_PM + cancel_work_sync(&desc->rxwork); } -#endif - mutex_unlock(&desc->plock); + if (!(message.event & PM_EVENT_AUTO)) + mutex_unlock(&desc->lock); return rv; } +#endif static int recover_from_urb_loss(struct wdm_device *desc) { @@ -815,23 +831,27 @@ static int recover_from_urb_loss(struct wdm_device *desc) } return rv; } + +#ifdef CONFIG_PM static int wdm_resume(struct usb_interface *intf) { struct wdm_device *desc = usb_get_intfdata(intf); int rv; dev_dbg(&desc->intf->dev, "wdm%d_resume\n", intf->minor); - mutex_lock(&desc->plock); + + clear_bit(WDM_SUSPENDING, &desc->flags); rv = recover_from_urb_loss(desc); - mutex_unlock(&desc->plock); + return rv; } +#endif static int wdm_pre_reset(struct usb_interface *intf) { struct wdm_device *desc = usb_get_intfdata(intf); - mutex_lock(&desc->plock); + mutex_lock(&desc->lock); return 0; } @@ -841,7 +861,7 @@ static int wdm_post_reset(struct usb_interface *intf) int rv; rv = recover_from_urb_loss(desc); - mutex_unlock(&desc->plock); + mutex_unlock(&desc->lock); return 0; } @@ -849,9 +869,11 @@ static struct usb_driver wdm_driver = { .name = "cdc_wdm", .probe = wdm_probe, .disconnect = wdm_disconnect, +#ifdef CONFIG_PM .suspend = wdm_suspend, .resume = wdm_resume, .reset_resume = wdm_resume, +#endif .pre_reset = wdm_pre_reset, .post_reset = wdm_post_reset, .id_table = wdm_ids, |