Am Donnerstag, 4. Januar 2007 19:38 schrieb Pete Zaitcev:
> On Thu, 4 Jan 2007 17:59:41 +0100, Oliver Neukum <[EMAIL PROTECTED]> wrote:
>
> > + case USB_CDC_DMM_TYPE:
> > + maxcom = le16_to_cpu(*((u16 *)(buffer + 5)));
>
> What an atrocious way to program. If you insist on saving a few instructions
> by not using buffer[6]<<8|buffer[5], at least put get_unaligned in there.
OK,
It seems to me that the root of all evil is using untyped data.
Do you like this version better? Do you have any other comments?
Regards
Oliver
--- drivers/usb/class/Kconfig~ 2007-01-01 01:53:20.000000000 +0100
+++ drivers/usb/class/Kconfig 2007-01-02 13:29:33.000000000 +0100
@@ -29,3 +29,14 @@
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.
+
--- drivers/usb/class/Makefile~ 2007-01-01 01:53:20.000000000 +0100
+++ drivers/usb/class/Makefile 2007-01-02 13:21:29.000000000 +0100
@@ -5,3 +5,4 @@
obj-$(CONFIG_USB_ACM) += cdc-acm.o
obj-$(CONFIG_USB_PRINTER) += usblp.o
+obj-$(CONFIG_USB_WDM) += cdc-wdm.o
--- /dev/null 2005-03-19 23:01:40.000000000 +0100
+++ drivers/usb/class/cdc-wdm.h 2007-01-05 12:48:41.000000000 +0100
@@ -0,0 +1,109 @@
+/* Header file for cdc-wdm.c */
+
+
+/* --- device descriptor --- */
+
+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 work_struct *rxwork;
+ struct work_struct *txwork;
+ struct usb_ctrlrequest *orq;
+ struct usb_ctrlrequest *irq;
+ spinlock_t iuspin;
+
+ unsigned long flags;
+ u16 bufsize;
+ u16 wMaxCommand;
+ __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;
+ int werr;
+ int rerr;
+};
+
+/* --- prototypes --- */
+
+static int __init wdm_init(void);
+static void __exit wdm_exit(void);
+
+static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id);
+static void wdm_disconnect(struct usb_interface *intf);
+
+static ssize_t wdm_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos);
+static ssize_t wdm_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos);
+static int wdm_release(struct inode *inode, struct file *file);
+static int wdm_open(struct inode *inode, struct file *file);
+static int wdm_flush (struct file * file, fl_owner_t id);
+
+static void wdm_out_callback (struct urb *urb);
+static void wdm_in_callback (struct urb *urb);
+
+static void free_urbs(struct wdm_device *desc);
+static void cleanup (struct wdm_device *desc);
+static void kill_urbs (struct wdm_device *desc);
+static int run_poll(struct wdm_device *desc, gfp_t gfp);
+
+/* --- table of supported interfaces ---*/
+
+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
+ },
+ { }
+};
+
+MODULE_DEVICE_TABLE (usb, wdm_ids);
+
+#define WDM_MINOR_BASE 32
+
+static const struct file_operations wdm_fops = {
+ .owner = THIS_MODULE,
+ .read = wdm_read,
+ .write = wdm_write,
+ //.poll = wdm_poll,
+ .open = wdm_open,
+ .flush = wdm_flush,
+ .release = wdm_release
+};
+
+static struct usb_class_driver wdm_class = {
+ .name = "cdc-wdm%d",
+ .fops = &wdm_fops,
+ .minor_base = WDM_MINOR_BASE,
+};
+
+/* --- flags --- */
+#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
+
+/* --- misc --- */
+#define WDM_MAX 16
+
+struct usb_cdc_dmm_header {
+ __u8 bFunctionLength;
+ __u8 bDescriptorType;
+ __u8 bDescriptorSubtype;
+ __u16 bcdVersion;
+ __le16 wMaxCommand;
+} __attribute__ ((packed));
+
--- /dev/null 2005-03-19 23:01:40.000000000 +0100
+++ drivers/usb/class/cdc-wdm.c 2007-01-05 12:52:33.000000000 +0100
@@ -0,0 +1,576 @@
+/* cdc-wdm.c
+
+This driver supports USB CDC WCM Device Management.
+
+Copyright (c) 2007 Oliver Neukum
+
+Some code taken from cdc-acm.c
+*/
+#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 <asm/uaccess.h>
+#include <linux/usb.h>
+#include <linux/usb/cdc.h>
+#include <asm/byteorder.h>
+#include <asm/bitops.h>
+#include <asm/unaligned.h>
+
+#include "cdc-wdm.h"
+#include "cdc-acm.h" /* for request types */
+
+#define DEBUG
+
+/*
+ * Version Information
+ */
+#define DRIVER_VERSION "v0.01"
+#define DRIVER_AUTHOR "Oliver Neukum"
+#define DRIVER_DESC "USB Abstract Control Model driver for USB WCM Device Management"
+
+static DEFINE_MUTEX(wdm_mutex);
+
+/* --- method tables --- */
+
+static struct usb_driver wdm_driver = {
+ .name = "cdc_wdm",
+ .probe = wdm_probe,
+ .disconnect = wdm_disconnect,
+ .id_table = wdm_ids,
+};
+
+/* --- file IO --- */
+
+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;
+ unsigned long flags;
+ struct wdm_device *desc = file->private_data;
+ struct usb_ctrlrequest *req;
+
+ if (count > desc->wMaxCommand)
+ count = desc->wMaxCommand;
+
+ spin_lock_irqsave(&desc->iuspin, flags);
+ we = desc->werr;
+ desc->werr = 0;
+ spin_unlock_irqrestore(&desc->iuspin, flags);
+ if (we < 0)
+ return -EIO;
+
+ run_poll(desc, GFP_KERNEL); /* read responses right on so that the output buffer not stall */
+ 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),
+ usb_sndctrlpipe(interface_to_usbdev(desc->intf), 0), /* using common endpoint 0 */
+ (unsigned char *)req,
+ buf,
+ count,
+ wdm_out_callback,
+ desc
+ );
+ req->bRequestType = USB_RT_ACM;
+ 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 {
+ info("Tx URB has been submitted");
+ }
+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;
+ unsigned long flags;
+ 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;
+ run_poll(desc, GFP_KERNEL);
+retry:
+ rv = wait_event_interruptible(desc->wait, test_bit(WDM_READ, &desc->flags));
+
+ if (rv < 0) {
+ rv = -ERESTARTSYS;
+ goto err;
+ }
+
+ spin_lock_irqsave(&desc->iuspin, flags);
+
+ if (desc->rerr) { /* read completed, error happened */
+ spin_unlock_irqrestore(&desc->iuspin, flags);
+ err("reading had resulted in %d", desc->rerr);
+ 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_irqrestore(&desc->iuspin, flags);
+ goto retry;
+ }
+ if (desc->reslength) { /* zero length read */
+ spin_unlock_irqrestore(&desc->iuspin, flags);
+ goto retry;
+ }
+ memmove(desc->ubuf, desc->inbuf, desc->length = desc->reslength);
+ clear_bit(WDM_READ, &desc->flags);
+ spin_unlock_irqrestore(&desc->iuspin, flags);
+ }
+
+ cntr = count > desc->length ? desc->length : count;
+ rv = copy_to_user(buffer, desc->ubuf + desc->read, cntr);
+ if (rv > 0) {
+ rv = -EFAULT;
+ goto err;
+ }
+ desc->length -= cntr;
+ desc->read += cntr;
+ 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));
+ if (desc->werr < 0)
+ err("Error in flush path: %d", desc->werr);
+
+ return desc->werr;
+}
+
+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 = 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) {
+ if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
+ cleanup(desc);
+ }
+ }
+ mutex_unlock (&wdm_mutex);
+ return 0;
+}
+
+static int run_poll(struct wdm_device *desc, gfp_t gfp)
+{
+ int rv = 0;
+
+ if (!test_and_set_bit(WDM_POLL_RUNNING, &desc->flags)) {
+ rv = usb_submit_urb(desc->validity, gfp);
+ if (rv < 0) {
+ clear_bit(WDM_POLL_RUNNING, &desc->flags);
+ err("Error submitting int urb - %d", rv);
+ } else {
+ info("Status urb has been submitted");
+ }
+ }
+ return rv;
+}
+
+/* --- callbacks --- */
+
+static void wdm_int_callback (struct urb *urb)
+{
+ int rv = 0;
+ 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 (urb->status) {
+ switch(urb->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", urb->status);
+ break;
+ }
+ }
+
+ if (urb->actual_length < sizeof(struct usb_cdc_notification)) {
+ err("wdm_int_callback - %d bytes", urb->actual_length);
+ clear_bit(WDM_POLL_RUNNING, &desc->flags);
+ rv = run_poll(desc, GFP_ATOMIC);
+ if (rv < 0) {
+ spin_lock(&desc->iuspin);
+ desc->werr = -EIO;
+ spin_unlock(&desc->iuspin);
+ return;
+ }
+ }
+
+ switch (dr->bNotificationType) {
+ case USB_CDC_NOTIFY_RESPONSE_AVAILABLE:
+ info(" NOTIFY_RESPONSE_AVAILABLE received: index %d len %d",
+ dr->wIndex, dr->wLength);
+ break;
+ default:
+ clear_bit(WDM_POLL_RUNNING, &desc->flags);
+ err("unknown notification %d received: index %d len %d",
+ dr->bNotificationType, dr->wIndex, dr->wLength);
+ rv = run_poll(desc, GFP_ATOMIC);
+ if (rv < 0) {
+ spin_lock(&desc->iuspin);
+ desc->werr = -EIO;
+ spin_unlock(&desc->iuspin);
+ return;
+ }
+ return;
+
+ }
+
+ req->bRequestType = USB_RT_ACM;
+ req->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE;
+ req->wValue = 0;
+ req->wIndex = desc->inum;
+ req->wLength = cpu_to_le16(desc->wMaxCommand);
+
+ usb_fill_control_urb(
+ desc->response,
+ interface_to_usbdev(desc->intf),
+ usb_sndctrlpipe(interface_to_usbdev(desc->intf), 0), /* using common endpoint 0 */
+ (unsigned char *)req,
+ desc->inbuf,
+ desc->wMaxCommand,
+ 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);
+ 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");
+ }
+ }
+ clear_bit(WDM_POLL_RUNNING, &desc->flags);
+}
+
+static void wdm_out_callback (struct urb *urb)
+{
+ struct wdm_device *desc;
+ info("wdm_out_callback - status: %d", urb->status);
+ 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);
+ run_poll(desc, GFP_ATOMIC);
+}
+
+static void wdm_in_callback (struct urb *urb)
+{
+ struct wdm_device *desc;
+ info("wdm_in_callback - status: %d", urb->status);
+ desc = urb->context;
+
+ spin_lock(&desc->iuspin);
+ desc->rerr = urb->status;
+ desc->reslength = urb->actual_length;
+ set_bit(WDM_READ, &desc->flags);
+ spin_unlock(&desc->iuspin);
+ wake_up(&desc->wait);
+}
+
+
+
+/* --- hotplug --- */
+
+static int wdm_probe (struct usb_interface *intf, const struct usb_device_id *id)
+{
+ int rv = -EINVAL;
+ struct wdm_device *desc;
+ struct usb_host_interface *iface;
+ struct usb_endpoint_descriptor *ep;
+ struct usb_cdc_dmm_header *dmhd;
+ struct urb *urbs = NULL, *urbi = NULL, *urbo = NULL;
+ 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_header *)buffer;
+ maxcom = le16_to_cpu(dmhd->wMaxCommand);
+ info("Finding maximum buffer length: %d", maxcom);
+ 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);
+ //INIT_WORK(&desc->txwork, wdm_txwork);
+
+ iface = &intf->altsetting[0];
+ ep = &iface->endpoint[0].desc;
+ if (!usb_endpoint_is_int_in(ep)) {
+ rv = -EINVAL;
+ goto err;
+ }
+
+ 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;
+
+ urbs = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urbs)
+ goto err;
+ desc->validity = urbs;
+
+ urbi = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urbi)
+ goto err;
+ desc->response = urbi;
+
+ urbo = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urbo)
+ goto err;
+ desc->command = urbo;
+
+ desc->ubuf = kmalloc(maxcom, GFP_KERNEL);
+ if (!desc->ubuf)
+ goto err;
+
+ //desc->sbuf = usb_buffer_alloc(interface_to_usbdev(intf), sizeof(struct usb_cdc_notification), GFP_KERNEL, &desc->shandle);
+desc->sbuf = kmalloc(sizeof(struct usb_cdc_notification), GFP_KERNEL);
+ if (!desc->sbuf)
+ goto err;
+
+ //desc->inbuf = usb_buffer_alloc(interface_to_usbdev(intf), maxcom, GFP_KERNEL, &desc->ihandle);
+desc->inbuf = kmalloc(maxcom, GFP_KERNEL);
+ if (!desc->inbuf)
+ goto err2;
+
+ usb_fill_int_urb(
+ urbs,
+ interface_to_usbdev(intf),
+ usb_rcvintpipe(interface_to_usbdev(intf), ep->bEndpointAddress),
+ desc->sbuf,
+ sizeof(struct usb_cdc_notification),
+ wdm_int_callback,
+ desc,
+ ep->bInterval
+ );
+ //urbs->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ usb_set_intfdata(intf, desc);
+ rv = usb_register_dev(intf, &wdm_class);
+ if (rv < 0)
+ goto err;
+out:
+ return rv;
+err2:
+ //usb_buffer_free(interface_to_usbdev(desc->intf), sizeof(struct usb_cdc_notification), desc->sbuf, desc->shandle);
+kfree(desc->sbuf);
+err:
+ free_urbs(desc);
+ kfree(desc->ubuf);
+ kfree(desc->orq);
+ kfree(desc->irq);
+ kfree(desc);
+ return 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), sizeof(struct usb_cdc_notification), desc->sbuf, desc->shandle);
+ //usb_buffer_free(interface_to_usbdev(desc->intf), desc->wMaxCommand, desc->inbuf, desc->ihandle);
+kfree(desc->sbuf);
+kfree(desc->inbuf);
+ kfree(desc->orq);
+ kfree(desc->irq);
+ kfree(desc->ubuf);
+ free_urbs(desc);
+ kfree(desc);
+}
+
+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);
+ kill_urbs(desc);
+ wake_up(&desc->wait);
+ if (!desc->count)
+ cleanup(desc);
+ mutex_unlock(&wdm_mutex);
+}
+
+/* --- 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( DRIVER_DESC );
+MODULE_LICENSE("GPL");
-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys - and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
linux-usb-devel@lists.sourceforge.net
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel