Release 4.9 drivers/usb/class/cdc-wdm.c
  
  
/*
 * cdc-wdm.c
 *
 * This driver supports USB CDC WCM Device Management.
 *
 * Copyright (c) 2007-2009 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/ioctl.h>
#include <linux/slab.h>
#include <linux/module.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>
#include <linux/usb/cdc-wdm.h>
/*
 * Version Information
 */
#define DRIVER_VERSION "v0.03"
#define DRIVER_AUTHOR "Oliver Neukum"
#define DRIVER_DESC "USB Abstract Control Model driver for USB WCM Device Management"
static const 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	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_RESPONDING		7
#define WDM_SUSPENDING		8
#define WDM_RESETTING		9
#define WDM_OVERFLOW		10
#define WDM_DRAIN_ON_OPEN	11
#define WDM_MAX			16
/* CDC-WMC r1.1 requires wMaxCommand to be "at least 256 decimal (0x100)" */
#define WDM_DEFAULT_BUFSIZE	256
static DEFINE_MUTEX(wdm_mutex);
static DEFINE_SPINLOCK(wdm_device_list_lock);
static LIST_HEAD(wdm_device_list);
/* --- 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;
	
__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;
	
int                     resp_count;
	
struct list_head	device_list;
	
int			(*manage_power)(struct usb_interface *, int);
};
static struct usb_driver wdm_driver;
/* return intfdata if we own the interface, else look up intf in the list */
static struct wdm_device *wdm_find_device(struct usb_interface *intf)
{
	struct wdm_device *desc;
	spin_lock(&wdm_device_list_lock);
	list_for_each_entry(desc, &wdm_device_list, device_list)
		if (desc->intf == intf)
			goto found;
	desc = NULL;
found:
	spin_unlock(&wdm_device_list_lock);
	return desc;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| bjorn mork | bjorn mork | 58 | 100.00% | 2 | 100.00% | 
 | Total | 58 | 100.00% | 2 | 100.00% | 
static struct wdm_device *wdm_find_device_by_minor(int minor)
{
	struct wdm_device *desc;
	spin_lock(&wdm_device_list_lock);
	list_for_each_entry(desc, &wdm_device_list, device_list)
		if (desc->intf->minor == minor)
			goto found;
	desc = NULL;
found:
	spin_unlock(&wdm_device_list_lock);
	return desc;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| bjorn mork | bjorn mork | 58 | 100.00% | 2 | 100.00% | 
 | Total | 58 | 100.00% | 2 | 100.00% | 
/* --- 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);
	kfree(desc->outbuf);
	desc->outbuf = NULL;
	clear_bit(WDM_IN_USE, &desc->flags);
	wake_up(&desc->wait);
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 77 | 100.00% | 2 | 100.00% | 
 | Total | 77 | 100.00% | 2 | 100.00% | 
/* forward declaration */
static int service_outstanding_interrupt(struct wdm_device *desc);
static void wdm_in_callback(struct urb *urb)
{
	struct wdm_device *desc = urb->context;
	int status = urb->status;
	int length = urb->actual_length;
	spin_lock(&desc->iuspin);
	clear_bit(WDM_RESPONDING, &desc->flags);
	if (status) {
		switch (status) {
		case -ENOENT:
			dev_dbg(&desc->intf->dev,
				"nonzero urb status received: -ENOENT\n");
			goto skip_error;
		case -ECONNRESET:
			dev_dbg(&desc->intf->dev,
				"nonzero urb status received: -ECONNRESET\n");
			goto skip_error;
		case -ESHUTDOWN:
			dev_dbg(&desc->intf->dev,
				"nonzero urb status received: -ESHUTDOWN\n");
			goto skip_error;
		case -EPIPE:
			dev_dbg(&desc->intf->dev,
				"nonzero urb status received: -EPIPE\n");
			break;
		default:
			dev_err(&desc->intf->dev,
				"Unexpected error %d\n", status);
			break;
		}
	}
	/*
         * only set a new error if there is no previous error.
         * Errors are only cleared during read/open
         */
	if (desc->rerr  == 0)
		desc->rerr = status;
	if (length + desc->length > desc->wMaxCommand) {
		/* The buffer would overflow */
		set_bit(WDM_OVERFLOW, &desc->flags);
	} else {
		/* we may already be in overflow */
		if (!test_bit(WDM_OVERFLOW, &desc->flags)) {
			memmove(desc->ubuf + desc->length, desc->inbuf, length);
			desc->length += length;
			desc->reslength = length;
		}
	}
	/*
         * Handling devices with the WDM_DRAIN_ON_OPEN flag set:
         * If desc->resp_count is unset, then the urb was submitted
         * without a prior notification.  If the device returned any
         * data, then this implies that it had messages queued without
         * notifying us.  Continue reading until that queue is flushed.
         */
	if (!desc->resp_count) {
		if (!length) {
			/* do not propagate the expected -EPIPE */
			desc->rerr = 0;
			goto unlock;
		}
		dev_dbg(&desc->intf->dev, "got %d bytes without notification\n", length);
		set_bit(WDM_RESPONDING, &desc->flags);
		usb_submit_urb(desc->response, GFP_ATOMIC);
	}
skip_error:
	set_bit(WDM_READ, &desc->flags);
	wake_up(&desc->wait);
	if (desc->rerr) {
		/*
                 * Since there was an error, userspace may decide to not read
                 * any data after poll'ing.
                 * We should respond to further attempts from the device to send
                 * data, so that we can get unstuck.
                 */
		service_outstanding_interrupt(desc);
	}
unlock:
	spin_unlock(&desc->iuspin);
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 237 | 68.50% | 4 | 57.14% | 
| bjorn mork | bjorn mork | 63 | 18.21% | 1 | 14.29% | 
| robert foss | robert foss | 29 | 8.38% | 1 | 14.29% | 
| greg kroah-hartman | greg kroah-hartman | 17 | 4.91% | 1 | 14.29% | 
 | Total | 346 | 100.00% | 7 | 100.00% | 
static void wdm_int_callback(struct urb *urb)
{
	int rv = 0;
	int responding;
	int status = urb->status;
	struct wdm_device *desc;
	struct usb_cdc_notification *dr;
	desc = urb->context;
	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);
			dev_err(&desc->intf->dev, "Stall on int endpoint\n");
			goto sw; /* halt is cleared in work */
		default:
			dev_err(&desc->intf->dev,
				"nonzero urb status received: %d\n", status);
			break;
		}
	}
	if (urb->actual_length < sizeof(struct usb_cdc_notification)) {
		dev_err(&desc->intf->dev, "wdm_int_callback - %d bytes\n",
			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\n",
			le16_to_cpu(dr->wIndex), le16_to_cpu(dr->wLength));
		break;
	case USB_CDC_NOTIFY_NETWORK_CONNECTION:
		dev_dbg(&desc->intf->dev,
			"NOTIFY_NETWORK_CONNECTION %s network\n",
			dr->wValue ? "connected to" : "disconnected from");
		goto exit;
	case USB_CDC_NOTIFY_SPEED_CHANGE:
		dev_dbg(&desc->intf->dev, "SPEED_CHANGE received (len %u)\n",
			urb->actual_length);
		goto exit;
	default:
		clear_bit(WDM_POLL_RUNNING, &desc->flags);
		dev_err(&desc->intf->dev,
			"unknown notification %d received: index %d len %d\n",
			dr->bNotificationType,
			le16_to_cpu(dr->wIndex),
			le16_to_cpu(dr->wLength));
		goto exit;
	}
	spin_lock(&desc->iuspin);
	responding = test_and_set_bit(WDM_RESPONDING, &desc->flags);
	if (!desc->resp_count++ && !responding
		&& !test_bit(WDM_DISCONNECTING, &desc->flags)
		&& !test_bit(WDM_SUSPENDING, &desc->flags)) {
		rv = usb_submit_urb(desc->response, GFP_ATOMIC);
		dev_dbg(&desc->intf->dev, "submit response URB %d\n", rv);
	}
	spin_unlock(&desc->iuspin);
	if (rv < 0) {
		clear_bit(WDM_RESPONDING, &desc->flags);
		if (rv == -EPERM)
			return;
		if (rv == -ENOMEM) {
sw:
			rv = schedule_work(&desc->rxwork);
			if (rv)
				dev_err(&desc->intf->dev,
					"Cannot schedule work\n");
		}
	}
exit:
	rv = usb_submit_urb(urb, GFP_ATOMIC);
	if (rv)
		dev_err(&desc->intf->dev,
			"%s - usb_submit_urb failed with result %d\n",
			__func__, rv);
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 389 | 82.77% | 6 | 66.67% | 
| greg kroah-hartman | greg kroah-hartman | 54 | 11.49% | 1 | 11.11% | 
| bjorn mork | bjorn mork | 21 | 4.47% | 1 | 11.11% | 
| greg suarez | greg suarez | 6 | 1.28% | 1 | 11.11% | 
 | Total | 470 | 100.00% | 9 | 100.00% | 
static void kill_urbs(struct wdm_device *desc)
{
	/* the order here is essential */
	usb_kill_urb(desc->command);
	usb_kill_urb(desc->validity);
	usb_kill_urb(desc->response);
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 33 | 100.00% | 2 | 100.00% | 
 | Total | 33 | 100.00% | 2 | 100.00% | 
static void free_urbs(struct wdm_device *desc)
{
	usb_free_urb(desc->validity);
	usb_free_urb(desc->response);
	usb_free_urb(desc->command);
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 32 | 100.00% | 1 | 100.00% | 
 | Total | 32 | 100.00% | 1 | 100.00% | 
static void cleanup(struct wdm_device *desc)
{
	kfree(desc->sbuf);
	kfree(desc->inbuf);
	kfree(desc->orq);
	kfree(desc->irq);
	kfree(desc->ubuf);
	free_urbs(desc);
	kfree(desc);
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 54 | 96.43% | 1 | 50.00% | 
| bjorn mork | bjorn mork | 2 | 3.57% | 1 | 50.00% | 
 | Total | 56 | 100.00% | 2 | 100.00% | 
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 usb_translate_errors(we);
	buf = kmalloc(count, GFP_KERNEL);
	if (!buf) {
		rv = -ENOMEM;
		goto outnl;
	}
	r = copy_from_user(buf, buffer, count);
	if (r > 0) {
		rv = -EFAULT;
		goto out_free_mem;
	}
	/* concurrent writes and disconnect */
	r = mutex_lock_interruptible(&desc->wlock);
	rv = -ERESTARTSYS;
	if (r)
		goto out_free_mem;
	if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
		rv = -ENODEV;
		goto out_free_mem_lock;
	}
	r = usb_autopm_get_interface(desc->intf);
	if (r < 0) {
		rv = usb_translate_errors(r);
		goto out_free_mem_lock;
	}
	if (!(file->f_flags & O_NONBLOCK))
		r = wait_event_interruptible(desc->wait, !test_bit(WDM_IN_USE,
								&desc->flags));
	else
		if (test_bit(WDM_IN_USE, &desc->flags))
			r = -EAGAIN;
	if (test_bit(WDM_RESETTING, &desc->flags))
		r = -EIO;
	if (r < 0) {
		rv = r;
		goto out_free_mem_pm;
	}
	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; /* already converted */
	req->wLength = cpu_to_le16(count);
	set_bit(WDM_IN_USE, &desc->flags);
	desc->outbuf = buf;
	rv = usb_submit_urb(desc->command, GFP_KERNEL);
	if (rv < 0) {
		desc->outbuf = NULL;
		clear_bit(WDM_IN_USE, &desc->flags);
		dev_err(&desc->intf->dev, "Tx URB error: %d\n", rv);
		rv = usb_translate_errors(rv);
		goto out_free_mem_pm;
	} else {
		dev_dbg(&desc->intf->dev, "Tx URB has been submitted index=%d\n",
			le16_to_cpu(req->wIndex));
	}
	usb_autopm_put_interface(desc->intf);
	mutex_unlock(&desc->wlock);
outnl:
	return rv < 0 ? rv : count;
out_free_mem_pm:
	usb_autopm_put_interface(desc->intf);
out_free_mem_lock:
	mutex_unlock(&desc->wlock);
out_free_mem:
	kfree(buf);
	return rv;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 521 | 94.38% | 11 | 73.33% | 
| bjorn mork | bjorn mork | 19 | 3.44% | 2 | 13.33% | 
| greg kroah-hartman | greg kroah-hartman | 9 | 1.63% | 1 | 6.67% | 
| david sterba | david sterba | 3 | 0.54% | 1 | 6.67% | 
 | Total | 552 | 100.00% | 15 | 100.00% | 
/*
 * Submit the read urb if resp_count is non-zero.
 *
 * Called with desc->iuspin locked
 */
static int service_outstanding_interrupt(struct wdm_device *desc)
{
	int rv = 0;
	/* submit read urb only if the device is waiting for it */
	if (!desc->resp_count || !--desc->resp_count)
		goto out;
	set_bit(WDM_RESPONDING, &desc->flags);
	spin_unlock_irq(&desc->iuspin);
	rv = usb_submit_urb(desc->response, GFP_KERNEL);
	spin_lock_irq(&desc->iuspin);
	if (rv) {
		dev_err(&desc->intf->dev,
			"usb_submit_urb failed with result %d\n", rv);
		/* make sure the next notification trigger a submit */
		clear_bit(WDM_RESPONDING, &desc->flags);
		desc->resp_count = 0;
	}
out:
	return rv;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| bjorn mork | bjorn mork | 111 | 99.11% | 2 | 66.67% | 
| robert foss | robert foss | 1 | 0.89% | 1 | 33.33% | 
 | Total | 112 | 100.00% | 3 | 100.00% | 
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;
	cntr = ACCESS_ONCE(desc->length);
	if (cntr == 0) {
		desc->read = 0;
retry:
		if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
			rv = -ENODEV;
			goto err;
		}
		if (test_bit(WDM_OVERFLOW, &desc->flags)) {
			clear_bit(WDM_OVERFLOW, &desc->flags);
			rv = -ENOBUFS;
			goto err;
		}
		i++;
		if (file->f_flags & O_NONBLOCK) {
			if (!test_bit(WDM_READ, &desc->flags)) {
				rv = cntr ? cntr : -EAGAIN;
				goto err;
			}
			rv = 0;
		} else {
			rv = wait_event_interruptible(desc->wait,
				test_bit(WDM_READ, &desc->flags));
		}
		/* may have happened while we slept */
		if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
			rv = -ENODEV;
			goto err;
		}
		if (test_bit(WDM_RESETTING, &desc->flags)) {
			rv = -EIO;
			goto err;
		}
		usb_mark_last_busy(interface_to_usbdev(desc->intf));
		if (rv < 0) {
			rv = -ERESTARTSYS;
			goto err;
		}
		spin_lock_irq(&desc->iuspin);
		if (desc->rerr) { /* read completed, error happened */
			rv = usb_translate_errors(desc->rerr);
			desc->rerr = 0;
			spin_unlock_irq(&desc->iuspin);
			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 */
			dev_dbg(&desc->intf->dev, "zero length - clearing WDM_READ\n");
			clear_bit(WDM_READ, &desc->flags);
			rv = service_outstanding_interrupt(desc);
			spin_unlock_irq(&desc->iuspin);
			if (rv < 0)
				goto err;
			goto retry;
		}
		cntr = desc->length;
		spin_unlock_irq(&desc->iuspin);
	}
	if (cntr > count)
		cntr = count;
	rv = copy_to_user(buffer, desc->ubuf, cntr);
	if (rv > 0) {
		rv = -EFAULT;
		goto err;
	}
	spin_lock_irq(&desc->iuspin);
	for (i = 0; i < desc->length - cntr; i++)
		desc->ubuf[i] = desc->ubuf[i + cntr];
	desc->length -= cntr;
	/* in case we had outstanding data */
	if (!desc->length) {
		clear_bit(WDM_READ, &desc->flags);
		service_outstanding_interrupt(desc);
	}
	spin_unlock_irq(&desc->iuspin);
	rv = cntr;
err:
	mutex_unlock(&desc->rlock);
	return rv;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 448 | 80.14% | 7 | 53.85% | 
| bjorn mork | bjorn mork | 50 | 8.94% | 4 | 30.77% | 
| ben hutchings | ben hutchings | 37 | 6.62% | 1 | 7.69% | 
| robert foss | robert foss | 24 | 4.29% | 1 | 7.69% | 
 | Total | 559 | 100.00% | 13 | 100.00% | 
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));
	/* cannot dereference desc->intf if WDM_DISCONNECTING */
	if (desc->werr < 0 && !test_bit(WDM_DISCONNECTING, &desc->flags))
		dev_err(&desc->intf->dev, "Error in flush path: %d\n",
			desc->werr);
	return usb_translate_errors(desc->werr);
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 64 | 75.29% | 2 | 50.00% | 
| bjorn mork | bjorn mork | 12 | 14.12% | 1 | 25.00% | 
| greg kroah-hartman | greg kroah-hartman | 9 | 10.59% | 1 | 25.00% | 
 | Total | 85 | 100.00% | 4 | 100.00% | 
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 = POLLHUP | 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;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 155 | 98.73% | 1 | 50.00% | 
| bjorn mork | bjorn mork | 2 | 1.27% | 1 | 50.00% | 
 | Total | 157 | 100.00% | 2 | 100.00% | 
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);
	desc = wdm_find_device_by_minor(minor);
	if (!desc)
		goto out;
	intf = desc->intf;
	if (test_bit(WDM_DISCONNECTING, &desc->flags))
		goto out;
	file->private_data = desc;
	rv = usb_autopm_get_interface(desc->intf);
	if (rv < 0) {
		dev_err(&desc->intf->dev, "Error autopm - %d\n", rv);
		goto out;
	}
	/* using write lock to protect desc->count */
	mutex_lock(&desc->wlock);
	if (!desc->count++) {
		desc->werr = 0;
		desc->rerr = 0;
		rv = usb_submit_urb(desc->validity, GFP_KERNEL);
		if (rv < 0) {
			desc->count--;
			dev_err(&desc->intf->dev,
				"Error submitting int urb - %d\n", rv);
			rv = usb_translate_errors(rv);
		} else if (test_bit(WDM_DRAIN_ON_OPEN, &desc->flags)) {
			/*
                         * Some devices keep pending messages queued
                         * without resending notifications.  We must
                         * flush the message queue before we can
                         * assume a one-to-one relationship between
                         * notifications and messages in the queue
                         */
			dev_dbg(&desc->intf->dev, "draining queued data\n");
			set_bit(WDM_RESPONDING, &desc->flags);
			rv = usb_submit_urb(desc->response, GFP_KERNEL);
		}
	} else {
		rv = 0;
	}
	mutex_unlock(&desc->wlock);
	if (desc->count == 1)
		desc->manage_power(intf, 1);
	usb_autopm_put_interface(desc->intf);
