summaryrefslogtreecommitdiffstats
path: root/drivers/usb/class
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/class')
-rw-r--r--drivers/usb/class/Kconfig11
-rw-r--r--drivers/usb/class/Makefile1
-rw-r--r--drivers/usb/class/cdc-acm.c103
-rw-r--r--drivers/usb/class/cdc-acm.h7
-rw-r--r--drivers/usb/class/cdc-wdm.c740
5 files changed, 812 insertions, 50 deletions
diff --git a/drivers/usb/class/Kconfig b/drivers/usb/class/Kconfig
index 3a9102d2591..66f17ed88cb 100644
--- a/drivers/usb/class/Kconfig
+++ b/drivers/usb/class/Kconfig
@@ -29,3 +29,14 @@ 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"
+ depends on USB
+ ---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.
+
diff --git a/drivers/usb/class/Makefile b/drivers/usb/class/Makefile
index cc391e6c2af..535d59a3060 100644
--- a/drivers/usb/class/Makefile
+++ b/drivers/usb/class/Makefile
@@ -5,3 +5,4 @@
obj-$(CONFIG_USB_ACM) += cdc-acm.o
obj-$(CONFIG_USB_PRINTER) += usblp.o
+obj-$(CONFIG_USB_WDM) += cdc-wdm.o
diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c
index 0147ea39340..63c34043b4d 100644
--- a/drivers/usb/class/cdc-acm.c
+++ b/drivers/usb/class/cdc-acm.c
@@ -31,6 +31,7 @@
* 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
+ * v0.26 - multiple write urbs, writesize increased
*/
/*
@@ -72,7 +73,7 @@
/*
* Version Information
*/
-#define DRIVER_VERSION "v0.25"
+#define DRIVER_VERSION "v0.26"
#define DRIVER_AUTHOR "Armin Fuerst, Pavel Machek, Johannes Erdfelt, Vojtech Pavlik, David Kubicek"
#define DRIVER_DESC "USB Abstract Control Model driver for USB modems and ISDN adapters"
@@ -118,7 +119,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,11 +133,6 @@ 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;
@@ -156,26 +152,22 @@ static inline int acm_wb_is_used(struct acm *acm, int wbn)
/*
* Finish write.
*/
-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;
+ wb->use = 0;
spin_unlock_irqrestore(&acm->write_lock, flags);
}
/*
* Poke write.
*/
-static int acm_write_start(struct acm *acm)
+static int acm_write_start(struct acm *acm, int wbn)
{
unsigned long flags;
- int wbn;
struct acm_wb *wb;
int rc;
@@ -190,24 +182,24 @@ static int acm_write_start(struct acm *acm)
return 0; /* A white lie */
}
- 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;
+ if(acm_wb_is_avail(acm) <= 1)
+ 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;
+ wb->urb->transfer_buffer = wb->buf;
+ wb->urb->transfer_dma = wb->dmah;
+ wb->urb->transfer_buffer_length = wb->len;
+ wb->urb->dev = acm->dev;
- if ((rc = usb_submit_urb(acm->writeurb, GFP_ATOMIC)) < 0) {
+ if ((rc = usb_submit_urb(wb->urb, GFP_ATOMIC)) < 0) {
dbg("usb_submit_urb(write bulk) failed: %d", rc);
- acm_write_done(acm);
+ acm_write_done(acm, wb);
}
return rc;
}
@@ -268,10 +260,10 @@ 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);
+ dbg("%s - urb shutting down with status: %d", __func__, status);
return;
default:
- dbg("%s - nonzero urb status received: %d", __FUNCTION__, status);
+ dbg("%s - nonzero urb status received: %d", __func__, status);
goto exit;
}
@@ -288,7 +280,7 @@ static void acm_ctrl_irq(struct urb *urb)
case USB_CDC_NOTIFY_SERIAL_STATE:
- newctrl = le16_to_cpu(get_unaligned((__le16 *) data));
+ newctrl = get_unaligned_le16(data);
if (acm->tty && !acm->clocal && (acm->ctrlin & ~newctrl & ACM_CTRL_DCD)) {
dbg("calling hangup");
@@ -315,7 +307,7 @@ exit:
retval = usb_submit_urb (urb, GFP_ATOMIC);
if (retval)
err ("%s - usb_submit_urb failed with result %d",
- __FUNCTION__, retval);
+ __func__, retval);
}
/* data interface returns incoming bytes, or we got unthrottled */
@@ -450,12 +442,13 @@ urbs:
/* data interface wrote those outgoing bytes */
static void acm_write_bulk(struct urb *urb)
{
- struct acm *acm = (struct acm *)urb->context;
+ struct acm *acm;
+ struct acm_wb *wb = urb->context;
dbg("Entering acm_write_bulk with status %d", urb->status);
- acm_write_done(acm);
- acm_write_start(acm);
+ acm = wb->instance;
+ acm_write_done(acm, wb);
if (ACM_READY(acm))
schedule_work(&acm->work);
}
@@ -489,6 +482,7 @@ static int acm_tty_open(struct tty_struct *tty, struct file *filp)
else
rv = 0;
+ set_bit(TTY_NO_WRITE_SPLIT, &tty->flags);
tty->driver_data = acm;
acm->tty = tty;
@@ -556,7 +550,8 @@ static void acm_tty_unregister(struct acm *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 < ACM_NW; i++)
+ usb_free_urb(acm->wb[i].urb);
for (i = 0; i < nr; i++)
usb_free_urb(acm->ru[i].urb);
kfree(acm->country_codes);
@@ -577,7 +572,8 @@ static void acm_tty_close(struct tty_struct *tty, struct file *filp)
if (acm->dev) {
acm_set_control(acm, acm->ctrlout = 0);
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 < nr; i++)
usb_kill_urb(acm->ru[i].urb);
usb_autopm_put_interface(acm->control);
@@ -605,7 +601,6 @@ static int acm_tty_write(struct tty_struct *tty, const unsigned char *buf, int c
spin_lock_irqsave(&acm->write_lock, flags);
if ((wbn = acm_wb_alloc(acm)) < 0) {
spin_unlock_irqrestore(&acm->write_lock, flags);
- acm_write_start(acm);
return 0;
}
wb = &acm->wb[wbn];
@@ -616,7 +611,7 @@ static int acm_tty_write(struct tty_struct *tty, const unsigned char *buf, int c
wb->len = count;
spin_unlock_irqrestore(&acm->write_lock, flags);
- if ((stat = acm_write_start(acm)) < 0)
+ if ((stat = acm_write_start(acm, wbn)) < 0)
return stat;
return count;
}
@@ -809,7 +804,7 @@ static int acm_probe (struct usb_interface *intf,
{
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;
@@ -886,9 +881,13 @@ static int acm_probe (struct usb_interface *intf,
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]);
+ /* 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:
@@ -976,7 +975,7 @@ skip_normal_probe:
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);
+ acm->writesize = le16_to_cpu(epwrite->wMaxPacketSize) * 20;
acm->control = control_interface;
acm->data = data_interface;
acm->minor = minor;
@@ -1031,10 +1030,19 @@ skip_normal_probe:
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;
+ for(i = 0; i < ACM_NW; i++)
+ {
+ struct acm_wb *snd = &(acm->wb[i]);
+
+ if (!(snd->urb = usb_alloc_urb(0, GFP_KERNEL))) {
+ dev_dbg(&intf->dev, "out of memory (write urbs usb_alloc_urb)");
+ goto alloc_fail7;
+ }
+
+ 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);
@@ -1070,10 +1078,6 @@ skip_countries:
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);
@@ -1091,7 +1095,8 @@ skip_countries:
return 0;
alloc_fail8:
- usb_free_urb(acm->writeurb);
+ for (i = 0; i < ACM_NW; i++)
+ usb_free_urb(acm->wb[i].urb);
alloc_fail7:
for (i = 0; i < num_rx_buf; i++)
usb_buffer_free(usb_dev, acm->readsize, acm->rb[i].base, acm->rb[i].dma);
@@ -1115,7 +1120,8 @@ static void stop_data_traffic(struct acm *acm)
tasklet_disable(&acm->urb_task);
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);
@@ -1242,6 +1248,9 @@ 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 */
+ },
/* control interfaces with various AT-command sets */
{ USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_ACM,
diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h
index 8df6a57dcf9..046e064b033 100644
--- a/drivers/usb/class/cdc-acm.h
+++ b/drivers/usb/class/cdc-acm.h
@@ -59,7 +59,7 @@
* 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,6 +67,8 @@ struct acm_wb {
dma_addr_t dmah;
int len;
int use;
+ struct urb *urb;
+ struct acm *instance;
};
struct acm_rb {
@@ -88,7 +90,7 @@ struct acm {
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 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 */
@@ -103,7 +105,6 @@ struct acm {
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 */
spinlock_t write_lock;
diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c
new file mode 100644
index 00000000000..731db051070
--- /dev/null
+++ b/drivers/usb/class/cdc-wdm.c
@@ -0,0 +1,740 @@
+/*
+ * cdc-wdm.c
+ *
+ * This driver supports USB CDC WCM Device Management.
+ *
+ * Copyright (c) 2007-2008 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/slab.h>
+#include <linux/module.h>
+#include <linux/smp_lock.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>
+
+/*
+ * Version Information
+ */
+#define DRIVER_VERSION "v0.02"
+#define DRIVER_AUTHOR "Oliver Neukum"
+
+static 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
+ },
+ { }
+};
+
+#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_MAX 16
+
+
+static DEFINE_MUTEX(wdm_mutex);
+
+/* --- 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;
+ u16 bMaxPacketSize0;
+ __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;
+};
+
+static struct usb_driver wdm_driver;
+
+/* --- 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);
+ clear_bit(WDM_IN_USE, &desc->flags);
+ kfree(desc->outbuf);
+ wake_up(&desc->wait);
+}
+
+static void wdm_in_callback(struct urb *urb)
+{
+ struct wdm_device *desc = urb->context;
+ int status = urb->status;
+
+ spin_lock(&desc->iuspin);
+
+ if (status) {
+ switch (status) {
+ case -ENOENT:
+ dev_dbg(&desc->intf->dev,
+ "nonzero urb status received: -ENOENT");
+ break;
+ case -ECONNRESET:
+ dev_dbg(&desc->intf->dev,
+ "nonzero urb status received: -ECONNRESET");
+ break;
+ case -ESHUTDOWN:
+ dev_dbg(&desc->intf->dev,
+ "nonzero urb status received: -ESHUTDOWN");
+ break;
+ case -EPIPE:
+ err("nonzero urb status received: -EPIPE");
+ break;
+ default:
+ err("Unexpected error %d", status);
+ break;
+ }
+ }
+
+ desc->rerr = status;
+ desc->reslength = urb->actual_length;
+ memmove(desc->ubuf + desc->length, desc->inbuf, desc->reslength);
+ desc->length += desc->reslength;
+ 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 status = urb->status;
+ struct wdm_device *desc;
+ struct usb_ctrlrequest *req;
+ struct usb_cdc_notification *dr;
+
+ desc = urb->context;
+ req = desc->irq;
+ 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);
+ err("Stall on int endpoint");
+ goto sw; /* halt is cleared in work */
+ default:
+ err("nonzero urb status received: %d", status);
+ break;
+ }
+ }
+
+ if (urb->actual_length < sizeof(struct usb_cdc_notification)) {
+ err("wdm_int_callback - %d bytes", 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;
+ default:
+ clear_bit(WDM_POLL_RUNNING, &desc->flags);
+ err("unknown notification %d received: index %d len %d",
+ dr->bNotificationType, dr->wIndex, dr->wLength);
+ goto exit;
+ }
+
+ req->bRequestType = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE);
+ req->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE;
+ req->wValue = 0;
+ req->wIndex = desc->inum;
+ req->wLength = cpu_to_le16(desc->bMaxPacketSize0);
+
+ usb_fill_control_urb(
+ desc->response,
+ interface_to_usbdev(desc->intf),
+ /* using common endpoint 0 */
+ usb_rcvctrlpipe(interface_to_usbdev(desc->intf), 0),
+ (unsigned char *)req,
+ desc->inbuf,
+ desc->bMaxPacketSize0,
+ wdm_in_callback,
+ desc
+ );
+ desc->response->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ spin_lock(&desc->iuspin);
+ clear_bit(WDM_READ, &desc->flags);
+ if (!test_bit(WDM_DISCONNECTING, &desc->flags)) {
+ 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) {
+ if (rv == -EPERM)
+ return;
+ if (rv == -ENOMEM) {
+sw:
+ rv = schedule_work(&desc->rxwork);
+ if (rv)
+ err("Cannot schedule work");
+ }
+ }
+exit:
+ rv = usb_submit_urb(urb, GFP_ATOMIC);
+ if (rv)
+ err("%s - usb_submit_urb failed with result %d",
+ __func__, rv);
+
+}
+
+static void kill_urbs(struct wdm_device *desc)
+{
+ 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)
+{
+ usb_buffer_free(interface_to_usbdev(desc->intf),
+ desc->wMaxPacketSize,
+ desc->sbuf,
+ desc->validity->transfer_dma);
+ usb_buffer_free(interface_to_usbdev(desc->intf),
+ desc->wMaxPacketSize,
+ desc->inbuf,
+ desc->response->transfer_dma);
+ 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;
+
+ r = mutex_lock_interruptible(&desc->wlock); /* concurrent writes */
+ rv = -ERESTARTSYS;
+ if (r)
+ goto outnl;
+
+ r = wait_event_interruptible(desc->wait, !test_bit(WDM_IN_USE,
+ &desc->flags));
+ if (r < 0)
+ goto out;
+
+ if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
+ rv = -ENODEV;
+ goto out;
+ }
+
+ desc->outbuf = buf = kmalloc(count, GFP_KERNEL);
+ if (!buf) {
+ rv = -ENOMEM;
+ goto out;
+ }
+
+ r = copy_from_user(buf, buffer, count);
+ if (r > 0) {
+ kfree(buf);
+ rv = -EFAULT;
+ 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);
+
+ rv = usb_submit_urb(desc->command, GFP_KERNEL);
+ if (rv < 0) {
+ kfree(buf);
+ clear_bit(WDM_IN_USE, &desc->flags);
+ } else {
+ dev_dbg(&desc->intf->dev, "Tx URB has been submitted index=%d",
+ req->wIndex);
+ }
+out:
+ mutex_unlock(&desc->wlock);
+outnl:
+ return rv < 0 ? rv : count;
+}
+
+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;
+
+ if (desc->length == 0) {
+ desc->read = 0;
+retry:
+ i++;
+ rv = wait_event_interruptible(desc->wait,
+ test_bit(WDM_READ, &desc->flags));
+
+ if (rv < 0) {
+ rv = -ERESTARTSYS;
+ goto err;
+ }
+
+ spin_lock_irq(&desc->iuspin);
+
+ if (desc->rerr) { /* read completed, error happened */
+ int t = desc->rerr;
+ desc->rerr = 0;
+ spin_unlock_irq(&desc->iuspin);
+ err("reading had resulted in %d", t);
+ 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 */
+ spin_unlock_irq(&desc->iuspin);
+ goto retry;
+ }
+ clear_bit(WDM_READ, &desc->flags);
+ spin_unlock_irq(&desc->iuspin);
+ }
+
+ cntr = count > desc->length ? desc->length : count;
+ rv = copy_to_user(buffer, desc->ubuf, cntr);
+ if (rv > 0) {
+ rv = -EFAULT;
+ goto err;
+ }
+
+ for (i = 0; i < desc->length - cntr; i++)
+ desc->ubuf[i] = desc->ubuf[i + cntr];
+
+ desc->length -= cntr;
+ rv = cntr;
+
+err:
+ mutex_unlock(&desc->rlock);
+ if (rv < 0)
+ err("wdm_read: exit error");
+ 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));
+ if (desc->werr < 0)
+ err("Error in flush path: %d", desc->werr);
+
+ return 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 = 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);
+ intf = usb_find_interface(&wdm_driver, minor);
+ if (!intf)
+ goto out;
+
+ desc = usb_get_intfdata(intf);
+ if (test_bit(WDM_DISCONNECTING, &desc->flags))
+ goto out;
+
+ desc->count++;
+ file->private_data = desc;
+
+ rv = usb_submit_urb(desc->validity, GFP_KERNEL);
+
+ if (rv < 0) {
+ desc->count--;
+ err("Error submitting int urb - %d", rv);
+ goto out;
+ }
+ rv = 0;
+
+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);
+ desc->count--;
+ if (!desc->count) {
+ dev_dbg(&desc->intf->dev, "wdm_release: cleanup");
+ kill_urbs(desc);
+ }
+ mutex_unlock(&wdm_mutex);
+ return 0;
+}
+
+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
+};
+
+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;
+
+ spin_lock_irqsave(&desc->iuspin, flags);
+ if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
+ spin_unlock_irqrestore(&desc->iuspin, flags);
+ } else {
+ spin_unlock_irqrestore(&desc->iuspin, flags);
+ rv = usb_submit_urb(desc->response, GFP_KERNEL);
+ if (rv < 0 && rv != -EPERM) {
+ spin_lock_irqsave(&desc->iuspin, flags);
+ if (!test_bit(WDM_DISCONNECTING, &desc->flags))
+ schedule_work(&desc->rxwork);
+ spin_unlock_irqrestore(&desc->iuspin, flags);
+ }
+ }
+}
+
+/* --- hotplug --- */
+
+static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ int rv = -EINVAL;
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct wdm_device *desc;
+ 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 = 0;
+
+ if (!buffer)
+ goto out;
+
+ while (buflen > 0) {
+ if (buffer [1] != USB_DT_CS_INTERFACE) {
+ err("skipping garbage");
+ 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:
+ err("Ignoring extra header, type %d, length %d",
+ buffer[2], buffer[0]);
+ break;
+ }
+next_desc:
+ buflen -= buffer[0];
+ buffer += buffer[0];
+ }
+
+ rv = -ENOMEM;
+ desc = kzalloc(sizeof(struct wdm_device), GFP_KERNEL);
+ if (!desc)
+ goto out;
+ mutex_init(&desc->wlock);
+ mutex_init(&desc->rlock);
+ spin_lock_init(&desc->iuspin);
+ init_waitqueue_head(&desc->wait);
+ desc->wMaxCommand = maxcom;
+ desc->inum = cpu_to_le16((u16)intf->cur_altsetting->desc.bInterfaceNumber);
+ desc->intf = intf;
+ INIT_WORK(&desc->rxwork, wdm_rxwork);
+
+ iface = &intf->altsetting[0];
+ ep = &iface->endpoint[0].desc;
+ if (!usb_endpoint_is_int_in(ep)) {
+ rv = -EINVAL;
+ goto err;
+ }
+
+ desc->wMaxPacketSize = le16_to_cpu(ep->wMaxPacketSize);
+ desc->bMaxPacketSize0 = udev->descriptor.bMaxPacketSize0;
+
+ 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 = usb_buffer_alloc(interface_to_usbdev(intf),
+ desc->wMaxPacketSize,
+ GFP_KERNEL,
+ &desc->validity->transfer_dma);
+ if (!desc->sbuf)
+ goto err;
+
+ desc->inbuf = usb_buffer_alloc(interface_to_usbdev(intf),
+ desc->bMaxPacketSize0,
+ GFP_KERNEL,
+ &desc->response->transfer_dma);
+ if (!desc->inbuf)
+ goto err2;
+
+ 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->validity->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ usb_set_intfdata(intf, desc);
+ rv = usb_register_dev(intf, &wdm_class);
+ dev_info(&intf->dev, "cdc-wdm%d: USB WDM device\n",
+ intf->minor - WDM_MINOR_BASE);
+ if (rv < 0)
+ goto err;
+out:
+ return rv;
+err2:
+ usb_buffer_free(interface_to_usbdev(desc->intf),
+ desc->wMaxPacketSize,
+ desc->sbuf,
+ desc->validity->transfer_dma);
+err:
+ free_urbs(desc);
+ kfree(desc->ubuf);
+ kfree(desc->orq);
+ kfree(desc->irq);
+ kfree(desc);
+ return rv;
+}
+
+static void wdm_disconnect(struct usb_interface *intf)
+{
+ struct wdm_device *desc;
+ unsigned long flags;
+
+ usb_deregister_dev(intf, &wdm_class);
+ mutex_lock(&wdm_mutex);
+ desc = usb_get_intfdata(intf);
+
+ /* 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);
+ clear_bit(WDM_IN_USE, &desc->flags);
+ spin_unlock_irqrestore(&desc->iuspin, flags);
+ cancel_work_sync(&desc->rxwork);
+ kill_urbs(desc);
+ wake_up_all(&desc->wait);
+ if (!desc->count)
+ cleanup(desc);
+ mutex_unlock(&wdm_mutex);
+}
+
+static struct usb_driver wdm_driver = {
+ .name = "cdc_wdm",
+ .probe = wdm_probe,
+ .disconnect = wdm_disconnect,
+ .id_table = wdm_ids,
+};
+
+/* --- low level module stuff --- */
+
+static int __init wdm_init(void)
+{
+ int rv;
+
+ rv = usb_register(&wdm_driver);
+
+ return rv;
+}
+
+static void __exit wdm_exit(void)
+{
+ usb_deregister(&wdm_driver);
+}
+
+module_init(wdm_init);
+module_exit(wdm_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION("USB Abstract Control Model driver for "
+ "USB WCM Device Management");
+MODULE_LICENSE("GPL");