scsi: ch: fixup refcounting imbalance for SCSI devices
[linux-2.6-microblaze.git] / drivers / scsi / ch.c
index ed5f4a6..e3a006d 100644 (file)
@@ -569,6 +569,7 @@ static void ch_destroy(struct kref *ref)
 {
        scsi_changer *ch = container_of(ref, scsi_changer, ref);
 
+       ch->device = NULL;
        kfree(ch->dt);
        kfree(ch);
 }
@@ -594,14 +595,17 @@ ch_open(struct inode *inode, struct file *file)
        spin_lock(&ch_index_lock);
        ch = idr_find(&ch_index_idr, minor);
 
-       if (NULL == ch || scsi_device_get(ch->device)) {
+       if (ch == NULL || !kref_get_unless_zero(&ch->ref)) {
                spin_unlock(&ch_index_lock);
                mutex_unlock(&ch_mutex);
                return -ENXIO;
        }
-       kref_get(&ch->ref);
        spin_unlock(&ch_index_lock);
-
+       if (scsi_device_get(ch->device)) {
+               kref_put(&ch->ref, ch_destroy);
+               mutex_unlock(&ch_mutex);
+               return -ENXIO;
+       }
        file->private_data = ch;
        mutex_unlock(&ch_mutex);
        return 0;
@@ -938,6 +942,12 @@ static int ch_probe(struct device *dev)
 
        ch->minor = ret;
        sprintf(ch->name,"ch%d",ch->minor);
+       ret = scsi_device_get(sd);
+       if (ret) {
+               sdev_printk(KERN_WARNING, sd, "ch%d: failed to get device\n",
+                           ch->minor);
+               goto remove_idr;
+       }
 
        class_dev = device_create(ch_sysfs_class, dev,
                                  MKDEV(SCSI_CHANGER_MAJOR, ch->minor), ch,
@@ -946,7 +956,7 @@ static int ch_probe(struct device *dev)
                sdev_printk(KERN_WARNING, sd, "ch%d: device_create failed\n",
                            ch->minor);
                ret = PTR_ERR(class_dev);
-               goto remove_idr;
+               goto put_device;
        }
 
        mutex_init(&ch->lock);
@@ -964,6 +974,8 @@ static int ch_probe(struct device *dev)
        return 0;
 destroy_dev:
        device_destroy(ch_sysfs_class, MKDEV(SCSI_CHANGER_MAJOR, ch->minor));
+put_device:
+       scsi_device_put(sd);
 remove_idr:
        idr_remove(&ch_index_idr, ch->minor);
 free_ch:
@@ -977,9 +989,11 @@ static int ch_remove(struct device *dev)
 
        spin_lock(&ch_index_lock);
        idr_remove(&ch_index_idr, ch->minor);
+       dev_set_drvdata(dev, NULL);
        spin_unlock(&ch_index_lock);
 
        device_destroy(ch_sysfs_class, MKDEV(SCSI_CHANGER_MAJOR,ch->minor));
+       scsi_device_put(ch->device);
        kref_put(&ch->ref, ch_destroy);
        return 0;
 }