Skip to content

Commit f5c2976

Browse files
bvanasschemartinkpetersen
authored andcommitted
scsi: ufs: core: Fix a race condition related to device management
If a device management command completion happens after wait_for_completion_timeout() times out and before ufshcd_clear_cmds() is called, then the completion code may crash on the complete() call in __ufshcd_transfer_req_compl(). Fix the following crash: Unable to handle kernel NULL pointer dereference at virtual address 0000000000000008 Call trace: complete+0x64/0x178 __ufshcd_transfer_req_compl+0x30c/0x9c0 ufshcd_poll+0xf0/0x208 ufshcd_sl_intr+0xb8/0xf0 ufshcd_intr+0x168/0x2f4 __handle_irq_event_percpu+0xa0/0x30c handle_irq_event+0x84/0x178 handle_fasteoi_irq+0x150/0x2e8 __handle_domain_irq+0x114/0x1e4 gic_handle_irq.31846+0x58/0x300 el1_irq+0xe4/0x1c0 efi_header_end+0x110/0x680 __irq_exit_rcu+0x108/0x124 __handle_domain_irq+0x118/0x1e4 gic_handle_irq.31846+0x58/0x300 el1_irq+0xe4/0x1c0 cpuidle_enter_state+0x3ac/0x8c4 do_idle+0x2fc/0x55c cpu_startup_entry+0x84/0x90 kernel_init+0x0/0x310 start_kernel+0x0/0x608 start_kernel+0x4ec/0x608 Link: https://lore.kernel.org/r/[email protected] Fixes: 5a0b0cb ("[SCSI] ufs: Add support for sending NOP OUT UPIU") Cc: Adrian Hunter <[email protected]> Cc: Avri Altman <[email protected]> Cc: Bean Huo <[email protected]> Cc: Stanley Chu <[email protected]> Signed-off-by: Bart Van Assche <[email protected]> Signed-off-by: Martin K. Petersen <[email protected]>
1 parent d9a434f commit f5c2976

File tree

1 file changed

+40
-18
lines changed

1 file changed

+40
-18
lines changed

drivers/ufs/core/ufshcd.c

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2953,37 +2953,59 @@ ufshcd_dev_cmd_completion(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
29532953
static int ufshcd_wait_for_dev_cmd(struct ufs_hba *hba,
29542954
struct ufshcd_lrb *lrbp, int max_timeout)
29552955
{
2956-
int err = 0;
2957-
unsigned long time_left;
2956+
unsigned long time_left = msecs_to_jiffies(max_timeout);
29582957
unsigned long flags;
2958+
bool pending;
2959+
int err;
29592960

2961+
retry:
29602962
time_left = wait_for_completion_timeout(hba->dev_cmd.complete,
2961-
msecs_to_jiffies(max_timeout));
2963+
time_left);
29622964

2963-
spin_lock_irqsave(hba->host->host_lock, flags);
2964-
hba->dev_cmd.complete = NULL;
29652965
if (likely(time_left)) {
2966+
/*
2967+
* The completion handler called complete() and the caller of
2968+
* this function still owns the @lrbp tag so the code below does
2969+
* not trigger any race conditions.
2970+
*/
2971+
hba->dev_cmd.complete = NULL;
29662972
err = ufshcd_get_tr_ocs(lrbp);
29672973
if (!err)
29682974
err = ufshcd_dev_cmd_completion(hba, lrbp);
2969-
}
2970-
spin_unlock_irqrestore(hba->host->host_lock, flags);
2971-
2972-
if (!time_left) {
2975+
} else {
29732976
err = -ETIMEDOUT;
29742977
dev_dbg(hba->dev, "%s: dev_cmd request timedout, tag %d\n",
29752978
__func__, lrbp->task_tag);
2976-
if (!ufshcd_clear_cmds(hba, 1U << lrbp->task_tag))
2979+
if (ufshcd_clear_cmds(hba, 1U << lrbp->task_tag) == 0) {
29772980
/* successfully cleared the command, retry if needed */
29782981
err = -EAGAIN;
2979-
/*
2980-
* in case of an error, after clearing the doorbell,
2981-
* we also need to clear the outstanding_request
2982-
* field in hba
2983-
*/
2984-
spin_lock_irqsave(&hba->outstanding_lock, flags);
2985-
__clear_bit(lrbp->task_tag, &hba->outstanding_reqs);
2986-
spin_unlock_irqrestore(&hba->outstanding_lock, flags);
2982+
/*
2983+
* Since clearing the command succeeded we also need to
2984+
* clear the task tag bit from the outstanding_reqs
2985+
* variable.
2986+
*/
2987+
spin_lock_irqsave(&hba->outstanding_lock, flags);
2988+
pending = test_bit(lrbp->task_tag,
2989+
&hba->outstanding_reqs);
2990+
if (pending) {
2991+
hba->dev_cmd.complete = NULL;
2992+
__clear_bit(lrbp->task_tag,
2993+
&hba->outstanding_reqs);
2994+
}
2995+
spin_unlock_irqrestore(&hba->outstanding_lock, flags);
2996+
2997+
if (!pending) {
2998+
/*
2999+
* The completion handler ran while we tried to
3000+
* clear the command.
3001+
*/
3002+
time_left = 1;
3003+
goto retry;
3004+
}
3005+
} else {
3006+
dev_err(hba->dev, "%s: failed to clear tag %d\n",
3007+
__func__, lrbp->task_tag);
3008+
}
29873009
}
29883010

29893011
return err;

0 commit comments

Comments
 (0)