From d3adb48dcd010810d69a1b3f2776c28e1f56a51d Mon Sep 17 00:00:00 2001 From: Mathieu Choplain Date: Thu, 2 Oct 2025 15:48:51 +0200 Subject: [PATCH 1/5] drivers: usb: udc: stm32: turn EP0 max packet size into a constant The EP0 max packet size was de facto a constant because its value was the same regardless of which USB IP was in use. However, it was stored as part of the instance configuration anyways which is wasteful and slower. Create new "UDC_STM32_EP0_MAX_PACKET_SIZE" driver-level constant with which all usage of the per-instance configuration field is replaced. Signed-off-by: Mathieu Choplain --- drivers/usb/udc/udc_stm32.c | 46 +++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/drivers/usb/udc/udc_stm32.c b/drivers/usb/udc/udc_stm32.c index 479eb54c2672f..c30506c8d5f7f 100644 --- a/drivers/usb/udc/udc_stm32.c +++ b/drivers/usb/udc/udc_stm32.c @@ -136,6 +136,13 @@ static const int syscfg_otg_hs_phy_clk[] = { }; #endif +/* + * Hardcode EP0 max packet size (bMaxPacketSize0) to 64, + * which is the maximum allowed by the USB Specification + * and supported by all STM32 USB controllers. + */ +#define UDC_STM32_EP0_MAX_PACKET_SIZE 64U + struct udc_stm32_data { PCD_HandleTypeDef pcd; const struct device *dev; @@ -152,7 +159,6 @@ struct udc_stm32_config { uint32_t num_endpoints; uint32_t pma_offset; uint32_t dram_size; - uint16_t ep0_mps; uint16_t ep_mps; /* PHY selected for use by instance */ uint32_t selected_phy; @@ -188,19 +194,20 @@ void HAL_PCD_ResetCallback(PCD_HandleTypeDef *hpcd) { struct udc_stm32_data *priv = hpcd2data(hpcd); const struct device *dev = priv->dev; - const struct udc_stm32_config *cfg = dev->config; struct udc_ep_config *ep; /* Re-Enable control endpoints */ ep = udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT); if (ep && ep->stat.enabled) { - HAL_PCD_EP_Open(&priv->pcd, USB_CONTROL_EP_OUT, cfg->ep0_mps, + HAL_PCD_EP_Open(&priv->pcd, USB_CONTROL_EP_OUT, + UDC_STM32_EP0_MAX_PACKET_SIZE, EP_TYPE_CTRL); } ep = udc_get_ep_cfg(dev, USB_CONTROL_EP_IN); if (ep && ep->stat.enabled) { - HAL_PCD_EP_Open(&priv->pcd, USB_CONTROL_EP_IN, cfg->ep0_mps, + HAL_PCD_EP_Open(&priv->pcd, USB_CONTROL_EP_IN, + UDC_STM32_EP0_MAX_PACKET_SIZE, EP_TYPE_CTRL); } @@ -288,7 +295,6 @@ static int udc_stm32_tx(const struct device *dev, struct udc_ep_config *epcfg, struct net_buf *buf) { struct udc_stm32_data *priv = udc_get_private(dev); - const struct udc_stm32_config *cfg = dev->config; uint8_t *data; uint32_t len; HAL_StatusTypeDef status; @@ -302,7 +308,7 @@ static int udc_stm32_tx(const struct device *dev, struct udc_ep_config *epcfg, len = buf->len; if (epcfg->addr == USB_CONTROL_EP_IN) { - len = MIN(cfg->ep0_mps, buf->len); + len = MIN(UDC_STM32_EP0_MAX_PACKET_SIZE, buf->len); } buf->data += len; @@ -444,8 +450,7 @@ static void handle_msg_data_in(struct udc_stm32_data *priv, uint8_t epnum) } if (ep == USB_CONTROL_EP_IN && buf->len) { - const struct udc_stm32_config *cfg = dev->config; - uint32_t len = MIN(cfg->ep0_mps, buf->len); + uint32_t len = MIN(UDC_STM32_EP0_MAX_PACKET_SIZE, buf->len); HAL_PCD_EP_Transmit(&priv->pcd, ep, buf->data, len); @@ -643,9 +648,8 @@ static void udc_stm32_mem_init(const struct device *dev) LOG_DBG("DRAM size: %ub", cfg->dram_size); - if (cfg->ep_mps % 4 || cfg->ep0_mps % 4) { - LOG_ERR("Not a 32-bit word multiple: ep0(%u)|ep(%u)", - cfg->ep0_mps, cfg->ep_mps); + if (cfg->ep_mps % 4) { + LOG_ERR("Not a 32-bit word multiple: ep(%u)", cfg->ep_mps); return; } @@ -657,8 +661,8 @@ static void udc_stm32_mem_init(const struct device *dev) priv->occupied_mem = words * 4; /* For EP0 TX, reserve only one MPS */ - HAL_PCDEx_SetTxFiFo(&priv->pcd, 0, cfg->ep0_mps / 4); - priv->occupied_mem += cfg->ep0_mps; + HAL_PCDEx_SetTxFiFo(&priv->pcd, 0, UDC_STM32_EP0_MAX_PACKET_SIZE / 4); + priv->occupied_mem += UDC_STM32_EP0_MAX_PACKET_SIZE; /* Reset TX allocs */ for (unsigned int i = 1U; i < cfg->num_endpoints; i++) { @@ -705,7 +709,6 @@ static int udc_stm32_ep_mem_config(const struct device *dev, static int udc_stm32_enable(const struct device *dev) { struct udc_stm32_data *priv = udc_get_private(dev); - const struct udc_stm32_config *cfg = dev->config; HAL_StatusTypeDef status; int ret; @@ -720,14 +723,16 @@ static int udc_stm32_enable(const struct device *dev) } ret = udc_ep_enable_internal(dev, USB_CONTROL_EP_OUT, - USB_EP_TYPE_CONTROL, cfg->ep0_mps, 0); + USB_EP_TYPE_CONTROL, + UDC_STM32_EP0_MAX_PACKET_SIZE, 0); if (ret) { LOG_ERR("Failed enabling ep 0x%02x", USB_CONTROL_EP_OUT); return ret; } ret |= udc_ep_enable_internal(dev, USB_CONTROL_EP_IN, - USB_EP_TYPE_CONTROL, cfg->ep0_mps, 0); + USB_EP_TYPE_CONTROL, + UDC_STM32_EP0_MAX_PACKET_SIZE, 0); if (ret) { LOG_ERR("Failed enabling ep 0x%02x", USB_CONTROL_EP_IN); return ret; @@ -1068,12 +1073,10 @@ static const struct udc_api udc_stm32_api = { #define USB_NUM_BIDIR_ENDPOINTS DT_INST_PROP(0, num_bidir_endpoints) #if defined(USB) || defined(USB_DRD_FS) -#define EP0_MPS 64U #define EP_MPS 64U #define USB_BTABLE_SIZE (8 * USB_NUM_BIDIR_ENDPOINTS) #define USB_RAM_SIZE DT_INST_PROP(0, ram_size) #else /* USB_OTG_FS */ -#define EP0_MPS USB_OTG_MAX_EP0_SIZE #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs) #define EP_MPS USB_OTG_HS_MAX_PACKET_SIZE #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otgfs) || DT_HAS_COMPAT_STATUS_OKAY(st_stm32_usb) @@ -1094,7 +1097,6 @@ static const struct udc_stm32_config udc0_cfg = { .num_endpoints = USB_NUM_BIDIR_ENDPOINTS, .dram_size = USB_RAM_SIZE, .pma_offset = USB_BTABLE_SIZE, - .ep0_mps = EP0_MPS, .ep_mps = EP_MPS, .selected_phy = UDC_STM32_NODE_PHY_ITFACE(DT_DRV_INST(0)), .selected_speed = UDC_STM32_NODE_SPEED(DT_DRV_INST(0)), @@ -1109,7 +1111,7 @@ static void priv_pcd_prepare(const struct device *dev) /* Default values */ priv->pcd.Init.dev_endpoints = cfg->num_endpoints; - priv->pcd.Init.ep0_mps = cfg->ep0_mps; + priv->pcd.Init.ep0_mps = UDC_STM32_EP0_MAX_PACKET_SIZE; priv->pcd.Init.speed = cfg->selected_speed; /* Per controller/Phy values */ @@ -1332,7 +1334,7 @@ static int udc_stm32_driver_init0(const struct device *dev) ep_cfg_out[i].caps.out = 1; if (i == 0) { ep_cfg_out[i].caps.control = 1; - ep_cfg_out[i].caps.mps = cfg->ep0_mps; + ep_cfg_out[i].caps.mps = UDC_STM32_EP0_MAX_PACKET_SIZE; } else { ep_cfg_out[i].caps.bulk = 1; ep_cfg_out[i].caps.interrupt = 1; @@ -1352,7 +1354,7 @@ static int udc_stm32_driver_init0(const struct device *dev) ep_cfg_in[i].caps.in = 1; if (i == 0) { ep_cfg_in[i].caps.control = 1; - ep_cfg_in[i].caps.mps = cfg->ep0_mps; + ep_cfg_in[i].caps.mps = UDC_STM32_EP0_MAX_PACKET_SIZE; } else { ep_cfg_in[i].caps.bulk = 1; ep_cfg_in[i].caps.interrupt = 1; From 6c349f01e54547cf851b0f82d92465951d6407a3 Mon Sep 17 00:00:00 2001 From: Mathieu Choplain Date: Thu, 2 Oct 2025 16:52:46 +0200 Subject: [PATCH 2/5] drivers: usb: udc: stm32: clean up handling of USB buffer table The ST USB controller (compatible 'st,stm32-usb') is fitted with private SRAM called the Private Memory Area or PMA. This is primarily used to hold data transfered over USB, but it also contains the so-called 'buffer table' (a.k.a. BTABLE) which holds the base address and size of buffers in the PMA allocated to each endpoint. The BTABLE is placed by the driver at the start of the PMA and occupies a fixed size ('USB_BTABLE_SIZE'), which was stored in the driver configuration field 'pma_offset'. This mechanism is unused on non-ST USB controllers from STM32 microcontrollers, but USB_BTABLE_SIZE is still defined (to a dummy value) and stored in the 'pma_offset' field which becomes unused. Remove the 'USB_BTABLE_SIZE' definition and the 'pma_offset' field from the driver configuration, and update the ST USB controller-specific verison of 'udc_stm32_mem_init' to derive the BTABLE size from the number of endpoints that the controller has instead. Signed-off-by: Mathieu Choplain --- drivers/usb/udc/udc_stm32.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/usb/udc/udc_stm32.c b/drivers/usb/udc/udc_stm32.c index c30506c8d5f7f..352de639a5ea4 100644 --- a/drivers/usb/udc/udc_stm32.c +++ b/drivers/usb/udc/udc_stm32.c @@ -157,7 +157,6 @@ struct udc_stm32_data { struct udc_stm32_config { uint32_t num_endpoints; - uint32_t pma_offset; uint32_t dram_size; uint16_t ep_mps; /* PHY selected for use by instance */ @@ -608,7 +607,12 @@ static inline void udc_stm32_mem_init(const struct device *dev) struct udc_stm32_data *priv = udc_get_private(dev); const struct udc_stm32_config *cfg = dev->config; - priv->occupied_mem = cfg->pma_offset; + /** + * Endpoint configuration table is placed at the + * beginning of Private Memory Area and consumes + * 8 bytes for each endpoint. + */ + priv->occupied_mem = (8 * cfg->num_endpoints); } static int udc_stm32_ep_mem_config(const struct device *dev, @@ -1074,7 +1078,6 @@ static const struct udc_api udc_stm32_api = { #if defined(USB) || defined(USB_DRD_FS) #define EP_MPS 64U -#define USB_BTABLE_SIZE (8 * USB_NUM_BIDIR_ENDPOINTS) #define USB_RAM_SIZE DT_INST_PROP(0, ram_size) #else /* USB_OTG_FS */ #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs) @@ -1083,7 +1086,6 @@ static const struct udc_api udc_stm32_api = { #define EP_MPS USB_OTG_FS_MAX_PACKET_SIZE #endif #define USB_RAM_SIZE DT_INST_PROP(0, ram_size) -#define USB_BTABLE_SIZE 0 #endif /* USB */ static struct udc_stm32_data udc0_priv; @@ -1096,7 +1098,6 @@ static struct udc_data udc0_data = { static const struct udc_stm32_config udc0_cfg = { .num_endpoints = USB_NUM_BIDIR_ENDPOINTS, .dram_size = USB_RAM_SIZE, - .pma_offset = USB_BTABLE_SIZE, .ep_mps = EP_MPS, .selected_phy = UDC_STM32_NODE_PHY_ITFACE(DT_DRV_INST(0)), .selected_speed = UDC_STM32_NODE_SPEED(DT_DRV_INST(0)), From 88aab2b9d2547cba01f1bca952f6a723f768d9ac Mon Sep 17 00:00:00 2001 From: Mathieu Choplain Date: Thu, 2 Oct 2025 17:30:39 +0200 Subject: [PATCH 3/5] drivers: usb: udc: stm32: accept non-word-aligned MaxPacketSize or FIFOs STM32 OTG USB controllers use word-addressable RAM with a 32-bit word size so all allocations from the USB SRAM must be 32-bit aligned. The driver did not accept unaligned values of wMaxPacketSize, and FIFO allocation code was implicitly expecting FIFO sizes to be aligned as well (since it allocated "bytes / 4" without rounding up). Update driver to accept values of wMaxPacketSize that aren't word-aligned and to allocate properly sized FIFOs sizes when an unaligned size has been requested. Signed-off-by: Mathieu Choplain --- drivers/usb/udc/udc_stm32.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/usb/udc/udc_stm32.c b/drivers/usb/udc/udc_stm32.c index 352de639a5ea4..9fb650dd24530 100644 --- a/drivers/usb/udc/udc_stm32.c +++ b/drivers/usb/udc/udc_stm32.c @@ -652,20 +652,15 @@ static void udc_stm32_mem_init(const struct device *dev) LOG_DBG("DRAM size: %ub", cfg->dram_size); - if (cfg->ep_mps % 4) { - LOG_ERR("Not a 32-bit word multiple: ep(%u)", cfg->ep_mps); - return; - } - /* The documentation is not clear at all about RX FiFo size requirement, * 160 has been selected through trial and error. */ - words = MAX(160, cfg->ep_mps / 4); + words = MAX(160, DIV_ROUND_UP(cfg->ep_mps, 4U)); HAL_PCDEx_SetRxFiFo(&priv->pcd, words); priv->occupied_mem = words * 4; /* For EP0 TX, reserve only one MPS */ - HAL_PCDEx_SetTxFiFo(&priv->pcd, 0, UDC_STM32_EP0_MAX_PACKET_SIZE / 4); + HAL_PCDEx_SetTxFiFo(&priv->pcd, 0, DIV_ROUND_UP(UDC_STM32_EP0_MAX_PACKET_SIZE, 4U)); priv->occupied_mem += UDC_STM32_EP0_MAX_PACKET_SIZE; /* Reset TX allocs */ @@ -686,7 +681,7 @@ static int udc_stm32_ep_mem_config(const struct device *dev, return 0; } - words = MIN(udc_mps_ep_size(ep), cfg->ep_mps) / 4; + words = DIV_ROUND_UP(MIN(udc_mps_ep_size(ep), cfg->ep_mps), 4U); words = (words <= 64) ? words * 2 : words; if (!enable) { From 8940344e4a0cdffb6f2550ec5a2bd9db4c4335ba Mon Sep 17 00:00:00 2001 From: Mathieu Choplain Date: Thu, 2 Oct 2025 17:41:49 +0200 Subject: [PATCH 4/5] drivers: usb: udc: stm32: allow EP max packet size up to HW capabilities The maximal packet size (for non-control endpoints) was obtained by the driver from HAL definitions which appear to not properly reflect hardware capabilities. Update driver to allow endpoints with wMaxPacketSize up to the maximal value allowed by the USB Specification depending on operation mode, since all STM32 USB controllers always support these values. Also move the EP max packet size field in the 'struct udc_stm32_config' to avoid implicit padding and add a documentation comment. Signed-off-by: Mathieu Choplain --- drivers/usb/udc/udc_stm32.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/drivers/usb/udc/udc_stm32.c b/drivers/usb/udc/udc_stm32.c index 9fb650dd24530..3a58b049b2d16 100644 --- a/drivers/usb/udc/udc_stm32.c +++ b/drivers/usb/udc/udc_stm32.c @@ -121,6 +121,17 @@ LOG_MODULE_REGISTER(udc_stm32, CONFIG_UDC_DRIVER_LOG_LEVEL); (PCD_SPEED_HIGH_IN_FULL), \ (PCD_SPEED_HIGH)))) +/* + * Returns max packet size allowed for endpoints of 'usb_node' + * + * Hardware always supports the maximal value allowed + * by the USB Specification at a given operating speed: + * 1024 bytes in High-Speed, 1023 bytes in Full-Speed + */ +#define UDC_STM32_NODE_EP_MPS(node_id) \ + ((UDC_STM32_NODE_SPEED(node_id) == PCD_SPEED_HIGH) ? 1024U : 1023U) + + #if DT_HAS_COMPAT_STATUS_OKAY(st_stm32n6_otghs) #define USB_USBPHYC_CR_FSEL_24MHZ USB_USBPHYC_CR_FSEL_1 #endif @@ -158,11 +169,12 @@ struct udc_stm32_data { struct udc_stm32_config { uint32_t num_endpoints; uint32_t dram_size; - uint16_t ep_mps; /* PHY selected for use by instance */ uint32_t selected_phy; /* Speed selected for use by instance */ uint32_t selected_speed; + /* Maximal packet size allowed for endpoints */ + uint16_t ep_mps; }; enum udc_stm32_msg_type { @@ -1070,18 +1082,7 @@ static const struct udc_api udc_stm32_api = { * Kconfig system. */ #define USB_NUM_BIDIR_ENDPOINTS DT_INST_PROP(0, num_bidir_endpoints) - -#if defined(USB) || defined(USB_DRD_FS) -#define EP_MPS 64U -#define USB_RAM_SIZE DT_INST_PROP(0, ram_size) -#else /* USB_OTG_FS */ -#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs) -#define EP_MPS USB_OTG_HS_MAX_PACKET_SIZE -#elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otgfs) || DT_HAS_COMPAT_STATUS_OKAY(st_stm32_usb) -#define EP_MPS USB_OTG_FS_MAX_PACKET_SIZE -#endif -#define USB_RAM_SIZE DT_INST_PROP(0, ram_size) -#endif /* USB */ +#define USB_RAM_SIZE DT_INST_PROP(0, ram_size) static struct udc_stm32_data udc0_priv; @@ -1093,7 +1094,7 @@ static struct udc_data udc0_data = { static const struct udc_stm32_config udc0_cfg = { .num_endpoints = USB_NUM_BIDIR_ENDPOINTS, .dram_size = USB_RAM_SIZE, - .ep_mps = EP_MPS, + .ep_mps = UDC_STM32_NODE_EP_MPS(DT_DRV_INST(0)), .selected_phy = UDC_STM32_NODE_PHY_ITFACE(DT_DRV_INST(0)), .selected_speed = UDC_STM32_NODE_SPEED(DT_DRV_INST(0)), }; From 00d8ee06727068e50247fa6f2a113e979f31a126 Mon Sep 17 00:00:00 2001 From: Mathieu Choplain Date: Fri, 3 Oct 2025 17:56:57 +0200 Subject: [PATCH 5/5] drivers: usb: udc: stm32: configure OTGFS/HS RxFIFO size using Kconfig Create a new Kconfig option allowing to tweak the RxFIFO size on OTG_FS and OTG_HS instances, and replace the old hardcoded method with this new mecanism. The default value of 600 bytes yields a similar size to the the previous hardcoded default of 160 words (= 640 bytes) when combined with the fixed overhead computed by the driver (~56 bytes on OTG_FS with 6 endpoints). Also fix a tiny error in a logging message (DRAM size in bytes, not bits). Signed-off-by: Mathieu Choplain --- drivers/usb/udc/Kconfig.stm32 | 20 ++++++++++++++++++++ drivers/usb/udc/udc_stm32.c | 25 ++++++++++++++++++------- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/drivers/usb/udc/Kconfig.stm32 b/drivers/usb/udc/Kconfig.stm32 index bf580957cc630..11f885f0dfcc3 100644 --- a/drivers/usb/udc/Kconfig.stm32 +++ b/drivers/usb/udc/Kconfig.stm32 @@ -36,6 +36,26 @@ config UDC_STM32_MAX_QMESSAGES help Maximum number of messages for handling of STM32 USBD ISR events. +config UDC_STM32_OTG_RXFIFO_BASELINE_SIZE + int "Baseline RxFIFO size (in bytes)" + default 600 + depends on DT_HAS_ST_STM32_OTGFS_ENABLED \ + || DT_HAS_ST_STM32_OTGHS_ENABLED + help + Baseline value for RXFIFO size computation + + The OTG_FS and OTG_HS USB controllers use a single "RxFIFO" to hold + data received by all OUT endpoints. The RxFIFO's size is influenced + by various parameters: the optimal value depends on the exact USB + configuration that will be used, which the driver does not know by + the time it has to configure the RxFIFO size. + + The total RxFIFO size will be equal to the value of this option + plus a fixed overhead that the driver can derive from the hardware + configuration (e.g., number of endpoints implemented). + + Refer to STM32 Reference Manuals for more details about the RxFIFO. + config UDC_STM32_CLOCK_CHECK bool "Runtime USB 48MHz clock check" default y if !(SOC_SERIES_STM32F1X || SOC_SERIES_STM32F3X || SOC_SERIES_STM32U5X) diff --git a/drivers/usb/udc/udc_stm32.c b/drivers/usb/udc/udc_stm32.c index 3a58b049b2d16..19c7062759241 100644 --- a/drivers/usb/udc/udc_stm32.c +++ b/drivers/usb/udc/udc_stm32.c @@ -660,16 +660,27 @@ static void udc_stm32_mem_init(const struct device *dev) { struct udc_stm32_data *priv = udc_get_private(dev); const struct udc_stm32_config *cfg = dev->config; - int words; + uint32_t rxfifo_size; /* in words */ - LOG_DBG("DRAM size: %ub", cfg->dram_size); + LOG_DBG("DRAM size: %uB", cfg->dram_size); - /* The documentation is not clear at all about RX FiFo size requirement, - * 160 has been selected through trial and error. + /* + * In addition to the user-provided baseline, RxFIFO should fit: + * - Global OUT NAK (1 word) + * - Received packet information (1 word) + * - Transfer complete status information (2 words per OUT endpoint) + * + * Align user-provided baseline up to 32-bit word size then + * add this "fixed" overhead to obtain the final RxFIFO size. */ - words = MAX(160, DIV_ROUND_UP(cfg->ep_mps, 4U)); - HAL_PCDEx_SetRxFiFo(&priv->pcd, words); - priv->occupied_mem = words * 4; + rxfifo_size = DIV_ROUND_UP(CONFIG_UDC_STM32_OTG_RXFIFO_BASELINE_SIZE, 4U); + rxfifo_size += 2U; /* Global OUT NAK and Rx packet info */ + rxfifo_size += 2U * cfg->num_endpoints; + + LOG_DBG("RxFIFO size: %uB", rxfifo_size * 4U); + + HAL_PCDEx_SetRxFiFo(&priv->pcd, rxfifo_size); + priv->occupied_mem = rxfifo_size * 4U; /* For EP0 TX, reserve only one MPS */ HAL_PCDEx_SetTxFiFo(&priv->pcd, 0, DIV_ROUND_UP(UDC_STM32_EP0_MAX_PACKET_SIZE, 4U));