usb: dwc3: gadget: never call ->complete() from ->ep_queue()
authorFelipe Balbi <felipe.balbi@linux.intel.com>
Mon, 26 Mar 2018 10:14:47 +0000 (13:14 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 24 Apr 2018 07:36:26 +0000 (09:36 +0200)
commit c91815b596245fd7da349ecc43c8def670d2269e upstream.

This is a requirement which has always existed but, somehow, wasn't
reflected in the documentation and problems weren't found until now
when Tuba Yavuz found a possible deadlock happening between dwc3 and
f_hid. She described the situation as follows:

spin_lock_irqsave(&hidg->write_spinlock, flags); // first acquire
/* we our function has been disabled by host */
if (!hidg->req) {
free_ep_req(hidg->in_ep, hidg->req);
goto try_again;
}

[...]

status = usb_ep_queue(hidg->in_ep, hidg->req, GFP_ATOMIC);
=>
[...]
=> usb_gadget_giveback_request
=>
f_hidg_req_complete
=>
spin_lock_irqsave(&hidg->write_spinlock, flags); // second acquire

Note that this happens because dwc3 would call ->complete() on a
failed usb_ep_queue() due to failed Start Transfer command. This is,
anyway, a theoretical situation because dwc3 currently uses "No
Response Update Transfer" command for Bulk and Interrupt endpoints.

It's still good to make this case impossible to happen even if the "No
Reponse Update Transfer" command is changed.

Reported-by: Tuba Yavuz <tuba@ece.ufl.edu>
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
Cc: stable <stable@vger.kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/dwc3/gadget.c

index 0ebdb313bb00b0efa11de13aa1da93cd8a9e7f64..fe75e969f5ac579531c7995d0bb7364268bc3d27 100644 (file)
@@ -174,18 +174,8 @@ static void dwc3_ep_inc_deq(struct dwc3_ep *dep)
        dwc3_ep_inc_trb(&dep->trb_dequeue);
 }
 
-/**
- * dwc3_gadget_giveback - call struct usb_request's ->complete callback
- * @dep: The endpoint to whom the request belongs to
- * @req: The request we're giving back
- * @status: completion code for the request
- *
- * Must be called with controller's lock held and interrupts disabled. This
- * function will unmap @req and call its ->complete() callback to notify upper
- * layers that it has completed.
- */
-void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
-               int status)
+void dwc3_gadget_del_and_unmap_request(struct dwc3_ep *dep,
+               struct dwc3_request *req, int status)
 {
        struct dwc3                     *dwc = dep->dwc;
 
@@ -198,18 +188,35 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
 
        if (req->trb)
                usb_gadget_unmap_request_by_dev(dwc->sysdev,
-                                               &req->request, req->direction);
+                               &req->request, req->direction);
 
        req->trb = NULL;
-
        trace_dwc3_gadget_giveback(req);
 
+       if (dep->number > 1)
+               pm_runtime_put(dwc->dev);
+}
+
+/**
+ * dwc3_gadget_giveback - call struct usb_request's ->complete callback
+ * @dep: The endpoint to whom the request belongs to
+ * @req: The request we're giving back
+ * @status: completion code for the request
+ *
+ * Must be called with controller's lock held and interrupts disabled. This
+ * function will unmap @req and call its ->complete() callback to notify upper
+ * layers that it has completed.
+ */
+void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
+               int status)
+{
+       struct dwc3                     *dwc = dep->dwc;
+
+       dwc3_gadget_del_and_unmap_request(dep, req, status);
+
        spin_unlock(&dwc->lock);
        usb_gadget_giveback_request(&dep->endpoint, &req->request);
        spin_lock(&dwc->lock);
-
-       if (dep->number > 1)
-               pm_runtime_put(dwc->dev);
 }
 
 /**
@@ -1233,7 +1240,7 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep, u16 cmd_param)
                if (req->trb)
                        memset(req->trb, 0, sizeof(struct dwc3_trb));
                dep->queued_requests--;
-               dwc3_gadget_giveback(dep, req, ret);
+               dwc3_gadget_del_and_unmap_request(dep, req, ret);
                return ret;
        }