Re: [PATCH v2] scsi: core: pair EH runtime PM get and put
From: Alan Stern
Date: Mon Jul 27 2026 - 10:26:57 EST
On Mon, Jul 27, 2026 at 01:56:15PM +0800, Hongjie Fang wrote:
> shost->eh_noresume is currently consulted twice in one error handling
> iteration: once before scsi_autopm_get_host() and once again before
> scsi_autopm_put_host().
>
> That is racy when a PM-triggered error path flips shost->eh_noresume while
> the SCSI EH thread is still running.
>
> The problem flow looks like this:
> PM path
> ufshcd_set_dev_pwr_mode()
> shost->eh_noresume = 1
> ufshcd_execute_start_stop <-- trigger EH
> ...
> shost->eh_noresume = 0
>
> EH path
> scsi_error_handler()
> if (!shost->eh_noresume)
> scsi_autopm_get_host() <-- skipped
> ...
> if (!shost->eh_noresume)
> scsi_autopm_put_host() <-- executed later
>
> In that case one EH iteration can skip autoresume on entry and still drop
> a runtime PM reference on exit. That leaves an unmatched runtime PM put
> and can trigger a runtime PM usage count underflow.
>
> Fix this by snapshotting shost->eh_noresume once per EH iteration and
> using that snapshot for both runtime PM get and put decisions.
>
> Fixes: ae0751ffc77e ("[SCSI] add flag to skip the runtime PM calls on the host")
> Signed-off-by: Hongjie Fang <hongjiefang@xxxxxxxxxxxx>
> ---
> drivers/scsi/scsi_error.c | 6 ++++--
> 1 file changed, 4 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c
> index 147127fb4db9..a216f56044d9 100644
> --- a/drivers/scsi/scsi_error.c
> +++ b/drivers/scsi/scsi_error.c
> @@ -2342,6 +2342,7 @@ static void scsi_unjam_host(struct Scsi_Host *shost)
> int scsi_error_handler(void *data)
> {
> struct Scsi_Host *shost = data;
> + bool eh_noresume;
>
> /*
> * We use TASK_INTERRUPTIBLE so that the thread is not
> @@ -2383,7 +2384,8 @@ int scsi_error_handler(void *data)
> * what we need to do to get it up and online again (if we can).
> * If we fail, we end up taking the thing offline.
> */
> - if (!shost->eh_noresume && scsi_autopm_get_host(shost) != 0) {
> + eh_noresume = shost->eh_noresume;
You should be aware that simply reading a variable does not snapshot its
value unless the read is protected by a lock or something else. In this
case, the compiler is allowed to eliminate the eh_noresume variable and
use shost->eh_noresume instead, both here and below, under the
assumption that shost->eh_noresume doesn't change in the meantime.
To truly snapshot the the value in a way that's immune to changes from
other threads, you have to use READ_ONCE():
eh_noresume = READ_ONCE(shost->eh_noresume);
The compiler is then not allowed to assume that shost->eh_noresume
remains unchanged later on. This is all explained (along with a _lot_
of other material -- search for "READ_ONCE") in
Documentation/memory-barriers.txt.
Alan Stern
> + if (!eh_noresume && scsi_autopm_get_host(shost) != 0) {
> SCSI_LOG_ERROR_RECOVERY(1,
> shost_printk(KERN_ERR, shost,
> "scsi_eh_%d: unable to autoresume\n",
> @@ -2407,7 +2409,7 @@ int scsi_error_handler(void *data)
> * which are still online.
> */
> scsi_restart_operations(shost);
> - if (!shost->eh_noresume)
> + if (!eh_noresume)
> scsi_autopm_put_host(shost);
> }
> __set_current_state(TASK_RUNNING);
> --
> 2.25.1
>