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 <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2025-02-06 13:43:15 -05:00
parent a3e24d2172
commit 59a754c48c
2 changed files with 51 additions and 4 deletions

View file

@ -156,6 +156,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

View file

@ -1,6 +1,6 @@
// Hardware interface to "USB OTG (on the go) controller" on stm32
//
// Copyright (C) 2019 Kevin O'Connor <kevin@koconnor.net>
// Copyright (C) 2019-2025 Kevin O'Connor <kevin@koconnor.net>
//
// 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;
}
}
}
}