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