[RFC][PATCH 1/5] usb: dwc2: Avoid sleeping while holding hsotg->lock

From: John Stultz
Date: Tue Dec 13 2016 - 02:11:04 EST


Basically when plugging in various cables in different orders, I'm
occasionally seeing the following BUG splat:

[ 86.215403] BUG: scheduling while atomic: kworker/u16:2/53/0x00000002
[ 86.219164] usb 1-1: USB disconnect, device number 9
[ 86.226845] Preemption disabled at:[ 86.230218]
[<ffffff8008673558>] dwc2_conn_id_status_change+0x120/0x250
[ 86.236894] CPU: 0 PID: 53 Comm: kworker/u16:2 Tainted: G W
4.9.0-rc8-00051-gd5a7979-dirty #1702
[ 86.246836] Hardware name: HiKey Development Board (DT)
[ 86.252100] Workqueue: dwc2 dwc2_conn_id_status_change
[ 86.257279] Call trace:
[ 86.259771] [<ffffff8008087c28>] dump_backtrace+0x0/0x1a0
[ 86.265210] [<ffffff8008087ddc>] show_stack+0x14/0x20
[ 86.270308] [<ffffff80084343f0>] dump_stack+0x90/0xb0
[ 86.275401] [<ffffff80080d8d94>] __schedule_bug+0x6c/0xb8
[ 86.280841] [<ffffff8008a07220>] __schedule+0x4f8/0x5b0
[ 86.286099] [<ffffff8008a073e8>] schedule+0x38/0xa0
[ 86.291017] [<ffffff8008a0a6cc>] schedule_hrtimeout_range_clock+0x8c/0xf0
[ 86.297846] [<ffffff8008a0a740>] schedule_hrtimeout_range+0x10/0x18
[ 86.304150] [<ffffff8008a0a4a0>] usleep_range+0x50/0x58
[ 86.309418] [<ffffff800866d8dc>] dwc2_wait_for_mode.isra.4+0x54/0xd0
[ 86.315815] [<ffffff800866f058>] dwc2_core_reset+0xe0/0x168
[ 86.321431] [<ffffff800867e364>] dwc2_hsotg_core_init_disconnected+0x2c/0x310
[ 86.328602] [<ffffff8008673568>] dwc2_conn_id_status_change+0x130/0x250
[ 86.335254] [<ffffff80080ccd48>] process_one_work+0x118/0x370
[ 86.341035] [<ffffff80080ccfe8>] worker_thread+0x48/0x498
[ 86.346473] [<ffffff80080d2eb0>] kthread+0xd0/0xe8
[ 86.351299] [<ffffff8008082e80>] ret_from_fork+0x10/0x50

This seems to be caused by the dwc2_wait_for_mode() calling
usleep_range() while the hstog->lock spinlock is held, since
we take that before calling dwc2_hsotg_core_init_disconnected().

This patch avoids the issue by adding an extra argument to
dwc2_core_reset(), as suggested by John Youn, which allows us to
skip the waiting, which should be unnecessary when calling from
dwc2_hsotg_core_init_disconnected().

Cc: Wei Xu <xuwei5@xxxxxxxxxxxxx>
Cc: Guodong Xu <guodong.xu@xxxxxxxxxx>
Cc: Amit Pundir <amit.pundir@xxxxxxxxxx>
Cc: Rob Herring <robh+dt@xxxxxxxxxx>
Cc: John Youn <johnyoun@xxxxxxxxxxxx>
Cc: Douglas Anderson <dianders@xxxxxxxxxxxx>
Cc: Chen Yu <chenyu56@xxxxxxxxxx>
Cc: Kishon Vijay Abraham I <kishon@xxxxxx>
Cc: Felipe Balbi <felipe.balbi@xxxxxxxxxxxxxxx>
Cc: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
Cc: linux-usb@xxxxxxxxxxxxxxx
Signed-off-by: John Stultz <john.stultz@xxxxxxxxxx>
---
drivers/usb/dwc2/core.c | 6 +++---
drivers/usb/dwc2/core.h | 2 +-
drivers/usb/dwc2/gadget.c | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c
index 4c0fa0b..2e83545 100644
--- a/drivers/usb/dwc2/core.c
+++ b/drivers/usb/dwc2/core.c
@@ -313,7 +313,7 @@ static bool dwc2_iddig_filter_enabled(struct dwc2_hsotg *hsotg)
* Do core a soft reset of the core. Be careful with this because it
* resets all the internal state machines of the core.
*/
-int dwc2_core_reset(struct dwc2_hsotg *hsotg)
+int dwc2_core_reset(struct dwc2_hsotg *hsotg, bool skip_wait)
{
u32 greset;
int count = 0;
@@ -369,7 +369,7 @@ int dwc2_core_reset(struct dwc2_hsotg *hsotg)
}
} while (!(greset & GRSTCTL_AHBIDLE));

- if (wait_for_host_mode)
+ if (wait_for_host_mode && !skip_wait)
dwc2_wait_for_mode(hsotg, true);

return 0;
@@ -500,7 +500,7 @@ int dwc2_core_reset_and_force_dr_mode(struct dwc2_hsotg *hsotg)
{
int retval;

- retval = dwc2_core_reset(hsotg);
+ retval = dwc2_core_reset(hsotg, false);
if (retval)
return retval;

diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h
index 2a21a04..963ea1b 100644
--- a/drivers/usb/dwc2/core.h
+++ b/drivers/usb/dwc2/core.h
@@ -1020,7 +1020,7 @@ enum dwc2_halt_status {
* The following functions support initialization of the core driver component
* and the DWC_otg controller
*/
-extern int dwc2_core_reset(struct dwc2_hsotg *hsotg);
+extern int dwc2_core_reset(struct dwc2_hsotg *hsotg, bool skip_wait);
extern int dwc2_core_reset_and_force_dr_mode(struct dwc2_hsotg *hsotg);
extern int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg);
extern int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, bool restore);
diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c
index 24fbebc..0bda799 100644
--- a/drivers/usb/dwc2/gadget.c
+++ b/drivers/usb/dwc2/gadget.c
@@ -2521,7 +2521,7 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg,
kill_all_requests(hsotg, hsotg->eps_out[0], -ECONNRESET);

if (!is_usb_reset)
- if (dwc2_core_reset(hsotg))
+ if (dwc2_core_reset(hsotg, true))
return;

/*
--
2.7.4