diff --git a/drivers/usb/udc/udc_dwc2.c b/drivers/usb/udc/udc_dwc2.c index fca8f607ec7..4ef5dc1825c 100644 --- a/drivers/usb/udc/udc_dwc2.c +++ b/drivers/usb/udc/udc_dwc2.c @@ -121,6 +121,7 @@ struct udc_dwc2_data { /* Isochronous endpoint enabled (IN on bits 0-15, OUT on bits 16-31) */ uint32_t iso_enabled; uint16_t iso_in_rearm; + uint16_t iso_out_rearm; uint16_t ep_out_disable; uint16_t ep_out_stall; uint16_t txf_set; @@ -140,6 +141,7 @@ struct udc_dwc2_data { unsigned int enumdone : 1; unsigned int enumspd : 2; unsigned int pending_dout_feed : 1; + unsigned int ignore_ep0_nakeff : 1; enum dwc2_suspend_type suspend_type; /* Number of endpoints including control endpoint */ uint8_t numdeveps; @@ -484,7 +486,6 @@ static int dwc2_tx_fifo_write(const struct device *dev, mem_addr_t dieptsiz_reg = (mem_addr_t)&base->in_ep[ep_idx].dieptsiz; /* TODO: use dwc2_get_dxepctl_reg() */ mem_addr_t diepctl_reg = (mem_addr_t)&base->in_ep[ep_idx].diepctl; - mem_addr_t diepint_reg = (mem_addr_t)&base->in_ep[ep_idx].diepint; uint32_t diepctl; uint32_t max_xfersize, max_pktcnt, pktcnt; @@ -616,9 +617,6 @@ static int dwc2_tx_fifo_write(const struct device *dev, } sys_write32(diepctl, diepctl_reg); - /* Clear IN Endpoint NAK Effective interrupt in case it was set */ - sys_write32(USB_DWC2_DIEPINT_INEPNAKEFF, diepint_reg); - if (dwc2_in_completer_mode(dev)) { const uint8_t *src = buf->data; @@ -810,7 +808,20 @@ static void dwc2_handle_xfer_next(const struct device *dev, if (USB_EP_DIR_IS_OUT(cfg->addr)) { dwc2_prep_rx(dev, buf, cfg); } else { - int err = dwc2_tx_fifo_write(dev, cfg, buf); + int err; + + if (cfg->addr == USB_CONTROL_EP_IN && + udc_ctrl_stage_is_status_in(dev)) { + /* It was observed that EPENA results in INEPNAKEFF + * interrupt which leads to endpoint disable. It is not + * clear how to prevent this without violating sequence + * described in Programming Guide, so just set a flag + * for interrupt handler to ignore it. + */ + priv->ignore_ep0_nakeff = 1; + } + + 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 @@ -861,6 +872,7 @@ static int dwc2_handle_evt_setup(const struct device *dev) * 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. */ + priv->ignore_ep0_nakeff = 0; udc_dwc2_ep_disable(dev, cfg_in, false, true); atomic_and(&priv->xfer_finished, ~(BIT(0) | BIT(16))); @@ -2657,7 +2669,21 @@ static inline void dwc2_handle_iepint(const struct device *dev) } if (status & USB_DWC2_DIEPINT_INEPNAKEFF) { - sys_set_bits(diepctl_reg, USB_DWC2_DEPCTL_EPDIS); + uint32_t diepctl = sys_read32(diepctl_reg); + + if (!(diepctl & USB_DWC2_DEPCTL_NAKSTS)) { + /* Ignore stale NAK effective interrupt */ + } else if (n == 0 && priv->ignore_ep0_nakeff) { + /* Status stage enabled endpoint. NAK will be + * cleared in STSPHSERCVD interrupt. + */ + } else if (diepctl & USB_DWC2_DEPCTL_EPENA) { + diepctl &= ~USB_DWC2_DEPCTL_EPENA; + diepctl |= USB_DWC2_DEPCTL_EPDIS; + sys_write32(diepctl, diepctl_reg); + } else if (priv->iso_in_rearm & (BIT(n))) { + priv->iso_in_rearm &= ~BIT(n); + } } if (status & USB_DWC2_DIEPINT_EPDISBLD) { @@ -2675,6 +2701,13 @@ static inline void dwc2_handle_iepint(const struct device *dev) if ((usb_dwc2_get_depctl_eptype(diepctl) == USB_DWC2_DEPCTL_EPTYPE_ISO) && (priv->iso_in_rearm & BIT(n))) { struct udc_ep_config *cfg = udc_get_ep_cfg(dev, n | USB_EP_DIR_IN); + struct net_buf *buf; + + /* Data is no longer relevant, discard it */ + buf = udc_buf_get(cfg); + if (buf) { + udc_submit_ep_event(dev, buf, 0); + } /* Try to queue next packet before SOF */ dwc2_handle_xfer_next(dev, cfg); @@ -2776,6 +2809,7 @@ static inline void dwc2_handle_oepint(const struct device *dev) while (epint) { uint8_t n = find_lsb_set(epint) - 1; mem_addr_t doepint_reg = (mem_addr_t)&base->out_ep[n].doepint; + mem_addr_t doepctl_reg = (mem_addr_t)&base->out_ep[n].doepctl; uint32_t doepint; uint32_t status; @@ -2828,7 +2862,28 @@ static inline void dwc2_handle_oepint(const struct device *dev) } if (status & USB_DWC2_DOEPINT_EPDISBLD) { + uint32_t doepctl = sys_read32(doepctl_reg); + k_event_post(&priv->ep_disabled, BIT(16 + n)); + + if ((usb_dwc2_get_depctl_eptype(doepctl) == USB_DWC2_DEPCTL_EPTYPE_ISO) && + (priv->iso_out_rearm & BIT(n))) { + struct udc_ep_config *cfg; + struct net_buf *buf; + + cfg = udc_get_ep_cfg(dev, n | USB_EP_DIR_OUT); + + /* Discard disabled transfer buffer */ + buf = udc_buf_get(cfg); + if (buf) { + udc_submit_ep_event(dev, buf, 0); + } + + /* Try to queue next packet before SOF */ + dwc2_handle_xfer_next(dev, cfg); + + priv->iso_out_rearm &= ~BIT(n); + } } epint &= ~BIT(n); @@ -2868,7 +2923,6 @@ static void dwc2_handle_incompisoin(const struct device *dev) /* Check if endpoint didn't receive ISO IN data */ if ((diepctl & mask) == val) { struct udc_ep_config *cfg; - struct net_buf *buf; cfg = udc_get_ep_cfg(dev, i | USB_EP_DIR_IN); __ASSERT_NO_MSG(cfg && cfg->stat.enabled && @@ -2876,13 +2930,7 @@ static void dwc2_handle_incompisoin(const struct device *dev) udc_dwc2_ep_disable(dev, cfg, false, false); - buf = udc_buf_get(cfg); - if (buf) { - /* Data is no longer relevant */ - udc_submit_ep_event(dev, buf, 0); - - rearm |= BIT(i); - } + rearm |= BIT(i); } eps &= ~BIT(i); @@ -2913,6 +2961,7 @@ static void dwc2_handle_incompisoout(const struct device *dev) ((priv->sof_num & 1) ? USB_DWC2_DEPCTL_DPID : 0) | USB_DWC2_DEPCTL_USBACTEP; uint32_t eps = (priv->iso_enabled & 0xFFFF0000UL) >> 16; + uint16_t rearm = 0; while (eps) { uint8_t i = find_lsb_set(eps) - 1; @@ -2924,7 +2973,6 @@ static void dwc2_handle_incompisoout(const struct device *dev) /* Check if endpoint didn't receive ISO OUT data */ if ((doepctl & mask) == val) { struct udc_ep_config *cfg; - struct net_buf *buf; cfg = udc_get_ep_cfg(dev, i); __ASSERT_NO_MSG(cfg && cfg->stat.enabled && @@ -2932,15 +2980,15 @@ static void dwc2_handle_incompisoout(const struct device *dev) udc_dwc2_ep_disable(dev, cfg, false, false); - buf = udc_buf_get(cfg); - if (buf) { - udc_submit_ep_event(dev, buf, 0); - } + rearm |= BIT(i); } eps &= ~BIT(i); } + /* Mark endpoints to re-arm in EPDISBLD handler */ + priv->iso_out_rearm = rearm; + sys_write32(USB_DWC2_GINTSTS_INCOMPISOOUT, gintsts_reg); } @@ -2963,8 +3011,31 @@ static void dwc2_handle_goutnakeff(const struct device *dev) doepctl_reg = (mem_addr_t)&base->out_ep[ep_idx].doepctl; doepctl = sys_read32(doepctl_reg); - /* The application cannot disable control OUT endpoint 0. */ - if (ep_idx != 0) { + if (!(doepctl & USB_DWC2_DEPCTL_EPENA)) { + /* While endpoint was enabled when application requested + * to disable it, there is a race window between setting + * DCTL SGOUTNAK bit and it becoming effective. During + * the window, it is possible for host to actually send + * OUT DATA packet ending the transfer which disables + * the endpoint. + * + * If we arrived here due to INCOMPISOOUT, clearing + * the rearm flag is enough. + */ + if (priv->iso_out_rearm & BIT(ep_idx)) { + priv->iso_out_rearm &= ~BIT(ep_idx); + } + + /* If application requested STALL then set it here, but + * otherwise there's nothing to do. + */ + if (!(priv->ep_out_stall & BIT(ep_idx))) { + continue; + } + } else if (ep_idx != 0) { + /* Only set EPDIS if EPENA is set, but do not set it for + * endpoint 0 which cannot be disabled by application. + */ doepctl |= USB_DWC2_DEPCTL_EPDIS; } diff --git a/samples/subsys/usb/uac2_implicit_feedback/src/main.c b/samples/subsys/usb/uac2_implicit_feedback/src/main.c index b7720fe0331..12abdf4fe68 100644 --- a/samples/subsys/usb/uac2_implicit_feedback/src/main.c +++ b/samples/subsys/usb/uac2_implicit_feedback/src/main.c @@ -93,6 +93,7 @@ static void uac2_terminal_update_cb(const struct device *dev, uint8_t terminal, !ctx->microphone_enabled) { i2s_trigger(ctx->i2s_dev, I2S_DIR_BOTH, I2S_TRIGGER_DROP); ctx->i2s_started = false; + ctx->rx_started = false; ctx->i2s_counter = 0; ctx->plus_ones = ctx->minus_ones = 0; if (ctx->pending_mic_samples) { @@ -172,6 +173,7 @@ static void uac2_data_recv_cb(const struct device *dev, uint8_t terminal, ret = i2s_write(ctx->i2s_dev, buf, size); if (ret < 0) { ctx->i2s_started = false; + ctx->rx_started = false; ctx->i2s_counter = 0; ctx->plus_ones = ctx->minus_ones = 0; if (ctx->pending_mic_samples) { diff --git a/subsys/usb/device_next/class/usbd_hid.c b/subsys/usb/device_next/class/usbd_hid.c index d5476a88ed6..d978aea0d27 100644 --- a/subsys/usb/device_next/class/usbd_hid.c +++ b/subsys/usb/device_next/class/usbd_hid.c @@ -84,19 +84,29 @@ struct hid_device_data { static inline uint8_t hid_get_in_ep(struct usbd_class_data *const c_data) { + struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data); const struct device *dev = usbd_class_get_private(c_data); const struct hid_device_config *dcfg = dev->config; struct usbd_hid_descriptor *desc = dcfg->desc; + if (USBD_SUPPORTS_HIGH_SPEED && usbd_bus_speed(uds_ctx) == USBD_SPEED_HS) { + return desc->hs_in_ep.bEndpointAddress; + } + return desc->in_ep.bEndpointAddress; } static inline uint8_t hid_get_out_ep(struct usbd_class_data *const c_data) { + struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data); const struct device *dev = usbd_class_get_private(c_data); const struct hid_device_config *dcfg = dev->config; struct usbd_hid_descriptor *desc = dcfg->desc; + if (USBD_SUPPORTS_HIGH_SPEED && usbd_bus_speed(uds_ctx) == USBD_SPEED_HS) { + return desc->hs_out_ep.bEndpointAddress; + } + return desc->out_ep.bEndpointAddress; } diff --git a/subsys/usb/device_next/class/usbd_uac2.c b/subsys/usb/device_next/class/usbd_uac2.c index efa3fd6cdf3..3a53b5c5d21 100644 --- a/subsys/usb/device_next/class/usbd_uac2.c +++ b/subsys/usb/device_next/class/usbd_uac2.c @@ -26,9 +26,10 @@ LOG_MODULE_REGISTER(usbd_uac2, CONFIG_USBD_UAC2_LOG_LEVEL); #define COUNT_UAC2_AS_ENDPOINT_BUFFERS(node) \ IF_ENABLED(DT_NODE_HAS_COMPAT(node, zephyr_uac2_audio_streaming), ( \ - + AS_HAS_ISOCHRONOUS_DATA_ENDPOINT(node) + \ - + AS_IS_USB_ISO_IN(node) /* ISO IN double buffering */ + \ - AS_HAS_EXPLICIT_FEEDBACK_ENDPOINT(node))) + + AS_HAS_ISOCHRONOUS_DATA_ENDPOINT(node) \ + + AS_IS_USB_ISO_IN(node) /* ISO IN double buffering */ \ + + AS_IS_USB_ISO_OUT(node) /* ISO OUT double buffering */ \ + + 2 * AS_HAS_EXPLICIT_FEEDBACK_ENDPOINT(node))) #define COUNT_UAC2_EP_BUFFERS(i) \ + DT_PROP(DT_DRV_INST(i), interrupt_endpoint) \ DT_INST_FOREACH_CHILD(i, COUNT_UAC2_AS_ENDPOINT_BUFFERS) @@ -87,6 +88,7 @@ struct uac2_ctx { atomic_t as_queued; atomic_t as_double; uint32_t fb_queued; + uint32_t fb_double; }; /* UAC2 device constant data */ @@ -348,6 +350,7 @@ static void schedule_iso_out_read(struct usbd_class_data *const c_data, const struct uac2_cfg *cfg = dev->config; struct uac2_ctx *ctx = dev->data; struct net_buf *buf; + atomic_t *queued_bits = &ctx->as_queued; void *data_buf; int as_idx = terminal_to_as_interface(dev, terminal); int ret; @@ -364,16 +367,19 @@ static void schedule_iso_out_read(struct usbd_class_data *const c_data, return; } - if (atomic_test_and_set_bit(&ctx->as_queued, as_idx)) { - /* Transfer already queued - do not requeue */ - return; + if (atomic_test_and_set_bit(queued_bits, as_idx)) { + queued_bits = &ctx->as_double; + if (atomic_test_and_set_bit(queued_bits, as_idx)) { + /* Transfer already double queued - nothing to do */ + return; + } } /* Prepare transfer to read audio OUT data from host */ data_buf = ctx->ops->get_recv_buf(dev, terminal, mps, ctx->user_data); if (!data_buf) { LOG_ERR("No data buffer for terminal %d", terminal); - atomic_clear_bit(&ctx->as_queued, as_idx); + atomic_clear_bit(queued_bits, as_idx); return; } @@ -386,7 +392,7 @@ static void schedule_iso_out_read(struct usbd_class_data *const c_data, */ ctx->ops->data_recv_cb(dev, terminal, data_buf, 0, ctx->user_data); - atomic_clear_bit(&ctx->as_queued, as_idx); + atomic_clear_bit(queued_bits, as_idx); return; } @@ -394,7 +400,9 @@ static void schedule_iso_out_read(struct usbd_class_data *const c_data, if (ret) { LOG_ERR("Failed to enqueue net_buf for 0x%02x", ep); net_buf_unref(buf); - atomic_clear_bit(&ctx->as_queued, as_idx); + ctx->ops->data_recv_cb(dev, terminal, + data_buf, 0, ctx->user_data); + atomic_clear_bit(queued_bits, as_idx); } } @@ -434,7 +442,11 @@ static void write_explicit_feedback(struct usbd_class_data *const c_data, LOG_ERR("Failed to enqueue net_buf for 0x%02x", ep); net_buf_unref(buf); } else { - ctx->fb_queued |= BIT(as_idx); + if (ctx->fb_queued & BIT(as_idx)) { + ctx->fb_double |= BIT(as_idx); + } else { + ctx->fb_queued |= BIT(as_idx); + } } } @@ -806,7 +818,17 @@ static int uac2_request(struct usbd_class_data *const c_data, struct net_buf *bu terminal = cfg->as_terminals[as_idx]; if (is_feedback) { - ctx->fb_queued &= ~BIT(as_idx); + bool clear_double = buf->frags; + + if (ctx->fb_queued & BIT(as_idx)) { + ctx->fb_queued &= ~BIT(as_idx); + } else { + clear_double = true; + } + + if (clear_double) { + ctx->fb_double &= ~BIT(as_idx); + } } else if (!atomic_test_and_clear_bit(&ctx->as_queued, as_idx) || buf->frags) { atomic_clear_bit(&ctx->as_double, as_idx); } @@ -814,6 +836,10 @@ static int uac2_request(struct usbd_class_data *const c_data, struct net_buf *bu if (USB_EP_DIR_IS_OUT(ep)) { ctx->ops->data_recv_cb(dev, terminal, buf->__buf, buf->len, ctx->user_data); + if (buf->frags) { + ctx->ops->data_recv_cb(dev, terminal, buf->frags->__buf, + buf->frags->len, ctx->user_data); + } } else if (!is_feedback) { ctx->ops->buf_release_cb(dev, terminal, buf->__buf, ctx->user_data); if (buf->frags) { @@ -869,7 +895,7 @@ static void uac2_sof(struct usbd_class_data *const c_data) * for now to allow faster recovery (i.e. reduce workload to be * done during this frame). */ - if (ctx->fb_queued & BIT(as_idx)) { + if (ctx->fb_queued & ctx->fb_double & BIT(as_idx)) { continue; }