usb: dwc3: gadget: fix deadlock in kick transfer

If kick transfer command fail and the return value isn't
EAGAIN, the current code will issue end transfer command
to stop the active transfer, and only set dep->flags with
DWC3_EP_END_TRANSFER_PENDING if the end transfer command
is done successfully. If the DWC3_EP_END_TRANSFER_PENDING
isn't set, it cleanup the cancelled requests and give the
request back to the gadget layer immediately.

For uvc gadget, the uvc gadget driver hold spinlock and
then call usb_ep_queue() to submit uvc usb request to dwc3
controller. The dwc3 controller may kick transfer for the
request sequentially, if the kick transfer command and
the end transfer command all failed, the dwc3 will give
the request back to the uvc gadget driver via the request
complete function uvc_video_complete(), in this function,
it try to get the spinlock again that lead to deadlock.

This case always happens with the following warning log:

WARNING: CPU: 0 PID: 14450 at drivers/usb/dwc3/gadget.c:1839 __dwc3_gadget_kick_transfer+0x3a0/0x3b0
...
Workqueue: events uvcg_video_pump
...
Call trace:
 __dwc3_gadget_kick_transfer+0x3a0/0x3b0
 __dwc3_gadget_ep_queue+0x128/0x1f0
 dwc3_gadget_ep_queue+0x40/0x6c
 usb_ep_queue+0x44/0x100
 uvcg_video_pump+0xd0/0x1d4
 process_one_work+0x1f4/0x490
 worker_thread+0x278/0x4dc
 kthread+0x13c/0x344
 ret_from_fork+0x10/0x30

Fixes: 0ec00e864a ("UPSTREAM: usb: dwc3: gadget: move cmd_endtransfer to extra function")
Change-Id: I43d455a45d542efcaa9234de60e37277611b3c47
Signed-off-by: William Wu <william.wu@rock-chips.com>
This commit is contained in:
William Wu
2022-11-16 09:44:58 +08:00
committed by Tao Huang
parent 9086c42501
commit a9a4f474d7

View File

@@ -1851,7 +1851,7 @@ static int __dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force, bool int
if (!interrupt)
dep->flags &= ~DWC3_EP_TRANSFER_STARTED;
else if (!ret)
else
dep->flags |= DWC3_EP_END_TRANSFER_PENDING;
return ret;
@@ -2032,8 +2032,11 @@ static int __dwc3_gadget_start_isoc(struct dwc3_ep *dep)
* status, issue END_TRANSFER command and retry on the next XferNotReady
* event.
*/
if (ret == -EAGAIN)
if (ret == -EAGAIN) {
ret = __dwc3_stop_active_transfer(dep, false, true);
if (ret)
dep->flags &= ~DWC3_EP_END_TRANSFER_PENDING;
}
return ret;
}