diff options
Diffstat (limited to 'drivers/usb/class/cdc-wdm.c')
| -rw-r--r-- | drivers/usb/class/cdc-wdm.c | 562 | 
1 files changed, 385 insertions, 177 deletions
diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 6ee4451bfe2..a051a7a2b1b 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -13,6 +13,7 @@   */  #include <linux/kernel.h>  #include <linux/errno.h> +#include <linux/ioctl.h>  #include <linux/slab.h>  #include <linux/module.h>  #include <linux/mutex.h> @@ -23,6 +24,7 @@  #include <linux/usb/cdc.h>  #include <asm/byteorder.h>  #include <asm/unaligned.h> +#include <linux/usb/cdc-wdm.h>  /*   * Version Information @@ -54,11 +56,17 @@ MODULE_DEVICE_TABLE (usb, wdm_ids);  #define WDM_POLL_RUNNING	6  #define WDM_RESPONDING		7  #define WDM_SUSPENDING		8 +#define WDM_RESETTING		9 +#define WDM_OVERFLOW		10  #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 --- */ @@ -80,7 +88,6 @@ struct wdm_device {  	u16			bufsize;  	u16			wMaxCommand;  	u16			wMaxPacketSize; -	u16			bMaxPacketSize0;  	__le16			inum;  	int			reslength;  	int			length; @@ -88,15 +95,51 @@ struct wdm_device {  	int			count;  	dma_addr_t		shandle;  	dma_addr_t		ihandle; -	struct mutex		lock; +	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; +} + +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; +} +  /* --- callbacks --- */  static void wdm_out_callback(struct urb *urb)  { @@ -105,8 +148,9 @@ static void wdm_out_callback(struct urb *urb)  	spin_lock(&desc->iuspin);  	desc->werr = urb->status;  	spin_unlock(&desc->iuspin); -	clear_bit(WDM_IN_USE, &desc->flags);  	kfree(desc->outbuf); +	desc->outbuf = NULL; +	clear_bit(WDM_IN_USE, &desc->flags);  	wake_up(&desc->wait);  } @@ -114,6 +158,7 @@ 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); @@ -144,9 +189,17 @@ static void wdm_in_callback(struct urb *urb)  	}  	desc->rerr = status; -	desc->reslength = urb->actual_length; -	memmove(desc->ubuf + desc->length, desc->inbuf, desc->reslength); -	desc->length += desc->reslength; +	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; +		} +	}  skip_error:  	wake_up(&desc->wait); @@ -157,13 +210,12 @@ skip_error:  static void wdm_int_callback(struct urb *urb)  {  	int rv = 0; +	int responding;  	int status = urb->status;  	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 (status) { @@ -202,6 +254,10 @@ static void wdm_int_callback(struct urb *urb)  			"NOTIFY_NETWORK_CONNECTION %s network",  			dr->wValue ? "connected to" : "disconnected from");  		goto exit; +	case USB_CDC_NOTIFY_SPEED_CHANGE: +		dev_dbg(&desc->intf->dev, "SPEED_CHANGE received (len %u)", +			urb->actual_length); +		goto exit;  	default:  		clear_bit(WDM_POLL_RUNNING, &desc->flags);  		dev_err(&desc->intf->dev, @@ -210,28 +266,10 @@ static void wdm_int_callback(struct urb *urb)  		goto exit;  	} -	req->bRequestType = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE); -	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), -		/* using common endpoint 0 */ -		usb_rcvctrlpipe(interface_to_usbdev(desc->intf), 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); -	set_bit(WDM_RESPONDING, &desc->flags); -	if (!test_bit(WDM_DISCONNECTING, &desc->flags) +	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, "%s: usb_submit_urb %d", @@ -276,14 +314,8 @@ static void free_urbs(struct wdm_device *desc)  static void cleanup(struct wdm_device *desc)  { -	usb_free_coherent(interface_to_usbdev(desc->intf), -			  desc->wMaxPacketSize, -			  desc->sbuf, -			  desc->validity->transfer_dma); -	usb_free_coherent(interface_to_usbdev(desc->intf), -			  desc->wMaxCommand, -			  desc->inbuf, -			  desc->response->transfer_dma); +	kfree(desc->sbuf); +	kfree(desc->inbuf);  	kfree(desc->orq);  	kfree(desc->irq);  	kfree(desc->ubuf); @@ -309,7 +341,7 @@ static ssize_t wdm_write  	if (we < 0)  		return -EIO; -	desc->outbuf = buf = kmalloc(count, GFP_KERNEL); +	buf = kmalloc(count, GFP_KERNEL);  	if (!buf) {  		rv = -ENOMEM;  		goto outnl; @@ -323,7 +355,7 @@ static ssize_t wdm_write  	}  	/* concurrent writes and disconnect */ -	r = mutex_lock_interruptible(&desc->lock); +	r = mutex_lock_interruptible(&desc->wlock);  	rv = -ERESTARTSYS;  	if (r) {  		kfree(buf); @@ -339,17 +371,23 @@ static ssize_t wdm_write  	r = usb_autopm_get_interface(desc->intf);  	if (r < 0) {  		kfree(buf); +		rv = usb_translate_errors(r);  		goto outnp;  	} -	if (!file->f_flags && O_NONBLOCK) +	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) {  		kfree(buf); +		rv = r;  		goto out;  	} @@ -373,12 +411,15 @@ static ssize_t wdm_write  	req->wIndex = desc->inum;  	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) {  		kfree(buf); +		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);  	} else {  		dev_dbg(&desc->intf->dev, "Tx URB has been submitted index=%d",  			req->wIndex); @@ -386,30 +427,68 @@ static ssize_t wdm_write  out:  	usb_autopm_put_interface(desc->intf);  outnp: -	mutex_unlock(&desc->lock); +	mutex_unlock(&desc->wlock);  outnl:  	return rv < 0 ? rv : count;  } +/* + * clear WDM_READ flag and possibly submit the read urb if resp_count + * is non-zero. + * + * Called with desc->iuspin locked + */ +static int clear_wdm_read_flag(struct wdm_device *desc) +{ +	int rv = 0; + +	clear_bit(WDM_READ, &desc->flags); + +	/* 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; +} +  static ssize_t wdm_read  (struct file *file, char __user *buffer, size_t count, loff_t *ppos)  { -	int rv, cntr = 0; +	int rv, cntr;  	int i = 0;  	struct wdm_device *desc = file->private_data; -	rv = mutex_lock_interruptible(&desc->lock); /*concurrent reads */ +	rv = mutex_lock_interruptible(&desc->rlock); /*concurrent reads */  	if (rv < 0)  		return -ERESTARTSYS; -	if (desc->length == 0) { +	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)) { @@ -427,6 +506,10 @@ retry:  			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; @@ -449,32 +532,41 @@ retry:  			spin_unlock_irq(&desc->iuspin);  			goto retry;  		} +  		if (!desc->reslength) { /* zero length read */ +			dev_dbg(&desc->intf->dev, "%s: zero length - clearing WDM_READ\n", __func__); +			rv = clear_wdm_read_flag(desc);  			spin_unlock_irq(&desc->iuspin); +			if (rv < 0) +				goto err;  			goto retry;  		} -		clear_bit(WDM_READ, &desc->flags); +		cntr = desc->length;  		spin_unlock_irq(&desc->iuspin);  	} -	cntr = count > desc->length ? desc->length : count; +	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); +		clear_wdm_read_flag(desc); +	spin_unlock_irq(&desc->iuspin);  	rv = cntr;  err: -	mutex_unlock(&desc->lock); +	mutex_unlock(&desc->rlock);  	return rv;  } @@ -483,11 +575,13 @@ 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) + +	/* 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 desc->werr; +	return usb_translate_errors(desc->werr);  }  static unsigned int wdm_poll(struct file *file, struct poll_table_struct *wait) @@ -498,7 +592,7 @@ static unsigned int wdm_poll(struct file *file, struct poll_table_struct *wait)  	spin_lock_irqsave(&desc->iuspin, flags);  	if (test_bit(WDM_DISCONNECTING, &desc->flags)) { -		mask = POLLERR; +		mask = POLLHUP | POLLERR;  		spin_unlock_irqrestore(&desc->iuspin, flags);  		goto desc_out;  	} @@ -524,11 +618,11 @@ static int wdm_open(struct inode *inode, struct file *file)  	struct wdm_device *desc;  	mutex_lock(&wdm_mutex); -	intf = usb_find_interface(&wdm_driver, minor); -	if (!intf) +	desc = wdm_find_device_by_minor(minor); +	if (!desc)  		goto out; -	desc = usb_get_intfdata(intf); +	intf = desc->intf;  	if (test_bit(WDM_DISCONNECTING, &desc->flags))  		goto out;  	file->private_data = desc; @@ -538,20 +632,25 @@ static int wdm_open(struct inode *inode, struct file *file)  		dev_err(&desc->intf->dev, "Error autopm - %d\n", rv);  		goto out;  	} -	intf->needs_remote_wakeup = 1; -	mutex_lock(&desc->lock); +	/* 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 {  		rv = 0;  	} -	mutex_unlock(&desc->lock); +	mutex_unlock(&desc->wlock); +	if (desc->count == 1) +		desc->manage_power(intf, 1);  	usb_autopm_put_interface(desc->intf);  out:  	mutex_unlock(&wdm_mutex); @@ -563,20 +662,46 @@ static int wdm_release(struct inode *inode, struct file *file)  	struct wdm_device *desc = file->private_data;  	mutex_lock(&wdm_mutex); -	mutex_lock(&desc->lock); + +	/* using write lock to protect desc->count */ +	mutex_lock(&desc->wlock);  	desc->count--; -	mutex_unlock(&desc->lock); +	mutex_unlock(&desc->wlock);  	if (!desc->count) { -		dev_dbg(&desc->intf->dev, "wdm_release: cleanup"); -		kill_urbs(desc); -		if (!test_bit(WDM_DISCONNECTING, &desc->flags)) -			desc->intf->needs_remote_wakeup = 0; +		if (!test_bit(WDM_DISCONNECTING, &desc->flags)) { +			dev_dbg(&desc->intf->dev, "wdm_release: cleanup"); +			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;  } +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; +} +  static const struct file_operations wdm_fops = {  	.owner =	THIS_MODULE,  	.read =		wdm_read, @@ -585,6 +710,8 @@ static const struct file_operations wdm_fops = {  	.flush =	wdm_flush,  	.release =	wdm_release,  	.poll =		wdm_poll, +	.unlocked_ioctl = wdm_ioctl, +	.compat_ioctl = wdm_ioctl,  	.llseek =	noop_llseek,  }; @@ -599,16 +726,20 @@ static void wdm_rxwork(struct work_struct *work)  {  	struct wdm_device *desc = container_of(work, struct wdm_device, rxwork);  	unsigned long flags; -	int rv; +	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); -		rv = usb_submit_urb(desc->response, GFP_KERNEL); +		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); @@ -618,70 +749,31 @@ static void wdm_rxwork(struct work_struct *work)  /* --- hotplug --- */ -static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id) +static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep, +		u16 bufsize, int (*manage_power)(struct usb_interface *, int))  { -	int rv = -EINVAL; -	struct usb_device *udev = interface_to_usbdev(intf); +	int rv = -ENOMEM;  	struct wdm_device *desc; -	struct usb_host_interface *iface; -	struct usb_endpoint_descriptor *ep; -	struct usb_cdc_dmm_desc *dmhd; -	u8 *buffer = intf->altsetting->extra; -	int buflen = intf->altsetting->extralen; -	u16 maxcom = 0; - -	if (!buffer) -		goto out; - -	while (buflen > 2) { -		if (buffer [1] != USB_DT_CS_INTERFACE) { -			dev_err(&intf->dev, "skipping garbage\n"); -			goto next_desc; -		} -		switch (buffer [2]) { -		case USB_CDC_HEADER_TYPE: -			break; -		case USB_CDC_DMM_TYPE: -			dmhd = (struct usb_cdc_dmm_desc *)buffer; -			maxcom = le16_to_cpu(dmhd->wMaxCommand); -			dev_dbg(&intf->dev, -				"Finding maximum buffer length: %d", maxcom); -			break; -		default: -			dev_err(&intf->dev, -				"Ignoring extra header, type %d, length %d\n", -				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->lock); +	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 = maxcom; +	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; -	iface = intf->cur_altsetting; -	if (iface->desc.bNumEndpoints != 1) -		goto err; -	ep = &iface->endpoint[0].desc; -	if (!ep || !usb_endpoint_is_int_in(ep)) +	if (!usb_endpoint_is_int_in(ep))  		goto err; -	desc->wMaxPacketSize = le16_to_cpu(ep->wMaxPacketSize); -	desc->bMaxPacketSize0 = udev->descriptor.bMaxPacketSize0; +	desc->wMaxPacketSize = usb_endpoint_maxp(ep);  	desc->orq = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);  	if (!desc->orq) @@ -706,19 +798,13 @@ next_desc:  	if (!desc->ubuf)  		goto err; -	desc->sbuf = usb_alloc_coherent(interface_to_usbdev(intf), -					desc->wMaxPacketSize, -					GFP_KERNEL, -					&desc->validity->transfer_dma); +	desc->sbuf = kmalloc(desc->wMaxPacketSize, GFP_KERNEL);  	if (!desc->sbuf)  		goto err; -	desc->inbuf = usb_alloc_coherent(interface_to_usbdev(intf), -					 desc->bMaxPacketSize0, -					 GFP_KERNEL, -					 &desc->response->transfer_dma); +	desc->inbuf = kmalloc(desc->wMaxCommand, GFP_KERNEL);  	if (!desc->inbuf) -		goto err2; +		goto err;  	usb_fill_int_urb(  		desc->validity, @@ -730,45 +816,150 @@ next_desc:  		desc,  		ep->bInterval  	); -	desc->validity->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; -	usb_set_intfdata(intf, desc); +	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; +	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; + +	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 err3; +		goto err;  	else -		dev_info(&intf->dev, "cdc-wdm%d: USB WDM device\n", -			intf->minor - WDM_MINOR_BASE); +		dev_info(&intf->dev, "%s: USB WDM device\n", dev_name(intf->usb_dev));  out:  	return rv; -err3: -	usb_set_intfdata(intf, NULL); -	usb_free_coherent(interface_to_usbdev(desc->intf), -			  desc->bMaxPacketSize0, -			desc->inbuf, -			desc->response->transfer_dma); -err2: -	usb_free_coherent(interface_to_usbdev(desc->intf), -			  desc->wMaxPacketSize, -			  desc->sbuf, -			  desc->validity->transfer_dma);  err: -	free_urbs(desc); -	kfree(desc->ubuf); -	kfree(desc->orq); -	kfree(desc->irq); -	kfree(desc); +	spin_lock(&wdm_device_list_lock); +	list_del(&desc->device_list); +	spin_unlock(&wdm_device_list_lock); +	cleanup(desc);  	return rv;  } +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; +} + +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_dmm_desc *dmhd; +	u8 *buffer = intf->altsetting->extra; +	int buflen = intf->altsetting->extralen; +	u16 maxcom = WDM_DEFAULT_BUFSIZE; + +	if (!buffer) +		goto err; +	while (buflen > 2) { +		if (buffer[1] != USB_DT_CS_INTERFACE) { +			dev_err(&intf->dev, "skipping garbage\n"); +			goto next_desc; +		} + +		switch (buffer[2]) { +		case USB_CDC_HEADER_TYPE: +			break; +		case USB_CDC_DMM_TYPE: +			dmhd = (struct usb_cdc_dmm_desc *)buffer; +			maxcom = le16_to_cpu(dmhd->wMaxCommand); +			dev_dbg(&intf->dev, +				"Finding maximum buffer length: %d", maxcom); +			break; +		default: +			dev_err(&intf->dev, +				"Ignoring extra header, type %d, length %d\n", +				buffer[2], buffer[0]); +			break; +		} +next_desc: +		buflen -= buffer[0]; +		buffer += buffer[0]; +	} + +	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); + +err: +	return rv; +} + +/** + * 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); +	if (rv < 0) +		goto err; + +	return &wdm_driver; +err: +	return ERR_PTR(rv); +} +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); -	desc = usb_get_intfdata(intf);  	/* the spinlock makes sure no new urbs are generated in the callbacks */  	spin_lock_irqsave(&desc->iuspin, flags); @@ -777,30 +968,42 @@ static void wdm_disconnect(struct usb_interface *intf)  	/* to terminate pending flushes */  	clear_bit(WDM_IN_USE, &desc->flags);  	spin_unlock_irqrestore(&desc->iuspin, flags); -	mutex_lock(&desc->lock); +	wake_up_all(&desc->wait); +	mutex_lock(&desc->rlock); +	mutex_lock(&desc->wlock);  	kill_urbs(desc);  	cancel_work_sync(&desc->rxwork); -	mutex_unlock(&desc->lock); -	wake_up_all(&desc->wait); +	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, "%s: %d open files - postponing cleanup\n", __func__, desc->count);  	mutex_unlock(&wdm_mutex);  }  #ifdef CONFIG_PM  static int wdm_suspend(struct usb_interface *intf, pm_message_t message)  { -	struct wdm_device *desc = usb_get_intfdata(intf); +	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 (!(message.event & PM_EVENT_AUTO)) -		mutex_lock(&desc->lock); +	if (!PMSG_IS_AUTO(message)) { +		mutex_lock(&desc->rlock); +		mutex_lock(&desc->wlock); +	}  	spin_lock_irq(&desc->iuspin); -	if ((message.event & PM_EVENT_AUTO) && +	if (PMSG_IS_AUTO(message) &&  			(test_bit(WDM_IN_USE, &desc->flags)  			|| test_bit(WDM_RESPONDING, &desc->flags))) {  		spin_unlock_irq(&desc->iuspin); @@ -813,8 +1016,10 @@ static int wdm_suspend(struct usb_interface *intf, pm_message_t message)  		kill_urbs(desc);  		cancel_work_sync(&desc->rxwork);  	} -	if (!(message.event & PM_EVENT_AUTO)) -		mutex_unlock(&desc->lock); +	if (!PMSG_IS_AUTO(message)) { +		mutex_unlock(&desc->wlock); +		mutex_unlock(&desc->rlock); +	}  	return rv;  } @@ -836,7 +1041,7 @@ static int recover_from_urb_loss(struct wdm_device *desc)  #ifdef CONFIG_PM  static int wdm_resume(struct usb_interface *intf)  { -	struct wdm_device *desc = usb_get_intfdata(intf); +	struct wdm_device *desc = wdm_find_device(intf);  	int rv;  	dev_dbg(&desc->intf->dev, "wdm%d_resume\n", intf->minor); @@ -850,19 +1055,38 @@ static int wdm_resume(struct usb_interface *intf)  static int wdm_pre_reset(struct usb_interface *intf)  { -	struct wdm_device *desc = usb_get_intfdata(intf); - -	mutex_lock(&desc->lock); +	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;  }  static int wdm_post_reset(struct usb_interface *intf)  { -	struct wdm_device *desc = usb_get_intfdata(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->lock); +	mutex_unlock(&desc->wlock); +	mutex_unlock(&desc->rlock);  	return 0;  } @@ -879,26 +1103,10 @@ static struct usb_driver wdm_driver = {  	.post_reset =	wdm_post_reset,  	.id_table =	wdm_ids,  	.supports_autosuspend = 1, +	.disable_hub_initiated_lpm = 1,  }; -/* --- 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_usb_driver(wdm_driver);  MODULE_AUTHOR(DRIVER_AUTHOR);  MODULE_DESCRIPTION(DRIVER_DESC);  | 