out:
	mutex_unlock(&wdm_mutex);
	return rv;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 204 | 68.69% | 4 | 44.44% | 
| bjorn mork | bjorn mork | 75 | 25.25% | 4 | 44.44% | 
| greg kroah-hartman | greg kroah-hartman | 18 | 6.06% | 1 | 11.11% | 
 | Total | 297 | 100.00% | 9 | 100.00% | 
static int wdm_release(struct inode *inode, struct file *file)
{
	struct wdm_device *desc = file->private_data;
	mutex_lock(&wdm_mutex);
	/* using write lock to protect desc->count */
	mutex_lock(&desc->wlock);
	desc->count--;
	mutex_unlock(&desc->wlock);
	if (!desc->count) {
		if (!test_bit(WDM_DISCONNECTING, &desc->flags)) {
			dev_dbg(&desc->intf->dev, "wdm_release: cleanup\n");
			kill_urbs(desc);
			spin_lock_irq(&desc->iuspin);
			desc->resp_count = 0;
			spin_unlock_irq(&desc->iuspin);
			desc->manage_power(desc->intf, 0);
		} else {
			/* must avoid dev_printk here as desc->intf is invalid */
			pr_debug(KBUILD_MODNAME " %s: device gone - cleaning up\n", __func__);
			cleanup(desc);
		}
	}
	mutex_unlock(&wdm_mutex);
	return 0;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 96 | 62.75% | 4 | 44.44% | 
| bjorn mork | bjorn mork | 35 | 22.88% | 4 | 44.44% | 
| greg suarez | greg suarez | 22 | 14.38% | 1 | 11.11% | 
 | Total | 153 | 100.00% | 9 | 100.00% | 
static long wdm_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct wdm_device *desc = file->private_data;
	int rv = 0;
	switch (cmd) {
	case IOCTL_WDM_MAX_COMMAND:
		if (copy_to_user((void __user *)arg, &desc->wMaxCommand, sizeof(desc->wMaxCommand)))
			rv = -EFAULT;
		break;
	default:
		rv = -ENOTTY;
	}
	return rv;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| bjorn mork | bjorn mork | 81 | 100.00% | 1 | 100.00% | 
 | Total | 81 | 100.00% | 1 | 100.00% | 
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,
	.unlocked_ioctl = wdm_ioctl,
	.compat_ioctl = wdm_ioctl,
	.llseek =	noop_llseek,
};
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 = 0;
	int responding;
	spin_lock_irqsave(&desc->iuspin, flags);
	if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
		spin_unlock_irqrestore(&desc->iuspin, flags);
	} else {
		responding = test_and_set_bit(WDM_RESPONDING, &desc->flags);
		spin_unlock_irqrestore(&desc->iuspin, flags);
		if (!responding)
			rv = usb_submit_urb(desc->response, GFP_KERNEL);
		if (rv < 0 && rv != -EPERM) {
			spin_lock_irqsave(&desc->iuspin, flags);
			clear_bit(WDM_RESPONDING, &desc->flags);
			if (!test_bit(WDM_DISCONNECTING, &desc->flags))
				schedule_work(&desc->rxwork);
			spin_unlock_irqrestore(&desc->iuspin, flags);
		}
	}
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 177 | 100.00% | 2 | 100.00% | 
 | Total | 177 | 100.00% | 2 | 100.00% | 
