From: Alan Stern Related to http://bugzilla.kernel.org/show_bug.cgi?id=4863 This is the patch for the SCSI core. I originally sent it in not as a real patch submission but as a starting point for further testing and modification. Mike Anderson was working on many of the same issues at the same time; see http://marc.theaimsgroup.com/?l=linux-scsi&m=111954227709734&w=2 as well as the thread containing my patch. Not much progress seems to have happened since then, and I honestly don't know whether James Bottomley has applied any of these or not. Signed-off-by: Alan Stern Signed-off-by: Andrew Morton --- drivers/scsi/hosts.c | 7 ++ drivers/scsi/scsi_priv.h | 1 drivers/scsi/scsi_scan.c | 118 ++++++++++++++++++++++++++-------------------- drivers/scsi/scsi_sysfs.c | 37 ++++++++------ include/scsi/scsi_host.h | 1 5 files changed, 98 insertions(+), 66 deletions(-) diff -puN drivers/scsi/hosts.c~fix-something-in-scsi drivers/scsi/hosts.c --- 25/drivers/scsi/hosts.c~fix-something-in-scsi Fri Jul 8 16:50:49 2005 +++ 25-akpm/drivers/scsi/hosts.c Fri Jul 8 16:50:49 2005 @@ -74,8 +74,13 @@ static void scsi_host_cancel(struct Scsi **/ void scsi_remove_host(struct Scsi_Host *shost) { - scsi_forget_host(shost); + set_bit(SHOST_REMOVE, &shost->shost_state); scsi_host_cancel(shost, 0); + + down(&shost->scan_mutex); /* Wait for scanning to stop */ + up(&shost->scan_mutex); + + scsi_forget_host(shost); scsi_proc_host_rm(shost); set_bit(SHOST_DEL, &shost->shost_state); diff -puN drivers/scsi/scsi_priv.h~fix-something-in-scsi drivers/scsi/scsi_priv.h --- 25/drivers/scsi/scsi_priv.h~fix-something-in-scsi Fri Jul 8 16:50:49 2005 +++ 25-akpm/drivers/scsi/scsi_priv.h Fri Jul 8 16:50:49 2005 @@ -121,6 +121,7 @@ extern void scsi_sysfs_unregister(void); extern void scsi_sysfs_device_initialize(struct scsi_device *); extern int scsi_sysfs_target_initialize(struct scsi_device *); extern struct scsi_transport_template blank_transport_template; +extern void __scsi_remove_device(struct scsi_device *); extern struct bus_type scsi_bus_type; diff -puN drivers/scsi/scsi_scan.c~fix-something-in-scsi drivers/scsi/scsi_scan.c --- 25/drivers/scsi/scsi_scan.c~fix-something-in-scsi Fri Jul 8 16:50:49 2005 +++ 25-akpm/drivers/scsi/scsi_scan.c Fri Jul 8 16:50:49 2005 @@ -855,8 +855,12 @@ static int scsi_probe_and_add_lun(struct out_free_sdev: if (res == SCSI_SCAN_LUN_PRESENT) { if (sdevp) { - scsi_device_get(sdev); - *sdevp = sdev; + if (scsi_device_get(sdev) == 0) { + *sdevp = sdev; + } else { + __scsi_remove_device(sdev); + res = SCSI_SCAN_NO_RESPONSE; + } } } else { if (sdev->host->hostt->slave_destroy) @@ -1190,22 +1194,28 @@ struct scsi_device *__scsi_add_device(st uint id, uint lun, void *hostdata) { struct scsi_device *sdev; - struct device *parent = &shost->shost_gendev; int res; - struct scsi_target *starget = scsi_alloc_target(parent, channel, id); + struct scsi_target *starget; - if (!starget) - return ERR_PTR(-ENOMEM); + down(&shost->scan_mutex); + if (test_bit(SHOST_REMOVE, &shost->shost_state)) { + sdev = ERR_PTR(-ENODEV); + goto out; + } + starget = scsi_alloc_target(&shost->shost_gendev, channel, id); + if (!starget) { + sdev = ERR_PTR(-ENOMEM); + goto out; + } get_device(&starget->dev); - down(&shost->scan_mutex); res = scsi_probe_and_add_lun(starget, lun, NULL, &sdev, 1, hostdata); if (res != SCSI_SCAN_LUN_PRESENT) sdev = ERR_PTR(-ENODEV); - up(&shost->scan_mutex); scsi_target_reap(starget); put_device(&starget->dev); - +out: + up(&shost->scan_mutex); return sdev; } EXPORT_SYMBOL(__scsi_add_device); @@ -1226,27 +1236,8 @@ void scsi_rescan_device(struct device *d } EXPORT_SYMBOL(scsi_rescan_device); -/** - * scsi_scan_target - scan a target id, possibly including all LUNs on the - * target. - * @sdevsca: Scsi_Device handle for scanning - * @shost: host to scan - * @channel: channel to scan - * @id: target id to scan - * - * Description: - * Scan the target id on @shost, @channel, and @id. Scan at least LUN - * 0, and possibly all LUNs on the target id. - * - * Use the pre-allocated @sdevscan as a handle for the scanning. This - * function sets sdevscan->host, sdevscan->id and sdevscan->lun; the - * scanning functions modify sdevscan->lun. - * - * First try a REPORT LUN scan, if that does not scan the target, do a - * sequential scan of LUNs on the target id. - **/ -void scsi_scan_target(struct device *parent, unsigned int channel, - unsigned int id, unsigned int lun, int rescan) +static void __scsi_scan_target(struct device *parent, unsigned int channel, + unsigned int id, unsigned int lun, int rescan) { struct Scsi_Host *shost = dev_to_shost(parent); int bflags = 0; @@ -1260,9 +1251,7 @@ void scsi_scan_target(struct device *par */ return; - starget = scsi_alloc_target(parent, channel, id); - if (!starget) return; @@ -1308,6 +1297,32 @@ void scsi_scan_target(struct device *par put_device(&starget->dev); } + +/** + * scsi_scan_target - scan a target id, possibly including all LUNs on the + * target. + * @parent: host to scan + * @channel: channel to scan + * @id: target id to scan + * @rescan: passed to LUN scanning routines + * + * Description: + * Scan the target id on @shost, @channel, and @id. Scan at least LUN + * 0, and possibly all LUNs on the target id. + * + * First try a REPORT LUN scan, if that does not scan the target, do a + * sequential scan of LUNs on the target id. + **/ +void scsi_scan_target(struct device *parent, unsigned int channel, + unsigned int id, unsigned int lun, int rescan) +{ + struct Scsi_Host *shost = dev_to_shost(parent); + + down(&shost->scan_mutex); + if (!test_bit(SHOST_REMOVE, &shost->shost_state)) + __scsi_scan_target(parent, channel, id, lun, rescan); + up(&shost->scan_mutex); +} EXPORT_SYMBOL(scsi_scan_target); static void scsi_scan_channel(struct Scsi_Host *shost, unsigned int channel, @@ -1333,10 +1348,12 @@ static void scsi_scan_channel(struct Scs order_id = shost->max_id - id - 1; else order_id = id; - scsi_scan_target(&shost->shost_gendev, channel, order_id, lun, rescan); + __scsi_scan_target(&shost->shost_gendev, channel, + order_id, lun, rescan); } else - scsi_scan_target(&shost->shost_gendev, channel, id, lun, rescan); + __scsi_scan_target(&shost->shost_gendev, channel, + id, lun, rescan); } int scsi_scan_host_selected(struct Scsi_Host *shost, unsigned int channel, @@ -1351,11 +1368,14 @@ int scsi_scan_host_selected(struct Scsi_ return -EINVAL; down(&shost->scan_mutex); + if (test_bit(SHOST_REMOVE, &shost->shost_state)) + goto out; if (channel == SCAN_WILD_CARD) for (channel = 0; channel <= shost->max_channel; channel++) scsi_scan_channel(shost, channel, id, lun, rescan); else scsi_scan_channel(shost, channel, id, lun, rescan); +out: up(&shost->scan_mutex); return 0; @@ -1387,23 +1407,17 @@ EXPORT_SYMBOL(scsi_scan_single_target); void scsi_forget_host(struct Scsi_Host *shost) { - struct scsi_target *starget, *tmp; + struct scsi_device *sdev; unsigned long flags; - /* - * Ok, this look a bit strange. We always look for the first device - * on the list as scsi_remove_device removes them from it - thus we - * also have to release the lock. - * We don't need to get another reference to the device before - * releasing the lock as we already own the reference from - * scsi_register_device that's release in scsi_remove_device. And - * after that we don't look at sdev anymore. - */ +restart: spin_lock_irqsave(shost->host_lock, flags); - list_for_each_entry_safe(starget, tmp, &shost->__targets, siblings) { + list_for_each_entry(sdev, &shost->__devices, siblings) { + if (sdev->sdev_state == SDEV_DEL) + continue; spin_unlock_irqrestore(shost->host_lock, flags); - scsi_remove_target(&starget->dev); - spin_lock_irqsave(shost->host_lock, flags); + __scsi_remove_device(sdev); + goto restart; } spin_unlock_irqrestore(shost->host_lock, flags); } @@ -1430,12 +1444,16 @@ void scsi_forget_host(struct Scsi_Host * */ struct scsi_device *scsi_get_host_dev(struct Scsi_Host *shost) { - struct scsi_device *sdev; + struct scsi_device *sdev = NULL; struct scsi_target *starget; + down(&shost->scan_mutex); + if (test_bit(SHOST_REMOVE, &shost->shost_state)) + goto out; + starget = scsi_alloc_target(&shost->shost_gendev, 0, shost->this_id); if (!starget) - return NULL; + goto out; sdev = scsi_alloc_sdev(starget, 0, NULL); if (sdev) { @@ -1443,6 +1461,8 @@ struct scsi_device *scsi_get_host_dev(st sdev->borken = 0; } put_device(&starget->dev); +out: + up(&shost->scan_mutex); return sdev; } EXPORT_SYMBOL(scsi_get_host_dev); diff -puN drivers/scsi/scsi_sysfs.c~fix-something-in-scsi drivers/scsi/scsi_sysfs.c --- 25/drivers/scsi/scsi_sysfs.c~fix-something-in-scsi Fri Jul 8 16:50:49 2005 +++ 25-akpm/drivers/scsi/scsi_sysfs.c Fri Jul 8 16:50:49 2005 @@ -591,7 +591,7 @@ int scsi_sysfs_add_sdev(struct scsi_devi error = attr_add(&sdev->sdev_gendev, sdev->host->hostt->sdev_attrs[i]); if (error) { - scsi_remove_device(sdev); + __scsi_remove_device(sdev); goto out; } } @@ -605,7 +605,7 @@ int scsi_sysfs_add_sdev(struct scsi_devi scsi_sysfs_sdev_attrs[i]); error = device_create_file(&sdev->sdev_gendev, attr); if (error) { - scsi_remove_device(sdev); + __scsi_remove_device(sdev); goto out; } } @@ -625,17 +625,10 @@ int scsi_sysfs_add_sdev(struct scsi_devi return error; } -/** - * scsi_remove_device - unregister a device from the scsi bus - * @sdev: scsi_device to unregister - **/ -void scsi_remove_device(struct scsi_device *sdev) +void __scsi_remove_device(struct scsi_device *sdev) { - struct Scsi_Host *shost = sdev->host; - - down(&shost->scan_mutex); if (scsi_device_set_state(sdev, SDEV_CANCEL) != 0) - goto out; + return; class_device_unregister(&sdev->sdev_classdev); device_del(&sdev->sdev_gendev); @@ -644,8 +637,17 @@ void scsi_remove_device(struct scsi_devi sdev->host->hostt->slave_destroy(sdev); transport_unregister_device(&sdev->sdev_gendev); put_device(&sdev->sdev_gendev); -out: - up(&shost->scan_mutex); +} + +/** + * scsi_remove_device - unregister a device from the scsi bus + * @sdev: scsi_device to unregister + **/ +void scsi_remove_device(struct scsi_device *sdev) +{ + down(&sdev->host->scan_mutex); + __scsi_remove_device(sdev); + up(&sdev->host->scan_mutex); } EXPORT_SYMBOL(scsi_remove_device); @@ -653,17 +655,20 @@ void __scsi_remove_target(struct scsi_ta { struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); unsigned long flags; - struct scsi_device *sdev, *tmp; + struct scsi_device *sdev; spin_lock_irqsave(shost->host_lock, flags); starget->reap_ref++; - list_for_each_entry_safe(sdev, tmp, &shost->__devices, siblings) { +restart: + list_for_each_entry(sdev, &shost->__devices, siblings) { if (sdev->channel != starget->channel || - sdev->id != starget->id) + sdev->id != starget->id || + sdev->sdev_state == SDEV_DEL) continue; spin_unlock_irqrestore(shost->host_lock, flags); scsi_remove_device(sdev); spin_lock_irqsave(shost->host_lock, flags); + goto restart; } spin_unlock_irqrestore(shost->host_lock, flags); scsi_target_reap(starget); diff -puN include/scsi/scsi_host.h~fix-something-in-scsi include/scsi/scsi_host.h --- 25/include/scsi/scsi_host.h~fix-something-in-scsi Fri Jul 8 16:50:49 2005 +++ 25-akpm/include/scsi/scsi_host.h Fri Jul 8 16:50:49 2005 @@ -436,6 +436,7 @@ enum { SHOST_DEL, SHOST_CANCEL, SHOST_RECOVERY, + SHOST_REMOVE, }; struct Scsi_Host { _