[PATCH V2] scsi: core: put LLD module refcnt after SCSI device is released

Ming Lei
2021-09-30T07:40:38Z

SCSI host release is triggered when SCSI device is freed, and we have to
make sure that LLD module won't be unloaded before SCSI host instance is
released because shost->hostt is required in host release handler.

So put LLD module refcnt after SCSI device is released.

The real release handler can be run from wq context in case of
in_interrupt(), so add one atomic counter for serializing putting
module via current and wq context. This way is fine since we don't
call scsi_device_put() in fast IO path.

Reported-by: Changhui Zhong <czhong@redhat.com>
Reported-by: Yi Zhang <yi.zhang@redhat.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Ming Lei <ming.lei@redhat.com>
---
drivers/scsi/scsi.c | 8 +++++++-
drivers/scsi/scsi_sysfs.c | 10 ++++++++++
include/scsi/scsi_device.h | 2 ++
3 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c
index b241f9e3885c..b6612161587f 100644
--- a/drivers/scsi/scsi.c
+++ b/drivers/scsi/scsi.c
@@ -553,8 +553,14 @@ EXPORT_SYMBOL(scsi_device_get);
*/
void scsi_device_put(struct scsi_device *sdev)
{
- module_put(sdev->host->hostt->module);
+ struct module *mod = sdev->host->hostt->module;
+
+ atomic_inc(&sdev->put_dev_cnt);
+
put_device(&sdev->sdev_gendev);
+
+ if (atomic_dec_if_positive(&sdev->put_dev_cnt) >= 0)
+ module_put(mod);
}
EXPORT_SYMBOL(scsi_device_put);

diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index 86793259e541..ba6defac91ae 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -449,9 +449,16 @@ static void scsi_device_dev_release_usercontext(struct work_struct *work)
struct scsi_vpd *vpd_pg80 = NULL, *vpd_pg83 = NULL;
struct scsi_vpd *vpd_pg0 = NULL, *vpd_pg89 = NULL;
unsigned long flags;
+ struct module *mod;
+ bool put_mod = false;

sdev = container_of(work, struct scsi_device, ew.work);

+ if (atomic_dec_if_positive(&sdev->put_dev_cnt) >= 0) {
+ put_mod = true;
+ mod = sdev->host->hostt->module;
+ }
+
scsi_dh_release_device(sdev);

parent = sdev->sdev_gendev.parent;
@@ -502,6 +509,9 @@ static void scsi_device_dev_release_usercontext(struct work_struct *work)

if (parent)
put_device(parent);
+
+ if (put_mod)
+ module_put(mod);
}

static void scsi_device_dev_release(struct device *dev)
diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h
index 430b73bd02ac..76268c473c22 100644
--- a/include/scsi/scsi_device.h
+++ b/include/scsi/scsi_device.h
@@ -111,6 +111,8 @@ struct scsi_device {
struct sbitmap budget_map;
atomic_t device_blocked; /* Device returned QUEUE_FULL. */

+ atomic_t put_dev_cnt; /* increased by 1 when we are putting device */
+
atomic_t restarts;
spinlock_t list_lock;
struct list_head starved_entry;
--
2.31.1