CHROMIUM: gobi: resubmit interrupt urb on failed reads

If we don't do this and there are pending reads when the card suspends, we can
fail to re-request interrupt urbs when the card comes back up, and we fall into
a hole. Oops.

BUG=None
TEST=Adhoc
suspend/resume a few times; make sure 'modem status' is sensible after each.

Change-Id: Ia0e1b96e983249113c1e1dbf35490487f60a205c
Signed-off-by: Elly Jones <ellyjones@chromium.org>
Signed-off-by: Jason Glasgow <jglasgow@chromium.org>

Review URL: http://codereview.chromium.org/6731086
(cherry picked from commit 900b6e1c5881ba4f9badb0a584ee466458e3c375)

CHROMIUM: Hide packet dumps behind a flag.

BUG=None
TEST=Adhoc
built, installed, dmesg | grep gobi, modem status

Change-Id: Ic9152dad864d4125204620b7f93b1170cf0d7159
Signed-off-by: Elly Jones <ellyjones@chromium.org>

Review URL: http://codereview.chromium.org/6731002
(cherry picked from commit 3288bce5d87f5fff3dfadc09668038e173069b9d)

CHROMIUM: gobi: Fix races in qc_deregister() once and for all.

This is a fix to my rewritten driver, and is ported from the fix at http://codereview.chromium.org/6602058/.

Signed-off-by: Elly Jones <ellyjones@chromium.org>
Signed-off-by: Jason Glasgow <jglasgow@chromium.org>
Signed-off-by: Mandeep Singh Baines <msb@chromium.org>

BUG=chromium-os:10342
TEST=/opt/Qualcomm/bin/open-deauth

Review URL: http://codereview.chromium.org/6612045

Change-Id: I404edbfde49a154604bc657b7f76776b7423f51e
(cherry picked from commit 41b9938a183c1f5c84d27c717f6e8556054271f5)

CHROMIUM: qcusbnet: Add gobi3k support.

This entails:
1) Adding a couple of new vid:pid pairs for known Gobi3k devices.
2) Handling the extra USB interface gobi3ks have
3) Installing a proper 'parent' pointer to help udev emit the correct number of
   device events, and
4) Changing the driver's registration name.

The latter change was requested by upstream when I first submitted this driver.
It is correct, in that the driver's name should reflect that it does not just
drive gobi2ks, but it will require changes in userspace to match.

BUG=chromium-os:12380
TEST=Adhoc
ls /dev/qcqmi* with both a g2k and g3k present; try 'gobi3k list', too.

Change-Id: I0e86fcbce22f3a1f3fd58caf0e8d47e39a1af9ed
Signed-off-by: Elly Jones <ellyjones@chromium.org>
Signed-off-by: Jason Glasgow <jglasgow@chromium.org>

Review URL: http://codereview.chromium.org/6672003
(cherry picked from commit b4fd3f92bdb53f0a47b3a2d31425e62c700900df)

CHROMIUM: qcusbnet: fix devqmi_close() races.

Originally, devqmi_close() was attached to the flush hook (instead of the
release hook), meaning it would be called whenever a userspace task called
close() (instead of just when the last ref to the file was dropped), which
necessitated the task-list-walking song and dance. Instead, add a new ioctl
which userspace can use to tear down the QMI connection and remove the refcount
check stuff.

This fixes a whole raft of nasty races in devqmi_close().

Note that it is not required that userspace use the ioctl; if it doesn't
(because, for example, it crashes), we'll still clean up any dangling QMI
contexts at release time.

