diff --git a/drivers/usb/udc/udc_dwc2.c b/drivers/usb/udc/udc_dwc2.c index 01c0f3040ce..d1fc555d839 100644 --- a/drivers/usb/udc/udc_dwc2.c +++ b/drivers/usb/udc/udc_dwc2.c @@ -25,6 +25,8 @@ LOG_MODULE_REGISTER(udc_dwc2, CONFIG_UDC_DRIVER_LOG_LEVEL); #include "udc_dwc2_vendor_quirks.h" enum dwc2_drv_event_type { + /* USB connection speed determined after bus reset */ + DWC2_DRV_EVT_ENUM_DONE, /* Trigger next transfer, must not be used for control OUT */ DWC2_DRV_EVT_XFER, /* Setup packet received */ @@ -203,6 +205,20 @@ static void dwc2_wait_for_bit(const struct device *dev, } } +static inline bool dwc2_in_completer_mode(const struct device *dev) +{ + struct udc_dwc2_data *const priv = udc_get_private(dev); + + return !IS_ENABLED(CONFIG_UDC_DWC2_DMA) || !priv->bufferdma; +} + +static inline bool dwc2_in_buffer_dma_mode(const struct device *dev) +{ + struct udc_dwc2_data *const priv = udc_get_private(dev); + + return IS_ENABLED(CONFIG_UDC_DWC2_DMA) && priv->bufferdma; +} + /* Get DOEPCTLn or DIEPCTLn register address */ static mem_addr_t dwc2_get_dxepctl_reg(const struct device *dev, const uint8_t ep) { @@ -250,6 +266,28 @@ static uint32_t dwc2_get_iept_xfersize(const struct device *dev, const uint32_t } } +static uint32_t dwc2_get_oept_pktctn(const struct device *dev, const uint32_t idx) +{ + struct udc_dwc2_data *const priv = udc_get_private(dev); + + if (idx == 0) { + return usb_dwc2_get_doeptsiz0_pktcnt(UINT32_MAX); + } else { + return priv->max_pktcnt; + } +} + +static uint32_t dwc2_get_oept_xfersize(const struct device *dev, const uint32_t idx) +{ + struct udc_dwc2_data *const priv = udc_get_private(dev); + + if (idx == 0) { + return usb_dwc2_get_doeptsiz0_xfersize(UINT32_MAX); + } else { + return priv->max_xfersize; + } +} + static void dwc2_flush_rx_fifo(const struct device *dev) { struct usb_dwc2_reg *const base = dwc2_get_base(dev); @@ -347,6 +385,42 @@ static bool dwc2_ep_is_iso(struct udc_ep_config *const cfg) return (cfg->attributes & USB_EP_TRANSFER_TYPE_MASK) == USB_EP_TYPE_ISO; } +static int dwc2_ctrl_feed_dout(const struct device *dev, const size_t length) +{ + struct udc_dwc2_data *const priv = udc_get_private(dev); + struct udc_ep_config *ep_cfg = udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT); + struct net_buf *buf; + size_t alloc_len = length; + + if (dwc2_in_buffer_dma_mode(dev)) { + /* Control OUT buffers must be multiple of bMaxPacketSize0 */ + alloc_len = ROUND_UP(length, 64); + } + + buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, alloc_len); + if (buf == NULL) { + return -ENOMEM; + } + + udc_buf_put(ep_cfg, buf); + k_event_post(&priv->xfer_new, BIT(16)); + k_event_post(&priv->drv_evt, BIT(DWC2_DRV_EVT_XFER)); + + return 0; +} + +static void dwc2_ensure_setup_ready(const struct device *dev) +{ + if (dwc2_in_completer_mode(dev)) { + /* In Completer mode EP0 can always receive SETUP data */ + return; + } + + if (!udc_buf_peek(dev, USB_CONTROL_EP_OUT)) { + dwc2_ctrl_feed_dout(dev, 8); + } +} + static bool dwc2_dma_buffer_ok_to_use(const struct device *dev, void *buf, uint32_t xfersize, uint16_t mps) { @@ -400,7 +474,7 @@ static int dwc2_tx_fifo_write(const struct device *dev, len = buf->len; } - if (!priv->bufferdma) { + if (dwc2_in_completer_mode(dev)) { uint32_t spcavail = dwc2_ftx_avail(dev, ep_idx); uint32_t spcperpkt = ROUND_UP(udc_mps_ep_size(cfg), 4); uint32_t max_pkts, max_transfer; @@ -465,7 +539,7 @@ static int dwc2_tx_fifo_write(const struct device *dev, usb_dwc2_set_dieptsizn_pktcnt(pktcnt) | usb_dwc2_set_dieptsizn_xfersize(len), dieptsiz_reg); - if (priv->bufferdma) { + if (dwc2_in_buffer_dma_mode(dev)) { if (!dwc2_dma_buffer_ok_to_use(dev, buf->data, len, cfg->mps)) { /* Cannot continue unless buffer is bounced. Device will * cease to function. Is fatal error appropriate here? @@ -508,7 +582,7 @@ static int dwc2_tx_fifo_write(const struct device *dev, /* Clear IN Endpoint NAK Effective interrupt in case it was set */ sys_write32(USB_DWC2_DIEPINT_INEPNAKEFF, diepint_reg); - if (!priv->bufferdma) { + if (dwc2_in_completer_mode(dev)) { const uint8_t *src = buf->data; while (pktcnt > 0) { @@ -582,18 +656,23 @@ static void dwc2_prep_rx(const struct device *dev, struct net_buf *buf, uint8_t ep_idx = USB_EP_GET_IDX(cfg->addr); mem_addr_t doeptsiz_reg = (mem_addr_t)&base->out_ep[ep_idx].doeptsiz; mem_addr_t doepctl_reg = dwc2_get_dxepctl_reg(dev, ep_idx); + uint32_t max_xfersize, max_pktcnt; + const uint32_t addnl = USB_MPS_ADDITIONAL_TRANSACTIONS(cfg->mps); uint32_t pktcnt; uint32_t doeptsiz; uint32_t doepctl; uint32_t xfersize; + max_xfersize = dwc2_get_oept_xfersize(dev, ep_idx); + max_pktcnt = dwc2_get_oept_pktctn(dev, ep_idx); + /* Clear NAK and set endpoint enable */ doepctl = sys_read32(doepctl_reg); doepctl |= USB_DWC2_DEPCTL_EPENA | USB_DWC2_DEPCTL_CNAK; if (dwc2_ep_is_iso(cfg)) { xfersize = USB_MPS_TO_TPL(cfg->mps); - pktcnt = 1 + USB_MPS_ADDITIONAL_TRANSACTIONS(cfg->mps); + pktcnt = 1 + addnl; if (xfersize > net_buf_tailroom(buf)) { LOG_ERR("ISO RX buffer too small"); @@ -610,36 +689,41 @@ static void dwc2_prep_rx(const struct device *dev, struct net_buf *buf, xfersize = net_buf_tailroom(buf); /* Do as many packets in a single transfer as possible */ - if (xfersize > priv->max_xfersize) { - xfersize = ROUND_DOWN(priv->max_xfersize, USB_MPS_TO_TPL(cfg->mps)); + if (xfersize > max_xfersize) { + xfersize = ROUND_DOWN(max_xfersize, USB_MPS_TO_TPL(cfg->mps)); } pktcnt = DIV_ROUND_UP(xfersize, USB_MPS_EP_SIZE(cfg->mps)); } - pktcnt = DIV_ROUND_UP(xfersize, udc_mps_ep_size(cfg)); + if (pktcnt > max_pktcnt) { + pktcnt = ROUND_DOWN(max_pktcnt, (1 + addnl)); + xfersize = pktcnt * udc_mps_ep_size(cfg); + } + doeptsiz = usb_dwc2_set_doeptsizn_pktcnt(pktcnt) | usb_dwc2_set_doeptsizn_xfersize(xfersize); if (cfg->addr == USB_CONTROL_EP_OUT) { - /* Use 1 to allow 8 byte long buffers for SETUP data */ - doeptsiz |= (1 << USB_DWC2_DOEPTSIZ0_SUPCNT_POS); + doeptsiz |= (3 << USB_DWC2_DOEPTSIZ0_SUPCNT_POS); } priv->rx_siz[ep_idx] = doeptsiz; sys_write32(doeptsiz, doeptsiz_reg); - if (priv->bufferdma) { - if (!dwc2_dma_buffer_ok_to_use(dev, buf->data, xfersize, cfg->mps)) { + if (dwc2_in_buffer_dma_mode(dev)) { + void *data = net_buf_tail(buf); + + if (!dwc2_dma_buffer_ok_to_use(dev, data, xfersize, cfg->mps)) { /* Cannot continue unless buffer is bounced. Device will * cease to function. Is fatal error appropriate here? */ return; } - sys_write32((uint32_t)buf->data, + sys_write32((uint32_t)data, (mem_addr_t)&base->out_ep[ep_idx].doepdma); - sys_cache_data_invd_range(buf->data, xfersize); + sys_cache_data_invd_range(data, xfersize); } sys_write32(doepctl, doepctl_reg); @@ -662,6 +746,26 @@ static void dwc2_handle_xfer_next(const struct device *dev, } else { int err = dwc2_tx_fifo_write(dev, cfg, buf); + if (cfg->addr == USB_CONTROL_EP_IN) { + /* Feed a buffer for the next setup packet after arming + * IN endpoint with the data. This is necessary both in + * IN Data Stage (Control Read Transfer) and IN Status + * Stage (Control Write Transfers and Control Transfers + * without Data Stage). + * + * The buffer must be fed here in Buffer DMA mode to + * allow receiving premature SETUP. This inevitably does + * automatically arm the buffer for OUT Status Stage. + * + * The buffer MUST NOT be fed here in Completer mode to + * avoid race condition where the next Control Write + * Transfer Data Stage is received into the buffer. + */ + if (dwc2_in_buffer_dma_mode(dev)) { + dwc2_ctrl_feed_dout(dev, 8); + } + } + if (err) { LOG_ERR("Failed to start write to TX FIFO, ep 0x%02x (err: %d)", cfg->addr, err); @@ -678,32 +782,35 @@ static void dwc2_handle_xfer_next(const struct device *dev, udc_ep_set_busy(dev, cfg->addr, true); } -static int dwc2_ctrl_feed_dout(const struct device *dev, const size_t length) +static int dwc2_handle_evt_setup(const struct device *dev) { - struct udc_ep_config *ep_cfg = udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT); + struct udc_dwc2_data *const priv = udc_get_private(dev); struct net_buf *buf; + int err; - buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, length); - if (buf == NULL) { - return -ENOMEM; - } + /* In Completer mode SETUP data is received without preparing endpoint 0 + * transfer beforehand. In Buffer DMA the SETUP can be copied to any EP0 + * OUT buffer. If there is any buffer queued, it is obsolete now. + */ + k_event_clear(&priv->xfer_finished, BIT(0) | BIT(16)); - udc_buf_put(ep_cfg, buf); - dwc2_prep_rx(dev, buf, ep_cfg); - LOG_DBG("feed buf %p", buf); + buf = udc_buf_get_all(dev, USB_CONTROL_EP_OUT); + if (buf) { + net_buf_unref(buf); + } - return 0; -} + buf = udc_buf_get_all(dev, USB_CONTROL_EP_IN); + if (buf) { + net_buf_unref(buf); + } -static int dwc2_handle_evt_setup(const struct device *dev) -{ - struct udc_dwc2_data *const priv = udc_get_private(dev); - struct net_buf *buf; - int err; + udc_ep_set_busy(dev, USB_CONTROL_EP_OUT, false); + udc_ep_set_busy(dev, USB_CONTROL_EP_IN, false); - buf = udc_buf_get(dev, USB_CONTROL_EP_OUT); + /* Allocate buffer and copy received SETUP for processing */ + buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, 8); if (buf == NULL) { - LOG_ERR("No buffer queued for control ep"); + LOG_ERR("No buffer available for control ep"); return -ENODATA; } @@ -720,30 +827,17 @@ static int dwc2_handle_evt_setup(const struct device *dev) /* Allocate and feed buffer for data OUT stage */ LOG_DBG("s:%p|feed for -out-", buf); - /* Allocate at least 8 bytes in case the host decides to send - * SETUP DATA instead of OUT DATA packet. - */ - err = dwc2_ctrl_feed_dout(dev, MAX(udc_data_stage_length(buf), 8)); + err = dwc2_ctrl_feed_dout(dev, udc_data_stage_length(buf)); if (err == -ENOMEM) { err = udc_submit_ep_event(dev, buf, err); } } else if (udc_ctrl_stage_is_data_in(dev)) { LOG_DBG("s:%p|feed for -in-status", buf); - err = dwc2_ctrl_feed_dout(dev, 8); - if (err == -ENOMEM) { - err = udc_submit_ep_event(dev, buf, err); - } - err = udc_ctrl_submit_s_in_status(dev); } else { LOG_DBG("s:%p|feed >setup", buf); - err = dwc2_ctrl_feed_dout(dev, 8); - if (err == -ENOMEM) { - err = udc_submit_ep_event(dev, buf, err); - } - err = udc_ctrl_submit_s_status(dev); } @@ -753,6 +847,7 @@ static int dwc2_handle_evt_setup(const struct device *dev) static inline int dwc2_handle_evt_dout(const struct device *dev, struct udc_ep_config *const cfg) { + struct udc_data *data = dev->data; struct net_buf *buf; int err = 0; @@ -769,25 +864,14 @@ static inline int dwc2_handle_evt_dout(const struct device *dev, /* s-in-status finished */ LOG_DBG("dout:%p| status, feed >s", buf); - /* Feed a buffer for the next setup packet */ - err = dwc2_ctrl_feed_dout(dev, 8); - if (err == -ENOMEM) { - err = udc_submit_ep_event(dev, buf, err); - } - /* Status stage finished, notify upper layer */ udc_ctrl_submit_status(dev, buf); - } else { - /* - * For all other cases we feed with a buffer - * large enough for setup packet. - */ - LOG_DBG("dout:%p| data, feed >s", buf); - err = dwc2_ctrl_feed_dout(dev, 8); - if (err == -ENOMEM) { - err = udc_submit_ep_event(dev, buf, err); + if (dwc2_in_buffer_dma_mode(dev)) { + dwc2_ctrl_feed_dout(dev, 8); } + } else { + LOG_DBG("dout:%p| data, feed >s", buf); } /* Update to next stage of control transfer */ @@ -796,6 +880,13 @@ static inline int dwc2_handle_evt_dout(const struct device *dev, if (udc_ctrl_stage_is_status_in(dev)) { err = udc_ctrl_submit_s_out_status(dev, buf); } + + if (data->stage == CTRL_PIPE_STAGE_ERROR) { + /* Allow receiving next SETUP. USB stack won't queue any + * buffer because it has no clue about this transfer. + */ + dwc2_ensure_setup_ready(dev); + } } else { err = udc_submit_ep_event(dev, buf, 0); } @@ -839,10 +930,12 @@ static int dwc2_handle_evt_din(const struct device *dev, udc_ctrl_update_stage(dev, buf); if (udc_ctrl_stage_is_status_out(dev)) { - /* - * IN transfer finished, release buffer, - * control OUT buffer should be already fed. - */ + if (dwc2_in_completer_mode(dev)) { + /* Allow OUT status stage */ + dwc2_ctrl_feed_dout(dev, 8); + } + + /* IN transfer finished, release buffer. */ net_buf_unref(buf); } @@ -1178,7 +1271,7 @@ static int dwc2_set_dedicated_fifo(const struct device *dev, tmp = *diepctl & ~USB_DWC2_DEPCTL_TXFNUM_MASK; reqdep = DIV_ROUND_UP(udc_mps_ep_size(cfg), 4U); - if (priv->bufferdma) { + if (dwc2_in_buffer_dma_mode(dev)) { /* In DMA mode, TxFIFO capable of holding 2 packets is enough */ reqdep *= MIN(2, (1 + addnl)); } else { @@ -1269,13 +1362,7 @@ static int dwc2_ep_control_enable(const struct device *dev, dxepctl0 |= USB_DWC2_DEPCTL_USBACTEP; if (cfg->addr == USB_CONTROL_EP_OUT) { - int ret; - dwc2_flush_rx_fifo(dev); - ret = dwc2_ctrl_feed_dout(dev, 8); - if (ret) { - return ret; - } } else { dwc2_flush_tx_fifo(dev, 0); } @@ -1560,6 +1647,9 @@ static int udc_dwc2_ep_set_halt(const struct device *dev, LOG_DBG("Set halt ep 0x%02x", cfg->addr); if (ep_idx != 0) { cfg->stat.halted = true; + } else { + /* Data/Status stage is STALLed, allow receiving next SETUP */ + dwc2_ensure_setup_ready(dev); } return 0; @@ -2256,7 +2346,7 @@ static void dwc2_on_bus_reset(const struct device *dev) } doepmsk = USB_DWC2_DOEPINT_SETUP | USB_DWC2_DOEPINT_XFERCOMPL; - if (priv->bufferdma) { + if (dwc2_in_buffer_dma_mode(dev)) { doepmsk |= USB_DWC2_DOEPINT_STSPHSERCVD; } @@ -2264,7 +2354,7 @@ static void dwc2_on_bus_reset(const struct device *dev) sys_set_bits((mem_addr_t)&base->diepmsk, USB_DWC2_DIEPINT_XFERCOMPL); /* Software has to handle RxFLvl interrupt only in Completer mode */ - if (!priv->bufferdma) { + if (dwc2_in_completer_mode(dev)) { sys_set_bits((mem_addr_t)&base->gintmsk, USB_DWC2_GINTSTS_RXFLVL); } @@ -2285,6 +2375,8 @@ static void dwc2_handle_enumdone(const struct device *dev) dsts = sys_read32((mem_addr_t)&base->dsts); priv->enumspd = usb_dwc2_get_dsts_enumspd(dsts); priv->enumdone = 1; + + k_event_post(&priv->drv_evt, BIT(DWC2_DRV_EVT_ENUM_DONE)); } static inline int dwc2_read_fifo_setup(const struct device *dev, uint8_t ep, @@ -2470,7 +2562,7 @@ static inline void dwc2_handle_out_xfercompl(const struct device *dev, } if (!valid) { - if (!priv->bufferdma) { + if (dwc2_in_completer_mode(dev)) { /* RxFlvl added data to net buf, rollback */ net_buf_remove_mem(buf, bcnt); } @@ -2479,12 +2571,12 @@ static inline void dwc2_handle_out_xfercompl(const struct device *dev, } } - if (priv->bufferdma && bcnt) { - sys_cache_data_invd_range(buf->data, bcnt); + if (dwc2_in_buffer_dma_mode(dev) && bcnt) { + sys_cache_data_invd_range(net_buf_tail(buf), bcnt); net_buf_add(buf, bcnt); } - if (!is_iso && (bcnt % udc_mps_ep_size(ep_cfg)) == 0 && + if (!is_iso && bcnt && (bcnt % udc_mps_ep_size(ep_cfg)) == 0 && net_buf_tailroom(buf)) { dwc2_prep_rx(dev, buf, ep_cfg); } else { @@ -2523,7 +2615,8 @@ static inline void dwc2_handle_oepint(const struct device *dev) /* StupPktRcvd is not enabled for interrupt, but must be checked * when XferComp hits to determine if SETUP token was received. */ - if (priv->bufferdma && (status & USB_DWC2_DOEPINT_XFERCOMPL) && + if (dwc2_in_buffer_dma_mode(dev) && + (status & USB_DWC2_DOEPINT_XFERCOMPL) && (doepint & USB_DWC2_DOEPINT_STUPPKTRCVD)) { uint32_t addr; @@ -2984,6 +3077,12 @@ static ALWAYS_INLINE void dwc2_thread_handler(void *const arg) config->irq_enable_func(dev); } + if (evt & BIT(DWC2_DRV_EVT_ENUM_DONE)) { + k_event_clear(&priv->drv_evt, BIT(DWC2_DRV_EVT_ENUM_DONE)); + + dwc2_ensure_setup_ready(dev); + } + udc_unlock_internal(dev); }