Index: drivers/net/usb/gobi/qmidevice.c |
diff --git a/drivers/net/usb/gobi/qmidevice.c b/drivers/net/usb/gobi/qmidevice.c |
index 33df8261612cb3d32a8846516fe13cae4d9ad49c..6abf7c4fcefa6126c53bf65ca0c031497876a7e4 100644 |
--- a/drivers/net/usb/gobi/qmidevice.c |
+++ b/drivers/net/usb/gobi/qmidevice.c |
@@ -732,7 +732,6 @@ static void client_free(struct qcusbnet *dev, u16 cid) |
kfree(client); |
} |
} |
- |
spin_unlock_irqrestore(&dev->qmi.clients_lock, flags); |
} |
@@ -946,10 +945,13 @@ static int devqmi_open(struct inode *inode, struct file *file) |
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) { |
@@ -959,7 +961,7 @@ static int devqmi_open(struct inode *inode, struct file *file) |
handle = (struct qmihandle *)file->private_data; |
handle->cid = (u16)-1; |
- handle->dev = dev; |
+ handle->dev = ref; |
DBG("%p %04x", handle, handle->cid); |
@@ -980,6 +982,11 @@ static int devqmi_ioctl(struct inode *inode, struct file *file, unsigned int cmd |
return -EBADF; |
} |
+ if (handle->dev->dying) { |
+ DBG("Dying device"); |
+ return -ENXIO; |
+ } |
+ |
if (!device_valid(handle->dev)) { |
DBG("Invalid device!\n"); |
return -ENXIO; |
@@ -1084,6 +1091,7 @@ static int devqmi_release(struct inode *inode, struct file *file) |
file->private_data = NULL; |
if (handle->cid != (u16)-1) |
client_free(handle->dev, handle->cid); |
+ qcusbnet_put(handle->dev); |
kfree(handle); |
return 0; |
} |
@@ -1101,6 +1109,11 @@ static ssize_t devqmi_read(struct file *file, char __user *buf, size_t size, |
return -EBADF; |
} |
+ if (handle->dev->dying) { |
+ DBG("Dying device"); |
+ return -ENXIO; |
+ } |
+ |
if (!device_valid(handle->dev)) { |
DBG("Invalid device!\n"); |
return -ENXIO; |
@@ -1185,6 +1198,7 @@ int qc_register(struct qcusbnet *dev) |
char *name; |
dev->valid = true; |
+ dev->dying = false; |
result = client_alloc(dev, QMICTL); |
if (result) { |
dev->valid = false; |
@@ -1252,14 +1266,8 @@ void qc_deregister(struct qcusbnet *dev) |
{ |
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; |
+ 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); |
@@ -1268,33 +1276,6 @@ void qc_deregister(struct qcusbnet *dev) |
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); |