Description:
SCSI mid-layer may hold references to Scsi_Host structs when
the owning module has already unloaded. Scsi_Host release path
touches scsi_host_template struct that is usually allocated
in the unloaded module's memory. That results in a crash.
To work around the problem, this change implements
scsi_host_template_release API to be called at driver unload
path to make sure all Scsi_Host structs are gone before
releasing scsi_host_template memory.

---
 drivers/scsi/hosts.c          |  2 ++
 drivers/scsi/qla2xxx/qla_os.c |  2 ++
 drivers/scsi/scsi_priv.h      |  1 +
 drivers/scsi/scsi_proc.c      | 64 +++++++++++++++++++++++++++++++++++++++----
 include/scsi/scsi_host.h      | 17 ++++++++++++
 5 files changed, 80 insertions(+), 6 deletions(-)

diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c
index 82ac1cd..51c9e2b 100644
--- a/drivers/scsi/hosts.c
+++ b/drivers/scsi/hosts.c
@@ -353,6 +353,8 @@ static void scsi_host_dev_release(struct device *dev)
  blk_free_tags(shost->bqt);
  }

+ scsi_host_template_release(shost->hostt);
+
  kfree(shost->shost_data);

  if (parent)
diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c
index fb8beee..e913b8b 100644
--- a/drivers/scsi/qla2xxx/qla_os.c
+++ b/drivers/scsi/qla2xxx/qla_os.c
@@ -270,6 +270,7 @@ struct scsi_host_template qla2xxx_driver_template = {

  .supported_mode = MODE_INITIATOR,
  .track_queue_depth = 1,
+ .wait_on_unload = 1,
 };

 static struct scsi_transport_template *qla2xxx_transport_template = NULL;
@@ -6116,6 +6117,7 @@ qla2x00_module_exit(void)
  kmem_cache_destroy(ctx_cachep);
  fc_release_transport(qla2xxx_transport_template);
  fc_release_transport(qla2xxx_transport_vport_template);
+ scsi_host_template_wait(&qla2xxx_driver_template);
 }

 module_init(qla2x00_module_init);
diff --git a/drivers/scsi/scsi_priv.h b/drivers/scsi/scsi_priv.h
index 4d01cdb..c76cb6e2 100644
--- a/drivers/scsi/scsi_priv.h
+++ b/drivers/scsi/scsi_priv.h
@@ -99,6 +99,7 @@ extern struct kmem_cache *scsi_sdb_cache;
 #ifdef CONFIG_SCSI_PROC_FS
 extern void scsi_proc_hostdir_add(struct scsi_host_template *);
 extern void scsi_proc_hostdir_rm(struct scsi_host_template *);
+extern void scsi_host_template_release(struct scsi_host_template *);
 extern void scsi_proc_host_add(struct Scsi_Host *);
 extern void scsi_proc_host_rm(struct Scsi_Host *);
 extern int scsi_init_procfs(void);
diff --git a/drivers/scsi/scsi_proc.c b/drivers/scsi/scsi_proc.c
index 251598e..daf2c99 100644
--- a/drivers/scsi/scsi_proc.c
+++ b/drivers/scsi/scsi_proc.c
@@ -99,15 +99,21 @@ static const struct file_operations proc_scsi_fops = {

 void scsi_proc_hostdir_add(struct scsi_host_template *sht)
 {
- if (!sht->show_info)
+ if (!sht->show_info && !sht->wait_on_unload)
  return;

  mutex_lock(&global_host_template_mutex);
  if (!sht->present++) {
- sht->proc_dir = proc_mkdir(sht->proc_name, proc_scsi);
-        if (!sht->proc_dir)
- printk(KERN_ERR "%s: proc_mkdir failed for %s\n",
-        __func__, sht->proc_name);
+ if (sht->show_info) {
+ sht->proc_dir = proc_mkdir(sht->proc_name, proc_scsi);
+         if (!sht->proc_dir)
+ printk(KERN_ERR "%s: proc_mkdir failed for %s\n",
+        __func__, sht->proc_name);
+ }
+ if (sht->wait_on_unload) {
+ sema_init(&sht->release_sem, 0);
+ sht->release_sem_waiters = 0;
+ }
  }
  mutex_unlock(&global_host_template_mutex);
 }
@@ -118,7 +124,7 @@ void scsi_proc_hostdir_add(struct scsi_host_template *sht)
  */
 void scsi_proc_hostdir_rm(struct scsi_host_template *sht)
 {
- if (!sht->show_info)
+ if (!sht->show_info && !sht->wait_on_unload)
  return;

  mutex_lock(&global_host_template_mutex);
@@ -126,9 +132,55 @@ void scsi_proc_hostdir_rm(struct scsi_host_template *sht)
  remove_proc_entry(sht->proc_name, proc_scsi);
  sht->proc_dir = NULL;
  }
+ if (sht->wait_on_unload)
+ ++sht->scsi_host_cleanups;
  mutex_unlock(&global_host_template_mutex);
 }

