From 4d045f2bd06230669dad73c6c3cb6a47d2fa135d Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 6 Feb 2025 13:43:15 -0500 Subject: [PATCH 1/4] stm32: Implement manual double buffering tx for usbotg It is possible for USB host controllers to send back-to-back IN tokens which only gives the MCU ~3us to queue the next USB packet in the hardware. That can be difficult to do if the MCU has to wake up the task code. The stm32 "usbotg" hardware does not support a builtin generic double buffering transmit capability, but it is possible to load the next packet directly from the irq handler code. This change adds support for queuing the next packet destined for the host so that the USB irq handler can directly load it into the hardware. Signed-off-by: Kevin O'Connor --- src/stm32/Kconfig | 3 +++ src/stm32/usbotg.c | 52 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index a3cb877f18..a8c17b0202 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -166,6 +166,9 @@ config HAVE_STM32_USBFS config HAVE_STM32_USBOTG bool default y if MACH_STM32F2 || MACH_STM32F4 || MACH_STM32F7 || MACH_STM32H7 +config STM32_USB_DOUBLE_BUFFER_TX + bool + default y config HAVE_STM32_CANBUS bool default y if MACH_STM32F1 || MACH_STM32F2 || MACH_STM32F4x5 || MACH_STM32F446 || MACH_STM32F0x2 diff --git a/src/stm32/usbotg.c b/src/stm32/usbotg.c index b2d52456d5..b41daf48ce 100644 --- a/src/stm32/usbotg.c +++ b/src/stm32/usbotg.c @@ -1,6 +1,6 @@ // Hardware interface to "USB OTG (on the go) controller" on stm32 // -// Copyright (C) 2019 Kevin O'Connor +// Copyright (C) 2019-2025 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -119,6 +119,24 @@ fifo_write_packet(uint32_t ep, const uint8_t *src, uint32_t len) return len; } +// Write a packet to a tx fifo (optimized for already aligned data) +static int +fifo_write_packet_fast(uint32_t ep, const uint32_t *src, uint32_t len) +{ + void *fifo = EPFIFO(ep); + USB_OTG_INEndpointTypeDef *epi = EPIN(ep); + uint32_t ctl = epi->DIEPCTL; + if (ctl & USB_OTG_DIEPCTL_EPENA) + return -1; + epi->DIEPINT = USB_OTG_DIEPINT_XFRC; + epi->DIEPTSIZ = len | (1 << USB_OTG_DIEPTSIZ_PKTCNT_Pos); + epi->DIEPCTL = ctl | USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK; + uint32_t i; + for (i=0; i < DIV_ROUND_UP(len, sizeof(uint32_t)); i++) + writel(fifo, src[i]); + return 0; +} + // Read a packet from the rx queue static int_fast8_t fifo_read_packet(uint8_t *dest, uint_fast8_t max_len) @@ -208,6 +226,12 @@ usb_read_bulk_out(void *data, uint_fast8_t max_len) return ret; } +// Storage for "bulk in" transmissions for a kind of manual "double buffering" +static struct { + uint32_t len; + uint32_t buf[USB_CDC_EP_BULK_IN_SIZE / sizeof(uint32_t)]; +} TX_BUF; + int_fast8_t usb_send_bulk_in(void *data, uint_fast8_t len) { @@ -219,10 +243,21 @@ usb_send_bulk_in(void *data, uint_fast8_t len) return len; } if (ctl & USB_OTG_DIEPCTL_EPENA) { - // Wait for space to transmit + if (!CONFIG_STM32_USB_DOUBLE_BUFFER_TX || TX_BUF.len || !len) { + // Wait for space to transmit + OTGD->DAINTMSK |= 1 << USB_CDC_EP_BULK_IN; + usb_irq_enable(); + return -1; + } + // Buffer next packet for transmission from irq handler + len = len > USB_CDC_EP_BULK_IN_SIZE ? USB_CDC_EP_BULK_IN_SIZE : len; + uint32_t blocks = DIV_ROUND_UP(len, sizeof(uint32_t)); + TX_BUF.buf[blocks-1] = 0; + memcpy(TX_BUF.buf, data, len); + TX_BUF.len = len; OTGD->DAINTMSK |= 1 << USB_CDC_EP_BULK_IN; usb_irq_enable(); - return -1; + return len; } int_fast8_t ret = fifo_write_packet(USB_CDC_EP_BULK_IN, data, len); usb_irq_enable(); @@ -373,6 +408,8 @@ usb_set_configure(void) | USB_OTG_GRSTCTL_TXFFLSH); while (OTG->GRSTCTL & USB_OTG_GRSTCTL_TXFFLSH) ; + if (CONFIG_STM32_USB_DOUBLE_BUFFER_TX) + TX_BUF.len = 0; usb_irq_enable(); } @@ -401,8 +438,15 @@ OTG_FS_IRQHandler(void) OTGD->DAINTMSK = msk & ~daint; if (pend & (1 << 0)) usb_notify_ep0(); - if (pend & (1 << USB_CDC_EP_BULK_IN)) + if (pend & (1 << USB_CDC_EP_BULK_IN)) { usb_notify_bulk_in(); + if (CONFIG_STM32_USB_DOUBLE_BUFFER_TX && TX_BUF.len) { + int ret = fifo_write_packet_fast(USB_CDC_EP_BULK_IN + , TX_BUF.buf, TX_BUF.len); + if (!ret) + TX_BUF.len = 0; + } + } } } From dc119c1478f1ac88210a351861e9a4a1de2975d2 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 14 Dec 2025 12:30:32 -0500 Subject: [PATCH 2/4] stm32: Simplify USBx_IRQn per-chip definitions in usbfs.c Signed-off-by: Kevin O'Connor --- src/stm32/usbfs.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/stm32/usbfs.c b/src/stm32/usbfs.c index a2fc6dd5a6..98d841bbb7 100644 --- a/src/stm32/usbfs.c +++ b/src/stm32/usbfs.c @@ -1,6 +1,6 @@ // Hardware interface to "fullspeed USB controller" // -// Copyright (C) 2018-2023 Kevin O'Connor +// Copyright (C) 2018-2025 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -19,29 +19,27 @@ // Transfer memory is accessed with 32bits, but contains only 16bits of data typedef volatile uint32_t epmword_t; #define WSIZE 2 - #define USBx_IRQn USB_LP_IRQn -#elif CONFIG_MACH_STM32F0 || CONFIG_MACH_STM32L4 - // Transfer memory is accessed with 16bits and contains 16bits of data - typedef volatile uint16_t epmword_t; - #define WSIZE 2 - #define USBx_IRQn USB_IRQn -#elif CONFIG_MACH_STM32G4 +#elif CONFIG_MACH_STM32F0 || CONFIG_MACH_STM32L4 || CONFIG_MACH_STM32G4 // Transfer memory is accessed with 16bits and contains 16bits of data typedef volatile uint16_t epmword_t; #define WSIZE 2 - #define USBx_IRQn USB_LP_IRQn #elif CONFIG_MACH_STM32G0 // Transfer memory is accessed with 32bits and contains 32bits of data typedef volatile uint32_t epmword_t; #define WSIZE 4 +#endif + +// Different chips have different names for the USB irq +#if CONFIG_MACH_STM32F1 || CONFIG_MACH_STM32G4 + #define USBx_IRQn USB_LP_IRQn +#elif CONFIG_MACH_STM32G0B1 + #define USBx_IRQn USB_UCPD1_2_IRQn +#else #define USBx_IRQn USB_IRQn #endif // The stm32g0 has slightly different register names #if CONFIG_MACH_STM32G0 - #if CONFIG_MACH_STM32G0B1 - #define USB_IRQn USB_UCPD1_2_IRQn - #endif #define USB USB_DRD_FS #define USB_PMAADDR USB_DRD_PMAADDR #define USB_EPADDR_FIELD USB_CHEP_ADDR @@ -55,8 +53,8 @@ // Some chip variants do not define these fields #ifndef USB_EP_DTOG_TX_Pos -#define USB_EP_DTOG_TX_Pos 6 -#define USB_EP_DTOG_RX_Pos 14 + #define USB_EP_DTOG_TX_Pos 6 + #define USB_EP_DTOG_RX_Pos 14 #endif From 21eccc6098d328eb28e5342eaf445698b0da2e63 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 14 Dec 2025 13:32:38 -0500 Subject: [PATCH 3/4] stm32: Allow disabling double buffering transmit in usbfs.c Only enable double buffering transmit if CONFIG_STM32_USB_DOUBLE_BUFFER_TX is set. Signed-off-by: Kevin O'Connor --- src/stm32/usbfs.c | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/src/stm32/usbfs.c b/src/stm32/usbfs.c index 98d841bbb7..b1d4058cef 100644 --- a/src/stm32/usbfs.c +++ b/src/stm32/usbfs.c @@ -242,8 +242,9 @@ usb_read_bulk_out(void *data, uint_fast8_t max_len) static uint32_t bulk_in_push_pos, bulk_in_pop_flag; #define BI_START 2 -int_fast8_t -usb_send_bulk_in(void *data, uint_fast8_t len) +// Send bulk packet to host with double buffering optimization +static int_fast8_t +usb_send_bulk_in_double_buffer(void *data, uint_fast8_t len) { if (readl(&bulk_in_pop_flag)) // No buffer space available @@ -276,6 +277,21 @@ usb_send_bulk_in(void *data, uint_fast8_t len) return len; } +// Send bulk usb packet to host +int_fast8_t +usb_send_bulk_in(void *data, uint_fast8_t len) +{ + if (CONFIG_STM32_USB_DOUBLE_BUFFER_TX) + return usb_send_bulk_in_double_buffer(data, len); + uint32_t ep = USB_CDC_EP_BULK_IN, epr = USB_EPR[ep]; + if ((epr & USB_EPTX_STAT) != USB_EP_TX_NAK) + // No buffer space available + return -1; + btable_write_packet(ep, BUFTX, data, len); + USB_EPR[ep] = calc_epr_bits(epr, USB_EPTX_STAT, USB_EP_TX_VALID); + return len; +} + int_fast8_t usb_read_ep0(void *data, uint_fast8_t max_len) { @@ -334,9 +350,10 @@ usb_set_configure(void) bulk_out_pop_count = 0; USB_EPR[ep] = calc_epr_bits(USB_EPR[ep], USB_EPRX_STAT, USB_EP_RX_VALID); - ep = USB_CDC_EP_BULK_IN; - bulk_in_push_pos = BI_START; - writel(&bulk_in_pop_flag, 0); + if (CONFIG_STM32_USB_DOUBLE_BUFFER_TX) { + bulk_in_push_pos = BI_START; + writel(&bulk_in_pop_flag, 0); + } } @@ -361,9 +378,12 @@ usb_reset(void) bulk_out_push_flag = USB_EP_DTOG_TX; ep = USB_CDC_EP_BULK_IN; - USB_EPR[ep] = (USB_CDC_EP_BULK_IN | USB_EP_BULK | USB_EP_KIND - | USB_EP_TX_NAK); - bulk_in_pop_flag = USB_EP_DTOG_RX; + uint32_t bi_epr_flags = USB_CDC_EP_BULK_IN | USB_EP_BULK | USB_EP_TX_NAK; + if (CONFIG_STM32_USB_DOUBLE_BUFFER_TX) { + bi_epr_flags |= USB_EP_KIND; + bulk_in_pop_flag = USB_EP_DTOG_RX; + } + USB_EPR[ep] = bi_epr_flags; USB->CNTR = USB_CNTR_CTRM | USB_CNTR_RESETM; USB->DADDR = USB_DADDR_EF; @@ -383,9 +403,12 @@ USB_IRQHandler(void) bulk_out_push_flag = 0; usb_notify_bulk_out(); } else if (ep == USB_CDC_EP_BULK_IN) { - USB_EPR[ep] = (calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0) - | bulk_in_pop_flag); - bulk_in_pop_flag = 0; + uint32_t ne = calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0); + if (CONFIG_STM32_USB_DOUBLE_BUFFER_TX) { + ne |= bulk_in_pop_flag; + bulk_in_pop_flag = 0; + } + USB_EPR[ep] = ne; usb_notify_bulk_in(); } else if (ep == 0) { USB_EPR[ep] = calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0); From bbf0eb7f4d06d8daf2f00ec4eac449717f7989f5 Mon Sep 17 00:00:00 2001 From: Timofey Titovets Date: Tue, 24 Mar 2026 17:32:14 +0100 Subject: [PATCH 4/4] stm32: usbotg block next bulk_in if buffer not empty Signed-off-by: Timofey Titovets Signed-off-by: Kevin O'Connor --- src/stm32/usbotg.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/stm32/usbotg.c b/src/stm32/usbotg.c index b41daf48ce..6e30836960 100644 --- a/src/stm32/usbotg.c +++ b/src/stm32/usbotg.c @@ -242,7 +242,8 @@ usb_send_bulk_in(void *data, uint_fast8_t len) usb_irq_enable(); return len; } - if (ctl & USB_OTG_DIEPCTL_EPENA) { + int dbuf_busy = CONFIG_STM32_USB_DOUBLE_BUFFER_TX && TX_BUF.len; + if (ctl & USB_OTG_DIEPCTL_EPENA || dbuf_busy) { if (!CONFIG_STM32_USB_DOUBLE_BUFFER_TX || TX_BUF.len || !len) { // Wait for space to transmit OTGD->DAINTMSK |= 1 << USB_CDC_EP_BULK_IN;