Skip to content

Commit f66b96d

Browse files
ArcaneNibbledlech
authored andcommitted
pbio/drv/usb/usb_ev3: Implement DMA-based endpoint handling
This currently implements an echo test rather than hooking up command handlers. This scaffolding is required before implementing the Pybricks protocol.
1 parent 1c0cf6f commit f66b96d

File tree

2 files changed

+337
-3
lines changed

2 files changed

+337
-3
lines changed

lib/pbio/drv/usb/usb_ev3.c

Lines changed: 320 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88

99
#if PBDRV_CONFIG_USB_EV3
1010

11+
#include <assert.h>
1112
#include <stdint.h>
1213

14+
#include <pbdrv/compiler.h>
1315
#include <pbdrv/usb.h>
1416
#include <pbio/os.h>
1517
#include <pbio/protocol.h>
@@ -19,6 +21,7 @@
1921
#include "pbdrvconfig.h"
2022

2123
#include <tiam1808/armv5/am1808/interrupt.h>
24+
#include <tiam1808/cppi41dma.h>
2225
#include <tiam1808/hw/hw_types.h>
2326
#include <tiam1808/hw/hw_usbOtg_AM1808.h>
2427
#include <tiam1808/hw/hw_syscfg0_AM1808.h>
@@ -218,6 +221,180 @@ static uint32_t pbdrv_usb_setup_misc_tx_byte;
218221
// Whether the device is using USB high-speed mode or not
219222
static bool pbdrv_usb_is_usb_hs;
220223

