Skip to content

Commit 43a245d

Browse files
author
Mathias Sandner
committed
RP2040 host mode: Add initial handling of multiple non-interrupt endpoints
See also the comment at hathach#1434 (comment) . This commit introduces a new structure to save the logical information about an endpoint that takes turn being committed to the single hardware epx endpoint. Compared to using the interrupt endpoints, which is the approach of the referenced issue, this has the advantage that we can trigger a transfer to occur *now* by writing to SIE_CTRL instead of waiting for hardware to poll the interrupt descriptors after the next sof packet up to 1 ms in the future. It has the disadvantage that we don't get to setup multiple transfers in advance, there can only be one epx transfer that is being attempted. At the moment, this means that only one non-interrupt transfer can be outstanding at a time from the tinyusb user's perspective. It should be possible to implement software queuing of transfers to hide this from the user, but this is not being attempted in this commit.
1 parent b624664 commit 43a245d

File tree

1 file changed

+167
-65
lines changed

1 file changed

+167
-65
lines changed

src/portable/raspberrypi/rp2040/hcd_rp2040.c

Lines changed: 167 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,39 @@
5252
#endif
5353
static_assert(PICO_USB_HOST_INTERRUPT_ENDPOINTS <= USB_MAX_ENDPOINTS, "");
5454

55-
// Host mode uses one shared endpoint register for non-interrupt endpoint
56-
static struct hw_endpoint ep_pool[1 + PICO_USB_HOST_INTERRUPT_ENDPOINTS];
57-
#define epx (ep_pool[0])
55+
/* Information about an endpoint to be copied into a struct hw_endpoint, e. g.
56+
* for epx. */
57+
struct sw_endpoint {
58+
bool configured;
59+
uint8_t dev_addr;
60+
uint8_t ep_addr;
61+
uint16_t wMaxPacketSize;
62+
uint8_t transfer_type;
63+
uint8_t bmInterval; /* probably useless because this is not for interrupt endpoints? */
64+
uint8_t next_pid;
65+
};
66+
67+
// Host mode uses one shared endpoint register for non-interrupt endpoint, but
68+
// we need to store the endpoint type somewhere.
69+
static struct hw_endpoint epx = {
70+
.buffer_control = &usbh_dpram->epx_buf_ctrl,
71+
.endpoint_control = &usbh_dpram->epx_ctrl,
72+
.hw_data_buf = &usbh_dpram->epx_data[0]
73+
};
74+
static struct sw_endpoint sw_ep_pool[USB_MAX_ENDPOINTS];
75+
76+
static struct sw_endpoint sw_ep_ctrl = {
77+
.configured = true,
78+
.dev_addr = 0, /* dynamic */
79+
.ep_addr = 0, /* dynamic */
80+
.wMaxPacketSize = 64,
81+
.transfer_type = TUSB_XFER_CONTROL,
82+
.bmInterval = 0,
83+
.next_pid = 1
84+
};
85+
86+
// pool for interrupt endpoints
87+
static struct hw_endpoint ep_pool[PICO_USB_HOST_INTERRUPT_ENDPOINTS];
5888

5989
#define usb_hw_set hw_set_alias(usb_hw)
6090
#define usb_hw_clear hw_clear_alias(usb_hw)
@@ -65,12 +95,29 @@ enum {
6595
USB_SIE_CTRL_PULLDOWN_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS
6696
};
6797

68-
static struct hw_endpoint *get_dev_ep(uint8_t dev_addr, uint8_t ep_addr)
98+
static struct sw_endpoint *get_dev_sw_ep(uint8_t dev_addr, uint8_t ep_addr)
6999
{
70-
uint8_t num = tu_edpt_number(ep_addr);
71-
if ( num == 0 ) return &epx;
100+
uint8_t num = tu_edpt_number(ep_addr);
101+
102+
if (num == 0) {
103+
sw_ep_ctrl.dev_addr = dev_addr;
104+
sw_ep_ctrl.ep_addr = ep_addr;
105+
return &sw_ep_ctrl;
106+
}
107+
108+
for (uint32_t i = 0; i < TU_ARRAY_SIZE(sw_ep_pool); i++) {
109+
struct sw_endpoint *ep = &sw_ep_pool[i];
110+
if (ep->configured && ep->dev_addr == dev_addr && ep->ep_addr == ep_addr) {
111+
return ep;
112+
}
113+
}
114+
115+
return NULL;
116+
}
72117