BUG=chromium-os:10360
TEST=Adhoc
Reproducing the bug this fixes (see 10360) is almost impossible. :(

Change-Id: Ic64f64d89757f2ad95d2df9e8da04ddda3209bda
Signed-off-by: Elly Jones <ellyjones@chromium.org>
Signed-off-by: Jason Glasgow <jglasgow@chromium.org>
Signed-off-by: Mandeep Singh Baines <msb@chromium.org>

Review URL: http://codereview.chromium.org/6694028
(cherry picked from commit 4b00239a9ecc343ed9a8aee9cb753fe19ac88917)

CHROMIUM: qcusbnet: don't leak clients_lock

Was present in the original qualcomm driver; we reported it to them and they
fixed it, but we didn't pull the fix into the rewrite. Oops.

BUG=None
TEST=Adhoc
'gobi3k reset' with a gobi3k present triggers this most of the time.

Change-Id: I02bd90fd17bec841a8d448507e2f8117f57e8328
Signed-off-by: Elly Jones <ellyjones@chromium.org>
Signed-off-by: Jason Glasgow <jglasgow@chromium.org>

Review URL: http://codereview.chromium.org/6695025
(cherry picked from commit fada8abe8cc60f45520bdec78a7a8f6bbf706bb1)

CHROMIUM: Add gobi driver.

(It isn't built yet, so this should have no functional changes at all).

BUG=chromium-os:5521
TEST=suite_Cellular

Change-Id: I28bc550bd600ba350f8b34926e698bbb2b3c8c6c
Signed-off-by: Elly Jones <ellyjones@chromium.org>
Signed-off-by: Jason Glasgow <jglasgow@chromium.org>
Signed-off-by: Olof Johansson <olofj@chromium.org>

Review URL: http://codereview.chromium.org/6539018
(cherry picked from commit 7e3365245483506d389c21d1074252a41efbcf68)

Review URL: http://codereview.chromium.org/6822058
(cherry picked from commit ff3fb27d776b6f7dbcdc0db8bc8d9901a6cfed4e)
diff --git a/drivers/net/usb/gobi/qcusbnet.c b/drivers/net/usb/gobi/qcusbnet.c
index 7e218a8..33e26a4 100644
--- a/drivers/net/usb/gobi/qcusbnet.c
+++ b/drivers/net/usb/gobi/qcusbnet.c
@@ -21,13 +21,51 @@
 #include "qmi.h"
 #include "qcusbnet.h"
 
-#define DRIVER_VERSION "1.0.110"
+#define DRIVER_VERSION "1.0.110+google"
 #define DRIVER_AUTHOR "Qualcomm Innovation Center"
-#define DRIVER_DESC "QCUSBNet2k"
+#define DRIVER_DESC "gobi"
+
+static LIST_HEAD(qcusbnet_list);
+static DEFINE_MUTEX(qcusbnet_lock);
 
 int qcusbnet_debug;
 static struct class *devclass;
 
+static void free_dev(struct kref *ref)
+{
+	struct qcusbnet *dev = container_of(ref, struct qcusbnet, refcount);
+	list_del(&dev->node);
+	kfree(dev);
+}
+
+void qcusbnet_put(struct qcusbnet *dev)
+{
+	mutex_lock(&qcusbnet_lock);
+	kref_put(&dev->refcount, free_dev);
+	mutex_unlock(&qcusbnet_lock);
+}
+
+struct qcusbnet *qcusbnet_get(struct qcusbnet *key)
+{
+	/* Given a putative qcusbnet struct, return either the struct itself
+	 * (with a ref taken) if the struct is still visible, or NULL if it's
+	 * not. This prevents object-visibility races where someone is looking
+	 * up an object as the last ref gets dropped; dropping the last ref and
+	 * removing the object from the list are atomic with respect to getting
+	 * a new ref. */
+	struct qcusbnet *entry;
+	mutex_lock(&qcusbnet_lock);
+	list_for_each_entry(entry, &qcusbnet_list, node) {
+		if (entry == key) {
+			kref_get(&entry->refcount);
+			mutex_unlock(&qcusbnet_lock);
+			return entry;
+		}
+	}
+	mutex_unlock(&qcusbnet_lock);
+	return NULL;
+}
+
 int qc_suspend(struct usb_interface *iface, pm_message_t event)
 {
 	struct usbnet *usbnet;
@@ -132,7 +170,8 @@
 		return -EINVAL;
 	}
 
-	if (iface->cur_altsetting->desc.bInterfaceNumber != 0) {
+	if (iface->cur_altsetting->desc.bInterfaceNumber != 0
+	    && iface->cur_altsetting->desc.bInterfaceNumber != 5) {
 		DBG("invalid interface %d\n",
 			  iface->cur_altsetting->desc.bInterfaceNumber);
 		return -EINVAL;
@@ -184,8 +223,8 @@
 
 	kfree(usbnet->net->netdev_ops);
 	usbnet->net->netdev_ops = NULL;
-
-	kfree(dev);
+	/* drop the list's ref */
+	qcusbnet_put(dev);
 }
 
 static void qcnet_urbhook(struct urb *urb)
@@ -531,6 +570,9 @@
 	MKVIDPID(0x05c6, 0x9225),	/* Sony Gobi 2000 */
 	MKVIDPID(0x05c6, 0x9235),	/* Top Global Gobi 2000 */
 	MKVIDPID(0x05c6, 0x9275),	/* iRex Technologies Gobi 2000 */
+
+	MKVIDPID(0x05c6, 0x920d),	/* Qualcomm Gobi 3000 */
+	MKVIDPID(0x1410, 0xa021),	/* Novatel Gobi 3000 */
 	{ }
 };
 
@@ -590,10 +632,12 @@
 	DBG("Mac Address: %pM\n", dev->usbnet->net->dev_addr);
 
 	dev->valid = false;
-	memset(&dev->qmi, 0, sizeof(struct qmidev));
+	memset(&dev->qmi, 0, sizeof(dev->qmi));
 
 	dev->qmi.devclass = devclass;
 
+	kref_init(&dev->refcount);
+	INIT_LIST_HEAD(&dev->node);
 	INIT_LIST_HEAD(&dev->qmi.clients);
 	init_completion(&dev->worker.work);
 	spin_lock_init(&dev->qmi.clients_lock);
@@ -605,6 +649,11 @@
 	status = qc_register(dev);
 	if (status) {
 		qc_deregister(dev);
+	} else {
+		mutex_lock(&qcusbnet_lock);
+		/* Give our initial ref to the list */
+		list_add(&dev->node, &qcusbnet_list);
+		mutex_unlock(&qcusbnet_lock);
 	}
 
 	return status;
@@ -612,7 +661,7 @@
 EXPORT_SYMBOL_GPL(qcnet_probe);
 
 static struct usb_driver qcusbnet = {
-	.name       = "QCUSBNet2k",
+	.name       = "gobi",
 	.id_table   = qc_vidpids,
 	.probe      = qcnet_probe,
 	.disconnect = usbnet_disconnect,
diff --git a/drivers/net/usb/gobi/qcusbnet.h b/drivers/net/usb/gobi/qcusbnet.h
index 2f20868..00c3a7e 100644
--- a/drivers/net/usb/gobi/qcusbnet.h
+++ b/drivers/net/usb/gobi/qcusbnet.h
@@ -19,6 +19,10 @@
 #ifndef QCUSBNET_QCUSBNET_H
 #define QCUSBNET_QCUSBNET_H
 
+#include "structs.h"
+
 extern int qc_suspend(struct usb_interface *iface, pm_message_t event);
+extern void qcusbnet_put(struct qcusbnet *dev);
+extern struct qcusbnet *qcusbnet_get(struct qcusbnet *dev);
 
 #endif /* !QCUSBNET_QCUSBNET_H */
diff --git a/drivers/net/usb/gobi/qmidevice.c b/drivers/net/usb/gobi/qmidevice.c
index 82cb0fb..be61dda 100644
--- a/drivers/net/usb/gobi/qmidevice.c
+++ b/drivers/net/usb/gobi/qmidevice.c
@@ -68,9 +68,11 @@
 static bool client_addurb(struct qcusbnet *dev, u16 cid, struct urb *urb);
 static struct urb *client_delurb(struct qcusbnet *dev, u16 cid);
 
+static int resubmit_int_urb(struct urb *urb);
+
 static int devqmi_open(struct inode *inode, struct file *file);
 static int devqmi_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);
-static int devqmi_close(struct file *file, fl_owner_t ftable);
+static int devqmi_release(struct inode *inode, struct file *file);
 static ssize_t devqmi_read(struct file *file, char __user *buf, size_t size,
 			   loff_t *pos);
 static ssize_t devqmi_write(struct file *file, const char __user *buf,
@@ -81,19 +83,20 @@
 static int setup_wds_callback(struct qcusbnet *dev);
 static int qmidms_getmeid(struct qcusbnet *dev);
 
-#define IOCTL_QMI_GET_SERVICE_FILE	(0x8BE0 + 1)
-#define IOCTL_QMI_GET_DEVICE_VIDPID	(0x8BE0 + 2)
-#define IOCTL_QMI_GET_DEVICE_MEID	(0x8BE0 + 3)
+#define IOCTL_QMI_GET_SERVICE_FILE      (0x8BE0 + 1)
+#define IOCTL_QMI_GET_DEVICE_VIDPID     (0x8BE0 + 2)
+#define IOCTL_QMI_GET_DEVICE_MEID       (0x8BE0 + 3)
+#define IOCTL_QMI_CLOSE                 (0x8BE0 + 4)
 #define CDC_GET_ENCAPSULATED_RESPONSE	0x01A1ll
 #define CDC_CONNECTION_SPEED_CHANGE	0x08000000002AA1ll
 
 static const struct file_operations devqmi_fops = {
-	.owner = THIS_MODULE,
-	.read  = devqmi_read,
-	.write = devqmi_write,
-	.ioctl = devqmi_ioctl,
-	.open  = devqmi_open,
-	.flush = devqmi_close,
+	.owner   = THIS_MODULE,
+	.read    = devqmi_read,
+	.write   = devqmi_write,
+	.ioctl   = devqmi_ioctl,
+	.open    = devqmi_open,
+	.release = devqmi_release,
 };
 
 #ifdef CONFIG_SMP
@@ -131,6 +134,22 @@
 	return test_bit(reason, &dev->down);
 }
 
+static int resubmit_int_urb(struct urb *urb)
+{
+	int status;
+	int interval;
+	if (!urb || !urb->dev)
+		return -EINVAL;
+	interval = urb->dev->speed == USB_SPEED_HIGH ? 7 : 3;
+	usb_fill_int_urb(urb, urb->dev, urb->pipe, urb->transfer_buffer,
+	                 urb->transfer_buffer_length, urb->complete,
+	                 urb->context, interval);
+	status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (status)
+		DBG("status %d", status);
+	return status;
+}
+
 static void read_callback(struct urb *urb)
 {
 	struct list_head *node;
@@ -157,6 +176,7 @@
 
 	if (urb->status) {
 		DBG("Read status = %d\n", urb->status);
+		resubmit_int_urb(dev->qmi.inturb);
 		return;
 	}
 
@@ -165,17 +185,20 @@
 	data = urb->transfer_buffer;
 	size = urb->actual_length;
 
-	print_hex_dump(KERN_INFO, "QCUSBNet2k: ", DUMP_PREFIX_OFFSET,
-		       16, 1, data, size, true);
+	if (qcusbnet_debug)
+		print_hex_dump(KERN_INFO, "gobi-read: ", DUMP_PREFIX_OFFSET,
+		               16, 1, data, size, true);
 
 	result = qmux_parse(&cid, data, size);
 	if (result < 0) {
 		DBG("Read error parsing QMUX %d\n", result);
+		resubmit_int_urb(dev->qmi.inturb);
 		return;
 	}
 
 	if (size < result + 3) {
 		DBG("Data buffer too small to parse\n");
+		resubmit_int_urb(dev->qmi.inturb);
 		return;
 	}
 
@@ -195,6 +218,7 @@
 					  "read will be discarded\n");
 				kfree(copy);
 				spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+				resubmit_int_urb(dev->qmi.inturb);
 				return;
 			}
 
@@ -209,6 +233,7 @@
 	}
 
 	spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
