mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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:
commit
3650bb4139
|
|
@ -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)) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user