Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

scsi: ufs: core: Quiesce request queues before checking pending cmds

In ufshcd_clock_scaling_prepare(), after SCSI layer is blocked,
ufshcd_pending_cmds() is called to check whether there are pending
transactions or not. And only if there are no pending transactions can we
proceed to kickstart the clock scaling sequence.

ufshcd_pending_cmds() traverses over all SCSI devices and calls
sbitmap_weight() on their budget_map. sbitmap_weight() can be broken down
to three steps:

1. Calculate the nr outstanding bits set in the 'word' bitmap.

2. Calculate the nr outstanding bits set in the 'cleared' bitmap.

3. Subtract the result from step 1 by the result from step 2.

This can lead to a race condition as outlined below:

Assume there is one pending transaction in the request queue of one SCSI
device, say sda, and the budget token of this request is 0, the 'word' is
0x1 and the 'cleared' is 0x0.

1. When step 1 executes, it gets the result as 1.

2. Before step 2 executes, block layer tries to dispatch a new request to
sda. Since the SCSI layer is blocked, the request cannot pass through
SCSI but the block layer would do budget_get() and budget_put() to
sda's budget map regardless, so the 'word' has become 0x3 and 'cleared'
has become 0x2 (assume the new request got budget token 1).

3. When step 2 executes, it gets the result as 1.

4. When step 3 executes, it gets the result as 0, meaning there is no
pending transactions, which is wrong.

Thread A Thread B
ufshcd_pending_cmds() __blk_mq_sched_dispatch_requests()
| |
sbitmap_weight(word) |
| scsi_mq_get_budget()
| |
| scsi_mq_put_budget()
| |
sbitmap_weight(cleared)
...

When this race condition happens, the clock scaling sequence is started
with transactions still in flight, leading to subsequent hibernate enter
failure, broken link, task abort and back to back error recovery.

Fix this race condition by quiescing the request queues before calling
ufshcd_pending_cmds() so that block layer won't touch the budget map when
ufshcd_pending_cmds() is working on it. In addition, remove the SCSI layer
blocking/unblocking to reduce redundancies and latencies.

Fixes: 8d077ede48c1 ("scsi: ufs: Optimize the command queueing code")
Co-developed-by: Can Guo <quic_cang@quicinc.com>
Signed-off-by: Can Guo <quic_cang@quicinc.com>
Signed-off-by: Ziqi Chen <quic_ziqichen@quicinc.com>
Link: https://lore.kernel.org/r/1717754818-39863-1-git-send-email-quic_ziqichen@quicinc.com
Reviewed-by: Bart Van Assche <bvanassche@acm.org>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>

authored by

Ziqi Chen and committed by
Martin K. Petersen
77691af4 52912ca8

+3 -3
+3 -3
drivers/ufs/core/ufshcd.c
··· 1366 1366 * make sure that there are no outstanding requests when 1367 1367 * clock scaling is in progress 1368 1368 */ 1369 - ufshcd_scsi_block_requests(hba); 1369 + blk_mq_quiesce_tagset(&hba->host->tag_set); 1370 1370 mutex_lock(&hba->wb_mutex); 1371 1371 down_write(&hba->clk_scaling_lock); 1372 1372 ··· 1375 1375 ret = -EBUSY; 1376 1376 up_write(&hba->clk_scaling_lock); 1377 1377 mutex_unlock(&hba->wb_mutex); 1378 - ufshcd_scsi_unblock_requests(hba); 1378 + blk_mq_unquiesce_tagset(&hba->host->tag_set); 1379 1379 goto out; 1380 1380 } 1381 1381 ··· 1396 1396 1397 1397 mutex_unlock(&hba->wb_mutex); 1398 1398 1399 - ufshcd_scsi_unblock_requests(hba); 1399 + blk_mq_unquiesce_tagset(&hba->host->tag_set); 1400 1400 ufshcd_release(hba); 1401 1401 } 1402 1402