From 5f05d8f4876143af8f5052e7679fe2515c1ccf12 Mon Sep 17 00:00:00 2001 From: Keir Fraser Date: Mon, 21 Jun 2021 12:08:01 +0100 Subject: [PATCH] AT32F403: Work around broken double-buffered endpoints using an ISR to quickly post new buffers. --- bootloader/usb/Makefile | 2 +- inc/util.h | 1 + src/usb/Makefile | 2 +- src/usb/hw_at32f4.c | 8 - src/usb/hw_usbd_at32f4.c | 544 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 547 insertions(+), 10 deletions(-) create mode 100644 src/usb/hw_usbd_at32f4.c diff --git a/bootloader/usb/Makefile b/bootloader/usb/Makefile index 4b2f930..70b5c47 100644 --- a/bootloader/usb/Makefile +++ b/bootloader/usb/Makefile @@ -11,7 +11,7 @@ OBJS-$(stm32f7) += hw_dwc_otg.o OBJS-$(stm32f7) += hw_f7.o OBJS-$(at32f4) += hw_dwc_otg.o -OBJS-$(at32f4) += hw_usbd.o +OBJS-$(at32f4) += hw_usbd_at32f4.o OBJS-$(at32f4) += hw_at32f4.o $(OBJS) $(OBJS-y): CFLAGS += -include $(ROOT)/src/usb/defs.h diff --git a/inc/util.h b/inc/util.h index d893c91..3d8b362 100644 --- a/inc/util.h +++ b/inc/util.h @@ -132,6 +132,7 @@ void EXC_unused(void); #define RESET_IRQ_PRI 0 #define INDEX_IRQ_PRI 2 #define TIMER_IRQ_PRI 4 +#define USB_IRQ_PRI 6 /* * Local variables: diff --git a/src/usb/Makefile b/src/usb/Makefile index ce587b3..a54d4bd 100644 --- a/src/usb/Makefile +++ b/src/usb/Makefile @@ -9,7 +9,7 @@ OBJS-$(stm32f7) += hw_dwc_otg.o OBJS-$(stm32f7) += hw_f7.o OBJS-$(at32f4) += hw_dwc_otg.o -OBJS-$(at32f4) += hw_usbd.o +OBJS-$(at32f4) += hw_usbd_at32f4.o OBJS-$(at32f4) += hw_at32f4.o $(OBJS) $(OBJS-y): CFLAGS += -include defs.h diff --git a/src/usb/hw_at32f4.c b/src/usb/hw_at32f4.c index 53ff627..12f8585 100644 --- a/src/usb/hw_at32f4.c +++ b/src/usb/hw_at32f4.c @@ -87,14 +87,6 @@ void usb_stall(uint8_t epnr) void usb_configure_ep(uint8_t epnr, uint8_t type, uint32_t size) { - switch (at32f4_series) { - case AT32F403: - /* Double-buffer hardware implementation is incompatible. */ - if (type == EPT_DBLBUF) - type = EPT_BULK; - break; - } - drv->configure_ep(epnr, type, size); } diff --git a/src/usb/hw_usbd_at32f4.c b/src/usb/hw_usbd_at32f4.c new file mode 100644 index 0000000..a99069a --- /dev/null +++ b/src/usb/hw_usbd_at32f4.c @@ -0,0 +1,544 @@ +/* + * hw_usbd_at32f4.c + * + * USB handling for AT32F4xx USBD peripheral. This is separate from the STM32 + * equivalent as Artery messed up implementation of double-buffered endpoints. + * Thus to achieve line rate requires IRQ trickery to quickly post new buffers. + * + * Written & released by Keir Fraser + * + * This is free and unencumbered software released into the public domain. + * See the file COPYING for more details, or visit . + */ + +#include "hw_usbd.h" + +void IRQ_19(void) __attribute__((alias("IRQ_USB_HP"))); +#define USB_HP_IRQ 19 + +static uint16_t buf_end; +static uint8_t pending_addr; + +/* Double-buffer endpoints: RX/TX buffer rings interfacing to USB IRQ. */ +#define NBUF 2 +static struct buf { + uint16_t data[USB_FS_MPS / 2]; + uint32_t count; +} rx_buf[NBUF], tx_buf[NBUF]; + +#define BUF_MASK(_ep, _idx) (((_ep)->_idx) & (NBUF-1)) + +static struct ep { + bool_t is_dblbuf; + union { + struct { + /* Normal (non-double-buffered) endpoints: We track which + * endpoints have been marked CTR (Correct TRansfer). On receive + * side, checking EPR_STAT_RX == NAK races update of the Buffer + * Descriptor's COUNT_RX by the hardware. */ + bool_t rx_ready; + bool_t tx_ready; + } std; + struct { + /* is_dblbuf: Non-IRQ context needs to kick the pipeline. */ + bool_t kick; + /* is_dblbuf: {rx,tx}_buf[] ring indexes */ + uint16_t bufc, bufp; + /* is_dblbuf: number of hw slots filled */ + unsigned int tx_hw_slots; + } db; + }; +} eps[8]; + +static void handle_dblbuf_tx_transfer(uint8_t epnr); +static void handle_dblbuf_rx_transfer(uint8_t epnr); + +static bool_t usbd_has_highspeed(void) +{ + return FALSE; +} + +static bool_t usbd_is_highspeed(void) +{ + return FALSE; +} + +static void usbd_init(void) +{ + /* Turn on clock. */ + rcc->apb1enr |= RCC_APB1ENR_USBEN; + + /* Exit power-down state. */ + usb->cntr &= ~USB_CNTR_PDWN; + delay_us(10); + + /* Exit reset state. */ + usb->cntr &= ~USB_CNTR_FRES; + delay_us(10); + + /* Clear IRQ state. */ + usb->istr = 0; + + IRQx_set_prio(USB_HP_IRQ, USB_IRQ_PRI); + IRQx_enable(USB_HP_IRQ); +} + +static void usbd_deinit(void) +{ + rcc->apb1enr &= ~RCC_APB1ENR_USBEN; +} + +#if 0 +static void dump_ep(uint8_t ep) +{ + const static char *names[] = { "DISA", "STAL", "NAK ", "VALI" }; + uint16_t epr; + ep &= 0x7f; + epr = usb->epr[ep]; + printk("[EP%u: Rx:%c%c(%s)%04x:%02u Tx:%c%c(%s)%04x:%02u %c]", + ep, + (epr & USB_EPR_CTR_RX) ? 'C' : ' ', + (epr & USB_EPR_DTOG_RX) ? 'D' : ' ', + names[(epr>>12)&3], + usb_bufd[ep].addr_rx, usb_bufd[ep].count_rx & 0x3ff, + (epr & USB_EPR_CTR_TX) ? 'C' : ' ', + (epr & USB_EPR_DTOG_TX) ? 'D' : ' ', + names[(epr>>4)&3], + usb_bufd[ep].addr_tx, usb_bufd[ep].count_tx & 0x3ff, + (epr & USB_EPR_SETUP) ? 'S' : ' '); +} +#endif + +static int usbd_ep_rx_ready(uint8_t epnr) +{ + volatile struct usb_bufd *bd; + struct ep *ep = &eps[epnr]; + + if (ep->is_dblbuf) + return ((ep->db.bufc != ep->db.bufp) + ? rx_buf[BUF_MASK(ep, db.bufc)].count + : -1); + + if (!ep->std.rx_ready) + return -1; + + bd = &usb_bufd[epnr]; + return bd->count_rx & 0x3ff; +} + +static bool_t usbd_ep_tx_ready(uint8_t epnr) +{ + struct ep *ep = &eps[epnr]; + if (ep->is_dblbuf) + return (uint16_t)(ep->db.bufp - ep->db.bufc) < NBUF; + return ep->std.tx_ready; +} + +static void usbd_read(uint8_t epnr, void *buf, uint32_t len) +{ + unsigned int i, base; + uint16_t epr = usb->epr[epnr], *p = buf; + volatile struct usb_bufd *bd = &usb_bufd[epnr]; + struct ep *ep = &eps[epnr]; + + if (ep->is_dblbuf) { + memcpy(buf, rx_buf[BUF_MASK(ep, db.bufc)].data, len); + barrier(); /* read data /then/ update consumer */ + ep->db.bufc++; + if (ep->db.kick) { + uint32_t oldpri = IRQ_save(USB_IRQ_PRI); + if (ep->db.kick) + handle_dblbuf_rx_transfer(epnr); + IRQ_restore(oldpri); + } + return; + } + + base = bd->addr_rx; + ep->std.rx_ready = FALSE; + base = (uint16_t)base >> 1; + + for (i = 0; i < len/2; i++) + *p++ = usb_buf[base + i]; + if (len&1) + *(uint8_t *)p = usb_buf[base + i]; + + /* Set status NAK->VALID. */ + epr &= 0x370f; /* preserve rw & t fields (except STAT_RX) */ + epr |= 0x8080; /* preserve rc_w0 fields */ + epr ^= USB_EPR_STAT_RX(USB_STAT_VALID); /* modify STAT_RX */ + usb->epr[epnr] = epr; +} + +static void usbd_write(uint8_t epnr, const void *buf, uint32_t len) +{ + unsigned int i, base; + uint16_t epr = usb->epr[epnr]; + const uint16_t *p = buf; + volatile struct usb_bufd *bd = &usb_bufd[epnr]; + struct ep *ep = &eps[epnr]; + + if (ep->is_dblbuf) { + struct buf *b = &tx_buf[BUF_MASK(ep, db.bufp)]; + memcpy(b->data, buf, len); + b->count = len; + barrier(); /* write data /then/ update producer */ + ep->db.bufp++; + if (ep->db.kick) { + uint32_t oldpri = IRQ_save(USB_IRQ_PRI); + if (ep->db.kick) + handle_dblbuf_tx_transfer(epnr); + IRQ_restore(oldpri); + } + return; + } + + base = bd->addr_tx; + bd->count_tx = len; + ep->std.tx_ready = FALSE; + base = (uint16_t)base >> 1; + + for (i = 0; i < len/2; i++) + usb_buf[base + i] = *p++; + if (len&1) + usb_buf[base + i] = *(const uint8_t *)p; + + /* Set status NAK->VALID. */ + epr &= 0x073f; /* preserve rw & t fields (except STAT_TX) */ + epr |= 0x8080; /* preserve rc_w0 fields */ + epr ^= USB_EPR_STAT_TX(USB_STAT_VALID); /* modify STAT_TX */ + usb->epr[epnr] = epr; +} + +static void usbd_stall(uint8_t ep) +{ + uint16_t epr = usb->epr[ep]; + epr &= 0x073f; + epr |= 0x8080; + epr ^= USB_EPR_STAT_TX(USB_STAT_STALL); + usb->epr[ep] = epr; +} + +static void usbd_configure_ep(uint8_t epnr, uint8_t type, uint32_t size) +{ + static const uint8_t types[] = { + [EPT_CONTROL] = USB_EP_TYPE_CONTROL, + [EPT_ISO] = USB_EP_TYPE_ISO, + [EPT_BULK] = USB_EP_TYPE_BULK, + [EPT_INTERRUPT] = USB_EP_TYPE_INTERRUPT + }; + + uint16_t old_epr, new_epr; + bool_t in, dbl_buf; + volatile struct usb_bufd *bd; + struct ep *ep; + + in = !!(epnr & 0x80); + epnr &= 0x7f; + ep = &eps[epnr]; + bd = &usb_bufd[epnr]; + + old_epr = usb->epr[epnr]; + new_epr = 0; + + dbl_buf = (type == EPT_DBLBUF); + if (dbl_buf) { + ASSERT(epnr != 0); + type = EPT_BULK; + new_epr |= USB_EPR_EP_KIND_DBL_BUF; + bd->addr_0 = buf_end; + bd->addr_1 = buf_end + size; + buf_end += 2*size; + ep->is_dblbuf = TRUE; + ep->db.bufc = ep->db.bufp = ep->db.tx_hw_slots = 0; + } + + type = types[type]; + + /* Sets: Type and Endpoint Address. + * Clears: CTR_RX and CTR_TX. */ + new_epr |= USB_EPR_EP_TYPE(type) | USB_EPR_EA(epnr); + + if (in || (epnr == 0)) { + if (dbl_buf) { + bd->count_0 = bd->count_1 = 0; + /* TX: Clears SW_BUF. */ + new_epr |= old_epr & 0x4000; + /* TX: Clears data toggle and sets status to VALID. */ + new_epr |= (old_epr & 0x0070) ^ USB_EPR_STAT_TX(USB_STAT_VALID); + ep->db.kick = TRUE; + } else { + bd->addr_tx = buf_end; + buf_end += size; + bd->count_tx = 0; + /* TX: Clears data toggle and sets status to NAK. */ + new_epr |= (old_epr & 0x0070) ^ USB_EPR_STAT_TX(USB_STAT_NAK); + /* IN Endpoint is immediately ready to transmit. */ + ep->std.tx_ready = TRUE; + } + } + + if (!in) { + if (dbl_buf) { + bd->count_0 = bd->count_1 = 0x8400; /* USB_FS_MPS = 64 bytes */ + /* RX: Sets SW_BUF. */ + new_epr |= (old_epr & 0x0040) ^ 0x0040; + ep->db.kick = FALSE; + } else { + bd->addr_rx = buf_end; + buf_end += size; + bd->count_rx = 0x8400; /* USB_FS_MPS = 64 bytes */ + /* OUT Endpoint must wait for a packet from the Host. */ + ep->std.rx_ready = FALSE; + } + /* RX: Clears data toggle and sets status to VALID. */ + new_epr |= (old_epr & 0x7000) ^ USB_EPR_STAT_RX(USB_STAT_VALID); + } + + barrier(); /* initialise soft flags /then/ enable endpoint */ + usb->epr[epnr] = new_epr; +} + +static void usbd_setaddr(uint8_t addr) +{ + pending_addr = addr; +} + +static void handle_reset(void) +{ + /* Reinitialise class-specific subsystem. */ + usb_cdc_acm_ops.reset(); + + /* Clear endpoint soft state. */ + memset(eps, 0, sizeof(eps)); + + /* Clear any in-progress Control Transfer. */ + ep0.data_len = -1; + ep0.tx.todo = -1; + + /* Prepare for Enumeration: Set up Endpoint 0 at Address 0. */ + pending_addr = 0; + buf_end = 64; + usb_configure_ep(0, EPT_CONTROL, EP0_MPS); + usb->daddr = USB_DADDR_EF | USB_DADDR_ADD(0); + usb->istr &= ~USB_ISTR_RESET; +} + +static void clear_ctr(uint8_t ep, uint16_t ctr) +{ + uint16_t epr = usb->epr[ep]; + epr &= 0x070f; /* preserve rw & t fields */ + epr |= 0x8080; /* preserve rc_w0 fields */ + epr &= ~ctr; /* clear specified rc_w0 field */ + usb->epr[ep] = epr; +} + +static void handle_rx_transfer(uint8_t epnr) +{ + uint16_t epr = usb->epr[epnr]; + struct ep *ep = &eps[epnr]; + + if (ep->is_dblbuf) + return; + + clear_ctr(epnr, USB_EPR_CTR_RX); + ep->std.rx_ready = TRUE; + + /* We only handle Control Transfers here (endpoint 0). */ + if (epnr == 0) + handle_rx_ep0(!!(epr & USB_EPR_SETUP)); +} + +static void handle_tx_transfer(uint8_t epnr) +{ + struct ep *ep = &eps[epnr]; + + if (ep->is_dblbuf) + return; + + clear_ctr(epnr, USB_EPR_CTR_TX); + ep->std.tx_ready = TRUE; + + /* We only handle Control Transfers here (endpoint 0). */ + if (epnr != 0) + return; + + handle_tx_ep0(); + + if (pending_addr && (ep0.tx.todo == -1)) { + /* We have just completed the Status stage of a SET_ADDRESS request. + * Now is the time to apply the address update. */ + usb->daddr = USB_DADDR_EF | USB_DADDR_ADD(pending_addr); + pending_addr = 0; + } +} + +static void usbd_process(void) +{ + uint16_t istr = usb->istr; + usb->istr = ~istr & 0x7f00; + + if (istr & USB_ISTR_CTR) { + uint8_t ep = USB_ISTR_GET_EP_ID(istr); + //dump_ep(ep); + if (istr & USB_ISTR_DIR) + handle_rx_transfer(ep); + else + handle_tx_transfer(ep); + //printk(" -> "); dump_ep(ep); printk("\n"); + } + + if (istr & USB_ISTR_PMAOVR) { + printk("[PMAOVR]\n"); + } + + if (istr & USB_ISTR_ERR) { + printk("[ERR]\n"); + } + + if (istr & USB_ISTR_WKUP) { + printk("[WKUP]\n"); + } + + if (istr & USB_ISTR_RESET) { + printk("[RESET]\n"); + handle_reset(); + } +} + +static void handle_dblbuf_rx_transfer(uint8_t epnr) +{ + struct ep *ep; + volatile struct usb_bufd *bd; + uint16_t epr, _epr, *p; + unsigned int i, base, len; + struct buf *buf; + + ep = &eps[epnr]; + epr = usb->epr[epnr]; + bd = &usb_bufd[epnr]; + + if ((uint16_t)(ep->db.bufp - ep->db.bufc) == NBUF) { + clear_ctr(epnr, USB_EPR_CTR_RX); + ep->db.kick = TRUE; + return; + } + + ep->db.kick = FALSE; + + /* Clear CTR_RX. Toggle SW_BUF. Status remains VALID at all times. */ + _epr = epr; + _epr &= 0x070f; /* preserve rw & t fields */ + _epr |= 0x80c0; /* preserve rc_w0 fields, toggle SW_BUF */ + _epr &= ~USB_EPR_CTR_RX; + usb->epr[epnr] = _epr; + + base = (epr & 0x0040) ? bd->addr_0 : bd->addr_1; + base = (uint16_t)base >> 1; + len = (epr & 0x0040) ? bd->count_0 : bd->count_1; + len &= 0x3ff; + + buf = &rx_buf[BUF_MASK(ep, db.bufp++)]; + p = buf->data; + buf->count = len; + + for (i = 0; i < 64/2; i++) + *p++ = usb_buf[base + i]; +} + +static void stuff_dblbuf_tx_packet(uint8_t epnr) +{ + struct ep *ep; + volatile struct usb_bufd *bd; + uint16_t epr, *p; + unsigned int i, base, len; + struct buf *buf; + + ep = &eps[epnr]; + epr = usb->epr[epnr]; + bd = &usb_bufd[epnr]; + + buf = &tx_buf[BUF_MASK(ep, db.bufc++)]; + p = buf->data; + len = buf->count; + + if (epr & 0x4000) { + base = bd->addr_1; + bd->count_1 = len; + } else { + base = bd->addr_0; + bd->count_0 = len; + } + base = (uint16_t)base >> 1; + + for (i = 0; i < 64/2; i++) + usb_buf[base + i] = *p++; + + ep->db.tx_hw_slots++; +} + +static void handle_dblbuf_tx_transfer(uint8_t epnr) +{ + struct ep *ep = &eps[epnr]; + uint16_t epr; + + ep->db.kick = (ep->db.bufc == ep->db.bufp) && (ep->db.tx_hw_slots == 0); + if (ep->db.kick) + return; + + if (ep->db.tx_hw_slots == 0) + stuff_dblbuf_tx_packet(epnr); + + /* Toggle SW_BUF. Status remains VALID at all times. */ + epr = usb->epr[epnr]; + epr &= 0x070f; /* preserve rw & t fields */ + epr |= 0xc080; /* preserve rc_w0 fields, toggle SW_BUF */ + usb->epr[epnr] = epr; + + if ((ep->db.tx_hw_slots == 1) && (ep->db.bufc != ep->db.bufp)) + stuff_dblbuf_tx_packet(epnr); +} + +static void IRQ_USB_HP(void) +{ + uint16_t istr = usb->istr; + if (usb->istr & USB_ISTR_CTR) { + uint8_t epnr = USB_ISTR_GET_EP_ID(istr); + if (istr & USB_ISTR_DIR) { + handle_dblbuf_rx_transfer(epnr); + } else { + struct ep *ep = &eps[epnr]; + ep->db.tx_hw_slots--; + clear_ctr(epnr, USB_EPR_CTR_TX); + handle_dblbuf_tx_transfer(epnr); + } + } +} + +const struct usb_driver usbd = { + .init = usbd_init, + .deinit = usbd_deinit, + .process = usbd_process, + + .has_highspeed = usbd_has_highspeed, + .is_highspeed = usbd_is_highspeed, + + .setaddr = usbd_setaddr, + + .configure_ep = usbd_configure_ep, + .ep_rx_ready = usbd_ep_rx_ready, + .ep_tx_ready = usbd_ep_tx_ready, + .read = usbd_read, + .write = usbd_write, + .stall = usbd_stall +}; + +/* + * Local variables: + * mode: C + * c-file-style: "Linux" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */