Merge patch series "scsi: ufs: core: Factor hba->outstanding_reqs helper and fix double completions"

Stanley Jhu <stanleyjhu@google.com> says:

In Single Doorbell (SDB) mode, multiple completion and error handling
paths can race on the same transfer request when bits in
hba->outstanding_reqs are not claimed atomically with completing the
request. A stale bit in hba->outstanding_reqs lets a later poll or
reset complete the request again and unmap its DMA buffers twice.

Patch 1 removes a WARN_ONCE() in ufshcd_poll() whose condition is always
false.

Patch 2 adds __ufshcd_clear_outstanding_reqs() and
ufshcd_clear_outstanding_reqs() to share the bit-claim logic across
ufshcd_poll() and ufshcd_abort() with no functional change.

Patch 3 uses the helper in ufshcd_clear_lu_cmds() to fix a double
completion during LU reset.

Patch 4 uses the helper in ufshcd_eh_timed_out() and checks its return
value to prevent releasing driver resources twice.

Patch 5 removes the completed-without-interrupt branch in ufshcd_abort()
to fix a double completion on timed-out commands.

Patch 6 skips releasing reserved requests at the end of ufshcd_abort().

This series is based on mkp/for-next (db8b4d2bf2).

Tested: QEMU ARM64 in SDB mode across probe, I/O, abort, LU reset, and
PM timeout paths without double completion or resource underflow.

Link: https://patch.msgid.link/20261022034443.3034432-1-stanleyjhu@google.com
Signed-off-by: Martin K. Petersen (Oracle) <mkp@kernel.org>
This commit is contained in:
Martin K. Petersen (Oracle) 2026-10-23 23:17:12 -04:00
commit 3650bb4139

View File

@ -6057,6 +6057,37 @@ static void ufshcd_clear_polled(struct ufs_hba *hba,
}
}
/*
* Return a bitmask of the transfer requests cleared from
* @hba->outstanding_reqs.
*/
static unsigned long __ufshcd_clear_outstanding_reqs(struct ufs_hba *hba,
unsigned long mask,
bool from_irq)
__must_hold(&hba->outstanding_lock)
{
unsigned long completed_reqs = mask & hba->outstanding_reqs;
if (from_irq) {
/* Do not complete polled requests from interrupt context. */
ufshcd_clear_polled(hba, &completed_reqs);
}
hba->outstanding_reqs &= ~completed_reqs;
return completed_reqs;
}
/*
* Return a bitmask of the transfer requests cleared from
* @hba->outstanding_reqs.
*/
static unsigned long ufshcd_clear_outstanding_reqs(struct ufs_hba *hba,
unsigned long mask)
{
guard(spinlock_irqsave)(&hba->outstanding_lock);
return __ufshcd_clear_outstanding_reqs(hba, mask, false);
}
/*
* Return: > 0 if one or more commands have been completed or 0 if no
* requests have been completed.
@ -6067,6 +6098,7 @@ static int ufshcd_poll(struct Scsi_Host *shost, unsigned int queue_num)
unsigned long completed_reqs, flags;
u32 tr_doorbell;
struct ufs_hw_queue *hwq;
bool from_irq;
if (hba->mcq_enabled) {
hwq = &hba->uhq[queue_num];
@ -6076,15 +6108,9 @@ static int ufshcd_poll(struct Scsi_Host *shost, unsigned int queue_num)
spin_lock_irqsave(&hba->outstanding_lock, flags);
tr_doorbell = ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL);
completed_reqs = ~tr_doorbell & hba->outstanding_reqs;
WARN_ONCE(completed_reqs & ~hba->outstanding_reqs,
"completed: %#lx; outstanding: %#lx\n", completed_reqs,
hba->outstanding_reqs);
if (queue_num == UFSHCD_POLL_FROM_INTERRUPT_CONTEXT) {
/* Do not complete polled requests from interrupt context. */
ufshcd_clear_polled(hba, &completed_reqs);
}
hba->outstanding_reqs &= ~completed_reqs;
from_irq = queue_num == UFSHCD_POLL_FROM_INTERRUPT_CONTEXT;
completed_reqs = __ufshcd_clear_outstanding_reqs(hba, ~tr_doorbell,
from_irq);
spin_unlock_irqrestore(&hba->outstanding_lock, flags);
if (completed_reqs)
@ -7965,7 +7991,8 @@ static bool ufshcd_clear_lu_cmds(struct request *req, void *priv)
return true;
}
ufshcd_compl_one_cqe(hba, tag, NULL);
if (ufshcd_clear_outstanding_reqs(hba, BIT(tag)))
ufshcd_compl_one_cqe(hba, tag, NULL);
return true;
}
@ -8118,7 +8145,6 @@ static int ufshcd_abort(struct scsi_cmnd *cmd)
struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
unsigned long flags;
int err = FAILED;
bool outstanding;
u32 reg;
ufshcd_hold(hba);
@ -8158,15 +8184,6 @@ static int ufshcd_abort(struct scsi_cmnd *cmd)
}
hba->req_abort_count++;
if (!hba->mcq_enabled && !(reg & (1 << tag))) {
/* only execute this code in single doorbell mode */
dev_err(hba->dev,
"%s: cmd was completed, but without a notifying intr, tag = %d",
__func__, tag);
__ufshcd_transfer_req_compl(hba, 1UL << tag);
goto release;
}
/*
* Task abort to the device W-LUN is illegal. When this command
* will fail, due to spec violation, scsi err handling next step
@ -8213,11 +8230,8 @@ static int ufshcd_abort(struct scsi_cmnd *cmd)
* Clear the corresponding bit from outstanding_reqs since the command
* has been aborted successfully.
*/
spin_lock_irqsave(&hba->outstanding_lock, flags);
outstanding = __test_and_clear_bit(tag, &hba->outstanding_reqs);
spin_unlock_irqrestore(&hba->outstanding_lock, flags);
if (outstanding)
if (ufshcd_clear_outstanding_reqs(hba, BIT(tag)) &&
ufshcd_is_scsi_cmd(cmd))
ufshcd_release_scsi_cmd(hba, cmd);
err = SUCCESS;
@ -9643,12 +9657,10 @@ static enum scsi_timeout_action ufshcd_eh_timed_out(struct scsi_cmnd *scmd)
*/
if (!test_bit(SCMD_STATE_COMPLETE, &scmd->state)) {
if (!hba->mcq_enabled) {
unsigned long flags;
struct request *rq = scsi_cmd_to_rq(scmd);
spin_lock_irqsave(&hba->outstanding_lock, flags);
__clear_bit(rq->tag, &hba->outstanding_reqs);
spin_unlock_irqrestore(&hba->outstanding_lock, flags);
if (!ufshcd_clear_outstanding_reqs(hba, BIT(rq->tag)))
return SCSI_EH_DONE;
}
if (ufshcd_is_scsi_cmd(scmd)) {