224+
// Buffers, used for different logical flows on the data endpoint
225+
static uint8_t ep1_rx_buf[PYBRICKS_EP_PKT_SZ_HS];
226+
static uint8_t ep1_tx_response_buf[PYBRICKS_EP_PKT_SZ_HS];
227+
static uint8_t ep1_tx_status_buf[PYBRICKS_EP_PKT_SZ_HS];
228+
static uint8_t ep1_tx_stdout_buf[PYBRICKS_EP_PKT_SZ_HS];
229+
230+
// Buffer status flags
231+
static volatile bool usb_rx_is_ready;
232+
static volatile bool usb_tx_response_is_not_ready;
233+
static volatile bool usb_tx_status_is_not_ready;
234+
static volatile bool usb_tx_stdout_is_not_ready;
235+
236+
// CPPI DMA support code
237+
238+
// Descriptors must be aligned to a power of 2 greater than or equal to their size.
239+
// We are using a descriptor of 32 bytes which is also the required alignment.
240+
#define CPPI_DESCRIPTOR_ALIGN 32
241+
242+
// Host Packet Descriptor
243+
// The TI support library has hardcoded assumptions about the layout of these structures,
244+
// so we declare it ourselves here in order to control it as we wish.
245+
typedef struct {
246+
hPDWord0 word0;
247+
hPDWord1 word1;
248+
hPDWord2 word2;
249+
uint32_t buf_len;
250+
void *buf_ptr;
251+
void *next_desc_ptr;
252+
uint32_t orig_buf_len;
253+
void *orig_buf_ptr;
254+
} __attribute__((aligned(CPPI_DESCRIPTOR_ALIGN))) usb_cppi_hpd_t;
255+
_Static_assert(sizeof(usb_cppi_hpd_t) <= CPPI_DESCRIPTOR_ALIGN);
256+
257+
// This goes into the lower bits of the queue CTRLD register
258+
#define CPPI_DESCRIPTOR_SIZE_BITS CPDMA_QUEUE_REGISTER_DESC_SIZE(usb_cppi_hpd_t)
259+
260+
// We only use a hardcoded descriptor for each logical flow,
261+
// rather than dynamically allocating them as needed
262+
enum {
263+
CPPI_DESC_RX,
264+
CPPI_DESC_TX_RESPONSE,
265+
CPPI_DESC_TX_STATUS,
266+
CPPI_DESC_TX_STDOUT,
267+
// the minimum number of descriptors we can allocate is 32,
268+
// even though we do not use nearly all of them
269+
CPPI_DESC_COUNT = 32,
270+
};
271+
272+
enum {
273+
// Documenting explicitly that we only use RX queue 0
274+
// (out of 16 total which are supported by the hardware)
275+
CPPI_RX_SUBMIT_QUEUE = 0,
276+
};
277+
278+
// CPPI memory
279+
static usb_cppi_hpd_t cppi_descriptors[CPPI_DESC_COUNT];
280+
static uint32_t cppi_linking_ram[CPPI_DESC_COUNT];
281+
// Tags a Host Packet Descriptor (i.e. the first descriptor
282+
// which contains full information about a packet, rather than
283+
// a Host Buffer Descriptor containing only an additional buffer).
284+
#define CPPI_HOST_PACKET_DESCRIPTOR_TYPE 0x10
285+
286+
// Fill in the CPPI DMA descriptor to receive a packet
287+
static void usb_setup_rx_dma_desc(void) {
288+
cppi_descriptors[CPPI_DESC_RX] = (usb_cppi_hpd_t) {
289+
.word0 = {
290+
.hostPktType = CPPI_HOST_PACKET_DESCRIPTOR_TYPE,
291+
},
292+
.word1 = {},
293+
.word2 = {
294+
.pktRetQueue = RX_COMPQ1,
295+
},
296+
.buf_len = PYBRICKS_EP_PKT_SZ_HS,
297+
.buf_ptr = ep1_rx_buf,
298+
.next_desc_ptr = 0,
299+
.orig_buf_len = PYBRICKS_EP_PKT_SZ_HS,
300+
.orig_buf_ptr = ep1_rx_buf,
301+
};
302+
303+
pbdrv_compiler_memory_barrier();
304+
305+
HWREG(USB_0_OTGBASE + CPDMA_QUEUE_REGISTER_D + CPPI_RX_SUBMIT_QUEUE * 16) =
306+
(uint32_t)(&cppi_descriptors[CPPI_DESC_RX]) | CPPI_DESCRIPTOR_SIZE_BITS;
307+
}
308+
309+
310+
// Fill in the CPPI DMA descriptor to send a packet
311+
static void usb_setup_tx_dma_desc(int tx_type, void *buf, uint32_t buf_len) {
312+
cppi_descriptors[tx_type] = (usb_cppi_hpd_t) {
313+
.word0 = {
314+
.hostPktType = CPPI_HOST_PACKET_DESCRIPTOR_TYPE,
315+
.pktLength = buf_len,
316+
},
317+
.word1 = {
318+
.srcPrtNum = 1, // port is EP1
319+
},
320+
.word2 = {
321+
.pktType = 5, // USB packet type
322+
.pktRetQueue = TX_COMPQ1,
323+
},
324+
.buf_len = buf_len,
325+
.buf_ptr = buf,
326+
.next_desc_ptr = 0,
327+
.orig_buf_len = buf_len,
328+
.orig_buf_ptr = buf,
329+
};
330+
331+
pbdrv_compiler_memory_barrier();
332+
333+
HWREG(USB_0_OTGBASE + CPDMA_QUEUE_REGISTER_D + TX_SUBMITQ1 * 16) =
334+
(uint32_t)(&cppi_descriptors[tx_type]) | CPPI_DESCRIPTOR_SIZE_BITS;
335+
}
336+
337+
// Helper function to set up CPPI DMA upon USB reset
338+
static void usb_reset_cppi_dma(void) {
339+
// Set up the FIFOs
340+
// We use a hardcoded address allocation as follows
341+
// @ 0 ==> EP0
342+
// @ 64 ==> EP1 IN (device to host, tx)
343+
// @ 64+512 ==> EP1 OUT (host to device, rx)
344+
HWREGB(USB0_BASE + USB_O_EPIDX) = 1;
345+
if (pbdrv_usb_is_usb_hs) {
346+
HWREGB(USB0_BASE + USB_O_TXFIFOSZ) = USB_TXFIFOSZ_SIZE_512;
347+
HWREGB(USB0_BASE + USB_O_RXFIFOSZ) = USB_RXFIFOSZ_SIZE_512;
348+
HWREGH(USB0_BASE + USB_O_TXMAXP1) = PYBRICKS_EP_PKT_SZ_HS;
349+
HWREGH(USB0_BASE + USB_O_RXMAXP1) = PYBRICKS_EP_PKT_SZ_HS;
350+
} else {
351+
HWREGB(USB0_BASE + USB_O_TXFIFOSZ) = USB_TXFIFOSZ_SIZE_64;
352+
HWREGB(USB0_BASE + USB_O_RXFIFOSZ) = USB_RXFIFOSZ_SIZE_64;
353+
HWREGH(USB0_BASE + USB_O_TXMAXP1) = PYBRICKS_EP_PKT_SZ_FS;
354+
HWREGH(USB0_BASE + USB_O_RXMAXP1) = PYBRICKS_EP_PKT_SZ_FS;
355+
}
356+
HWREGH(USB0_BASE + USB_O_TXFIFOADD) = EP0_BUF_SZ / 8;
357+
HWREGH(USB0_BASE + USB_O_RXFIFOADD) = (EP0_BUF_SZ + PYBRICKS_EP_PKT_SZ_HS) / 8;
358+
359+
// Set up the TX fifo for DMA and a stall condition
360+
HWREGH(USB0_BASE + USB_O_TXCSRL1) = ((USB_TXCSRH1_AUTOSET | USB_TXCSRH1_MODE | USB_TXCSRH1_DMAEN | USB_TXCSRH1_DMAMOD) << 8) | USB_TXCSRL1_STALL;
361+
// Set up the RX fifo for DMA and a stall condition
362+
HWREGH(USB0_BASE + USB_O_RXCSRL1) = ((USB_RXCSRH1_AUTOCL | USB_RXCSRH1_DMAEN) << 8) | USB_RXCSRL1_STALL;
363+
364+
// Set up CPPI DMA
365+
HWREG(USB_0_OTGBASE + CPDMA_LRAM_0_BASE) = (uint32_t)cppi_linking_ram;
366+
HWREG(USB_0_OTGBASE + CPDMA_LRAM_0_SIZE) = CPPI_DESC_COUNT;
367+
HWREG(USB_0_OTGBASE + CPDMA_LRAM_1_BASE) = 0;
368+
369+
HWREG(USB_0_OTGBASE + CPDMA_QUEUEMGR_REGION_0) = (uint32_t)cppi_descriptors;
370+
// 32 descriptors of 32 bytes each
371+
HWREG(USB_0_OTGBASE + CPDMA_QUEUEMGR_REGION_0_CONTROL) = 0;
372+
373+
// scheduler table: RX on 0, TX on 0
374+
HWREG(USB_0_OTGBASE + CPDMA_SCHED_TABLE_0) =
375+
((CPDMA_SCHED_TX | 0) << CPDMA_SCHED_ENTRY_SHIFT) |
376+
((CPDMA_SCHED_RX | 0) << 0);
377+
HWREG(USB_0_OTGBASE + CPDMA_SCHED_CONTROL_REG) = (1 << SCHEDULER_ENABLE_SHFT) | (2 - 1);
378+
379+
// CPPI RX
380+
HWREG(USB_0_OTGBASE + CPDMA_RX_CHANNEL_REG_A) =
381+
(CPPI_RX_SUBMIT_QUEUE << 0) |
382+
(CPPI_RX_SUBMIT_QUEUE << 16);
383+
HWREG(USB_0_OTGBASE + CPDMA_RX_CHANNEL_REG_B) =
384+
(CPPI_RX_SUBMIT_QUEUE << 0) |
385+
(CPPI_RX_SUBMIT_QUEUE << 16);
386+
HWREG(USB_0_OTGBASE + CPDMA_RX_CHANNEL_CONFIG_REG) =
387+
CPDMA_RX_GLOBAL_CHAN_CFG_ENABLE |
388+
CPDMA_RX_GLOBAL_CHAN_CFG_ERR_RETRY | // starvation = retry
389+
CPDMA_RX_GLOBAL_CHAN_CFG_DESC_TY | // "host" descriptors (the only valid type)
390+
RX_COMPQ1;
391+
392+
// CPPI TX
393+
HWREG(USB_0_OTGBASE + CPDMA_TX_CHANNEL_CONFIG_REG) =
394+
CPDMA_TX_GLOBAL_CHAN_CFG_ENABLE |
395+
TX_COMPQ1;
396+
}
397+
221398
// Helper function for dividing up buffers for EP0 and feeding the FIFO
222399
static void usb_setup_send_chunk(void) {
223400
unsigned int this_chunk_sz = pbdrv_usb_setup_data_to_send_sz;
@@ -349,7 +526,11 @@ static void usb_device_intr(void) {
349526
pbdrv_usb_is_usb_hs = false;
350527
}
351528

352-
// TODO: More tasks in the future
529+
// Set up all the CPPI DMA registers
530+
usb_reset_cppi_dma();
531+
532+
// queue RX descriptor
533+
usb_setup_rx_dma_desc();
353534
}
354535

