diff options
Diffstat (limited to 'drivers/usb/class')
| -rw-r--r-- | drivers/usb/class/Kconfig | 23 | ||||
| -rw-r--r-- | drivers/usb/class/Makefile | 2 | ||||
| -rw-r--r-- | drivers/usb/class/cdc-acm.c | 1704 | ||||
| -rw-r--r-- | drivers/usb/class/cdc-acm.h | 51 | ||||
| -rw-r--r-- | drivers/usb/class/cdc-wdm.c | 1113 | ||||
| -rw-r--r-- | drivers/usb/class/usblp.c | 526 | ||||
| -rw-r--r-- | drivers/usb/class/usbtmc.c | 1231 |
7 files changed, 3798 insertions, 852 deletions
diff --git a/drivers/usb/class/Kconfig b/drivers/usb/class/Kconfig index 3a9102d2591..bb8b73682a7 100644 --- a/drivers/usb/class/Kconfig +++ b/drivers/usb/class/Kconfig @@ -2,11 +2,10 @@ # USB Class driver configuration # comment "USB Device Class drivers" - depends on USB config USB_ACM tristate "USB Modem (CDC ACM) support" - depends on USB + depends on TTY ---help--- This driver supports USB modems and ISDN adapters which support the Communication Device Class Abstract Control Model interface. @@ -21,7 +20,6 @@ config USB_ACM config USB_PRINTER tristate "USB Printer support" - depends on USB help Say Y here if you want to connect a USB printer to your computer's USB port. @@ -29,3 +27,22 @@ config USB_PRINTER To compile this driver as a module, choose M here: the module will be called usblp. +config USB_WDM + tristate "USB Wireless Device Management support" + ---help--- + This driver supports the WMC Device Management functionality + of cell phones compliant to the CDC WMC specification. You can use + AT commands over this device. + + To compile this driver as a module, choose M here: the + module will be called cdc-wdm. + +config USB_TMC + tristate "USB Test and Measurement Class support" + help + Say Y here if you want to connect a USB device that follows + the USB.org specification for USB Test and Measurement devices + to your computer's USB port. + + To compile this driver as a module, choose M here: the + module will be called usbtmc. diff --git a/drivers/usb/class/Makefile b/drivers/usb/class/Makefile index cc391e6c2af..32e85277b5c 100644 --- a/drivers/usb/class/Makefile +++ b/drivers/usb/class/Makefile @@ -5,3 +5,5 @@ obj-$(CONFIG_USB_ACM) += cdc-acm.o obj-$(CONFIG_USB_PRINTER) += usblp.o +obj-$(CONFIG_USB_WDM) += cdc-wdm.o +obj-$(CONFIG_USB_TMC) += usbtmc.o diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 0147ea39340..e934e19f49f 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -2,38 +2,17 @@ * cdc-acm.c * * Copyright (c) 1999 Armin Fuerst <fuerst@in.tum.de> - * Copyright (c) 1999 Pavel Machek <pavel@suse.cz> + * Copyright (c) 1999 Pavel Machek <pavel@ucw.cz> * Copyright (c) 1999 Johannes Erdfelt <johannes@erdfelt.com> * Copyright (c) 2000 Vojtech Pavlik <vojtech@suse.cz> * Copyright (c) 2004 Oliver Neukum <oliver@neukum.name> * Copyright (c) 2005 David Kubicek <dave@awk.cz> + * Copyright (c) 2011 Johan Hovold <jhovold@gmail.com> * * USB Abstract Control Model driver for USB modems and ISDN adapters * * Sponsored by SuSE * - * ChangeLog: - * v0.9 - thorough cleaning, URBification, almost a rewrite - * v0.10 - some more cleanups - * v0.11 - fixed flow control, read error doesn't stop reads - * v0.12 - added TIOCM ioctls, added break handling, made struct acm kmalloced - * v0.13 - added termios, added hangup - * v0.14 - sized down struct acm - * v0.15 - fixed flow control again - characters could be lost - * v0.16 - added code for modems with swapped data and control interfaces - * v0.17 - added new style probing - * v0.18 - fixed new style probing for devices with more configurations - * v0.19 - fixed CLOCAL handling (thanks to Richard Shih-Ping Chan) - * v0.20 - switched to probing on interface (rather than device) class - * v0.21 - revert to probing on device for devices with multiple configs - * v0.22 - probe only the control interface. if usbcore doesn't choose the - * config we want, sysadmin changes bConfigurationValue in sysfs. - * v0.23 - use softirq for rx processing, as needed by tty layer - * v0.24 - change probe method to evaluate CDC union descriptor - * v0.25 - downstream tasks paralelized to maximize throughput - */ - -/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or @@ -50,17 +29,19 @@ */ #undef DEBUG +#undef VERBOSE_DEBUG #include <linux/kernel.h> #include <linux/errno.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/tty.h> +#include <linux/serial.h> #include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/module.h> #include <linux/mutex.h> -#include <asm/uaccess.h> +#include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/cdc.h> #include <asm/byteorder.h> @@ -69,32 +50,95 @@ #include "cdc-acm.h" -/* - * Version Information - */ -#define DRIVER_VERSION "v0.25" -#define DRIVER_AUTHOR "Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik, David Kubicek" + +#define DRIVER_AUTHOR "Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik, David Kubicek, Johan Hovold" #define DRIVER_DESC "USB Abstract Control Model driver for USB modems and ISDN adapters" static struct usb_driver acm_driver; static struct tty_driver *acm_tty_driver; static struct acm *acm_table[ACM_TTY_MINORS]; -static DEFINE_MUTEX(open_mutex); +static DEFINE_MUTEX(acm_table_lock); + +/* + * acm_table accessors + */ + +/* + * Look up an ACM structure by index. If found and not disconnected, increment + * its refcount and return it with its mutex held. + */ +static struct acm *acm_get_by_index(unsigned index) +{ + struct acm *acm; + + mutex_lock(&acm_table_lock); + acm = acm_table[index]; + if (acm) { + mutex_lock(&acm->mutex); + if (acm->disconnected) { + mutex_unlock(&acm->mutex); + acm = NULL; + } else { + tty_port_get(&acm->port); + mutex_unlock(&acm->mutex); + } + } + mutex_unlock(&acm_table_lock); + return acm; +} + +/* + * Try to find an available minor number and if found, associate it with 'acm'. + */ +static int acm_alloc_minor(struct acm *acm) +{ + int minor; + + mutex_lock(&acm_table_lock); + for (minor = 0; minor < ACM_TTY_MINORS; minor++) { + if (!acm_table[minor]) { + acm_table[minor] = acm; + break; + } + } + mutex_unlock(&acm_table_lock); + + return minor; +} -#define ACM_READY(acm) (acm && acm->dev && acm->used) +/* Release the minor number associated with 'acm'. */ +static void acm_release_minor(struct acm *acm) +{ + mutex_lock(&acm_table_lock); + acm_table[acm->minor] = NULL; + mutex_unlock(&acm_table_lock); +} /* * Functions for ACM control messages. */ -static int acm_ctrl_msg(struct acm *acm, int request, int value, void *buf, int len) +static int acm_ctrl_msg(struct acm *acm, int request, int value, + void *buf, int len) { - int retval = usb_control_msg(acm->dev, usb_sndctrlpipe(acm->dev, 0), + int retval; + + retval = usb_autopm_get_interface(acm->control); + if (retval) + return retval; + + retval = usb_control_msg(acm->dev, usb_sndctrlpipe(acm->dev, 0), request, USB_RT_ACM, value, acm->control->altsetting[0].desc.bInterfaceNumber, buf, len, 5000); - dbg("acm_control_msg: rq: 0x%02x val: %#x len: %#x result: %d", request, value, len, retval); + + dev_dbg(&acm->control->dev, + "%s - rq 0x%02x, val %#x, len %#x, result %d\n", + __func__, request, value, len, retval); + + usb_autopm_put_interface(acm->control); + return retval < 0 ? retval : 0; } @@ -118,7 +162,7 @@ static int acm_wb_alloc(struct acm *acm) int i, wbn; struct acm_wb *wb; - wbn = acm->write_current; + wbn = 0; i = 0; for (;;) { wb = &acm->wb[wbn]; @@ -132,85 +176,56 @@ static int acm_wb_alloc(struct acm *acm) } } -static void acm_wb_free(struct acm *acm, int wbn) -{ - acm->wb[wbn].use = 0; -} - static int acm_wb_is_avail(struct acm *acm) { int i, n; + unsigned long flags; n = ACM_NW; - for (i = 0; i < ACM_NW; i++) { + 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. + * Finish write. Caller must hold acm->write_lock */ -static void acm_write_done(struct acm *acm) +static void acm_write_done(struct acm *acm, struct acm_wb *wb) { - unsigned long flags; - int wbn; - - spin_lock_irqsave(&acm->write_lock, flags); - acm->write_ready = 1; - wbn = acm->write_current; - acm_wb_free(acm, wbn); - acm->write_current = (wbn + 1) % ACM_NW; - spin_unlock_irqrestore(&acm->write_lock, flags); + wb->use = 0; + acm->transmitting--; + usb_autopm_put_interface_async(acm->control); } /* * Poke write. + * + * the caller is responsible for locking */ -static int acm_write_start(struct acm *acm) + +static int acm_start_wb(struct acm *acm, struct acm_wb *wb) { - unsigned long flags; - int wbn; - struct acm_wb *wb; int rc; - spin_lock_irqsave(&acm->write_lock, flags); - if (!acm->dev) { - spin_unlock_irqrestore(&acm->write_lock, flags); - return -ENODEV; - } + acm->transmitting++; - if (!acm->write_ready) { - spin_unlock_irqrestore(&acm->write_lock, flags); - return 0; /* A white lie */ - } + wb->urb->transfer_buffer = wb->buf; + wb->urb->transfer_dma = wb->dmah; + wb->urb->transfer_buffer_length = wb->len; + wb->urb->dev = acm->dev; - wbn = acm->write_current; - if (!acm_wb_is_used(acm, wbn)) { - spin_unlock_irqrestore(&acm->write_lock, flags); - return 0; - } - wb = &acm->wb[wbn]; - - acm->write_ready = 0; - spin_unlock_irqrestore(&acm->write_lock, flags); - - acm->writeurb->transfer_buffer = wb->buf; - acm->writeurb->transfer_dma = wb->dmah; - acm->writeurb->transfer_buffer_length = wb->len; - acm->writeurb->dev = acm->dev; - - if ((rc = usb_submit_urb(acm->writeurb, GFP_ATOMIC)) < 0) { - dbg("usb_submit_urb(write bulk) failed: %d", rc); - acm_write_done(acm); + rc = usb_submit_urb(wb->urb, GFP_ATOMIC); + if (rc < 0) { + dev_err(&acm->data->dev, + "%s - usb_submit_urb(write bulk) failed: %d\n", + __func__, rc); + acm_write_done(acm, wb); } return rc; } + /* * attributes exported through sysfs */ @@ -257,6 +272,7 @@ static void acm_ctrl_irq(struct urb *urb) struct usb_cdc_notification *dr = urb->transfer_buffer; unsigned char *data; int newctrl; + int difference; int retval; int status = urb->status; @@ -268,326 +284,378 @@ static void acm_ctrl_irq(struct urb *urb) case -ENOENT: case -ESHUTDOWN: /* this urb is terminated, clean up */ - dbg("%s - urb shutting down with status: %d", __FUNCTION__, status); + dev_dbg(&acm->control->dev, + "%s - urb shutting down with status: %d\n", + __func__, status); return; default: - dbg("%s - nonzero urb status received: %d", __FUNCTION__, status); + dev_dbg(&acm->control->dev, + "%s - nonzero urb status received: %d\n", + __func__, status); goto exit; } - if (!ACM_READY(acm)) - goto exit; + usb_mark_last_busy(acm->dev); data = (unsigned char *)(dr + 1); switch (dr->bNotificationType) { + case USB_CDC_NOTIFY_NETWORK_CONNECTION: + dev_dbg(&acm->control->dev, "%s - network connection: %d\n", + __func__, dr->wValue); + break; - case USB_CDC_NOTIFY_NETWORK_CONNECTION: - - dbg("%s network", dr->wValue ? "connected to" : "disconnected from"); - break; - - case USB_CDC_NOTIFY_SERIAL_STATE: - - newctrl = le16_to_cpu(get_unaligned((__le16 *) data)); + case USB_CDC_NOTIFY_SERIAL_STATE: + newctrl = get_unaligned_le16(data); - if (acm->tty && !acm->clocal && (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) { - dbg("calling hangup"); - tty_hangup(acm->tty); - } + if (!acm->clocal && (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) { + dev_dbg(&acm->control->dev, "%s - calling hangup\n", + __func__); + tty_port_tty_hangup(&acm->port, false); + } - acm->ctrlin = newctrl; + difference = acm->ctrlin ^ newctrl; + spin_lock(&acm->read_lock); + acm->ctrlin = newctrl; + acm->oldcount = acm->iocount; + + if (difference & ACM_CTRL_DSR) + acm->iocount.dsr++; + if (difference & ACM_CTRL_BRK) + acm->iocount.brk++; + if (difference & ACM_CTRL_RI) + acm->iocount.rng++; + if (difference & ACM_CTRL_DCD) + acm->iocount.dcd++; + if (difference & ACM_CTRL_FRAMING) + acm->iocount.frame++; + if (difference & ACM_CTRL_PARITY) + acm->iocount.parity++; + if (difference & ACM_CTRL_OVERRUN) + acm->iocount.overrun++; + spin_unlock(&acm->read_lock); - dbg("input control lines: dcd%c dsr%c break%c ring%c framing%c parity%c overrun%c", - acm->ctrlin & ACM_CTRL_DCD ? '+' : '-', acm->ctrlin & ACM_CTRL_DSR ? '+' : '-', - acm->ctrlin & ACM_CTRL_BRK ? '+' : '-', acm->ctrlin & ACM_CTRL_RI ? '+' : '-', - acm->ctrlin & ACM_CTRL_FRAMING ? '+' : '-', acm->ctrlin & ACM_CTRL_PARITY ? '+' : '-', - acm->ctrlin & ACM_CTRL_OVERRUN ? '+' : '-'); + if (difference) + wake_up_all(&acm->wioctl); - break; + break; - default: - dbg("unknown notification %d received: index %d len %d data0 %d data1 %d", - dr->bNotificationType, dr->wIndex, - dr->wLength, data[0], data[1]); - break; + default: + dev_dbg(&acm->control->dev, + "%s - unknown notification %d received: index %d " + "len %d data0 %d data1 %d\n", + __func__, + dr->bNotificationType, dr->wIndex, + dr->wLength, data[0], data[1]); + break; } exit: - retval = usb_submit_urb (urb, GFP_ATOMIC); + retval = usb_submit_urb(urb, GFP_ATOMIC); if (retval) - err ("%s - usb_submit_urb failed with result %d", - __FUNCTION__, retval); + dev_err(&acm->control->dev, "%s - usb_submit_urb failed: %d\n", + __func__, retval); } -/* data interface returns incoming bytes, or we got unthrottled */ -static void acm_read_bulk(struct urb *urb) +static int acm_submit_read_urb(struct acm *acm, int index, gfp_t mem_flags) { - struct acm_rb *buf; - struct acm_ru *rcv = urb->context; - struct acm *acm = rcv->instance; - int status = urb->status; + int res; - dbg("Entering acm_read_bulk with status %d", status); + if (!test_and_clear_bit(index, &acm->read_urbs_free)) + return 0; - if (!ACM_READY(acm)) - return; + dev_vdbg(&acm->data->dev, "%s - urb %d\n", __func__, index); - if (status) - dev_dbg(&acm->data->dev, "bulk rx status %d\n", status); + res = usb_submit_urb(acm->read_urbs[index], mem_flags); + if (res) { + if (res != -EPERM) { + dev_err(&acm->data->dev, + "%s - usb_submit_urb failed: %d\n", + __func__, res); + } + set_bit(index, &acm->read_urbs_free); + return res; + } - buf = rcv->buffer; - buf->size = urb->actual_length; + return 0; +} - if (likely(status == 0)) { - spin_lock(&acm->read_lock); - list_add_tail(&rcv->list, &acm->spare_read_urbs); - list_add_tail(&buf->list, &acm->filled_read_bufs); - spin_unlock(&acm->read_lock); - } else { - /* we drop the buffer due to an error */ - spin_lock(&acm->read_lock); - list_add_tail(&rcv->list, &acm->spare_read_urbs); - list_add(&buf->list, &acm->spare_read_bufs); - spin_unlock(&acm->read_lock); - /* nevertheless the tasklet must be kicked unconditionally - so the queue cannot dry up */ +static int acm_submit_read_urbs(struct acm *acm, gfp_t mem_flags) +{ + int res; + int i; + + for (i = 0; i < acm->rx_buflimit; ++i) { + res = acm_submit_read_urb(acm, i, mem_flags); + if (res) + return res; } - tasklet_schedule(&acm->urb_task); + + return 0; } -static void acm_rx_tasklet(unsigned long _acm) +static void acm_process_read_urb(struct acm *acm, struct urb *urb) { - struct acm *acm = (void *)_acm; - struct acm_rb *buf; - struct tty_struct *tty = acm->tty; - struct acm_ru *rcv; + if (!urb->actual_length) + return; + + tty_insert_flip_string(&acm->port, urb->transfer_buffer, + urb->actual_length); + tty_flip_buffer_push(&acm->port); +} + +static void acm_read_bulk_callback(struct urb *urb) +{ + struct acm_rb *rb = urb->context; + struct acm *acm = rb->instance; unsigned long flags; - unsigned char throttled; - dbg("Entering acm_rx_tasklet"); - if (!ACM_READY(acm)) - return; + dev_vdbg(&acm->data->dev, "%s - urb %d, len %d\n", __func__, + rb->index, urb->actual_length); + set_bit(rb->index, &acm->read_urbs_free); - spin_lock_irqsave(&acm->throttle_lock, flags); - throttled = acm->throttle; - spin_unlock_irqrestore(&acm->throttle_lock, flags); - if (throttled) + if (!acm->dev) { + dev_dbg(&acm->data->dev, "%s - disconnected\n", __func__); return; - -next_buffer: - spin_lock_irqsave(&acm->read_lock, flags); - if (list_empty(&acm->filled_read_bufs)) { - spin_unlock_irqrestore(&acm->read_lock, flags); - goto urbs; } - buf = list_entry(acm->filled_read_bufs.next, - struct acm_rb, list); - list_del(&buf->list); - spin_unlock_irqrestore(&acm->read_lock, flags); - - dbg("acm_rx_tasklet: procesing buf 0x%p, size = %d", buf, buf->size); - - tty_buffer_request_room(tty, buf->size); - spin_lock_irqsave(&acm->throttle_lock, flags); - throttled = acm->throttle; - spin_unlock_irqrestore(&acm->throttle_lock, flags); - if (!throttled) - tty_insert_flip_string(tty, buf->base, buf->size); - tty_flip_buffer_push(tty); - - if (throttled) { - dbg("Throttling noticed"); - spin_lock_irqsave(&acm->read_lock, flags); - list_add(&buf->list, &acm->filled_read_bufs); - spin_unlock_irqrestore(&acm->read_lock, flags); + + if (urb->status) { + dev_dbg(&acm->data->dev, "%s - non-zero urb status: %d\n", + __func__, urb->status); return; } + usb_mark_last_busy(acm->dev); + + acm_process_read_urb(acm, urb); + + /* throttle device if requested by tty */ spin_lock_irqsave(&acm->read_lock, flags); - list_add(&buf->list, &acm->spare_read_bufs); - spin_unlock_irqrestore(&acm->read_lock, flags); - goto next_buffer; - -urbs: - while (!list_empty(&acm->spare_read_bufs)) { - spin_lock_irqsave(&acm->read_lock, flags); - if (list_empty(&acm->spare_read_urbs)) { - spin_unlock_irqrestore(&acm->read_lock, flags); - return; - } - rcv = list_entry(acm->spare_read_urbs.next, - struct acm_ru, list); - list_del(&rcv->list); + acm->throttled = acm->throttle_req; + if (!acm->throttled) { + spin_unlock_irqrestore(&acm->read_lock, flags); + acm_submit_read_urb(acm, rb->index, GFP_ATOMIC); + } else { spin_unlock_irqrestore(&acm->read_lock, flags); - - buf = list_entry(acm->spare_read_bufs.next, - struct acm_rb, list); - list_del(&buf->list); - - rcv->buffer = buf; - - usb_fill_bulk_urb(rcv->urb, acm->dev, - acm->rx_endpoint, - buf->base, - acm->readsize, - acm_read_bulk, rcv); - 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) { - list_add(&buf->list, &acm->spare_read_bufs); - spin_lock_irqsave(&acm->read_lock, flags); - list_add(&rcv->list, &acm->spare_read_urbs); - spin_unlock_irqrestore(&acm->read_lock, flags); - return; - } } } /* data interface wrote those outgoing bytes */ static void acm_write_bulk(struct urb *urb) { - struct acm *acm = (struct acm *)urb->context; + struct acm_wb *wb = urb->context; + struct acm *acm = wb->instance; + unsigned long flags; - dbg("Entering acm_write_bulk with status %d", urb->status); + if (urb->status || (urb->actual_length != urb->transfer_buffer_length)) + dev_vdbg(&acm->data->dev, "%s - len %d/%d, status %d\n", + __func__, + urb->actual_length, + urb->transfer_buffer_length, + urb->status); - acm_write_done(acm); - acm_write_start(acm); - if (ACM_READY(acm)) - schedule_work(&acm->work); + spin_lock_irqsave(&acm->write_lock, flags); + acm_write_done(acm, wb); + spin_unlock_irqrestore(&acm->write_lock, flags); + schedule_work(&acm->work); } static void acm_softint(struct work_struct *work) { struct acm *acm = container_of(work, struct acm, work); - dbg("Entering acm_softint."); - - if (!ACM_READY(acm)) - return; - tty_wakeup(acm->tty); + + dev_vdbg(&acm->data->dev, "%s\n", __func__); + + tty_port_tty_wakeup(&acm->port); } /* * TTY handlers */ -static int acm_tty_open(struct tty_struct *tty, struct file *filp) +static int acm_tty_install(struct tty_driver *driver, struct tty_struct *tty) { struct acm *acm; - int rv = -EINVAL; - int i; - dbg("Entering acm_tty_open."); + int retval; - mutex_lock(&open_mutex); + dev_dbg(tty->dev, "%s\n", __func__); - acm = acm_table[tty->index]; - if (!acm || !acm->dev) - goto err_out; - else - rv = 0; + acm = acm_get_by_index(tty->index); + if (!acm) + return -ENODEV; + + retval = tty_standard_install(driver, tty); + if (retval) + goto error_init_termios; tty->driver_data = acm; - acm->tty = tty; - /* force low_latency on so that our tty_push actually forces the data through, - otherwise it is scheduled, and with high data rates data can get lost. */ - tty->low_latency = 1; + return 0; + +error_init_termios: + tty_port_put(&acm->port); + return retval; +} + +static int acm_tty_open(struct tty_struct *tty, struct file *filp) +{ + struct acm *acm = tty->driver_data; - if (usb_autopm_get_interface(acm->control) < 0) - goto early_bail; + dev_dbg(tty->dev, "%s\n", __func__); + + return tty_port_open(&acm->port, tty, filp); +} + +static void acm_port_dtr_rts(struct tty_port *port, int raise) +{ + struct acm *acm = container_of(port, struct acm, port); + int val; + int res; + + if (raise) + val = ACM_CTRL_DTR | ACM_CTRL_RTS; + else + val = 0; + + /* FIXME: add missing ctrlout locking throughout driver */ + acm->ctrlout = val; + + res = acm_set_control(acm, val); + if (res && (acm->ctrl_caps & USB_CDC_CAP_LINE)) + dev_err(&acm->control->dev, "failed to set dtr/rts\n"); +} + +static int acm_port_activate(struct tty_port *port, struct tty_struct *tty) +{ + struct acm *acm = container_of(port, struct acm, port); + int retval = -ENODEV; + int i; + + dev_dbg(&acm->control->dev, "%s\n", __func__); mutex_lock(&acm->mutex); - if (acm->used++) { - usb_autopm_put_interface(acm->control); - goto done; - } + if (acm->disconnected) + goto disconnected; + + retval = usb_autopm_get_interface(acm->control); + if (retval) + goto error_get_interface; + /* + * FIXME: Why do we need this? Allocating 64K of physically contiguous + * memory is really nasty... + */ + set_bit(TTY_NO_WRITE_SPLIT, &tty->flags); + acm->control->needs_remote_wakeup = 1; acm->ctrlurb->dev = acm->dev; - if (usb_submit_urb(acm->ctrlurb, GFP_KERNEL)) { - dbg("usb_submit_urb(ctrl irq) failed"); - goto bail_out; + retval = usb_submit_urb(acm->ctrlurb, GFP_KERNEL); + if (retval) { + dev_err(&acm->control->dev, + "%s - usb_submit_urb(ctrl irq) failed\n", __func__); + goto error_submit_urb; } - if (0 > acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS) && - (acm->ctrl_caps & USB_CDC_CAP_LINE)) - goto full_bailout; - - INIT_LIST_HEAD(&acm->spare_read_urbs); - INIT_LIST_HEAD(&acm->spare_read_bufs); - INIT_LIST_HEAD(&acm->filled_read_bufs); - for (i = 0; i < acm->rx_buflimit; i++) { - list_add(&(acm->ru[i].list), &acm->spare_read_urbs); - } - for (i = 0; i < acm->rx_buflimit; i++) { - list_add(&(acm->rb[i].list), &acm->spare_read_bufs); - } + /* + * Unthrottle device in case the TTY was closed while throttled. + */ + spin_lock_irq(&acm->read_lock); + acm->throttled = 0; + acm->throttle_req = 0; + spin_unlock_irq(&acm->read_lock); - acm->throttle = 0; + retval = acm_submit_read_urbs(acm, GFP_KERNEL); + if (retval) + goto error_submit_read_urbs; - tasklet_schedule(&acm->urb_task); + usb_autopm_put_interface(acm->control); -done: -err_out: mutex_unlock(&acm->mutex); - mutex_unlock(&open_mutex); - return rv; -full_bailout: + return 0; + +error_submit_read_urbs: + for (i = 0; i < acm->rx_buflimit; i++) + usb_kill_urb(acm->read_urbs[i]); usb_kill_urb(acm->ctrlurb); -bail_out: +error_submit_urb: usb_autopm_put_interface(acm->control); - acm->used--; +error_get_interface: +disconnected: mutex_unlock(&acm->mutex); -early_bail: - mutex_unlock(&open_mutex); - return -EIO; + + return usb_translate_errors(retval); } -static void acm_tty_unregister(struct acm *acm) +static void acm_port_destruct(struct tty_port *port) { - int i,nr; + struct acm *acm = container_of(port, struct acm, port); - nr = acm->rx_buflimit; - tty_unregister_device(acm_tty_driver, acm->minor); + dev_dbg(&acm->control->dev, "%s\n", __func__); + + acm_release_minor(acm); usb_put_intf(acm->control); - acm_table[acm->minor] = NULL; - usb_free_urb(acm->ctrlurb); - usb_free_urb(acm->writeurb); - for (i = 0; i < nr; i++) - usb_free_urb(acm->ru[i].urb); kfree(acm->country_codes); kfree(acm); } -static void acm_tty_close(struct tty_struct *tty, struct file *filp) +static void acm_port_shutdown(struct tty_port *port) { - struct acm *acm = tty->driver_data; - int i,nr; + struct acm *acm = container_of(port, struct acm, port); + struct urb *urb; + struct acm_wb *wb; + int i; - if (!acm || !acm->used) - return; + dev_dbg(&acm->control->dev, "%s\n", __func__); + + /* + * Need to grab write_lock to prevent race with resume, but no need to + * hold it due to the tty-port initialised flag. + */ + spin_lock_irq(&acm->write_lock); + spin_unlock_irq(&acm->write_lock); + + usb_autopm_get_interface_no_resume(acm->control); + acm->control->needs_remote_wakeup = 0; + usb_autopm_put_interface(acm->control); - nr = acm->rx_buflimit; - mutex_lock(&open_mutex); - if (!--acm->used) { - if (acm->dev) { - acm_set_control(acm, acm->ctrlout = 0); - usb_kill_urb(acm->ctrlurb); - usb_kill_urb(acm->writeurb); - for (i = 0; i < nr; i++) - usb_kill_urb(acm->ru[i].urb); - usb_autopm_put_interface(acm->control); - } else - acm_tty_unregister(acm); + for (;;) { + urb = usb_get_from_anchor(&acm->delayed); + if (!urb) + break; + wb = urb->context; + wb->use = 0; + usb_autopm_put_interface_async(acm->control); } - mutex_unlock(&open_mutex); + + usb_kill_urb(acm->ctrlurb); + for (i = 0; i < ACM_NW; i++) + usb_kill_urb(acm->wb[i].urb); + for (i = 0; i < acm->rx_buflimit; i++) + usb_kill_urb(acm->read_urbs[i]); } -static int acm_tty_write(struct tty_struct *tty, const unsigned char *buf, int count) +static void acm_tty_cleanup(struct tty_struct *tty) +{ + struct acm *acm = tty->driver_data; + dev_dbg(&acm->control->dev, "%s\n", __func__); + tty_port_put(&acm->port); +} + +static void acm_tty_hangup(struct tty_struct *tty) +{ + struct acm *acm = tty->driver_data; + dev_dbg(&acm->control->dev, "%s\n", __func__); + tty_port_hangup(&acm->port); +} + +static void acm_tty_close(struct tty_struct *tty, struct file *filp) +{ + struct acm *acm = tty->driver_data; + dev_dbg(&acm->control->dev, "%s\n", __func__); + tty_port_close(&acm->port, tty, filp); +} + +static int acm_tty_write(struct tty_struct *tty, + const unsigned char *buf, int count) { struct acm *acm = tty->driver_data; int stat; @@ -595,28 +663,47 @@ static int acm_tty_write(struct tty_struct *tty, const unsigned char *buf, int c int wbn; struct acm_wb *wb; - dbg("Entering acm_tty_write to write %d bytes,", count); - - if (!ACM_READY(acm)) - return -EINVAL; if (!count) return 0; + dev_vdbg(&acm->data->dev, "%s - count %d\n", __func__, count); + spin_lock_irqsave(&acm->write_lock, flags); - if ((wbn = acm_wb_alloc(acm)) < 0) { + wbn = acm_wb_alloc(acm); + if (wbn < 0) { spin_unlock_irqrestore(&acm->write_lock, flags); - acm_write_start(acm); return 0; } wb = &acm->wb[wbn]; + if (!acm->dev) { + wb->use = 0; + spin_unlock_irqrestore(&acm->write_lock, flags); + return -ENODEV; + } + count = (count > acm->writesize) ? acm->writesize : count; - dbg("Get %d bytes...", count); + dev_vdbg(&acm->data->dev, "%s - write %d\n", __func__, count); memcpy(wb->buf, buf, count); wb->len = count; + + stat = usb_autopm_get_interface_async(acm->control); + if (stat) { + wb->use = 0; + spin_unlock_irqrestore(&acm->write_lock, flags); + return stat; + } + + if (acm->susp_count) { + usb_anchor_urb(wb->urb, &acm->delayed); + spin_unlock_irqrestore(&acm->write_lock, flags); + return count; + } + + stat = acm_start_wb(acm, wb); spin_unlock_irqrestore(&acm->write_lock, flags); - if ((stat = acm_write_start(acm)) < 0) + if (stat < 0) return stat; return count; } @@ -624,20 +711,22 @@ static int acm_tty_write(struct tty_struct *tty, const unsigned char *buf, int c static int acm_tty_write_room(struct tty_struct *tty) { struct acm *acm = tty->driver_data; - if (!ACM_READY(acm)) - return -EINVAL; /* * 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) { struct acm *acm = tty->driver_data; - if (!ACM_READY(acm)) - return -EINVAL; + /* + * if the device was unplugged then any remaining characters fell out + * of the connector ;) + */ + if (acm->disconnected) + return 0; /* * This is inaccurate (overcounts), but it works. */ @@ -647,40 +736,43 @@ static int acm_tty_chars_in_buffer(struct tty_struct *tty) static void acm_tty_throttle(struct tty_struct *tty) { struct acm *acm = tty->driver_data; - if (!ACM_READY(acm)) - return; - spin_lock_bh(&acm->throttle_lock); - acm->throttle = 1; - spin_unlock_bh(&acm->throttle_lock); + + spin_lock_irq(&acm->read_lock); + acm->throttle_req = 1; + spin_unlock_irq(&acm->read_lock); } static void acm_tty_unthrottle(struct tty_struct *tty) { struct acm *acm = tty->driver_data; - if (!ACM_READY(acm)) - return; - spin_lock_bh(&acm->throttle_lock); - acm->throttle = 0; - spin_unlock_bh(&acm->throttle_lock); - tasklet_schedule(&acm->urb_task); + unsigned int was_throttled; + + spin_lock_irq(&acm->read_lock); + was_throttled = acm->throttled; + acm->throttled = 0; + acm->throttle_req = 0; + spin_unlock_irq(&acm->read_lock); + + if (was_throttled) + acm_submit_read_urbs(acm, GFP_KERNEL); } -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; - if (!ACM_READY(acm)) - return; - if (acm_send_break(acm, state ? 0xffff : 0)) - dbg("send break failed"); + int retval; + + retval = acm_send_break(acm, state ? 0xffff : 0); + if (retval < 0) + dev_dbg(&acm->control->dev, "%s - send break failed\n", + __func__); + return retval; } -static int acm_tty_tiocmget(struct tty_struct *tty, struct file *file) +static int acm_tty_tiocmget(struct tty_struct *tty) { struct acm *acm = tty->driver_data; - if (!ACM_READY(acm)) - return -EINVAL; - return (acm->ctrlout & ACM_CTRL_DTR ? TIOCM_DTR : 0) | (acm->ctrlout & ACM_CTRL_RTS ? TIOCM_RTS : 0) | (acm->ctrlin & ACM_CTRL_DSR ? TIOCM_DSR : 0) | @@ -689,18 +781,17 @@ static int acm_tty_tiocmget(struct tty_struct *tty, struct file *file) TIOCM_CTS; } -static int acm_tty_tiocmset(struct tty_struct *tty, struct file *file, +static int acm_tty_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear) { struct acm *acm = tty->driver_data; unsigned int newctrl; - if (!ACM_READY(acm)) - return -EINVAL; - newctrl = acm->ctrlout; - set = (set & TIOCM_DTR ? ACM_CTRL_DTR : 0) | (set & TIOCM_RTS ? ACM_CTRL_RTS : 0); - clear = (clear & TIOCM_DTR ? ACM_CTRL_DTR : 0) | (clear & TIOCM_RTS ? ACM_CTRL_RTS : 0); + set = (set & TIOCM_DTR ? ACM_CTRL_DTR : 0) | + (set & TIOCM_RTS ? ACM_CTRL_RTS : 0); + clear = (clear & TIOCM_DTR ? ACM_CTRL_DTR : 0) | + (clear & TIOCM_RTS ? ACM_CTRL_RTS : 0); newctrl = (newctrl & ~clear) | set; @@ -709,77 +800,236 @@ static int acm_tty_tiocmset(struct tty_struct *tty, struct file *file, return acm_set_control(acm, acm->ctrlout = newctrl); } -static int acm_tty_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg) +static int get_serial_info(struct acm *acm, struct serial_struct __user *info) { - struct acm *acm = tty->driver_data; + struct serial_struct tmp; - if (!ACM_READY(acm)) + if (!info) return -EINVAL; - return -ENOIOCTLCMD; + memset(&tmp, 0, sizeof(tmp)); + tmp.flags = ASYNC_LOW_LATENCY; + tmp.xmit_fifo_size = acm->writesize; + tmp.baud_base = le32_to_cpu(acm->line.dwDTERate); + tmp.close_delay = acm->port.close_delay / 10; + tmp.closing_wait = acm->port.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : + acm->port.closing_wait / 10; + + if (copy_to_user(info, &tmp, sizeof(tmp))) + return -EFAULT; + else + return 0; } -static const __u32 acm_tty_speed[] = { - 0, 50, 75, 110, 134, 150, 200, 300, 600, - 1200, 1800, 2400, 4800, 9600, 19200, 38400, - 57600, 115200, 230400, 460800, 500000, 576000, - 921600, 1000000, 1152000, 1500000, 2000000, - 2500000, 3000000, 3500000, 4000000 -}; +static int set_serial_info(struct acm *acm, + struct serial_struct __user *newinfo) +{ + struct serial_struct new_serial; + unsigned int closing_wait, close_delay; + int retval = 0; -static const __u8 acm_tty_size[] = { - 5, 6, 7, 8 -}; + if (copy_from_user(&new_serial, newinfo, sizeof(new_serial))) + return -EFAULT; + + close_delay = new_serial.close_delay * 10; + closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : new_serial.closing_wait * 10; + + mutex_lock(&acm->port.mutex); + + if (!capable(CAP_SYS_ADMIN)) { + if ((close_delay != acm->port.close_delay) || + (closing_wait != acm->port.closing_wait)) + retval = -EPERM; + else + retval = -EOPNOTSUPP; + } else { + acm->port.close_delay = close_delay; + acm->port.closing_wait = closing_wait; + } + + mutex_unlock(&acm->port.mutex); + return retval; +} -static void acm_tty_set_termios(struct tty_struct *tty, struct ktermios *termios_old) +static int wait_serial_change(struct acm *acm, unsigned long arg) +{ + int rv = 0; + DECLARE_WAITQUEUE(wait, current); + struct async_icount old, new; + + if (arg & (TIOCM_DSR | TIOCM_RI | TIOCM_CD )) + return -EINVAL; + do { + spin_lock_irq(&acm->read_lock); + old = acm->oldcount; + new = acm->iocount; + acm->oldcount = new; + spin_unlock_irq(&acm->read_lock); + + if ((arg & TIOCM_DSR) && + old.dsr != new.dsr) + break; + if ((arg & TIOCM_CD) && + old.dcd != new.dcd) + break; + if ((arg & TIOCM_RI) && + old.rng != new.rng) + break; + + add_wait_queue(&acm->wioctl, &wait); + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + remove_wait_queue(&acm->wioctl, &wait); + if (acm->disconnected) { + if (arg & TIOCM_CD) + break; + else + rv = -ENODEV; + } else { + if (signal_pending(current)) + rv = -ERESTARTSYS; + } + } while (!rv); + + + + return rv; +} + +static int get_serial_usage(struct acm *acm, + struct serial_icounter_struct __user *count) +{ + struct serial_icounter_struct icount; + int rv = 0; + + memset(&icount, 0, sizeof(icount)); + icount.dsr = acm->iocount.dsr; + icount.rng = acm->iocount.rng; + icount.dcd = acm->iocount.dcd; + icount.frame = acm->iocount.frame; + icount.overrun = acm->iocount.overrun; + icount.parity = acm->iocount.parity; + icount.brk = acm->iocount.brk; + + if (copy_to_user(count, &icount, sizeof(icount)) > 0) + rv = -EFAULT; + + return rv; +} + +static int acm_tty_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) { struct acm *acm = tty->driver_data; - struct ktermios *termios = tty->termios; + int rv = -ENOIOCTLCMD; + + switch (cmd) { + case TIOCGSERIAL: /* gets serial port data */ + rv = get_serial_info(acm, (struct serial_struct __user *) arg); + break; + case TIOCSSERIAL: + rv = set_serial_info(acm, (struct serial_struct __user *) arg); + break; + case TIOCMIWAIT: + rv = usb_autopm_get_interface(acm->control); + if (rv < 0) { + rv = -EIO; + break; + } + rv = wait_serial_change(acm, arg); + usb_autopm_put_interface(acm->control); + break; + case TIOCGICOUNT: + rv = get_serial_usage(acm, (struct serial_icounter_struct __user *) arg); + break; + } + + return rv; +} + +static void acm_tty_set_termios(struct tty_struct *tty, + struct ktermios *termios_old) +{ + struct acm *acm = tty->driver_data; + struct ktermios *termios = &tty->termios; struct usb_cdc_line_coding newline; int newctrl = acm->ctrlout; - if (!ACM_READY(acm)) - return; - - newline.dwDTERate = cpu_to_le32p(acm_tty_speed + - (termios->c_cflag & CBAUD & ~CBAUDEX) + (termios->c_cflag & CBAUDEX ? 15 : 0)); + newline.dwDTERate = cpu_to_le32(tty_get_baud_rate(tty)); newline.bCharFormat = termios->c_cflag & CSTOPB ? 2 : 0; newline.bParityType = termios->c_cflag & PARENB ? - (termios->c_cflag & PARODD ? 1 : 2) + (termios->c_cflag & CMSPAR ? 2 : 0) : 0; - newline.bDataBits = acm_tty_size[(termios->c_cflag & CSIZE) >> 4]; - + (termios->c_cflag & PARODD ? 1 : 2) + + (termios->c_cflag & CMSPAR ? 2 : 0) : 0; + switch (termios->c_cflag & CSIZE) { + case CS5: + newline.bDataBits = 5; + break; + case CS6: + newline.bDataBits = 6; + break; + case CS7: + newline.bDataBits = 7; + break; + case CS8: + default: + newline.bDataBits = 8; + break; + } + /* FIXME: Needs to clear unsupported bits in the termios */ acm->clocal = ((termios->c_cflag & CLOCAL) != 0); if (!newline.dwDTERate) { newline.dwDTERate = acm->line.dwDTERate; newctrl &= ~ACM_CTRL_DTR; - } else newctrl |= ACM_CTRL_DTR; + } else + newctrl |= ACM_CTRL_DTR; if (newctrl != acm->ctrlout) acm_set_control(acm, acm->ctrlout = newctrl); if (memcmp(&acm->line, &newline, sizeof newline)) { memcpy(&acm->line, &newline, sizeof newline); - dbg("set line: %d %d %d %d", le32_to_cpu(newline.dwDTERate), + dev_dbg(&acm->control->dev, "%s - set line: %d %d %d %d\n", + __func__, + le32_to_cpu(newline.dwDTERate), newline.bCharFormat, newline.bParityType, newline.bDataBits); acm_set_line(acm, &acm->line); } } +static const struct tty_port_operations acm_port_ops = { + .dtr_rts = acm_port_dtr_rts, + .shutdown = acm_port_shutdown, + .activate = acm_port_activate, + .destruct = acm_port_destruct, +}; + /* * 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; struct acm_wb *wb; + struct usb_device *usb_dev = interface_to_usbdev(acm->control); - for (wb = &acm->wb[0], i = 0; i < ACM_NW; i++, wb++) { - usb_buffer_free(acm->dev, acm->writesize, wb->buf, wb->dmah); - } + for (wb = &acm->wb[0], i = 0; i < ACM_NW; i++, wb++) + usb_free_coherent(usb_dev, acm->writesize, wb->buf, wb->dmah); +} + +static void acm_read_buffers_free(struct acm *acm) +{ + struct usb_device *usb_dev = interface_to_usbdev(acm->control); + int i; + + for (i = 0; i < acm->rx_buflimit; i++) + usb_free_coherent(usb_dev, acm->readsize, + acm->read_buffers[i].base, acm->read_buffers[i].dma); } /* Little helper: write buffers allocate */ @@ -789,13 +1039,13 @@ static int acm_write_buffers_alloc(struct acm *acm) struct acm_wb *wb; for (wb = &acm->wb[0], i = 0; i < ACM_NW; i++, wb++) { - wb->buf = usb_buffer_alloc(acm->dev, acm->writesize, GFP_KERNEL, + wb->buf = usb_alloc_coherent(acm->dev, acm->writesize, GFP_KERNEL, &wb->dmah); if (!wb->buf) { while (i != 0) { --i; --wb; - usb_buffer_free(acm->dev, acm->writesize, + usb_free_coherent(acm->dev, acm->writesize, wb->buf, wb->dmah); } return -ENOMEM; @@ -804,33 +1054,40 @@ static int acm_write_buffers_alloc(struct acm *acm) return 0; } -static int acm_probe (struct usb_interface *intf, - const struct usb_device_id *id) +static int acm_probe(struct usb_interface *intf, + const struct usb_device_id *id) { struct usb_cdc_union_desc *union_header = NULL; struct usb_cdc_country_functional_desc *cfd = NULL; - char *buffer = intf->altsetting->extra; + unsigned char *buffer = intf->altsetting->extra; int buflen = intf->altsetting->extralen; struct usb_interface *control_interface; struct usb_interface *data_interface; - struct usb_endpoint_descriptor *epctrl; - struct usb_endpoint_descriptor *epread; - struct usb_endpoint_descriptor *epwrite; + struct usb_endpoint_descriptor *epctrl = NULL; + struct usb_endpoint_descriptor *epread = NULL; + struct usb_endpoint_descriptor *epwrite = NULL; struct usb_device *usb_dev = interface_to_usbdev(intf); struct acm *acm; int minor; - int ctrlsize,readsize; + int ctrlsize, readsize; u8 *buf; u8 ac_management_function = 0; u8 call_management_function = 0; int call_interface_num = -1; - int data_interface_num; + int data_interface_num = -1; unsigned long quirks; int num_rx_buf; int i; + int combined_interfaces = 0; + struct device *tty_dev; + int rv = -ENOMEM; /* normal quirks */ quirks = (unsigned long)id->driver_info; + + if (quirks == IGNORE_DEVICE) + return -ENODEV; + num_rx_buf = (quirks == SINGLE_RX_URB) ? 1 : ACM_NR; /* handle quirks deadly to normal probing*/ @@ -839,58 +1096,66 @@ static int acm_probe (struct usb_interface *intf, control_interface = usb_ifnum_to_if(usb_dev, 0); goto skip_normal_probe; } - + /* normal probing*/ if (!buffer) { - err("Weird descriptor references\n"); + dev_err(&intf->dev, "Weird descriptor references\n"); return -EINVAL; } if (!buflen) { - if (intf->cur_altsetting->endpoint->extralen && intf->cur_altsetting->endpoint->extra) { - dev_dbg(&intf->dev,"Seeking extra descriptors on endpoint\n"); + if (intf->cur_altsetting->endpoint && + intf->cur_altsetting->endpoint->extralen && + intf->cur_altsetting->endpoint->extra) { + dev_dbg(&intf->dev, + "Seeking extra descriptors on endpoint\n"); buflen = intf->cur_altsetting->endpoint->extralen; buffer = intf->cur_altsetting->endpoint->extra; } else { - err("Zero length descriptor references\n"); + dev_err(&intf->dev, + "Zero length descriptor references\n"); return -EINVAL; } } while (buflen > 0) { - if (buffer [1] != USB_DT_CS_INTERFACE) { - err("skipping garbage\n"); + if (buffer[1] != USB_DT_CS_INTERFACE) { + dev_err(&intf->dev, "skipping garbage\n"); goto next_desc; } - switch (buffer [2]) { - case USB_CDC_UNION_TYPE: /* we've found it */ - if (union_header) { - err("More than one union descriptor, skipping ..."); - goto next_desc; - } - union_header = (struct usb_cdc_union_desc *) - buffer; - break; - case USB_CDC_COUNTRY_TYPE: /* export through sysfs*/ - cfd = (struct usb_cdc_country_functional_desc *)buffer; - break; - case USB_CDC_HEADER_TYPE: /* maybe check version */ - break; /* for now we ignore it */ - case USB_CDC_ACM_TYPE: - ac_management_function = buffer[3]; - break; - case USB_CDC_CALL_MANAGEMENT_TYPE: - call_management_function = buffer[3]; - call_interface_num = buffer[4]; - if ((call_management_function & 3) != 3) - err("This device cannot do calls on its own. It is no modem."); - break; - - default: - err("Ignoring extra header, type %d, length %d", buffer[2], buffer[0]); - break; + switch (buffer[2]) { + case USB_CDC_UNION_TYPE: /* we've found it */ + if (union_header) { + dev_err(&intf->dev, "More than one " + "union descriptor, skipping ...\n"); + goto next_desc; } + union_header = (struct usb_cdc_union_desc *)buffer; + break; + case USB_CDC_COUNTRY_TYPE: /* export through sysfs*/ + cfd = (struct usb_cdc_country_functional_desc *)buffer; + break; + case USB_CDC_HEADER_TYPE: /* maybe check version */ + break; /* for now we ignore it */ + case USB_CDC_ACM_TYPE: + ac_management_function = buffer[3]; + break; + case USB_CDC_CALL_MANAGEMENT_TYPE: + call_management_function = buffer[3]; + call_interface_num = buffer[4]; + if ((quirks & NOT_A_MODEM) == 0 && (call_management_function & 3) != 3) + dev_err(&intf->dev, "This device cannot do calls on its own. It is not a modem.\n"); + break; + default: + /* there are LOTS more CDC descriptors that + * could legitimately be found here. + */ + dev_dbg(&intf->dev, "Ignoring descriptor: " + "type %02x, length %d\n", + buffer[2], buffer[0]); + break; + } next_desc: buflen -= buffer[0]; buffer += buffer[0]; @@ -898,33 +1163,76 @@ next_desc: if (!union_header) { if (call_interface_num > 0) { - dev_dbg(&intf->dev,"No union descriptor, using call management descriptor\n"); - data_interface = usb_ifnum_to_if(usb_dev, (data_interface_num = call_interface_num)); + dev_dbg(&intf->dev, "No union descriptor, using call management descriptor\n"); + /* quirks for Droids MuIn LCD */ + if (quirks & NO_DATA_INTERFACE) + data_interface = usb_ifnum_to_if(usb_dev, 0); + else + data_interface = usb_ifnum_to_if(usb_dev, (data_interface_num = call_interface_num)); control_interface = intf; } else { - dev_dbg(&intf->dev,"No union descriptor, giving up\n"); - return -ENODEV; + if (intf->cur_altsetting->desc.bNumEndpoints != 3) { + dev_dbg(&intf->dev,"No union descriptor, giving up\n"); + return -ENODEV; + } else { + dev_warn(&intf->dev,"No union descriptor, testing for castrated device\n"); + combined_interfaces = 1; + control_interface = data_interface = intf; + goto look_for_collapsed_interface; + } } } else { control_interface = usb_ifnum_to_if(usb_dev, union_header->bMasterInterface0); data_interface = usb_ifnum_to_if(usb_dev, (data_interface_num = union_header->bSlaveInterface0)); if (!control_interface || !data_interface) { - dev_dbg(&intf->dev,"no interfaces\n"); + dev_dbg(&intf->dev, "no interfaces\n"); return -ENODEV; } } - + if (data_interface_num != call_interface_num) - dev_dbg(&intf->dev,"Separate call control interface. That is not fully supported.\n"); + dev_dbg(&intf->dev, "Separate call control interface. That is not fully supported.\n"); + + if (control_interface == data_interface) { + /* some broken devices designed for windows work this way */ + dev_warn(&intf->dev,"Control and data interfaces are not separated!\n"); + combined_interfaces = 1; + /* a popular other OS doesn't use it */ + quirks |= NO_CAP_LINE; + if (data_interface->cur_altsetting->desc.bNumEndpoints != 3) { + dev_err(&intf->dev, "This needs exactly 3 endpoints\n"); + return -EINVAL; + } +look_for_collapsed_interface: + for (i = 0; i < 3; i++) { + struct usb_endpoint_descriptor *ep; + ep = &data_interface->cur_altsetting->endpoint[i].desc; + + if (usb_endpoint_is_int_in(ep)) + epctrl = ep; + else if (usb_endpoint_is_bulk_out(ep)) + epwrite = ep; + else if (usb_endpoint_is_bulk_in(ep)) + epread = ep; + else + return -EINVAL; + } + if (!epctrl || !epread || !epwrite) + return -ENODEV; + else + goto made_compressed_probe; + } skip_normal_probe: /*workaround for switched interfaces */ - if (data_interface->cur_altsetting->desc.bInterfaceClass != CDC_DATA_INTERFACE_TYPE) { - if (control_interface->cur_altsetting->desc.bInterfaceClass == CDC_DATA_INTERFACE_TYPE) { + if (data_interface->cur_altsetting->desc.bInterfaceClass + != CDC_DATA_INTERFACE_TYPE) { + if (control_interface->cur_altsetting->desc.bInterfaceClass + == CDC_DATA_INTERFACE_TYPE) { struct usb_interface *t; - dev_dbg(&intf->dev,"Your device has switched interfaces.\n"); - + dev_dbg(&intf->dev, + "Your device has switched interfaces.\n"); t = control_interface; control_interface = data_interface; data_interface = t; @@ -934,16 +1242,18 @@ skip_normal_probe: } /* Accept probe requests only for the control interface */ - if (intf != control_interface) + if (!combined_interfaces && intf != control_interface) return -ENODEV; - - if (usb_interface_claimed(data_interface)) { /* valid in this context */ - dev_dbg(&intf->dev,"The data interface isn't available\n"); + + if (!combined_interfaces && usb_interface_claimed(data_interface)) { + /* valid in this context */ + dev_dbg(&intf->dev, "The data interface isn't available\n"); return -EBUSY; } - if (data_interface->cur_altsetting->desc.bNumEndpoints < 2) + if (data_interface->cur_altsetting->desc.bNumEndpoints < 2 || + control_interface->cur_altsetting->desc.bNumEndpoints == 0) return -EINVAL; epctrl = &control_interface->cur_altsetting->endpoint[0].desc; @@ -955,174 +1265,235 @@ skip_normal_probe: if (!usb_endpoint_dir_in(epread)) { /* descriptors are swapped */ struct usb_endpoint_descriptor *t; - dev_dbg(&intf->dev,"The data interface has switched endpoints\n"); - + dev_dbg(&intf->dev, + "The data interface has switched endpoints\n"); t = epread; epread = epwrite; epwrite = t; } - dbg("interfaces are valid"); - for (minor = 0; minor < ACM_TTY_MINORS && acm_table[minor]; minor++); +made_compressed_probe: + dev_dbg(&intf->dev, "interfaces are valid\n"); - if (minor == ACM_TTY_MINORS) { - err("no more free acm devices"); - return -ENODEV; + acm = kzalloc(sizeof(struct acm), GFP_KERNEL); + if (acm == NULL) { + dev_err(&intf->dev, "out of memory (acm kzalloc)\n"); + goto alloc_fail; } - if (!(acm = kzalloc(sizeof(struct acm), GFP_KERNEL))) { - dev_dbg(&intf->dev, "out of memory (acm kzalloc)\n"); - goto alloc_fail; + minor = acm_alloc_minor(acm); + if (minor == ACM_TTY_MINORS) { + dev_err(&intf->dev, "no more free acm devices\n"); + kfree(acm); + return -ENODEV; } - ctrlsize = le16_to_cpu(epctrl->wMaxPacketSize); - readsize = le16_to_cpu(epread->wMaxPacketSize)* ( quirks == SINGLE_RX_URB ? 1 : 2); - acm->writesize = le16_to_cpu(epwrite->wMaxPacketSize); + ctrlsize = usb_endpoint_maxp(epctrl); + readsize = usb_endpoint_maxp(epread) * + (quirks == SINGLE_RX_URB ? 1 : 2); + acm->combined_interfaces = combined_interfaces; + acm->writesize = usb_endpoint_maxp(epwrite) * 20; acm->control = control_interface; acm->data = data_interface; acm->minor = minor; acm->dev = usb_dev; acm->ctrl_caps = ac_management_function; + if (quirks & NO_CAP_LINE) + acm->ctrl_caps &= ~USB_CDC_CAP_LINE; acm->ctrlsize = ctrlsize; acm->readsize = readsize; acm->rx_buflimit = num_rx_buf; - acm->urb_task.func = acm_rx_tasklet; - acm->urb_task.data = (unsigned long) acm; INIT_WORK(&acm->work, acm_softint); - spin_lock_init(&acm->throttle_lock); + init_waitqueue_head(&acm->wioctl); 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); + acm->is_int_ep = usb_endpoint_xfer_int(epread); + if (acm->is_int_ep) + acm->bInterval = epread->bInterval; + tty_port_init(&acm->port); + acm->port.ops = &acm_port_ops; + init_usb_anchor(&acm->delayed); + + buf = usb_alloc_coherent(usb_dev, ctrlsize, GFP_KERNEL, &acm->ctrl_dma); if (!buf) { - dev_dbg(&intf->dev, "out of memory (ctrl buffer alloc)\n"); + dev_err(&intf->dev, "out of memory (ctrl buffer alloc)\n"); goto alloc_fail2; } acm->ctrl_buffer = buf; if (acm_write_buffers_alloc(acm) < 0) { - dev_dbg(&intf->dev, "out of memory (write buffer alloc)\n"); + dev_err(&intf->dev, "out of memory (write buffer alloc)\n"); goto alloc_fail4; } acm->ctrlurb = usb_alloc_urb(0, GFP_KERNEL); if (!acm->ctrlurb) { - dev_dbg(&intf->dev, "out of memory (ctrlurb kmalloc)\n"); + dev_err(&intf->dev, "out of memory (ctrlurb kmalloc)\n"); goto alloc_fail5; } for (i = 0; i < num_rx_buf; i++) { - struct acm_ru *rcv = &(acm->ru[i]); - - if (!(rcv->urb = usb_alloc_urb(0, GFP_KERNEL))) { - dev_dbg(&intf->dev, "out of memory (read urbs usb_alloc_urb)\n"); - goto alloc_fail7; + struct acm_rb *rb = &(acm->read_buffers[i]); + struct urb *urb; + + rb->base = usb_alloc_coherent(acm->dev, readsize, GFP_KERNEL, + &rb->dma); + if (!rb->base) { + dev_err(&intf->dev, "out of memory " + "(read bufs usb_alloc_coherent)\n"); + goto alloc_fail6; + } + rb->index = i; + rb->instance = acm; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + dev_err(&intf->dev, + "out of memory (read urbs usb_alloc_urb)\n"); + goto alloc_fail6; + } + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + urb->transfer_dma = rb->dma; + if (acm->is_int_ep) { + usb_fill_int_urb(urb, acm->dev, + acm->rx_endpoint, + rb->base, + acm->readsize, + acm_read_bulk_callback, rb, + acm->bInterval); + } else { + usb_fill_bulk_urb(urb, acm->dev, + acm->rx_endpoint, + rb->base, + acm->readsize, + acm_read_bulk_callback, rb); } - rcv->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; - rcv->instance = acm; + acm->read_urbs[i] = urb; + __set_bit(i, &acm->read_urbs_free); } - for (i = 0; i < num_rx_buf; i++) { - struct acm_rb *buf = &(acm->rb[i]); + for (i = 0; i < ACM_NW; i++) { + struct acm_wb *snd = &(acm->wb[i]); - if (!(buf->base = usb_buffer_alloc(acm->dev, readsize, GFP_KERNEL, &buf->dma))) { - dev_dbg(&intf->dev, "out of memory (read bufs usb_buffer_alloc)\n"); + snd->urb = usb_alloc_urb(0, GFP_KERNEL); + if (snd->urb == NULL) { + dev_err(&intf->dev, + "out of memory (write urbs usb_alloc_urb)\n"); goto alloc_fail7; } - } - acm->writeurb = usb_alloc_urb(0, GFP_KERNEL); - if (!acm->writeurb) { - dev_dbg(&intf->dev, "out of memory (writeurb kmalloc)\n"); - goto alloc_fail7; + + if (usb_endpoint_xfer_int(epwrite)) + usb_fill_int_urb(snd->urb, usb_dev, + usb_sndintpipe(usb_dev, epwrite->bEndpointAddress), + NULL, acm->writesize, acm_write_bulk, snd, epwrite->bInterval); + else + usb_fill_bulk_urb(snd->urb, usb_dev, + usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress), + NULL, acm->writesize, acm_write_bulk, snd); + snd->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + snd->instance = acm; } - usb_set_intfdata (intf, acm); + usb_set_intfdata(intf, acm); i = device_create_file(&intf->dev, &dev_attr_bmCapabilities); if (i < 0) - goto alloc_fail8; + goto alloc_fail7; if (cfd) { /* export the country data */ acm->country_codes = kmalloc(cfd->bLength - 4, GFP_KERNEL); if (!acm->country_codes) goto skip_countries; acm->country_code_size = cfd->bLength - 4; - memcpy(acm->country_codes, (u8 *)&cfd->wCountyCode0, cfd->bLength - 4); + memcpy(acm->country_codes, (u8 *)&cfd->wCountyCode0, + cfd->bLength - 4); acm->country_rel_date = cfd->iCountryCodeRelDate; i = device_create_file(&intf->dev, &dev_attr_wCountryCodes); if (i < 0) { kfree(acm->country_codes); + acm->country_codes = NULL; + acm->country_code_size = 0; goto skip_countries; } - i = device_create_file(&intf->dev, &dev_attr_iCountryCodeRelDate); + i = device_create_file(&intf->dev, + &dev_attr_iCountryCodeRelDate); if (i < 0) { + device_remove_file(&intf->dev, &dev_attr_wCountryCodes); kfree(acm->country_codes); + acm->country_codes = NULL; + acm->country_code_size = 0; goto skip_countries; } } skip_countries: - usb_fill_int_urb(acm->ctrlurb, usb_dev, usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress), - acm->ctrl_buffer, ctrlsize, acm_ctrl_irq, acm, epctrl->bInterval); + usb_fill_int_urb(acm->ctrlurb, usb_dev, + usb_rcvintpipe(usb_dev, epctrl->bEndpointAddress), + acm->ctrl_buffer, ctrlsize, acm_ctrl_irq, acm, + /* works around buggy devices */ + epctrl->bInterval ? epctrl->bInterval : 16); acm->ctrlurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; acm->ctrlurb->transfer_dma = acm->ctrl_dma; - usb_fill_bulk_urb(acm->writeurb, usb_dev, usb_sndbulkpipe(usb_dev, epwrite->bEndpointAddress), - NULL, acm->writesize, acm_write_bulk, acm); - acm->writeurb->transfer_flags |= URB_NO_FSBR | URB_NO_TRANSFER_DMA_MAP; - dev_info(&intf->dev, "ttyACM%d: USB ACM device\n", minor); - acm_set_control(acm, acm->ctrlout); - acm->line.dwDTERate = cpu_to_le32(9600); acm->line.bDataBits = 8; 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); - - acm_table[minor] = acm; + tty_dev = tty_port_register_device(&acm->port, acm_tty_driver, minor, + &control_interface->dev); + if (IS_ERR(tty_dev)) { + rv = PTR_ERR(tty_dev); + goto alloc_fail8; + } return 0; alloc_fail8: - usb_free_urb(acm->writeurb); + if (acm->country_codes) { + device_remove_file(&acm->control->dev, + &dev_attr_wCountryCodes); + device_remove_file(&acm->control->dev, + &dev_attr_iCountryCodeRelDate); + } + device_remove_file(&acm->control->dev, &dev_attr_bmCapabilities); alloc_fail7: + usb_set_intfdata(intf, NULL); + for (i = 0; i < ACM_NW; i++) + usb_free_urb(acm->wb[i].urb); +alloc_fail6: for (i = 0; i < num_rx_buf; i++) - usb_buffer_free(usb_dev, acm->readsize, acm->rb[i].base, acm->rb[i].dma); - for (i = 0; i < num_rx_buf; i++) - usb_free_urb(acm->ru[i].urb); + usb_free_urb(acm->read_urbs[i]); + acm_read_buffers_free(acm); usb_free_urb(acm->ctrlurb); alloc_fail5: acm_write_buffers_free(acm); alloc_fail4: - usb_buffer_free(usb_dev, ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); + usb_free_coherent(usb_dev, ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); alloc_fail2: + acm_release_minor(acm); kfree(acm); alloc_fail: - return -ENOMEM; + return rv; } static void stop_data_traffic(struct acm *acm) { int i; - tasklet_disable(&acm->urb_task); + dev_dbg(&acm->control->dev, "%s\n", __func__); usb_kill_urb(acm->ctrlurb); - usb_kill_urb(acm->writeurb); + for (i = 0; i < ACM_NW; i++) + usb_kill_urb(acm->wb[i].urb); 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); + usb_kill_urb(acm->read_urbs[i]); cancel_work_sync(&acm->work); } @@ -1131,108 +1502,164 @@ static void acm_disconnect(struct usb_interface *intf) { struct acm *acm = usb_get_intfdata(intf); struct usb_device *usb_dev = interface_to_usbdev(intf); + struct tty_struct *tty; int i; - if (!acm || !acm->dev) { - dbg("disconnect on nonexisting interface"); - return; - } + dev_dbg(&intf->dev, "%s\n", __func__); - mutex_lock(&open_mutex); - if (!usb_get_intfdata(intf)) { - mutex_unlock(&open_mutex); + /* sibling interface is already cleaning up */ + if (!acm) return; - } - if (acm->country_codes){ + + mutex_lock(&acm->mutex); + acm->disconnected = true; + if (acm->country_codes) { device_remove_file(&acm->control->dev, &dev_attr_wCountryCodes); device_remove_file(&acm->control->dev, &dev_attr_iCountryCodeRelDate); } + wake_up_all(&acm->wioctl); device_remove_file(&acm->control->dev, &dev_attr_bmCapabilities); - acm->dev = NULL; usb_set_intfdata(acm->control, NULL); usb_set_intfdata(acm->data, NULL); + mutex_unlock(&acm->mutex); - stop_data_traffic(acm); + tty = tty_port_tty_get(&acm->port); + if (tty) { + tty_vhangup(tty); + tty_kref_put(tty); + } - 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); + stop_data_traffic(acm); - usb_driver_release_interface(&acm_driver, intf == acm->control ? acm->data : intf); + tty_unregister_device(acm_tty_driver, acm->minor); - if (!acm->used) { - acm_tty_unregister(acm); - mutex_unlock(&open_mutex); - return; - } + usb_free_urb(acm->ctrlurb); + for (i = 0; i < ACM_NW; i++) + usb_free_urb(acm->wb[i].urb); + for (i = 0; i < acm->rx_buflimit; i++) + usb_free_urb(acm->read_urbs[i]); + acm_write_buffers_free(acm); + usb_free_coherent(usb_dev, acm->ctrlsize, acm->ctrl_buffer, acm->ctrl_dma); + acm_read_buffers_free(acm); - mutex_unlock(&open_mutex); + if (!acm->combined_interfaces) + usb_driver_release_interface(&acm_driver, intf == acm->control ? + acm->data : acm->control); - if (acm->tty) - tty_hangup(acm->tty); + tty_port_put(&acm->port); } +#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->susp_count++) + spin_lock_irq(&acm->write_lock); + if (PMSG_IS_AUTO(message)) { + if (acm->transmitting) { + spin_unlock_irq(&acm->write_lock); + return -EBUSY; + } + } + cnt = acm->susp_count++; + spin_unlock_irq(&acm->write_lock); + + if (cnt) return 0; - /* - we treat opened interfaces differently, - we must guard against open - */ - mutex_lock(&acm->mutex); - if (acm->used) - stop_data_traffic(acm); + stop_data_traffic(acm); - mutex_unlock(&acm->mutex); return 0; } static int acm_resume(struct usb_interface *intf) { struct acm *acm = usb_get_intfdata(intf); + struct urb *urb; int rv = 0; + spin_lock_irq(&acm->write_lock); + if (--acm->susp_count) - return 0; + goto out; - mutex_lock(&acm->mutex); - if (acm->used) { - rv = usb_submit_urb(acm->ctrlurb, GFP_NOIO); + if (test_bit(ASYNCB_INITIALIZED, &acm->port.flags)) { + rv = usb_submit_urb(acm->ctrlurb, GFP_ATOMIC); + + for (;;) { + urb = usb_get_from_anchor(&acm->delayed); + if (!urb) + break; + + acm_start_wb(acm, urb->context); + } + + /* + * delayed error checking because we must + * do the write path at all cost + */ if (rv < 0) - goto err_out; + goto out; - tasklet_schedule(&acm->urb_task); + rv = acm_submit_read_urbs(acm, GFP_ATOMIC); } +out: + spin_unlock_irq(&acm->write_lock); -err_out: - mutex_unlock(&acm->mutex); return rv; } + +static int acm_reset_resume(struct usb_interface *intf) +{ + struct acm *acm = usb_get_intfdata(intf); + + if (test_bit(ASYNCB_INITIALIZED, &acm->port.flags)) + tty_port_tty_hangup(&acm->port, false); + + return acm_resume(intf); +} + +#endif /* CONFIG_PM */ + +#define NOKIA_PCSUITE_ACM_INFO(x) \ + USB_DEVICE_AND_INTERFACE_INFO(0x0421, x, \ + USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, \ + USB_CDC_ACM_PROTO_VENDOR) + +#define SAMSUNG_PCSUITE_ACM_INFO(x) \ + USB_DEVICE_AND_INTERFACE_INFO(0x04e7, x, \ + USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, \ + USB_CDC_ACM_PROTO_VENDOR) + /* * USB driver structure. */ -static struct usb_device_id acm_ids[] = { +static const struct usb_device_id acm_ids[] = { /* quirky and broken devices */ + { USB_DEVICE(0x17ef, 0x7000), /* Lenovo USB modem */ + .driver_info = NO_UNION_NORMAL, },/* has no union descriptor */ { USB_DEVICE(0x0870, 0x0001), /* Metricom GS Modem */ .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ }, { USB_DEVICE(0x0e8d, 0x0003), /* FIREFLY, MediaTek Inc; andrey.arapov@gmail.com */ .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ }, + { USB_DEVICE(0x0e8d, 0x3329), /* MediaTek Inc GPS */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, { USB_DEVICE(0x0482, 0x0203), /* KYOCERA AH-K3001V */ .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ }, { USB_DEVICE(0x079b, 0x000f), /* BT On-Air USB MODEM */ .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ }, + { USB_DEVICE(0x0ace, 0x1602), /* ZyDAS 56K USB MODEM */ + .driver_info = SINGLE_RX_URB, + }, { USB_DEVICE(0x0ace, 0x1608), /* ZyDAS 56K USB MODEM */ .driver_info = SINGLE_RX_URB, /* firmware bug */ }, @@ -1242,6 +1669,155 @@ static struct usb_device_id acm_ids[] = { { USB_DEVICE(0x22b8, 0x7000), /* Motorola Q Phone */ .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ }, + { 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 */ + }, + { USB_DEVICE(0x0572, 0x1324), /* Conexant USB MODEM RD02-D400 */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { USB_DEVICE(0x0572, 0x1328), /* Shiro / Aztech USB MODEM UM-3100 */ + .driver_info = NO_UNION_NORMAL, /* has no union descriptor */ + }, + { USB_DEVICE(0x22b8, 0x6425), /* Motorola MOTOMAGX phones */ + }, + /* Motorola H24 HSPA module: */ + { USB_DEVICE(0x22b8, 0x2d91) }, /* modem */ + { USB_DEVICE(0x22b8, 0x2d92), /* modem + diagnostics */ + .driver_info = NO_UNION_NORMAL, /* handle only modem interface */ + }, + { USB_DEVICE(0x22b8, 0x2d93), /* modem + AT port */ + .driver_info = NO_UNION_NORMAL, /* handle only modem interface */ + }, + { USB_DEVICE(0x22b8, 0x2d95), /* modem + AT port + diagnostics */ + .driver_info = NO_UNION_NORMAL, /* handle only modem interface */ + }, + { USB_DEVICE(0x22b8, 0x2d96), /* modem + NMEA */ + .driver_info = NO_UNION_NORMAL, /* handle only modem interface */ + }, + { USB_DEVICE(0x22b8, 0x2d97), /* modem + diagnostics + NMEA */ + .driver_info = NO_UNION_NORMAL, /* handle only modem interface */ + }, + { USB_DEVICE(0x22b8, 0x2d99), /* modem + AT port + NMEA */ + .driver_info = NO_UNION_NORMAL, /* handle only modem interface */ + }, + { USB_DEVICE(0x22b8, 0x2d9a), /* modem + AT port + diagnostics + NMEA */ + .driver_info = NO_UNION_NORMAL, /* handle only modem interface */ + }, + + { USB_DEVICE(0x0572, 0x1329), /* Hummingbird huc56s (Conexant) */ + .driver_info = NO_UNION_NORMAL, /* union descriptor misplaced on + data interface instead of + communications interface. + Maybe we should define a new + quirk for this. */ + }, + { USB_DEVICE(0x0572, 0x1340), /* Conexant CX93010-2x UCMxx */ + .driver_info = NO_UNION_NORMAL, + }, + { USB_DEVICE(0x05f9, 0x4002), /* PSC Scanning, Magellan 800i */ + .driver_info = NO_UNION_NORMAL, + }, + { USB_DEVICE(0x1bbb, 0x0003), /* Alcatel OT-I650 */ + .driver_info = NO_UNION_NORMAL, /* reports zero length descriptor */ + }, + { USB_DEVICE(0x1576, 0x03b1), /* Maretron USB100 */ + .driver_info = NO_UNION_NORMAL, /* reports zero length descriptor */ + }, + + /* Nokia S60 phones expose two ACM channels. The first is + * a modem and is picked up by the standard AT-command + * information below. The second is 'vendor-specific' but + * is treated as a serial device at the S60 end, so we want + * to expose it on Linux too. */ + { NOKIA_PCSUITE_ACM_INFO(0x042D), }, /* Nokia 3250 */ + { NOKIA_PCSUITE_ACM_INFO(0x04D8), }, /* Nokia 5500 Sport */ + { NOKIA_PCSUITE_ACM_INFO(0x04C9), }, /* Nokia E50 */ + { NOKIA_PCSUITE_ACM_INFO(0x0419), }, /* Nokia E60 */ + { NOKIA_PCSUITE_ACM_INFO(0x044D), }, /* Nokia E61 */ + { NOKIA_PCSUITE_ACM_INFO(0x0001), }, /* Nokia E61i */ + { NOKIA_PCSUITE_ACM_INFO(0x0475), }, /* Nokia E62 */ + { NOKIA_PCSUITE_ACM_INFO(0x0508), }, /* Nokia E65 */ + { NOKIA_PCSUITE_ACM_INFO(0x0418), }, /* Nokia E70 */ + { NOKIA_PCSUITE_ACM_INFO(0x0425), }, /* Nokia N71 */ + { NOKIA_PCSUITE_ACM_INFO(0x0486), }, /* Nokia N73 */ + { NOKIA_PCSUITE_ACM_INFO(0x04DF), }, /* Nokia N75 */ + { NOKIA_PCSUITE_ACM_INFO(0x000e), }, /* Nokia N77 */ + { NOKIA_PCSUITE_ACM_INFO(0x0445), }, /* Nokia N80 */ + { NOKIA_PCSUITE_ACM_INFO(0x042F), }, /* Nokia N91 & N91 8GB */ + { NOKIA_PCSUITE_ACM_INFO(0x048E), }, /* Nokia N92 */ + { NOKIA_PCSUITE_ACM_INFO(0x0420), }, /* Nokia N93 */ + { NOKIA_PCSUITE_ACM_INFO(0x04E6), }, /* Nokia N93i */ + { NOKIA_PCSUITE_ACM_INFO(0x04B2), }, /* Nokia 5700 XpressMusic */ + { NOKIA_PCSUITE_ACM_INFO(0x0134), }, /* Nokia 6110 Navigator (China) */ + { NOKIA_PCSUITE_ACM_INFO(0x046E), }, /* Nokia 6110 Navigator */ + { NOKIA_PCSUITE_ACM_INFO(0x002f), }, /* Nokia 6120 classic & */ + { NOKIA_PCSUITE_ACM_INFO(0x0088), }, /* Nokia 6121 classic */ + { NOKIA_PCSUITE_ACM_INFO(0x00fc), }, /* Nokia 6124 classic */ + { NOKIA_PCSUITE_ACM_INFO(0x0042), }, /* Nokia E51 */ + { NOKIA_PCSUITE_ACM_INFO(0x00b0), }, /* Nokia E66 */ + { NOKIA_PCSUITE_ACM_INFO(0x00ab), }, /* Nokia E71 */ + { NOKIA_PCSUITE_ACM_INFO(0x0481), }, /* Nokia N76 */ + { NOKIA_PCSUITE_ACM_INFO(0x0007), }, /* Nokia N81 & N81 8GB */ + { NOKIA_PCSUITE_ACM_INFO(0x0071), }, /* Nokia N82 */ + { NOKIA_PCSUITE_ACM_INFO(0x04F0), }, /* Nokia N95 & N95-3 NAM */ + { NOKIA_PCSUITE_ACM_INFO(0x0070), }, /* Nokia N95 8GB */ + { NOKIA_PCSUITE_ACM_INFO(0x00e9), }, /* Nokia 5320 XpressMusic */ + { NOKIA_PCSUITE_ACM_INFO(0x0099), }, /* Nokia 6210 Navigator, RM-367 */ + { NOKIA_PCSUITE_ACM_INFO(0x0128), }, /* Nokia 6210 Navigator, RM-419 */ + { NOKIA_PCSUITE_ACM_INFO(0x008f), }, /* Nokia 6220 Classic */ + { NOKIA_PCSUITE_ACM_INFO(0x00a0), }, /* Nokia 6650 */ + { NOKIA_PCSUITE_ACM_INFO(0x007b), }, /* Nokia N78 */ + { NOKIA_PCSUITE_ACM_INFO(0x0094), }, /* Nokia N85 */ + { NOKIA_PCSUITE_ACM_INFO(0x003a), }, /* Nokia N96 & N96-3 */ + { NOKIA_PCSUITE_ACM_INFO(0x00e9), }, /* Nokia 5320 XpressMusic */ + { NOKIA_PCSUITE_ACM_INFO(0x0108), }, /* Nokia 5320 XpressMusic 2G */ + { NOKIA_PCSUITE_ACM_INFO(0x01f5), }, /* Nokia N97, RM-505 */ + { NOKIA_PCSUITE_ACM_INFO(0x02e3), }, /* Nokia 5230, RM-588 */ + { NOKIA_PCSUITE_ACM_INFO(0x0178), }, /* Nokia E63 */ + { NOKIA_PCSUITE_ACM_INFO(0x010e), }, /* Nokia E75 */ + { NOKIA_PCSUITE_ACM_INFO(0x02d9), }, /* Nokia 6760 Slide */ + { NOKIA_PCSUITE_ACM_INFO(0x01d0), }, /* Nokia E52 */ + { NOKIA_PCSUITE_ACM_INFO(0x0223), }, /* Nokia E72 */ + { NOKIA_PCSUITE_ACM_INFO(0x0275), }, /* Nokia X6 */ + { NOKIA_PCSUITE_ACM_INFO(0x026c), }, /* Nokia N97 Mini */ + { NOKIA_PCSUITE_ACM_INFO(0x0154), }, /* Nokia 5800 XpressMusic */ + { NOKIA_PCSUITE_ACM_INFO(0x04ce), }, /* Nokia E90 */ + { NOKIA_PCSUITE_ACM_INFO(0x01d4), }, /* Nokia E55 */ + { NOKIA_PCSUITE_ACM_INFO(0x0302), }, /* Nokia N8 */ + { NOKIA_PCSUITE_ACM_INFO(0x0335), }, /* Nokia E7 */ + { NOKIA_PCSUITE_ACM_INFO(0x03cd), }, /* Nokia C7 */ + { SAMSUNG_PCSUITE_ACM_INFO(0x6651), }, /* Samsung GTi8510 (INNOV8) */ + + /* Support for Owen devices */ + { USB_DEVICE(0x03eb, 0x0030), }, /* Owen SI30 */ + + /* NOTE: non-Nokia COMM/ACM/0xff is likely MSFT RNDIS... NOT a modem! */ + + /* Support Lego NXT using pbLua firmware */ + { USB_DEVICE(0x0694, 0xff00), + .driver_info = NOT_A_MODEM, + }, + + /* Support for Droids MuIn LCD */ + { USB_DEVICE(0x04d8, 0x000b), + .driver_info = NO_DATA_INTERFACE, + }, + +#if IS_ENABLED(CONFIG_INPUT_IMS_PCU) + { USB_DEVICE(0x04d8, 0x0082), /* Application mode */ + .driver_info = IGNORE_DEVICE, + }, + { USB_DEVICE(0x04d8, 0x0083), /* Bootloader mode */ + .driver_info = IGNORE_DEVICE, + }, +#endif + + /* control interfaces without any protocol set */ + { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, + USB_CDC_PROTO_NONE) }, /* control interfaces with various AT-command sets */ { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, @@ -1253,24 +1829,29 @@ static struct usb_device_id acm_ids[] = { { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, USB_CDC_ACM_PROTO_AT_GSM) }, { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, - USB_CDC_ACM_PROTO_AT_3G ) }, + USB_CDC_ACM_PROTO_AT_3G) }, { USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM, USB_CDC_ACM_PROTO_AT_CDMA) }, - /* NOTE: COMM/ACM/0xff is likely MSFT RNDIS ... NOT a modem!! */ { } }; -MODULE_DEVICE_TABLE (usb, acm_ids); +MODULE_DEVICE_TABLE(usb, acm_ids); static struct usb_driver acm_driver = { .name = "cdc_acm", .probe = acm_probe, .disconnect = acm_disconnect, +#ifdef CONFIG_PM .suspend = acm_suspend, .resume = acm_resume, + .reset_resume = acm_reset_resume, +#endif .id_table = acm_ids, +#ifdef CONFIG_PM .supports_autosuspend = 1, +#endif + .disable_hub_initiated_lpm = 1, }; /* @@ -1278,8 +1859,11 @@ static struct usb_driver acm_driver = { */ static const struct tty_operations acm_ops = { + .install = acm_tty_install, .open = acm_tty_open, .close = acm_tty_close, + .cleanup = acm_tty_cleanup, + .hangup = acm_tty_hangup, .write = acm_tty_write, .write_room = acm_tty_write_room, .ioctl = acm_tty_ioctl, @@ -1302,7 +1886,6 @@ static int __init acm_init(void) acm_tty_driver = alloc_tty_driver(ACM_TTY_MINORS); if (!acm_tty_driver) return -ENOMEM; - acm_tty_driver->owner = THIS_MODULE, acm_tty_driver->driver_name = "acm", acm_tty_driver->name = "ttyACM", acm_tty_driver->major = ACM_TTY_MAJOR, @@ -1311,7 +1894,8 @@ static int __init acm_init(void) acm_tty_driver->subtype = SERIAL_TYPE_NORMAL, acm_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; acm_tty_driver->init_termios = tty_std_termios; - acm_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + acm_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | + HUPCL | CLOCAL; tty_set_operations(acm_tty_driver, &acm_ops); retval = tty_register_driver(acm_tty_driver); @@ -1327,7 +1911,7 @@ static int __init acm_init(void) return retval; } - info(DRIVER_VERSION ":" DRIVER_DESC); + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n"); return 0; } @@ -1342,7 +1926,7 @@ static void __exit acm_exit(void) module_init(acm_init); module_exit(acm_exit); -MODULE_AUTHOR( DRIVER_AUTHOR ); -MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); MODULE_LICENSE("GPL"); - +MODULE_ALIAS_CHARDEV_MAJOR(ACM_TTY_MAJOR); diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index 8df6a57dcf9..fc75651afe1 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -52,14 +52,14 @@ */ /* - * The only reason to have several buffers is to accomodate assumptions + * The only reason to have several buffers is to accommodate assumptions * in line disciplines. They ask for empty space amount, receive our URB size, * and proceed to issue several 1-character writes, assuming they will fit. * The very first write takes a complete URB. Fortunately, this only happens * when processing onlcr, so we only need 2 buffers. These values must be * powers of 2. */ -#define ACM_NW 2 +#define ACM_NW 16 #define ACM_NR 16 struct acm_wb { @@ -67,19 +67,15 @@ struct acm_wb { dma_addr_t dmah; int len; int use; + struct urb *urb; + struct acm *instance; }; struct acm_rb { - struct list_head list; int size; unsigned char *base; dma_addr_t dma; -}; - -struct acm_ru { - struct list_head list; - struct acm_rb *buffer; - struct urb *urb; + int index; struct acm *instance; }; @@ -87,45 +83,52 @@ struct acm { struct usb_device *dev; /* the corresponding usb device */ struct usb_interface *control; /* control interface */ struct usb_interface *data; /* data interface */ - struct tty_struct *tty; /* the corresponding tty */ - struct urb *ctrlurb, *writeurb; /* urbs */ + struct tty_port port; /* our tty port data */ + struct urb *ctrlurb; /* urbs */ u8 *ctrl_buffer; /* buffers of urbs */ dma_addr_t ctrl_dma; /* dma handles of buffers */ u8 *country_codes; /* country codes from device */ unsigned int country_code_size; /* size of this buffer */ unsigned int country_rel_date; /* release date of version */ struct acm_wb wb[ACM_NW]; - struct acm_ru ru[ACM_NR]; - struct acm_rb rb[ACM_NR]; + unsigned long read_urbs_free; + struct urb *read_urbs[ACM_NR]; + struct acm_rb read_buffers[ACM_NR]; int rx_buflimit; int rx_endpoint; spinlock_t read_lock; - struct list_head spare_read_urbs; - struct list_head spare_read_bufs; - struct list_head filled_read_bufs; - int write_current; /* current write buffer */ int write_used; /* number of non-empty write buffers */ - int write_ready; /* write urb is not running */ + int transmitting; spinlock_t write_lock; struct mutex mutex; + bool disconnected; struct usb_cdc_line_coding line; /* bits, stop, parity */ struct work_struct work; /* work queue entry for line discipline waking up */ - 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) */ unsigned int ctrlout; /* output control lines (DTR, RTS) */ + struct async_icount iocount; /* counters for control line changes */ + struct async_icount oldcount; /* for comparison of counter */ + wait_queue_head_t wioctl; /* for ioctl */ unsigned int writesize; /* max packet size for the output bulk endpoint */ unsigned int readsize,ctrlsize; /* buffer sizes for freeing */ - unsigned int used; /* someone has this acm's device open */ unsigned int minor; /* acm minor number */ - unsigned char throttle; /* throttled by tty layer */ unsigned char clocal; /* termios CLOCAL */ unsigned int ctrl_caps; /* control capabilities from the class specific header */ unsigned int susp_count; /* number of suspended interfaces */ + unsigned int combined_interfaces:1; /* control and data collapsed */ + unsigned int is_int_ep:1; /* interrupt endpoints contrary to spec used */ + unsigned int throttled:1; /* actually throttled */ + unsigned int throttle_req:1; /* throttle requested */ + u8 bInterval; + struct usb_anchor delayed; /* writes queued for a device about to be woken */ }; #define CDC_DATA_INTERFACE_TYPE 0x0a /* constants describing various quirks and errors */ -#define NO_UNION_NORMAL 1 -#define SINGLE_RX_URB 2 +#define NO_UNION_NORMAL BIT(0) +#define SINGLE_RX_URB BIT(1) +#define NO_CAP_LINE BIT(2) +#define NOT_A_MODEM BIT(3) +#define NO_DATA_INTERFACE BIT(4) +#define IGNORE_DEVICE BIT(5) diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c new file mode 100644 index 00000000000..a051a7a2b1b --- /dev/null +++ b/drivers/usb/class/cdc-wdm.c @@ -0,0 +1,1113 @@ +/* + * cdc-wdm.c + * + * This driver supports USB CDC WCM Device Management. + * + * Copyright (c) 2007-2009 Oliver Neukum + * + * Some code taken from cdc-acm.c + * + * Released under the GPLv2. + * + * Many thanks to Carl Nordbeck + */ +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/ioctl.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> +#include <linux/bitops.h> +#include <linux/poll.h> +#include <linux/usb.h> +#include <linux/usb/cdc.h> +#include <asm/byteorder.h> +#include <asm/unaligned.h> +#include <linux/usb/cdc-wdm.h> + +/* + * Version Information + */ +#define DRIVER_VERSION "v0.03" +#define DRIVER_AUTHOR "Oliver Neukum" +#define DRIVER_DESC "USB Abstract Control Model driver for USB WCM Device Management" + +static const struct usb_device_id wdm_ids[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_DMM + }, + { } +}; + +MODULE_DEVICE_TABLE (usb, wdm_ids); + +#define WDM_MINOR_BASE 176 + + +#define WDM_IN_USE 1 +#define WDM_DISCONNECTING 2 +#define WDM_RESULT 3 +#define WDM_READ 4 +#define WDM_INT_STALL 5 +#define WDM_POLL_RUNNING 6 +#define WDM_RESPONDING 7 +#define WDM_SUSPENDING 8 +#define WDM_RESETTING 9 +#define WDM_OVERFLOW 10 + +#define WDM_MAX 16 + +/* CDC-WMC r1.1 requires wMaxCommand to be "at least 256 decimal (0x100)" */ +#define WDM_DEFAULT_BUFSIZE 256 + +static DEFINE_MUTEX(wdm_mutex); +static DEFINE_SPINLOCK(wdm_device_list_lock); +static LIST_HEAD(wdm_device_list); + +/* --- method tables --- */ + +struct wdm_device { + u8 *inbuf; /* buffer for response */ + u8 *outbuf; /* buffer for command */ + u8 *sbuf; /* buffer for status */ + u8 *ubuf; /* buffer for copy to user space */ + + struct urb *command; + struct urb *response; + struct urb *validity; + struct usb_interface *intf; + struct usb_ctrlrequest *orq; + struct usb_ctrlrequest *irq; + spinlock_t iuspin; + + unsigned long flags; + u16 bufsize; + u16 wMaxCommand; + u16 wMaxPacketSize; + __le16 inum; + int reslength; + int length; + int read; + int count; + dma_addr_t shandle; + dma_addr_t ihandle; + struct mutex wlock; + struct mutex rlock; + wait_queue_head_t wait; + struct work_struct rxwork; + int werr; + int rerr; + int resp_count; + + struct list_head device_list; + int (*manage_power)(struct usb_interface *, int); +}; + +static struct usb_driver wdm_driver; + +/* return intfdata if we own the interface, else look up intf in the list */ +static struct wdm_device *wdm_find_device(struct usb_interface *intf) +{ + struct wdm_device *desc; + + spin_lock(&wdm_device_list_lock); + list_for_each_entry(desc, &wdm_device_list, device_list) + if (desc->intf == intf) + goto found; + desc = NULL; +found: + spin_unlock(&wdm_device_list_lock); + + return desc; +} + +static struct wdm_device *wdm_find_device_by_minor(int minor) +{ + struct wdm_device *desc; + + spin_lock(&wdm_device_list_lock); + list_for_each_entry(desc, &wdm_device_list, device_list) + if (desc->intf->minor == minor) + goto found; + desc = NULL; +found: + spin_unlock(&wdm_device_list_lock); + + return desc; +} + +/* --- callbacks --- */ +static void wdm_out_callback(struct urb *urb) +{ + struct wdm_device *desc; + desc = urb->context; + spin_lock(&desc->iuspin); + desc->werr = urb->status; + spin_unlock(&desc->iuspin); + kfree(desc->outbuf); + desc->outbuf = NULL; + clear_bit(WDM_IN_USE, &desc->flags); + wake_up(&desc->wait); +} + +static void wdm_in_callback(struct urb *urb) +{ + struct wdm_device *desc = urb->context; + int status = urb->status; + int length = urb->actual_length; + + 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"); + goto skip_error; + case -ECONNRESET: + dev_dbg(&desc->intf->dev, + "nonzero urb status received: -ECONNRESET"); + goto skip_error; + case -ESHUTDOWN: + dev_dbg(&desc->intf->dev, + "nonzero urb status received: -ESHUTDOWN"); + goto skip_error; + case -EPIPE: + dev_err(&desc->intf->dev, + "nonzero urb status received: -EPIPE\n"); + break; + default: + dev_err(&desc->intf->dev, + "Unexpected error %d\n", status); + break; + } + } + + desc->rerr = status; + if (length + desc->length > desc->wMaxCommand) { + /* The buffer would overflow */ + set_bit(WDM_OVERFLOW, &desc->flags); + } else { + /* we may already be in overflow */ + if (!test_bit(WDM_OVERFLOW, &desc->flags)) { + memmove(desc->ubuf + desc->length, desc->inbuf, length); + desc->length += length; + desc->reslength = length; + } + } +skip_error: + wake_up(&desc->wait); + + set_bit(WDM_READ, &desc->flags); + spin_unlock(&desc->iuspin); +} + +static void wdm_int_callback(struct urb *urb) +{ + int rv = 0; + int responding; + int status = urb->status; + struct wdm_device *desc; + struct usb_cdc_notification *dr; + + desc = urb->context; + dr = (struct usb_cdc_notification *)desc->sbuf; + + if (status) { + switch (status) { + case -ESHUTDOWN: + case -ENOENT: + case -ECONNRESET: + return; /* unplug */ + case -EPIPE: + set_bit(WDM_INT_STALL, &desc->flags); + dev_err(&desc->intf->dev, "Stall on int endpoint\n"); + goto sw; /* halt is cleared in work */ + default: + dev_err(&desc->intf->dev, + "nonzero urb status received: %d\n", status); + break; + } + } + + if (urb->actual_length < sizeof(struct usb_cdc_notification)) { + dev_err(&desc->intf->dev, "wdm_int_callback - %d bytes\n", + urb->actual_length); + goto exit; + } + + switch (dr->bNotificationType) { + case USB_CDC_NOTIFY_RESPONSE_AVAILABLE: + dev_dbg(&desc->intf->dev, + "NOTIFY_RESPONSE_AVAILABLE received: index %d len %d", + dr->wIndex, dr->wLength); + break; + + case USB_CDC_NOTIFY_NETWORK_CONNECTION: + + dev_dbg(&desc->intf->dev, + "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, + "unknown notification %d received: index %d len %d\n", + dr->bNotificationType, dr->wIndex, dr->wLength); + goto exit; + } + + spin_lock(&desc->iuspin); + responding = test_and_set_bit(WDM_RESPONDING, &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", + __func__, rv); + } + spin_unlock(&desc->iuspin); + if (rv < 0) { + clear_bit(WDM_RESPONDING, &desc->flags); + if (rv == -EPERM) + return; + if (rv == -ENOMEM) { +sw: + rv = schedule_work(&desc->rxwork); + if (rv) + dev_err(&desc->intf->dev, + "Cannot schedule work\n"); + } + } +exit: + rv = usb_submit_urb(urb, GFP_ATOMIC); + if (rv) + dev_err(&desc->intf->dev, + "%s - usb_submit_urb failed with result %d\n", + __func__, rv); + +} + +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); +} + +static void free_urbs(struct wdm_device *desc) +{ + usb_free_urb(desc->validity); + usb_free_urb(desc->response); + usb_free_urb(desc->command); +} + +static void cleanup(struct wdm_device *desc) +{ + kfree(desc->sbuf); + kfree(desc->inbuf); + kfree(desc->orq); + kfree(desc->irq); + kfree(desc->ubuf); + free_urbs(desc); + kfree(desc); +} + +static ssize_t wdm_write +(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + u8 *buf; + int rv = -EMSGSIZE, r, we; + struct wdm_device *desc = file->private_data; + struct usb_ctrlrequest *req; + + if (count > desc->wMaxCommand) + count = desc->wMaxCommand; + + spin_lock_irq(&desc->iuspin); + we = desc->werr; + desc->werr = 0; + spin_unlock_irq(&desc->iuspin); + if (we < 0) + return -EIO; + + 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->wlock); + rv = -ERESTARTSYS; + 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) { + kfree(buf); + rv = usb_translate_errors(r); + goto outnp; + } + + if (!(file->f_flags & O_NONBLOCK)) + r = wait_event_interruptible(desc->wait, !test_bit(WDM_IN_USE, + &desc->flags)); + else + if (test_bit(WDM_IN_USE, &desc->flags)) + r = -EAGAIN; + + if (test_bit(WDM_RESETTING, &desc->flags)) + r = -EIO; + + if (r < 0) { + kfree(buf); + rv = r; + goto out; + } + + req = desc->orq; + usb_fill_control_urb( + desc->command, + interface_to_usbdev(desc->intf), + /* using common endpoint 0 */ + usb_sndctrlpipe(interface_to_usbdev(desc->intf), 0), + (unsigned char *)req, + buf, + count, + wdm_out_callback, + desc + ); + + req->bRequestType = (USB_DIR_OUT | USB_TYPE_CLASS | + USB_RECIP_INTERFACE); + req->bRequest = USB_CDC_SEND_ENCAPSULATED_COMMAND; + req->wValue = 0; + req->wIndex = desc->inum; + req->wLength = cpu_to_le16(count); + set_bit(WDM_IN_USE, &desc->flags); + desc->outbuf = buf; + + rv = usb_submit_urb(desc->command, GFP_KERNEL); + if (rv < 0) { + kfree(buf); + desc->outbuf = NULL; + clear_bit(WDM_IN_USE, &desc->flags); + dev_err(&desc->intf->dev, "Tx URB error: %d\n", rv); + rv = usb_translate_errors(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; +} + +/* + * 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) +{ + int rv, cntr; + int i = 0; + struct wdm_device *desc = file->private_data; + + + rv = mutex_lock_interruptible(&desc->rlock); /*concurrent reads */ + if (rv < 0) + return -ERESTARTSYS; + + cntr = ACCESS_ONCE(desc->length); + if (cntr == 0) { + desc->read = 0; +retry: + if (test_bit(WDM_DISCONNECTING, &desc->flags)) { + rv = -ENODEV; + goto err; + } + if (test_bit(WDM_OVERFLOW, &desc->flags)) { + clear_bit(WDM_OVERFLOW, &desc->flags); + rv = -ENOBUFS; + goto err; + } + i++; + if (file->f_flags & O_NONBLOCK) { + if (!test_bit(WDM_READ, &desc->flags)) { + rv = cntr ? cntr : -EAGAIN; + goto err; + } + rv = 0; + } else { + rv = wait_event_interruptible(desc->wait, + test_bit(WDM_READ, &desc->flags)); + } + + /* may have happened while we slept */ + if (test_bit(WDM_DISCONNECTING, &desc->flags)) { + rv = -ENODEV; + goto err; + } + if (test_bit(WDM_RESETTING, &desc->flags)) { + rv = -EIO; + goto err; + } + usb_mark_last_busy(interface_to_usbdev(desc->intf)); + if (rv < 0) { + rv = -ERESTARTSYS; + goto err; + } + + spin_lock_irq(&desc->iuspin); + + if (desc->rerr) { /* read completed, error happened */ + desc->rerr = 0; + spin_unlock_irq(&desc->iuspin); + rv = -EIO; + goto err; + } + /* + * recheck whether we've lost the race + * against the completion handler + */ + if (!test_bit(WDM_READ, &desc->flags)) { /* lost race */ + spin_unlock_irq(&desc->iuspin); + goto retry; + } + + if (!desc->reslength) { /* zero length read */ + dev_dbg(&desc->intf->dev, "%s: zero length - clearing WDM_READ\n", __func__); + rv = clear_wdm_read_flag(desc); + spin_unlock_irq(&desc->iuspin); + if (rv < 0) + goto err; + goto retry; + } + cntr = desc->length; + spin_unlock_irq(&desc->iuspin); + } + + if (cntr > count) + cntr = count; + rv = copy_to_user(buffer, desc->ubuf, cntr); + if (rv > 0) { + rv = -EFAULT; + goto err; + } + + spin_lock_irq(&desc->iuspin); + + for (i = 0; i < desc->length - cntr; i++) + desc->ubuf[i] = desc->ubuf[i + cntr]; + + desc->length -= cntr; + /* in case we had outstanding data */ + if (!desc->length) + clear_wdm_read_flag(desc); + spin_unlock_irq(&desc->iuspin); + rv = cntr; + +err: + mutex_unlock(&desc->rlock); + return rv; +} + +static int wdm_flush(struct file *file, fl_owner_t id) +{ + struct wdm_device *desc = file->private_data; + + wait_event(desc->wait, !test_bit(WDM_IN_USE, &desc->flags)); + + /* cannot dereference desc->intf if WDM_DISCONNECTING */ + if (desc->werr < 0 && !test_bit(WDM_DISCONNECTING, &desc->flags)) + dev_err(&desc->intf->dev, "Error in flush path: %d\n", + desc->werr); + + return usb_translate_errors(desc->werr); +} + +static unsigned int wdm_poll(struct file *file, struct poll_table_struct *wait) +{ + struct wdm_device *desc = file->private_data; + unsigned long flags; + unsigned int mask = 0; + + spin_lock_irqsave(&desc->iuspin, flags); + if (test_bit(WDM_DISCONNECTING, &desc->flags)) { + mask = POLLHUP | POLLERR; + spin_unlock_irqrestore(&desc->iuspin, flags); + goto desc_out; + } + if (test_bit(WDM_READ, &desc->flags)) + mask = POLLIN | POLLRDNORM; + if (desc->rerr || desc->werr) + mask |= POLLERR; + if (!test_bit(WDM_IN_USE, &desc->flags)) + mask |= POLLOUT | POLLWRNORM; + spin_unlock_irqrestore(&desc->iuspin, flags); + + poll_wait(file, &desc->wait, wait); + +desc_out: + return mask; +} + +static int wdm_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + int rv = -ENODEV; + struct usb_interface *intf; + struct wdm_device *desc; + + mutex_lock(&wdm_mutex); + desc = wdm_find_device_by_minor(minor); + if (!desc) + goto out; + + intf = desc->intf; + if (test_bit(WDM_DISCONNECTING, &desc->flags)) + goto out; + file->private_data = desc; + + rv = usb_autopm_get_interface(desc->intf); + if (rv < 0) { + dev_err(&desc->intf->dev, "Error autopm - %d\n", rv); + goto out; + } + + /* using write lock to protect desc->count */ + mutex_lock(&desc->wlock); + if (!desc->count++) { + desc->werr = 0; + desc->rerr = 0; + rv = usb_submit_urb(desc->validity, GFP_KERNEL); + if (rv < 0) { + desc->count--; + dev_err(&desc->intf->dev, + "Error submitting int urb - %d\n", rv); + rv = usb_translate_errors(rv); + } + } else { + rv = 0; + } + mutex_unlock(&desc->wlock); + if (desc->count == 1) + desc->manage_power(intf, 1); + usb_autopm_put_interface(desc->intf); +out: + mutex_unlock(&wdm_mutex); + return rv; +} + +static int wdm_release(struct inode *inode, struct file *file) +{ + struct wdm_device *desc = file->private_data; + + mutex_lock(&wdm_mutex); + + /* using write lock to protect desc->count */ + mutex_lock(&desc->wlock); + desc->count--; + mutex_unlock(&desc->wlock); + + if (!desc->count) { + 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 */ + pr_debug(KBUILD_MODNAME " %s: device gone - cleaning up\n", __func__); + cleanup(desc); + } + } + mutex_unlock(&wdm_mutex); + return 0; +} + +static long wdm_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct wdm_device *desc = file->private_data; + int rv = 0; + + switch (cmd) { + case IOCTL_WDM_MAX_COMMAND: + if (copy_to_user((void __user *)arg, &desc->wMaxCommand, sizeof(desc->wMaxCommand))) + rv = -EFAULT; + break; + default: + rv = -ENOTTY; + } + return rv; +} + +static const struct file_operations wdm_fops = { + .owner = THIS_MODULE, + .read = wdm_read, + .write = wdm_write, + .open = wdm_open, + .flush = wdm_flush, + .release = wdm_release, + .poll = wdm_poll, + .unlocked_ioctl = wdm_ioctl, + .compat_ioctl = wdm_ioctl, + .llseek = noop_llseek, +}; + +static struct usb_class_driver wdm_class = { + .name = "cdc-wdm%d", + .fops = &wdm_fops, + .minor_base = WDM_MINOR_BASE, +}; + +/* --- error handling --- */ +static void wdm_rxwork(struct work_struct *work) +{ + struct wdm_device *desc = container_of(work, struct wdm_device, rxwork); + unsigned long flags; + int rv = 0; + int responding; + + spin_lock_irqsave(&desc->iuspin, flags); + if (test_bit(WDM_DISCONNECTING, &desc->flags)) { + spin_unlock_irqrestore(&desc->iuspin, flags); + } else { + responding = test_and_set_bit(WDM_RESPONDING, &desc->flags); + spin_unlock_irqrestore(&desc->iuspin, flags); + if (!responding) + rv = usb_submit_urb(desc->response, GFP_KERNEL); + if (rv < 0 && rv != -EPERM) { + spin_lock_irqsave(&desc->iuspin, flags); + clear_bit(WDM_RESPONDING, &desc->flags); + if (!test_bit(WDM_DISCONNECTING, &desc->flags)) + schedule_work(&desc->rxwork); + spin_unlock_irqrestore(&desc->iuspin, flags); + } + } +} + +/* --- hotplug --- */ + +static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep, + u16 bufsize, int (*manage_power)(struct usb_interface *, int)) +{ + int rv = -ENOMEM; + struct wdm_device *desc; + + desc = kzalloc(sizeof(struct wdm_device), GFP_KERNEL); + if (!desc) + goto out; + INIT_LIST_HEAD(&desc->device_list); + mutex_init(&desc->rlock); + mutex_init(&desc->wlock); + spin_lock_init(&desc->iuspin); + init_waitqueue_head(&desc->wait); + desc->wMaxCommand = bufsize; + /* this will be expanded and needed in hardware endianness */ + desc->inum = cpu_to_le16((u16)intf->cur_altsetting->desc.bInterfaceNumber); + desc->intf = intf; + INIT_WORK(&desc->rxwork, wdm_rxwork); + + rv = -EINVAL; + if (!usb_endpoint_is_int_in(ep)) + goto err; + + desc->wMaxPacketSize = usb_endpoint_maxp(ep); + + desc->orq = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL); + if (!desc->orq) + goto err; + desc->irq = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL); + if (!desc->irq) + goto err; + + desc->validity = usb_alloc_urb(0, GFP_KERNEL); + if (!desc->validity) + goto err; + + desc->response = usb_alloc_urb(0, GFP_KERNEL); + if (!desc->response) + goto err; + + desc->command = usb_alloc_urb(0, GFP_KERNEL); + if (!desc->command) + goto err; + + desc->ubuf = kmalloc(desc->wMaxCommand, GFP_KERNEL); + if (!desc->ubuf) + goto err; + + desc->sbuf = kmalloc(desc->wMaxPacketSize, GFP_KERNEL); + if (!desc->sbuf) + goto err; + + desc->inbuf = kmalloc(desc->wMaxCommand, GFP_KERNEL); + if (!desc->inbuf) + goto err; + + usb_fill_int_urb( + desc->validity, + interface_to_usbdev(intf), + usb_rcvintpipe(interface_to_usbdev(intf), ep->bEndpointAddress), + desc->sbuf, + desc->wMaxPacketSize, + wdm_int_callback, + desc, + ep->bInterval + ); + + desc->irq->bRequestType = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE); + desc->irq->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE; + desc->irq->wValue = 0; + desc->irq->wIndex = desc->inum; + desc->irq->wLength = cpu_to_le16(desc->wMaxCommand); + + usb_fill_control_urb( + desc->response, + interface_to_usbdev(intf), + /* using common endpoint 0 */ + usb_rcvctrlpipe(interface_to_usbdev(desc->intf), 0), + (unsigned char *)desc->irq, + desc->inbuf, + desc->wMaxCommand, + wdm_in_callback, + desc + ); + + desc->manage_power = manage_power; + + spin_lock(&wdm_device_list_lock); + list_add(&desc->device_list, &wdm_device_list); + spin_unlock(&wdm_device_list_lock); + + rv = usb_register_dev(intf, &wdm_class); + if (rv < 0) + goto err; + else + dev_info(&intf->dev, "%s: USB WDM device\n", dev_name(intf->usb_dev)); +out: + return rv; +err: + spin_lock(&wdm_device_list_lock); + list_del(&desc->device_list); + spin_unlock(&wdm_device_list_lock); + cleanup(desc); + return rv; +} + +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); + + intf->needs_remote_wakeup = on; + if (!rv) + usb_autopm_put_interface(intf); + return 0; +} + +static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + int rv = -EINVAL; + struct usb_host_interface *iface; + struct usb_endpoint_descriptor *ep; + struct usb_cdc_dmm_desc *dmhd; + u8 *buffer = intf->altsetting->extra; + int buflen = intf->altsetting->extralen; + u16 maxcom = WDM_DEFAULT_BUFSIZE; + + if (!buffer) + goto err; + while (buflen > 2) { + if (buffer[1] != USB_DT_CS_INTERFACE) { + dev_err(&intf->dev, "skipping garbage\n"); + goto next_desc; + } + + switch (buffer[2]) { + case USB_CDC_HEADER_TYPE: + break; + case USB_CDC_DMM_TYPE: + dmhd = (struct usb_cdc_dmm_desc *)buffer; + maxcom = le16_to_cpu(dmhd->wMaxCommand); + dev_dbg(&intf->dev, + "Finding maximum buffer length: %d", maxcom); + break; + default: + dev_err(&intf->dev, + "Ignoring extra header, type %d, length %d\n", + buffer[2], buffer[0]); + break; + } +next_desc: + buflen -= buffer[0]; + buffer += buffer[0]; + } + + iface = intf->cur_altsetting; + if (iface->desc.bNumEndpoints != 1) + goto err; + ep = &iface->endpoint[0].desc; + + rv = wdm_create(intf, ep, maxcom, &wdm_manage_power); + +err: + return rv; +} + +/** + * usb_cdc_wdm_register - register a WDM subdriver + * @intf: usb interface the subdriver will associate with + * @ep: interrupt endpoint to monitor for notifications + * @bufsize: maximum message size to support for read/write + * + * Create WDM usb class character device and associate it with intf + * without binding, allowing another driver to manage the interface. + * + * The subdriver will manage the given interrupt endpoint exclusively + * and will issue control requests referring to the given intf. It + * will otherwise avoid interferring, and in particular not do + * usb_set_intfdata/usb_get_intfdata on intf. + * + * The return value is a pointer to the subdriver's struct usb_driver. + * The registering driver is responsible for calling this subdriver's + * disconnect, suspend, resume, pre_reset and post_reset methods from + * its own. + */ +struct usb_driver *usb_cdc_wdm_register(struct usb_interface *intf, + struct usb_endpoint_descriptor *ep, + int bufsize, + int (*manage_power)(struct usb_interface *, int)) +{ + int rv = -EINVAL; + + rv = wdm_create(intf, ep, bufsize, manage_power); + if (rv < 0) + goto err; + + return &wdm_driver; +err: + return ERR_PTR(rv); +} +EXPORT_SYMBOL(usb_cdc_wdm_register); + +static void wdm_disconnect(struct usb_interface *intf) +{ + struct wdm_device *desc; + unsigned long flags; + + usb_deregister_dev(intf, &wdm_class); + desc = wdm_find_device(intf); + mutex_lock(&wdm_mutex); + + /* the spinlock makes sure no new urbs are generated in the callbacks */ + 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); + wake_up_all(&desc->wait); + mutex_lock(&desc->rlock); + mutex_lock(&desc->wlock); + kill_urbs(desc); + cancel_work_sync(&desc->rxwork); + mutex_unlock(&desc->wlock); + mutex_unlock(&desc->rlock); + + /* the desc->intf pointer used as list key is now invalid */ + spin_lock(&wdm_device_list_lock); + list_del(&desc->device_list); + spin_unlock(&wdm_device_list_lock); + + if (!desc->count) + cleanup(desc); + else + dev_dbg(&intf->dev, "%s: %d open files - postponing cleanup\n", __func__, desc->count); + mutex_unlock(&wdm_mutex); +} + +#ifdef CONFIG_PM +static int wdm_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct wdm_device *desc = wdm_find_device(intf); + int rv = 0; + + dev_dbg(&desc->intf->dev, "wdm%d_suspend\n", intf->minor); + + /* if this is an autosuspend the caller does the locking */ + if (!PMSG_IS_AUTO(message)) { + mutex_lock(&desc->rlock); + mutex_lock(&desc->wlock); + } + spin_lock_irq(&desc->iuspin); + + if (PMSG_IS_AUTO(message) && + (test_bit(WDM_IN_USE, &desc->flags) + || test_bit(WDM_RESPONDING, &desc->flags))) { + spin_unlock_irq(&desc->iuspin); + rv = -EBUSY; + } else { + + set_bit(WDM_SUSPENDING, &desc->flags); + spin_unlock_irq(&desc->iuspin); + /* callback submits work - order is essential */ + kill_urbs(desc); + cancel_work_sync(&desc->rxwork); + } + if (!PMSG_IS_AUTO(message)) { + mutex_unlock(&desc->wlock); + mutex_unlock(&desc->rlock); + } + + return rv; +} +#endif + +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) + dev_err(&desc->intf->dev, + "Error resume submitting int urb - %d\n", rv); + } + return rv; +} + +#ifdef CONFIG_PM +static int wdm_resume(struct usb_interface *intf) +{ + struct wdm_device *desc = wdm_find_device(intf); + int rv; + + dev_dbg(&desc->intf->dev, "wdm%d_resume\n", intf->minor); + + clear_bit(WDM_SUSPENDING, &desc->flags); + rv = recover_from_urb_loss(desc); + + return rv; +} +#endif + +static int wdm_pre_reset(struct usb_interface *intf) +{ + struct wdm_device *desc = wdm_find_device(intf); + + /* + * we notify everybody using poll of + * an exceptional situation + * must be done before recovery lest a spontaneous + * message from the device is lost + */ + spin_lock_irq(&desc->iuspin); + set_bit(WDM_RESETTING, &desc->flags); /* inform read/write */ + set_bit(WDM_READ, &desc->flags); /* unblock read */ + clear_bit(WDM_IN_USE, &desc->flags); /* unblock write */ + desc->rerr = -EINTR; + spin_unlock_irq(&desc->iuspin); + wake_up_all(&desc->wait); + mutex_lock(&desc->rlock); + mutex_lock(&desc->wlock); + kill_urbs(desc); + cancel_work_sync(&desc->rxwork); + return 0; +} + +static int wdm_post_reset(struct usb_interface *intf) +{ + struct wdm_device *desc = wdm_find_device(intf); + int rv; + + clear_bit(WDM_OVERFLOW, &desc->flags); + clear_bit(WDM_RESETTING, &desc->flags); + rv = recover_from_urb_loss(desc); + mutex_unlock(&desc->wlock); + mutex_unlock(&desc->rlock); + return 0; +} + +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, + .supports_autosuspend = 1, + .disable_hub_initiated_lpm = 1, +}; + +module_usb_driver(wdm_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/class/usblp.c b/drivers/usb/class/usblp.c index 0647164d36d..0924ee40a96 100644 --- a/drivers/usb/class/usblp.c +++ b/drivers/usb/class/usblp.c @@ -2,7 +2,7 @@ * usblp.c * * Copyright (c) 1999 Michael Gee <michael@linuxspecific.com> - * Copyright (c) 1999 Pavel Machek <pavel@suse.cz> + * Copyright (c) 1999 Pavel Machek <pavel@ucw.cz> * Copyright (c) 2000 Randy Dunlap <rdunlap@xenotime.net> * Copyright (c) 2000 Vojtech Pavlik <vojtech@suse.cz> # Copyright (c) 2001 Pete Zaitcev <zaitcev@redhat.com> @@ -27,7 +27,7 @@ * v0.11 - add proto_bias option (Pete Zaitcev) * v0.12 - add hpoj.sourceforge.net ioctls (David Paschal) * v0.13 - alloc space for statusbuf (<status> not on stack); - * use usb_buffer_alloc() for read buf & write buf; + * use usb_alloc_coherent() for read buf & write buf; * none - Maintained in Linux kernel after v0.13 */ @@ -52,12 +52,12 @@ #include <linux/sched.h> #include <linux/signal.h> #include <linux/poll.h> -#include <linux/init.h> #include <linux/slab.h> #include <linux/lp.h> #include <linux/mutex.h> #undef DEBUG #include <linux/usb.h> +#include <linux/ratelimit.h> /* * Version Information @@ -135,7 +135,7 @@ MFG:HEWLETT-PACKARD;MDL:DESKJET 970C;CMD:MLC,PCL,PML;CLASS:PRINTER;DESCRIPTION:H * ->lock locks what interrupt accesses. */ struct usblp { - struct usb_device *dev; /* USB device */ + struct usb_device *dev; /* USB device */ struct mutex wmut; struct mutex mut; spinlock_t lock; /* locks rcomplete, wcomplete */ @@ -163,36 +163,39 @@ struct usblp { unsigned char used; /* True if open */ unsigned char present; /* True if not disconnected */ unsigned char bidir; /* interface is bidirectional */ - unsigned char sleeping; /* interface is suspended */ unsigned char no_paper; /* Paper Out happened */ unsigned char *device_id_string; /* IEEE 1284 DEVICE ID string (ptr) */ /* first 2 bytes are (big-endian) length */ }; #ifdef DEBUG -static void usblp_dump(struct usblp *usblp) { +static void usblp_dump(struct usblp *usblp) +{ + struct device *dev = &usblp->intf->dev; int p; - dbg("usblp=0x%p", usblp); - dbg("dev=0x%p", usblp->dev); - dbg("present=%d", usblp->present); - dbg("readbuf=0x%p", usblp->readbuf); - dbg("readcount=%d", usblp->readcount); - dbg("ifnum=%d", usblp->ifnum); - for (p = USBLP_FIRST_PROTOCOL; p <= USBLP_LAST_PROTOCOL; p++) { - dbg("protocol[%d].alt_setting=%d", p, usblp->protocol[p].alt_setting); - dbg("protocol[%d].epwrite=%p", p, usblp->protocol[p].epwrite); - dbg("protocol[%d].epread=%p", p, usblp->protocol[p].epread); - } - dbg("current_protocol=%d", usblp->current_protocol); - dbg("minor=%d", usblp->minor); - dbg("wstatus=%d", usblp->wstatus); - dbg("rstatus=%d", usblp->rstatus); - dbg("quirks=%d", usblp->quirks); - dbg("used=%d", usblp->used); - dbg("bidir=%d", usblp->bidir); - dbg("sleeping=%d", usblp->sleeping); - dbg("device_id_string=\"%s\"", + dev_dbg(dev, "usblp=0x%p\n", usblp); + dev_dbg(dev, "dev=0x%p\n", usblp->dev); + dev_dbg(dev, "present=%d\n", usblp->present); + dev_dbg(dev, "readbuf=0x%p\n", usblp->readbuf); + dev_dbg(dev, "readcount=%d\n", usblp->readcount); + dev_dbg(dev, "ifnum=%d\n", usblp->ifnum); + for (p = USBLP_FIRST_PROTOCOL; p <= USBLP_LAST_PROTOCOL; p++) { + dev_dbg(dev, "protocol[%d].alt_setting=%d\n", p, + usblp->protocol[p].alt_setting); + dev_dbg(dev, "protocol[%d].epwrite=%p\n", p, + usblp->protocol[p].epwrite); + dev_dbg(dev, "protocol[%d].epread=%p\n", p, + usblp->protocol[p].epread); + } + dev_dbg(dev, "current_protocol=%d\n", usblp->current_protocol); + dev_dbg(dev, "minor=%d\n", usblp->minor); + dev_dbg(dev, "wstatus=%d\n", usblp->wstatus); + dev_dbg(dev, "rstatus=%d\n", usblp->rstatus); + dev_dbg(dev, "quirks=%d\n", usblp->quirks); + dev_dbg(dev, "used=%d\n", usblp->used); + dev_dbg(dev, "bidir=%d\n", usblp->bidir); + dev_dbg(dev, "device_id_string=\"%s\"\n", usblp->device_id_string ? usblp->device_id_string + 2 : (unsigned char *)"(null)"); @@ -218,14 +221,15 @@ static const struct quirk_printer_struct quirk_printers[] = { { 0x03f0, 0x0304, USBLP_QUIRK_BIDIR }, /* HP DeskJet 810C/812C */ { 0x03f0, 0x0404, USBLP_QUIRK_BIDIR }, /* HP DeskJet 830C */ { 0x03f0, 0x0504, USBLP_QUIRK_BIDIR }, /* HP DeskJet 885C */ - { 0x03f0, 0x0604, USBLP_QUIRK_BIDIR }, /* HP DeskJet 840C */ - { 0x03f0, 0x0804, USBLP_QUIRK_BIDIR }, /* HP DeskJet 816C */ + { 0x03f0, 0x0604, USBLP_QUIRK_BIDIR }, /* HP DeskJet 840C */ + { 0x03f0, 0x0804, USBLP_QUIRK_BIDIR }, /* HP DeskJet 816C */ { 0x03f0, 0x1104, USBLP_QUIRK_BIDIR }, /* HP Deskjet 959C */ { 0x0409, 0xefbe, USBLP_QUIRK_BIDIR }, /* NEC Picty900 (HP OEM) */ { 0x0409, 0xbef4, USBLP_QUIRK_BIDIR }, /* NEC Picty760 (HP OEM) */ { 0x0409, 0xf0be, USBLP_QUIRK_BIDIR }, /* NEC Picty920 (HP OEM) */ { 0x0409, 0xf1be, USBLP_QUIRK_BIDIR }, /* NEC Picty800 (HP OEM) */ { 0x0482, 0x0010, USBLP_QUIRK_BIDIR }, /* Kyocera Mita FS 820, by zut <kernel@zut.de> */ + { 0x04f9, 0x000d, USBLP_QUIRK_BIDIR }, /* Brother Industries, Ltd HL-1440 Laser Printer */ { 0x04b8, 0x0202, USBLP_QUIRK_BAD_CLASS }, /* Seiko Epson Receipt Printer M129C */ { 0, 0 } }; @@ -255,14 +259,14 @@ static int usblp_ctrl_msg(struct usblp *usblp, int request, int type, int dir, i /* High byte has the interface index. Low byte has the alternate setting. */ - if ((request == USBLP_REQ_GET_ID) && (type == USB_TYPE_CLASS)) { - index = (usblp->ifnum<<8)|usblp->protocol[usblp->current_protocol].alt_setting; - } + if ((request == USBLP_REQ_GET_ID) && (type == USB_TYPE_CLASS)) + index = (usblp->ifnum<<8)|usblp->protocol[usblp->current_protocol].alt_setting; retval = usb_control_msg(usblp->dev, dir ? usb_rcvctrlpipe(usblp->dev, 0) : usb_sndctrlpipe(usblp->dev, 0), request, type | dir | recip, value, index, buf, len, USBLP_CTL_TIMEOUT); - dbg("usblp_control_msg: rq: 0x%02x dir: %d recip: %d value: %d idx: %d len: %#x result: %d", + dev_dbg(&usblp->intf->dev, + "usblp_control_msg: rq: 0x%02x dir: %d recip: %d value: %d idx: %d len: %#x result: %d\n", request, !!dir, recip, value, index, len, retval); return retval < 0 ? retval : 0; } @@ -349,8 +353,7 @@ static int usblp_check_status(struct usblp *usblp, int err) mutex_lock(&usblp->mut); if ((error = usblp_read_status(usblp, usblp->statusbuf)) < 0) { mutex_unlock(&usblp->mut); - if (printk_ratelimit()) - printk(KERN_ERR + printk_ratelimited(KERN_ERR "usblp%d: error %d reading printer status\n", usblp->minor, error); return 0; @@ -373,9 +376,9 @@ static int usblp_check_status(struct usblp *usblp, int err) return newerr; } -static int handle_bidir (struct usblp *usblp) +static int handle_bidir(struct usblp *usblp) { - if (usblp->bidir && usblp->used && !usblp->sleeping) { + if (usblp->bidir && usblp->used) { if (usblp_submit_read(usblp) < 0) return -EIO; } @@ -396,14 +399,13 @@ static int usblp_open(struct inode *inode, struct file *file) if (minor < 0) return -ENODEV; - mutex_lock (&usblp_mutex); + mutex_lock(&usblp_mutex); retval = -ENODEV; intf = usb_find_interface(&usblp_driver, minor); - if (!intf) { + if (!intf) goto out; - } - usblp = usb_get_intfdata (intf); + usblp = usb_get_intfdata(intf); if (!usblp || !usblp->dev || !usblp->present) goto out; @@ -434,18 +436,18 @@ static int usblp_open(struct inode *inode, struct file *file) retval = -EIO; } out: - mutex_unlock (&usblp_mutex); + mutex_unlock(&usblp_mutex); return retval; } -static void usblp_cleanup (struct usblp *usblp) +static void usblp_cleanup(struct usblp *usblp) { printk(KERN_INFO "usblp%d: removed\n", usblp->minor); kfree(usblp->readbuf); - kfree (usblp->device_id_string); - kfree (usblp->statusbuf); - kfree (usblp); + kfree(usblp->device_id_string); + kfree(usblp->statusbuf); + kfree(usblp); } static void usblp_unlink_urbs(struct usblp *usblp) @@ -459,14 +461,14 @@ static int usblp_release(struct inode *inode, struct file *file) usblp->flags &= ~LP_ABORT; - mutex_lock (&usblp_mutex); + mutex_lock(&usblp_mutex); usblp->used = 0; if (usblp->present) { usblp_unlink_urbs(usblp); usb_autopm_put_interface(usblp->intf); - } else /* finish cleanup from disconnect */ - usblp_cleanup (usblp); - mutex_unlock (&usblp_mutex); + } else /* finish cleanup from disconnect */ + usblp_cleanup(usblp); + mutex_unlock(&usblp_mutex); return 0; } @@ -496,194 +498,193 @@ static long usblp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) int twoints[2]; int retval = 0; - mutex_lock (&usblp->mut); + mutex_lock(&usblp->mut); if (!usblp->present) { retval = -ENODEV; goto done; } - if (usblp->sleeping) { - retval = -ENODEV; - goto done; - } - - dbg("usblp_ioctl: cmd=0x%x (%c nr=%d len=%d dir=%d)", cmd, _IOC_TYPE(cmd), - _IOC_NR(cmd), _IOC_SIZE(cmd), _IOC_DIR(cmd) ); + dev_dbg(&usblp->intf->dev, + "usblp_ioctl: cmd=0x%x (%c nr=%d len=%d dir=%d)\n", cmd, + _IOC_TYPE(cmd), _IOC_NR(cmd), _IOC_SIZE(cmd), _IOC_DIR(cmd)); if (_IOC_TYPE(cmd) == 'P') /* new-style ioctl number */ switch (_IOC_NR(cmd)) { - case IOCNR_GET_DEVICE_ID: /* get the DEVICE_ID string */ - if (_IOC_DIR(cmd) != _IOC_READ) { - retval = -EINVAL; - goto done; - } + case IOCNR_GET_DEVICE_ID: /* get the DEVICE_ID string */ + if (_IOC_DIR(cmd) != _IOC_READ) { + retval = -EINVAL; + goto done; + } - length = usblp_cache_device_id_string(usblp); - if (length < 0) { - retval = length; - goto done; - } - if (length > _IOC_SIZE(cmd)) - length = _IOC_SIZE(cmd); /* truncate */ - - if (copy_to_user((void __user *) arg, - usblp->device_id_string, - (unsigned long) length)) { - retval = -EFAULT; - goto done; - } + length = usblp_cache_device_id_string(usblp); + if (length < 0) { + retval = length; + goto done; + } + if (length > _IOC_SIZE(cmd)) + length = _IOC_SIZE(cmd); /* truncate */ + + if (copy_to_user((void __user *) arg, + usblp->device_id_string, + (unsigned long) length)) { + retval = -EFAULT; + goto done; + } - break; + break; - case IOCNR_GET_PROTOCOLS: - if (_IOC_DIR(cmd) != _IOC_READ || - _IOC_SIZE(cmd) < sizeof(twoints)) { - retval = -EINVAL; - goto done; - } + case IOCNR_GET_PROTOCOLS: + if (_IOC_DIR(cmd) != _IOC_READ || + _IOC_SIZE(cmd) < sizeof(twoints)) { + retval = -EINVAL; + goto done; + } - twoints[0] = usblp->current_protocol; - twoints[1] = 0; - for (i = USBLP_FIRST_PROTOCOL; - i <= USBLP_LAST_PROTOCOL; i++) { - if (usblp->protocol[i].alt_setting >= 0) - twoints[1] |= (1<<i); - } + twoints[0] = usblp->current_protocol; + twoints[1] = 0; + for (i = USBLP_FIRST_PROTOCOL; + i <= USBLP_LAST_PROTOCOL; i++) { + if (usblp->protocol[i].alt_setting >= 0) + twoints[1] |= (1<<i); + } - if (copy_to_user((void __user *)arg, - (unsigned char *)twoints, - sizeof(twoints))) { - retval = -EFAULT; - goto done; - } + if (copy_to_user((void __user *)arg, + (unsigned char *)twoints, + sizeof(twoints))) { + retval = -EFAULT; + goto done; + } - break; + break; - case IOCNR_SET_PROTOCOL: - if (_IOC_DIR(cmd) != _IOC_WRITE) { - retval = -EINVAL; - goto done; - } + case IOCNR_SET_PROTOCOL: + if (_IOC_DIR(cmd) != _IOC_WRITE) { + retval = -EINVAL; + goto done; + } #ifdef DEBUG - if (arg == -10) { - usblp_dump(usblp); - break; - } + if (arg == -10) { + usblp_dump(usblp); + break; + } #endif - usblp_unlink_urbs(usblp); - retval = usblp_set_protocol(usblp, arg); - if (retval < 0) { - usblp_set_protocol(usblp, - usblp->current_protocol); - } - break; + usblp_unlink_urbs(usblp); + retval = usblp_set_protocol(usblp, arg); + if (retval < 0) { + usblp_set_protocol(usblp, + usblp->current_protocol); + } + break; - case IOCNR_HP_SET_CHANNEL: - if (_IOC_DIR(cmd) != _IOC_WRITE || - le16_to_cpu(usblp->dev->descriptor.idVendor) != 0x03F0 || - usblp->quirks & USBLP_QUIRK_BIDIR) { - retval = -EINVAL; - goto done; - } + case IOCNR_HP_SET_CHANNEL: + if (_IOC_DIR(cmd) != _IOC_WRITE || + le16_to_cpu(usblp->dev->descriptor.idVendor) != 0x03F0 || + usblp->quirks & USBLP_QUIRK_BIDIR) { + retval = -EINVAL; + goto done; + } - err = usblp_hp_channel_change_request(usblp, - arg, &newChannel); - if (err < 0) { - err("usblp%d: error = %d setting " - "HP channel", - usblp->minor, err); - retval = -EIO; - goto done; - } + err = usblp_hp_channel_change_request(usblp, + arg, &newChannel); + if (err < 0) { + dev_err(&usblp->dev->dev, + "usblp%d: error = %d setting " + "HP channel\n", + usblp->minor, err); + retval = -EIO; + goto done; + } - dbg("usblp%d requested/got HP channel %ld/%d", - usblp->minor, arg, newChannel); - break; + dev_dbg(&usblp->intf->dev, + "usblp%d requested/got HP channel %ld/%d\n", + usblp->minor, arg, newChannel); + break; - case IOCNR_GET_BUS_ADDRESS: - if (_IOC_DIR(cmd) != _IOC_READ || - _IOC_SIZE(cmd) < sizeof(twoints)) { - retval = -EINVAL; - goto done; - } + case IOCNR_GET_BUS_ADDRESS: + if (_IOC_DIR(cmd) != _IOC_READ || + _IOC_SIZE(cmd) < sizeof(twoints)) { + retval = -EINVAL; + goto done; + } - twoints[0] = usblp->dev->bus->busnum; - twoints[1] = usblp->dev->devnum; - if (copy_to_user((void __user *)arg, - (unsigned char *)twoints, - sizeof(twoints))) { - retval = -EFAULT; - goto done; - } + twoints[0] = usblp->dev->bus->busnum; + twoints[1] = usblp->dev->devnum; + if (copy_to_user((void __user *)arg, + (unsigned char *)twoints, + sizeof(twoints))) { + retval = -EFAULT; + goto done; + } - dbg("usblp%d is bus=%d, device=%d", - usblp->minor, twoints[0], twoints[1]); - break; + dev_dbg(&usblp->intf->dev, + "usblp%d is bus=%d, device=%d\n", + usblp->minor, twoints[0], twoints[1]); + break; - case IOCNR_GET_VID_PID: - if (_IOC_DIR(cmd) != _IOC_READ || - _IOC_SIZE(cmd) < sizeof(twoints)) { - retval = -EINVAL; - goto done; - } + case IOCNR_GET_VID_PID: + if (_IOC_DIR(cmd) != _IOC_READ || + _IOC_SIZE(cmd) < sizeof(twoints)) { + retval = -EINVAL; + goto done; + } - twoints[0] = le16_to_cpu(usblp->dev->descriptor.idVendor); - twoints[1] = le16_to_cpu(usblp->dev->descriptor.idProduct); - if (copy_to_user((void __user *)arg, - (unsigned char *)twoints, - sizeof(twoints))) { - retval = -EFAULT; - goto done; - } + twoints[0] = le16_to_cpu(usblp->dev->descriptor.idVendor); + twoints[1] = le16_to_cpu(usblp->dev->descriptor.idProduct); + if (copy_to_user((void __user *)arg, + (unsigned char *)twoints, + sizeof(twoints))) { + retval = -EFAULT; + goto done; + } - dbg("usblp%d is VID=0x%4.4X, PID=0x%4.4X", - usblp->minor, twoints[0], twoints[1]); - break; + dev_dbg(&usblp->intf->dev, + "usblp%d is VID=0x%4.4X, PID=0x%4.4X\n", + usblp->minor, twoints[0], twoints[1]); + break; - case IOCNR_SOFT_RESET: - if (_IOC_DIR(cmd) != _IOC_NONE) { - retval = -EINVAL; - goto done; - } - retval = usblp_reset(usblp); - break; - default: - retval = -ENOTTY; + case IOCNR_SOFT_RESET: + if (_IOC_DIR(cmd) != _IOC_NONE) { + retval = -EINVAL; + goto done; + } + retval = usblp_reset(usblp); + break; + default: + retval = -ENOTTY; } else /* old-style ioctl value */ switch (cmd) { - case LPGETSTATUS: - if ((retval = usblp_read_status(usblp, usblp->statusbuf))) { - if (printk_ratelimit()) - printk(KERN_ERR "usblp%d:" - "failed reading printer status (%d)\n", - usblp->minor, retval); - retval = -EIO; - goto done; - } - status = *usblp->statusbuf; - if (copy_to_user ((void __user *)arg, &status, sizeof(int))) - retval = -EFAULT; - break; + case LPGETSTATUS: + if ((retval = usblp_read_status(usblp, usblp->statusbuf))) { + printk_ratelimited(KERN_ERR "usblp%d:" + "failed reading printer status (%d)\n", + usblp->minor, retval); + retval = -EIO; + goto done; + } + status = *usblp->statusbuf; + if (copy_to_user((void __user *)arg, &status, sizeof(int))) + retval = -EFAULT; + break; - case LPABORT: - if (arg) - usblp->flags |= LP_ABORT; - else - usblp->flags &= ~LP_ABORT; - break; + case LPABORT: + if (arg) + usblp->flags |= LP_ABORT; + else + usblp->flags &= ~LP_ABORT; + break; - default: - retval = -ENOTTY; + default: + retval = -ENOTTY; } done: - mutex_unlock (&usblp->mut); + mutex_unlock(&usblp->mut); return retval; } @@ -845,7 +846,7 @@ static ssize_t usblp_read(struct file *file, char __user *buffer, size_t len, lo } done: - mutex_unlock (&usblp->mut); + mutex_unlock(&usblp->mut); return count; } @@ -878,16 +879,19 @@ static int usblp_wwait(struct usblp *usblp, int nonblock) if (rc <= 0) break; - if (usblp->flags & LP_ABORT) { - if (schedule_timeout(msecs_to_jiffies(5000)) == 0) { + if (schedule_timeout(msecs_to_jiffies(1500)) == 0) { + if (usblp->flags & LP_ABORT) { err = usblp_check_status(usblp, err); if (err == 1) { /* Paper out */ rc = -ENOSPC; break; } + } else { + /* Prod the printer, Gentoo#251237. */ + mutex_lock(&usblp->mut); + usblp_read_status(usblp, usblp->statusbuf); + mutex_unlock(&usblp->mut); } - } else { - schedule(); } } set_current_state(TASK_RUNNING); @@ -909,8 +913,6 @@ static int usblp_wtest(struct usblp *usblp, int nonblock) return 0; } spin_unlock_irqrestore(&usblp->lock, flags); - if (usblp->sleeping) - return -ENODEV; if (nonblock) return -EAGAIN; return 1; @@ -963,8 +965,6 @@ static int usblp_rtest(struct usblp *usblp, int nonblock) return 0; } spin_unlock_irqrestore(&usblp->lock, flags); - if (usblp->sleeping) - return -ENODEV; if (nonblock) return -EAGAIN; return 1; @@ -995,7 +995,7 @@ static int usblp_submit_read(struct usblp *usblp) usblp->rcomplete = 0; spin_unlock_irqrestore(&usblp->lock, flags); if ((rc = usb_submit_urb(urb, GFP_KERNEL)) < 0) { - dbg("error submitting urb (%d)", rc); + dev_dbg(&usblp->intf->dev, "error submitting urb (%d)\n", rc); spin_lock_irqsave(&usblp->lock, flags); usblp->rstatus = rc; usblp->rcomplete = 1; @@ -1029,7 +1029,7 @@ raise_urb: * while you are sending print data, and you don't try to query the * printer status every couple of milliseconds, you will probably be OK. */ -static unsigned int usblp_quirks (__u16 vendor, __u16 product) +static unsigned int usblp_quirks(__u16 vendor, __u16 product) { int i; @@ -1037,7 +1037,7 @@ static unsigned int usblp_quirks (__u16 vendor, __u16 product) if (vendor == quirk_printers[i].vendorId && product == quirk_printers[i].productId) return quirk_printers[i].quirks; - } + } return 0; } @@ -1050,10 +1050,17 @@ static const struct file_operations usblp_fops = { .compat_ioctl = usblp_ioctl, .open = usblp_open, .release = usblp_release, + .llseek = noop_llseek, }; +static char *usblp_devnode(struct device *dev, umode_t *mode) +{ + return kasprintf(GFP_KERNEL, "usb/%s", dev_name(dev)); +} + static struct usb_class_driver usblp_class = { .name = "lp%d", + .devnode = usblp_devnode, .fops = &usblp_fops, .minor_base = USBLP_MINOR_BASE, }; @@ -1061,7 +1068,7 @@ static struct usb_class_driver usblp_class = { static ssize_t usblp_show_ieee1284_id(struct device *dev, struct device_attribute *attr, char *buf) { struct usb_interface *intf = to_usb_interface(dev); - struct usblp *usblp = usb_get_intfdata (intf); + struct usblp *usblp = usb_get_intfdata(intf); if (usblp->device_id_string[0] == 0 && usblp->device_id_string[1] == 0) @@ -1075,20 +1082,21 @@ static DEVICE_ATTR(ieee1284_id, S_IRUGO, usblp_show_ieee1284_id, NULL); static int usblp_probe(struct usb_interface *intf, const struct usb_device_id *id) { - struct usb_device *dev = interface_to_usbdev (intf); - struct usblp *usblp = NULL; + struct usb_device *dev = interface_to_usbdev(intf); + struct usblp *usblp; int protocol; int retval; /* Malloc and start initializing usblp structure so we can use it * directly. */ - if (!(usblp = kzalloc(sizeof(struct usblp), GFP_KERNEL))) { + usblp = kzalloc(sizeof(struct usblp), GFP_KERNEL); + if (!usblp) { retval = -ENOMEM; - goto abort; + goto abort_ret; } usblp->dev = dev; mutex_init(&usblp->wmut); - mutex_init (&usblp->mut); + mutex_init(&usblp->mut); spin_lock_init(&usblp->lock); init_waitqueue_head(&usblp->rwait); init_waitqueue_head(&usblp->wwait); @@ -1129,7 +1137,8 @@ static int usblp_probe(struct usb_interface *intf, /* Analyze and pick initial alternate settings and endpoints. */ protocol = usblp_select_alts(usblp); if (protocol < 0) { - dbg("incompatible printer-class device 0x%4.4X/0x%4.4X", + dev_dbg(&intf->dev, + "incompatible printer-class device 0x%4.4X/0x%4.4X\n", le16_to_cpu(dev->descriptor.idVendor), le16_to_cpu(dev->descriptor.idProduct)); retval = -ENODEV; @@ -1152,20 +1161,20 @@ static int usblp_probe(struct usb_interface *intf, usblp_check_status(usblp, 0); #endif - usb_set_intfdata (intf, usblp); + usb_set_intfdata(intf, usblp); usblp->present = 1; retval = usb_register_dev(intf, &usblp_class); if (retval) { - printk(KERN_ERR "usblp: Not able to get a minor" - " (base %u, slice default): %d\n", - USBLP_MINOR_BASE, retval); + dev_err(&intf->dev, + "usblp: Not able to get a minor (base %u, slice default): %d\n", + USBLP_MINOR_BASE, retval); goto abort_intfdata; } usblp->minor = intf->minor; - printk(KERN_INFO "usblp%d: USB %sdirectional printer dev %d " - "if %d alt %d proto %d vid 0x%4.4X pid 0x%4.4X\n", + dev_info(&intf->dev, + "usblp%d: USB %sdirectional printer dev %d if %d alt %d proto %d vid 0x%4.4X pid 0x%4.4X\n", usblp->minor, usblp->bidir ? "Bi" : "Uni", dev->devnum, usblp->ifnum, usblp->protocol[usblp->current_protocol].alt_setting, @@ -1176,15 +1185,14 @@ static int usblp_probe(struct usb_interface *intf, return 0; abort_intfdata: - usb_set_intfdata (intf, NULL); + usb_set_intfdata(intf, NULL); device_remove_file(&intf->dev, &dev_attr_ieee1284_id); abort: - if (usblp) { - kfree(usblp->readbuf); - kfree(usblp->statusbuf); - kfree(usblp->device_id_string); - kfree(usblp); - } + kfree(usblp->readbuf); + kfree(usblp->statusbuf); + kfree(usblp->device_id_string); + kfree(usblp); +abort_ret: return retval; } @@ -1303,7 +1311,8 @@ static int usblp_set_protocol(struct usblp *usblp, int protocol) usblp->bidir = (usblp->protocol[protocol].epread != NULL); usblp->current_protocol = protocol; - dbg("usblp%d set protocol %d", usblp->minor, protocol); + dev_dbg(&usblp->intf->dev, "usblp%d set protocol %d\n", + usblp->minor, protocol); return 0; } @@ -1316,7 +1325,8 @@ static int usblp_cache_device_id_string(struct usblp *usblp) err = usblp_get_id(usblp, 0, usblp->device_id_string, USBLP_DEVICE_ID_SIZE - 1); if (err < 0) { - dbg("usblp%d: error = %d reading IEEE-1284 Device ID string", + dev_dbg(&usblp->intf->dev, + "usblp%d: error = %d reading IEEE-1284 Device ID string\n", usblp->minor, err); usblp->device_id_string[0] = usblp->device_id_string[1] = '\0'; return -EIO; @@ -1332,7 +1342,7 @@ static int usblp_cache_device_id_string(struct usblp *usblp) length = USBLP_DEVICE_ID_SIZE - 1; usblp->device_id_string[length] = '\0'; - dbg("usblp%d Device ID string [len=%d]=\"%s\"", + dev_dbg(&usblp->intf->dev, "usblp%d Device ID string [len=%d]=\"%s\"\n", usblp->minor, length, &usblp->device_id_string[2]); return length; @@ -1340,38 +1350,36 @@ static int usblp_cache_device_id_string(struct usblp *usblp) static void usblp_disconnect(struct usb_interface *intf) { - struct usblp *usblp = usb_get_intfdata (intf); + struct usblp *usblp = usb_get_intfdata(intf); usb_deregister_dev(intf, &usblp_class); if (!usblp || !usblp->dev) { - err("bogus disconnect"); - BUG (); + dev_err(&intf->dev, "bogus disconnect\n"); + BUG(); } device_remove_file(&intf->dev, &dev_attr_ieee1284_id); - mutex_lock (&usblp_mutex); - mutex_lock (&usblp->mut); + mutex_lock(&usblp_mutex); + mutex_lock(&usblp->mut); usblp->present = 0; wake_up(&usblp->wwait); wake_up(&usblp->rwait); - usb_set_intfdata (intf, NULL); + usb_set_intfdata(intf, NULL); usblp_unlink_urbs(usblp); - mutex_unlock (&usblp->mut); + mutex_unlock(&usblp->mut); if (!usblp->used) - usblp_cleanup (usblp); - mutex_unlock (&usblp_mutex); + usblp_cleanup(usblp); + mutex_unlock(&usblp_mutex); } -static int usblp_suspend (struct usb_interface *intf, pm_message_t message) +static int usblp_suspend(struct usb_interface *intf, pm_message_t message) { - struct usblp *usblp = usb_get_intfdata (intf); + struct usblp *usblp = usb_get_intfdata(intf); - /* we take no more IO */ - usblp->sleeping = 1; usblp_unlink_urbs(usblp); #if 0 /* XXX Do we want this? What if someone is reading, should we fail? */ /* not strictly necessary, but just in case */ @@ -1382,18 +1390,17 @@ static int usblp_suspend (struct usb_interface *intf, pm_message_t message) return 0; } -static int usblp_resume (struct usb_interface *intf) +static int usblp_resume(struct usb_interface *intf) { - struct usblp *usblp = usb_get_intfdata (intf); + struct usblp *usblp = usb_get_intfdata(intf); int r; - usblp->sleeping = 0; - r = handle_bidir (usblp); + r = handle_bidir(usblp); return r; } -static struct usb_device_id usblp_ids [] = { +static const struct usb_device_id usblp_ids[] = { { USB_DEVICE_INFO(7, 1, 1) }, { USB_DEVICE_INFO(7, 1, 2) }, { USB_DEVICE_INFO(7, 1, 3) }, @@ -1404,7 +1411,7 @@ static struct usb_device_id usblp_ids [] = { { } /* Terminating entry */ }; -MODULE_DEVICE_TABLE (usb, usblp_ids); +MODULE_DEVICE_TABLE(usb, usblp_ids); static struct usb_driver usblp_driver = { .name = "usblp", @@ -1416,21 +1423,10 @@ static struct usb_driver usblp_driver = { .supports_autosuspend = 1, }; -static int __init usblp_init(void) -{ - return usb_register(&usblp_driver); -} - -static void __exit usblp_exit(void) -{ - usb_deregister(&usblp_driver); -} - -module_init(usblp_init); -module_exit(usblp_exit); +module_usb_driver(usblp_driver); -MODULE_AUTHOR( DRIVER_AUTHOR ); -MODULE_DESCRIPTION( DRIVER_DESC ); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); module_param(proto_bias, int, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(proto_bias, "Favourite protocol number"); MODULE_LICENSE("GPL"); diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c new file mode 100644 index 00000000000..103a6e9ee49 --- /dev/null +++ b/drivers/usb/class/usbtmc.c @@ -0,0 +1,1231 @@ +/** + * drivers/usb/class/usbtmc.c - USB Test & Measurement class driver + * + * Copyright (C) 2007 Stefan Kopp, Gechingen, Germany + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2008 Greg Kroah-Hartman <gregkh@suse.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * The GNU General Public License is available at + * http://www.gnu.org/copyleft/gpl.html. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/kref.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/usb.h> +#include <linux/usb/tmc.h> + + +#define RIGOL 1 +#define USBTMC_HEADER_SIZE 12 +#define USBTMC_MINOR_BASE 176 + +/* + * Size of driver internal IO buffer. Must be multiple of 4 and at least as + * large as wMaxPacketSize (which is usually 512 bytes). + */ +#define USBTMC_SIZE_IOBUFFER 2048 + +/* Default USB timeout (in milliseconds) */ +#define USBTMC_TIMEOUT 5000 + +/* + * Maximum number of read cycles to empty bulk in endpoint during CLEAR and + * ABORT_BULK_IN requests. Ends the loop if (for whatever reason) a short + * packet is never read. + */ +#define USBTMC_MAX_READS_TO_CLEAR_BULK_IN 100 + +static const struct usb_device_id usbtmc_devices[] = { + { USB_INTERFACE_INFO(USB_CLASS_APP_SPEC, 3, 0), }, + { USB_INTERFACE_INFO(USB_CLASS_APP_SPEC, 3, 1), }, + { 0, } /* terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, usbtmc_devices); + +/* + * This structure is the capabilities for the device + * See section 4.2.1.8 of the USBTMC specification, + * and section 4.2.2 of the USBTMC usb488 subclass + * specification for details. + */ +struct usbtmc_dev_capabilities { + __u8 interface_capabilities; + __u8 device_capabilities; + __u8 usb488_interface_capabilities; + __u8 usb488_device_capabilities; +}; + +/* This structure holds private data for each USBTMC device. One copy is + * allocated for each USBTMC device in the driver's probe function. + */ +struct usbtmc_device_data { + const struct usb_device_id *id; + struct usb_device *usb_dev; + struct usb_interface *intf; + + unsigned int bulk_in; + unsigned int bulk_out; + + u8 bTag; + u8 bTag_last_write; /* needed for abort */ + u8 bTag_last_read; /* needed for abort */ + + u8 rigol_quirk; + + /* attributes from the USB TMC spec for this device */ + u8 TermChar; + bool TermCharEnabled; + bool auto_abort; + + bool zombie; /* fd of disconnected device */ + + struct usbtmc_dev_capabilities capabilities; + struct kref kref; + struct mutex io_mutex; /* only one i/o function running at a time */ +}; +#define to_usbtmc_data(d) container_of(d, struct usbtmc_device_data, kref) + +struct usbtmc_ID_rigol_quirk { + __u16 idVendor; + __u16 idProduct; +}; + +static const struct usbtmc_ID_rigol_quirk usbtmc_id_quirk[] = { + { 0x1ab1, 0x0588 }, + { 0, 0 } +}; + +/* Forward declarations */ +static struct usb_driver usbtmc_driver; + +static void usbtmc_delete(struct kref *kref) +{ + struct usbtmc_device_data *data = to_usbtmc_data(kref); + + usb_put_dev(data->usb_dev); +} + +static int usbtmc_open(struct inode *inode, struct file *filp) +{ + struct usb_interface *intf; + struct usbtmc_device_data *data; + int retval = 0; + + intf = usb_find_interface(&usbtmc_driver, iminor(inode)); + if (!intf) { + pr_err("can not find device for minor %d", iminor(inode)); + return -ENODEV; + } + + data = usb_get_intfdata(intf); + kref_get(&data->kref); + + /* Store pointer in file structure's private data field */ + filp->private_data = data; + + return retval; +} + +static int usbtmc_release(struct inode *inode, struct file *file) +{ + struct usbtmc_device_data *data = file->private_data; + + kref_put(&data->kref, usbtmc_delete); + return 0; +} + +static int usbtmc_ioctl_abort_bulk_in(struct usbtmc_device_data *data) +{ + u8 *buffer; + struct device *dev; + int rv; + int n; + int actual; + struct usb_host_interface *current_setting; + int max_size; + + dev = &data->intf->dev; + buffer = kmalloc(USBTMC_SIZE_IOBUFFER, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + rv = usb_control_msg(data->usb_dev, + usb_rcvctrlpipe(data->usb_dev, 0), + USBTMC_REQUEST_INITIATE_ABORT_BULK_IN, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, + data->bTag_last_read, data->bulk_in, + buffer, 2, USBTMC_TIMEOUT); + + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + + dev_dbg(dev, "INITIATE_ABORT_BULK_IN returned %x\n", buffer[0]); + + if (buffer[0] == USBTMC_STATUS_FAILED) { + rv = 0; + goto exit; + } + + if (buffer[0] != USBTMC_STATUS_SUCCESS) { + dev_err(dev, "INITIATE_ABORT_BULK_IN returned %x\n", + buffer[0]); + rv = -EPERM; + goto exit; + } + + max_size = 0; + current_setting = data->intf->cur_altsetting; + for (n = 0; n < current_setting->desc.bNumEndpoints; n++) + if (current_setting->endpoint[n].desc.bEndpointAddress == + data->bulk_in) + max_size = usb_endpoint_maxp(¤t_setting->endpoint[n].desc); + + if (max_size == 0) { + dev_err(dev, "Couldn't get wMaxPacketSize\n"); + rv = -EPERM; + goto exit; + } + + dev_dbg(&data->intf->dev, "wMaxPacketSize is %d\n", max_size); + + n = 0; + + do { + dev_dbg(dev, "Reading from bulk in EP\n"); + + rv = usb_bulk_msg(data->usb_dev, + usb_rcvbulkpipe(data->usb_dev, + data->bulk_in), + buffer, USBTMC_SIZE_IOBUFFER, + &actual, USBTMC_TIMEOUT); + + n++; + + if (rv < 0) { + dev_err(dev, "usb_bulk_msg returned %d\n", rv); + goto exit; + } + } while ((actual == max_size) && + (n < USBTMC_MAX_READS_TO_CLEAR_BULK_IN)); + + if (actual == max_size) { + dev_err(dev, "Couldn't clear device buffer within %d cycles\n", + USBTMC_MAX_READS_TO_CLEAR_BULK_IN); + rv = -EPERM; + goto exit; + } + + n = 0; + +usbtmc_abort_bulk_in_status: + rv = usb_control_msg(data->usb_dev, + usb_rcvctrlpipe(data->usb_dev, 0), + USBTMC_REQUEST_CHECK_ABORT_BULK_IN_STATUS, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, + 0, data->bulk_in, buffer, 0x08, + USBTMC_TIMEOUT); + + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + + dev_dbg(dev, "INITIATE_ABORT_BULK_IN returned %x\n", buffer[0]); + + if (buffer[0] == USBTMC_STATUS_SUCCESS) { + rv = 0; + goto exit; + } + + if (buffer[0] != USBTMC_STATUS_PENDING) { + dev_err(dev, "INITIATE_ABORT_BULK_IN returned %x\n", buffer[0]); + rv = -EPERM; + goto exit; + } + + if (buffer[1] == 1) + do { + dev_dbg(dev, "Reading from bulk in EP\n"); + + rv = usb_bulk_msg(data->usb_dev, + usb_rcvbulkpipe(data->usb_dev, + data->bulk_in), + buffer, USBTMC_SIZE_IOBUFFER, + &actual, USBTMC_TIMEOUT); + + n++; + + if (rv < 0) { + dev_err(dev, "usb_bulk_msg returned %d\n", rv); + goto exit; + } + } while ((actual == max_size) && + (n < USBTMC_MAX_READS_TO_CLEAR_BULK_IN)); + + if (actual == max_size) { + dev_err(dev, "Couldn't clear device buffer within %d cycles\n", + USBTMC_MAX_READS_TO_CLEAR_BULK_IN); + rv = -EPERM; + goto exit; + } + + goto usbtmc_abort_bulk_in_status; + +exit: + kfree(buffer); + return rv; + +} + +static int usbtmc_ioctl_abort_bulk_out(struct usbtmc_device_data *data) +{ + struct device *dev; + u8 *buffer; + int rv; + int n; + + dev = &data->intf->dev; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + rv = usb_control_msg(data->usb_dev, + usb_rcvctrlpipe(data->usb_dev, 0), + USBTMC_REQUEST_INITIATE_ABORT_BULK_OUT, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, + data->bTag_last_write, data->bulk_out, + buffer, 2, USBTMC_TIMEOUT); + + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + + dev_dbg(dev, "INITIATE_ABORT_BULK_OUT returned %x\n", buffer[0]); + + if (buffer[0] != USBTMC_STATUS_SUCCESS) { + dev_err(dev, "INITIATE_ABORT_BULK_OUT returned %x\n", + buffer[0]); + rv = -EPERM; + goto exit; + } + + n = 0; + +usbtmc_abort_bulk_out_check_status: + rv = usb_control_msg(data->usb_dev, + usb_rcvctrlpipe(data->usb_dev, 0), + USBTMC_REQUEST_CHECK_ABORT_BULK_OUT_STATUS, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, + 0, data->bulk_out, buffer, 0x08, + USBTMC_TIMEOUT); + n++; + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + + dev_dbg(dev, "CHECK_ABORT_BULK_OUT returned %x\n", buffer[0]); + + if (buffer[0] == USBTMC_STATUS_SUCCESS) + goto usbtmc_abort_bulk_out_clear_halt; + + if ((buffer[0] == USBTMC_STATUS_PENDING) && + (n < USBTMC_MAX_READS_TO_CLEAR_BULK_IN)) + goto usbtmc_abort_bulk_out_check_status; + + rv = -EPERM; + goto exit; + +usbtmc_abort_bulk_out_clear_halt: + rv = usb_clear_halt(data->usb_dev, + usb_sndbulkpipe(data->usb_dev, data->bulk_out)); + + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + rv = 0; + +exit: + kfree(buffer); + return rv; +} + +/* + * Sends a REQUEST_DEV_DEP_MSG_IN message on the Bulk-IN endpoint. + * @transfer_size: number of bytes to request from the device. + * + * See the USBTMC specification, Table 4. + * + * Also updates bTag_last_write. + */ +static int send_request_dev_dep_msg_in(struct usbtmc_device_data *data, size_t transfer_size) +{ + int retval; + u8 *buffer; + int actual; + + buffer = kmalloc(USBTMC_HEADER_SIZE, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + /* Setup IO buffer for REQUEST_DEV_DEP_MSG_IN message + * Refer to class specs for details + */ + buffer[0] = 2; + buffer[1] = data->bTag; + buffer[2] = ~data->bTag; + buffer[3] = 0; /* Reserved */ + buffer[4] = transfer_size >> 0; + buffer[5] = transfer_size >> 8; + buffer[6] = transfer_size >> 16; + buffer[7] = transfer_size >> 24; + buffer[8] = data->TermCharEnabled * 2; + /* Use term character? */ + buffer[9] = data->TermChar; + buffer[10] = 0; /* Reserved */ + buffer[11] = 0; /* Reserved */ + + /* Send bulk URB */ + retval = usb_bulk_msg(data->usb_dev, + usb_sndbulkpipe(data->usb_dev, + data->bulk_out), + buffer, USBTMC_HEADER_SIZE, &actual, USBTMC_TIMEOUT); + + /* Store bTag (in case we need to abort) */ + data->bTag_last_write = data->bTag; + + /* Increment bTag -- and increment again if zero */ + data->bTag++; + if (!data->bTag) + data->bTag++; + + kfree(buffer); + if (retval < 0) { + dev_err(&data->intf->dev, "usb_bulk_msg in send_request_dev_dep_msg_in() returned %d\n", retval); + return retval; + } + + return 0; +} + +static ssize_t usbtmc_read(struct file *filp, char __user *buf, + size_t count, loff_t *f_pos) +{ + struct usbtmc_device_data *data; + struct device *dev; + u32 n_characters; + u8 *buffer; + int actual; + size_t done; + size_t remaining; + int retval; + size_t this_part; + + /* Get pointer to private data structure */ + data = filp->private_data; + dev = &data->intf->dev; + + buffer = kmalloc(USBTMC_SIZE_IOBUFFER, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + mutex_lock(&data->io_mutex); + if (data->zombie) { + retval = -ENODEV; + goto exit; + } + + if (data->rigol_quirk) { + dev_dbg(dev, "usb_bulk_msg_in: count(%zu)\n", count); + + retval = send_request_dev_dep_msg_in(data, count); + + if (retval < 0) { + if (data->auto_abort) + usbtmc_ioctl_abort_bulk_out(data); + goto exit; + } + } + + /* Loop until we have fetched everything we requested */ + remaining = count; + this_part = remaining; + done = 0; + + while (remaining > 0) { + if (!data->rigol_quirk) { + dev_dbg(dev, "usb_bulk_msg_in: remaining(%zu), count(%zu)\n", remaining, count); + + if (remaining > USBTMC_SIZE_IOBUFFER - USBTMC_HEADER_SIZE - 3) + this_part = USBTMC_SIZE_IOBUFFER - USBTMC_HEADER_SIZE - 3; + else + this_part = remaining; + + retval = send_request_dev_dep_msg_in(data, this_part); + if (retval < 0) { + dev_err(dev, "usb_bulk_msg returned %d\n", retval); + if (data->auto_abort) + usbtmc_ioctl_abort_bulk_out(data); + goto exit; + } + } + + /* Send bulk URB */ + retval = usb_bulk_msg(data->usb_dev, + usb_rcvbulkpipe(data->usb_dev, + data->bulk_in), + buffer, USBTMC_SIZE_IOBUFFER, &actual, + USBTMC_TIMEOUT); + + dev_dbg(dev, "usb_bulk_msg: retval(%u), done(%zu), remaining(%zu), actual(%d)\n", retval, done, remaining, actual); + + /* Store bTag (in case we need to abort) */ + data->bTag_last_read = data->bTag; + + if (retval < 0) { + dev_dbg(dev, "Unable to read data, error %d\n", retval); + if (data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } + + /* Parse header in first packet */ + if ((done == 0) || !data->rigol_quirk) { + /* Sanity checks for the header */ + if (actual < USBTMC_HEADER_SIZE) { + dev_err(dev, "Device sent too small first packet: %u < %u\n", actual, USBTMC_HEADER_SIZE); + if (data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } + + if (buffer[0] != 2) { + dev_err(dev, "Device sent reply with wrong MsgID: %u != 2\n", buffer[0]); + if (data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } + + if (buffer[1] != data->bTag_last_write) { + dev_err(dev, "Device sent reply with wrong bTag: %u != %u\n", buffer[1], data->bTag_last_write); + if (data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } + + /* How many characters did the instrument send? */ + n_characters = buffer[4] + + (buffer[5] << 8) + + (buffer[6] << 16) + + (buffer[7] << 24); + + if (n_characters > this_part) { + dev_err(dev, "Device wants to return more data than requested: %u > %zu\n", n_characters, count); + if (data->auto_abort) + usbtmc_ioctl_abort_bulk_in(data); + goto exit; + } + + /* Remove the USBTMC header */ + actual -= USBTMC_HEADER_SIZE; + + /* Check if the message is smaller than requested */ + if (data->rigol_quirk) { + if (remaining > n_characters) + remaining = n_characters; + /* Remove padding if it exists */ + if (actual > remaining) + actual = remaining; + } + else { + if (this_part > n_characters) + this_part = n_characters; + /* Remove padding if it exists */ + if (actual > this_part) + actual = this_part; + } + + dev_dbg(dev, "Bulk-IN header: N_characters(%u), bTransAttr(%u)\n", n_characters, buffer[8]); + + remaining -= actual; + + /* Terminate if end-of-message bit received from device */ + if ((buffer[8] & 0x01) && (actual >= n_characters)) + remaining = 0; + + dev_dbg(dev, "Bulk-IN header: remaining(%zu), buf(%p), buffer(%p) done(%zu)\n", remaining,buf,buffer,done); + + + /* Copy buffer to user space */ + if (copy_to_user(buf + done, &buffer[USBTMC_HEADER_SIZE], actual)) { + /* There must have been an addressing problem */ + retval = -EFAULT; + goto exit; + } + done += actual; + } + else { + if (actual > remaining) + actual = remaining; + + remaining -= actual; + + dev_dbg(dev, "Bulk-IN header cont: actual(%u), done(%zu), remaining(%zu), buf(%p), buffer(%p)\n", actual, done, remaining,buf,buffer); + + /* Copy buffer to user space */ + if (copy_to_user(buf + done, buffer, actual)) { + /* There must have been an addressing problem */ + retval = -EFAULT; + goto exit; + } + done += actual; + } + } + + /* Update file position value */ + *f_pos = *f_pos + done; + retval = done; + +exit: + mutex_unlock(&data->io_mutex); + kfree(buffer); + return retval; +} + +static ssize_t usbtmc_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + struct usbtmc_device_data *data; + u8 *buffer; + int retval; + int actual; + unsigned long int n_bytes; + int remaining; + int done; + int this_part; + + data = filp->private_data; + + buffer = kmalloc(USBTMC_SIZE_IOBUFFER, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + mutex_lock(&data->io_mutex); + if (data->zombie) { + retval = -ENODEV; + goto exit; + } + + remaining = count; + done = 0; + + while (remaining > 0) { + if (remaining > USBTMC_SIZE_IOBUFFER - USBTMC_HEADER_SIZE) { + this_part = USBTMC_SIZE_IOBUFFER - USBTMC_HEADER_SIZE; + buffer[8] = 0; + } else { + this_part = remaining; + buffer[8] = 1; + } + + /* Setup IO buffer for DEV_DEP_MSG_OUT message */ + buffer[0] = 1; + buffer[1] = data->bTag; + buffer[2] = ~data->bTag; + buffer[3] = 0; /* Reserved */ + buffer[4] = this_part >> 0; + buffer[5] = this_part >> 8; + buffer[6] = this_part >> 16; + buffer[7] = this_part >> 24; + /* buffer[8] is set above... */ + buffer[9] = 0; /* Reserved */ + buffer[10] = 0; /* Reserved */ + buffer[11] = 0; /* Reserved */ + + if (copy_from_user(&buffer[USBTMC_HEADER_SIZE], buf + done, this_part)) { + retval = -EFAULT; + goto exit; + } + + n_bytes = roundup(USBTMC_HEADER_SIZE + this_part, 4); + memset(buffer + USBTMC_HEADER_SIZE + this_part, 0, n_bytes - (USBTMC_HEADER_SIZE + this_part)); + + do { + retval = usb_bulk_msg(data->usb_dev, + usb_sndbulkpipe(data->usb_dev, + data->bulk_out), + buffer, n_bytes, + &actual, USBTMC_TIMEOUT); + if (retval != 0) + break; + n_bytes -= actual; + } while (n_bytes); + + data->bTag_last_write = data->bTag; + data->bTag++; + + if (!data->bTag) + data->bTag++; + + if (retval < 0) { + dev_err(&data->intf->dev, + "Unable to send data, error %d\n", retval); + if (data->auto_abort) + usbtmc_ioctl_abort_bulk_out(data); + goto exit; + } + + remaining -= this_part; + done += this_part; + } + + retval = count; +exit: + mutex_unlock(&data->io_mutex); + kfree(buffer); + return retval; +} + +static int usbtmc_ioctl_clear(struct usbtmc_device_data *data) +{ + struct usb_host_interface *current_setting; + struct usb_endpoint_descriptor *desc; + struct device *dev; + u8 *buffer; + int rv; + int n; + int actual; + int max_size; + + dev = &data->intf->dev; + + dev_dbg(dev, "Sending INITIATE_CLEAR request\n"); + + buffer = kmalloc(USBTMC_SIZE_IOBUFFER, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + rv = usb_control_msg(data->usb_dev, + usb_rcvctrlpipe(data->usb_dev, 0), + USBTMC_REQUEST_INITIATE_CLEAR, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, 0, buffer, 1, USBTMC_TIMEOUT); + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + + dev_dbg(dev, "INITIATE_CLEAR returned %x\n", buffer[0]); + + if (buffer[0] != USBTMC_STATUS_SUCCESS) { + dev_err(dev, "INITIATE_CLEAR returned %x\n", buffer[0]); + rv = -EPERM; + goto exit; + } + + max_size = 0; + current_setting = data->intf->cur_altsetting; + for (n = 0; n < current_setting->desc.bNumEndpoints; n++) { + desc = ¤t_setting->endpoint[n].desc; + if (desc->bEndpointAddress == data->bulk_in) + max_size = usb_endpoint_maxp(desc); + } + + if (max_size == 0) { + dev_err(dev, "Couldn't get wMaxPacketSize\n"); + rv = -EPERM; + goto exit; + } + + dev_dbg(dev, "wMaxPacketSize is %d\n", max_size); + + n = 0; + +usbtmc_clear_check_status: + + dev_dbg(dev, "Sending CHECK_CLEAR_STATUS request\n"); + + rv = usb_control_msg(data->usb_dev, + usb_rcvctrlpipe(data->usb_dev, 0), + USBTMC_REQUEST_CHECK_CLEAR_STATUS, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, 0, buffer, 2, USBTMC_TIMEOUT); + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + + dev_dbg(dev, "CHECK_CLEAR_STATUS returned %x\n", buffer[0]); + + if (buffer[0] == USBTMC_STATUS_SUCCESS) + goto usbtmc_clear_bulk_out_halt; + + if (buffer[0] != USBTMC_STATUS_PENDING) { + dev_err(dev, "CHECK_CLEAR_STATUS returned %x\n", buffer[0]); + rv = -EPERM; + goto exit; + } + + if (buffer[1] == 1) + do { + dev_dbg(dev, "Reading from bulk in EP\n"); + + rv = usb_bulk_msg(data->usb_dev, + usb_rcvbulkpipe(data->usb_dev, + data->bulk_in), + buffer, USBTMC_SIZE_IOBUFFER, + &actual, USBTMC_TIMEOUT); + n++; + + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", + rv); + goto exit; + } + } while ((actual == max_size) && + (n < USBTMC_MAX_READS_TO_CLEAR_BULK_IN)); + + if (actual == max_size) { + dev_err(dev, "Couldn't clear device buffer within %d cycles\n", + USBTMC_MAX_READS_TO_CLEAR_BULK_IN); + rv = -EPERM; + goto exit; + } + + goto usbtmc_clear_check_status; + +usbtmc_clear_bulk_out_halt: + + rv = usb_clear_halt(data->usb_dev, + usb_sndbulkpipe(data->usb_dev, data->bulk_out)); + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + rv = 0; + +exit: + kfree(buffer); + return rv; +} + +static int usbtmc_ioctl_clear_out_halt(struct usbtmc_device_data *data) +{ + int rv; + + rv = usb_clear_halt(data->usb_dev, + usb_sndbulkpipe(data->usb_dev, data->bulk_out)); + + if (rv < 0) { + dev_err(&data->usb_dev->dev, "usb_control_msg returned %d\n", + rv); + return rv; + } + return 0; +} + +static int usbtmc_ioctl_clear_in_halt(struct usbtmc_device_data *data) +{ + int rv; + + rv = usb_clear_halt(data->usb_dev, + usb_rcvbulkpipe(data->usb_dev, data->bulk_in)); + + if (rv < 0) { + dev_err(&data->usb_dev->dev, "usb_control_msg returned %d\n", + rv); + return rv; + } + return 0; +} + +static int get_capabilities(struct usbtmc_device_data *data) +{ + struct device *dev = &data->usb_dev->dev; + char *buffer; + int rv = 0; + + buffer = kmalloc(0x18, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + rv = usb_control_msg(data->usb_dev, usb_rcvctrlpipe(data->usb_dev, 0), + USBTMC_REQUEST_GET_CAPABILITIES, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, 0, buffer, 0x18, USBTMC_TIMEOUT); + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto err_out; + } + + dev_dbg(dev, "GET_CAPABILITIES returned %x\n", buffer[0]); + if (buffer[0] != USBTMC_STATUS_SUCCESS) { + dev_err(dev, "GET_CAPABILITIES returned %x\n", buffer[0]); + rv = -EPERM; + goto err_out; + } + dev_dbg(dev, "Interface capabilities are %x\n", buffer[4]); + dev_dbg(dev, "Device capabilities are %x\n", buffer[5]); + dev_dbg(dev, "USB488 interface capabilities are %x\n", buffer[14]); + dev_dbg(dev, "USB488 device capabilities are %x\n", buffer[15]); + + data->capabilities.interface_capabilities = buffer[4]; + data->capabilities.device_capabilities = buffer[5]; + data->capabilities.usb488_interface_capabilities = buffer[14]; + data->capabilities.usb488_device_capabilities = buffer[15]; + rv = 0; + +err_out: + kfree(buffer); + return rv; +} + +#define capability_attribute(name) \ +static ssize_t name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usbtmc_device_data *data = usb_get_intfdata(intf); \ + \ + return sprintf(buf, "%d\n", data->capabilities.name); \ +} \ +static DEVICE_ATTR_RO(name) + +capability_attribute(interface_capabilities); +capability_attribute(device_capabilities); +capability_attribute(usb488_interface_capabilities); +capability_attribute(usb488_device_capabilities); + +static struct attribute *capability_attrs[] = { + &dev_attr_interface_capabilities.attr, + &dev_attr_device_capabilities.attr, + &dev_attr_usb488_interface_capabilities.attr, + &dev_attr_usb488_device_capabilities.attr, + NULL, +}; + +static struct attribute_group capability_attr_grp = { + .attrs = capability_attrs, +}; + +static ssize_t TermChar_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usbtmc_device_data *data = usb_get_intfdata(intf); + + return sprintf(buf, "%c\n", data->TermChar); +} + +static ssize_t TermChar_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usbtmc_device_data *data = usb_get_intfdata(intf); + + if (count < 1) + return -EINVAL; + data->TermChar = buf[0]; + return count; +} +static DEVICE_ATTR_RW(TermChar); + +#define data_attribute(name) \ +static ssize_t name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usbtmc_device_data *data = usb_get_intfdata(intf); \ + \ + return sprintf(buf, "%d\n", data->name); \ +} \ +static ssize_t name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usbtmc_device_data *data = usb_get_intfdata(intf); \ + ssize_t result; \ + unsigned val; \ + \ + result = sscanf(buf, "%u\n", &val); \ + if (result != 1) \ + result = -EINVAL; \ + data->name = val; \ + if (result < 0) \ + return result; \ + else \ + return count; \ +} \ +static DEVICE_ATTR_RW(name) + +data_attribute(TermCharEnabled); +data_attribute(auto_abort); + +static struct attribute *data_attrs[] = { + &dev_attr_TermChar.attr, + &dev_attr_TermCharEnabled.attr, + &dev_attr_auto_abort.attr, + NULL, +}; + +static struct attribute_group data_attr_grp = { + .attrs = data_attrs, +}; + +static int usbtmc_ioctl_indicator_pulse(struct usbtmc_device_data *data) +{ + struct device *dev; + u8 *buffer; + int rv; + + dev = &data->intf->dev; + + buffer = kmalloc(2, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + rv = usb_control_msg(data->usb_dev, + usb_rcvctrlpipe(data->usb_dev, 0), + USBTMC_REQUEST_INDICATOR_PULSE, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, 0, buffer, 0x01, USBTMC_TIMEOUT); + + if (rv < 0) { + dev_err(dev, "usb_control_msg returned %d\n", rv); + goto exit; + } + + dev_dbg(dev, "INDICATOR_PULSE returned %x\n", buffer[0]); + + if (buffer[0] != USBTMC_STATUS_SUCCESS) { + dev_err(dev, "INDICATOR_PULSE returned %x\n", buffer[0]); + rv = -EPERM; + goto exit; + } + rv = 0; + +exit: + kfree(buffer); + return rv; +} + +static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct usbtmc_device_data *data; + int retval = -EBADRQC; + + data = file->private_data; + mutex_lock(&data->io_mutex); + if (data->zombie) { + retval = -ENODEV; + goto skip_io_on_zombie; + } + + switch (cmd) { + case USBTMC_IOCTL_CLEAR_OUT_HALT: + retval = usbtmc_ioctl_clear_out_halt(data); + break; + + case USBTMC_IOCTL_CLEAR_IN_HALT: + retval = usbtmc_ioctl_clear_in_halt(data); + break; + + case USBTMC_IOCTL_INDICATOR_PULSE: + retval = usbtmc_ioctl_indicator_pulse(data); + break; + + case USBTMC_IOCTL_CLEAR: + retval = usbtmc_ioctl_clear(data); + break; + + case USBTMC_IOCTL_ABORT_BULK_OUT: + retval = usbtmc_ioctl_abort_bulk_out(data); + break; + + case USBTMC_IOCTL_ABORT_BULK_IN: + retval = usbtmc_ioctl_abort_bulk_in(data); + break; + } + +skip_io_on_zombie: + mutex_unlock(&data->io_mutex); + return retval; +} + +static const struct file_operations fops = { + .owner = THIS_MODULE, + .read = usbtmc_read, + .write = usbtmc_write, + .open = usbtmc_open, + .release = usbtmc_release, + .unlocked_ioctl = usbtmc_ioctl, + .llseek = default_llseek, +}; + +static struct usb_class_driver usbtmc_class = { + .name = "usbtmc%d", + .fops = &fops, + .minor_base = USBTMC_MINOR_BASE, +}; + + +static int usbtmc_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usbtmc_device_data *data; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int n; + int retcode; + + dev_dbg(&intf->dev, "%s called\n", __func__); + + data = devm_kzalloc(&intf->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(&intf->dev, "Unable to allocate kernel memory\n"); + return -ENOMEM; + } + + data->intf = intf; + data->id = id; + data->usb_dev = usb_get_dev(interface_to_usbdev(intf)); + usb_set_intfdata(intf, data); + kref_init(&data->kref); + mutex_init(&data->io_mutex); + data->zombie = 0; + + /* Determine if it is a Rigol or not */ + data->rigol_quirk = 0; + dev_dbg(&intf->dev, "Trying to find if device Vendor 0x%04X Product 0x%04X has the RIGOL quirk\n", + le16_to_cpu(data->usb_dev->descriptor.idVendor), + le16_to_cpu(data->usb_dev->descriptor.idProduct)); + for(n = 0; usbtmc_id_quirk[n].idVendor > 0; n++) { + if ((usbtmc_id_quirk[n].idVendor == le16_to_cpu(data->usb_dev->descriptor.idVendor)) && + (usbtmc_id_quirk[n].idProduct == le16_to_cpu(data->usb_dev->descriptor.idProduct))) { + dev_dbg(&intf->dev, "Setting this device as having the RIGOL quirk\n"); + data->rigol_quirk = 1; + break; + } + } + + /* Initialize USBTMC bTag and other fields */ + data->bTag = 1; + data->TermCharEnabled = 0; + data->TermChar = '\n'; + + /* USBTMC devices have only one setting, so use that */ + iface_desc = data->intf->cur_altsetting; + + /* Find bulk in endpoint */ + for (n = 0; n < iface_desc->desc.bNumEndpoints; n++) { + endpoint = &iface_desc->endpoint[n].desc; + + if (usb_endpoint_is_bulk_in(endpoint)) { + data->bulk_in = endpoint->bEndpointAddress; + dev_dbg(&intf->dev, "Found bulk in endpoint at %u\n", + data->bulk_in); + break; + } + } + + /* Find bulk out endpoint */ + for (n = 0; n < iface_desc->desc.bNumEndpoints; n++) { + endpoint = &iface_desc->endpoint[n].desc; + + if (usb_endpoint_is_bulk_out(endpoint)) { + data->bulk_out = endpoint->bEndpointAddress; + dev_dbg(&intf->dev, "Found Bulk out endpoint at %u\n", + data->bulk_out); + break; + } + } + + retcode = get_capabilities(data); + if (retcode) + dev_err(&intf->dev, "can't read capabilities\n"); + else + retcode = sysfs_create_group(&intf->dev.kobj, + &capability_attr_grp); + + retcode = sysfs_create_group(&intf->dev.kobj, &data_attr_grp); + + retcode = usb_register_dev(intf, &usbtmc_class); + if (retcode) { + dev_err(&intf->dev, "Not able to get a minor" + " (base %u, slice default): %d\n", USBTMC_MINOR_BASE, + retcode); + goto error_register; + } + dev_dbg(&intf->dev, "Using minor number %d\n", intf->minor); + + return 0; + +error_register: + sysfs_remove_group(&intf->dev.kobj, &capability_attr_grp); + sysfs_remove_group(&intf->dev.kobj, &data_attr_grp); + kref_put(&data->kref, usbtmc_delete); + return retcode; +} + +static void usbtmc_disconnect(struct usb_interface *intf) +{ + struct usbtmc_device_data *data; + + dev_dbg(&intf->dev, "usbtmc_disconnect called\n"); + + data = usb_get_intfdata(intf); + usb_deregister_dev(intf, &usbtmc_class); + sysfs_remove_group(&intf->dev.kobj, &capability_attr_grp); + sysfs_remove_group(&intf->dev.kobj, &data_attr_grp); + mutex_lock(&data->io_mutex); + data->zombie = 1; + mutex_unlock(&data->io_mutex); + kref_put(&data->kref, usbtmc_delete); +} + +static int usbtmc_suspend(struct usb_interface *intf, pm_message_t message) +{ + /* this driver does not have pending URBs */ + return 0; +} + +static int usbtmc_resume(struct usb_interface *intf) +{ + return 0; +} + +static struct usb_driver usbtmc_driver = { + .name = "usbtmc", + .id_table = usbtmc_devices, + .probe = usbtmc_probe, + .disconnect = usbtmc_disconnect, + .suspend = usbtmc_suspend, + .resume = usbtmc_resume, +}; + +module_usb_driver(usbtmc_driver); + +MODULE_LICENSE("GPL"); |