73-
for ( uint32_t i = 1; i < TU_ARRAY_SIZE(ep_pool); i++ )
118+
static struct hw_endpoint *get_dev_hw_ep(uint8_t dev_addr, uint8_t ep_addr)
119+
{
120+
for ( uint32_t i = 0; i < TU_ARRAY_SIZE(ep_pool); i++ )
74121
{
75122
struct hw_endpoint *ep = &ep_pool[i];
76123
if ( ep->configured && (ep->dev_addr == dev_addr) && (ep->ep_addr == ep_addr) ) return ep;
@@ -273,31 +320,44 @@ static struct hw_endpoint *_hw_endpoint_allocate(uint8_t transfer_type)
273320
{
274321
struct hw_endpoint *ep = NULL;
275322

276-
if (transfer_type == TUSB_XFER_INTERRUPT)
277-
{
278-
ep = _next_free_interrupt_ep();
279-
pico_info("Allocate interrupt ep %d\n", ep->interrupt_num);
280-
assert(ep);
281-
ep->buffer_control = &usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num].ctrl;
282-
ep->endpoint_control = &usbh_dpram->int_ep_ctrl[ep->interrupt_num].ctrl;
283-
// 0 for epx (double buffered): TODO increase to 1024 for ISO
284-
// 2x64 for intep0
285-
// 3x64 for intep1
286-
// etc
287-
ep->hw_data_buf = &usbh_dpram->epx_data[64 * (ep->interrupt_num + 2)];
288-
}
289-
else
290-
{
291-
ep = &epx;
292-
ep->buffer_control = &usbh_dpram->epx_buf_ctrl;
293-
ep->endpoint_control = &usbh_dpram->epx_ctrl;
294-
ep->hw_data_buf = &usbh_dpram->epx_data[0];
295-
}
323+
ep = _next_free_interrupt_ep();
324+
pico_info("Allocate interrupt ep %d\n", ep->interrupt_num);
325+
assert(ep);
326+
ep->buffer_control = &usbh_dpram->int_ep_buffer_ctrl[ep->interrupt_num].ctrl;
327+
ep->endpoint_control = &usbh_dpram->int_ep_ctrl[ep->interrupt_num].ctrl;
328+
// 0 for epx (double buffered): TODO increase to 1024 for ISO
329+
// 2x64 for intep0
330+
// 3x64 for intep1
331+
// etc
332+
ep->hw_data_buf = &usbh_dpram->epx_data[64 * (ep->interrupt_num + 2)];
296333

297334
return ep;
298335
}
299336

300-
static void _hw_endpoint_init(struct hw_endpoint *ep, uint8_t dev_addr, uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type, uint8_t bmInterval)
337+
static struct sw_endpoint *_sw_endpoint_allocate(void)
338+
{
339+
for (uint i = 0; i < TU_ARRAY_SIZE(sw_ep_pool); i++) {
340+
if (!sw_ep_pool[i].configured) {
341+
return &sw_ep_pool[i];
342+
}
343+
}
344+
return NULL;
345+
}
346+
347+
static void _sw_endpoint_restore_epx(void) {
348+
uint8_t const num_old = tu_edpt_number(epx.ep_addr);
349+
350+
if (num_old != 0) {
351+
/* previous use of epx was not for control transfer: save next_pid
352+
* toggle state in sw endpoint */
353+
struct sw_endpoint *eps = get_dev_sw_ep(epx.dev_addr, epx.ep_addr);
354+
if (eps != NULL) {
355+
eps->next_pid = epx.next_pid;
356+
}
357+
}
358+
}
359+
360+
static void _hw_endpoint_init(struct hw_endpoint *ep, uint8_t dev_addr, uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type, uint8_t bmInterval, uint8_t next_pid)
301361
{
302362
// Already has data buffer, endpoint control, and buffer control allocated at this point
303363
assert(ep->endpoint_control);
@@ -313,8 +373,7 @@ static void _hw_endpoint_init(struct hw_endpoint *ep, uint8_t dev_addr, uint8_t
313373
// For host, IN to host == RX, anything else rx == false
314374
ep->rx = (dir == TUSB_DIR_IN);
315375

316-
// Response to a setup packet on EP0 starts with pid of 1
317-
ep->next_pid = (num == 0 ? 1u : 0u);
376+
ep->next_pid = next_pid;
318377
ep->wMaxPacketSize = wMaxPacketSize;
319378
ep->transfer_type = transfer_type;
320379

@@ -462,6 +521,12 @@ void hcd_device_close(uint8_t rhport, uint8_t dev_addr)
462521
hw_endpoint_reset_transfer(ep);
463522
}
464523
}
524+
525+
for (size_t i = 1; i < TU_ARRAY_SIZE(sw_ep_pool); i++) {
526+
if (sw_ep_pool[i].dev_addr == dev_addr) {
527+
sw_ep_pool[i].configured = false;
528+
}
529+
}
465530
}
466531