355536
if (intr_src & USBOTG_INTR_EP0) {
@@ -399,11 +580,15 @@ static void usb_device_intr(void) {
399580
if (pbdrv_usb_config == 1) {
400581
// configuring
401582

402-
// TODO: Handle configuring
583+
// Reset data toggle, clear stall, flush fifo
584+
HWREGB(USB0_BASE + USB_O_TXCSRL1) = USB_TXCSRL1_CLRDT | USB_TXCSRL1_FLUSH;
585+
HWREGB(USB0_BASE + USB_O_RXCSRL1) = USB_RXCSRL1_CLRDT | USB_RXCSRL1_FLUSH;
403586
} else {
404587
// deconfiguring
405588

406-
// TODO: Handle deconfiguring
589+
// Set stall condition
590+
HWREGB(USB0_BASE + USB_O_TXCSRL1) = USB_TXCSRL1_STALL;
591+
HWREGB(USB0_BASE + USB_O_RXCSRL1) = USB_RXCSRL1_STALL;
407592
}
408593
handled = true;
409594
}
@@ -450,6 +635,48 @@ static void usb_device_intr(void) {
450635
}
451636
}
452637
break;
638+
639+
case BM_REQ_RECIP_EP:
640+
switch (setup_pkt.s.bRequest) {
641+
case GET_STATUS:
642+
if (setup_pkt.s.wIndex == 1) {
643+
pbdrv_usb_setup_misc_tx_byte = !!(HWREGB(USB0_BASE + USB_O_RXCSRL1) & USB_RXCSRL1_STALL);
644+
pbdrv_usb_setup_data_to_send = &pbdrv_usb_setup_misc_tx_byte;
645+
pbdrv_usb_setup_data_to_send_sz = 2;
646+
handled = true;
647+
} else if (setup_pkt.s.wIndex == 0x81) {
648+
pbdrv_usb_setup_misc_tx_byte = !!(HWREGB(USB0_BASE + USB_O_TXCSRL1) & USB_TXCSRL1_STALL);
649+
pbdrv_usb_setup_data_to_send = &pbdrv_usb_setup_misc_tx_byte;
650+
pbdrv_usb_setup_data_to_send_sz = 2;
651+
handled = true;
652+
}
653+
break;
654+
655+
case CLEAR_FEATURE:
656+
if (setup_pkt.s.wValue == 0) {
657+
if (setup_pkt.s.wIndex == 1) {
658+
HWREGB(USB0_BASE + USB_O_RXCSRL1) &= ~USB_RXCSRL1_STALL;
659+
handled = true;
660+
} else if (setup_pkt.s.wIndex == 0x81) {
661+
HWREGB(USB0_BASE + USB_O_TXCSRL1) &= ~USB_TXCSRL1_STALL;
662+
handled = true;
663+
}
664+
}
665+
break;
666+
667+
case SET_FEATURE:
668+
if (setup_pkt.s.wValue == 0) {
669+
if (setup_pkt.s.wIndex == 1) {
670+
HWREGB(USB0_BASE + USB_O_RXCSRL1) |= USB_RXCSRL1_STALL;
671+
handled = true;
672+
} else if (setup_pkt.s.wIndex == 0x81) {
673+
HWREGB(USB0_BASE + USB_O_TXCSRL1) |= USB_TXCSRL1_STALL;
674+
handled = true;
675+
}
676+
}
677+
break;
678+
}
679+
break;
453680
}
454681
}
455682