+/**
+ * scsi_host_template_release - runs when a user of scsi_host_template
+ * (like Scsi_Host) is gone.
+ * @sht: pointer to scsi_host_template
+*/
+void scsi_host_template_release(struct scsi_host_template *sht)
+{
+ int release_sem_waiters = 0;
+ struct semaphore *release_sem;
+ if (!sht->wait_on_unload)
+ return;
+
+ mutex_lock(&global_host_template_mutex);
+ if (!--sht->scsi_host_cleanups && !sht->present) {
+ release_sem = &sht->release_sem;
+ release_sem_waiters = sht->release_sem_waiters;
+ sht->release_sem_waiters = 0;
+ }
+ mutex_unlock(&global_host_template_mutex);
+ for (; release_sem_waiters; --release_sem_waiters)
+ up(release_sem);
+}
+
+/**
+ * scsi_host_template_wait - Waits till all references to
+ * scsi_host_template are gone
+ * @sht: pointer to scsi_host_template
+*/
+void scsi_host_template_wait(struct scsi_host_template *sht)
+{
+ unsigned char present = 0;
+ struct semaphore *release_sem;
+ if (!sht->wait_on_unload)
+ return;
+ mutex_lock(&global_host_template_mutex);
+ present = sht->present + sht->scsi_host_cleanups;
+ if (present) {
+ ++sht->release_sem_waiters;
+ release_sem = &sht->release_sem;
+ }
+ mutex_unlock(&global_host_template_mutex);
+ if (present) down(release_sem);
+}
+EXPORT_SYMBOL(scsi_host_template_wait);

 /**
  * scsi_proc_host_add - Add entry for this host to appropriate /proc dir
diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h
index e44955b..aef3b86 100644
--- a/include/scsi/scsi_host.h
+++ b/include/scsi/scsi_host.h
@@ -449,6 +449,13 @@ struct scsi_host_template {
  unsigned no_async_abort:1;

  /*
+ * True if the owning dynamically-loadable module
+ * makes sure this struct's users are gone on unload
+ * by calling scsi_host_template_wait
+ */
+ unsigned wait_on_unload:1;
+
+ /*
  * Countdown for host blocking with no commands outstanding.
  */
  unsigned int max_host_blocked;
@@ -498,6 +505,15 @@ struct scsi_host_template {

  /* temporary flag to disable blk-mq I/O path */
  bool disable_blk_mq;
+
+ /* semaphore to wait for the struct users to quit */
+ struct semaphore release_sem;
+
+ /* count of waiters on release_sem */
+ int release_sem_waiters;
+
+ /* count of Scsi_Host cleanups in progress */
+ int scsi_host_cleanups;
 };

 /*
@@ -796,6 +812,7 @@ extern void scsi_host_put(struct Scsi_Host *t);
 extern struct Scsi_Host *scsi_host_lookup(unsigned short);
 extern const char *scsi_host_state_name(enum scsi_host_state);
 extern void scsi_cmd_get_serial(struct Scsi_Host *, struct scsi_cmnd *);
+extern void scsi_host_template_wait(struct scsi_host_template *);

 static inline int __must_check scsi_add_host(struct Scsi_Host *host,
       struct device *dev)
-- 
1.9.1

Reply via email to