If a device is removed via sysfs before scsi_remove_host() is invoked that device may still be in state SDEV_CANCEL when scsi_remove_host() returns. SCSI LLDs may start cleaning up host resources needed by their queuecommand() callback as soon as scsi_remove_host() finished. Hence scsi_remove_host() must wait until blk_cleanup_queue() for all devices associated with the host has finished. That avoids that queuecommand() gets invoked after scsi_remove_host() finished. Signed-off-by: Bart Van Assche <bvanassche@xxxxxxx> Cc: Tejun Heo <tj@xxxxxxxxxx> Cc: James Bottomley <JBottomley@xxxxxxxxxxxxx> Cc: Mike Christie <michaelc@xxxxxxxxxxx> Cc: Hannes Reinecke <hare@xxxxxxx> --- drivers/scsi/hosts.c | 30 ++++++++++++++++++++++++++++++ drivers/scsi/scsi_priv.h | 1 + drivers/scsi/scsi_sysfs.c | 1 + include/scsi/scsi_host.h | 1 + 4 files changed, 33 insertions(+) diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c index 6ae16cd..b68a013 100644 --- a/drivers/scsi/hosts.c +++ b/drivers/scsi/hosts.c @@ -150,12 +150,31 @@ int scsi_host_set_state(struct Scsi_Host *shost, enum scsi_host_state state) } EXPORT_SYMBOL(scsi_host_set_state); +/* Return true if and only if scsi_remove_host() is allowed to finish. */ +static bool scsi_remove_host_done(struct Scsi_Host *shost) +{ + lockdep_assert_held(shost->host_lock); + + return list_empty(&shost->__devices); +} + +/* Test whether scsi_remove_host() may finish, and if so, wake it up. */ +void scsi_check_remove_host_done(struct Scsi_Host *shost) +{ + lockdep_assert_held(shost->host_lock); + + if (scsi_remove_host_done(shost)) + wake_up(&shost->remove_host); +} + /** * scsi_remove_host - remove a scsi host * @shost: a pointer to a scsi host to remove **/ void scsi_remove_host(struct Scsi_Host *shost) { + DEFINE_WAIT(wait); + mutex_lock(&shost->scan_mutex); spin_lock_irq(shost->host_lock); if (scsi_host_set_state(shost, SHOST_CANCEL)) @@ -174,6 +193,16 @@ void scsi_remove_host(struct Scsi_Host *shost) spin_lock_irq(shost->host_lock); if (scsi_host_set_state(shost, SHOST_DEL)) BUG_ON(scsi_host_set_state(shost, SHOST_DEL_RECOVERY)); + while (!scsi_remove_host_done(shost)) { + prepare_to_wait_exclusive(&shost->remove_host, &wait, + TASK_INTERRUPTIBLE); + if (scsi_remove_host_done(shost)) + break; + spin_unlock_irq(shost->host_lock); + schedule(); + spin_lock_irq(shost->host_lock); + } + finish_wait(&shost->remove_host, &wait); spin_unlock_irq(shost->host_lock); transport_unregister_device(&shost->shost_gendev); @@ -349,6 +378,7 @@ struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize) shost->shost_state = SHOST_CREATED; INIT_LIST_HEAD(&shost->__devices); INIT_LIST_HEAD(&shost->__targets); + init_waitqueue_head(&shost->remove_host); INIT_LIST_HEAD(&shost->eh_cmd_q); INIT_LIST_HEAD(&shost->starved_list); init_waitqueue_head(&shost->host_wait); diff --git a/drivers/scsi/scsi_priv.h b/drivers/scsi/scsi_priv.h index 8f9a0ca..882c823 100644 --- a/drivers/scsi/scsi_priv.h +++ b/drivers/scsi/scsi_priv.h @@ -26,6 +26,7 @@ struct scsi_nl_hdr; /* hosts.c */ extern int scsi_init_hosts(void); extern void scsi_exit_hosts(void); +extern void scsi_check_remove_host_done(struct Scsi_Host *shost); /* scsi.c */ extern int scsi_dispatch_cmd(struct scsi_cmnd *cmd); diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c index b319c20..6368576 100644 --- a/drivers/scsi/scsi_sysfs.c +++ b/drivers/scsi/scsi_sysfs.c @@ -348,6 +348,7 @@ static void scsi_device_dev_release_usercontext(struct work_struct *work) starget->reap_ref++; list_del(&sdev->siblings); list_del(&sdev->same_target_siblings); + scsi_check_remove_host_done(sdev->host); spin_unlock_irqrestore(sdev->host->host_lock, flags); cancel_work_sync(&sdev->event_work); diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h index 4908480..1b7fd89 100644 --- a/include/scsi/scsi_host.h +++ b/include/scsi/scsi_host.h @@ -577,6 +577,7 @@ struct Scsi_Host { struct completion * eh_action; /* Wait for specific actions on the host. */ wait_queue_head_t host_wait; + wait_queue_head_t remove_host; struct scsi_host_template *hostt; struct scsi_transport_template *transportt; -- 1.7.10.4 -- To unsubscribe from this list: send the line "unsubscribe linux-scsi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html