2022-09-18 03:47:22

by Duoming Zhou

[permalink] [raw]
Subject: [PATCH] usb: chipidea: fix deadlock in ci_otg_del_timer

There is a deadlock in ci_otg_del_timer(), the process is
shown below:

(thread 1) | (thread 2)
ci_otg_del_timer() | ci_otg_hrtimer_func()
... |
spin_lock_irqsave() //(1) | ...
... |
hrtimer_cancel() | spin_lock_irqsave() //(2)
(block forever)

We hold ci->lock in position (1) and use hrtimer_cancel() to
wait ci_otg_hrtimer_func() to stop, but ci_otg_hrtimer_func()
also need ci->lock in position (2). As a result, the
hrtimer_cancel() in ci_otg_del_timer() will be blocked forever.

This patch extracts hrtimer_cancel() from the protection of
spin_lock_irqsave() in order that the ci_otg_hrtimer_func()
could obtain the ci->lock.

What`s more, there will be no race happen. Because the
"next_timer" is always under the protection of
spin_lock_irqsave() and we only check whether "next_timer"
equals to NUM_OTG_FSM_TIMERS in the following code.

Fixes: 3a316ec4c91c ("usb: chipidea: use hrtimer for otg fsm timers")
Signed-off-by: Duoming Zhou <[email protected]>
---
drivers/usb/chipidea/otg_fsm.c | 2 ++
1 file changed, 2 insertions(+)

diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c
index 61b157b9c66..a7858462428 100644
--- a/drivers/usb/chipidea/otg_fsm.c
+++ b/drivers/usb/chipidea/otg_fsm.c
@@ -256,8 +256,10 @@ static void ci_otg_del_timer(struct ci_hdrc *ci, enum otg_fsm_timer t)
ci->enabled_otg_timer_bits &= ~(1 << t);
if (ci->next_otg_timer == t) {
if (ci->enabled_otg_timer_bits == 0) {
+ spin_unlock_irqrestore(&ci->lock, flags);
/* No enabled timers after delete it */
hrtimer_cancel(&ci->otg_fsm_hrtimer);
+ spin_lock_irqsave(&ci->lock, flags);
ci->next_otg_timer = NUM_OTG_FSM_TIMERS;
} else {
/* Find the next timer */
--
2.17.1