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

Reply via email to