From f0730924e9e32bb8935c60040a26d94179355088 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Wed, 3 Mar 2010 00:37:56 +0100 Subject: USB: cdc-acm: Fix stupid NULL pointer in resume() Stupid logic bug passing a just nulled pointer Signed-off-by: Oliver Neukum Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb/class') diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 975d556b478..be6331e2c27 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -1441,7 +1441,7 @@ static int acm_resume(struct usb_interface *intf) wb = acm->delayed_wb; acm->delayed_wb = NULL; spin_unlock_irq(&acm->write_lock); - acm_start_wb(acm, acm->delayed_wb); + acm_start_wb(acm, wb); } else { spin_unlock_irq(&acm->write_lock); } -- cgit From 860e41a71c1731e79e1920dc42676bafc925af5e Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Sat, 27 Feb 2010 20:54:24 +0100 Subject: 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 Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-wdm.c | 84 ++++++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 39 deletions(-) (limited to 'drivers/usb/class') diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 18aafcb08fc..cf1c5fb918d 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -87,9 +87,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; @@ -305,14 +303,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 +342,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 +380,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 +393,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; @@ -465,7 +471,7 @@ retry: rv = cntr; err: - mutex_unlock(&desc->rlock); + mutex_unlock(&desc->lock); if (rv < 0 && rv != -EAGAIN) dev_err(&desc->intf->dev, "wdm_read: exit error\n"); return rv; @@ -533,7 +539,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 +550,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 +562,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 +661,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; @@ -772,7 +776,9 @@ static void wdm_disconnect(struct usb_interface *intf) 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); + mutex_unlock(&desc->lock); wake_up_all(&desc->wait); if (!desc->count) cleanup(desc); @@ -786,7 +792,7 @@ 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); + mutex_lock(&desc->lock); #ifdef CONFIG_PM if ((message.event & PM_EVENT_AUTO) && test_bit(WDM_IN_USE, &desc->flags)) { @@ -798,7 +804,7 @@ static int wdm_suspend(struct usb_interface *intf, pm_message_t message) #ifdef CONFIG_PM } #endif - mutex_unlock(&desc->plock); + mutex_unlock(&desc->lock); return rv; } @@ -821,9 +827,9 @@ static int wdm_resume(struct usb_interface *intf) int rv; dev_dbg(&desc->intf->dev, "wdm%d_resume\n", intf->minor); - mutex_lock(&desc->plock); + mutex_lock(&desc->lock); rv = recover_from_urb_loss(desc); - mutex_unlock(&desc->plock); + mutex_unlock(&desc->lock); return rv; } @@ -831,7 +837,7 @@ 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 +847,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; } -- cgit From 922a5eadd5a3aa0b806be0c18694b618d41d0784 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Sat, 27 Feb 2010 20:54:59 +0100 Subject: usb: cdc-wdm: Fix race between autosuspend and reading from the device While an available response is read the device must not be autosuspended. This requires a flag dedicated to that purpose. Signed-off-by: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-wdm.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'drivers/usb/class') diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index cf1c5fb918d..940b17af162 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -52,6 +52,7 @@ 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_MAX 16 @@ -115,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"); @@ -145,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); @@ -227,6 +230,7 @@ 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); + set_bit(WDM_RESPONDING, &desc->flags); if (!test_bit(WDM_DISCONNECTING, &desc->flags)) { rv = usb_submit_urb(desc->response, GFP_ATOMIC); dev_dbg(&desc->intf->dev, "%s: usb_submit_urb %d", @@ -234,6 +238,7 @@ static void wdm_int_callback(struct urb *urb) } spin_unlock(&desc->iuspin); if (rv < 0) { + clear_bit(WDM_RESPONDING, &desc->flags); if (rv == -EPERM) return; if (rv == -ENOMEM) { @@ -795,7 +800,8 @@ static int wdm_suspend(struct usb_interface *intf, pm_message_t message) mutex_lock(&desc->lock); #ifdef CONFIG_PM 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))) { rv = -EBUSY; } else { #endif -- cgit From d855fe2e9c19edaa47baba0e7f95e17f7a24dba8 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Sat, 27 Feb 2010 20:55:26 +0100 Subject: usb: cdc-wdm: Fix race between disconnect and debug messages dev_dbg() and dev_err() cannot be used to report failures that may have been caused by a device's removal Signed-off-by: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-wdm.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'drivers/usb/class') diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 940b17af162..72e2eb03045 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -435,11 +435,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; } @@ -477,8 +474,6 @@ retry: err: mutex_unlock(&desc->lock); - if (rv < 0 && rv != -EAGAIN) - dev_err(&desc->intf->dev, "wdm_read: exit error\n"); return rv; } -- cgit From beb1d35f1690fe27694472a010a8e4a9ae11cc50 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Sat, 27 Feb 2010 20:55:52 +0100 Subject: usb: cdc-wdm: Fix submission of URB after suspension There's a window under which cdc-wdm may submit an URB to a device about to be suspended. This introduces a flag to prevent it. Signed-off-by: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-wdm.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers/usb/class') diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 72e2eb03045..a6b5e9fd071 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -53,7 +53,7 @@ MODULE_DEVICE_TABLE (usb, wdm_ids); #define WDM_INT_STALL 5 #define WDM_POLL_RUNNING 6 #define WDM_RESPONDING 7 - +#define WDM_SUSPENDING 8 #define WDM_MAX 16 @@ -231,7 +231,8 @@ static void wdm_int_callback(struct urb *urb) spin_lock(&desc->iuspin); clear_bit(WDM_READ, &desc->flags); set_bit(WDM_RESPONDING, &desc->flags); - if (!test_bit(WDM_DISCONNECTING, &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); @@ -800,6 +801,7 @@ static int wdm_suspend(struct usb_interface *intf, pm_message_t message) rv = -EBUSY; } else { #endif + set_bit(WDM_SUSPENDING, &desc->flags); cancel_work_sync(&desc->rxwork); kill_urbs(desc); #ifdef CONFIG_PM @@ -830,6 +832,7 @@ static int wdm_resume(struct usb_interface *intf) dev_dbg(&desc->intf->dev, "wdm%d_resume\n", intf->minor); mutex_lock(&desc->lock); rv = recover_from_urb_loss(desc); + clear_bit(WDM_SUSPENDING, &desc->flags); mutex_unlock(&desc->lock); return rv; } -- cgit From 62e6685470fb04fb7688ecef96c39160498721d5 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Sat, 27 Feb 2010 20:56:22 +0100 Subject: usb: cdc-wdm:Fix loss of data due to autosuspend The guarding flag must be set and tested under spinlock and cleared before the URBs are resubmitted in resume. Signed-off-by: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-wdm.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers/usb/class') diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index a6b5e9fd071..07c12974fe1 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -794,14 +794,17 @@ 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->lock); + spin_lock_irq(&desc->iuspin); #ifdef CONFIG_PM if ((message.event & PM_EVENT_AUTO) && (test_bit(WDM_IN_USE, &desc->flags) || test_bit(WDM_RESPONDING, &desc->flags))) { + spin_unlock_irq(&desc->iuspin); rv = -EBUSY; } else { #endif set_bit(WDM_SUSPENDING, &desc->flags); + spin_unlock_irq(&desc->iuspin); cancel_work_sync(&desc->rxwork); kill_urbs(desc); #ifdef CONFIG_PM @@ -831,8 +834,8 @@ static int wdm_resume(struct usb_interface *intf) dev_dbg(&desc->intf->dev, "wdm%d_resume\n", intf->minor); mutex_lock(&desc->lock); - rv = recover_from_urb_loss(desc); clear_bit(WDM_SUSPENDING, &desc->flags); + rv = recover_from_urb_loss(desc); mutex_unlock(&desc->lock); return rv; } -- cgit From d93d16e9aa58887feadd999ea26b7b8139e98b56 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Sat, 27 Feb 2010 20:56:47 +0100 Subject: usb: cdc-wdm: Fix order in disconnect and fix locking - as the callback can schedule work, URBs must be killed first - if the driver causes an autoresume, the caller must handle locking Signed-off-by: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-wdm.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'drivers/usb/class') diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 07c12974fe1..b5749050886 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -776,9 +776,9 @@ 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) @@ -786,6 +786,7 @@ static void wdm_disconnect(struct usb_interface *intf) 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); @@ -793,27 +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->lock); + /* 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); -#ifdef CONFIG_PM + if ((message.event & PM_EVENT_AUTO) && (test_bit(WDM_IN_USE, &desc->flags) || test_bit(WDM_RESPONDING, &desc->flags))) { spin_unlock_irq(&desc->iuspin); rv = -EBUSY; } else { -#endif + set_bit(WDM_SUSPENDING, &desc->flags); spin_unlock_irq(&desc->iuspin); - cancel_work_sync(&desc->rxwork); + /* callback submits work - order is essential */ kill_urbs(desc); -#ifdef CONFIG_PM + cancel_work_sync(&desc->rxwork); } -#endif - mutex_unlock(&desc->lock); + if (!(message.event & PM_EVENT_AUTO)) + mutex_unlock(&desc->lock); return rv; } +#endif static int recover_from_urb_loss(struct wdm_device *desc) { @@ -827,6 +831,8 @@ 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); @@ -839,6 +845,7 @@ static int wdm_resume(struct usb_interface *intf) mutex_unlock(&desc->lock); return rv; } +#endif static int wdm_pre_reset(struct usb_interface *intf) { @@ -862,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, -- cgit From 338124c1f18c2c737656ac58735f040d90b23d8c Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Sat, 27 Feb 2010 20:57:12 +0100 Subject: usb: cdc-wdm: Fix deadlock between write and resume The new runtime PM scheme allows resume() to have no locks. This fixes the deadlock. Signed-off-by: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-wdm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/usb/class') diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index b5749050886..189141ca4e0 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -839,10 +839,10 @@ static int wdm_resume(struct usb_interface *intf) int rv; dev_dbg(&desc->intf->dev, "wdm%d_resume\n", intf->minor); - mutex_lock(&desc->lock); + clear_bit(WDM_SUSPENDING, &desc->flags); rv = recover_from_urb_loss(desc); - mutex_unlock(&desc->lock); + return rv; } #endif -- cgit