drivers: udc_dwc2: Workaround endpoint disable race condition

Endpoint disable function is racing against bus traffic. If the bus
traffic leads to transfer completion immediately before the endpoint
disable is executed, then the transfer complete interrupt will remain
set when the endpoint is disabled. For OUT endpoints this leads to "No
buffer for ep" errors, while for IN endpoint this can lead to double
buffer pull which causes assertion failure.

The proper solution would be to change endpoint disable to not actually
wait for the individual events (and accept that the endpoint may not
need to be disabled because the transfer can just finish). For the time
being workaround the issue by clearing XferCompl bit on endpoint
disable.

Signed-off-by: Tomasz Moń <tomasz.mon@nordicsemi.no>
This commit is contained in:
Tomasz Moń 2024-11-29 13:44:03 +01:00 committed by Benjamin Cabé
commit a26d3c2f2e

View file

@ -1402,6 +1402,19 @@ static void udc_dwc2_ep_disable(const struct device *dev,
return;
}
/* FIXME: This function needs to be changed to not synchronously wait
* for the events to happen because the actions here are racing against
* the USB host packets. It is possible that the IN token or OUT DATA
* gets sent shortly before this function disables the endpoint. If this
* happens, the XferCompl would be set and driver will incorrectly think
* that either:
* * never queued transfer finished, OR
* * transfer queued in incompisoin handler finished (before it really
* does and then it'll "double"-finish when it actually finishes)
*
* For the time being XferCompl is cleared as a workaround.
*/
if (USB_EP_DIR_IS_OUT(cfg->addr)) {
mem_addr_t dctl_reg, gintsts_reg, doepint_reg;
uint32_t dctl;
@ -1440,7 +1453,7 @@ static void udc_dwc2_ep_disable(const struct device *dev,
}
/* Clear Endpoint Disabled interrupt */
sys_write32(USB_DWC2_DIEPINT_EPDISBLD, doepint_reg);
sys_write32(USB_DWC2_DOEPINT_EPDISBLD | USB_DWC2_DOEPINT_XFERCOMPL, doepint_reg);
dctl |= USB_DWC2_DCTL_CGOUTNAK;
sys_write32(dctl, dctl_reg);
@ -1464,7 +1477,7 @@ static void udc_dwc2_ep_disable(const struct device *dev,
dwc2_wait_for_bit(dev, diepint_reg, USB_DWC2_DIEPINT_EPDISBLD);
/* Clear Endpoint Disabled interrupt */
sys_write32(USB_DWC2_DIEPINT_EPDISBLD, diepint_reg);
sys_write32(USB_DWC2_DIEPINT_EPDISBLD | USB_DWC2_DIEPINT_XFERCOMPL, diepint_reg);
/* TODO: Read DIEPTSIZn here? Programming Guide suggest it to
* let application know how many bytes of interrupted transfer