mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 01:32:21 +02:00
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: 6eb045e092 ("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>
This commit is contained in:
parent
fda6a1f3c3
commit
dccf3b1798
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user