diff --git a/drivers/usb/udc/udc_common.c b/drivers/usb/udc/udc_common.c index e46ff96776504..162dcb9d15410 100644 --- a/drivers/usb/udc/udc_common.c +++ b/drivers/usb/udc/udc_common.c @@ -269,41 +269,11 @@ static bool ep_check_config(const struct device *dev, return true; } -static void ep_update_mps(const struct device *dev, - const struct udc_ep_config *const cfg, - const uint8_t attributes, - uint16_t *const mps) -{ - struct udc_device_caps caps = udc_caps(dev); - const uint16_t spec_int_mps = caps.hs ? 1024 : 64; - const uint16_t spec_bulk_mps = caps.hs ? 512 : 64; - - /* - * TODO: It does not take into account the actual speed of the - * bus after the RESET. Should be fixed/improved when the driver - * for high speed controller are ported. - */ - switch (ep_attrib_get_transfer(attributes)) { - case USB_EP_TYPE_BULK: - *mps = MIN(cfg->caps.mps, spec_bulk_mps); - break; - case USB_EP_TYPE_INTERRUPT: - *mps = MIN(cfg->caps.mps, spec_int_mps); - break; - case USB_EP_TYPE_CONTROL: - __fallthrough; - case USB_EP_TYPE_ISO: - __fallthrough; - default: - return; - } -} - -int udc_ep_try_config(const struct device *dev, - const uint8_t ep, - const uint8_t attributes, - uint16_t *const mps, - const uint8_t interval) +int udc_ep_claim_config(const struct device *dev, + const uint8_t ep, + const uint8_t attributes, + const uint16_t mps, + const uint8_t interval) { const struct udc_api *api = dev->api; struct udc_ep_config *cfg; @@ -316,9 +286,10 @@ int udc_ep_try_config(const struct device *dev, api->lock(dev); - ret = ep_check_config(dev, cfg, ep, attributes, *mps, interval); - if (ret == true && *mps == 0U) { - ep_update_mps(dev, cfg, attributes, mps); + ret = ep_check_config(dev, cfg, ep, attributes, mps, interval); + if (ret) { + cfg->m_mps = MAX(cfg->m_mps, mps); + cfg->stat.claimed = 1; } api->unlock(dev); @@ -326,6 +297,117 @@ int udc_ep_try_config(const struct device *dev, return (ret == false) ? -ENOTSUP : 0; } +void udc_get_eps_fifo_size(const struct device *dev, + size_t *const rx_size, + size_t *const tx_size, + uint16_t *const out_mps, + uint16_t *const in_mps) +{ + struct udc_data *const data = dev->data; + uint16_t t_out_mps = 0; + uint16_t t_in_mps = 0; + size_t t_rx_size = 0; + size_t t_tx_size = 0; + + for (unsigned int i = 0; i < ARRAY_SIZE(data->ep_lut); i++) { + struct udc_ep_config *const cfg = data->ep_lut[i]; + uint16_t tpl; + + if (cfg == NULL || !cfg->stat.claimed) { + continue; + } + + tpl = USB_MPS_TO_TPL(cfg->m_mps); + + if (USB_EP_DIR_IS_IN(cfg->addr)) { + t_tx_size += ROUND_UP(tpl, 4); + if (tpl > USB_MPS_TO_TPL(t_in_mps)) { + t_in_mps = cfg->m_mps; + } + } else { + t_rx_size += ROUND_UP(tpl, 4); + if (tpl > USB_MPS_TO_TPL(t_out_mps)) { + t_out_mps = cfg->m_mps; + } + } + } + + if (rx_size != NULL) { + *rx_size = t_rx_size; + } + + if (tx_size != NULL) { + *tx_size = t_tx_size; + } + + if (out_mps != NULL) { + *out_mps = t_out_mps; + } + + if (in_mps != NULL) { + *in_mps = t_in_mps; + } +} + +void udc_get_claimed_eps(const struct device *dev, + uint8_t *const out_eps, uint8_t *const in_eps) +{ + struct udc_data *const data = dev->data; + uint16_t t_out_eps = 0; + uint16_t t_in_eps = 0; + + for (unsigned int i = 0; i < ARRAY_SIZE(data->ep_lut); i++) { + struct udc_ep_config *const cfg = data->ep_lut[i]; + + if (cfg == NULL || !cfg->stat.claimed) { + continue; + } + + if (USB_EP_DIR_IS_IN(cfg->addr)) { + t_in_eps++; + } else { + t_out_eps++; + } + } + + if (out_eps != NULL) { + *out_eps = t_out_eps; + } + + if (in_eps != NULL) { + *in_eps = t_in_eps; + } +} + +static void udc_release_eps(const struct device *dev) +{ + struct udc_data *const data = dev->data; + + for (unsigned int i = 0; i < ARRAY_SIZE(data->ep_lut); i++) { + struct udc_ep_config *const cfg = data->ep_lut[i]; + + if (cfg != NULL) { + cfg->stat.claimed = 0; + cfg->m_mps = 0; + cfg->mps = 0; + } + } +} + +static inline void udc_log_fifo_size(const struct device *dev) +{ + size_t rx_size, tx_size; + uint16_t out_mps, in_mps; + uint8_t out_eps, in_eps; + + udc_get_eps_fifo_size(dev, &rx_size, &tx_size, &out_mps, &in_mps); + LOG_DBG("FIFO size RX %u TX %u, MPS OUT 0x%04x IN 0x%04x", + rx_size, tx_size, out_mps, in_mps); + + udc_get_claimed_eps(dev, &out_eps, &in_eps); + LOG_DBG("Number of used endpoints OUT %u IN %u", out_eps, in_eps); +} + int udc_ep_enable_internal(const struct device *dev, const uint8_t ep, const uint8_t attributes, @@ -351,6 +433,11 @@ int udc_ep_enable_internal(const struct device *dev, return -ENODEV; } + if (USB_EP_GET_IDX(ep) && USB_MPS_TO_TPL(mps) > USB_MPS_TO_TPL(cfg->m_mps)) { + LOG_ERR("Endpoint 0x%02x MPS %u is too large", cfg->addr, mps); + return -EINVAL; + } + cfg->attributes = attributes; cfg->mps = mps; cfg->interval = interval; @@ -714,6 +801,9 @@ int udc_enable(const struct device *dev) } data->stage = CTRL_PIPE_STAGE_SETUP; + if (UDC_COMMON_LOG_LEVEL == LOG_LEVEL_DBG) { + udc_log_fifo_size(dev); + } ret = api->enable(dev); if (ret == 0) { @@ -766,6 +856,7 @@ int udc_init(const struct device *dev, goto udc_init_error; } + udc_release_eps(dev); data->event_cb = event_cb; data->event_ctx = event_ctx; diff --git a/drivers/usb/udc/udc_common.h b/drivers/usb/udc/udc_common.h index ed6712e95ae89..3cdf6204ac64e 100644 --- a/drivers/usb/udc/udc_common.h +++ b/drivers/usb/udc/udc_common.h @@ -179,6 +179,50 @@ static inline void udc_submit_sof_event(const struct device *dev) #define udc_submit_sof_event(dev) ARG_UNUSED(dev) #endif +/** + * @brief Estimate required FIFO sizes. + * + * This function estimates the required FIFO sizes for the IN and OUT + * endpoints, as well as determining the largest IN and OUT endpoint sizes + * based on the maximum packet size value provided by the higher layer during + * initialization. + * + * All numbers are without control endpoints. Drivers must take this into + * account. + * + * This function provides the correct numbers if the configurations have + * identical endpoint mapping. Otherwise, it provides worst-case RX and TX FIFO + * sizes. + * + * @param[in] dev Pointer to device struct of the driver instance + * @param[in] rx_size Required RX FIFO size + * @param[in] tx_size Required TX FIFO size + * @param[in] out_mps Largest OUT MPS + * @param[in] in_mps Largest IN MPS + */ +void udc_get_eps_fifo_size(const struct device *dev, + size_t *const rx_size, + size_t *const tx_size, + uint16_t *const out_mps, + uint16_t *const in_mps); + +/** + * @brief Get number of claimed endpoints. + * + * This function provides the correct numbers if the configurations have + * identical endpoint mapping. Otherwise, it provides worst-case numbers. + * + * All numbers are without control endpoints. Drivers must take this into + * account. + * + * @param[in] dev Pointer to device struct of the driver instance + * @param[in] out_eps Number of claimed OUT endpoints + * @param[in] in_eps Number of claimed IN endpoints + */ +void udc_get_claimed_eps(const struct device *dev, + uint8_t *const out_eps, + uint8_t *const in_eps); + /** * @brief Helper function to enable endpoint. * diff --git a/drivers/usb/udc/udc_dwc2.c b/drivers/usb/udc/udc_dwc2.c index c83e6671c29b8..d2b145d5aa0a2 100644 --- a/drivers/usb/udc/udc_dwc2.c +++ b/drivers/usb/udc/udc_dwc2.c @@ -44,18 +44,20 @@ enum dwc2_drv_event_type { DWC2_DRV_EVT_HIBERNATION_EXIT_HOST_RESUME, }; -/* Minimum RX FIFO size in 32-bit words considering the largest used OUT packet - * of 512 bytes. The value must be adjusted according to the number of OUT - * endpoints. - */ -#define UDC_DWC2_GRXFSIZ_FS_DEFAULT (15U + 512U/4U) -/* Default Rx FIFO size in 32-bit words calculated to support High-Speed with: - * * 1 control endpoint in Completer/Buffer DMA mode: 13 locations - * * Global OUT NAK: 1 location - * * Space for 3 * 1024 packets: ((1024/4) + 1) * 3 = 774 locations - * Driver adds 2 locations for each OUT endpoint to this value. +/* Default Rx FIFO size in 32-bit words calculated in Completer/Buffer DMA mode + * + * (5 * number of control endpoints + 8) + + * ((largest endpoints size / 4) + 1 for status) * n + + * (2 * number of OUT endpoints) + 1 for global NAK + * + * For example, largest endpoint size is 1024, + * there are 3 OUT endpoints plus control OUT endpoint, + * number of extra transactions to support high_bandwidth endpoints is 2: + * + * 13 + (1024/4 + 1) * (1 + 2) + 2 * 4 + 1 = 793 (3172 bytes) */ -#define UDC_DWC2_GRXFSIZ_HS_DEFAULT (13 + 1 + 774) +#define UDC_DWC2_GRXFSIZ_DEFAULT(ep_size, extra, out_eps) \ + (13 + ((ep_size) / 4 + 1) * (1 + (extra)) + (out_eps + 1) * 2 + 1) /* TX FIFO0 depth in 32-bit words (used by control IN endpoint) * Try 2 * bMaxPacketSize0 to allow simultaneous operation with a fallback to @@ -66,9 +68,6 @@ enum dwc2_drv_event_type { /* Get Data FIFO access register */ #define UDC_DWC2_EP_FIFO(base, idx) ((mem_addr_t)base + 0x1000 * (idx + 1)) -/* Percentage limit of how much SPRAM can be allocated for RxFIFO */ -#define MAX_RXFIFO_GDFIFO_PERCENTAGE 25 - enum dwc2_suspend_type { DWC2_SUSPEND_NO_POWER_SAVING, DWC2_SUSPEND_HIBERNATION, @@ -1391,7 +1390,11 @@ static int dwc2_set_dedicated_fifo(const struct device *dev, /* Keep everything but FIFO number */ tmp = *diepctl & ~USB_DWC2_DEPCTL_TXFNUM_MASK; - reqdep = DIV_ROUND_UP(udc_mps_ep_size(cfg), 4U); + /* Use the maximum possible MPS value to ensure that the alternate + * setting does not result in too small memory window being allocated + * and locked because a higher FIFO is still in use. + */ + reqdep = DIV_ROUND_UP(USB_MPS_EP_SIZE(cfg->m_mps), 4U); if (dwc2_in_buffer_dma_mode(dev)) { /* In DMA mode, TxFIFO capable of holding 2 packets is enough */ reqdep *= MIN(2, (1 + addnl)); @@ -1555,7 +1558,7 @@ static int udc_dwc2_ep_activate(const struct device *dev, return -EINVAL; } - if (USB_EP_DIR_IS_IN(cfg->addr) && udc_mps_ep_size(cfg) != 0U) { + if (USB_EP_DIR_IS_IN(cfg->addr) && USB_MPS_EP_SIZE(cfg->m_mps) != 0U) { int ret = dwc2_set_dedicated_fifo(dev, cfg, &dxepctl); if (ret) { @@ -2220,34 +2223,25 @@ static int udc_dwc2_init_controller(const struct device *dev) if (priv->dynfifosizing) { uint32_t gnptxfsiz; uint32_t default_depth; - uint32_t spram_size; - uint32_t max_rxfifo; - - /* Get available SPRAM size and calculate max allocatable RX fifo size */ - val = sys_read32((mem_addr_t)&base->gdfifocfg); - spram_size = usb_dwc2_get_gdfifocfg_gdfifocfg(val); - max_rxfifo = ((spram_size * MAX_RXFIFO_GDFIFO_PERCENTAGE) / 100); - - /* TODO: For proper runtime FIFO sizing UDC driver would have to - * have prior knowledge of the USB configurations. Only with the - * prior knowledge, the driver will be able to fairly distribute - * available resources. For the time being just use different - * defaults based on maximum configured PHY speed, but this has - * to be revised if e.g. thresholding support would be necessary - * on some target. - */ - if (hs_phy) { - default_depth = UDC_DWC2_GRXFSIZ_HS_DEFAULT; - } else { - default_depth = UDC_DWC2_GRXFSIZ_FS_DEFAULT; - } - default_depth += priv->outeps * 2U; + uint16_t out_ep_size; + uint8_t num_out_eps; + uint8_t extra; + uint16_t mps; + + udc_get_eps_fifo_size(dev, NULL, NULL, &mps, NULL); + udc_get_claimed_eps(dev, &num_out_eps, NULL); + extra = USB_MPS_ADDITIONAL_TRANSACTIONS(mps); + out_ep_size = USB_MPS_EP_SIZE(mps); + + LOG_DBG("Largest OUT MPS 0x%04x, TPL %ux %u bytes", + mps, extra + 1, out_ep_size); + default_depth = UDC_DWC2_GRXFSIZ_DEFAULT(out_ep_size, extra, num_out_eps); /* Driver does not dynamically resize RxFIFO so there is no need * to store reset value. Read the reset value and make sure that * the programmed value is not greater than what driver sets. */ - priv->rxfifo_depth = MIN(MIN(priv->rxfifo_depth, default_depth), max_rxfifo); + priv->rxfifo_depth = MIN(priv->rxfifo_depth, default_depth); sys_write32(usb_dwc2_set_grxfsiz(priv->rxfifo_depth), grxfsiz_reg); /* Set TxFIFO 0 depth */ diff --git a/drivers/usb/udc/udc_rpi_pico.c b/drivers/usb/udc/udc_rpi_pico.c index b0fb2fcf44f10..b4c7388cdc48c 100644 --- a/drivers/usb/udc/udc_rpi_pico.c +++ b/drivers/usb/udc/udc_rpi_pico.c @@ -1118,8 +1118,21 @@ static int udc_rpi_pico_init(const struct device *dev) { const struct rpi_pico_config *config = dev->config; const struct pinctrl_dev_config *const pcfg = config->pcfg; + size_t rx_size; + size_t tx_size; int err; + /* + * There are 3840 bytes available for endpoint buffers, including + * control endpoints, in DPSRAM. This means that 3712 bytes + * (58 x 64 byte blocks) are available for interface endpoints. + */ + udc_get_eps_fifo_size(dev, &rx_size, &tx_size, NULL, NULL); + if ((rx_size + tx_size) > 3712U) { + LOG_WRN("Required memory size %u + %u exceeds available DPRAM space %u", + rx_size, tx_size, 3712U); + } + if (pcfg != NULL) { err = pinctrl_apply_state(pcfg, PINCTRL_STATE_DEFAULT); if (err) { diff --git a/include/zephyr/drivers/usb/udc.h b/include/zephyr/drivers/usb/udc.h index 9b1f25e78e093..1481ec29cc505 100644 --- a/include/zephyr/drivers/usb/udc.h +++ b/include/zephyr/drivers/usb/udc.h @@ -88,6 +88,8 @@ struct udc_ep_caps { * USB device controller endpoint status */ struct udc_ep_stat { + /** Endpoint is claimed */ + uint32_t claimed : 1; /** Endpoint is enabled */ uint32_t enabled : 1; /** Endpoint is halted (returning STALL PID) */ @@ -114,6 +116,8 @@ struct udc_ep_config { struct udc_ep_caps caps; /** Endpoint status */ struct udc_ep_stat stat; + /** Largest MPS within all interface settings */ + uint16_t m_mps; /** Endpoint address */ uint8_t addr; /** Endpoint attributes */ @@ -522,14 +526,12 @@ static inline int udc_host_wakeup(const struct device *dev) } /** - * @brief Try an endpoint configuration. + * @brief Test and claim an endpoint configuration. * - * Try an endpoint configuration based on endpoint descriptor. - * This function may modify wMaxPacketSize descriptor fields - * of the endpoint. All properties of the descriptor, - * such as direction, and transfer type, should be set correctly. - * If wMaxPacketSize value is zero, it will be - * updated to maximum buffer size of the endpoint. + * Test and claim an endpoint configuration based on endpoint descriptor. + * All properties of the descriptor, such as wMaxPacketSize, direction, and + * transfer type, should be set correctly. It does not claim an endpoint + * exclusively, as it may be called for alternate settings. * * @param[in] dev Pointer to device struct of the driver instance * @param[in] ep Endpoint address (same as bEndpointAddress) @@ -542,11 +544,11 @@ static inline int udc_host_wakeup(const struct device *dev) * @retval -ENOTSUP endpoint configuration not supported * @retval -ENODEV no endpoints available */ -int udc_ep_try_config(const struct device *dev, - const uint8_t ep, - const uint8_t attributes, - uint16_t *const mps, - const uint8_t interval); +int udc_ep_claim_config(const struct device *dev, + const uint8_t ep, + const uint8_t attributes, + const uint16_t mps, + const uint8_t interval); /** * @brief Configure and enable endpoint. diff --git a/subsys/usb/device_next/usbd_init.c b/subsys/usb/device_next/usbd_init.c index 021fc8f2fca5a..96c72ad7171e1 100644 --- a/subsys/usb/device_next/usbd_init.c +++ b/subsys/usb/device_next/usbd_init.c @@ -28,7 +28,7 @@ static int assign_ep_addr(const struct device *dev, int ret = -ENODEV; for (unsigned int idx = 1; idx < 16U; idx++) { - uint16_t mps = sys_le16_to_cpu(ed->wMaxPacketSize); + const uint16_t mps = sys_le16_to_cpu(ed->wMaxPacketSize); uint8_t ep; if (USB_EP_DIR_IS_IN(ed->bEndpointAddress)) { @@ -43,14 +43,13 @@ static int assign_ep_addr(const struct device *dev, } - ret = udc_ep_try_config(dev, ep, - ed->bmAttributes, &mps, - ed->bInterval); + ret = udc_ep_claim_config(dev, ep, + ed->bmAttributes, mps, + ed->bInterval); if (ret == 0) { LOG_DBG("ep 0x%02x -> 0x%02x", ed->bEndpointAddress, ep); ed->bEndpointAddress = ep; - ed->wMaxPacketSize = sys_cpu_to_le16(mps); usbd_ep_bm_set(class_ep_bm, ed->bEndpointAddress); usbd_ep_bm_set(config_ep_bm, ed->bEndpointAddress); diff --git a/tests/drivers/udc/src/main.c b/tests/drivers/udc/src/main.c index 716f41440d2fb..056450dbdc3cf 100644 --- a/tests/drivers/udc/src/main.c +++ b/tests/drivers/udc/src/main.c @@ -101,26 +101,10 @@ static void test_udc_ep_try_config(const struct device *dev, uint16_t mps = sys_le16_to_cpu(ed->wMaxPacketSize); int err; - err = udc_ep_try_config(dev, ed->bEndpointAddress, - ed->bmAttributes, &mps, - ed->bInterval); + err = udc_ep_claim_config(dev, ed->bEndpointAddress, + ed->bmAttributes, mps, + ed->bInterval); zassert_equal(err, 0, "Failed to test endpoint configuration"); - - if (ed->bmAttributes == USB_EP_TYPE_CONTROL || - ed->bmAttributes == USB_EP_TYPE_ISO) { - /* - * Skip subsequent test since udc_ep_try_config() does not - * update mps argument for control and iso endpoints. - */ - return; - } - - mps = 0; - err = udc_ep_try_config(dev, ed->bEndpointAddress, - ed->bmAttributes, &mps, - ed->bInterval); - zassert_equal(err, 0, "Failed to test endpoint configuration"); - zassert_not_equal(mps, 0, "Failed to test endpoint configuration"); } static void test_udc_ep_enable(const struct device *dev, @@ -360,6 +344,7 @@ static void test_udc_ep_mps(uint8_t type) .wMaxPacketSize = sys_cpu_to_le16(0), .bInterval = 0, }; + struct udc_device_caps caps; const struct device *dev; uint16_t supported = 0; int err; @@ -373,21 +358,43 @@ static void test_udc_ep_mps(uint8_t type) err = udc_enable(dev); zassert_ok(err, "Failed to enable UDC driver"); + caps = udc_caps(dev); if (type == USB_EP_TYPE_INTERRUPT) { ed.bInterval = 1; + if (caps.hs) { + supported = 1024; + } else { + supported = 64; + } + } + + if (type == USB_EP_TYPE_BULK) { + if (caps.hs) { + supported = 512; + } else { + supported = 64; + } + } + + if (type == USB_EP_TYPE_ISO) { + if (caps.hs) { + supported = 1024; + } else { + supported = 1023; + } } for (uint8_t i = 1; i < 16U; i++) { - err = udc_ep_try_config(dev, i, - ed.bmAttributes, &supported, - ed.bInterval); + err = udc_ep_claim_config(dev, i, + ed.bmAttributes, supported, + ed.bInterval); if (!err) { ed.bEndpointAddress = i; break; } } - zassert_ok(err, "Failed to determine MPS"); + zassert_ok(err, "Failed to determine OUT MPS"); for (int i = 0; i < ARRAY_SIZE(mps); i++) { if (mps[i] > supported) { @@ -396,8 +403,26 @@ static void test_udc_ep_mps(uint8_t type) ed.wMaxPacketSize = sys_cpu_to_le16(mps[i]); test_udc_ep_api(dev, &ed); + } + + for (uint8_t i = 1; i < 16U; i++) { + err = udc_ep_claim_config(dev, i | USB_EP_DIR_IN, + ed.bmAttributes, supported, + ed.bInterval); + if (!err) { + ed.bEndpointAddress = i | USB_EP_DIR_IN; + break; + } + } - ed.bEndpointAddress |= USB_EP_DIR_IN; + zassert_ok(err, "Failed to determine IN MPS"); + + for (int i = 0; i < ARRAY_SIZE(mps); i++) { + if (mps[i] > supported) { + continue; + } + + ed.wMaxPacketSize = sys_cpu_to_le16(mps[i]); test_udc_ep_api(dev, &ed); }