]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
scsi: core: wake eh reliably when using scsi_schedule_eh
authorDavid Jeffery <djeffery@redhat.com>
Mon, 15 Jun 2026 17:46:30 +0000 (13:46 -0400)
committerMartin K. Petersen <martin.petersen@oracle.com>
Mon, 13 Jul 2026 02:21:22 +0000 (22:21 -0400)
Drivers which use the scsi_schedule_eh function to run the error handler
currently risk the error handler thread never waking once all commands are
timed out or inactive. There is no enforced memory order between setting
the host into error recovery state and counting busy commands. This can
result in a race with scsi_dec_host_busy where neither CPU sees both
conditions of all commands inactive and the host error state to request
waking the error handler.

To fix this, run the scsi_schedule_eh's scsi_eh_wakeup from a new work item
which will use rcu to ensure scsi_schedule_eh's call to scsi_host_busy will
occur after the error state is globally visible and will be seen by any
current scsi_dec_host_busy callers.

Fixes: 6eb045e092ef ("scsi: core: avoid host-wide host_busy counter for scsi_mq")
Signed-off-by: David Jeffery <djeffery@redhat.com>
Link: https://patch.msgid.link/20260615174630.11492-1-djeffery@redhat.com
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
drivers/scsi/hosts.c
drivers/scsi/scsi_error.c
drivers/scsi/scsi_priv.h
include/scsi/scsi_host.h

index e047747d4ecf8163c939be3f5d43f370204c236e..46cc8e3c79a26eba366fc05b45d736f6e43310ee 100644 (file)
@@ -357,6 +357,7 @@ static void scsi_host_dev_release(struct device *dev)
        /* Wait for functions invoked through call_rcu(&scmd->rcu, ...) */
        rcu_barrier();
 
+       cancel_work_sync(&shost->eh_work);
        if (shost->tmf_work_q)
                destroy_workqueue(shost->tmf_work_q);
        if (shost->ehandler)
@@ -422,6 +423,7 @@ struct Scsi_Host *scsi_host_alloc(const struct scsi_host_template *sht, int priv
        INIT_LIST_HEAD(&shost->starved_list);
        init_waitqueue_head(&shost->host_wait);
        mutex_init(&shost->scan_mutex);
+       INIT_WORK(&shost->eh_work, scsi_rcu_eh_wakeup);
 
        index = ida_alloc(&host_index_ida, GFP_KERNEL);
        if (index < 0) {
index 147127fb4db9cc4e9ef1de2ee6af3ac83a7fc381..453a2232452dba2f0486b8af4f4a5a24222d90b6 100644 (file)
@@ -73,6 +73,26 @@ void scsi_eh_wakeup(struct Scsi_Host *shost, unsigned int busy)
        }
 }
 
+void scsi_rcu_eh_wakeup(struct work_struct *work)
+{
+       struct Scsi_Host *shost = container_of(work, struct Scsi_Host, eh_work);
+       unsigned long flags;
+       unsigned int busy;
+
+       /*
+        * Ensure any running scsi_dec_host_busy has completed its rcu section
+        * so changes to host state and host_eh_scheduled are visible to all
+        * future calls of scsi_dec_host_busy
+        */
+       synchronize_rcu();
+
+       busy = scsi_host_busy(shost);
+
+       spin_lock_irqsave(shost->host_lock, flags);
+       scsi_eh_wakeup(shost, busy);
+       spin_unlock_irqrestore(shost->host_lock, flags);
+}
+
 /**
  * scsi_schedule_eh - schedule EH for SCSI host
  * @shost:     SCSI host to invoke error handling on.
@@ -88,7 +108,7 @@ void scsi_schedule_eh(struct Scsi_Host *shost)
        if (scsi_host_set_state(shost, SHOST_RECOVERY) == 0 ||
            scsi_host_set_state(shost, SHOST_CANCEL_RECOVERY) == 0) {
                shost->host_eh_scheduled++;
-               scsi_eh_wakeup(shost, scsi_host_busy(shost));
+               queue_work(shost->tmf_work_q, &shost->eh_work);
        }
 
        spin_unlock_irqrestore(shost->host_lock, flags);
index 37e5601be2b85dca310edb6593d2064f9ae20015..3dbf2ca59536c9dfe735cd5451d72d24334116f8 100644 (file)
@@ -91,6 +91,7 @@ extern enum blk_eh_timer_return scsi_timeout(struct request *req);
 extern int scsi_error_handler(void *host);
 extern enum scsi_disposition scsi_decide_disposition(struct scsi_cmnd *cmd);
 extern void scsi_eh_wakeup(struct Scsi_Host *shost, unsigned int busy);
+extern void scsi_rcu_eh_wakeup(struct work_struct *work);
 extern void scsi_eh_scmd_add(struct scsi_cmnd *);
 void scsi_eh_ready_devs(struct Scsi_Host *shost,
                        struct list_head *work_q,
index 7e2011830ba4bd48fe8a7df3c88285d6da6d0a71..f6b286fa59f214b98ea705b30d05ab08ce47e459 100644 (file)
@@ -750,6 +750,9 @@ struct Scsi_Host {
         */
        struct device *dma_dev;
 
+       /* Used for an rcu-synchronizing eh wakeup */
+       struct work_struct eh_work;
+
        /* Delay for runtime autosuspend */
        int rpm_autosuspend_delay;