/* --- hotplug --- */
static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep,
		u16 bufsize, int (*manage_power)(struct usb_interface *, int),
		bool drain_on_open)
{
	int rv = -ENOMEM;
	struct wdm_device *desc;
	desc = kzalloc(sizeof(struct wdm_device), GFP_KERNEL);
	if (!desc)
		goto out;
	INIT_LIST_HEAD(&desc->device_list);
	mutex_init(&desc->rlock);
	mutex_init(&desc->wlock);
	spin_lock_init(&desc->iuspin);
	init_waitqueue_head(&desc->wait);
	desc->wMaxCommand = bufsize;
	/* this will be expanded and needed in hardware endianness */
	desc->inum = cpu_to_le16((u16)intf->cur_altsetting->desc.bInterfaceNumber);
	desc->intf = intf;
	INIT_WORK(&desc->rxwork, wdm_rxwork);
	rv = -EINVAL;
	if (!usb_endpoint_is_int_in(ep))
		goto err;
	desc->wMaxPacketSize = usb_endpoint_maxp(ep);
	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 = kmalloc(desc->wMaxPacketSize, GFP_KERNEL);
	if (!desc->sbuf)
		goto err;
	desc->inbuf = kmalloc(desc->wMaxCommand, GFP_KERNEL);
	if (!desc->inbuf)
		goto err;
	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->irq->bRequestType = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE);
	desc->irq->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE;
	desc->irq->wValue = 0;
	desc->irq->wIndex = desc->inum; /* already converted */
	desc->irq->wLength = cpu_to_le16(desc->wMaxCommand);
	usb_fill_control_urb(
		desc->response,
		interface_to_usbdev(intf),
		/* using common endpoint 0 */
		usb_rcvctrlpipe(interface_to_usbdev(desc->intf), 0),
		(unsigned char *)desc->irq,
		desc->inbuf,
		desc->wMaxCommand,
		wdm_in_callback,
		desc
	);
	desc->manage_power = manage_power;
	/*
         * "drain_on_open" enables a hack to work around a firmware
         * issue observed on network functions, in particular MBIM
         * functions.
         *
         * Quoting section 7 of the CDC-WMC r1.1 specification:
         *
         *  "The firmware shall interpret GetEncapsulatedResponse as a
         *   request to read response bytes. The firmware shall send
         *   the next wLength bytes from the response. The firmware
         *   shall allow the host to retrieve data using any number of
         *   GetEncapsulatedResponse requests. The firmware shall
         *   return a zero- length reply if there are no data bytes
         *   available.
         *
         *   The firmware shall send ResponseAvailable notifications
         *   periodically, using any appropriate algorithm, to inform
         *   the host that there is data available in the reply
         *   buffer. The firmware is allowed to send ResponseAvailable
         *   notifications even if there is no data available, but
         *   this will obviously reduce overall performance."
         *
         * These requirements, although they make equally sense, are
         * often not implemented by network functions. Some firmwares
         * will queue data indefinitely, without ever resending a
         * notification. The result is that the driver and firmware
         * loses "syncronization" if the driver ever fails to respond
         * to a single notification, something which easily can happen
         * on release(). When this happens, the driver will appear to
         * never receive notifications for the most current data. Each
         * notification will only cause a single read, which returns
         * the oldest data in the firmware's queue.
         *
         * The "drain_on_open" hack resolves the situation by draining
         * data from the firmware until none is returned, without a
         * prior notification.
         *
         * This will inevitably race with the firmware, risking that
         * we read data from the device before handling the associated
         * notification. To make things worse, some of the devices
         * needing the hack do not implement the "return zero if no
         * data is available" requirement either. Instead they return
         * an error on the subsequent read in this case.  This means
         * that "winning" the race can cause an unexpected EIO to
         * userspace.
         *
         * "winning" the race is more likely on resume() than on
         * open(), and the unexpected error is more harmful in the
         * middle of an open session. The hack is therefore only
         * applied on open(), and not on resume() where it logically
         * would be equally necessary. So we define open() as the only
         * driver <-> device "syncronization point".  Should we happen
         * to lose a notification after open(), then syncronization
         * will be lost until release()
         *
         * The hack should not be enabled for CDC WDM devices
         * conforming to the CDC-WMC r1.1 specification.  This is
         * ensured by setting drain_on_open to false in wdm_probe().
         */
	if (drain_on_open)
		set_bit(WDM_DRAIN_ON_OPEN, &desc->flags);
	spin_lock(&wdm_device_list_lock);
	list_add(&desc->device_list, &wdm_device_list);
	spin_unlock(&wdm_device_list_lock);
	rv = usb_register_dev(intf, &wdm_class);
	if (rv < 0)
		goto err;
	else
		dev_info(&intf->dev, "%s: USB WDM device\n", dev_name(intf->usb_dev));