+	resubmit_int_urb(dev->qmi.inturb);
 }
 
 static void int_callback(struct urb *urb)
@@ -254,21 +279,15 @@
 			}
 		} else {
 			DBG("ignoring invalid interrupt in packet\n");
-			print_hex_dump(KERN_INFO, "QCUSBNet2k: ",
-				       DUMP_PREFIX_OFFSET, 16, 1,
-				       urb->transfer_buffer,
-				       urb->actual_length, true);
+			if (qcusbnet_debug)
+				print_hex_dump(KERN_INFO, "gobi-int: ",
+				               DUMP_PREFIX_OFFSET, 16, 1,
+				               urb->transfer_buffer,
+				               urb->actual_length, true);
 		}
 	}
 
-	interval = (dev->usbnet->udev->speed == USB_SPEED_HIGH) ? 7 : 3;
-
-	usb_fill_int_urb(urb, urb->dev,	urb->pipe, urb->transfer_buffer,
-			 urb->transfer_buffer_length, urb->complete,
-			 urb->context, interval);
-	status = usb_submit_urb(urb, GFP_ATOMIC);
-	if (status)
-		DBG("Error re-submitting Int URB %d\n", status);
+	resubmit_int_urb(dev->qmi.inturb);
 	return;
 }
 
@@ -522,8 +541,9 @@
 			     NULL, dev);
 
 	DBG("Actual Write:\n");
