xref: /linux/drivers/usb/class/cdc-wdm.c (revision 18abf874367456540846319574864e6ff32752e2)
15fd54aceSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
2afba937eSOliver Neukum /*
3afba937eSOliver Neukum  * cdc-wdm.c
4afba937eSOliver Neukum  *
5afba937eSOliver Neukum  * This driver supports USB CDC WCM Device Management.
6afba937eSOliver Neukum  *
7052fbc0dSOliver Neukum  * Copyright (c) 2007-2009 Oliver Neukum
8afba937eSOliver Neukum  *
9afba937eSOliver Neukum  * Some code taken from cdc-acm.c
10afba937eSOliver Neukum  *
11afba937eSOliver Neukum  * Released under the GPLv2.
12afba937eSOliver Neukum  *
13afba937eSOliver Neukum  * Many thanks to Carl Nordbeck
14afba937eSOliver Neukum  */
15afba937eSOliver Neukum #include <linux/kernel.h>
16afba937eSOliver Neukum #include <linux/errno.h>
173edce1cfSBjørn Mork #include <linux/ioctl.h>
18afba937eSOliver Neukum #include <linux/slab.h>
19afba937eSOliver Neukum #include <linux/module.h>
20afba937eSOliver Neukum #include <linux/mutex.h>
21afba937eSOliver Neukum #include <linux/uaccess.h>
22afba937eSOliver Neukum #include <linux/bitops.h>
23afba937eSOliver Neukum #include <linux/poll.h>
24afba937eSOliver Neukum #include <linux/usb.h>
25afba937eSOliver Neukum #include <linux/usb/cdc.h>
26afba937eSOliver Neukum #include <asm/byteorder.h>
27afba937eSOliver Neukum #include <asm/unaligned.h>
283cc36157SBjørn Mork #include <linux/usb/cdc-wdm.h>
29afba937eSOliver Neukum 
30afba937eSOliver Neukum #define DRIVER_AUTHOR "Oliver Neukum"
3187d65e54SOliver Neukum #define DRIVER_DESC "USB Abstract Control Model driver for USB WCM Device Management"
32afba937eSOliver Neukum 
336ef4852bSNémeth Márton static const struct usb_device_id wdm_ids[] = {
34afba937eSOliver Neukum 	{
35afba937eSOliver Neukum 		.match_flags = USB_DEVICE_ID_MATCH_INT_CLASS |
36afba937eSOliver Neukum 				 USB_DEVICE_ID_MATCH_INT_SUBCLASS,
37afba937eSOliver Neukum 		.bInterfaceClass = USB_CLASS_COMM,
38afba937eSOliver Neukum 		.bInterfaceSubClass = USB_CDC_SUBCLASS_DMM
39afba937eSOliver Neukum 	},
40afba937eSOliver Neukum 	{ }
41afba937eSOliver Neukum };
42afba937eSOliver Neukum 
43aa5380b9SOliver Neukum MODULE_DEVICE_TABLE (usb, wdm_ids);
44aa5380b9SOliver Neukum 
45afba937eSOliver Neukum #define WDM_MINOR_BASE	176
46afba937eSOliver Neukum 
47afba937eSOliver Neukum 
48afba937eSOliver Neukum #define WDM_IN_USE		1
49afba937eSOliver Neukum #define WDM_DISCONNECTING	2
50afba937eSOliver Neukum #define WDM_RESULT		3
51afba937eSOliver Neukum #define WDM_READ		4
52afba937eSOliver Neukum #define WDM_INT_STALL		5
53afba937eSOliver Neukum #define WDM_POLL_RUNNING	6
54922a5eadSOliver Neukum #define WDM_RESPONDING		7
55beb1d35fSOliver Neukum #define WDM_SUSPENDING		8
5688044202SBjørn Mork #define WDM_RESETTING		9
57c0f5eceeSOliver Neukum #define WDM_OVERFLOW		10
58afba937eSOliver Neukum 
59afba937eSOliver Neukum #define WDM_MAX			16
60afba937eSOliver Neukum 
6137d2a363SOliver Neukum /* we cannot wait forever at flush() */
6237d2a363SOliver Neukum #define WDM_FLUSH_TIMEOUT	(30 * HZ)
6337d2a363SOliver Neukum 
647e3054a0SBjørn Mork /* CDC-WMC r1.1 requires wMaxCommand to be "at least 256 decimal (0x100)" */
657e3054a0SBjørn Mork #define WDM_DEFAULT_BUFSIZE	256
66afba937eSOliver Neukum 
67afba937eSOliver Neukum static DEFINE_MUTEX(wdm_mutex);
68b0c13860SBjørn Mork static DEFINE_SPINLOCK(wdm_device_list_lock);
69b0c13860SBjørn Mork static LIST_HEAD(wdm_device_list);
70afba937eSOliver Neukum 
71afba937eSOliver Neukum /* --- method tables --- */
72afba937eSOliver Neukum 
73afba937eSOliver Neukum struct wdm_device {
74afba937eSOliver Neukum 	u8			*inbuf; /* buffer for response */
75afba937eSOliver Neukum 	u8			*outbuf; /* buffer for command */
76afba937eSOliver Neukum 	u8			*sbuf; /* buffer for status */
77afba937eSOliver Neukum 	u8			*ubuf; /* buffer for copy to user space */
78afba937eSOliver Neukum 
79afba937eSOliver Neukum 	struct urb		*command;
80afba937eSOliver Neukum 	struct urb		*response;
81afba937eSOliver Neukum 	struct urb		*validity;
82afba937eSOliver Neukum 	struct usb_interface	*intf;
83afba937eSOliver Neukum 	struct usb_ctrlrequest	*orq;
84afba937eSOliver Neukum 	struct usb_ctrlrequest	*irq;
85afba937eSOliver Neukum 	spinlock_t		iuspin;
86afba937eSOliver Neukum 
87afba937eSOliver Neukum 	unsigned long		flags;
88afba937eSOliver Neukum 	u16			bufsize;
89afba937eSOliver Neukum 	u16			wMaxCommand;
90afba937eSOliver Neukum 	u16			wMaxPacketSize;
91afba937eSOliver Neukum 	__le16			inum;
92afba937eSOliver Neukum 	int			reslength;
93afba937eSOliver Neukum 	int			length;
94afba937eSOliver Neukum 	int			read;
95afba937eSOliver Neukum 	int			count;
96afba937eSOliver Neukum 	dma_addr_t		shandle;
97afba937eSOliver Neukum 	dma_addr_t		ihandle;
98e8537bd2SBjørn Mork 	struct mutex		wlock;
99e8537bd2SBjørn Mork 	struct mutex		rlock;
100afba937eSOliver Neukum 	wait_queue_head_t	wait;
101afba937eSOliver Neukum 	struct work_struct	rxwork;
1022df69484SSebastian Andrzej Siewior 	struct work_struct	service_outs_intr;
103afba937eSOliver Neukum 	int			werr;
104afba937eSOliver Neukum 	int			rerr;
10573e06865SGreg Suarez 	int                     resp_count;
106b0c13860SBjørn Mork 
107b0c13860SBjørn Mork 	struct list_head	device_list;
1083cc36157SBjørn Mork 	int			(*manage_power)(struct usb_interface *, int);
109afba937eSOliver Neukum };
110afba937eSOliver Neukum 
111afba937eSOliver Neukum static struct usb_driver wdm_driver;
112afba937eSOliver Neukum 
113b0c13860SBjørn Mork /* return intfdata if we own the interface, else look up intf in the list */
114b0c13860SBjørn Mork static struct wdm_device *wdm_find_device(struct usb_interface *intf)
115b0c13860SBjørn Mork {
1166a448868SBjørn Mork 	struct wdm_device *desc;
117b0c13860SBjørn Mork 
118b0c13860SBjørn Mork 	spin_lock(&wdm_device_list_lock);
119b0c13860SBjørn Mork 	list_for_each_entry(desc, &wdm_device_list, device_list)
120b0c13860SBjørn Mork 		if (desc->intf == intf)
1216a448868SBjørn Mork 			goto found;
1226a448868SBjørn Mork 	desc = NULL;
1236a448868SBjørn Mork found:
124b0c13860SBjørn Mork 	spin_unlock(&wdm_device_list_lock);
125b0c13860SBjørn Mork 
126b0c13860SBjørn Mork 	return desc;
127b0c13860SBjørn Mork }
128b0c13860SBjørn Mork 
129b0c13860SBjørn Mork static struct wdm_device *wdm_find_device_by_minor(int minor)
130b0c13860SBjørn Mork {
1316a448868SBjørn Mork 	struct wdm_device *desc;
132b0c13860SBjørn Mork 
133b0c13860SBjørn Mork 	spin_lock(&wdm_device_list_lock);
134b0c13860SBjørn Mork 	list_for_each_entry(desc, &wdm_device_list, device_list)
135b0c13860SBjørn Mork 		if (desc->intf->minor == minor)
1366a448868SBjørn Mork 			goto found;
1376a448868SBjørn Mork 	desc = NULL;
1386a448868SBjørn Mork found:
139b0c13860SBjørn Mork 	spin_unlock(&wdm_device_list_lock);
140b0c13860SBjørn Mork 
141b0c13860SBjørn Mork 	return desc;
142b0c13860SBjørn Mork }
143b0c13860SBjørn Mork 
144afba937eSOliver Neukum /* --- callbacks --- */
145afba937eSOliver Neukum static void wdm_out_callback(struct urb *urb)
146afba937eSOliver Neukum {
147afba937eSOliver Neukum 	struct wdm_device *desc;
148579b9ccaSSebastian Andrzej Siewior 	unsigned long flags;
149579b9ccaSSebastian Andrzej Siewior 
150afba937eSOliver Neukum 	desc = urb->context;
151579b9ccaSSebastian Andrzej Siewior 	spin_lock_irqsave(&desc->iuspin, flags);
152afba937eSOliver Neukum 	desc->werr = urb->status;
153579b9ccaSSebastian Andrzej Siewior 	spin_unlock_irqrestore(&desc->iuspin, flags);
154afba937eSOliver Neukum 	kfree(desc->outbuf);
1555c22837aSOliver Neukum 	desc->outbuf = NULL;
1565c22837aSOliver Neukum 	clear_bit(WDM_IN_USE, &desc->flags);
15737d2a363SOliver Neukum 	wake_up_all(&desc->wait);
158afba937eSOliver Neukum }
159afba937eSOliver Neukum 
160afba937eSOliver Neukum static void wdm_in_callback(struct urb *urb)
161afba937eSOliver Neukum {
162579b9ccaSSebastian Andrzej Siewior 	unsigned long flags;
163afba937eSOliver Neukum 	struct wdm_device *desc = urb->context;
164afba937eSOliver Neukum 	int status = urb->status;
165c0f5eceeSOliver Neukum 	int length = urb->actual_length;
166afba937eSOliver Neukum 
167579b9ccaSSebastian Andrzej Siewior 	spin_lock_irqsave(&desc->iuspin, flags);
168922a5eadSOliver Neukum 	clear_bit(WDM_RESPONDING, &desc->flags);
169afba937eSOliver Neukum 
170afba937eSOliver Neukum 	if (status) {
171afba937eSOliver Neukum 		switch (status) {
172afba937eSOliver Neukum 		case -ENOENT:
173afba937eSOliver Neukum 			dev_dbg(&desc->intf->dev,
174ce8bb344SOliver Neukum 				"nonzero urb status received: -ENOENT\n");
175922a5eadSOliver Neukum 			goto skip_error;
176afba937eSOliver Neukum 		case -ECONNRESET:
177afba937eSOliver Neukum 			dev_dbg(&desc->intf->dev,
178ce8bb344SOliver Neukum 				"nonzero urb status received: -ECONNRESET\n");
179922a5eadSOliver Neukum 			goto skip_error;
180afba937eSOliver Neukum 		case -ESHUTDOWN:
181afba937eSOliver Neukum 			dev_dbg(&desc->intf->dev,
182ce8bb344SOliver Neukum 				"nonzero urb status received: -ESHUTDOWN\n");
183922a5eadSOliver Neukum 			goto skip_error;
184afba937eSOliver Neukum 		case -EPIPE:
18519445816SBjørn Mork 			dev_err(&desc->intf->dev,
1869908a32eSGreg Kroah-Hartman 				"nonzero urb status received: -EPIPE\n");
187afba937eSOliver Neukum 			break;
188afba937eSOliver Neukum 		default:
1899908a32eSGreg Kroah-Hartman 			dev_err(&desc->intf->dev,
1909908a32eSGreg Kroah-Hartman 				"Unexpected error %d\n", status);
191afba937eSOliver Neukum 			break;
192afba937eSOliver Neukum 		}
193afba937eSOliver Neukum 	}
194afba937eSOliver Neukum 
195c1da59daSRobert Foss 	/*
196c1da59daSRobert Foss 	 * only set a new error if there is no previous error.
197c1da59daSRobert Foss 	 * Errors are only cleared during read/open
1988fec9355SBjørn Mork 	 * Avoid propagating -EPIPE (stall) to userspace since it is
1998fec9355SBjørn Mork 	 * better handled as an empty read
200c1da59daSRobert Foss 	 */
2018fec9355SBjørn Mork 	if (desc->rerr == 0 && status != -EPIPE)
202afba937eSOliver Neukum 		desc->rerr = status;
203c1da59daSRobert Foss 
204c0f5eceeSOliver Neukum 	if (length + desc->length > desc->wMaxCommand) {
205c0f5eceeSOliver Neukum 		/* The buffer would overflow */
206c0f5eceeSOliver Neukum 		set_bit(WDM_OVERFLOW, &desc->flags);
207c0f5eceeSOliver Neukum 	} else {
208c0f5eceeSOliver Neukum 		/* we may already be in overflow */
209c0f5eceeSOliver Neukum 		if (!test_bit(WDM_OVERFLOW, &desc->flags)) {
210c0f5eceeSOliver Neukum 			memmove(desc->ubuf + desc->length, desc->inbuf, length);
211c0f5eceeSOliver Neukum 			desc->length += length;
212c0f5eceeSOliver Neukum 			desc->reslength = length;
213c0f5eceeSOliver Neukum 		}
214c0f5eceeSOliver Neukum 	}
215922a5eadSOliver Neukum skip_error:
216afba937eSOliver Neukum 
217c1da59daSRobert Foss 	if (desc->rerr) {
218c1da59daSRobert Foss 		/*
219c1da59daSRobert Foss 		 * Since there was an error, userspace may decide to not read
220c1da59daSRobert Foss 		 * any data after poll'ing.
221c1da59daSRobert Foss 		 * We should respond to further attempts from the device to send
222c1da59daSRobert Foss 		 * data, so that we can get unstuck.
223c1da59daSRobert Foss 		 */
2242df69484SSebastian Andrzej Siewior 		schedule_work(&desc->service_outs_intr);
2252df69484SSebastian Andrzej Siewior 	} else {
2262df69484SSebastian Andrzej Siewior 		set_bit(WDM_READ, &desc->flags);
2272df69484SSebastian Andrzej Siewior 		wake_up(&desc->wait);
228c1da59daSRobert Foss 	}
229579b9ccaSSebastian Andrzej Siewior 	spin_unlock_irqrestore(&desc->iuspin, flags);
230afba937eSOliver Neukum }
231afba937eSOliver Neukum 
232afba937eSOliver Neukum static void wdm_int_callback(struct urb *urb)
233afba937eSOliver Neukum {
234579b9ccaSSebastian Andrzej Siewior 	unsigned long flags;
235afba937eSOliver Neukum 	int rv = 0;
2366dd433e6SOliver Neukum 	int responding;
237afba937eSOliver Neukum 	int status = urb->status;
238afba937eSOliver Neukum 	struct wdm_device *desc;
239afba937eSOliver Neukum 	struct usb_cdc_notification *dr;
240afba937eSOliver Neukum 
241afba937eSOliver Neukum 	desc = urb->context;
242afba937eSOliver Neukum 	dr = (struct usb_cdc_notification *)desc->sbuf;
243afba937eSOliver Neukum 
244afba937eSOliver Neukum 	if (status) {
245afba937eSOliver Neukum 		switch (status) {
246afba937eSOliver Neukum 		case -ESHUTDOWN:
247afba937eSOliver Neukum 		case -ENOENT:
248afba937eSOliver Neukum 		case -ECONNRESET:
249afba937eSOliver Neukum 			return; /* unplug */
250afba937eSOliver Neukum 		case -EPIPE:
251afba937eSOliver Neukum 			set_bit(WDM_INT_STALL, &desc->flags);
2529908a32eSGreg Kroah-Hartman 			dev_err(&desc->intf->dev, "Stall on int endpoint\n");
253afba937eSOliver Neukum 			goto sw; /* halt is cleared in work */
254afba937eSOliver Neukum 		default:
2559908a32eSGreg Kroah-Hartman 			dev_err(&desc->intf->dev,
2569908a32eSGreg Kroah-Hartman 				"nonzero urb status received: %d\n", status);
257afba937eSOliver Neukum 			break;
258afba937eSOliver Neukum 		}
259afba937eSOliver Neukum 	}
260afba937eSOliver Neukum 
261afba937eSOliver Neukum 	if (urb->actual_length < sizeof(struct usb_cdc_notification)) {
2629908a32eSGreg Kroah-Hartman 		dev_err(&desc->intf->dev, "wdm_int_callback - %d bytes\n",
2639908a32eSGreg Kroah-Hartman 			urb->actual_length);
264afba937eSOliver Neukum 		goto exit;
265afba937eSOliver Neukum 	}
266afba937eSOliver Neukum 
267afba937eSOliver Neukum 	switch (dr->bNotificationType) {
268afba937eSOliver Neukum 	case USB_CDC_NOTIFY_RESPONSE_AVAILABLE:
269afba937eSOliver Neukum 		dev_dbg(&desc->intf->dev,
270ce8bb344SOliver Neukum 			"NOTIFY_RESPONSE_AVAILABLE received: index %d len %d\n",
271323ece54SOliver Neukum 			le16_to_cpu(dr->wIndex), le16_to_cpu(dr->wLength));
272afba937eSOliver Neukum 		break;
273afba937eSOliver Neukum 
274afba937eSOliver Neukum 	case USB_CDC_NOTIFY_NETWORK_CONNECTION:
275afba937eSOliver Neukum 
276afba937eSOliver Neukum 		dev_dbg(&desc->intf->dev,
277ce8bb344SOliver Neukum 			"NOTIFY_NETWORK_CONNECTION %s network\n",
278afba937eSOliver Neukum 			dr->wValue ? "connected to" : "disconnected from");
279afba937eSOliver Neukum 		goto exit;
2809983d6dcSBjørn Mork 	case USB_CDC_NOTIFY_SPEED_CHANGE:
281ce8bb344SOliver Neukum 		dev_dbg(&desc->intf->dev, "SPEED_CHANGE received (len %u)\n",
2829983d6dcSBjørn Mork 			urb->actual_length);
2839983d6dcSBjørn Mork 		goto exit;
284afba937eSOliver Neukum 	default:
285afba937eSOliver Neukum 		clear_bit(WDM_POLL_RUNNING, &desc->flags);
2869908a32eSGreg Kroah-Hartman 		dev_err(&desc->intf->dev,
2879908a32eSGreg Kroah-Hartman 			"unknown notification %d received: index %d len %d\n",
288323ece54SOliver Neukum 			dr->bNotificationType,
289323ece54SOliver Neukum 			le16_to_cpu(dr->wIndex),
290323ece54SOliver Neukum 			le16_to_cpu(dr->wLength));
291afba937eSOliver Neukum 		goto exit;
292afba937eSOliver Neukum 	}
293afba937eSOliver Neukum 
294579b9ccaSSebastian Andrzej Siewior 	spin_lock_irqsave(&desc->iuspin, flags);
2956dd433e6SOliver Neukum 	responding = test_and_set_bit(WDM_RESPONDING, &desc->flags);
29673e06865SGreg Suarez 	if (!desc->resp_count++ && !responding
29773e06865SGreg Suarez 		&& !test_bit(WDM_DISCONNECTING, &desc->flags)
298beb1d35fSOliver Neukum 		&& !test_bit(WDM_SUSPENDING, &desc->flags)) {
299afba937eSOliver Neukum 		rv = usb_submit_urb(desc->response, GFP_ATOMIC);
300ce8bb344SOliver Neukum 		dev_dbg(&desc->intf->dev, "submit response URB %d\n", rv);
301afba937eSOliver Neukum 	}
302579b9ccaSSebastian Andrzej Siewior 	spin_unlock_irqrestore(&desc->iuspin, flags);
303afba937eSOliver Neukum 	if (rv < 0) {
304922a5eadSOliver Neukum 		clear_bit(WDM_RESPONDING, &desc->flags);
305afba937eSOliver Neukum 		if (rv == -EPERM)
306afba937eSOliver Neukum 			return;
307afba937eSOliver Neukum 		if (rv == -ENOMEM) {
308afba937eSOliver Neukum sw:
309afba937eSOliver Neukum 			rv = schedule_work(&desc->rxwork);
310afba937eSOliver Neukum 			if (rv)
3119908a32eSGreg Kroah-Hartman 				dev_err(&desc->intf->dev,
3129908a32eSGreg Kroah-Hartman 					"Cannot schedule work\n");
313afba937eSOliver Neukum 		}
314afba937eSOliver Neukum 	}
315afba937eSOliver Neukum exit:
316afba937eSOliver Neukum 	rv = usb_submit_urb(urb, GFP_ATOMIC);
317afba937eSOliver Neukum 	if (rv)
3189908a32eSGreg Kroah-Hartman 		dev_err(&desc->intf->dev,
3199908a32eSGreg Kroah-Hartman 			"%s - usb_submit_urb failed with result %d\n",
320afba937eSOliver Neukum 			__func__, rv);
321afba937eSOliver Neukum 
322afba937eSOliver Neukum }
323afba937eSOliver Neukum 
324*18abf874SOliver Neukum static void poison_urbs(struct wdm_device *desc)
325afba937eSOliver Neukum {
32617d80d56SOliver Neukum 	/* the order here is essential */
327*18abf874SOliver Neukum 	usb_poison_urb(desc->command);
328*18abf874SOliver Neukum 	usb_poison_urb(desc->validity);
329*18abf874SOliver Neukum 	usb_poison_urb(desc->response);
330*18abf874SOliver Neukum }
331*18abf874SOliver Neukum 
332*18abf874SOliver Neukum static void unpoison_urbs(struct wdm_device *desc)
333*18abf874SOliver Neukum {
334*18abf874SOliver Neukum 	/*
335*18abf874SOliver Neukum 	 *  the order here is not essential
336*18abf874SOliver Neukum 	 *  it is symmetrical just to be nice
337*18abf874SOliver Neukum 	 */
338*18abf874SOliver Neukum 	usb_unpoison_urb(desc->response);
339*18abf874SOliver Neukum 	usb_unpoison_urb(desc->validity);
340*18abf874SOliver Neukum 	usb_unpoison_urb(desc->command);
341afba937eSOliver Neukum }
342afba937eSOliver Neukum 
343afba937eSOliver Neukum static void free_urbs(struct wdm_device *desc)
344afba937eSOliver Neukum {
345afba937eSOliver Neukum 	usb_free_urb(desc->validity);
346afba937eSOliver Neukum 	usb_free_urb(desc->response);
347afba937eSOliver Neukum 	usb_free_urb(desc->command);
348afba937eSOliver Neukum }
349afba937eSOliver Neukum 
350afba937eSOliver Neukum static void cleanup(struct wdm_device *desc)
351afba937eSOliver Neukum {
3528457d99cSBjørn Mork 	kfree(desc->sbuf);
3538457d99cSBjørn Mork 	kfree(desc->inbuf);
354afba937eSOliver Neukum 	kfree(desc->orq);
355afba937eSOliver Neukum 	kfree(desc->irq);
356afba937eSOliver Neukum 	kfree(desc->ubuf);
357afba937eSOliver Neukum 	free_urbs(desc);
358afba937eSOliver Neukum 	kfree(desc);
359afba937eSOliver Neukum }
360afba937eSOliver Neukum 
361afba937eSOliver Neukum static ssize_t wdm_write
362afba937eSOliver Neukum (struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
363afba937eSOliver Neukum {
364afba937eSOliver Neukum 	u8 *buf;
365afba937eSOliver Neukum 	int rv = -EMSGSIZE, r, we;
366afba937eSOliver Neukum 	struct wdm_device *desc = file->private_data;
367afba937eSOliver Neukum 	struct usb_ctrlrequest *req;
368afba937eSOliver Neukum 
369afba937eSOliver Neukum 	if (count > desc->wMaxCommand)
370afba937eSOliver Neukum 		count = desc->wMaxCommand;
371afba937eSOliver Neukum 
372afba937eSOliver Neukum 	spin_lock_irq(&desc->iuspin);
373afba937eSOliver Neukum 	we = desc->werr;
374afba937eSOliver Neukum 	desc->werr = 0;
375afba937eSOliver Neukum 	spin_unlock_irq(&desc->iuspin);
376afba937eSOliver Neukum 	if (we < 0)
37776cb03e7SOliver Neukum 		return usb_translate_errors(we);
378afba937eSOliver Neukum 
37964b9533eSGeliang Tang 	buf = memdup_user(buffer, count);
38064b9533eSGeliang Tang 	if (IS_ERR(buf))
38164b9533eSGeliang Tang 		return PTR_ERR(buf);
382860e41a7SOliver Neukum 
383860e41a7SOliver Neukum 	/* concurrent writes and disconnect */
384e8537bd2SBjørn Mork 	r = mutex_lock_interruptible(&desc->wlock);
385860e41a7SOliver Neukum 	rv = -ERESTARTSYS;
38628965e17SOliver Neukum 	if (r)
38728965e17SOliver Neukum 		goto out_free_mem;
388860e41a7SOliver Neukum 
389860e41a7SOliver Neukum 	if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
390860e41a7SOliver Neukum 		rv = -ENODEV;
39128965e17SOliver Neukum 		goto out_free_mem_lock;
392860e41a7SOliver Neukum 	}
393afba937eSOliver Neukum 
39417d80d56SOliver Neukum 	r = usb_autopm_get_interface(desc->intf);
395860e41a7SOliver Neukum 	if (r < 0) {
39612a98b2bSOliver Neukum 		rv = usb_translate_errors(r);
39728965e17SOliver Neukum 		goto out_free_mem_lock;
398860e41a7SOliver Neukum 	}
3997f1dc313SOliver Neukum 
4000cdfb819SDavid Sterba 	if (!(file->f_flags & O_NONBLOCK))
401afba937eSOliver Neukum 		r = wait_event_interruptible(desc->wait, !test_bit(WDM_IN_USE,
402afba937eSOliver Neukum 								&desc->flags));
4037f1dc313SOliver Neukum 	else
4047f1dc313SOliver Neukum 		if (test_bit(WDM_IN_USE, &desc->flags))
4057f1dc313SOliver Neukum 			r = -EAGAIN;
40688044202SBjørn Mork 
40788044202SBjørn Mork 	if (test_bit(WDM_RESETTING, &desc->flags))
40888044202SBjørn Mork 		r = -EIO;
40988044202SBjørn Mork 
41037d2a363SOliver Neukum 	if (test_bit(WDM_DISCONNECTING, &desc->flags))
41137d2a363SOliver Neukum 		r = -ENODEV;
41237d2a363SOliver Neukum 
413860e41a7SOliver Neukum 	if (r < 0) {
41412a98b2bSOliver Neukum 		rv = r;
41528965e17SOliver Neukum 		goto out_free_mem_pm;
416afba937eSOliver Neukum 	}
417afba937eSOliver Neukum 
418afba937eSOliver Neukum 	req = desc->orq;
419afba937eSOliver Neukum 	usb_fill_control_urb(
420afba937eSOliver Neukum 		desc->command,
421afba937eSOliver Neukum 		interface_to_usbdev(desc->intf),
422afba937eSOliver Neukum 		/* using common endpoint 0 */
423afba937eSOliver Neukum 		usb_sndctrlpipe(interface_to_usbdev(desc->intf), 0),
424afba937eSOliver Neukum 		(unsigned char *)req,
425afba937eSOliver Neukum 		buf,
426afba937eSOliver Neukum 		count,
427afba937eSOliver Neukum 		wdm_out_callback,
428afba937eSOliver Neukum 		desc
429afba937eSOliver Neukum 	);
430afba937eSOliver Neukum 
431afba937eSOliver Neukum 	req->bRequestType = (USB_DIR_OUT | USB_TYPE_CLASS |
432afba937eSOliver Neukum 			     USB_RECIP_INTERFACE);
433afba937eSOliver Neukum 	req->bRequest = USB_CDC_SEND_ENCAPSULATED_COMMAND;
434afba937eSOliver Neukum 	req->wValue = 0;
435323ece54SOliver Neukum 	req->wIndex = desc->inum; /* already converted */
436afba937eSOliver Neukum 	req->wLength = cpu_to_le16(count);
437afba937eSOliver Neukum 	set_bit(WDM_IN_USE, &desc->flags);
4385c22837aSOliver Neukum 	desc->outbuf = buf;
439afba937eSOliver Neukum 
440afba937eSOliver Neukum 	rv = usb_submit_urb(desc->command, GFP_KERNEL);
441afba937eSOliver Neukum 	if (rv < 0) {
4425c22837aSOliver Neukum 		desc->outbuf = NULL;
443afba937eSOliver Neukum 		clear_bit(WDM_IN_USE, &desc->flags);
44437d2a363SOliver Neukum 		wake_up_all(&desc->wait); /* for wdm_wait_for_response() */
4459908a32eSGreg Kroah-Hartman 		dev_err(&desc->intf->dev, "Tx URB error: %d\n", rv);
44612a98b2bSOliver Neukum 		rv = usb_translate_errors(rv);
44728965e17SOliver Neukum 		goto out_free_mem_pm;
448afba937eSOliver Neukum 	} else {
449ce8bb344SOliver Neukum 		dev_dbg(&desc->intf->dev, "Tx URB has been submitted index=%d\n",
450323ece54SOliver Neukum 			le16_to_cpu(req->wIndex));
451afba937eSOliver Neukum 	}
45228965e17SOliver Neukum 
45317d80d56SOliver Neukum 	usb_autopm_put_interface(desc->intf);
454e8537bd2SBjørn Mork 	mutex_unlock(&desc->wlock);
45564b9533eSGeliang Tang 	return count;
45628965e17SOliver Neukum 
45728965e17SOliver Neukum out_free_mem_pm:
45828965e17SOliver Neukum 	usb_autopm_put_interface(desc->intf);
45928965e17SOliver Neukum out_free_mem_lock:
46028965e17SOliver Neukum 	mutex_unlock(&desc->wlock);
46128965e17SOliver Neukum out_free_mem:
46228965e17SOliver Neukum 	kfree(buf);
46328965e17SOliver Neukum 	return rv;
464afba937eSOliver Neukum }
465afba937eSOliver Neukum 
4668dd5cd53SBjørn Mork /*
467c1da59daSRobert Foss  * Submit the read urb if resp_count is non-zero.
4688dd5cd53SBjørn Mork  *
4698dd5cd53SBjørn Mork  * Called with desc->iuspin locked
4708dd5cd53SBjørn Mork  */
471c1da59daSRobert Foss static int service_outstanding_interrupt(struct wdm_device *desc)
4728dd5cd53SBjørn Mork {
4738dd5cd53SBjørn Mork 	int rv = 0;
4748dd5cd53SBjørn Mork 
4758dd5cd53SBjørn Mork 	/* submit read urb only if the device is waiting for it */
476f563926fSBjørn Mork 	if (!desc->resp_count || !--desc->resp_count)
4778dd5cd53SBjørn Mork 		goto out;
4788dd5cd53SBjørn Mork 
4795e5ff0b4STetsuo Handa 	if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
4805e5ff0b4STetsuo Handa 		rv = -ENODEV;
4815e5ff0b4STetsuo Handa 		goto out;
4825e5ff0b4STetsuo Handa 	}
4835e5ff0b4STetsuo Handa 	if (test_bit(WDM_RESETTING, &desc->flags)) {
4845e5ff0b4STetsuo Handa 		rv = -EIO;
4855e5ff0b4STetsuo Handa 		goto out;
4865e5ff0b4STetsuo Handa 	}
4875e5ff0b4STetsuo Handa 
4888dd5cd53SBjørn Mork 	set_bit(WDM_RESPONDING, &desc->flags);
4898dd5cd53SBjørn Mork 	spin_unlock_irq(&desc->iuspin);
490e871db8dSSebastian Andrzej Siewior 	rv = usb_submit_urb(desc->response, GFP_KERNEL);
4918dd5cd53SBjørn Mork 	spin_lock_irq(&desc->iuspin);
4928dd5cd53SBjørn Mork 	if (rv) {
4935e5ff0b4STetsuo Handa 		if (!test_bit(WDM_DISCONNECTING, &desc->flags))
4948dd5cd53SBjørn Mork 			dev_err(&desc->intf->dev,
4958dd5cd53SBjørn Mork 				"usb_submit_urb failed with result %d\n", rv);
4968dd5cd53SBjørn Mork 
4978dd5cd53SBjørn Mork 		/* make sure the next notification trigger a submit */
4988dd5cd53SBjørn Mork 		clear_bit(WDM_RESPONDING, &desc->flags);
4998dd5cd53SBjørn Mork 		desc->resp_count = 0;
5008dd5cd53SBjørn Mork 	}
5018dd5cd53SBjørn Mork out:
5028dd5cd53SBjørn Mork 	return rv;
5038dd5cd53SBjørn Mork }
5048dd5cd53SBjørn Mork 
505afba937eSOliver Neukum static ssize_t wdm_read
506afba937eSOliver Neukum (struct file *file, char __user *buffer, size_t count, loff_t *ppos)
507afba937eSOliver Neukum {
508711c68b3SBen Hutchings 	int rv, cntr;
509afba937eSOliver Neukum 	int i = 0;
510afba937eSOliver Neukum 	struct wdm_device *desc = file->private_data;
511afba937eSOliver Neukum 
512afba937eSOliver Neukum 
513e8537bd2SBjørn Mork 	rv = mutex_lock_interruptible(&desc->rlock); /*concurrent reads */
514afba937eSOliver Neukum 	if (rv < 0)
515afba937eSOliver Neukum 		return -ERESTARTSYS;
516afba937eSOliver Neukum 
5176aa7de05SMark Rutland 	cntr = READ_ONCE(desc->length);
518711c68b3SBen Hutchings 	if (cntr == 0) {
519afba937eSOliver Neukum 		desc->read = 0;
520afba937eSOliver Neukum retry:
5217f1dc313SOliver Neukum 		if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
5227f1dc313SOliver Neukum 			rv = -ENODEV;
5237f1dc313SOliver Neukum 			goto err;
5247f1dc313SOliver Neukum 		}
525c0f5eceeSOliver Neukum 		if (test_bit(WDM_OVERFLOW, &desc->flags)) {
526c0f5eceeSOliver Neukum 			clear_bit(WDM_OVERFLOW, &desc->flags);
527c0f5eceeSOliver Neukum 			rv = -ENOBUFS;
528c0f5eceeSOliver Neukum 			goto err;
529c0f5eceeSOliver Neukum 		}
530afba937eSOliver Neukum 		i++;
5317f1dc313SOliver Neukum 		if (file->f_flags & O_NONBLOCK) {
5327f1dc313SOliver Neukum 			if (!test_bit(WDM_READ, &desc->flags)) {
53353b7f7b5SGustavo A. R. Silva 				rv = -EAGAIN;
5347f1dc313SOliver Neukum 				goto err;
5357f1dc313SOliver Neukum 			}
5367f1dc313SOliver Neukum 			rv = 0;
5377f1dc313SOliver Neukum 		} else {
538afba937eSOliver Neukum 			rv = wait_event_interruptible(desc->wait,
539afba937eSOliver Neukum 				test_bit(WDM_READ, &desc->flags));
5407f1dc313SOliver Neukum 		}
541afba937eSOliver Neukum 
5427f1dc313SOliver Neukum 		/* may have happened while we slept */
54317d80d56SOliver Neukum 		if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
54417d80d56SOliver Neukum 			rv = -ENODEV;
54517d80d56SOliver Neukum 			goto err;
54617d80d56SOliver Neukum 		}
54788044202SBjørn Mork 		if (test_bit(WDM_RESETTING, &desc->flags)) {
54888044202SBjørn Mork 			rv = -EIO;
54988044202SBjørn Mork 			goto err;
55088044202SBjørn Mork 		}
55117d80d56SOliver Neukum 		usb_mark_last_busy(interface_to_usbdev(desc->intf));
552afba937eSOliver Neukum 		if (rv < 0) {
553afba937eSOliver Neukum 			rv = -ERESTARTSYS;
554afba937eSOliver Neukum 			goto err;
555afba937eSOliver Neukum 		}
556afba937eSOliver Neukum 
557afba937eSOliver Neukum 		spin_lock_irq(&desc->iuspin);
558afba937eSOliver Neukum 
559afba937eSOliver Neukum 		if (desc->rerr) { /* read completed, error happened */
56085e8a0b9SOliver Neukum 			rv = usb_translate_errors(desc->rerr);
561afba937eSOliver Neukum 			desc->rerr = 0;
562afba937eSOliver Neukum 			spin_unlock_irq(&desc->iuspin);
563afba937eSOliver Neukum 			goto err;
564afba937eSOliver Neukum 		}
565afba937eSOliver Neukum 		/*
566afba937eSOliver Neukum 		 * recheck whether we've lost the race
567afba937eSOliver Neukum 		 * against the completion handler
568afba937eSOliver Neukum 		 */
569afba937eSOliver Neukum 		if (!test_bit(WDM_READ, &desc->flags)) { /* lost race */
570afba937eSOliver Neukum 			spin_unlock_irq(&desc->iuspin);
571afba937eSOliver Neukum 			goto retry;
572afba937eSOliver Neukum 		}
573c0f5eceeSOliver Neukum 
574afba937eSOliver Neukum 		if (!desc->reslength) { /* zero length read */
575ce8bb344SOliver Neukum 			dev_dbg(&desc->intf->dev, "zero length - clearing WDM_READ\n");
576c1da59daSRobert Foss 			clear_bit(WDM_READ, &desc->flags);
577c1da59daSRobert Foss 			rv = service_outstanding_interrupt(desc);
578afba937eSOliver Neukum 			spin_unlock_irq(&desc->iuspin);
5798dd5cd53SBjørn Mork 			if (rv < 0)
5808dd5cd53SBjørn Mork 				goto err;
581afba937eSOliver Neukum 			goto retry;
582afba937eSOliver Neukum 		}
583711c68b3SBen Hutchings 		cntr = desc->length;
584afba937eSOliver Neukum 		spin_unlock_irq(&desc->iuspin);
585afba937eSOliver Neukum 	}
586afba937eSOliver Neukum 
587711c68b3SBen Hutchings 	if (cntr > count)
588711c68b3SBen Hutchings 		cntr = count;
589afba937eSOliver Neukum 	rv = copy_to_user(buffer, desc->ubuf, cntr);
590afba937eSOliver Neukum 	if (rv > 0) {
591afba937eSOliver Neukum 		rv = -EFAULT;
592afba937eSOliver Neukum 		goto err;
593afba937eSOliver Neukum 	}
594afba937eSOliver Neukum 
595711c68b3SBen Hutchings 	spin_lock_irq(&desc->iuspin);
596711c68b3SBen Hutchings 
597afba937eSOliver Neukum 	for (i = 0; i < desc->length - cntr; i++)
598afba937eSOliver Neukum 		desc->ubuf[i] = desc->ubuf[i + cntr];
599afba937eSOliver Neukum 
600afba937eSOliver Neukum 	desc->length -= cntr;
60187d65e54SOliver Neukum 	/* in case we had outstanding data */
602c1da59daSRobert Foss 	if (!desc->length) {
603c1da59daSRobert Foss 		clear_bit(WDM_READ, &desc->flags);
604c1da59daSRobert Foss 		service_outstanding_interrupt(desc);
605c1da59daSRobert Foss 	}
60673e06865SGreg Suarez 	spin_unlock_irq(&desc->iuspin);
607afba937eSOliver Neukum 	rv = cntr;
608afba937eSOliver Neukum 
609afba937eSOliver Neukum err:
610e8537bd2SBjørn Mork 	mutex_unlock(&desc->rlock);
611afba937eSOliver Neukum 	return rv;
612afba937eSOliver Neukum }
613afba937eSOliver Neukum 
61437d2a363SOliver Neukum static int wdm_wait_for_response(struct file *file, long timeout)
615afba937eSOliver Neukum {
616afba937eSOliver Neukum 	struct wdm_device *desc = file->private_data;
61737d2a363SOliver Neukum 	long rv; /* Use long here because (int) MAX_SCHEDULE_TIMEOUT < 0. */
618afba937eSOliver Neukum 
6191426bd2cSOliver Neukum 	/*
62037d2a363SOliver Neukum 	 * Needs both flags. We cannot do with one because resetting it would
62137d2a363SOliver Neukum 	 * cause a race with write() yet we need to signal a disconnect.
6221426bd2cSOliver Neukum 	 */
62337d2a363SOliver Neukum 	rv = wait_event_interruptible_timeout(desc->wait,
6241426bd2cSOliver Neukum 			      !test_bit(WDM_IN_USE, &desc->flags) ||
62537d2a363SOliver Neukum 			      test_bit(WDM_DISCONNECTING, &desc->flags),
62637d2a363SOliver Neukum 			      timeout);
6276b0b79d3SBjørn Mork 
62837d2a363SOliver Neukum 	/*
62937d2a363SOliver Neukum 	 * To report the correct error. This is best effort.
63037d2a363SOliver Neukum 	 * We are inevitably racing with the hardware.
63137d2a363SOliver Neukum 	 */
6321426bd2cSOliver Neukum 	if (test_bit(WDM_DISCONNECTING, &desc->flags))
6331426bd2cSOliver Neukum 		return -ENODEV;
63437d2a363SOliver Neukum 	if (!rv)
63537d2a363SOliver Neukum 		return -EIO;
63637d2a363SOliver Neukum 	if (rv < 0)
63737d2a363SOliver Neukum 		return -EINTR;
638afba937eSOliver Neukum 
63937d2a363SOliver Neukum 	spin_lock_irq(&desc->iuspin);
64037d2a363SOliver Neukum 	rv = desc->werr;
64137d2a363SOliver Neukum 	desc->werr = 0;
64237d2a363SOliver Neukum 	spin_unlock_irq(&desc->iuspin);
64337d2a363SOliver Neukum 
64437d2a363SOliver Neukum 	return usb_translate_errors(rv);
64537d2a363SOliver Neukum 
64637d2a363SOliver Neukum }
64737d2a363SOliver Neukum 
64837d2a363SOliver Neukum /*
64937d2a363SOliver Neukum  * You need to send a signal when you react to malicious or defective hardware.
65037d2a363SOliver Neukum  * Also, don't abort when fsync() returned -EINVAL, for older kernels which do
65137d2a363SOliver Neukum  * not implement wdm_flush() will return -EINVAL.
65237d2a363SOliver Neukum  */
65337d2a363SOliver Neukum static int wdm_fsync(struct file *file, loff_t start, loff_t end, int datasync)
65437d2a363SOliver Neukum {
65537d2a363SOliver Neukum 	return wdm_wait_for_response(file, MAX_SCHEDULE_TIMEOUT);
65637d2a363SOliver Neukum }
65737d2a363SOliver Neukum 
65837d2a363SOliver Neukum /*
65937d2a363SOliver Neukum  * Same with wdm_fsync(), except it uses finite timeout in order to react to
66037d2a363SOliver Neukum  * malicious or defective hardware which ceased communication after close() was
66137d2a363SOliver Neukum  * implicitly called due to process termination.
66237d2a363SOliver Neukum  */
66337d2a363SOliver Neukum static int wdm_flush(struct file *file, fl_owner_t id)
66437d2a363SOliver Neukum {
66537d2a363SOliver Neukum 	return wdm_wait_for_response(file, WDM_FLUSH_TIMEOUT);
666afba937eSOliver Neukum }
667afba937eSOliver Neukum 
668afc9a42bSAl Viro static __poll_t wdm_poll(struct file *file, struct poll_table_struct *wait)
669afba937eSOliver Neukum {
670afba937eSOliver Neukum 	struct wdm_device *desc = file->private_data;
671afba937eSOliver Neukum 	unsigned long flags;
672afc9a42bSAl Viro 	__poll_t mask = 0;
673afba937eSOliver Neukum 
674afba937eSOliver Neukum 	spin_lock_irqsave(&desc->iuspin, flags);
675afba937eSOliver Neukum 	if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
676a9a08845SLinus Torvalds 		mask = EPOLLHUP | EPOLLERR;
677afba937eSOliver Neukum 		spin_unlock_irqrestore(&desc->iuspin, flags);
678afba937eSOliver Neukum 		goto desc_out;
679afba937eSOliver Neukum 	}
680afba937eSOliver Neukum 	if (test_bit(WDM_READ, &desc->flags))
681a9a08845SLinus Torvalds 		mask = EPOLLIN | EPOLLRDNORM;
682afba937eSOliver Neukum 	if (desc->rerr || desc->werr)
683a9a08845SLinus Torvalds 		mask |= EPOLLERR;
684afba937eSOliver Neukum 	if (!test_bit(WDM_IN_USE, &desc->flags))
685a9a08845SLinus Torvalds 		mask |= EPOLLOUT | EPOLLWRNORM;
686afba937eSOliver Neukum 	spin_unlock_irqrestore(&desc->iuspin, flags);
687afba937eSOliver Neukum 
688afba937eSOliver Neukum 	poll_wait(file, &desc->wait, wait);
689afba937eSOliver Neukum 
690afba937eSOliver Neukum desc_out:
691afba937eSOliver Neukum 	return mask;
692afba937eSOliver Neukum }
693afba937eSOliver Neukum 
694afba937eSOliver Neukum static int wdm_open(struct inode *inode, struct file *file)
695afba937eSOliver Neukum {
696afba937eSOliver Neukum 	int minor = iminor(inode);
697afba937eSOliver Neukum 	int rv = -ENODEV;
698afba937eSOliver Neukum 	struct usb_interface *intf;
699afba937eSOliver Neukum 	struct wdm_device *desc;
700afba937eSOliver Neukum 
701afba937eSOliver Neukum 	mutex_lock(&wdm_mutex);
702b0c13860SBjørn Mork 	desc = wdm_find_device_by_minor(minor);
703b0c13860SBjørn Mork 	if (!desc)
704afba937eSOliver Neukum 		goto out;
705afba937eSOliver Neukum 
706b0c13860SBjørn Mork 	intf = desc->intf;
707afba937eSOliver Neukum 	if (test_bit(WDM_DISCONNECTING, &desc->flags))
708afba937eSOliver Neukum 		goto out;
709afba937eSOliver Neukum 	file->private_data = desc;
710afba937eSOliver Neukum 
71117d80d56SOliver Neukum 	rv = usb_autopm_get_interface(desc->intf);
71217d80d56SOliver Neukum 	if (rv < 0) {
7139908a32eSGreg Kroah-Hartman 		dev_err(&desc->intf->dev, "Error autopm - %d\n", rv);
71417d80d56SOliver Neukum 		goto out;
71517d80d56SOliver Neukum 	}
716afba937eSOliver Neukum 
717e8537bd2SBjørn Mork 	/* using write lock to protect desc->count */
718e8537bd2SBjørn Mork 	mutex_lock(&desc->wlock);
71917d80d56SOliver Neukum 	if (!desc->count++) {
720d771d8aaSOliver Neukum 		desc->werr = 0;
721d771d8aaSOliver Neukum 		desc->rerr = 0;
72217d80d56SOliver Neukum 		rv = usb_submit_urb(desc->validity, GFP_KERNEL);
723afba937eSOliver Neukum 		if (rv < 0) {
724afba937eSOliver Neukum 			desc->count--;
7259908a32eSGreg Kroah-Hartman 			dev_err(&desc->intf->dev,
7269908a32eSGreg Kroah-Hartman 				"Error submitting int urb - %d\n", rv);
72712a98b2bSOliver Neukum 			rv = usb_translate_errors(rv);
728afba937eSOliver Neukum 		}
72917d80d56SOliver Neukum 	} else {
730afba937eSOliver Neukum 		rv = 0;
73117d80d56SOliver Neukum 	}
732e8537bd2SBjørn Mork 	mutex_unlock(&desc->wlock);
7333cc36157SBjørn Mork 	if (desc->count == 1)
7343cc36157SBjørn Mork 		desc->manage_power(intf, 1);
73517d80d56SOliver Neukum 	usb_autopm_put_interface(desc->intf);
736afba937eSOliver Neukum out:
737afba937eSOliver Neukum 	mutex_unlock(&wdm_mutex);
738afba937eSOliver Neukum 	return rv;
739afba937eSOliver Neukum }
740afba937eSOliver Neukum 
741afba937eSOliver Neukum static int wdm_release(struct inode *inode, struct file *file)
742afba937eSOliver Neukum {
743afba937eSOliver Neukum 	struct wdm_device *desc = file->private_data;
744afba937eSOliver Neukum 
745afba937eSOliver Neukum 	mutex_lock(&wdm_mutex);
746e8537bd2SBjørn Mork 
747e8537bd2SBjørn Mork 	/* using write lock to protect desc->count */
748e8537bd2SBjørn Mork 	mutex_lock(&desc->wlock);
749afba937eSOliver Neukum 	desc->count--;
750e8537bd2SBjørn Mork 	mutex_unlock(&desc->wlock);
75117d80d56SOliver Neukum 
752afba937eSOliver Neukum 	if (!desc->count) {
7536b0b79d3SBjørn Mork 		if (!test_bit(WDM_DISCONNECTING, &desc->flags)) {
754ce8bb344SOliver Neukum 			dev_dbg(&desc->intf->dev, "wdm_release: cleanup\n");
755*18abf874SOliver Neukum 			poison_urbs(desc);
75673e06865SGreg Suarez 			spin_lock_irq(&desc->iuspin);
75773e06865SGreg Suarez 			desc->resp_count = 0;
75873e06865SGreg Suarez 			spin_unlock_irq(&desc->iuspin);
7593cc36157SBjørn Mork 			desc->manage_power(desc->intf, 0);
760*18abf874SOliver Neukum 			unpoison_urbs(desc);
761880bca3aSBjørn Mork 		} else {
7626b0b79d3SBjørn Mork 			/* must avoid dev_printk here as desc->intf is invalid */
7636b0b79d3SBjørn Mork 			pr_debug(KBUILD_MODNAME " %s: device gone - cleaning up\n", __func__);
7642f338c8aSOliver Neukum 			cleanup(desc);
765afba937eSOliver Neukum 		}
766880bca3aSBjørn Mork 	}
767afba937eSOliver Neukum 	mutex_unlock(&wdm_mutex);
768afba937eSOliver Neukum 	return 0;
769afba937eSOliver Neukum }
770afba937eSOliver Neukum 
7713edce1cfSBjørn Mork static long wdm_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
7723edce1cfSBjørn Mork {
7733edce1cfSBjørn Mork 	struct wdm_device *desc = file->private_data;
7743edce1cfSBjørn Mork 	int rv = 0;
7753edce1cfSBjørn Mork 
7763edce1cfSBjørn Mork 	switch (cmd) {
7773edce1cfSBjørn Mork 	case IOCTL_WDM_MAX_COMMAND:
7783edce1cfSBjørn Mork 		if (copy_to_user((void __user *)arg, &desc->wMaxCommand, sizeof(desc->wMaxCommand)))
7793edce1cfSBjørn Mork 			rv = -EFAULT;
7803edce1cfSBjørn Mork 		break;
7813edce1cfSBjørn Mork 	default:
7823edce1cfSBjørn Mork 		rv = -ENOTTY;
7833edce1cfSBjørn Mork 	}
7843edce1cfSBjørn Mork 	return rv;
7853edce1cfSBjørn Mork }
7863edce1cfSBjørn Mork 
787afba937eSOliver Neukum static const struct file_operations wdm_fops = {
788afba937eSOliver Neukum 	.owner =	THIS_MODULE,
789afba937eSOliver Neukum 	.read =		wdm_read,
790afba937eSOliver Neukum 	.write =	wdm_write,
79137d2a363SOliver Neukum 	.fsync =	wdm_fsync,
792afba937eSOliver Neukum 	.open =		wdm_open,
793afba937eSOliver Neukum 	.flush =	wdm_flush,
794afba937eSOliver Neukum 	.release =	wdm_release,
7956038f373SArnd Bergmann 	.poll =		wdm_poll,
7963edce1cfSBjørn Mork 	.unlocked_ioctl = wdm_ioctl,
7971832f2d8SArnd Bergmann 	.compat_ioctl = compat_ptr_ioctl,
7986038f373SArnd Bergmann 	.llseek =	noop_llseek,
799afba937eSOliver Neukum };
800afba937eSOliver Neukum 
801afba937eSOliver Neukum static struct usb_class_driver wdm_class = {
802afba937eSOliver Neukum 	.name =		"cdc-wdm%d",
803afba937eSOliver Neukum 	.fops =		&wdm_fops,
804afba937eSOliver Neukum 	.minor_base =	WDM_MINOR_BASE,
805afba937eSOliver Neukum };
806afba937eSOliver Neukum 
807afba937eSOliver Neukum /* --- error handling --- */
808afba937eSOliver Neukum static void wdm_rxwork(struct work_struct *work)
809afba937eSOliver Neukum {
810afba937eSOliver Neukum 	struct wdm_device *desc = container_of(work, struct wdm_device, rxwork);
811afba937eSOliver Neukum 	unsigned long flags;
8126dd433e6SOliver Neukum 	int rv = 0;
8136dd433e6SOliver Neukum 	int responding;
814afba937eSOliver Neukum 
815afba937eSOliver Neukum 	spin_lock_irqsave(&desc->iuspin, flags);
816afba937eSOliver Neukum 	if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
817afba937eSOliver Neukum 		spin_unlock_irqrestore(&desc->iuspin, flags);
818afba937eSOliver Neukum 	} else {
8196dd433e6SOliver Neukum 		responding = test_and_set_bit(WDM_RESPONDING, &desc->flags);
820afba937eSOliver Neukum 		spin_unlock_irqrestore(&desc->iuspin, flags);
8216dd433e6SOliver Neukum 		if (!responding)
822afba937eSOliver Neukum 			rv = usb_submit_urb(desc->response, GFP_KERNEL);
823afba937eSOliver Neukum 		if (rv < 0 && rv != -EPERM) {
824afba937eSOliver Neukum 			spin_lock_irqsave(&desc->iuspin, flags);
8256dd433e6SOliver Neukum 			clear_bit(WDM_RESPONDING, &desc->flags);
826afba937eSOliver Neukum 			if (!test_bit(WDM_DISCONNECTING, &desc->flags))
827afba937eSOliver Neukum 				schedule_work(&desc->rxwork);
828afba937eSOliver Neukum 			spin_unlock_irqrestore(&desc->iuspin, flags);
829afba937eSOliver Neukum 		}
830afba937eSOliver Neukum 	}
831afba937eSOliver Neukum }
832afba937eSOliver Neukum 
8332df69484SSebastian Andrzej Siewior static void service_interrupt_work(struct work_struct *work)
8342df69484SSebastian Andrzej Siewior {
8352df69484SSebastian Andrzej Siewior 	struct wdm_device *desc;
8362df69484SSebastian Andrzej Siewior 
8372df69484SSebastian Andrzej Siewior 	desc = container_of(work, struct wdm_device, service_outs_intr);
8382df69484SSebastian Andrzej Siewior 
8392df69484SSebastian Andrzej Siewior 	spin_lock_irq(&desc->iuspin);
8402df69484SSebastian Andrzej Siewior 	service_outstanding_interrupt(desc);
8412df69484SSebastian Andrzej Siewior 	if (!desc->resp_count) {
8422df69484SSebastian Andrzej Siewior 		set_bit(WDM_READ, &desc->flags);
8432df69484SSebastian Andrzej Siewior 		wake_up(&desc->wait);
8442df69484SSebastian Andrzej Siewior 	}
8452df69484SSebastian Andrzej Siewior 	spin_unlock_irq(&desc->iuspin);
8462df69484SSebastian Andrzej Siewior }
8472df69484SSebastian Andrzej Siewior 
848afba937eSOliver Neukum /* --- hotplug --- */
849afba937eSOliver Neukum 
8503cc36157SBjørn Mork static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep,
85119445816SBjørn Mork 		u16 bufsize, int (*manage_power)(struct usb_interface *, int))
852afba937eSOliver Neukum {
8530dffb486SBjørn Mork 	int rv = -ENOMEM;
854afba937eSOliver Neukum 	struct wdm_device *desc;
855afba937eSOliver Neukum 
856afba937eSOliver Neukum 	desc = kzalloc(sizeof(struct wdm_device), GFP_KERNEL);
857afba937eSOliver Neukum 	if (!desc)
858afba937eSOliver Neukum 		goto out;
859b0c13860SBjørn Mork 	INIT_LIST_HEAD(&desc->device_list);
860e8537bd2SBjørn Mork 	mutex_init(&desc->rlock);
861e8537bd2SBjørn Mork 	mutex_init(&desc->wlock);
862afba937eSOliver Neukum 	spin_lock_init(&desc->iuspin);
863afba937eSOliver Neukum 	init_waitqueue_head(&desc->wait);
8640dffb486SBjørn Mork 	desc->wMaxCommand = bufsize;
865052fbc0dSOliver Neukum 	/* this will be expanded and needed in hardware endianness */
866afba937eSOliver Neukum 	desc->inum = cpu_to_le16((u16)intf->cur_altsetting->desc.bInterfaceNumber);
867afba937eSOliver Neukum 	desc->intf = intf;
868afba937eSOliver Neukum 	INIT_WORK(&desc->rxwork, wdm_rxwork);
8692df69484SSebastian Andrzej Siewior 	INIT_WORK(&desc->service_outs_intr, service_interrupt_work);
870afba937eSOliver Neukum 
871afba937eSOliver Neukum 	rv = -EINVAL;
8720dffb486SBjørn Mork 	if (!usb_endpoint_is_int_in(ep))
873052fbc0dSOliver Neukum 		goto err;
874afba937eSOliver Neukum 
87529cc8897SKuninori Morimoto 	desc->wMaxPacketSize = usb_endpoint_maxp(ep);
876afba937eSOliver Neukum 
877afba937eSOliver Neukum 	desc->orq = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);
878afba937eSOliver Neukum 	if (!desc->orq)
879afba937eSOliver Neukum 		goto err;
880afba937eSOliver Neukum 	desc->irq = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL);
881afba937eSOliver Neukum 	if (!desc->irq)
882afba937eSOliver Neukum 		goto err;
883afba937eSOliver Neukum 
884afba937eSOliver Neukum 	desc->validity = usb_alloc_urb(0, GFP_KERNEL);
885afba937eSOliver Neukum 	if (!desc->validity)
886afba937eSOliver Neukum 		goto err;
887afba937eSOliver Neukum 
888afba937eSOliver Neukum 	desc->response = usb_alloc_urb(0, GFP_KERNEL);
889afba937eSOliver Neukum 	if (!desc->response)
890afba937eSOliver Neukum 		goto err;
891afba937eSOliver Neukum 
892afba937eSOliver Neukum 	desc->command = usb_alloc_urb(0, GFP_KERNEL);
893afba937eSOliver Neukum 	if (!desc->command)
894afba937eSOliver Neukum 		goto err;
895afba937eSOliver Neukum 
896afba937eSOliver Neukum 	desc->ubuf = kmalloc(desc->wMaxCommand, GFP_KERNEL);
897afba937eSOliver Neukum 	if (!desc->ubuf)
898afba937eSOliver Neukum 		goto err;
899afba937eSOliver Neukum 
9008457d99cSBjørn Mork 	desc->sbuf = kmalloc(desc->wMaxPacketSize, GFP_KERNEL);
901afba937eSOliver Neukum 	if (!desc->sbuf)
902afba937eSOliver Neukum 		goto err;
903afba937eSOliver Neukum 
9048457d99cSBjørn Mork 	desc->inbuf = kmalloc(desc->wMaxCommand, GFP_KERNEL);
905afba937eSOliver Neukum 	if (!desc->inbuf)
9068457d99cSBjørn Mork 		goto err;
907afba937eSOliver Neukum 
908afba937eSOliver Neukum 	usb_fill_int_urb(
909afba937eSOliver Neukum 		desc->validity,
910afba937eSOliver Neukum 		interface_to_usbdev(intf),
911afba937eSOliver Neukum 		usb_rcvintpipe(interface_to_usbdev(intf), ep->bEndpointAddress),
912afba937eSOliver Neukum 		desc->sbuf,
913afba937eSOliver Neukum 		desc->wMaxPacketSize,
914afba937eSOliver Neukum 		wdm_int_callback,
915afba937eSOliver Neukum 		desc,
916afba937eSOliver Neukum 		ep->bInterval
917afba937eSOliver Neukum 	);
918afba937eSOliver Neukum 
91919b85b3bSBjørn Mork 	desc->irq->bRequestType = (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE);
92019b85b3bSBjørn Mork 	desc->irq->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE;
92119b85b3bSBjørn Mork 	desc->irq->wValue = 0;
922323ece54SOliver Neukum 	desc->irq->wIndex = desc->inum; /* already converted */
92319b85b3bSBjørn Mork 	desc->irq->wLength = cpu_to_le16(desc->wMaxCommand);
92419b85b3bSBjørn Mork 
92519b85b3bSBjørn Mork 	usb_fill_control_urb(
92619b85b3bSBjørn Mork 		desc->response,
9278143a896SBjørn Mork 		interface_to_usbdev(intf),
92819b85b3bSBjørn Mork 		/* using common endpoint 0 */
92919b85b3bSBjørn Mork 		usb_rcvctrlpipe(interface_to_usbdev(desc->intf), 0),
93019b85b3bSBjørn Mork 		(unsigned char *)desc->irq,
93119b85b3bSBjørn Mork 		desc->inbuf,
93219b85b3bSBjørn Mork 		desc->wMaxCommand,
93319b85b3bSBjørn Mork 		wdm_in_callback,
93419b85b3bSBjørn Mork 		desc
93519b85b3bSBjørn Mork 	);
93619b85b3bSBjørn Mork 
9373cc36157SBjørn Mork 	desc->manage_power = manage_power;
9383cc36157SBjørn Mork 
939b0c13860SBjørn Mork 	spin_lock(&wdm_device_list_lock);
940b0c13860SBjørn Mork 	list_add(&desc->device_list, &wdm_device_list);
941b0c13860SBjørn Mork 	spin_unlock(&wdm_device_list_lock);
942b0c13860SBjørn Mork 
943afba937eSOliver Neukum 	rv = usb_register_dev(intf, &wdm_class);
944052fbc0dSOliver Neukum 	if (rv < 0)
945b0c13860SBjørn Mork 		goto err;
946052fbc0dSOliver Neukum 	else
947820c629aSBjørn Mork 		dev_info(&intf->dev, "%s: USB WDM device\n", dev_name(intf->usb_dev));
948afba937eSOliver Neukum out:
949afba937eSOliver Neukum 	return rv;
950afba937eSOliver Neukum err:
9516286d85eSBjørn Mork 	spin_lock(&wdm_device_list_lock);
9526286d85eSBjørn Mork 	list_del(&desc->device_list);
9536286d85eSBjørn Mork 	spin_unlock(&wdm_device_list_lock);
9540dffb486SBjørn Mork 	cleanup(desc);
9550dffb486SBjørn Mork 	return rv;
9560dffb486SBjørn Mork }
9570dffb486SBjørn Mork 
9583cc36157SBjørn Mork static int wdm_manage_power(struct usb_interface *intf, int on)
9593cc36157SBjørn Mork {
9603cc36157SBjørn Mork 	/* need autopm_get/put here to ensure the usbcore sees the new value */
9613cc36157SBjørn Mork 	int rv = usb_autopm_get_interface(intf);
9623cc36157SBjørn Mork 
9633cc36157SBjørn Mork 	intf->needs_remote_wakeup = on;
9644144bc86SBjørn Mork 	if (!rv)
9653cc36157SBjørn Mork 		usb_autopm_put_interface(intf);
9664144bc86SBjørn Mork 	return 0;
9673cc36157SBjørn Mork }
9683cc36157SBjørn Mork 
9690dffb486SBjørn Mork static int wdm_probe(struct usb_interface *intf, const struct usb_device_id *id)
9700dffb486SBjørn Mork {
9710dffb486SBjørn Mork 	int rv = -EINVAL;
9720dffb486SBjørn Mork 	struct usb_host_interface *iface;
9730dffb486SBjørn Mork 	struct usb_endpoint_descriptor *ep;
9747fae7bfbSOliver Neukum 	struct usb_cdc_parsed_header hdr;
9750dffb486SBjørn Mork 	u8 *buffer = intf->altsetting->extra;
9760dffb486SBjørn Mork 	int buflen = intf->altsetting->extralen;
9770dffb486SBjørn Mork 	u16 maxcom = WDM_DEFAULT_BUFSIZE;
9780dffb486SBjørn Mork 
9790dffb486SBjørn Mork 	if (!buffer)
9800dffb486SBjørn Mork 		goto err;
9810dffb486SBjørn Mork 
9827fae7bfbSOliver Neukum 	cdc_parse_cdc_header(&hdr, intf, buffer, buflen);
9837fae7bfbSOliver Neukum 
9847fae7bfbSOliver Neukum 	if (hdr.usb_cdc_dmm_desc)
9857fae7bfbSOliver Neukum 		maxcom = le16_to_cpu(hdr.usb_cdc_dmm_desc->wMaxCommand);
9860dffb486SBjørn Mork 
9870dffb486SBjørn Mork 	iface = intf->cur_altsetting;
9880dffb486SBjørn Mork 	if (iface->desc.bNumEndpoints != 1)
9890dffb486SBjørn Mork 		goto err;
9900dffb486SBjørn Mork 	ep = &iface->endpoint[0].desc;
9910dffb486SBjørn Mork 
99219445816SBjørn Mork 	rv = wdm_create(intf, ep, maxcom, &wdm_manage_power);
9930dffb486SBjørn Mork 
9940dffb486SBjørn Mork err:
995afba937eSOliver Neukum 	return rv;
996afba937eSOliver Neukum }
997afba937eSOliver Neukum 
9983cc36157SBjørn Mork /**
9993cc36157SBjørn Mork  * usb_cdc_wdm_register - register a WDM subdriver
10003cc36157SBjørn Mork  * @intf: usb interface the subdriver will associate with
10013cc36157SBjørn Mork  * @ep: interrupt endpoint to monitor for notifications
10023cc36157SBjørn Mork  * @bufsize: maximum message size to support for read/write
1003ddcb6c6aSLee Jones  * @manage_power: call-back invoked during open and release to
1004ddcb6c6aSLee Jones  *                manage the device's power
10053cc36157SBjørn Mork  * Create WDM usb class character device and associate it with intf
10063cc36157SBjørn Mork  * without binding, allowing another driver to manage the interface.
10073cc36157SBjørn Mork  *
10083cc36157SBjørn Mork  * The subdriver will manage the given interrupt endpoint exclusively
10093cc36157SBjørn Mork  * and will issue control requests referring to the given intf. It
10103cc36157SBjørn Mork  * will otherwise avoid interferring, and in particular not do
10113cc36157SBjørn Mork  * usb_set_intfdata/usb_get_intfdata on intf.
10123cc36157SBjørn Mork  *
10133cc36157SBjørn Mork  * The return value is a pointer to the subdriver's struct usb_driver.
10143cc36157SBjørn Mork  * The registering driver is responsible for calling this subdriver's
10153cc36157SBjørn Mork  * disconnect, suspend, resume, pre_reset and post_reset methods from
10163cc36157SBjørn Mork  * its own.
10173cc36157SBjørn Mork  */
10183cc36157SBjørn Mork struct usb_driver *usb_cdc_wdm_register(struct usb_interface *intf,
10193cc36157SBjørn Mork 					struct usb_endpoint_descriptor *ep,
10203cc36157SBjørn Mork 					int bufsize,
10213cc36157SBjørn Mork 					int (*manage_power)(struct usb_interface *, int))
10223cc36157SBjørn Mork {
10236dade7adSColin Ian King 	int rv;
10243cc36157SBjørn Mork 
102519445816SBjørn Mork 	rv = wdm_create(intf, ep, bufsize, manage_power);
10263cc36157SBjørn Mork 	if (rv < 0)
10273cc36157SBjørn Mork 		goto err;
10283cc36157SBjørn Mork 
10293cc36157SBjørn Mork 	return &wdm_driver;
10303cc36157SBjørn Mork err:
10313cc36157SBjørn Mork 	return ERR_PTR(rv);
10323cc36157SBjørn Mork }
10333cc36157SBjørn Mork EXPORT_SYMBOL(usb_cdc_wdm_register);
10343cc36157SBjørn Mork 
1035afba937eSOliver Neukum static void wdm_disconnect(struct usb_interface *intf)
1036afba937eSOliver Neukum {
1037afba937eSOliver Neukum 	struct wdm_device *desc;
1038afba937eSOliver Neukum 	unsigned long flags;
1039afba937eSOliver Neukum 
1040afba937eSOliver Neukum 	usb_deregister_dev(intf, &wdm_class);
1041b0c13860SBjørn Mork 	desc = wdm_find_device(intf);
1042afba937eSOliver Neukum 	mutex_lock(&wdm_mutex);
1043afba937eSOliver Neukum 
1044afba937eSOliver Neukum 	/* the spinlock makes sure no new urbs are generated in the callbacks */
1045afba937eSOliver Neukum 	spin_lock_irqsave(&desc->iuspin, flags);
1046afba937eSOliver Neukum 	set_bit(WDM_DISCONNECTING, &desc->flags);
1047afba937eSOliver Neukum 	set_bit(WDM_READ, &desc->flags);
1048afba937eSOliver Neukum 	spin_unlock_irqrestore(&desc->iuspin, flags);
104962aaf24dSBjørn Mork 	wake_up_all(&desc->wait);
1050e8537bd2SBjørn Mork 	mutex_lock(&desc->rlock);
1051e8537bd2SBjørn Mork 	mutex_lock(&desc->wlock);
1052*18abf874SOliver Neukum 	poison_urbs(desc);
1053d93d16e9SOliver Neukum 	cancel_work_sync(&desc->rxwork);
10542df69484SSebastian Andrzej Siewior 	cancel_work_sync(&desc->service_outs_intr);
1055e8537bd2SBjørn Mork 	mutex_unlock(&desc->wlock);
1056e8537bd2SBjørn Mork 	mutex_unlock(&desc->rlock);
10576286d85eSBjørn Mork 
10586286d85eSBjørn Mork 	/* the desc->intf pointer used as list key is now invalid */
10596286d85eSBjørn Mork 	spin_lock(&wdm_device_list_lock);
10606286d85eSBjørn Mork 	list_del(&desc->device_list);
10616286d85eSBjørn Mork 	spin_unlock(&wdm_device_list_lock);
10626286d85eSBjørn Mork 
1063afba937eSOliver Neukum 	if (!desc->count)
1064afba937eSOliver Neukum 		cleanup(desc);
1065880bca3aSBjørn Mork 	else
106613a88bf5SOliver Neukum 		dev_dbg(&intf->dev, "%d open files - postponing cleanup\n", desc->count);
1067afba937eSOliver Neukum 	mutex_unlock(&wdm_mutex);
1068afba937eSOliver Neukum }
1069afba937eSOliver Neukum 
1070d93d16e9SOliver Neukum #ifdef CONFIG_PM
107117d80d56SOliver Neukum static int wdm_suspend(struct usb_interface *intf, pm_message_t message)
107217d80d56SOliver Neukum {
1073b0c13860SBjørn Mork 	struct wdm_device *desc = wdm_find_device(intf);
107417d80d56SOliver Neukum 	int rv = 0;
107517d80d56SOliver Neukum 
107617d80d56SOliver Neukum 	dev_dbg(&desc->intf->dev, "wdm%d_suspend\n", intf->minor);
107717d80d56SOliver Neukum 
1078d93d16e9SOliver Neukum 	/* if this is an autosuspend the caller does the locking */
1079e8537bd2SBjørn Mork 	if (!PMSG_IS_AUTO(message)) {
1080e8537bd2SBjørn Mork 		mutex_lock(&desc->rlock);
1081e8537bd2SBjørn Mork 		mutex_lock(&desc->wlock);
1082e8537bd2SBjørn Mork 	}
108362e66854SOliver Neukum 	spin_lock_irq(&desc->iuspin);
1084d93d16e9SOliver Neukum 
10855b1b0b81SAlan Stern 	if (PMSG_IS_AUTO(message) &&
1086922a5eadSOliver Neukum 			(test_bit(WDM_IN_USE, &desc->flags)
1087922a5eadSOliver Neukum 			|| test_bit(WDM_RESPONDING, &desc->flags))) {
108862e66854SOliver Neukum 		spin_unlock_irq(&desc->iuspin);
108917d80d56SOliver Neukum 		rv = -EBUSY;
109017d80d56SOliver Neukum 	} else {
1091d93d16e9SOliver Neukum 
1092beb1d35fSOliver Neukum 		set_bit(WDM_SUSPENDING, &desc->flags);
109362e66854SOliver Neukum 		spin_unlock_irq(&desc->iuspin);
1094d93d16e9SOliver Neukum 		/* callback submits work - order is essential */
1095*18abf874SOliver Neukum 		poison_urbs(desc);
1096d93d16e9SOliver Neukum 		cancel_work_sync(&desc->rxwork);
10972df69484SSebastian Andrzej Siewior 		cancel_work_sync(&desc->service_outs_intr);
1098*18abf874SOliver Neukum 		unpoison_urbs(desc);
109917d80d56SOliver Neukum 	}
1100e8537bd2SBjørn Mork 	if (!PMSG_IS_AUTO(message)) {
1101e8537bd2SBjørn Mork 		mutex_unlock(&desc->wlock);
1102e8537bd2SBjørn Mork 		mutex_unlock(&desc->rlock);
1103e8537bd2SBjørn Mork 	}
110417d80d56SOliver Neukum 
110517d80d56SOliver Neukum 	return rv;
110617d80d56SOliver Neukum }
1107d93d16e9SOliver Neukum #endif
110817d80d56SOliver Neukum 
110917d80d56SOliver Neukum static int recover_from_urb_loss(struct wdm_device *desc)
111017d80d56SOliver Neukum {
111117d80d56SOliver Neukum 	int rv = 0;
111217d80d56SOliver Neukum 
111317d80d56SOliver Neukum 	if (desc->count) {
111417d80d56SOliver Neukum 		rv = usb_submit_urb(desc->validity, GFP_NOIO);
111517d80d56SOliver Neukum 		if (rv < 0)
11169908a32eSGreg Kroah-Hartman 			dev_err(&desc->intf->dev,
11179908a32eSGreg Kroah-Hartman 				"Error resume submitting int urb - %d\n", rv);
111817d80d56SOliver Neukum 	}
111917d80d56SOliver Neukum 	return rv;
112017d80d56SOliver Neukum }
1121d93d16e9SOliver Neukum 
1122d93d16e9SOliver Neukum #ifdef CONFIG_PM
112317d80d56SOliver Neukum static int wdm_resume(struct usb_interface *intf)
112417d80d56SOliver Neukum {
1125b0c13860SBjørn Mork 	struct wdm_device *desc = wdm_find_device(intf);
112617d80d56SOliver Neukum 	int rv;
112717d80d56SOliver Neukum 
112817d80d56SOliver Neukum 	dev_dbg(&desc->intf->dev, "wdm%d_resume\n", intf->minor);
1129338124c1SOliver Neukum 
1130beb1d35fSOliver Neukum 	clear_bit(WDM_SUSPENDING, &desc->flags);
113162e66854SOliver Neukum 	rv = recover_from_urb_loss(desc);
1132338124c1SOliver Neukum 
113317d80d56SOliver Neukum 	return rv;
113417d80d56SOliver Neukum }
1135d93d16e9SOliver Neukum #endif
113617d80d56SOliver Neukum 
113717d80d56SOliver Neukum static int wdm_pre_reset(struct usb_interface *intf)
113817d80d56SOliver Neukum {
1139b0c13860SBjørn Mork 	struct wdm_device *desc = wdm_find_device(intf);
114017d80d56SOliver Neukum 
1141d771d8aaSOliver Neukum 	/*
1142d771d8aaSOliver Neukum 	 * we notify everybody using poll of
1143d771d8aaSOliver Neukum 	 * an exceptional situation
1144d771d8aaSOliver Neukum 	 * must be done before recovery lest a spontaneous
1145d771d8aaSOliver Neukum 	 * message from the device is lost
1146d771d8aaSOliver Neukum 	 */
1147d771d8aaSOliver Neukum 	spin_lock_irq(&desc->iuspin);
114888044202SBjørn Mork 	set_bit(WDM_RESETTING, &desc->flags);	/* inform read/write */
114988044202SBjørn Mork 	set_bit(WDM_READ, &desc->flags);	/* unblock read */
115088044202SBjørn Mork 	clear_bit(WDM_IN_USE, &desc->flags);	/* unblock write */
1151d771d8aaSOliver Neukum 	desc->rerr = -EINTR;
1152d771d8aaSOliver Neukum 	spin_unlock_irq(&desc->iuspin);
1153d771d8aaSOliver Neukum 	wake_up_all(&desc->wait);
115488044202SBjørn Mork 	mutex_lock(&desc->rlock);
115588044202SBjørn Mork 	mutex_lock(&desc->wlock);
1156*18abf874SOliver Neukum 	poison_urbs(desc);
115788044202SBjørn Mork 	cancel_work_sync(&desc->rxwork);
11582df69484SSebastian Andrzej Siewior 	cancel_work_sync(&desc->service_outs_intr);
115917d80d56SOliver Neukum 	return 0;
116017d80d56SOliver Neukum }
116117d80d56SOliver Neukum 
116217d80d56SOliver Neukum static int wdm_post_reset(struct usb_interface *intf)
116317d80d56SOliver Neukum {
1164b0c13860SBjørn Mork 	struct wdm_device *desc = wdm_find_device(intf);
116517d80d56SOliver Neukum 	int rv;
116617d80d56SOliver Neukum 
1167*18abf874SOliver Neukum 	unpoison_urbs(desc);
1168c0f5eceeSOliver Neukum 	clear_bit(WDM_OVERFLOW, &desc->flags);
116988044202SBjørn Mork 	clear_bit(WDM_RESETTING, &desc->flags);
117017d80d56SOliver Neukum 	rv = recover_from_urb_loss(desc);
1171e8537bd2SBjørn Mork 	mutex_unlock(&desc->wlock);
1172e8537bd2SBjørn Mork 	mutex_unlock(&desc->rlock);
11730742a338SYueHaibing 	return rv;
117417d80d56SOliver Neukum }
117517d80d56SOliver Neukum 
1176afba937eSOliver Neukum static struct usb_driver wdm_driver = {
1177afba937eSOliver Neukum 	.name =		"cdc_wdm",
1178afba937eSOliver Neukum 	.probe =	wdm_probe,
1179afba937eSOliver Neukum 	.disconnect =	wdm_disconnect,
1180d93d16e9SOliver Neukum #ifdef CONFIG_PM
118117d80d56SOliver Neukum 	.suspend =	wdm_suspend,
118217d80d56SOliver Neukum 	.resume =	wdm_resume,
118317d80d56SOliver Neukum 	.reset_resume =	wdm_resume,
1184d93d16e9SOliver Neukum #endif
118517d80d56SOliver Neukum 	.pre_reset =	wdm_pre_reset,
118617d80d56SOliver Neukum 	.post_reset =	wdm_post_reset,
1187afba937eSOliver Neukum 	.id_table =	wdm_ids,
118817d80d56SOliver Neukum 	.supports_autosuspend = 1,
1189e1f12eb6SSarah Sharp 	.disable_hub_initiated_lpm = 1,
1190afba937eSOliver Neukum };
1191afba937eSOliver Neukum 
119265db4305SGreg Kroah-Hartman module_usb_driver(wdm_driver);
1193afba937eSOliver Neukum 
1194afba937eSOliver Neukum MODULE_AUTHOR(DRIVER_AUTHOR);
119587d65e54SOliver Neukum MODULE_DESCRIPTION(DRIVER_DESC);
1196afba937eSOliver Neukum MODULE_LICENSE("GPL");
1197