scsi: core: Make sure that hosts outlive targets
authorMing Lei <ming.lei@redhat.com>
Thu, 28 Jul 2022 22:18:49 +0000 (15:18 -0700)
committerMartin K. Petersen <martin.petersen@oracle.com>
Mon, 1 Aug 2022 23:45:14 +0000 (19:45 -0400)
Fix the race conditions between SCSI LLD kernel module unloading and SCSI
device and target removal by making sure that SCSI hosts are destroyed
after all associated target and device objects have been freed.

Link: https://lore.kernel.org/r/20220728221851.1822295-3-bvanassche@acm.org
Cc: Christoph Hellwig <hch@lst.de>
Cc: Ming Lei <ming.lei@redhat.com>
Cc: Mike Christie <michael.christie@oracle.com>
Cc: Hannes Reinecke <hare@suse.de>
Cc: John Garry <john.garry@huawei.com>
Reviewed-by: Mike Christie <michael.christie@oracle.com>
Signed-off-by: Ming Lei <ming.lei@redhat.com>
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
[ bvanassche: Reworked Ming's patch and split it ]
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
drivers/scsi/hosts.c
drivers/scsi/scsi_scan.c
include/scsi/scsi_host.h

index ef6c0e37accefcd185ece90fa6dced559ad8450b..8fa98c8d0ee0937deee2788303ff8b83fa30016c 100644 (file)
@@ -190,6 +190,13 @@ void scsi_remove_host(struct Scsi_Host *shost)
        transport_unregister_device(&shost->shost_gendev);
        device_unregister(&shost->shost_dev);
        device_del(&shost->shost_gendev);
+
+       /*
+        * After scsi_remove_host() has returned the scsi LLD module can be
+        * unloaded and/or the host resources can be released. Hence wait until
+        * the dependent SCSI targets and devices are gone before returning.
+        */
+       wait_event(shost->targets_wq, atomic_read(&shost->target_count) == 0);
 }
 EXPORT_SYMBOL(scsi_remove_host);
 
@@ -394,6 +401,7 @@ struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)
        INIT_LIST_HEAD(&shost->starved_list);
        init_waitqueue_head(&shost->host_wait);
        mutex_init(&shost->scan_mutex);
+       init_waitqueue_head(&shost->targets_wq);
 
        index = ida_alloc(&host_index_ida, GFP_KERNEL);
        if (index < 0) {
index 4c1efd6a3b0ca9d4f5abe56cdae4d5a97e20250b..ac6059702d13514d8bb9ed704e644b62117109fb 100644 (file)
@@ -406,9 +406,14 @@ static void scsi_target_destroy(struct scsi_target *starget)
 static void scsi_target_dev_release(struct device *dev)
 {
        struct device *parent = dev->parent;
+       struct Scsi_Host *shost = dev_to_shost(parent);
        struct scsi_target *starget = to_scsi_target(dev);
 
        kfree(starget);
+
+       if (atomic_dec_return(&shost->target_count) == 0)
+               wake_up(&shost->targets_wq);
+
        put_device(parent);
 }
 
@@ -523,6 +528,8 @@ static struct scsi_target *scsi_alloc_target(struct device *parent,
        starget->max_target_blocked = SCSI_DEFAULT_TARGET_BLOCKED;
        init_waitqueue_head(&starget->sdev_wq);
 
+       atomic_inc(&shost->target_count);
+
  retry:
        spin_lock_irqsave(shost->host_lock, flags);
 
index 667d889b92b52e3f424175ce3658be1007e1b2d0..339f975d356ee253bc71e4d2c813659c58181e0d 100644 (file)
@@ -689,6 +689,9 @@ struct Scsi_Host {
        /* ldm bits */
        struct device           shost_gendev, shost_dev;
 
+       atomic_t                target_count;
+       wait_queue_head_t       targets_wq;
+
        /*
         * Points to the transport data (if any) which is allocated
         * separately