467532
uint32_t hcd_frame_number(uint8_t rhport)
@@ -491,20 +556,39 @@ void hcd_int_disable(uint8_t rhport)
491556

492557
bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc)
493558
{
559+
uint8_t transfer_type;
494560
(void) rhport;
495561

496562
pico_trace("hcd_edpt_open dev_addr %d, ep_addr %d\n", dev_addr, ep_desc->bEndpointAddress);
497563

498564
// Allocated differently based on if it's an interrupt endpoint or not
499-
struct hw_endpoint *ep = _hw_endpoint_allocate(ep_desc->bmAttributes.xfer);
500-
TU_ASSERT(ep);
501-
502-
_hw_endpoint_init(ep,
503-
dev_addr,
504-
ep_desc->bEndpointAddress,
505-
tu_edpt_packet_size(ep_desc),
506-
ep_desc->bmAttributes.xfer,
507-
ep_desc->bInterval);
565+
transfer_type = ep_desc->bmAttributes.xfer;
566+
if (transfer_type == TUSB_XFER_INTERRUPT) {
567+
struct hw_endpoint *ep = _hw_endpoint_allocate(ep_desc->bmAttributes.xfer);
568+
TU_ASSERT(ep);
569+
570+
_hw_endpoint_init(ep,
571+
dev_addr,
572+
ep_desc->bEndpointAddress,
573+
tu_edpt_packet_size(ep_desc),
574+
transfer_type,
575+
ep_desc->bInterval,
576+
0);
577+
} else if (transfer_type != TUSB_XFER_CONTROL) {
578+
struct sw_endpoint *ep = _sw_endpoint_allocate();
579+
TU_ASSERT(ep);
580+
581+
/* Only remember information about endpoint, will be configured into HW
582+
* epx for each transfer. */
583+
ep->configured = true;
584+
ep->dev_addr = dev_addr;
585+
ep->ep_addr = ep_desc->bEndpointAddress;
586+
ep->wMaxPacketSize = tu_edpt_packet_size(ep_desc);
587+
ep->transfer_type = transfer_type;
588+
ep->bmInterval = ep_desc->bInterval;
589+
} else {
590+
/* control endpoint is implicitly open (sw_ep_ctrl) */
591+
}
508592

509593
return true;
510594
}
@@ -514,48 +598,65 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *
514598
(void) rhport;
515599

516600
pico_trace("hcd_edpt_xfer dev_addr %d, ep_addr 0x%x, len %d\n", dev_addr, ep_addr, buflen);
517-
601+
518602
uint8_t const ep_num = tu_edpt_number(ep_addr);
519603
tusb_dir_t const ep_dir = tu_edpt_dir(ep_addr);
520604

521-
// Get appropriate ep. Either EPX or interrupt endpoint
522-
struct hw_endpoint *ep = get_dev_ep(dev_addr, ep_addr);
523-
TU_ASSERT(ep);
605+
// Get appropriate ep
606+
struct hw_endpoint *ep = get_dev_hw_ep(dev_addr, ep_addr);
607+
if (ep != NULL) {
608+
// EP should be inactive
609+
assert(!ep->active);
524610

525-
// EP should be inactive
526-
assert(!ep->active);
611+
// Interrupt ep registers should already be configured
612+
hw_endpoint_xfer_start(ep, buffer, buflen);
613+
return true;
614+
}
527615