-	print_hex_dump(KERN_INFO,  "QCUSBNet2k: ", DUMP_PREFIX_OFFSET,
-		       16, 1, buf, size, true);
+	if (qcusbnet_debug)
+		print_hex_dump(KERN_INFO,  "gobi-write: ", DUMP_PREFIX_OFFSET,
+		               16, 1, buf, size, true);
 
 	sema_init(&sem, 0);
 
@@ -679,11 +699,6 @@
 	unsigned long flags;
 	u8 tid;
 
-	if (!device_valid(dev)) {
-		DBG("invalid device\n");
-		return;
-	}
-
 	DBG("releasing 0x%04X\n", cid);
 
 	if (cid != QMICTL) {
@@ -736,7 +751,6 @@
 			kfree(client);
 		}
 	}
-
 	spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
 }
 
@@ -950,10 +964,13 @@
 	struct qmidev *qmidev = container_of(inode->i_cdev, struct qmidev, cdev);
 	struct qcusbnet *dev = container_of(qmidev, struct qcusbnet, qmi);
 
-	if (!device_valid(dev)) {
-		DBG("Invalid device\n");
+	/* We need an extra ref on the device per fd, since we stash a ref
+	 * inside the handle. If qcusbnet_get() returns NULL, that means the
+	 * device has been removed from the list - no new refs for us. */
+	struct qcusbnet *ref = qcusbnet_get(dev);
+
+	if (!ref)
 		return -ENXIO;
-	}
 
 	file->private_data = kmalloc(sizeof(struct qmihandle), GFP_KERNEL);
 	if (!file->private_data) {
@@ -963,7 +980,7 @@
 
 	handle = (struct qmihandle *)file->private_data;
 	handle->cid = (u16)-1;
-	handle->dev = dev;
+	handle->dev = ref;
 
 	DBG("%p %04x", handle, handle->cid);
 
@@ -984,9 +1001,13 @@
 		return -EBADF;
 	}
 