@@ -489,6 +716,61 @@ static void usb_device_intr(void) {
489716
}
490717
}
491718

719+
// EP1 interrupts, which only trigger on error conditions since we use DMA
720+
721+
if (intr_src & USBOTG_INTR_EP1_OUT) {
722+
// EP 1 OUT, host to device, rx
723+
uint8_t rxcsr = HWREGB(USB0_BASE + USB_O_RXCSRL1);
724+
725+
// Clear error bits
726+
rxcsr &= ~USB_RXCSRL1_STALLED;
727+
728+
HWREGB(USB0_BASE + USB_O_RXCSRL1) = rxcsr;
729+
}
730+
731+
if (intr_src & USBOTG_INTR_EP1_IN) {
732+
// EP 1 IN, device to host, tx
733+
uint8_t txcsr = HWREGB(USB0_BASE + USB_O_TXCSRL1);
734+
735+
// Clear error bits
736+
txcsr &= ~(USB_TXCSRL1_STALLED | USB_TXCSRL1_UNDRN | USB_TXCSRL1_FIFONE);
737+
738+
HWREGB(USB0_BASE + USB_O_TXCSRL1) = txcsr;
739+
}
740+
741+
// Check for DMA completions
742+
uint32_t dma_q_pend_0 = HWREG(USB_0_OTGBASE + CPDMA_PEND_0_REGISTER);
743+
744+
if (dma_q_pend_0 & (1 << RX_COMPQ1)) {
745+
// DMA for EP 1 OUT is done
746+
747+
// Pop the descriptor from the queue
748+
uint32_t qctrld = HWREG(USB_0_OTGBASE + CPDMA_QUEUE_REGISTER_D + RX_COMPQ1 * 16);
749+
(void)qctrld;
750+
751+
// Signal the main loop that we have something
752+
usb_rx_is_ready = true;
753+
pbio_os_request_poll();
754+
}
755+
756+
if (dma_q_pend_0 & (1 << TX_COMPQ1)) {
757+
// DMA for EP 1 IN is done
758+
759+
// Pop the descriptor from the queue
760+
uint32_t qctrld = HWREG(USB_0_OTGBASE + CPDMA_QUEUE_REGISTER_D + TX_COMPQ1 * 16) & ~CPDMA_QUEUE_REGISTER_DESC_SIZE_MASK;
761+
762+
if (qctrld == (uint32_t)(&cppi_descriptors[CPPI_DESC_TX_RESPONSE])) {
763+
usb_tx_response_is_not_ready = false;
764+
pbio_os_request_poll();
765+
} else if (qctrld == (uint32_t)(&cppi_descriptors[CPPI_DESC_TX_STATUS])) {
766+
usb_tx_status_is_not_ready = false;
767+
pbio_os_request_poll();
768+
} else if (qctrld == (uint32_t)(&cppi_descriptors[CPPI_DESC_TX_STDOUT])) {
769+
usb_tx_stdout_is_not_ready = false;
770+
pbio_os_request_poll();
771+
}
772+
}
773+
492774
HWREG(USB_0_OTGBASE + USB_0_INTR_SRC_CLEAR) = intr_src;
493775
HWREG(USB_0_OTGBASE + USB_0_END_OF_INTR) = 0;
494776
}
@@ -498,6 +780,41 @@ static pbio_os_process_t pbdrv_usb_ev3_process;
498780
static pbio_error_t pbdrv_usb_ev3_process_thread(pbio_os_state_t *state, void *context) {
499781
PBIO_OS_ASYNC_BEGIN(state);
500782

783+
for (;;) {
784+
PBIO_OS_AWAIT_UNTIL(state, usb_rx_is_ready);
785+
786+
if (usb_rx_is_ready) {
787+
// This barrier prevents *subsequent* memory reads from being
788+
// speculatively moved *earlier*, outside the if statement
789+
// (which is technically allowed by the as-if rule).
790+
pbdrv_compiler_memory_barrier();
791+
792+
uint32_t usb_rx_sz = cppi_descriptors[CPPI_DESC_RX].word0.pktLength;
793+
794+
// TODO: Remove this echo test
795+
unsigned int i;
796+
for (i = 0; i < usb_rx_sz; i++) {
797+
ep1_tx_response_buf[i] = ep1_rx_buf[i] + 1;
798+
}
799+
for (; i < 512; i++) {
800+
ep1_tx_response_buf[i] = 0xaa;
801+
}
802+
803+
(void)ep1_tx_status_buf;
804+
(void)ep1_tx_stdout_buf;
805+
806+
unsigned int tx_sz = pbdrv_usb_is_usb_hs ? PYBRICKS_EP_PKT_SZ_HS : PYBRICKS_EP_PKT_SZ_FS;
807+
if (!usb_tx_response_is_not_ready) {
808+
usb_tx_response_is_not_ready = true;
809+
usb_setup_tx_dma_desc(CPPI_DESC_TX_RESPONSE, ep1_tx_response_buf, tx_sz);
810+
}
811+
812+
// Re-queue RX buffer after processing is complete
813+
usb_rx_is_ready = false;
814+
usb_setup_rx_dma_desc();
815+
}
816+
}
817+
501818
PBIO_OS_ASYNC_END(PBIO_SUCCESS);
502819
}
503820

0 commit comments

Comments
 (0)