528-
// Control endpoint can change direction 0x00 <-> 0x80
529-
if ( ep_addr != ep->ep_addr )
530-
{
531-
assert(ep_num == 0);
532616

533-
// Direction has flipped on endpoint control so re init it but with same properties
534-
_hw_endpoint_init(ep, dev_addr, ep_addr, ep->wMaxPacketSize, ep->transfer_type, 0);
535-
}
617+
struct sw_endpoint *eps = get_dev_sw_ep(dev_addr, ep_addr);
618+
if (eps != NULL) {
619+
ep = &epx;
620+
621+
/* TODO: Handle multiple outstanding requests to different sw
622+
* endpoints. */
623+
assert(!ep->active);
624+
625+
/* configure shared epx hw endpoint with sw values */
626+
if (ep->dev_addr != eps->dev_addr || ep->ep_addr != eps->ep_addr) {
627+
_sw_endpoint_restore_epx();
628+
_hw_endpoint_init(
629+
ep,
630+
dev_addr,
631+
ep_addr,
632+
eps->wMaxPacketSize,
633+
eps->transfer_type,
634+
eps->bmInterval,
635+
eps->next_pid
636+
);
637+
}
536638

537-
// If a normal transfer (non-interrupt) then initiate using
538-
// sie ctrl registers. Otherwise interrupt ep registers should
539-
// already be configured
540-
if (ep == &epx) {
541639
hw_endpoint_xfer_start(ep, buffer, buflen);
542640

543641
// That has set up buffer control, endpoint control etc
544642
// for host we have to initiate the transfer
545643
usb_hw->dev_addr_ctrl = (uint32_t) (dev_addr | (ep_num << USB_ADDR_ENDP_ENDPOINT_LSB));
546644

547-
uint32_t flags = USB_SIE_CTRL_START_TRANS_BITS | SIE_CTRL_BASE |
548-
(ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS);
645+
uint32_t flags = SIE_CTRL_BASE;
646+
flags |= ep_dir ? USB_SIE_CTRL_RECEIVE_DATA_BITS : USB_SIE_CTRL_SEND_DATA_BITS;
549647
// Set pre if we are a low speed device on full speed hub
550648
flags |= need_pre(dev_addr) ? USB_SIE_CTRL_PREAMBLE_EN_BITS : 0;
551649

650+
/* This seems to be required because otherwise the controller sometimes
651+
* ignores the request to start our transaction (or starts the wrong
652+
* kind of transaction, who knows). */
552653
usb_hw->sie_ctrl = flags;
553-
}else
554-
{
555-
hw_endpoint_xfer_start(ep, buffer, buflen);
654+
655+
usb_hw->sie_ctrl = flags | USB_SIE_CTRL_START_TRANS_BITS;
656+
return true;
556657
}
557658

558-
return true;
659+
return false;
559660
}
560661

561662
bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8])
@@ -568,15 +669,16 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet
568669
usbh_dpram->setup_packet[i] = setup_packet[i];
569670
}
570671

571-
// Configure EP0 struct with setup info for the trans complete
572-
struct hw_endpoint *ep = _hw_endpoint_allocate(0);
672+
struct hw_endpoint *ep = &epx;
573673
TU_ASSERT(ep);
574674

575675
// EPX should be inactive
576676
assert(!ep->active);
577677

578678
// EP0 out
579-
_hw_endpoint_init(ep, dev_addr, 0x00, ep->wMaxPacketSize, 0, 0);
679+
// Response to a setup packet on EP0 starts with pid of 1
680+
_sw_endpoint_restore_epx();
681+
_hw_endpoint_init(ep, dev_addr, 0x00, ep->wMaxPacketSize, 0, 0, 1);
580682
assert(ep->configured);
581683

582684
ep->remaining_len = 8;

0 commit comments

Comments
 (0)