+	if (handle->dev->dying) {
+		DBG("Dying device");
+		return -ENXIO;
+	}
+
 	if (!device_valid(handle->dev)) {
-		DBG("Invalid device! Updating f_ops\n");
-		file->f_op = file->f_dentry->d_inode->i_fop;
+		DBG("Invalid device!\n");
 		return -ENXIO;
 	}
 
@@ -1012,6 +1033,31 @@
 		return 0;
 		break;
 
+	/* Okay, all aboard the nasty hack express. If we don't have this
+	 * ioctl() (and we just rely on userspace to close() the file
+	 * descriptors), if userspace has any refs left to this fd (like, say, a
+	 * pending read()), then the read might hang around forever. Userspace
+	 * needs a way to cause us to kick people off those waitqueues before
+	 * closing the fd for good.
+	 *
+	 * If this driver used workqueues, the correct approach here would
+	 * instead be to make the file descriptor select()able, and then just
+	 * use select() instead of aio in userspace (thus allowing us to get
+	 * away with one thread total and avoiding the recounting mess
+	 * altogether).
+	 */
+	case IOCTL_QMI_CLOSE:
+		DBG("Tearing down QMI for service %lu", arg);
+		if (handle->cid == (u16)-1) {
+			DBG("no qmi cid");
+			return -EBADR;
+		}
+
+		file->private_data = NULL;
+		client_free(handle->dev, handle->cid);
+		kfree(handle);
+		return 0;
+		break;
 
 	case IOCTL_QMI_GET_DEVICE_VIDPID:
 		if (!arg) {
@@ -1056,60 +1102,15 @@
 	}
 }
 
