Skip to content

Commit dccf3b1

Browse files
David Jefferymartinkpetersen
authored andcommitted
scsi: core: wake eh reliably when using scsi_schedule_eh
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: 6eb045e ("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>
1 parent fda6a1f commit dccf3b1

4 files changed

Lines changed: 27 additions & 1 deletion

File tree

drivers/scsi/hosts.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ static void scsi_host_dev_release(struct device *dev)
357357
/* Wait for functions invoked through call_rcu(&scmd->rcu, ...) */
358358
rcu_barrier();
359359

360+
cancel_work_sync(&shost->eh_work);
360361
if (shost->tmf_work_q)
361362
destroy_workqueue(shost->tmf_work_q);
362363
if (shost->ehandler)
@@ -422,6 +423,7 @@ struct Scsi_Host *scsi_host_alloc(const struct scsi_host_template *sht, int priv
422423
INIT_LIST_HEAD(&shost->starved_list);
423424
init_waitqueue_head(&shost->host_wait);
424425
mutex_init(&shost->scan_mutex);
426+
INIT_WORK(&shost->eh_work, scsi_rcu_eh_wakeup);
425427

426428
index = ida_alloc(&host_index_ida, GFP_KERNEL);
427429
if (index < 0) {

drivers/scsi/scsi_error.c

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,26 @@ void scsi_eh_wakeup(struct Scsi_Host *shost, unsigned int busy)
7373
}
7474
}
7575

76+
void scsi_rcu_eh_wakeup(struct work_struct *work)
77+
{
78+
struct Scsi_Host *shost = container_of(work, struct Scsi_Host, eh_work);
79+
unsigned long flags;
80+
unsigned int busy;
81+
82+
/*
83+
* Ensure any running scsi_dec_host_busy has completed its rcu section
84+
* so changes to host state and host_eh_scheduled are visible to all
85+
* future calls of scsi_dec_host_busy
86+
*/
87+
synchronize_rcu();
88+
89+
busy = scsi_host_busy(shost);
90+
91+
spin_lock_irqsave(shost->host_lock, flags);
92+
scsi_eh_wakeup(shost, busy);
93+
spin_unlock_irqrestore(shost->host_lock, flags);
94+
}
95+
7696
/**
7797
* scsi_schedule_eh - schedule EH for SCSI host
7898
* @shost: SCSI host to invoke error handling on.
@@ -88,7 +108,7 @@ void scsi_schedule_eh(struct Scsi_Host *shost)
88108
if (scsi_host_set_state(shost, SHOST_RECOVERY) == 0 ||
89109
scsi_host_set_state(shost, SHOST_CANCEL_RECOVERY) == 0) {
90110
shost->host_eh_scheduled++;
91-
scsi_eh_wakeup(shost, scsi_host_busy(shost));
111+
queue_work(shost->tmf_work_q, &shost->eh_work);
92112
}
93113

94114
spin_unlock_irqrestore(shost->host_lock, flags);

drivers/scsi/scsi_priv.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ extern enum blk_eh_timer_return scsi_timeout(struct request *req);
9191
extern int scsi_error_handler(void *host);
9292
extern enum scsi_disposition scsi_decide_disposition(struct scsi_cmnd *cmd);
9393
extern void scsi_eh_wakeup(struct Scsi_Host *shost, unsigned int busy);
94+
extern void scsi_rcu_eh_wakeup(struct work_struct *work);
9495
extern void scsi_eh_scmd_add(struct scsi_cmnd *);
9596
void scsi_eh_ready_devs(struct Scsi_Host *shost,
9697
struct list_head *work_q,

include/scsi/scsi_host.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,9 @@ struct Scsi_Host {
750750
*/
751751
struct device *dma_dev;
752752

753+
/* Used for an rcu-synchronizing eh wakeup */
754+
struct work_struct eh_work;
755+
753756
/* Delay for runtime autosuspend */
754757
int rpm_autosuspend_delay;
755758

0 commit comments

Comments
 (0)