From 0d0b802ee01537a98be89c6760684fe1a221c81a Mon Sep 17 00:00:00 2001 From: "William D. Jones" Date: Mon, 2 Sep 2019 19:32:11 -0400 Subject: [PATCH] stm32h7: Implement IN and OUT receive for EP0; device descriptor returned. --- src/portable/st/stm32h7/dcd_stm32h7.c | 208 +++++++++++++++++++++++++- 1 file changed, 200 insertions(+), 8 deletions(-) diff --git a/src/portable/st/stm32h7/dcd_stm32h7.c b/src/portable/st/stm32h7/dcd_stm32h7.c index 464d46118..c1fc18e93 100644 --- a/src/portable/st/stm32h7/dcd_stm32h7.c +++ b/src/portable/st/stm32h7/dcd_stm32h7.c @@ -219,11 +219,43 @@ bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) { (void) rhport; - (void) ep_addr; - (void) buffer; - (void) total_bytes; + USB_OTG_DeviceTypeDef * dev = DEVICE_BASE; + USB_OTG_OUTEndpointTypeDef * out_ep = OUT_EP_BASE; + USB_OTG_INEndpointTypeDef * in_ep = IN_EP_BASE; - return false; + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); + xfer->buffer = buffer; + xfer->total_len = total_bytes; + xfer->queued_len = 0; + xfer->short_packet = false; + + uint16_t num_packets = (total_bytes / xfer->max_size); + uint8_t short_packet_size = total_bytes % xfer->max_size; + + // Zero-size packet is special case. + if(short_packet_size > 0 || (total_bytes == 0)) { + num_packets++; + } + + // IN and OUT endpoint xfers are interrupt-driven, we just schedule them + // here. + if(dir == TUSB_DIR_IN) { + // A full IN transfer (multiple packets, possibly) triggers XFRC. + in_ep[epnum].DIEPTSIZ = (num_packets << USB_OTG_DIEPTSIZ_PKTCNT_Pos) | \ + ((total_bytes & USB_OTG_DIEPTSIZ_XFRSIZ_Msk) << USB_OTG_DIEPTSIZ_XFRSIZ_Pos); + in_ep[epnum].DIEPCTL |= USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK; + dev->DIEPEMPMSK |= (1 << epnum); + } else { + // Each complete packet for OUT xfers triggers XFRC. + out_ep[epnum].DOEPTSIZ = (1 << USB_OTG_DOEPTSIZ_PKTCNT_Pos) | \ + ((xfer->max_size & USB_OTG_DOEPTSIZ_XFRSIZ_Msk) << USB_OTG_DOEPTSIZ_XFRSIZ_Pos); + out_ep[epnum].DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK; + } + + return true; } // TODO: The logic for STALLing and disabling an endpoint is very similar @@ -242,6 +274,108 @@ void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr) /*------------------------------------------------------------------*/ +// TODO: Split into "receive on endpoint 0" and "receive generic"; endpoint 0's +// DOEPTSIZ register is smaller than the others, and so is insufficient for +// determining how much of an OUT transfer is actually remaining. +static void receive_packet(xfer_ctl_t * xfer, /* USB_OTG_OUTEndpointTypeDef * out_ep, */ uint16_t xfer_size) { + uint32_t * rx_fifo = FIFO_BASE(0); + + // See above TODO + // uint16_t remaining = (out_ep->DOEPTSIZ & USB_OTG_DOEPTSIZ_XFRSIZ_Msk) >> USB_OTG_DOEPTSIZ_XFRSIZ_Pos; + // xfer->queued_len = xfer->total_len - remaining; + + uint16_t remaining = xfer->total_len - xfer->queued_len; + uint16_t to_recv_size; + + if(remaining <= xfer->max_size) { + // Avoid buffer overflow. + to_recv_size = (xfer_size > remaining) ? remaining : xfer_size; + } else { + // Room for full packet, choose recv_size based on what the microcontroller + // claims. + to_recv_size = (xfer_size > xfer->max_size) ? xfer->max_size : xfer_size; + } + + uint8_t to_recv_rem = to_recv_size % 4; + uint16_t to_recv_size_aligned = to_recv_size - to_recv_rem; + + // Do not assume xfer buffer is aligned. + uint8_t * base = (xfer->buffer + xfer->queued_len); + + // This for loop always runs at least once- skip if less than 4 bytes + // to collect. + if(to_recv_size >= 4) { + for(uint16_t i = 0; i < to_recv_size_aligned; i += 4) { + uint32_t tmp = (* rx_fifo); + base[i] = tmp & 0x000000FF; + base[i + 1] = (tmp & 0x0000FF00) >> 8; + base[i + 2] = (tmp & 0x00FF0000) >> 16; + base[i + 3] = (tmp & 0xFF000000) >> 24; + } + } + + // Do not read invalid bytes from RX FIFO. + if(to_recv_rem != 0) { + uint32_t tmp = (* rx_fifo); + uint8_t * last_32b_bound = base + to_recv_size_aligned; + + last_32b_bound[0] = tmp & 0x000000FF; + if(to_recv_rem > 1) { + last_32b_bound[1] = (tmp & 0x0000FF00) >> 8; + } + if(to_recv_rem > 2) { + last_32b_bound[2] = (tmp & 0x00FF0000) >> 16; + } + } + + xfer->queued_len += xfer_size; + + // Per USB spec, a short OUT packet (including length 0) is always + // indicative of the end of a transfer (at least for ctl, bulk, int). + xfer->short_packet = (xfer_size < xfer->max_size); +} + +static void transmit_packet(xfer_ctl_t * xfer, USB_OTG_INEndpointTypeDef * in_ep, uint8_t fifo_num) { + uint32_t * tx_fifo = FIFO_BASE(fifo_num); + + uint16_t remaining = (in_ep->DIEPTSIZ & USB_OTG_DIEPTSIZ_XFRSIZ_Msk) >> USB_OTG_DIEPTSIZ_XFRSIZ_Pos; + xfer->queued_len = xfer->total_len - remaining; + + uint16_t to_xfer_size = (remaining > xfer->max_size) ? xfer->max_size : remaining; + uint8_t to_xfer_rem = to_xfer_size % 4; + uint16_t to_xfer_size_aligned = to_xfer_size - to_xfer_rem; + + // Buffer might not be aligned to 32b, so we need to force alignment + // by copying to a temp var. + uint8_t * base = (xfer->buffer + xfer->queued_len); + + // This for loop always runs at least once- skip if less than 4 bytes + // to send off. + if(to_xfer_size >= 4) { + for(uint16_t i = 0; i < to_xfer_size_aligned; i += 4) { + uint32_t tmp = base[i] | (base[i + 1] << 8) | \ + (base[i + 2] << 16) | (base[i + 3] << 24); + (* tx_fifo) = tmp; + } + } + + // Do not read beyond end of buffer if not divisible by 4. + if(to_xfer_rem != 0) { + uint32_t tmp = 0; + uint8_t * last_32b_bound = base + to_xfer_size_aligned; + + tmp |= last_32b_bound[0]; + if(to_xfer_rem > 1) { + tmp |= (last_32b_bound[1] << 8); + } + if(to_xfer_rem > 2) { + tmp |= (last_32b_bound[2] << 16); + } + + (* tx_fifo) = tmp; + } +} + static void read_rx_fifo(USB_OTG_OUTEndpointTypeDef * out_ep) { uint32_t * rx_fifo = FIFO_BASE(0); @@ -252,12 +386,14 @@ static void read_rx_fifo(USB_OTG_OUTEndpointTypeDef * out_ep) { uint8_t epnum = (ctl_word & USB_OTG_GRXSTSP_EPNUM_Msk) >> USB_OTG_GRXSTSP_EPNUM_Pos; uint16_t bcnt = (ctl_word & USB_OTG_GRXSTSP_BCNT_Msk) >> USB_OTG_GRXSTSP_BCNT_Pos; - (void) bcnt; - switch(pktsts) { case 0x01: // Global OUT NAK (Interrupt) break; case 0x02: // Out packet recvd + { + xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); + receive_packet(xfer, bcnt); + } break; case 0x03: // Out packet done (Interrupt) break; @@ -274,7 +410,7 @@ static void read_rx_fifo(USB_OTG_OUTEndpointTypeDef * out_ep) { _setup_packet[5 - 2*setup_left] = (* rx_fifo); } break; - default: // Invalid, do something here, like breakpoint? + default: // Invalid TU_BREAKPOINT(); break; } @@ -283,7 +419,8 @@ static void read_rx_fifo(USB_OTG_OUTEndpointTypeDef * out_ep) { static void handle_epout_ints(USB_OTG_DeviceTypeDef * dev, USB_OTG_OUTEndpointTypeDef * out_ep) { // DAINT for a given EP clears when DOEPINTx is cleared. // OEPINT will be cleared when DAINT's out bits are cleared. - for(int n = 0; n < 4; n++) { + for(int n = 0; n < 8; n++) { + xfer_ctl_t * xfer = XFER_CTL_BASE(n, TUSB_DIR_OUT); if(dev->DAINT & (1 << (USB_OTG_DAINT_OEPINT_Pos + n))) { // SETUP packet Setup Phase done. if(out_ep[n].DOEPINT & USB_OTG_DOEPINT_STUP) { @@ -292,6 +429,50 @@ static void handle_epout_ints(USB_OTG_DeviceTypeDef * dev, USB_OTG_OUTEndpointTy _setup_offs = 0; } } + + // OUT XFER complete (single packet). + if(out_ep[n].DOEPINT & USB_OTG_DOEPINT_XFRC) { + out_ep[n].DOEPINT = USB_OTG_DOEPINT_XFRC; + + // TODO: Because of endpoint 0's constrained size, we handle XFRC + // on a packet-basis. The core can internally handle multiple OUT + // packets; it would be more efficient to only trigger XFRC on a + // completed transfer for non-0 endpoints. + + // Transfer complete if short packet or total len is transferred + if(xfer->short_packet || (xfer->queued_len == xfer->total_len)) { + xfer->short_packet = false; + dcd_event_xfer_complete(0, n, xfer->queued_len, XFER_RESULT_SUCCESS, true); + } else { + // Schedule another packet to be received. + out_ep[n].DOEPTSIZ = (1 << USB_OTG_DOEPTSIZ_PKTCNT_Pos) | \ + ((xfer->max_size & USB_OTG_DOEPTSIZ_XFRSIZ_Msk) << USB_OTG_DOEPTSIZ_XFRSIZ_Pos); + out_ep[n].DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK; + } + } + } +} + +static void handle_epin_ints(USB_OTG_DeviceTypeDef * dev, USB_OTG_INEndpointTypeDef * in_ep) { + // DAINT for a given EP clears when DIEPINTx is cleared. + // IEPINT will be cleared when DAINT's out bits are cleared. + for(uint8_t n = 0; n < 8; n++) { + xfer_ctl_t * xfer = XFER_CTL_BASE(n, TUSB_DIR_IN); + + if(dev->DAINT & (1 << (USB_OTG_DAINT_IEPINT_Pos + n))) { + // IN XFER complete (entire xfer). + if(in_ep[n].DIEPINT & USB_OTG_DIEPINT_XFRC) { + in_ep[n].DIEPINT = USB_OTG_DIEPINT_XFRC; + dev->DIEPEMPMSK &= ~(1 << n); // Turn off TXFE b/c xfer inactive. + dcd_event_xfer_complete(0, n | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true); + } + + // XFER FIFO empty + if(in_ep[n].DIEPINT & USB_OTG_DIEPINT_TXFE) { + in_ep[n].DIEPINT = USB_OTG_DIEPINT_TXFE; + transmit_packet(xfer, &in_ep[n], n); + } + } } } @@ -299,6 +480,7 @@ void OTG_FS_IRQHandler (void) { USB_OTG_DeviceTypeDef * dev = DEVICE_BASE; USB_OTG_OUTEndpointTypeDef * out_ep = OUT_EP_BASE; + USB_OTG_INEndpointTypeDef * in_ep = IN_EP_BASE; uint32_t int_status = USB_OTG_FS->GINTSTS; @@ -315,6 +497,11 @@ void OTG_FS_IRQHandler (void) dcd_event_bus_signal(0, DCD_EVENT_BUS_RESET, true); } + if(int_status & USB_OTG_GINTSTS_SOF) { + USB_OTG_FS->GINTSTS = USB_OTG_GINTSTS_SOF; + dcd_event_bus_signal(0, DCD_EVENT_SOF, true); + } + if(int_status & USB_OTG_GINTSTS_RXFLVL) { read_rx_fifo(out_ep); } @@ -323,6 +510,11 @@ void OTG_FS_IRQHandler (void) if(int_status & USB_OTG_GINTSTS_OEPINT) { handle_epout_ints(dev, out_ep); } + + // IN endpoint interrupt handling. + if(int_status & USB_OTG_GINTSTS_IEPINT) { + handle_epin_ints(dev, in_ep); + } } #endif