-static int devqmi_close(struct file *file, fl_owner_t ftable)
+static int devqmi_release(struct inode *inode, struct file *file)
 {
 	struct qmihandle *handle = (struct qmihandle *)file->private_data;
-	struct task_struct *task;
-	struct fdtable *fdtable;
-	int count = 0;
-	int used = 0;
-	unsigned long flags;
-
-	if (!handle) {
-		DBG("bad file data\n");
-		return -EBADF;
-	}
-
-	if (file_count(file) != 1) {
-		rcu_read_lock();
-		for_each_process(task) {
-			if (!task || !task->files)
-				continue;
-			spin_lock_irqsave(&task->files->file_lock, flags);
-			fdtable = files_fdtable(task->files);
-			for (count = 0; count < fdtable->max_fds; count++) {
-				/* Before this function was called, this file was removed
-				 * from our task's file table so if we find it in a file
-				 * table then it is being used by another task
-				 */
-				if (fdtable->fd[count] == file) {
-					used++;
-					break;
-				}
-			}
-			spin_unlock_irqrestore(&task->files->file_lock, flags);
-		}
-		rcu_read_unlock();
-
-		if (used > 0) {
-			DBG("not closing, as this FD is open by %d other process\n", used);
-			return 0;
-		}
-	}
-
-	if (!device_valid(handle->dev)) {
-		DBG("Invalid device! Updating f_ops\n");
-		file->f_op = file->f_dentry->d_inode->i_fop;
-		return -ENXIO;
-	}
-
-	DBG("0x%04X\n", handle->cid);
-
+	if (!handle)
+		return 0;
 	file->private_data = NULL;
-
 	if (handle->cid != (u16)-1)
 		client_free(handle->dev, handle->cid);
-
+	qcusbnet_put(handle->dev);
 	kfree(handle);
 	return 0;
 }
@@ -1127,9 +1128,13 @@
 		return -EBADF;
 	}
 
+	if (handle->dev->dying) {
+		DBG("Dying device");
+		return -ENXIO;
+	}
+
 	if (!device_valid(handle->dev)) {
-		DBG("Invalid device! Updating f_ops\n");
-		file->f_op = file->f_dentry->d_inode->i_fop;
+		DBG("Invalid device!\n");
 		return -ENXIO;
 	}
 
@@ -1212,6 +1217,7 @@
 	char *name;
 
 	dev->valid = true;
