[PATCH] usb: dwc3: Fix latency of DSTS while receiving wakeup event

From: Prashanth K
Date: Tue Jul 30 2024 - 08:52:43 EST


When operating in High-Speed, it is observed that DSTS[USBLNKST] doesn't
update link state immediately after receiving the wakeup interrupt. Since
wakeup event handler calls the resume callbacks, there is a chance that
function drivers can perform an ep queue. Which in turn tries to perform
remote wakeup from send_gadget_ep_cmd(), this happens because DSTS[[21:18]
wasn't updated to U0 yet. It is observed that the latency of DSTS can be
in order of milli-seconds. Hence update the dwc->link_state from evtinfo,
and use this variable to prevent calling remote wakup unnecessarily.

Fixes: ecba9bc9946b ("usb: dwc3: gadget: Check for L1/L2/U3 for Start Transfer")
Cc: <stable@xxxxxxxxxxxxxxx>
Signed-off-by: Prashanth K <quic_prashk@xxxxxxxxxxx>
---
drivers/usb/dwc3/gadget.c | 25 ++++++++++++++++++-------
1 file changed, 18 insertions(+), 7 deletions(-)

diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 89fc690fdf34..3b55285118b0 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -328,7 +328,8 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd,
}

if (DWC3_DEPCMD_CMD(cmd) == DWC3_DEPCMD_STARTTRANSFER) {
- int link_state;
+ int link_state;
+ bool remote_wakeup = false;

/*
* Initiate remote wakeup if the link state is in U3 when
@@ -339,15 +340,26 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd,
link_state = dwc3_gadget_get_link_state(dwc);
switch (link_state) {
case DWC3_LINK_STATE_U2:
- if (dwc->gadget->speed >= USB_SPEED_SUPER)
+ if (dwc->gadget->speed < USB_SPEED_SUPER)
+ remote_wakeup = true;
+ break;
+ case DWC3_LINK_STATE_U3:
+ /*
+ * In HS, DSTS can take few milliseconds to update linkstate bits,
+ * so rely on dwc->link_state to identify whether gadget woke up.
+ * Don't issue remote wakuep again if link is already in U0.
+ */
+ if (dwc->link_state == DWC3_LINK_STATE_U0)
break;

- fallthrough;
- case DWC3_LINK_STATE_U3:
+ remote_wakeup = true;
+ break;
+ }
+
+ if (remote_wakeup) {
ret = __dwc3_gadget_wakeup(dwc, false);
dev_WARN_ONCE(dwc->dev, ret, "wakeup failed --> %d\n",
ret);
- break;
}
}

@@ -4214,6 +4226,7 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc)
static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc, unsigned int evtinfo)
{
dwc->suspended = false;
+ dwc->link_state = evtinfo & DWC3_LINK_STATE_MASK;

/*
* TODO take core out of low power mode when that's
@@ -4225,8 +4238,6 @@ static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc, unsigned int evtinfo)
dwc->gadget_driver->resume(dwc->gadget);
spin_lock(&dwc->lock);
}
-
- dwc->link_state = evtinfo & DWC3_LINK_STATE_MASK;
}

static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc,
--
2.25.1