diff options
Diffstat (limited to 'drivers/usb/class/cdc-wdm.c')
| -rw-r--r-- | drivers/usb/class/cdc-wdm.c | 60 |
1 files changed, 49 insertions, 11 deletions
diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index d3318a0df8e..a051a7a2b1b 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -101,6 +101,7 @@ struct wdm_device { struct work_struct rxwork; int werr; int rerr; + int resp_count; struct list_head device_list; int (*manage_power)(struct usb_interface *, int); @@ -253,6 +254,10 @@ static void wdm_int_callback(struct urb *urb) "NOTIFY_NETWORK_CONNECTION %s network", dr->wValue ? "connected to" : "disconnected from"); goto exit; + case USB_CDC_NOTIFY_SPEED_CHANGE: + dev_dbg(&desc->intf->dev, "SPEED_CHANGE received (len %u)", + urb->actual_length); + goto exit; default: clear_bit(WDM_POLL_RUNNING, &desc->flags); dev_err(&desc->intf->dev, @@ -262,9 +267,9 @@ static void wdm_int_callback(struct urb *urb) } spin_lock(&desc->iuspin); - clear_bit(WDM_READ, &desc->flags); responding = test_and_set_bit(WDM_RESPONDING, &desc->flags); - if (!responding && !test_bit(WDM_DISCONNECTING, &desc->flags) + if (!desc->resp_count++ && !responding + && !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", @@ -427,6 +432,38 @@ outnl: return rv < 0 ? rv : count; } +/* + * clear WDM_READ flag and possibly submit the read urb if resp_count + * is non-zero. + * + * Called with desc->iuspin locked + */ +static int clear_wdm_read_flag(struct wdm_device *desc) +{ + int rv = 0; + + clear_bit(WDM_READ, &desc->flags); + + /* submit read urb only if the device is waiting for it */ + if (!desc->resp_count || !--desc->resp_count) + goto out; + + set_bit(WDM_RESPONDING, &desc->flags); + spin_unlock_irq(&desc->iuspin); + rv = usb_submit_urb(desc->response, GFP_KERNEL); + spin_lock_irq(&desc->iuspin); + if (rv) { + dev_err(&desc->intf->dev, + "usb_submit_urb failed with result %d\n", rv); + + /* make sure the next notification trigger a submit */ + clear_bit(WDM_RESPONDING, &desc->flags); + desc->resp_count = 0; + } +out: + return rv; +} + static ssize_t wdm_read (struct file *file, char __user *buffer, size_t count, loff_t *ppos) { @@ -498,8 +535,10 @@ retry: if (!desc->reslength) { /* zero length read */ dev_dbg(&desc->intf->dev, "%s: zero length - clearing WDM_READ\n", __func__); - clear_bit(WDM_READ, &desc->flags); + rv = clear_wdm_read_flag(desc); spin_unlock_irq(&desc->iuspin); + if (rv < 0) + goto err; goto retry; } cntr = desc->length; @@ -522,10 +561,8 @@ retry: desc->length -= cntr; /* in case we had outstanding data */ if (!desc->length) - clear_bit(WDM_READ, &desc->flags); - + clear_wdm_read_flag(desc); spin_unlock_irq(&desc->iuspin); - rv = cntr; err: @@ -635,6 +672,9 @@ static int wdm_release(struct inode *inode, struct file *file) if (!test_bit(WDM_DISCONNECTING, &desc->flags)) { dev_dbg(&desc->intf->dev, "wdm_release: cleanup"); kill_urbs(desc); + spin_lock_irq(&desc->iuspin); + desc->resp_count = 0; + spin_unlock_irq(&desc->iuspin); desc->manage_power(desc->intf, 0); } else { /* must avoid dev_printk here as desc->intf is invalid */ @@ -820,13 +860,11 @@ static int wdm_manage_power(struct usb_interface *intf, int on) { /* need autopm_get/put here to ensure the usbcore sees the new value */ int rv = usb_autopm_get_interface(intf); - if (rv < 0) - goto err; intf->needs_remote_wakeup = on; - usb_autopm_put_interface(intf); -err: - return rv; + if (!rv) + usb_autopm_put_interface(intf); + return 0; } static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id) |