out:
	return rv;
err:
	spin_lock(&wdm_device_list_lock);
	list_del(&desc->device_list);
	spin_unlock(&wdm_device_list_lock);
	cleanup(desc);
	return rv;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 392 | 64.16% | 4 | 25.00% | 
| bjorn mork | bjorn mork | 216 | 35.35% | 10 | 62.50% | 
| al viro | al viro | 2 | 0.33% | 1 | 6.25% | 
| kuninori morimoto | kuninori morimoto | 1 | 0.16% | 1 | 6.25% | 
 | Total | 611 | 100.00% | 16 | 100.00% | 
static int wdm_manage_power(struct usb_interface *intf, int on)
{
	/* need autopm_get/put here to ensure the usbcore sees the new value */
	int rv = usb_autopm_get_interface(intf);
	intf->needs_remote_wakeup = on;
	if (!rv)
		usb_autopm_put_interface(intf);
	return 0;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| bjorn mork | bjorn mork | 42 | 100.00% | 2 | 100.00% | 
 | Total | 42 | 100.00% | 2 | 100.00% | 
static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
	int rv = -EINVAL;
	struct usb_host_interface *iface;
	struct usb_endpoint_descriptor *ep;
	struct usb_cdc_parsed_header hdr;
	u8 *buffer = intf->altsetting->extra;
	int buflen = intf->altsetting->extralen;
	u16 maxcom = WDM_DEFAULT_BUFSIZE;
	if (!buffer)
		goto err;
	cdc_parse_cdc_header(&hdr, intf, buffer, buflen);
	if (hdr.usb_cdc_dmm_desc)
		maxcom = le16_to_cpu(hdr.usb_cdc_dmm_desc->wMaxCommand);
	iface = intf->cur_altsetting;
	if (iface->desc.bNumEndpoints != 1)
		goto err;
	ep = &iface->endpoint[0].desc;
	rv = wdm_create(intf, ep, maxcom, &wdm_manage_power, false);
err:
	return rv;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| bjorn mork | bjorn mork | 125 | 83.33% | 4 | 57.14% | 
| oliver neukum | oliver neukum | 25 | 16.67% | 3 | 42.86% | 
 | Total | 150 | 100.00% | 7 | 100.00% | 
/**
 * usb_cdc_wdm_register - register a WDM subdriver
 * @intf: usb interface the subdriver will associate with
 * @ep: interrupt endpoint to monitor for notifications
 * @bufsize: maximum message size to support for read/write
 *
 * Create WDM usb class character device and associate it with intf
 * without binding, allowing another driver to manage the interface.
 *
 * The subdriver will manage the given interrupt endpoint exclusively
 * and will issue control requests referring to the given intf. It
 * will otherwise avoid interferring, and in particular not do
 * usb_set_intfdata/usb_get_intfdata on intf.
 *
 * The return value is a pointer to the subdriver's struct usb_driver.
 * The registering driver is responsible for calling this subdriver's
 * disconnect, suspend, resume, pre_reset and post_reset methods from
 * its own.
 */
struct usb_driver *usb_cdc_wdm_register(struct usb_interface *intf,
					struct usb_endpoint_descriptor *ep,
					int bufsize,
					int (*manage_power)(struct usb_interface *, int))
{
	int rv = -EINVAL;
	rv = wdm_create(intf, ep, bufsize, manage_power, true);
	if (rv < 0)
		goto err;
	return &wdm_driver;
err:
	return ERR_PTR(rv);
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| bjorn mork | bjorn mork | 69 | 92.00% | 3 | 75.00% | 
| oliver neukum | oliver neukum | 6 | 8.00% | 1 | 25.00% | 
 | Total | 75 | 100.00% | 4 | 100.00% | 
EXPORT_SYMBOL(usb_cdc_wdm_register);
static void wdm_disconnect(struct usb_interface *intf)
{
	struct wdm_device *desc;
	unsigned long flags;
	usb_deregister_dev(intf, &wdm_class);
	desc = wdm_find_device(intf);
	mutex_lock(&wdm_mutex);
	/* 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);
	/* to terminate pending flushes */
	clear_bit(WDM_IN_USE, &desc->flags);
	spin_unlock_irqrestore(&desc->iuspin, flags);
	wake_up_all(&desc->wait);
	mutex_lock(&desc->rlock);
	mutex_lock(&desc->wlock);
	kill_urbs(desc);
	cancel_work_sync(&desc->rxwork);
	mutex_unlock(&desc->wlock);
	mutex_unlock(&desc->rlock);
	/* the desc->intf pointer used as list key is now invalid */
	spin_lock(&wdm_device_list_lock);
	list_del(&desc->device_list);
	spin_unlock(&wdm_device_list_lock);
	if (!desc->count)
		cleanup(desc);
	else
		dev_dbg(&intf->dev, "%d open files - postponing cleanup\n", desc->count);
	mutex_unlock(&wdm_mutex);
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 143 | 71.50% | 5 | 50.00% | 
| bjorn mork | bjorn mork | 57 | 28.50% | 5 | 50.00% | 
 | Total | 200 | 100.00% | 10 | 100.00% | 
#ifdef CONFIG_PM
static int wdm_suspend(struct usb_interface *intf, pm_message_t message)
{
	struct wdm_device *desc = wdm_find_device(intf);
	int rv = 0;
	dev_dbg(&desc->intf->dev, "wdm%d_suspend\n", intf->minor);
	/* if this is an autosuspend the caller does the locking */
	if (!PMSG_IS_AUTO(message)) {
		mutex_lock(&desc->rlock);
		mutex_lock(&desc->wlock);
	}
	spin_lock_irq(&desc->iuspin);
	if (PMSG_IS_AUTO(message) &&
			(test_bit(WDM_IN_USE, &desc->flags)
			|| test_bit(WDM_RESPONDING, &desc->flags))) {
		spin_unlock_irq(&desc->iuspin);
		rv = -EBUSY;
	} else {
		set_bit(WDM_SUSPENDING, &desc->flags);
		spin_unlock_irq(&desc->iuspin);
		/* callback submits work - order is essential */
		kill_urbs(desc);
		cancel_work_sync(&desc->rxwork);
	}
	if (!PMSG_IS_AUTO(message)) {
		mutex_unlock(&desc->wlock);
		mutex_unlock(&desc->rlock);
	}
	return rv;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 162 | 86.17% | 5 | 55.56% | 
| bjorn mork | bjorn mork | 16 | 8.51% | 2 | 22.22% | 
| alan stern | alan stern | 10 | 5.32% | 2 | 22.22% | 
 | Total | 188 | 100.00% | 9 | 100.00% | 
#endif
static int recover_from_urb_loss(struct wdm_device *desc)
{
	int rv = 0;
	if (desc->count) {
		rv = usb_submit_urb(desc->validity, GFP_NOIO);
		if (rv < 0)
			dev_err(&desc->intf->dev,
				"Error resume submitting int urb - %d\n", rv);
	}
	return rv;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 49 | 84.48% | 1 | 50.00% | 
| greg kroah-hartman | greg kroah-hartman | 9 | 15.52% | 1 | 50.00% | 
 | Total | 58 | 100.00% | 2 | 100.00% | 
#ifdef CONFIG_PM
static int wdm_resume(struct usb_interface *intf)
{
	struct wdm_device *desc = wdm_find_device(intf);
	int rv;
	dev_dbg(&desc->intf->dev, "wdm%d_resume\n", intf->minor);
	clear_bit(WDM_SUSPENDING, &desc->flags);
	rv = recover_from_urb_loss(desc);
	return rv;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 59 | 98.33% | 3 | 75.00% | 
| bjorn mork | bjorn mork | 1 | 1.67% | 1 | 25.00% | 
 | Total | 60 | 100.00% | 4 | 100.00% | 
#endif
static int wdm_pre_reset(struct usb_interface *intf)
{
	struct wdm_device *desc = wdm_find_device(intf);
	/*
         * we notify everybody using poll of
         * an exceptional situation
         * must be done before recovery lest a spontaneous
         * message from the device is lost
         */
	spin_lock_irq(&desc->iuspin);
	set_bit(WDM_RESETTING, &desc->flags);	/* inform read/write */
	set_bit(WDM_READ, &desc->flags);	/* unblock read */
	clear_bit(WDM_IN_USE, &desc->flags);	/* unblock write */
	desc->rerr = -EINTR;
	spin_unlock_irq(&desc->iuspin);
	wake_up_all(&desc->wait);
	mutex_lock(&desc->rlock);
	mutex_lock(&desc->wlock);
	kill_urbs(desc);
	cancel_work_sync(&desc->rxwork);
	return 0;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 62 | 52.54% | 2 | 40.00% | 
| bjorn mork | bjorn mork | 56 | 47.46% | 3 | 60.00% | 
 | Total | 118 | 100.00% | 5 | 100.00% | 
static int wdm_post_reset(struct usb_interface *intf)
{
	struct wdm_device *desc = wdm_find_device(intf);
	int rv;
	clear_bit(WDM_OVERFLOW, &desc->flags);
	clear_bit(WDM_RESETTING, &desc->flags);
	rv = recover_from_urb_loss(desc);
	mutex_unlock(&desc->wlock);
	mutex_unlock(&desc->rlock);
	return 0;
}
Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 50 | 71.43% | 2 | 40.00% | 
| bjorn mork | bjorn mork | 20 | 28.57% | 3 | 60.00% | 
 | Total | 70 | 100.00% | 5 | 100.00% | 
static struct usb_driver wdm_driver = {
	.name =		"cdc_wdm",
	.probe =	wdm_probe,
	.disconnect =	wdm_disconnect,
#ifdef CONFIG_PM
	.suspend =	wdm_suspend,
	.resume =	wdm_resume,
	.reset_resume =	wdm_resume,
#endif
	.pre_reset =	wdm_pre_reset,
	.post_reset =	wdm_post_reset,
	.id_table =	wdm_ids,
	.supports_autosuspend = 1,
	.disable_hub_initiated_lpm = 1,
};
module_usb_driver(wdm_driver);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
Overall Contributors
 | Person | Tokens | Prop | Commits | CommitProp | 
| oliver neukum | oliver neukum | 3843 | 71.40% | 25 | 40.98% | 
| bjorn mork | bjorn mork | 1259 | 23.39% | 23 | 37.70% | 
| greg kroah-hartman | greg kroah-hartman | 118 | 2.19% | 2 | 3.28% | 
| robert foss | robert foss | 66 | 1.23% | 1 | 1.64% | 
| ben hutchings | ben hutchings | 37 | 0.69% | 1 | 1.64% | 
| greg suarez | greg suarez | 31 | 0.58% | 1 | 1.64% | 
| alan stern | alan stern | 10 | 0.19% | 2 | 3.28% | 
| arnd bergmann | arnd bergmann | 6 | 0.11% | 1 | 1.64% | 
| sarah sharp | sarah sharp | 5 | 0.09% | 1 | 1.64% | 
| david sterba | david sterba | 3 | 0.06% | 1 | 1.64% | 
| al viro | al viro | 2 | 0.04% | 1 | 1.64% | 
| marton nemeth | marton nemeth | 1 | 0.02% | 1 | 1.64% | 
| kuninori morimoto | kuninori morimoto | 1 | 0.02% | 1 | 1.64% | 
 | Total | 5382 | 100.00% | 61 | 100.00% |