From cd37a69c367b6a5f685db0ac7d01f08545081a30 Mon Sep 17 00:00:00 2001 From: Stanley Jhu Date: Thu, 22 Oct 2026 11:44:38 +0800 Subject: [PATCH 1/6] scsi: ufs: core: Remove an always-false WARN_ONCE() The WARN_ONCE() in ufshcd_poll() can never fire. completed_reqs is set to ~tr_doorbell & hba->outstanding_reqs on the line above, and hba->outstanding_reqs cannot change in between because ufshcd_poll() holds hba->outstanding_lock. So completed_reqs & ~hba->outstanding_reqs is always zero. Remove it. No functional change. Cc: stable@vger.kernel.org Reviewed-by: Bean Huo Reviewed-by: Peter Wang Reviewed-by: Bart Van Assche Signed-off-by: Stanley Jhu Link: https://patch.msgid.link/20261022034443.3034432-2-stanleyjhu@google.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/core/ufshcd.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 2ba244cf40ac..f03a8d39fb7a 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -6017,9 +6017,6 @@ 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); From 22d14117ec1845a81acb3a5c4be325f7e8332f70 Mon Sep 17 00:00:00 2001 From: Stanley Jhu Date: Thu, 22 Oct 2026 11:44:39 +0800 Subject: [PATCH 2/6] scsi: ufs: core: Add a helper for clearing hba->outstanding_reqs ufshcd_poll() and ufshcd_abort() each have their own copy of the code that clears bits in hba->outstanding_reqs under hba->outstanding_lock. Move that code into ufshcd_clear_outstanding_reqs() and its locked variant __ufshcd_clear_outstanding_reqs(), and use them in both functions. No functional change. Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/3779e125-9acc-42c8-9489-51b9e3c6716d@acm.org Suggested-by: Bart Van Assche Reviewed-by: Bean Huo Reviewed-by: Peter Wang Signed-off-by: Stanley Jhu Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/20261022034443.3034432-3-stanleyjhu@google.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/core/ufshcd.c | 48 +++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index f03a8d39fb7a..f2e3b782f1fe 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -5997,6 +5997,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. @@ -6007,6 +6038,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]; @@ -6016,12 +6048,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; - 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) @@ -8056,7 +8085,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); @@ -8151,11 +8179,7 @@ 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_release_scsi_cmd(hba, cmd); err = SUCCESS; From 6b9c1f49971e4dfe8b8916f0527d30e97c15f234 Mon Sep 17 00:00:00 2001 From: Stanley Jhu Date: Thu, 22 Oct 2026 11:44:40 +0800 Subject: [PATCH 3/6] scsi: ufs: core: Fix double completion in ufshcd_clear_lu_cmds() In SDB mode, ufshcd_clear_lu_cmds() clears the request slot in the controller and completes the request, but leaves its bit set in hba->outstanding_reqs. ufshcd_poll() treats a request whose bit is set in hba->outstanding_reqs and clear in the doorbell register as finished, so the next ufshcd_poll() completes the same request again and unmaps its DMA buffers twice. Clear the bit with ufshcd_clear_outstanding_reqs() before completing the request, and complete it only if the bit was still set. If ufshcd_poll() has already completed the request, the bit is already clear and ufshcd_clear_lu_cmds() skips it. Fixes: f18fac1e2b72 ("scsi: ufs: core: Rework ufshcd_eh_device_reset_handler()") Cc: stable@vger.kernel.org Reviewed-by: Bean Huo Reviewed-by: Peter Wang Reviewed-by: Bart Van Assche Signed-off-by: Stanley Jhu Link: https://patch.msgid.link/20261022034443.3034432-4-stanleyjhu@google.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/core/ufshcd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index f2e3b782f1fe..2b9fd8c7c328 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -7932,7 +7932,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; } From 3a7e551ae4bcc53f271fc15ab8e930598e28dad8 Mon Sep 17 00:00:00 2001 From: Stanley Jhu Date: Thu, 22 Oct 2026 11:44:41 +0800 Subject: [PATCH 4/6] scsi: ufs: core: Fix double release in ufshcd_eh_timed_out() In SDB mode, a command that times out during a power management operation can be released twice: 1. The command times out while its normal completion is running. 2. The completion path clears BIT(rq->tag) in hba->outstanding_reqs and releases the command. It has not called scsi_done() yet, so SCMD_STATE_COMPLETE is still clear. 3. ufshcd_eh_timed_out() recovers the link, sees SCMD_STATE_COMPLETE clear, clears BIT(rq->tag) in hba->outstanding_reqs without checking whether it was still set, and releases the same command a second time. Return SCSI_EH_DONE when ufshcd_clear_outstanding_reqs() returns 0, as another path has already claimed the command. Fixes: 01d5e237b339 ("scsi: ufs: core: Handle PM commands timeout before SCSI EH") Cc: stable@vger.kernel.org Reviewed-by: Peter Wang Signed-off-by: Stanley Jhu Reviewed-by: Bean Huo Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/20261022034443.3034432-5-stanleyjhu@google.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/core/ufshcd.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 2b9fd8c7c328..6cb5767b69fd 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -9601,12 +9601,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)) { From 7cfdfdd6557906fd09cf0a9a564a07012e7fb0c1 Mon Sep 17 00:00:00 2001 From: Stanley Jhu Date: Thu, 22 Oct 2026 11:44:42 +0800 Subject: [PATCH 5/6] scsi: ufs: core: Remove completed-without-interrupt path in ufshcd_abort() In SDB mode, a command that times out right as the controller finishes it can be completed twice: 1. The command times out and scsi_timeout() schedules ufshcd_abort(). 2. The controller finishes the command and clears its doorbell bit in REG_UTP_TRANSFER_REQ_DOOR_BELL, while the completion handler has not yet cleared BIT(tag) in hba->outstanding_reqs. 3. ufshcd_abort() sees the doorbell bit clear and calls __ufshcd_transfer_req_compl() without clearing BIT(tag) in hba->outstanding_reqs. 4. The normal completion handler or the logical unit reset that follows sees BIT(tag) still set in hba->outstanding_reqs and completes the same command a second time. This branch only exists for a rare corner case, a completion interrupt that is lost or arrives late. Remove it to simplify the abort path and leave such a command to the SCSI error handler, like any other command that cannot be aborted. The cost is a slower recovery in this corner case, which is acceptable since it only happens after a timeout. Fixes: 169f5eb28869 ("scsi: ufs: Optimize SCSI command processing") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/fdb5c391-01ef-4a23-94c2-4f3853909348@acm.org Signed-off-by: Stanley Jhu Reviewed-by: Peter Wang Reviewed-by: Bean Huo Reviewed-by: Bart Van Assche Link: https://patch.msgid.link/20261022034443.3034432-6-stanleyjhu@google.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/core/ufshcd.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 6cb5767b69fd..834c7f3b592e 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -8125,15 +8125,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 From eb2c4847a1cb2f426cfc702364df7a38ebbf730b Mon Sep 17 00:00:00 2001 From: Stanley Jhu Date: Thu, 22 Oct 2026 11:44:43 +0800 Subject: [PATCH 6/6] scsi: ufs: core: Do not release a reserved request in ufshcd_abort() In SDB mode, aborting a timed-out device management request drops a clock hold that the request never took: 1. ufshcd_dev_man_lock() takes one ufshcd_hold() for the whole device management command. ufshcd_queue_reserved_command() submits the request without taking a hold of its own. 2. The request times out and ufshcd_abort() clears its slot with ufshcd_clear_cmd(). 3. ufshcd_abort() claims BIT(tag) in hba->outstanding_reqs and calls ufshcd_release_scsi_cmd(), which calls ufshcd_release() for a hold the request never took. 4. When ufshcd_dev_man_unlock() drops the hold from step 1, hba->clk_gating.active_reqs goes below zero. Release the command only if it is a SCSI command. Fixes: 08b12cda6c44 ("scsi: ufs: core: Switch to scsi_get_internal_cmd()") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/2060a8da-581b-46d1-a510-779f4df716cd@acm.org Reviewed-by: Bart Van Assche Reviewed-by: Peter Wang Signed-off-by: Stanley Jhu Reviewed-by: Bean Huo Link: https://patch.msgid.link/20261022034443.3034432-7-stanleyjhu@google.com Signed-off-by: Martin K. Petersen (Oracle) --- drivers/ufs/core/ufshcd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c index 834c7f3b592e..ddb232ef07a8 100644 --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -8171,7 +8171,8 @@ static int ufshcd_abort(struct scsi_cmnd *cmd) * Clear the corresponding bit from outstanding_reqs since the command * has been aborted successfully. */ - if (ufshcd_clear_outstanding_reqs(hba, BIT(tag))) + if (ufshcd_clear_outstanding_reqs(hba, BIT(tag)) && + ufshcd_is_scsi_cmd(cmd)) ufshcd_release_scsi_cmd(hba, cmd); err = SUCCESS;