Hi,
this is the first take at implementing CDC-WMC Device Management.
Comments please.
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-04 15:18:33.000000000 +0100
@@ -0,0 +1,102 @@
+/* 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
+
+
--- /dev/null 2005-03-19 23:01:40.000000000 +0100
+++ drivers/usb/class/cdc-wdm.c 2007-01-04 17:38:21.000000000 +0100
@@ -0,0 +1,573 @@
+/* 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 "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 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\n");
+ goto next_desc;
+ }
+
+ switch (buffer [2]) {
+ case USB_CDC_HEADER_TYPE:
+ break;
+ case USB_CDC_DMM_TYPE:
+ maxcom = le16_to_cpu(*((u16 *)(buffer + 5)));
+ 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