diff options
Diffstat (limited to 'drivers/scsi/scsi_scan.c')
| -rw-r--r-- | drivers/scsi/scsi_scan.c | 241 |
1 files changed, 147 insertions, 94 deletions
diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c index 012f73a9688..e02b3aab56c 100644 --- a/drivers/scsi/scsi_scan.c +++ b/drivers/scsi/scsi_scan.c @@ -33,6 +33,7 @@ #include <linux/kthread.h> #include <linux/spinlock.h> #include <linux/async.h> +#include <linux/slab.h> #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> @@ -96,7 +97,7 @@ MODULE_PARM_DESC(max_luns, #define SCSI_SCAN_TYPE_DEFAULT "sync" #endif -static char scsi_scan_type[6] = SCSI_SCAN_TYPE_DEFAULT; +char scsi_scan_type[6] = SCSI_SCAN_TYPE_DEFAULT; module_param_string(scan, scsi_scan_type, sizeof(scsi_scan_type), S_IRUGO); MODULE_PARM_DESC(scan, "sync, async or none"); @@ -183,18 +184,6 @@ int scsi_complete_async_scans(void) return 0; } -/* Only exported for the benefit of scsi_wait_scan */ -EXPORT_SYMBOL_GPL(scsi_complete_async_scans); - -#ifndef MODULE -/* - * For async scanning we need to wait for all the scans to complete before - * trying to mount the root fs. Otherwise non-modular drivers may not be ready - * yet. - */ -late_initcall(scsi_complete_async_scans); -#endif - /** * scsi_unlock_floptical - unlock device via a special MODE SENSE command * @sdev: scsi device to send command to @@ -241,6 +230,7 @@ static struct scsi_device *scsi_alloc_sdev(struct scsi_target *starget, int display_failure_msg = 1, ret; struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); extern void scsi_evt_thread(struct work_struct *work); + extern void scsi_requeue_run_queue(struct work_struct *work); sdev = kzalloc(sizeof(*sdev) + shost->transportt->device_size, GFP_ATOMIC); @@ -263,6 +253,7 @@ static struct scsi_device *scsi_alloc_sdev(struct scsi_target *starget, INIT_LIST_HEAD(&sdev->event_list); spin_lock_init(&sdev->list_lock); INIT_WORK(&sdev->event_work, scsi_evt_thread); + INIT_WORK(&sdev->requeue_work, scsi_requeue_run_queue); sdev->sdev_gendev.parent = get_device(&starget->dev); sdev->sdev_target = starget; @@ -294,7 +285,7 @@ static struct scsi_device *scsi_alloc_sdev(struct scsi_target *starget, kfree(sdev); goto out; } - + WARN_ON_ONCE(!blk_get_queue(sdev->request_queue)); sdev->request_queue->queuedata = sdev; scsi_adjust_queue_depth(sdev, 0, sdev->host->cmd_per_lun); @@ -316,10 +307,7 @@ static struct scsi_device *scsi_alloc_sdev(struct scsi_target *starget, return sdev; out_device_destroy: - scsi_device_set_state(sdev, SDEV_DEL); - transport_destroy_device(&sdev->sdev_gendev); - put_device(&sdev->sdev_dev); - put_device(&sdev->sdev_gendev); + __scsi_remove_device(sdev); out: if (display_failure_msg) printk(ALLOC_FAILURE_MSG, __func__); @@ -332,6 +320,7 @@ static void scsi_target_destroy(struct scsi_target *starget) struct Scsi_Host *shost = dev_to_shost(dev->parent); unsigned long flags; + starget->state = STARGET_DEL; transport_destroy_device(dev); spin_lock_irqsave(shost->host_lock, flags); if (shost->hostt->target_destroy) @@ -383,6 +372,37 @@ static struct scsi_target *__scsi_find_target(struct device *parent, } /** + * scsi_target_reap_ref_release - remove target from visibility + * @kref: the reap_ref in the target being released + * + * Called on last put of reap_ref, which is the indication that no device + * under this target is visible anymore, so render the target invisible in + * sysfs. Note: we have to be in user context here because the target reaps + * should be done in places where the scsi device visibility is being removed. + */ +static void scsi_target_reap_ref_release(struct kref *kref) +{ + struct scsi_target *starget + = container_of(kref, struct scsi_target, reap_ref); + + /* + * if we get here and the target is still in the CREATED state that + * means it was allocated but never made visible (because a scan + * turned up no LUNs), so don't call device_del() on it. + */ + if (starget->state != STARGET_CREATED) { + transport_remove_device(&starget->dev); + device_del(&starget->dev); + } + scsi_target_destroy(starget); +} + +static void scsi_target_reap_ref_put(struct scsi_target *starget) +{ + kref_put(&starget->reap_ref, scsi_target_reap_ref_release); +} + +/** * scsi_alloc_target - allocate a new or find an existing target * @parent: parent of the target (need not be a scsi host) * @channel: target channel number (zero if no channels) @@ -404,7 +424,7 @@ static struct scsi_target *scsi_alloc_target(struct device *parent, + shost->transportt->target_size; struct scsi_target *starget; struct scsi_target *found_target; - int error; + int error, ref_got; starget = kzalloc(size, GFP_KERNEL); if (!starget) { @@ -413,12 +433,10 @@ static struct scsi_target *scsi_alloc_target(struct device *parent, } dev = &starget->dev; device_initialize(dev); - starget->reap_ref = 1; + kref_init(&starget->reap_ref); dev->parent = get_device(parent); dev_set_name(dev, "target%d:%d:%d", shost->host_no, channel, id); -#ifndef CONFIG_SYSFS_DEPRECATED dev->bus = &scsi_bus_type; -#endif dev->type = &scsi_target_type; starget->id = id; starget->channel = channel; @@ -455,30 +473,36 @@ static struct scsi_target *scsi_alloc_target(struct device *parent, return starget; found: - found_target->reap_ref++; + /* + * release routine already fired if kref is zero, so if we can still + * take the reference, the target must be alive. If we can't, it must + * be dying and we need to wait for a new target + */ + ref_got = kref_get_unless_zero(&found_target->reap_ref); + spin_unlock_irqrestore(shost->host_lock, flags); - if (found_target->state != STARGET_DEL) { - put_device(parent); - kfree(starget); + if (ref_got) { + put_device(dev); return found_target; } - /* Unfortunately, we found a dying target; need to - * wait until it's dead before we can get a new one */ + /* + * Unfortunately, we found a dying target; need to wait until it's + * dead before we can get a new one. There is an anomaly here. We + * *should* call scsi_target_reap() to balance the kref_get() of the + * reap_ref above. However, since the target being released, it's + * already invisible and the reap_ref is irrelevant. If we call + * scsi_target_reap() we might spuriously do another device_del() on + * an already invisible target. + */ put_device(&found_target->dev); - flush_scheduled_work(); + /* + * length of time is irrelevant here, we just want to yield the CPU + * for a tick to avoid busy waiting for the target to die. + */ + msleep(1); goto retry; } -static void scsi_target_reap_usercontext(struct work_struct *work) -{ - struct scsi_target *starget = - container_of(work, struct scsi_target, ew.work); - - transport_remove_device(&starget->dev); - device_del(&starget->dev); - scsi_target_destroy(starget); -} - /** * scsi_target_reap - check to see if target is in use and destroy if not * @starget: target to be checked @@ -489,27 +513,13 @@ static void scsi_target_reap_usercontext(struct work_struct *work) */ void scsi_target_reap(struct scsi_target *starget) { - struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); - unsigned long flags; - enum scsi_target_state state; - int empty; - - spin_lock_irqsave(shost->host_lock, flags); - state = starget->state; - empty = --starget->reap_ref == 0 && - list_empty(&starget->devices) ? 1 : 0; - spin_unlock_irqrestore(shost->host_lock, flags); - - if (!empty) - return; - - BUG_ON(state == STARGET_DEL); - starget->state = STARGET_DEL; - if (state == STARGET_CREATED) - scsi_target_destroy(starget); - else - execute_in_process_context(scsi_target_reap_usercontext, - &starget->ew); + /* + * serious problem if this triggers: STARGET_DEL is only set in the if + * the reap_ref drops to zero, so we're trying to do another final put + * on an already released kref + */ + BUG_ON(starget->state == STARGET_DEL); + scsi_target_reap_ref_put(starget); } /** @@ -778,6 +788,16 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result, sdev->model = (char *) (sdev->inquiry + 16); sdev->rev = (char *) (sdev->inquiry + 32); + if (strncmp(sdev->vendor, "ATA ", 8) == 0) { + /* + * sata emulation layer device. This is a hack to work around + * the SATL power management specifications which state that + * when the SATL detects the device has gone into standby + * mode, it shall respond with NOT READY. + */ + sdev->allow_restart = 1; + } + if (*bflags & BLIST_ISROM) { sdev->type = TYPE_ROM; sdev->removable = 1; @@ -879,7 +899,7 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result, * broken RA4x00 Compaq Disk Array */ if (*bflags & BLIST_MAX_512) - blk_queue_max_sectors(sdev->request_queue, 512); + blk_queue_max_hw_sectors(sdev->request_queue, 512); /* * Some devices may not want to have a start command automatically @@ -925,6 +945,14 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result, if (*bflags & BLIST_RETRY_HWERROR) sdev->retry_hwerror = 1; + if (*bflags & BLIST_NO_DIF) + sdev->no_dif = 1; + + sdev->eh_timeout = SCSI_DEFAULT_EH_TIMEOUT; + + if (*bflags & BLIST_SKIP_VPD_PAGES) + sdev->skip_vpd_pages = 1; + transport_configure_device(&sdev->sdev_gendev); if (sdev->host->hostt->slave_configure) { @@ -942,6 +970,9 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result, } } + if (sdev->scsi_level >= SCSI_3) + scsi_attach_vpd(sdev); + sdev->max_queue_depth = sdev->queue_depth; /* @@ -1220,7 +1251,7 @@ static void scsi_sequential_lun_scan(struct scsi_target *starget, } /** - * scsilun_to_int: convert a scsi_lun to an int + * scsilun_to_int - convert a scsi_lun to an int * @scsilun: struct scsi_lun to be converted. * * Description: @@ -1252,7 +1283,7 @@ int scsilun_to_int(struct scsi_lun *scsilun) EXPORT_SYMBOL(scsilun_to_int); /** - * int_to_scsilun: reverts an int into a scsi_lun + * int_to_scsilun - reverts an int into a scsi_lun * @lun: integer to be reverted * @scsilun: struct scsi_lun to be set. * @@ -1297,6 +1328,7 @@ EXPORT_SYMBOL(int_to_scsilun); * LUNs even if it's older than SCSI-3. * If BLIST_NOREPORTLUN is set, return 1 always. * If BLIST_NOLUN is set, return 0 always. + * If starget->no_report_luns is set, return 1 always. * * Return: * 0: scan completed (or no memory, so further scanning is futile) @@ -1323,6 +1355,7 @@ static int scsi_report_lun_scan(struct scsi_target *starget, int bflags, * Only support SCSI-3 and up devices if BLIST_NOREPORTLUN is not set. * Also allow SCSI-2 if BLIST_REPORTLUN2 is set and host adapter does * support more than 8 LUNs. + * Don't attempt if the target doesn't support REPORT LUNS. */ if (bflags & BLIST_NOREPORTLUN) return 1; @@ -1334,13 +1367,17 @@ static int scsi_report_lun_scan(struct scsi_target *starget, int bflags, return 1; if (bflags & BLIST_NOLUN) return 0; + if (starget->no_report_luns) + return 1; if (!(sdev = scsi_device_lookup_by_target(starget, 0))) { sdev = scsi_alloc_sdev(starget, 0, NULL); if (!sdev) return 0; - if (scsi_device_get(sdev)) + if (scsi_device_get(sdev)) { + __scsi_remove_device(sdev); return 0; + } } sprintf(devname, "host %d channel %d id %d", @@ -1510,14 +1547,22 @@ struct scsi_device *__scsi_add_device(struct Scsi_Host *shost, uint channel, starget = scsi_alloc_target(parent, channel, id); if (!starget) return ERR_PTR(-ENOMEM); + scsi_autopm_get_target(starget); mutex_lock(&shost->scan_mutex); if (!shost->async_scan) scsi_complete_async_scans(); - if (scsi_host_scan_allowed(shost)) + if (scsi_host_scan_allowed(shost) && scsi_autopm_get_host(shost) == 0) { scsi_probe_and_add_lun(starget, lun, NULL, &sdev, 1, hostdata); + scsi_autopm_put_host(shost); + } mutex_unlock(&shost->scan_mutex); + scsi_autopm_put_target(starget); + /* + * paired with scsi_alloc_target(). Target will be destroyed unless + * scsi_probe_and_add_lun made an underlying device visible + */ scsi_target_reap(starget); put_device(&starget->dev); @@ -1571,6 +1616,7 @@ static void __scsi_scan_target(struct device *parent, unsigned int channel, starget = scsi_alloc_target(parent, channel, id); if (!starget) return; + scsi_autopm_get_target(starget); if (lun != SCAN_WILD_CARD) { /* @@ -1596,8 +1642,11 @@ static void __scsi_scan_target(struct device *parent, unsigned int channel, } out_reap: - /* now determine if the target has any children at all - * and if not, nuke it */ + scsi_autopm_put_target(starget); + /* + * paired with scsi_alloc_target(): determine if the target has + * any children at all and if not, nuke it + */ scsi_target_reap(starget); put_device(&starget->dev); @@ -1630,8 +1679,10 @@ void scsi_scan_target(struct device *parent, unsigned int channel, if (!shost->async_scan) scsi_complete_async_scans(); - if (scsi_host_scan_allowed(shost)) + if (scsi_host_scan_allowed(shost) && scsi_autopm_get_host(shost) == 0) { __scsi_scan_target(parent, channel, id, lun, rescan); + scsi_autopm_put_host(shost); + } mutex_unlock(&shost->scan_mutex); } EXPORT_SYMBOL(scsi_scan_target); @@ -1683,7 +1734,7 @@ int scsi_scan_host_selected(struct Scsi_Host *shost, unsigned int channel, if (!shost->async_scan) scsi_complete_async_scans(); - if (scsi_host_scan_allowed(shost)) { + if (scsi_host_scan_allowed(shost) && scsi_autopm_get_host(shost) == 0) { if (channel == SCAN_WILD_CARD) for (channel = 0; channel <= shost->max_channel; channel++) @@ -1691,6 +1742,7 @@ int scsi_scan_host_selected(struct Scsi_Host *shost, unsigned int channel, rescan); else scsi_scan_channel(shost, channel, id, lun, rescan); + scsi_autopm_put_host(shost); } mutex_unlock(&shost->scan_mutex); @@ -1701,6 +1753,9 @@ static void scsi_sysfs_add_devices(struct Scsi_Host *shost) { struct scsi_device *sdev; shost_for_each_device(sdev, shost) { + /* target removed before the device could be added */ + if (sdev->sdev_state == SDEV_DEL) + continue; if (!scsi_host_scan_allowed(shost) || scsi_sysfs_add_sdev(sdev) != 0) __scsi_remove_device(sdev); @@ -1806,6 +1861,7 @@ static void scsi_finish_async_scan(struct async_scan_data *data) } spin_unlock(&async_scan_lock); + scsi_autopm_put_host(shost); scsi_host_put(shost); kfree(data); } @@ -1825,12 +1881,13 @@ static void do_scsi_scan_host(struct Scsi_Host *shost) } } -static int do_scan_async(void *_data) +static void do_scan_async(void *_data, async_cookie_t c) { struct async_scan_data *data = _data; - do_scsi_scan_host(data->shost); + struct Scsi_Host *shost = data->shost; + + do_scsi_scan_host(shost); scsi_finish_async_scan(data); - return 0; } /** @@ -1839,21 +1896,26 @@ static int do_scan_async(void *_data) **/ void scsi_scan_host(struct Scsi_Host *shost) { - struct task_struct *p; struct async_scan_data *data; if (strncmp(scsi_scan_type, "none", 4) == 0) return; + if (scsi_autopm_get_host(shost) < 0) + return; data = scsi_prep_async_scan(shost); if (!data) { do_scsi_scan_host(shost); + scsi_autopm_put_host(shost); return; } - p = kthread_run(do_scan_async, data, "scsi_scan_%d", shost->host_no); - if (IS_ERR(p)) - do_scan_async(data); + /* register with the async subsystem so wait_for_device_probe() + * will flush this work + */ + async_schedule(do_scan_async, data); + + /* scsi_autopm_put_host(shost) is called in scsi_finish_async_scan() */ } EXPORT_SYMBOL(scsi_scan_host); @@ -1874,12 +1936,9 @@ void scsi_forget_host(struct Scsi_Host *shost) spin_unlock_irqrestore(shost->host_lock, flags); } -/* - * Function: scsi_get_host_dev() - * - * Purpose: Create a scsi_device that points to the host adapter itself. - * - * Arguments: SHpnt - Host that needs a scsi_device +/** + * scsi_get_host_dev - Create a scsi_device that points to the host adapter itself + * @shost: Host that needs a scsi_device * * Lock status: None assumed. * @@ -1892,7 +1951,7 @@ void scsi_forget_host(struct Scsi_Host *shost) * * Note - this device is not accessible from any high-level * drivers (including generics), which is probably not - * optimal. We can add hooks later to attach + * optimal. We can add hooks later to attach. */ struct scsi_device *scsi_get_host_dev(struct Scsi_Host *shost) { @@ -1907,10 +1966,9 @@ struct scsi_device *scsi_get_host_dev(struct Scsi_Host *shost) goto out; sdev = scsi_alloc_sdev(starget, 0, NULL); - if (sdev) { - sdev->sdev_gendev.parent = get_device(&starget->dev); + if (sdev) sdev->borken = 0; - } else + else scsi_target_reap(starget); put_device(&starget->dev); out: @@ -1919,18 +1977,13 @@ struct scsi_device *scsi_get_host_dev(struct Scsi_Host *shost) } EXPORT_SYMBOL(scsi_get_host_dev); -/* - * Function: scsi_free_host_dev() - * - * Purpose: Free a scsi_device that points to the host adapter itself. - * - * Arguments: SHpnt - Host that needs a scsi_device +/** + * scsi_free_host_dev - Free a scsi_device that points to the host adapter itself + * @sdev: Host device to be freed * * Lock status: None assumed. * * Returns: Nothing - * - * Notes: */ void scsi_free_host_dev(struct scsi_device *sdev) { |
