Commit 860dc736 authored by James Bottomley's avatar James Bottomley

[SCSI] fix async scan add/remove race resulting in an oops

Async scanning introduced a very wide window where the SCSI device is
up and running but has not yet been added to sysfs.  We delay the
adding until all scans have completed to retain the same ordering as
sync scanning.

This delay in visibility causes an oops if a device is removed before
we make it visible because the SCSI removal routines have an inbuilt
assumption that if a device is in SDEV_RUNNING state, it must be
visible (which is not necessarily true in the async scanning case).

Fix this by introducing an additional is_visible flag which we can use
to condition the tear down so we do the right thing for running but
not yet made visible.
Reported-by: default avatarAlexey Kuznetsov <kuznet@ms2.inr.ac.ru>
Signed-off-by: default avatarJames Bottomley <James.Bottomley@suse.de>
parent 3bf3583b
...@@ -952,16 +952,6 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result, ...@@ -952,16 +952,6 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result,
return SCSI_SCAN_LUN_PRESENT; return SCSI_SCAN_LUN_PRESENT;
} }
static inline void scsi_destroy_sdev(struct scsi_device *sdev)
{
scsi_device_set_state(sdev, SDEV_DEL);
if (sdev->host->hostt->slave_destroy)
sdev->host->hostt->slave_destroy(sdev);
transport_destroy_device(&sdev->sdev_gendev);
put_device(&sdev->sdev_dev);
put_device(&sdev->sdev_gendev);
}
#ifdef CONFIG_SCSI_LOGGING #ifdef CONFIG_SCSI_LOGGING
/** /**
* scsi_inq_str - print INQUIRY data from min to max index, strip trailing whitespace * scsi_inq_str - print INQUIRY data from min to max index, strip trailing whitespace
...@@ -1139,7 +1129,7 @@ static int scsi_probe_and_add_lun(struct scsi_target *starget, ...@@ -1139,7 +1129,7 @@ static int scsi_probe_and_add_lun(struct scsi_target *starget,
} }
} }
} else } else
scsi_destroy_sdev(sdev); __scsi_remove_device(sdev);
out: out:
return res; return res;
} }
...@@ -1500,7 +1490,7 @@ static int scsi_report_lun_scan(struct scsi_target *starget, int bflags, ...@@ -1500,7 +1490,7 @@ static int scsi_report_lun_scan(struct scsi_target *starget, int bflags,
/* /*
* the sdev we used didn't appear in the report luns scan * the sdev we used didn't appear in the report luns scan
*/ */
scsi_destroy_sdev(sdev); __scsi_remove_device(sdev);
return ret; return ret;
} }
...@@ -1710,7 +1700,7 @@ static void scsi_sysfs_add_devices(struct Scsi_Host *shost) ...@@ -1710,7 +1700,7 @@ static void scsi_sysfs_add_devices(struct Scsi_Host *shost)
shost_for_each_device(sdev, shost) { shost_for_each_device(sdev, shost) {
if (!scsi_host_scan_allowed(shost) || if (!scsi_host_scan_allowed(shost) ||
scsi_sysfs_add_sdev(sdev) != 0) scsi_sysfs_add_sdev(sdev) != 0)
scsi_destroy_sdev(sdev); __scsi_remove_device(sdev);
} }
} }
...@@ -1943,7 +1933,7 @@ void scsi_free_host_dev(struct scsi_device *sdev) ...@@ -1943,7 +1933,7 @@ void scsi_free_host_dev(struct scsi_device *sdev)
{ {
BUG_ON(sdev->id != sdev->host->this_id); BUG_ON(sdev->id != sdev->host->this_id);
scsi_destroy_sdev(sdev); __scsi_remove_device(sdev);
} }
EXPORT_SYMBOL(scsi_free_host_dev); EXPORT_SYMBOL(scsi_free_host_dev);
...@@ -854,82 +854,73 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev) ...@@ -854,82 +854,73 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)
transport_configure_device(&starget->dev); transport_configure_device(&starget->dev);
error = device_add(&sdev->sdev_gendev); error = device_add(&sdev->sdev_gendev);
if (error) { if (error) {
put_device(sdev->sdev_gendev.parent);
printk(KERN_INFO "error 1\n"); printk(KERN_INFO "error 1\n");
return error; goto out_remove;
} }
error = device_add(&sdev->sdev_dev); error = device_add(&sdev->sdev_dev);
if (error) { if (error) {
printk(KERN_INFO "error 2\n"); printk(KERN_INFO "error 2\n");
goto clean_device; device_del(&sdev->sdev_gendev);
goto out_remove;
} }
transport_add_device(&sdev->sdev_gendev);
sdev->is_visible = 1;
/* create queue files, which may be writable, depending on the host */ /* create queue files, which may be writable, depending on the host */
if (sdev->host->hostt->change_queue_depth) if (sdev->host->hostt->change_queue_depth)
error = device_create_file(&sdev->sdev_gendev, &sdev_attr_queue_depth_rw); error = device_create_file(&sdev->sdev_gendev, &sdev_attr_queue_depth_rw);
else else
error = device_create_file(&sdev->sdev_gendev, &dev_attr_queue_depth); error = device_create_file(&sdev->sdev_gendev, &dev_attr_queue_depth);
if (error) { if (error)
__scsi_remove_device(sdev); goto out_remove;
goto out;
}
if (sdev->host->hostt->change_queue_type) if (sdev->host->hostt->change_queue_type)
error = device_create_file(&sdev->sdev_gendev, &sdev_attr_queue_type_rw); error = device_create_file(&sdev->sdev_gendev, &sdev_attr_queue_type_rw);
else else
error = device_create_file(&sdev->sdev_gendev, &dev_attr_queue_type); error = device_create_file(&sdev->sdev_gendev, &dev_attr_queue_type);
if (error) { if (error)
__scsi_remove_device(sdev); goto out_remove;
goto out;
}
error = bsg_register_queue(rq, &sdev->sdev_gendev, NULL, NULL); error = bsg_register_queue(rq, &sdev->sdev_gendev, NULL, NULL);
if (error) if (error)
/* we're treating error on bsg register as non-fatal,
* so pretend nothing went wrong */
sdev_printk(KERN_INFO, sdev, sdev_printk(KERN_INFO, sdev,
"Failed to register bsg queue, errno=%d\n", error); "Failed to register bsg queue, errno=%d\n", error);
/* we're treating error on bsg register as non-fatal, so pretend
* nothing went wrong */
error = 0;
/* add additional host specific attributes */ /* add additional host specific attributes */
if (sdev->host->hostt->sdev_attrs) { if (sdev->host->hostt->sdev_attrs) {
for (i = 0; sdev->host->hostt->sdev_attrs[i]; i++) { for (i = 0; sdev->host->hostt->sdev_attrs[i]; i++) {
error = device_create_file(&sdev->sdev_gendev, error = device_create_file(&sdev->sdev_gendev,
sdev->host->hostt->sdev_attrs[i]); sdev->host->hostt->sdev_attrs[i]);
if (error) { if (error)
__scsi_remove_device(sdev); goto out_remove;
goto out;
}
} }
} }
transport_add_device(&sdev->sdev_gendev); return 0;
out:
return error;
clean_device:
scsi_device_set_state(sdev, SDEV_CANCEL);
device_del(&sdev->sdev_gendev);
transport_destroy_device(&sdev->sdev_gendev);
put_device(&sdev->sdev_dev);
put_device(&sdev->sdev_gendev);
out_remove:
__scsi_remove_device(sdev);
return error; return error;
} }
void __scsi_remove_device(struct scsi_device *sdev) void __scsi_remove_device(struct scsi_device *sdev)
{ {
struct device *dev = &sdev->sdev_gendev; struct device *dev = &sdev->sdev_gendev;
if (scsi_device_set_state(sdev, SDEV_CANCEL) != 0) if (sdev->is_visible) {
return; if (scsi_device_set_state(sdev, SDEV_CANCEL) != 0)
return;
bsg_unregister_queue(sdev->request_queue); bsg_unregister_queue(sdev->request_queue);
device_unregister(&sdev->sdev_dev); device_unregister(&sdev->sdev_dev);
transport_remove_device(dev); transport_remove_device(dev);
device_del(dev); device_del(dev);
} else
put_device(&sdev->sdev_dev);
scsi_device_set_state(sdev, SDEV_DEL); scsi_device_set_state(sdev, SDEV_DEL);
if (sdev->host->hostt->slave_destroy) if (sdev->host->hostt->slave_destroy)
sdev->host->hostt->slave_destroy(sdev); sdev->host->hostt->slave_destroy(sdev);
......
...@@ -145,6 +145,7 @@ struct scsi_device { ...@@ -145,6 +145,7 @@ struct scsi_device {
unsigned retry_hwerror:1; /* Retry HARDWARE_ERROR */ unsigned retry_hwerror:1; /* Retry HARDWARE_ERROR */
unsigned last_sector_bug:1; /* do not use multisector accesses on unsigned last_sector_bug:1; /* do not use multisector accesses on
SD_LAST_BUGGY_SECTORS */ SD_LAST_BUGGY_SECTORS */
unsigned is_visible:1; /* is the device visible in sysfs */
DECLARE_BITMAP(supported_events, SDEV_EVT_MAXBITS); /* supported events */ DECLARE_BITMAP(supported_events, SDEV_EVT_MAXBITS); /* supported events */
struct list_head event_list; /* asserted events */ struct list_head event_list; /* asserted events */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment