diff options
Diffstat (limited to 'drivers/usb/class')
-rw-r--r-- | drivers/usb/class/cdc-acm.c | 246 | ||||
-rw-r--r-- | drivers/usb/class/cdc-acm.h | 6 | ||||
-rw-r--r-- | drivers/usb/class/cdc-wdm.c | 127 |
3 files changed, 302 insertions, 77 deletions
diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 63c34043b4d..c257453fa9d 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -51,6 +51,7 @@ */ #undef DEBUG +#undef VERBOSE_DEBUG #include <linux/kernel.h> #include <linux/errno.h> @@ -70,6 +71,9 @@ #include "cdc-acm.h" + +#define ACM_CLOSE_TIMEOUT 15 /* seconds to let writes drain */ + /* * Version Information */ @@ -85,6 +89,12 @@ static DEFINE_MUTEX(open_mutex); #define ACM_READY(acm) (acm && acm->dev && acm->used) +#ifdef VERBOSE_DEBUG +#define verbose 1 +#else +#define verbose 0 +#endif + /* * Functions for ACM control messages. */ @@ -136,19 +146,17 @@ static int acm_wb_alloc(struct acm *acm) static int acm_wb_is_avail(struct acm *acm) { int i, n; + unsigned long flags; n = ACM_NW; + spin_lock_irqsave(&acm->write_lock, flags); for (i = 0; i < ACM_NW; i++) { n -= acm->wb[i].use; } + spin_unlock_irqrestore(&acm->write_lock, flags); return n; } -static inline int acm_wb_is_used(struct acm *acm, int wbn) -{ - return acm->wb[wbn].use; -} - /* * Finish write. */ @@ -157,51 +165,62 @@ static void acm_write_done(struct acm *acm, struct acm_wb *wb) unsigned long flags; spin_lock_irqsave(&acm->write_lock, flags); - acm->write_ready = 1; wb->use = 0; + acm->transmitting--; spin_unlock_irqrestore(&acm->write_lock, flags); } /* * Poke write. + * + * the caller is responsible for locking */ + +static int acm_start_wb(struct acm *acm, struct acm_wb *wb) +{ + int rc; + + acm->transmitting++; + + wb->urb->transfer_buffer = wb->buf; + wb->urb->transfer_dma = wb->dmah; + wb->urb->transfer_buffer_length = wb->len; + wb->urb->dev = acm->dev; + + if ((rc = usb_submit_urb(wb->urb, GFP_ATOMIC)) < 0) { + dbg("usb_submit_urb(write bulk) failed: %d", rc); + acm_write_done(acm, wb); + } + return rc; +} + static int acm_write_start(struct acm *acm, int wbn) { unsigned long flags; - struct acm_wb *wb; + struct acm_wb *wb = &acm->wb[wbn]; int rc; spin_lock_irqsave(&acm->write_lock, flags); if (!acm->dev) { + wb->use = 0; spin_unlock_irqrestore(&acm->write_lock, flags); return -ENODEV; } - if (!acm->write_ready) { + dbg("%s susp_count: %d", __func__, acm->susp_count); + if (acm->susp_count) { + acm->delayed_wb = wb; + schedule_work(&acm->waker); spin_unlock_irqrestore(&acm->write_lock, flags); return 0; /* A white lie */ } + usb_mark_last_busy(acm->dev); - if (!acm_wb_is_used(acm, wbn)) { - spin_unlock_irqrestore(&acm->write_lock, flags); - return 0; - } - wb = &acm->wb[wbn]; - - if(acm_wb_is_avail(acm) <= 1) - acm->write_ready = 0; + rc = acm_start_wb(acm, wb); spin_unlock_irqrestore(&acm->write_lock, flags); - wb->urb->transfer_buffer = wb->buf; - wb->urb->transfer_dma = wb->dmah; - wb->urb->transfer_buffer_length = wb->len; - wb->urb->dev = acm->dev; - - if ((rc = usb_submit_urb(wb->urb, GFP_ATOMIC)) < 0) { - dbg("usb_submit_urb(write bulk) failed: %d", rc); - acm_write_done(acm, wb); - } return rc; + } /* * attributes exported through sysfs @@ -304,6 +323,7 @@ static void acm_ctrl_irq(struct urb *urb) break; } exit: + usb_mark_last_busy(acm->dev); retval = usb_submit_urb (urb, GFP_ATOMIC); if (retval) err ("%s - usb_submit_urb failed with result %d", @@ -320,8 +340,11 @@ static void acm_read_bulk(struct urb *urb) dbg("Entering acm_read_bulk with status %d", status); - if (!ACM_READY(acm)) + if (!ACM_READY(acm)) { + dev_dbg(&acm->data->dev, "Aborting, acm not ready"); return; + } + usb_mark_last_busy(acm->dev); if (status) dev_dbg(&acm->data->dev, "bulk rx status %d\n", status); @@ -331,6 +354,7 @@ static void acm_read_bulk(struct urb *urb) if (likely(status == 0)) { spin_lock(&acm->read_lock); + acm->processing++; list_add_tail(&rcv->list, &acm->spare_read_urbs); list_add_tail(&buf->list, &acm->filled_read_bufs); spin_unlock(&acm->read_lock); @@ -343,7 +367,8 @@ static void acm_read_bulk(struct urb *urb) /* nevertheless the tasklet must be kicked unconditionally so the queue cannot dry up */ } - tasklet_schedule(&acm->urb_task); + if (likely(!acm->susp_count)) + tasklet_schedule(&acm->urb_task); } static void acm_rx_tasklet(unsigned long _acm) @@ -354,16 +379,23 @@ static void acm_rx_tasklet(unsigned long _acm) struct acm_ru *rcv; unsigned long flags; unsigned char throttled; + dbg("Entering acm_rx_tasklet"); if (!ACM_READY(acm)) + { + dbg("acm_rx_tasklet: ACM not ready"); return; + } spin_lock_irqsave(&acm->throttle_lock, flags); throttled = acm->throttle; spin_unlock_irqrestore(&acm->throttle_lock, flags); if (throttled) + { + dbg("acm_rx_tasklet: throttled"); return; + } next_buffer: spin_lock_irqsave(&acm->read_lock, flags); @@ -403,6 +435,7 @@ urbs: while (!list_empty(&acm->spare_read_bufs)) { spin_lock_irqsave(&acm->read_lock, flags); if (list_empty(&acm->spare_read_urbs)) { + acm->processing = 0; spin_unlock_irqrestore(&acm->read_lock, flags); return; } @@ -425,44 +458,72 @@ urbs: rcv->urb->transfer_dma = buf->dma; rcv->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; - dbg("acm_rx_tasklet: sending urb 0x%p, rcv 0x%p, buf 0x%p", rcv->urb, rcv, buf); - /* This shouldn't kill the driver as unsuccessful URBs are returned to the free-urbs-pool and resubmited ASAP */ - if (usb_submit_urb(rcv->urb, GFP_ATOMIC) < 0) { + spin_lock_irqsave(&acm->read_lock, flags); + if (acm->susp_count || usb_submit_urb(rcv->urb, GFP_ATOMIC) < 0) { list_add(&buf->list, &acm->spare_read_bufs); - spin_lock_irqsave(&acm->read_lock, flags); list_add(&rcv->list, &acm->spare_read_urbs); + acm->processing = 0; spin_unlock_irqrestore(&acm->read_lock, flags); return; + } else { + spin_unlock_irqrestore(&acm->read_lock, flags); + dbg("acm_rx_tasklet: sending urb 0x%p, rcv 0x%p, buf 0x%p", rcv->urb, rcv, buf); } } + spin_lock_irqsave(&acm->read_lock, flags); + acm->processing = 0; + spin_unlock_irqrestore(&acm->read_lock, flags); } /* data interface wrote those outgoing bytes */ static void acm_write_bulk(struct urb *urb) { - struct acm *acm; struct acm_wb *wb = urb->context; + struct acm *acm = wb->instance; - dbg("Entering acm_write_bulk with status %d", urb->status); + if (verbose || urb->status + || (urb->actual_length != urb->transfer_buffer_length)) + dev_dbg(&acm->data->dev, "tx %d/%d bytes -- > %d\n", + urb->actual_length, + urb->transfer_buffer_length, + urb->status); - acm = wb->instance; acm_write_done(acm, wb); if (ACM_READY(acm)) schedule_work(&acm->work); + else + wake_up_interruptible(&acm->drain_wait); } static void acm_softint(struct work_struct *work) { struct acm *acm = container_of(work, struct acm, work); - dbg("Entering acm_softint."); - + + dev_vdbg(&acm->data->dev, "tx work\n"); if (!ACM_READY(acm)) return; tty_wakeup(acm->tty); } +static void acm_waker(struct work_struct *waker) +{ + struct acm *acm = container_of(waker, struct acm, waker); + int rv; + + rv = usb_autopm_get_interface(acm->control); + if (rv < 0) { + err("Autopm failure in %s", __func__); + return; + } + if (acm->delayed_wb) { + acm_start_wb(acm, acm->delayed_wb); + acm->delayed_wb = NULL; + } + usb_autopm_put_interface(acm->control); +} + /* * TTY handlers */ @@ -492,6 +553,8 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp) if (usb_autopm_get_interface(acm->control) < 0) goto early_bail; + else + acm->control->needs_remote_wakeup = 1; mutex_lock(&acm->mutex); if (acm->used++) { @@ -509,6 +572,7 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp) if (0 > acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS) && (acm->ctrl_caps & USB_CDC_CAP_LINE)) goto full_bailout; + usb_autopm_put_interface(acm->control); INIT_LIST_HEAD(&acm->spare_read_urbs); INIT_LIST_HEAD(&acm->spare_read_bufs); @@ -525,8 +589,8 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp) tasklet_schedule(&acm->urb_task); done: -err_out: mutex_unlock(&acm->mutex); +err_out: mutex_unlock(&open_mutex); return rv; @@ -558,6 +622,8 @@ static void acm_tty_unregister(struct acm *acm) kfree(acm); } +static int acm_tty_chars_in_buffer(struct tty_struct *tty); + static void acm_tty_close(struct tty_struct *tty, struct file *filp) { struct acm *acm = tty->driver_data; @@ -570,12 +636,21 @@ static void acm_tty_close(struct tty_struct *tty, struct file *filp) mutex_lock(&open_mutex); if (!--acm->used) { if (acm->dev) { + usb_autopm_get_interface(acm->control); acm_set_control(acm, acm->ctrlout = 0); + + /* try letting the last writes drain naturally */ + wait_event_interruptible_timeout(acm->drain_wait, + (ACM_NW == acm_wb_is_avail(acm)) + || !acm->dev, + ACM_CLOSE_TIMEOUT * HZ); + usb_kill_urb(acm->ctrlurb); for (i = 0; i < ACM_NW; i++) usb_kill_urb(acm->wb[i].urb); for (i = 0; i < nr; i++) usb_kill_urb(acm->ru[i].urb); + acm->control->needs_remote_wakeup = 0; usb_autopm_put_interface(acm->control); } else acm_tty_unregister(acm); @@ -625,7 +700,7 @@ static int acm_tty_write_room(struct tty_struct *tty) * Do not let the line discipline to know that we have a reserve, * or it might get too enthusiastic. */ - return (acm->write_ready && acm_wb_is_avail(acm)) ? acm->writesize : 0; + return acm_wb_is_avail(acm) ? acm->writesize : 0; } static int acm_tty_chars_in_buffer(struct tty_struct *tty) @@ -660,13 +735,16 @@ static void acm_tty_unthrottle(struct tty_struct *tty) tasklet_schedule(&acm->urb_task); } -static void acm_tty_break_ctl(struct tty_struct *tty, int state) +static int acm_tty_break_ctl(struct tty_struct *tty, int state) { struct acm *acm = tty->driver_data; + int retval; if (!ACM_READY(acm)) - return; - if (acm_send_break(acm, state ? 0xffff : 0)) + return -EINVAL; + retval = acm_send_break(acm, state ? 0xffff : 0); + if (retval < 0) dbg("send break failed"); + return retval; } static int acm_tty_tiocmget(struct tty_struct *tty, struct file *file) @@ -766,7 +844,7 @@ static void acm_tty_set_termios(struct tty_struct *tty, struct ktermios *termios * USB probe and disconnect routines. */ -/* Little helper: write buffers free */ +/* Little helpers: write/read buffers free */ static void acm_write_buffers_free(struct acm *acm) { int i; @@ -777,6 +855,15 @@ static void acm_write_buffers_free(struct acm *acm) } } +static void acm_read_buffers_free(struct acm *acm) +{ + struct usb_device *usb_dev = interface_to_usbdev(acm->control); + int i, n = acm->rx_buflimit; + + for (i = 0; i < n; i++) + usb_buffer_free(usb_dev, acm->readsize, acm->rb[i].base, acm->rb[i].dma); +} + /* Little helper: write buffers allocate */ static int acm_write_buffers_alloc(struct acm *acm) { @@ -987,11 +1074,12 @@ skip_normal_probe: acm->urb_task.func = acm_rx_tasklet; acm->urb_task.data = (unsigned long) acm; INIT_WORK(&acm->work, acm_softint); + INIT_WORK(&acm->waker, acm_waker); + init_waitqueue_head(&acm->drain_wait); spin_lock_init(&acm->throttle_lock); spin_lock_init(&acm->write_lock); spin_lock_init(&acm->read_lock); mutex_init(&acm->mutex); - acm->write_ready = 1; acm->rx_endpoint = usb_rcvbulkpipe(usb_dev, epread->bEndpointAddress); buf = usb_buffer_alloc(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma); @@ -1023,9 +1111,11 @@ skip_normal_probe: rcv->instance = acm; } for (i = 0; i < num_rx_buf; i++) { - struct acm_rb *buf = &(acm->rb[i]); + struct acm_rb *rb = &(acm->rb[i]); - if (!(buf->base = usb_buffer_alloc(acm->dev, readsize, GFP_KERNEL, &buf->dma))) { + rb->base = usb_buffer_alloc(acm->dev, readsize, + GFP_KERNEL, &rb->dma); + if (!rb->base) { dev_dbg(&intf->dev, "out of memory (read bufs usb_buffer_alloc)\n"); goto alloc_fail7; } @@ -1087,6 +1177,7 @@ skip_countries: acm_set_line(acm, &acm->line); usb_driver_claim_interface(&acm_driver, data_interface, acm); + usb_set_intfdata(data_interface, acm); usb_get_intf(control_interface); tty_register_device(acm_tty_driver, minor, &control_interface->dev); @@ -1098,8 +1189,7 @@ alloc_fail8: for (i = 0; i < ACM_NW; i++) usb_free_urb(acm->wb[i].urb); alloc_fail7: - for (i = 0; i < num_rx_buf; i++) - usb_buffer_free(usb_dev, acm->readsize, acm->rb[i].base, acm->rb[i].dma); + acm_read_buffers_free(acm); for (i = 0; i < num_rx_buf; i++) usb_free_urb(acm->ru[i].urb); usb_free_urb(acm->ctrlurb); @@ -1116,6 +1206,7 @@ alloc_fail: static void stop_data_traffic(struct acm *acm) { int i; + dbg("Entering stop_data_traffic"); tasklet_disable(&acm->urb_task); @@ -1125,30 +1216,22 @@ static void stop_data_traffic(struct acm *acm) for (i = 0; i < acm->rx_buflimit; i++) usb_kill_urb(acm->ru[i].urb); - INIT_LIST_HEAD(&acm->filled_read_bufs); - INIT_LIST_HEAD(&acm->spare_read_bufs); - tasklet_enable(&acm->urb_task); cancel_work_sync(&acm->work); + cancel_work_sync(&acm->waker); } static void acm_disconnect(struct usb_interface *intf) { struct acm *acm = usb_get_intfdata(intf); struct usb_device *usb_dev = interface_to_usbdev(intf); - int i; - if (!acm || !acm->dev) { - dbg("disconnect on nonexisting interface"); + /* sibling interface is already cleaning up */ + if (!acm) return; - } mutex_lock(&open_mutex); - if (!usb_get_intfdata(intf)) { - mutex_unlock(&open_mutex); - return; - } if (acm->country_codes){ device_remove_file(&acm->control->dev, &dev_attr_wCountryCodes); @@ -1164,10 +1247,10 @@ static void acm_disconnect(struct usb_interface *intf) acm_write_buffers_free(acm); usb_buffer_free(usb_dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); - for (i = 0; i < acm->rx_buflimit; i++) - usb_buffer_free(usb_dev, acm->readsize, acm->rb[i].base, acm->rb[i].dma); + acm_read_buffers_free(acm); - usb_driver_release_interface(&acm_driver, intf == acm->control ? acm->data : intf); + usb_driver_release_interface(&acm_driver, intf == acm->control ? + acm->data : acm->control); if (!acm->used) { acm_tty_unregister(acm); @@ -1181,11 +1264,31 @@ static void acm_disconnect(struct usb_interface *intf) tty_hangup(acm->tty); } +#ifdef CONFIG_PM static int acm_suspend(struct usb_interface *intf, pm_message_t message) { struct acm *acm = usb_get_intfdata(intf); + int cnt; + + if (acm->dev->auto_pm) { + int b; + + spin_lock_irq(&acm->read_lock); + spin_lock(&acm->write_lock); + b = acm->processing + acm->transmitting; + spin_unlock(&acm->write_lock); + spin_unlock_irq(&acm->read_lock); + if (b) + return -EBUSY; + } - if (acm->susp_count++) + spin_lock_irq(&acm->read_lock); + spin_lock(&acm->write_lock); + cnt = acm->susp_count++; + spin_unlock(&acm->write_lock); + spin_unlock_irq(&acm->read_lock); + + if (cnt) return 0; /* we treat opened interfaces differently, @@ -1204,15 +1307,21 @@ static int acm_resume(struct usb_interface *intf) { struct acm *acm = usb_get_intfdata(intf); int rv = 0; + int cnt; + + spin_lock_irq(&acm->read_lock); + acm->susp_count -= 1; + cnt = acm->susp_count; + spin_unlock_irq(&acm->read_lock); - if (--acm->susp_count) + if (cnt) return 0; mutex_lock(&acm->mutex); if (acm->used) { rv = usb_submit_urb(acm->ctrlurb, GFP_NOIO); if (rv < 0) - goto err_out; + goto err_out; tasklet_schedule(&acm->urb_task); } @@ -1221,6 +1330,8 @@ err_out: mutex_unlock(&acm->mutex); return rv; } + +#endif /* CONFIG_PM */ /* * USB driver structure. */ @@ -1251,6 +1362,9 @@ static struct usb_device_id acm_ids[] = { { USB_DEVICE(0x0803, 0x3095), /* Zoom Telephonics Model 3095F USB MODEM */ .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ }, + { USB_DEVICE(0x0572, 0x1321), /* Conexant USB MODEM CX93010 */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, /* control interfaces with various AT-command sets */ { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, @@ -1276,10 +1390,14 @@ static struct usb_driver acm_driver = { .name = "cdc_acm", .probe = acm_probe, .disconnect = acm_disconnect, +#ifdef CONFIG_PM .suspend = acm_suspend, .resume = acm_resume, +#endif .id_table = acm_ids, +#ifdef CONFIG_PM .supports_autosuspend = 1, +#endif }; /* diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index 046e064b033..1f95e7aa1b6 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -106,11 +106,14 @@ struct acm { struct list_head spare_read_bufs; struct list_head filled_read_bufs; int write_used; /* number of non-empty write buffers */ - int write_ready; /* write urb is not running */ + int processing; + int transmitting; spinlock_t write_lock; struct mutex mutex; struct usb_cdc_line_coding line; /* bits, stop, parity */ struct work_struct work; /* work queue entry for line discipline waking up */ + struct work_struct waker; + wait_queue_head_t drain_wait; /* close processing */ struct tasklet_struct urb_task; /* rx processing */ spinlock_t throttle_lock; /* synchronize throtteling and read callback */ unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ @@ -123,6 +126,7 @@ struct acm { unsigned char clocal; /* termios CLOCAL */ unsigned int ctrl_caps; /* control capabilities from the class specific header */ unsigned int susp_count; /* number of suspended interfaces */ + struct acm_wb *delayed_wb; /* write queued for a device about to be woken */ }; #define CDC_DATA_INTERFACE_TYPE 0x0a diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 731db051070..7e8e1235e4e 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -28,8 +28,9 @@ /* * Version Information */ -#define DRIVER_VERSION "v0.02" +#define DRIVER_VERSION "v0.03" #define DRIVER_AUTHOR "Oliver Neukum" +#define DRIVER_DESC "USB Abstract Control Model driver for USB WCM Device Management" static struct usb_device_id wdm_ids[] = { { @@ -87,6 +88,7 @@ struct wdm_device { dma_addr_t ihandle; struct mutex wlock; struct mutex rlock; + struct mutex plock; wait_queue_head_t wait; struct work_struct rxwork; int werr; @@ -205,7 +207,7 @@ static void wdm_int_callback(struct urb *urb) req->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE; req->wValue = 0; req->wIndex = desc->inum; - req->wLength = cpu_to_le16(desc->bMaxPacketSize0); + req->wLength = cpu_to_le16(desc->wMaxCommand); usb_fill_control_urb( desc->response, @@ -214,7 +216,7 @@ static void wdm_int_callback(struct urb *urb) usb_rcvctrlpipe(interface_to_usbdev(desc->intf), 0), (unsigned char *)req, desc->inbuf, - desc->bMaxPacketSize0, + desc->wMaxCommand, wdm_in_callback, desc ); @@ -247,6 +249,7 @@ exit: static void kill_urbs(struct wdm_device *desc) { + /* the order here is essential */ usb_kill_urb(desc->command); usb_kill_urb(desc->validity); usb_kill_urb(desc->response); @@ -266,7 +269,7 @@ static void cleanup(struct wdm_device *desc) desc->sbuf, desc->validity->transfer_dma); usb_buffer_free(interface_to_usbdev(desc->intf), - desc->wMaxPacketSize, + desc->wMaxCommand, desc->inbuf, desc->response->transfer_dma); kfree(desc->orq); @@ -299,6 +302,9 @@ static ssize_t wdm_write if (r) goto outnl; + r = usb_autopm_get_interface(desc->intf); + if (r < 0) + goto outnp; r = wait_event_interruptible(desc->wait, !test_bit(WDM_IN_USE, &desc->flags)); if (r < 0) @@ -347,11 +353,14 @@ static ssize_t wdm_write if (rv < 0) { kfree(buf); clear_bit(WDM_IN_USE, &desc->flags); + err("Tx URB error: %d", rv); } else { dev_dbg(&desc->intf->dev, "Tx URB has been submitted index=%d", req->wIndex); } out: + usb_autopm_put_interface(desc->intf); +outnp: mutex_unlock(&desc->wlock); outnl: return rv < 0 ? rv : count; @@ -376,6 +385,11 @@ retry: rv = wait_event_interruptible(desc->wait, test_bit(WDM_READ, &desc->flags)); + if (test_bit(WDM_DISCONNECTING, &desc->flags)) { + rv = -ENODEV; + goto err; + } + usb_mark_last_busy(interface_to_usbdev(desc->intf)); if (rv < 0) { rv = -ERESTARTSYS; goto err; @@ -418,6 +432,9 @@ retry: desc->ubuf[i] = desc->ubuf[i + cntr]; desc->length -= cntr; + /* in case we had outstanding data */ + if (!desc->length) + clear_bit(WDM_READ, &desc->flags); rv = cntr; err: @@ -480,18 +497,28 @@ static int wdm_open(struct inode *inode, struct file *file) if (test_bit(WDM_DISCONNECTING, &desc->flags)) goto out; - desc->count++; + ; file->private_data = desc; - rv = usb_submit_urb(desc->validity, GFP_KERNEL); - + rv = usb_autopm_get_interface(desc->intf); if (rv < 0) { - desc->count--; - err("Error submitting int urb - %d", rv); + err("Error autopm - %d", rv); goto out; } - rv = 0; + intf->needs_remote_wakeup = 1; + mutex_lock(&desc->plock); + if (!desc->count++) { + rv = usb_submit_urb(desc->validity, GFP_KERNEL); + if (rv < 0) { + desc->count--; + err("Error submitting int urb - %d", rv); + } + } else { + rv = 0; + } + mutex_unlock(&desc->plock); + usb_autopm_put_interface(desc->intf); out: mutex_unlock(&wdm_mutex); return rv; @@ -502,10 +529,15 @@ 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); desc->count--; + mutex_unlock(&desc->plock); + if (!desc->count) { dev_dbg(&desc->intf->dev, "wdm_release: cleanup"); kill_urbs(desc); + if (!test_bit(WDM_DISCONNECTING, &desc->flags)) + desc->intf->needs_remote_wakeup = 0; } mutex_unlock(&wdm_mutex); return 0; @@ -597,6 +629,7 @@ next_desc: goto out; mutex_init(&desc->wlock); mutex_init(&desc->rlock); + mutex_init(&desc->plock); spin_lock_init(&desc->iuspin); init_waitqueue_head(&desc->wait); desc->wMaxCommand = maxcom; @@ -698,6 +731,7 @@ static void wdm_disconnect(struct usb_interface *intf) spin_lock_irqsave(&desc->iuspin, flags); set_bit(WDM_DISCONNECTING, &desc->flags); set_bit(WDM_READ, &desc->flags); + /* to terminate pending flushes */ clear_bit(WDM_IN_USE, &desc->flags); spin_unlock_irqrestore(&desc->iuspin, flags); cancel_work_sync(&desc->rxwork); @@ -708,11 +742,81 @@ static void wdm_disconnect(struct usb_interface *intf) mutex_unlock(&wdm_mutex); } +static int wdm_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct wdm_device *desc = usb_get_intfdata(intf); + int rv = 0; + + dev_dbg(&desc->intf->dev, "wdm%d_suspend\n", intf->minor); + + mutex_lock(&desc->plock); +#ifdef CONFIG_PM + if (interface_to_usbdev(desc->intf)->auto_pm && test_bit(WDM_IN_USE, &desc->flags)) { + rv = -EBUSY; + } else { +#endif + cancel_work_sync(&desc->rxwork); + kill_urbs(desc); +#ifdef CONFIG_PM + } +#endif + mutex_unlock(&desc->plock); + + return rv; +} + +static int recover_from_urb_loss(struct wdm_device *desc) +{ + int rv = 0; + + if (desc->count) { + rv = usb_submit_urb(desc->validity, GFP_NOIO); + if (rv < 0) + err("Error resume submitting int urb - %d", rv); + } + return rv; +} +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); + rv = recover_from_urb_loss(desc); + mutex_unlock(&desc->plock); + return rv; +} + +static int wdm_pre_reset(struct usb_interface *intf) +{ + struct wdm_device *desc = usb_get_intfdata(intf); + + mutex_lock(&desc->plock); + return 0; +} + +static int wdm_post_reset(struct usb_interface *intf) +{ + struct wdm_device *desc = usb_get_intfdata(intf); + int rv; + + rv = recover_from_urb_loss(desc); + mutex_unlock(&desc->plock); + return 0; +} + static struct usb_driver wdm_driver = { .name = "cdc_wdm", .probe = wdm_probe, .disconnect = wdm_disconnect, + .suspend = wdm_suspend, + .resume = wdm_resume, + .reset_resume = wdm_resume, + .pre_reset = wdm_pre_reset, + .post_reset = wdm_post_reset, .id_table = wdm_ids, + .supports_autosuspend = 1, }; /* --- low level module stuff --- */ @@ -735,6 +839,5 @@ module_init(wdm_init); module_exit(wdm_exit); MODULE_AUTHOR(DRIVER_AUTHOR); -MODULE_DESCRIPTION("USB Abstract Control Model driver for " - "USB WCM Device Management"); +MODULE_DESCRIPTION(DRIVER_DESC); MODULE_LICENSE("GPL"); |