+	dev->dying = false;
 	result = client_alloc(dev, QMICTL);
 	if (result) {
 		dev->valid = false;
@@ -1269,7 +1275,7 @@
 	}
 
 	printk(KERN_INFO "creating qcqmi%d\n", qmiidx);
-	device_create(dev->qmi.devclass, NULL, devno, NULL, "qcqmi%d", qmiidx);
+	device_create(dev->qmi.devclass, &dev->iface->dev, devno, NULL, "qcqmi%d", qmiidx);
 
 	dev->qmi.devnum = devno;
 	return 0;
@@ -1279,19 +1285,8 @@
 {
 	struct list_head *node, *tmp;
 	struct client *client;
-	struct inode *inode;
-	struct list_head *inodes;
-	struct task_struct *task;
-	struct fdtable *fdtable;
-	struct file *file;
-	unsigned long flags;
-	int count = 0;
 
-	if (!device_valid(dev)) {
-		DBG("wrong device\n");
-		return;
-	}
-
+	dev->dying = true;
 	list_for_each_safe(node, tmp, &dev->qmi.clients) {
 		client = list_entry(node, struct client, node);
 		DBG("release 0x%04X\n", client->cid);
@@ -1300,33 +1295,6 @@
 
 	qc_stopread(dev);
 	dev->valid = false;
-	list_for_each(inodes, &dev->qmi.cdev.list) {
-		inode = container_of(inodes, struct inode, i_devices);
-		if (inode != NULL && !IS_ERR(inode)) {
-			rcu_read_lock();
-			for_each_process(task) {
-				if (!task || !task->files)
-					continue;
-				spin_lock_irqsave(&task->files->file_lock, flags);
-				fdtable = files_fdtable(task->files);
-				for (count = 0; count < fdtable->max_fds; count++) {
-					file = fdtable->fd[count];
-					if (file != NULL &&  file->f_dentry != NULL) {
-						if (file->f_dentry->d_inode == inode) {
-							rcu_assign_pointer(fdtable->fd[count], NULL);
-							spin_unlock_irqrestore(&task->files->file_lock, flags);
-							DBG("forcing close of open file handle\n");
-							filp_close(file, task->files);
-							spin_lock_irqsave(&task->files->file_lock, flags);
-						}
-					}
-				}
-				spin_unlock_irqrestore(&task->files->file_lock, flags);
-			}
-			rcu_read_unlock();
-		}
-	}
-
 	if (!IS_ERR(dev->qmi.devclass))
 		device_destroy(dev->qmi.devclass, dev->qmi.devnum);
 	cdev_del(&dev->qmi.cdev);
@@ -1377,6 +1345,8 @@
 				spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
 				kfree(rbuf);
 				break;
+			} else {
+				spin_unlock_irqrestore(&dev->qmi.clients_lock, flags);
 			}
 		} else {
 			spin_lock_irqsave(&dev->qmi.clients_lock, flags);
diff --git a/drivers/net/usb/gobi/structs.h b/drivers/net/usb/gobi/structs.h
index 7a89c5c..13b3788 100644
--- a/drivers/net/usb/gobi/structs.h
+++ b/drivers/net/usb/gobi/structs.h
@@ -25,6 +25,7 @@
 #include <linux/usb.h>
 #include <linux/version.h>
 #include <linux/cdev.h>
+#include <linux/kobject.h>
 #include <linux/kthread.h>
 
 #include <linux/usb/usbnet.h>
@@ -78,12 +79,15 @@
 };
 
 struct qcusbnet {
+	struct list_head node;
+	struct kref refcount;
 	struct usbnet *usbnet;
 	struct usb_interface *iface;
 	int (*open)(struct net_device *);
 	int (*stop)(struct net_device *);
 	unsigned long down;
 	bool valid;
+	bool dying;
 	struct qmidev qmi;
 	char meid[14];
 	struct worker worker;