]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
usb: dwc3: gadget: END_TRANSFER before CLEAR_STALL command
authorThinh Nguyen <Thinh.Nguyen@synopsys.com>
Thu, 3 Sep 2020 01:43:04 +0000 (18:43 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 5 Nov 2020 10:43:28 +0000 (11:43 +0100)
commit d97c78a1908e59a1fdbcbece87cd0440b5d7a1f2 upstream.

According the programming guide (for all DWC3 IPs), when the driver
handles ClearFeature(halt) request, it should issue CLEAR_STALL command
_after_ the END_TRANSFER command completes. The END_TRANSFER command may
take some time to complete. So, delay the ClearFeature(halt) request
control status stage and wait for END_TRANSFER command completion
interrupt. Only after END_TRANSFER command completes that the driver
may issue CLEAR_STALL command.

Cc: stable@vger.kernel.org
Fixes: cb11ea56f37a ("usb: dwc3: gadget: Properly handle ClearFeature(halt)")
Signed-off-by: Thinh Nguyen <thinhn@synopsys.com>
Signed-off-by: Felipe Balbi <balbi@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/dwc3/core.h
drivers/usb/dwc3/ep0.c
drivers/usb/dwc3/gadget.c
drivers/usb/dwc3/gadget.h

index 4dfbffa944de1d4d611c390c98706d0302708846..c848f9164f929b2452292d1ac82afd7601a95b8b 100644 (file)
@@ -700,6 +700,7 @@ struct dwc3_ep {
 #define DWC3_EP_END_TRANSFER_PENDING BIT(4)
 #define DWC3_EP_PENDING_REQUEST        BIT(5)
 #define DWC3_EP_DELAY_START    BIT(6)
+#define DWC3_EP_PENDING_CLEAR_STALL    BIT(11)
 
        /* This last one is specific to EP0 */
 #define DWC3_EP0_DIR_IN                BIT(31)
index 697656aad6ea73eeb58f1b718d2dff29733383ed..991cab9a7491dc52f4ac817f90e703e0ee0f6f48 100644 (file)
@@ -524,6 +524,11 @@ static int dwc3_ep0_handle_endpoint(struct dwc3 *dwc,
                ret = __dwc3_gadget_ep_set_halt(dep, set, true);
                if (ret)
                        return -EINVAL;
+
+               /* ClearFeature(Halt) may need delayed status */
+               if (!set && (dep->flags & DWC3_EP_END_TRANSFER_PENDING))
+                       return USB_GADGET_DELAYED_STATUS;
+
                break;
        default:
                return -EINVAL;
@@ -1049,6 +1054,17 @@ static void dwc3_ep0_do_control_status(struct dwc3 *dwc,
        __dwc3_ep0_do_control_status(dwc, dep);
 }
 
+void dwc3_ep0_send_delayed_status(struct dwc3 *dwc)
+{
+       unsigned int direction = !dwc->ep0_expect_in;
+
+       if (dwc->ep0state != EP0_STATUS_PHASE)
+               return;
+
+       dwc->delayed_status = false;
+       __dwc3_ep0_do_control_status(dwc, dwc->eps[direction]);
+}
+
 static void dwc3_ep0_end_control_data(struct dwc3 *dwc, struct dwc3_ep *dep)
 {
        struct dwc3_gadget_ep_cmd_params params;
index 6c8e495455d466ff7d35bfb74c78736679ccb898..1d65de84464d592e27d7e0d60ee7988f8f428c3f 100644 (file)
@@ -1719,6 +1719,18 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol)
                        return 0;
                }
 
+               dwc3_stop_active_transfer(dep, true, true);
+
+               list_for_each_entry_safe(req, tmp, &dep->started_list, list)
+                       dwc3_gadget_move_cancelled_request(req);
+
+               if (dep->flags & DWC3_EP_END_TRANSFER_PENDING) {
+                       dep->flags |= DWC3_EP_PENDING_CLEAR_STALL;
+                       return 0;
+               }
+
+               dwc3_gadget_ep_cleanup_cancelled_requests(dep);
+
                ret = dwc3_send_clear_stall_ep_cmd(dep);
                if (ret) {
                        dev_err(dwc->dev, "failed to clear STALL on %s\n",
@@ -1728,14 +1740,6 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol)
 
                dep->flags &= ~(DWC3_EP_STALL | DWC3_EP_WEDGE);
 
-               dwc3_stop_active_transfer(dep, true, true);
-
-               list_for_each_entry_safe(req, tmp, &dep->started_list, list)
-                       dwc3_gadget_move_cancelled_request(req);
-
-               if (!(dep->flags & DWC3_EP_END_TRANSFER_PENDING))
-                       dwc3_gadget_ep_cleanup_cancelled_requests(dep);
-
                if ((dep->flags & DWC3_EP_DELAY_START) &&
                    !usb_endpoint_xfer_isoc(dep->endpoint.desc))
                        __dwc3_gadget_kick_transfer(dep);
@@ -2767,6 +2771,26 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
                        dep->flags &= ~DWC3_EP_END_TRANSFER_PENDING;
                        dep->flags &= ~DWC3_EP_TRANSFER_STARTED;
                        dwc3_gadget_ep_cleanup_cancelled_requests(dep);
+
+                       if (dep->flags & DWC3_EP_PENDING_CLEAR_STALL) {
+                               struct dwc3 *dwc = dep->dwc;
+
+                               dep->flags &= ~DWC3_EP_PENDING_CLEAR_STALL;
+                               if (dwc3_send_clear_stall_ep_cmd(dep)) {
+                                       struct usb_ep *ep0 = &dwc->eps[0]->endpoint;
+
+                                       dev_err(dwc->dev, "failed to clear STALL on %s\n",
+                                               dep->name);
+                                       if (dwc->delayed_status)
+                                               __dwc3_gadget_ep0_set_halt(ep0, 1);
+                                       return;
+                               }
+
+                               dep->flags &= ~(DWC3_EP_STALL | DWC3_EP_WEDGE);
+                               if (dwc->delayed_status)
+                                       dwc3_ep0_send_delayed_status(dwc);
+                       }
+
                        if ((dep->flags & DWC3_EP_DELAY_START) &&
                            !usb_endpoint_xfer_isoc(dep->endpoint.desc))
                                __dwc3_gadget_kick_transfer(dep);
index 5faf4d1249e024a9455be9656246bc4204c80d8e..f207e59c7d03cb658c328f64ffe14065432e33b1 100644 (file)
@@ -111,6 +111,7 @@ int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value);
 int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request,
                gfp_t gfp_flags);
 int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol);
+void dwc3_ep0_send_delayed_status(struct dwc3 *dwc);
 
 /**
  * dwc3_gadget_ep_get_transfer_index - Gets transfer index from HW