blob: 44957c85156b46efdb4f01dfcac4035429f31817 [file] [log] [blame]
From d97c78a1908e59a1fdbcbece87cd0440b5d7a1f2 Mon Sep 17 00:00:00 2001
From: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
Date: Wed, 2 Sep 2020 18:43:04 -0700
Subject: usb: dwc3: gadget: END_TRANSFER before CLEAR_STALL command
From: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
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 | 1 +
drivers/usb/dwc3/ep0.c | 16 ++++++++++++++++
drivers/usb/dwc3/gadget.c | 40 ++++++++++++++++++++++++++++++++--------
drivers/usb/dwc3/gadget.h | 1 +
4 files changed, 50 insertions(+), 8 deletions(-)
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -710,6 +710,7 @@ struct dwc3_ep {
#define DWC3_EP_IGNORE_NEXT_NOSTREAM BIT(8)
#define DWC3_EP_FORCE_RESTART_STREAM BIT(9)
#define DWC3_EP_FIRST_STREAM_PRIMED BIT(10)
+#define DWC3_EP_PENDING_CLEAR_STALL BIT(11)
/* This last one is specific to EP0 */
#define DWC3_EP0_DIR_IN BIT(31)
--- a/drivers/usb/dwc3/ep0.c
+++ b/drivers/usb/dwc3/ep0.c
@@ -524,6 +524,11 @@ static int dwc3_ep0_handle_endpoint(stru
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(s
__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;
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -1827,6 +1827,18 @@ int __dwc3_gadget_ep_set_halt(struct dwc
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",
@@ -1836,14 +1848,6 @@ int __dwc3_gadget_ep_set_halt(struct dwc
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);
@@ -3003,6 +3007,26 @@ static void dwc3_endpoint_interrupt(stru
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);
--- a/drivers/usb/dwc3/gadget.h
+++ b/drivers/usb/dwc3/gadget.h
@@ -113,6 +113,7 @@ int dwc3_gadget_ep0_set_halt(struct usb_
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