new driver cdc-wdm
From: | Oliver Neukum <oliver@neukum.org> | |
To: | linux-usb-devel@lists.sourceforge.net | |
Subject: | [rfc] new driver cdc-wdm | |
Date: | Thu, 4 Jan 2007 17:59:41 +0100 |
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=so... _______________________________________________ linux-usb-devel@lists.sourceforge.net To unsubscribe, use the last form field at: https://lists.sourceforge.net/lists/listinfo/linux-usb-devel