diff options
| author | hathach <[email protected]> | 2026-07-09 23:38:08 +0700 |
|---|---|---|
| committer | hathach <[email protected]> | 2026-07-09 23:38:08 +0700 |
| commit | 33cabfe3f045a89ffdcfadce6ed5331c8b7bc870 (patch) | |
| tree | 7bf81b9ed79db4ea475757706e776d3159be8ef7 | |
| parent | fcd5a0603e3588cbf4b33f0335da2b630dc76368 (diff) | |
class/vendor: add interrupt/iso endpoint pairs and alt-setting support
Non-buffered per-type source/sink endpoints (bulk/int/iso) with manual RX
arming across altsettings.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
| -rw-r--r-- | src/class/vendor/vendor_device.c | 597 | ||||
| -rw-r--r-- | src/class/vendor/vendor_device.h | 161 |
2 files changed, 739 insertions, 19 deletions
diff --git a/src/class/vendor/vendor_device.c b/src/class/vendor/vendor_device.c index b3537b665..24f1405dc 100644 --- a/src/class/vendor/vendor_device.c +++ b/src/class/vendor/vendor_device.c @@ -21,6 +21,26 @@ typedef struct { uint8_t rhport; uint8_t itf_num; + #if CFG_TUD_VENDOR_EP_INT_OUT + uint8_t ep_int_out; + uint16_t int_rx_xfer_len; + #endif + #if CFG_TUD_VENDOR_EP_INT_IN + uint8_t ep_int_in; + #endif + #if CFG_TUD_VENDOR_EP_ISO_OUT + uint8_t ep_iso_out; + uint16_t iso_rx_xfer_len; + #endif + #if CFG_TUD_VENDOR_EP_ISO_IN + uint8_t ep_iso_in; + #endif + #if CFG_TUD_VENDOR_ALT_SETTINGS // implies non-buffered: fields cleared by bus reset + uint8_t cur_alt; + const uint8_t* p_itf_desc; // whole interface block incl. all altsettings (static app descriptor) + uint16_t itf_desc_len; + #endif + #if CFG_TUD_VENDOR_TXRX_BUFFERED /*------------- From this point, data is not cleared by bus reset -------------*/ tu_edpt_stream_t tx_stream; @@ -35,7 +55,10 @@ typedef struct { } vendord_interface_t; #if CFG_TUD_VENDOR_TXRX_BUFFERED - #define ITF_MEM_RESET_SIZE (offsetof(vendord_interface_t, itf_num) + TU_FIELD_SIZE(vendord_interface_t, itf_num)) + // The reset region is everything before the streams; tx_stream is the first preserved field + // (see the struct comment), so its offset is exactly that boundary regardless of which endpoint + // gates are enabled. + #define ITF_MEM_RESET_SIZE offsetof(vendord_interface_t, tx_stream) #else #define ITF_MEM_RESET_SIZE sizeof(vendord_interface_t) #endif @@ -52,6 +75,32 @@ typedef struct { CFG_TUD_MEM_SECTION static vendord_epbuf_t _vendord_epbuf[CFG_TUD_VENDOR]; #endif +#if CFG_TUD_VENDOR_EP_INT_OUT || CFG_TUD_VENDOR_EP_INT_IN +typedef struct { + #if CFG_TUD_VENDOR_EP_INT_OUT + TUD_EPBUF_DEF(int_out, CFG_TUD_VENDOR_EP_INT_OUT_BUFSIZE); + #endif + #if CFG_TUD_VENDOR_EP_INT_IN + TUD_EPBUF_DEF(int_in, CFG_TUD_VENDOR_EP_INT_IN_BUFSIZE); + #endif +} vendord_int_epbuf_t; + +CFG_TUD_MEM_SECTION static vendord_int_epbuf_t _vendord_int_epbuf[CFG_TUD_VENDOR]; +#endif + +#if CFG_TUD_VENDOR_EP_ISO_OUT || CFG_TUD_VENDOR_EP_ISO_IN +typedef struct { + #if CFG_TUD_VENDOR_EP_ISO_OUT + TUD_EPBUF_DEF(iso_out, CFG_TUD_VENDOR_EP_ISO_OUT_BUFSIZE); + #endif + #if CFG_TUD_VENDOR_EP_ISO_IN + TUD_EPBUF_DEF(iso_in, CFG_TUD_VENDOR_EP_ISO_IN_BUFSIZE); + #endif +} vendord_iso_epbuf_t; + +CFG_TUD_MEM_SECTION static vendord_iso_epbuf_t _vendord_iso_epbuf[CFG_TUD_VENDOR]; +#endif + //--------------------------------------------------------------------+ // Weak stubs: invoked if no strong implementation is available //--------------------------------------------------------------------+ @@ -66,15 +115,61 @@ TU_ATTR_WEAK void tud_vendor_tx_cb(uint8_t idx, uint32_t sent_bytes) { (void) sent_bytes; } +#if CFG_TUD_VENDOR_EP_INT_OUT +TU_ATTR_WEAK void tud_vendor_int_rx_cb(uint8_t idx, const uint8_t *buffer, uint32_t bufsize) { + (void)idx; + (void)buffer; + (void)bufsize; +} +#endif + +#if CFG_TUD_VENDOR_EP_INT_IN +TU_ATTR_WEAK void tud_vendor_int_tx_cb(uint8_t idx, uint32_t sent_bytes) { + (void)idx; + (void)sent_bytes; +} +#endif + +#if CFG_TUD_VENDOR_EP_ISO_OUT +TU_ATTR_WEAK void tud_vendor_iso_rx_cb(uint8_t idx, const uint8_t *buffer, uint32_t bufsize) { + (void)idx; + (void)buffer; + (void)bufsize; +} +#endif + +#if CFG_TUD_VENDOR_EP_ISO_IN +TU_ATTR_WEAK void tud_vendor_iso_tx_cb(uint8_t idx, uint32_t sent_bytes) { + (void)idx; + (void)sent_bytes; +} +#endif + bool tud_vendor_n_mounted(uint8_t idx) { TU_VERIFY(idx < CFG_TUD_VENDOR); vendord_interface_t *p_itf = &_vendord_itf[idx]; + // bulk may be absent (interrupt-only vendor interface): count the interrupt endpoints too #if CFG_TUD_VENDOR_TXRX_BUFFERED - return (p_itf->rx_stream.ep_addr != 0) || (p_itf->tx_stream.ep_addr != 0); + bool mounted = (p_itf->rx_stream.ep_addr != 0) || (p_itf->tx_stream.ep_addr != 0); #else - return (p_itf->ep_out != 0) || (p_itf->ep_in != 0); + bool mounted = (p_itf->ep_out != 0) || (p_itf->ep_in != 0); + #endif + #if CFG_TUD_VENDOR_EP_INT_OUT + mounted = mounted || (p_itf->ep_int_out != 0); #endif + #if CFG_TUD_VENDOR_EP_INT_IN + mounted = mounted || (p_itf->ep_int_in != 0); + #endif + // an altsetting may expose only isochronous endpoints; count them so apps that gate an iso + // pump on tud_vendor_mounted() still arm it + #if CFG_TUD_VENDOR_EP_ISO_OUT + mounted = mounted || (p_itf->ep_iso_out != 0); + #endif + #if CFG_TUD_VENDOR_EP_ISO_IN + mounted = mounted || (p_itf->ep_iso_in != 0); + #endif + return mounted; } //--------------------------------------------------------------------+ @@ -107,6 +202,30 @@ void tud_vendor_n_read_flush(uint8_t idx) { } #endif +// Shared non-buffered transfer helpers for the bulk / interrupt / isochronous endpoints, which are +// identical apart from the endpoint, its epbuf and its buffer size. TU_ATTR_UNUSED: in buffered +// mode with the int/iso gates off none is referenced, and clang/IAR error on an unused static. +TU_ATTR_UNUSED static inline uint32_t vendord_ep_write(vendord_interface_t *p_itf, uint8_t ep, uint8_t *epbuf, + uint32_t bufsize, const void *buffer, uint32_t len) { + TU_VERIFY(ep > 0, 0); // must be opened + TU_VERIFY(usbd_edpt_claim(p_itf->rhport, ep), 0); + const uint32_t xact_len = tu_min32(len, bufsize); + memcpy(epbuf, buffer, xact_len); + TU_ASSERT(usbd_edpt_xfer(p_itf->rhport, ep, epbuf, (uint16_t) xact_len, false), 0); + return xact_len; +} + +TU_ATTR_UNUSED static inline uint32_t vendord_ep_write_available(vendord_interface_t *p_itf, uint8_t ep, uint32_t bufsize) { + TU_VERIFY(ep > 0, 0); // must be opened + return usbd_edpt_busy(p_itf->rhport, ep) ? 0 : bufsize; +} + +TU_ATTR_UNUSED static inline bool vendord_ep_read_xfer(vendord_interface_t *p_itf, uint8_t ep, uint8_t *epbuf, uint16_t xfer_len) { + TU_VERIFY(ep > 0); // must be opened + TU_VERIFY(usbd_edpt_claim(p_itf->rhport, ep)); + return usbd_edpt_xfer(p_itf->rhport, ep, epbuf, xfer_len, false); +} + #if CFG_TUD_VENDOR_RX_MANUAL_XFER bool tud_vendor_n_read_xfer(uint8_t idx) { TU_VERIFY(idx < CFG_TUD_VENDOR); @@ -116,9 +235,8 @@ bool tud_vendor_n_read_xfer(uint8_t idx) { return tu_edpt_stream_read_xfer(&p_itf->rx_stream); #else - // Non-FIFO mode - TU_VERIFY(usbd_edpt_claim(p_itf->rhport, p_itf->ep_out)); - return usbd_edpt_xfer(p_itf->rhport, p_itf->ep_out, _vendord_epbuf[idx].epout, p_itf->rx_xfer_len, false); + // Non-FIFO mode (0 while an altsetting without a bulk OUT ep is active) + return vendord_ep_read_xfer(p_itf, p_itf->ep_out, _vendord_epbuf[idx].epout, p_itf->rx_xfer_len); #endif } #endif @@ -135,12 +253,8 @@ uint32_t tud_vendor_n_write(uint8_t idx, const void *buffer, uint32_t bufsize) { return tu_edpt_stream_write(&p_itf->tx_stream, buffer, (uint16_t)bufsize); #else - // non-fifo mode: direct transfer - TU_VERIFY(usbd_edpt_claim(p_itf->rhport, p_itf->ep_in), 0); - const uint32_t xact_len = tu_min32(bufsize, CFG_TUD_VENDOR_TX_EPSIZE); - memcpy(_vendord_epbuf[idx].epin, buffer, xact_len); - TU_ASSERT(usbd_edpt_xfer(p_itf->rhport, p_itf->ep_in, _vendord_epbuf[idx].epin, (uint16_t)xact_len, false), 0); - return xact_len; + // non-fifo mode: direct transfer (ep_in is 0 while an altsetting without a bulk IN ep is active) + return vendord_ep_write(p_itf, p_itf->ep_in, _vendord_epbuf[idx].epin, CFG_TUD_VENDOR_TX_EPSIZE, buffer, bufsize); #endif } @@ -152,9 +266,7 @@ uint32_t tud_vendor_n_write_available(uint8_t idx) { return tu_edpt_stream_write_available(&p_itf->tx_stream); #else - // Non-FIFO mode - TU_VERIFY(p_itf->ep_in > 0, 0); // must be opened - return usbd_edpt_busy(p_itf->rhport, p_itf->ep_in) ? 0 : CFG_TUD_VENDOR_TX_EPSIZE; + return vendord_ep_write_available(p_itf, p_itf->ep_in, CFG_TUD_VENDOR_TX_EPSIZE); #endif } @@ -174,6 +286,63 @@ bool tud_vendor_n_write_clear(uint8_t idx) { #endif //--------------------------------------------------------------------+ +// Interrupt endpoint API +//--------------------------------------------------------------------+ +#if CFG_TUD_VENDOR_EP_INT_OUT +bool tud_vendor_n_int_read_xfer(uint8_t idx) { + TU_VERIFY(idx < CFG_TUD_VENDOR); + vendord_interface_t *p_itf = &_vendord_itf[idx]; + return vendord_ep_read_xfer(p_itf, p_itf->ep_int_out, _vendord_int_epbuf[idx].int_out, p_itf->int_rx_xfer_len); +} +#endif + +#if CFG_TUD_VENDOR_EP_INT_IN +uint32_t tud_vendor_n_int_write(uint8_t idx, const void *buffer, uint32_t bufsize) { + TU_VERIFY(idx < CFG_TUD_VENDOR, 0); + vendord_interface_t *p_itf = &_vendord_itf[idx]; + return vendord_ep_write(p_itf, p_itf->ep_int_in, _vendord_int_epbuf[idx].int_in, CFG_TUD_VENDOR_EP_INT_IN_BUFSIZE, buffer, bufsize); +} + +uint32_t tud_vendor_n_int_write_available(uint8_t idx) { + TU_VERIFY(idx < CFG_TUD_VENDOR, 0); + vendord_interface_t *p_itf = &_vendord_itf[idx]; + return vendord_ep_write_available(p_itf, p_itf->ep_int_in, CFG_TUD_VENDOR_EP_INT_IN_BUFSIZE); +} +#endif + +//--------------------------------------------------------------------+ +// Isochronous endpoint API +//--------------------------------------------------------------------+ +#if CFG_TUD_VENDOR_EP_ISO_OUT +bool tud_vendor_n_iso_read_xfer(uint8_t idx) { + TU_VERIFY(idx < CFG_TUD_VENDOR); + vendord_interface_t *p_itf = &_vendord_itf[idx]; + return vendord_ep_read_xfer(p_itf, p_itf->ep_iso_out, _vendord_iso_epbuf[idx].iso_out, p_itf->iso_rx_xfer_len); +} +#endif + +#if CFG_TUD_VENDOR_EP_ISO_IN +uint32_t tud_vendor_n_iso_write(uint8_t idx, const void *buffer, uint32_t bufsize) { + TU_VERIFY(idx < CFG_TUD_VENDOR, 0); + vendord_interface_t *p_itf = &_vendord_itf[idx]; + return vendord_ep_write(p_itf, p_itf->ep_iso_in, _vendord_iso_epbuf[idx].iso_in, CFG_TUD_VENDOR_EP_ISO_IN_BUFSIZE, buffer, bufsize); +} + +uint32_t tud_vendor_n_iso_write_available(uint8_t idx) { + TU_VERIFY(idx < CFG_TUD_VENDOR, 0); + vendord_interface_t *p_itf = &_vendord_itf[idx]; + return vendord_ep_write_available(p_itf, p_itf->ep_iso_in, CFG_TUD_VENDOR_EP_ISO_IN_BUFSIZE); +} +#endif + +#if CFG_TUD_VENDOR_ALT_SETTINGS +uint8_t tud_vendor_n_alt(uint8_t idx) { + TU_VERIFY(idx < CFG_TUD_VENDOR, 0); + return _vendord_itf[idx].cur_alt; +} +#endif + +//--------------------------------------------------------------------+ // USBD Driver API //--------------------------------------------------------------------+ void vendord_init(void) { @@ -232,17 +401,61 @@ static uint8_t find_vendor_itf(uint8_t ep_addr) { for (uint8_t idx = 0; idx < CFG_TUD_VENDOR; idx++) { const vendord_interface_t *p_vendor = &_vendord_itf[idx]; if (ep_addr == 0) { - // find unused: require both ep == 0 - #if CFG_TUD_VENDOR_TXRX_BUFFERED - if (p_vendor->rx_stream.ep_addr == 0 && p_vendor->tx_stream.ep_addr == 0) { + // find unused interface slot + #if CFG_TUD_VENDOR_ALT_SETTINGS + // an opened interface parked in an altsetting without endpoints (the mandatory empty alt 0) + // has all ep fields 0, so the endpoint fields cannot distinguish free from open: use p_itf_desc + if (p_vendor->p_itf_desc == NULL) { + return idx; + } + #elif CFG_TUD_VENDOR_TXRX_BUFFERED + // A slot is free only if none of its endpoints are assigned; bulk may be absent + // (an interrupt-only vendor interface), so check the interrupt endpoints too. + if (p_vendor->rx_stream.ep_addr == 0 && p_vendor->tx_stream.ep_addr == 0 + #if CFG_TUD_VENDOR_EP_INT_OUT + && p_vendor->ep_int_out == 0 + #endif + #if CFG_TUD_VENDOR_EP_INT_IN + && p_vendor->ep_int_in == 0 + #endif + ) { return idx; } #else - if (p_vendor->ep_out == 0 && p_vendor->ep_in == 0) { + // A slot is free only if none of its endpoints are assigned. Bulk may be absent (an + // interrupt-only vendor interface), so the interrupt endpoints must be checked too. + if (p_vendor->ep_out == 0 && p_vendor->ep_in == 0 + #if CFG_TUD_VENDOR_EP_INT_OUT + && p_vendor->ep_int_out == 0 + #endif + #if CFG_TUD_VENDOR_EP_INT_IN + && p_vendor->ep_int_in == 0 + #endif + ) { return idx; } #endif } else { + #if CFG_TUD_VENDOR_EP_INT_OUT + if (ep_addr == p_vendor->ep_int_out) { + return idx; + } + #endif + #if CFG_TUD_VENDOR_EP_INT_IN + if (ep_addr == p_vendor->ep_int_in) { + return idx; + } + #endif + #if CFG_TUD_VENDOR_EP_ISO_OUT + if (ep_addr == p_vendor->ep_iso_out) { + return idx; + } + #endif + #if CFG_TUD_VENDOR_EP_ISO_IN + if (ep_addr == p_vendor->ep_iso_in) { + return idx; + } + #endif #if CFG_TUD_VENDOR_TXRX_BUFFERED if (ep_addr == p_vendor->rx_stream.ep_addr || ep_addr == p_vendor->tx_stream.ep_addr) { return idx; @@ -257,6 +470,266 @@ static uint8_t find_vendor_itf(uint8_t ep_addr) { return 0xff; } +#if CFG_TUD_VENDOR_ALT_SETTINGS + +// Reserve an isochronous endpoint at open time. Ports with a dedicated iso allocator +// (TUP_DCD_EDPT_ISO_ALLOC) reserve the FIFO here and (re)activate on altsetting selection; +// ports with dcd_edpt_close instead open it once here (open == allocate + activate). +static inline bool vendord_iso_ep_alloc(uint8_t rhport, const tusb_desc_endpoint_t* desc_ep) { + #ifdef TUP_DCD_EDPT_ISO_ALLOC + return usbd_edpt_iso_alloc(rhport, desc_ep->bEndpointAddress, tu_edpt_packet_size(desc_ep)); + #else + return usbd_edpt_open(rhport, desc_ep); + #endif +} + +// (Re)activate an isochronous endpoint on altsetting selection. +static inline bool vendord_iso_ep_activate(uint8_t rhport, const tusb_desc_endpoint_t* desc_ep) { + #ifdef TUP_DCD_EDPT_ISO_ALLOC + return usbd_edpt_iso_activate(rhport, desc_ep); // resets ep_status, aborting any stale transfer + #else + // No iso alloc/activate API: close (which zeros ep_status and frees a stale claim left by a + // prior selection) then re-open, so a re-selected altsetting starts from a clean state. + usbd_edpt_close(rhport, desc_ep->bEndpointAddress); + return usbd_edpt_open(rhport, desc_ep); + #endif +} + +// Abort any in-flight transfer on a tracked endpoint and release its usbd claim; no-op for an +// unset (0) address. stall disables the endpoint in the dcd, clear-stall resets it to DATA0. +static inline void vendord_abort_ep(uint8_t rhport, uint8_t ep_addr) { + if (ep_addr) { + usbd_edpt_stall(rhport, ep_addr); + usbd_edpt_clear_stall(rhport, ep_addr); + } +} + +// Select an altsetting. Endpoints were hardware-opened once at vendord_open (dcds like +// dwc2 allocate FIFO linearly and cannot close/re-open endpoints dynamically): switching +// only re-targets the API to the selected altsetting's endpoints. Bulk/interrupt +// endpoints get a stall/clear-stall cycle, which portably aborts any in-flight transfer +// (stall disables the endpoint in the dcd) and resets the data toggle to DATA0 as +// SET_INTERFACE requires. Isochronous endpoints are (re)activated, which does the same. +// Single pass: the current altsetting's endpoints are dropped only once the target altsetting +// is confirmed present, so a SET_INTERFACE to an unknown alt leaves the interface intact. +static bool vendord_set_alt(uint8_t rhport, uint8_t idx, uint8_t alt) { + vendord_interface_t *p_vendor = &_vendord_itf[idx]; + const uint8_t* p_desc = p_vendor->p_itf_desc; + const uint8_t* desc_end = p_desc + p_vendor->itf_desc_len; + bool in_target_alt = false; + bool alt_found = false; + + while (tu_desc_in_bounds(p_desc, desc_end)) { + const uint8_t desc_type = tu_desc_type(p_desc); + if (desc_type == TUSB_DESC_INTERFACE) { + in_target_alt = (((const tusb_desc_interface_t*)p_desc)->bAlternateSetting == alt); + if (in_target_alt && !alt_found) { + alt_found = true; + // target altsetting confirmed present: abort then drop the previous altsetting's endpoints, + // so a bulk/interrupt endpoint absent from the target altsetting can't stay armed and keep + // its usbd claim in the dcd. (Endpoints the target altsetting reuses are reset again below; + // a double reset is harmless. Iso endpoints are re-activated on reselection.) + vendord_abort_ep(rhport, p_vendor->ep_in); + vendord_abort_ep(rhport, p_vendor->ep_out); + #if CFG_TUD_VENDOR_EP_INT_OUT + vendord_abort_ep(rhport, p_vendor->ep_int_out); + #endif + #if CFG_TUD_VENDOR_EP_INT_IN + vendord_abort_ep(rhport, p_vendor->ep_int_in); + #endif + p_vendor->ep_in = 0; + p_vendor->ep_out = 0; + #if CFG_TUD_VENDOR_EP_INT_OUT + p_vendor->ep_int_out = 0; + #endif + #if CFG_TUD_VENDOR_EP_INT_IN + p_vendor->ep_int_in = 0; + #endif + #if CFG_TUD_VENDOR_EP_ISO_OUT + p_vendor->ep_iso_out = 0; + #endif + #if CFG_TUD_VENDOR_EP_ISO_IN + p_vendor->ep_iso_in = 0; + #endif + } + } else if (in_target_alt && desc_type == TUSB_DESC_ENDPOINT) { + const tusb_desc_endpoint_t* desc_ep = (const tusb_desc_endpoint_t*) p_desc; + const uint8_t ep_addr = desc_ep->bEndpointAddress; + const bool is_in = tu_edpt_dir(ep_addr) == TUSB_DIR_IN; + (void) is_in; + + switch (desc_ep->bmAttributes.xfer) { + case TUSB_XFER_BULK: + // abort in-flight transfer + reset data toggle + usbd_edpt_stall(rhport, ep_addr); + usbd_edpt_clear_stall(rhport, ep_addr); + if (is_in) { + p_vendor->ep_in = ep_addr; + } else { + p_vendor->ep_out = ep_addr; + p_vendor->rx_xfer_len = + CFG_TUD_VENDOR_RX_NEED_ZLP ? CFG_TUD_VENDOR_RX_EPSIZE : tu_edpt_packet_size(desc_ep); + #if CFG_TUD_VENDOR_RX_MANUAL_XFER == 0 + TU_ASSERT(usbd_edpt_xfer(rhport, p_vendor->ep_out, _vendord_epbuf[idx].epout, + p_vendor->rx_xfer_len, false)); + #endif + } + break; + + #if CFG_TUD_VENDOR_EP_INT_IN || CFG_TUD_VENDOR_EP_INT_OUT + case TUSB_XFER_INTERRUPT: + // stall/clear only for the enabled direction (an endpoint of a disabled + // direction was never opened, so must not be poked in the dcd) + #if CFG_TUD_VENDOR_EP_INT_IN + if (is_in) { + usbd_edpt_stall(rhport, ep_addr); + usbd_edpt_clear_stall(rhport, ep_addr); + p_vendor->ep_int_in = ep_addr; + } + #endif + #if CFG_TUD_VENDOR_EP_INT_OUT + if (!is_in) { + usbd_edpt_stall(rhport, ep_addr); + usbd_edpt_clear_stall(rhport, ep_addr); + p_vendor->ep_int_out = ep_addr; + p_vendor->int_rx_xfer_len = tu_edpt_packet_size(desc_ep); + } + #endif + break; + #endif + + #if CFG_TUD_VENDOR_EP_ISO_IN || CFG_TUD_VENDOR_EP_ISO_OUT + case TUSB_XFER_ISOCHRONOUS: + #if CFG_TUD_VENDOR_EP_ISO_IN + if (is_in) { + TU_ASSERT(vendord_iso_ep_activate(rhport, desc_ep)); + p_vendor->ep_iso_in = ep_addr; + } + #endif + #if CFG_TUD_VENDOR_EP_ISO_OUT + if (!is_in) { + TU_ASSERT(vendord_iso_ep_activate(rhport, desc_ep)); + p_vendor->ep_iso_out = ep_addr; + p_vendor->iso_rx_xfer_len = tu_edpt_packet_size(desc_ep); + } + #endif + break; + #endif + + default: + break; // unsupported endpoint type / direction gate disabled: ignore + } + } + p_desc = tu_desc_next(p_desc); + } + + TU_VERIFY(alt_found); // unknown alt: endpoints were never cleared, current altsetting intact + p_vendor->cur_alt = alt; + return true; +} + +uint16_t vendord_open(uint8_t rhport, const tusb_desc_interface_t *desc_itf, uint16_t max_len) { + TU_VERIFY(TUSB_CLASS_VENDOR_SPECIFIC == desc_itf->bInterfaceClass, 0); + const uint8_t* desc_end = (const uint8_t*)desc_itf + max_len; + + const uint8_t idx = find_vendor_itf(0); + TU_ASSERT(idx < CFG_TUD_VENDOR, 0); + vendord_interface_t *p_vendor = &_vendord_itf[idx]; + p_vendor->rhport = rhport; + p_vendor->itf_num = desc_itf->bInterfaceNumber; + // p_itf_desc is assigned only after the parse succeeds: find_vendor_itf() treats a non-NULL + // p_itf_desc as an occupied slot, so setting it before a mid-parse TU_ASSERT could fail would + // leak the slot (a retry would find no free interface until the next bus reset). + + // Consume every altsetting of this interface and hardware-open each endpoint exactly once + // (bulk/interrupt via usbd_edpt_open, isochronous FIFO-allocated; iso activation and the + // toggle reset happen on altsetting selection). An endpoint address that recurs in another + // altsetting must carry an identical configuration, since it is opened only on first sight; + // reconfiguring the same address per-alt is not supported and is rejected here. + uint8_t seen_type[CFG_TUD_ENDPPOINT_MAX][2]; + uint16_t seen_mps[CFG_TUD_ENDPPOINT_MAX][2]; + tu_memclr(seen_type, sizeof(seen_type)); // 0 == TUSB_XFER_CONTROL, never used as a data ep here + const uint8_t* p_desc = tu_desc_next(desc_itf); + while (tu_desc_in_bounds(p_desc, desc_end)) { + const uint8_t desc_type = tu_desc_type(p_desc); + if (desc_type == TUSB_DESC_INTERFACE_ASSOCIATION) { + break; + } + if (desc_type == TUSB_DESC_INTERFACE) { + if (((const tusb_desc_interface_t*)p_desc)->bInterfaceNumber != p_vendor->itf_num) { + break; // next interface + } + } else if (desc_type == TUSB_DESC_ENDPOINT) { + const tusb_desc_endpoint_t* desc_ep = (const tusb_desc_endpoint_t*) p_desc; + const uint8_t epnum = tu_edpt_number(desc_ep->bEndpointAddress); + const uint8_t dir = tu_edpt_dir(desc_ep->bEndpointAddress); + const uint8_t xfer = desc_ep->bmAttributes.xfer; + const uint16_t mps = tu_edpt_packet_size(desc_ep); + TU_ASSERT(epnum < CFG_TUD_ENDPPOINT_MAX, 0); + + if (seen_type[epnum][dir] != 0) { + // reused address in a later altsetting: must be an exact match (opened only once) + TU_ASSERT(seen_type[epnum][dir] == xfer && seen_mps[epnum][dir] == mps, 0); + } else { + switch (xfer) { + case TUSB_XFER_BULK: + TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0); + break; + + #if CFG_TUD_VENDOR_EP_INT_IN || CFG_TUD_VENDOR_EP_INT_OUT + case TUSB_XFER_INTERRUPT: + #if CFG_TUD_VENDOR_EP_INT_IN + if (dir == TUSB_DIR_IN) { + TU_ASSERT(mps <= CFG_TUD_VENDOR_EP_INT_IN_BUFSIZE, 0); + TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0); + } + #endif + #if CFG_TUD_VENDOR_EP_INT_OUT + if (dir == TUSB_DIR_OUT) { + TU_ASSERT(mps <= CFG_TUD_VENDOR_EP_INT_OUT_BUFSIZE, 0); + TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0); + } + #endif + break; + #endif + + #if CFG_TUD_VENDOR_EP_ISO_IN || CFG_TUD_VENDOR_EP_ISO_OUT + case TUSB_XFER_ISOCHRONOUS: + #if CFG_TUD_VENDOR_EP_ISO_IN + if (dir == TUSB_DIR_IN) { + TU_ASSERT(mps <= CFG_TUD_VENDOR_EP_ISO_IN_BUFSIZE, 0); + TU_ASSERT(vendord_iso_ep_alloc(rhport, desc_ep), 0); + } + #endif + #if CFG_TUD_VENDOR_EP_ISO_OUT + if (dir == TUSB_DIR_OUT) { + TU_ASSERT(mps <= CFG_TUD_VENDOR_EP_ISO_OUT_BUFSIZE, 0); + TU_ASSERT(vendord_iso_ep_alloc(rhport, desc_ep), 0); + } + #endif + break; + #endif + + default: + break; // unsupported endpoint type / direction gate disabled: ignore + } + seen_type[epnum][dir] = xfer; + seen_mps[epnum][dir] = mps; + } + } + p_desc = tu_desc_next(p_desc); + } + // parse succeeded: commit the descriptor pointer (marks the slot occupied) before selecting alt 0 + p_vendor->p_itf_desc = (const uint8_t*) desc_itf; + p_vendor->itf_desc_len = (uint16_t)((uintptr_t)p_desc - (uintptr_t)desc_itf); + + // default altsetting active until the host selects another + TU_ASSERT(vendord_set_alt(rhport, idx, 0), 0); + return p_vendor->itf_desc_len; +} + +#else // !CFG_TUD_VENDOR_ALT_SETTINGS + uint16_t vendord_open(uint8_t rhport, const tusb_desc_interface_t *desc_itf, uint16_t max_len) { TU_VERIFY(TUSB_CLASS_VENDOR_SPECIFIC == desc_itf->bInterfaceClass, 0); const uint8_t* desc_end = (const uint8_t*)desc_itf + max_len; @@ -275,6 +748,31 @@ uint16_t vendord_open(uint8_t rhport, const tusb_desc_interface_t *desc_itf, uin break; // end of this interface } else if (desc_type == TUSB_DESC_ENDPOINT) { const tusb_desc_endpoint_t* desc_ep = (const tusb_desc_endpoint_t*) p_desc; + + #if CFG_TUD_VENDOR_EP_INT_OUT || CFG_TUD_VENDOR_EP_INT_IN + if (desc_ep->bmAttributes.xfer == TUSB_XFER_INTERRUPT) { + const bool is_int_in = tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN; + (void) is_int_in; + #if CFG_TUD_VENDOR_EP_INT_IN + if (is_int_in) { + TU_ASSERT(tu_edpt_packet_size(desc_ep) <= CFG_TUD_VENDOR_EP_INT_IN_BUFSIZE, 0); + TU_ASSERT(usbd_edpt_open(rhport, desc_ep)); + p_vendor->ep_int_in = desc_ep->bEndpointAddress; + } + #endif + #if CFG_TUD_VENDOR_EP_INT_OUT + if (!is_int_in) { + TU_ASSERT(tu_edpt_packet_size(desc_ep) <= CFG_TUD_VENDOR_EP_INT_OUT_BUFSIZE, 0); + TU_ASSERT(usbd_edpt_open(rhport, desc_ep)); + p_vendor->ep_int_out = desc_ep->bEndpointAddress; + p_vendor->int_rx_xfer_len = tu_edpt_packet_size(desc_ep); + } + #endif + p_desc = tu_desc_next(p_desc); + continue; + } + #endif + TU_ASSERT(usbd_edpt_open(rhport, desc_ep)); uint16_t rx_xfer_len = CFG_TUD_VENDOR_RX_NEED_ZLP ? CFG_TUD_VENDOR_RX_EPSIZE : tu_edpt_packet_size(desc_ep); @@ -313,6 +811,40 @@ uint16_t vendord_open(uint8_t rhport, const tusb_desc_interface_t *desc_itf, uin return (uint16_t)((uintptr_t)p_desc - (uintptr_t)desc_itf); } +#endif // CFG_TUD_VENDOR_ALT_SETTINGS + +// Handle interface standard requests (GET/SET_INTERFACE when altsettings are enabled), +// delegate everything else to the application callback as before. +bool vendord_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { +#if CFG_TUD_VENDOR_ALT_SETTINGS + if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && + request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE) { + const uint8_t itf_num = tu_u16_low(request->wIndex); + uint8_t idx; + for (idx = 0; idx < CFG_TUD_VENDOR; idx++) { + if (_vendord_itf[idx].itf_num == itf_num && _vendord_itf[idx].p_itf_desc != NULL) { + break; + } + } + if (idx < CFG_TUD_VENDOR) { + if (request->bRequest == TUSB_REQ_SET_INTERFACE) { + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(vendord_set_alt(rhport, idx, tu_u16_low(request->wValue))); + return tud_control_status(rhport, request); + } + return true; + } else if (request->bRequest == TUSB_REQ_GET_INTERFACE) { + if (stage == CONTROL_STAGE_SETUP) { + return tud_control_xfer(rhport, request, &_vendord_itf[idx].cur_alt, 1); + } + return true; + } + } + } +#endif + return tud_vendor_control_xfer_cb(rhport, stage, request); +} + bool vendord_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { (void)rhport; (void)result; @@ -320,6 +852,33 @@ bool vendord_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint TU_VERIFY(idx < CFG_TUD_VENDOR); vendord_interface_t *p_vendor = &_vendord_itf[idx]; +#if CFG_TUD_VENDOR_EP_INT_OUT + if (ep_addr == p_vendor->ep_int_out) { + // not re-armed automatically: application calls tud_vendor_n_int_read_xfer() + tud_vendor_int_rx_cb(idx, _vendord_int_epbuf[idx].int_out, xferred_bytes); + return true; + } +#endif +#if CFG_TUD_VENDOR_EP_INT_IN + if (ep_addr == p_vendor->ep_int_in) { + tud_vendor_int_tx_cb(idx, xferred_bytes); + return true; + } +#endif +#if CFG_TUD_VENDOR_EP_ISO_OUT + if (ep_addr == p_vendor->ep_iso_out) { + // not re-armed automatically: application calls tud_vendor_n_iso_read_xfer() + tud_vendor_iso_rx_cb(idx, _vendord_iso_epbuf[idx].iso_out, xferred_bytes); + return true; + } +#endif +#if CFG_TUD_VENDOR_EP_ISO_IN + if (ep_addr == p_vendor->ep_iso_in) { + tud_vendor_iso_tx_cb(idx, xferred_bytes); + return true; + } +#endif + #if CFG_TUD_VENDOR_TXRX_BUFFERED if (ep_addr == p_vendor->rx_stream.ep_addr) { // Put received data to FIFO diff --git a/src/class/vendor/vendor_device.h b/src/class/vendor/vendor_device.h index 32216f25f..ce33e2f62 100644 --- a/src/class/vendor/vendor_device.h +++ b/src/class/vendor/vendor_device.h @@ -60,6 +60,68 @@ extern "C" { #define CFG_TUD_VENDOR_RX_NEED_ZLP 0 #endif +// Enable support for an optional interrupt OUT / interrupt IN endpoint in the vendor +// interface, each direction gated separately. Interrupt endpoints are non-buffered: +// OUT is armed manually one packet at a time with tud_vendor_n_int_read_xfer() (data +// delivered via tud_vendor_int_rx_cb), IN is a direct transfer via tud_vendor_n_int_write(). +#ifndef CFG_TUD_VENDOR_EP_INT_OUT + #define CFG_TUD_VENDOR_EP_INT_OUT 0 +#endif + +#ifndef CFG_TUD_VENDOR_EP_INT_IN + #define CFG_TUD_VENDOR_EP_INT_IN 0 +#endif + +// Buffer sizes for interrupt endpoint transfers, must be >= the endpoint max packet size +#ifndef CFG_TUD_VENDOR_EP_INT_OUT_BUFSIZE + #define CFG_TUD_VENDOR_EP_INT_OUT_BUFSIZE 64 +#endif + +#ifndef CFG_TUD_VENDOR_EP_INT_IN_BUFSIZE + #define CFG_TUD_VENDOR_EP_INT_IN_BUFSIZE 64 +#endif + +// Enable support for an optional isochronous OUT / IN endpoint, each direction gated +// separately, with the same non-buffered API shape as the interrupt pair. Isochronous +// endpoints must not claim bandwidth in the default altsetting (USB 2.0 5.6.3): place +// them in a non-zero altsetting and enable CFG_TUD_VENDOR_ALT_SETTINGS. +#ifndef CFG_TUD_VENDOR_EP_ISO_OUT + #define CFG_TUD_VENDOR_EP_ISO_OUT 0 +#endif + +#ifndef CFG_TUD_VENDOR_EP_ISO_IN + #define CFG_TUD_VENDOR_EP_ISO_IN 0 +#endif + +// Buffer sizes for isochronous endpoint transfers, must be >= the endpoint max packet size +#ifndef CFG_TUD_VENDOR_EP_ISO_OUT_BUFSIZE + #define CFG_TUD_VENDOR_EP_ISO_OUT_BUFSIZE 64 +#endif + +#ifndef CFG_TUD_VENDOR_EP_ISO_IN_BUFSIZE + #define CFG_TUD_VENDOR_EP_ISO_IN_BUFSIZE 64 +#endif + +// Enable alternate-setting support: the vendor interface may carry multiple altsettings, +// each with its own endpoint set. GET_INTERFACE is answered and SET_INTERFACE performed by +// closing the current altsetting's endpoints and opening the requested one's (isochronous +// endpoints are FIFO-allocated at open and activated on selection). The configuration +// descriptor must stay valid while mounted (static, the usual TinyUSB pattern). +// Non-buffered mode only. +#ifndef CFG_TUD_VENDOR_ALT_SETTINGS + #define CFG_TUD_VENDOR_ALT_SETTINGS 0 +#endif + +#if CFG_TUD_VENDOR_ALT_SETTINGS && CFG_TUD_VENDOR_TXRX_BUFFERED + #error CFG_TUD_VENDOR_ALT_SETTINGS requires non-buffered mode (CFG_TUD_VENDOR_RX/TX_BUFSIZE = 0) +#endif + +// An isochronous endpoint must not claim bandwidth in the default altsetting (USB 2.0 5.6.3), +// so it can only live in a non-zero altsetting, which requires alternate-setting support. +#if (CFG_TUD_VENDOR_EP_ISO_OUT || CFG_TUD_VENDOR_EP_ISO_IN) && !CFG_TUD_VENDOR_ALT_SETTINGS + #error CFG_TUD_VENDOR_EP_ISO_OUT/IN requires CFG_TUD_VENDOR_ALT_SETTINGS +#endif + //--------------------------------------------------------------------+ // Application API (Multiple Interfaces) i.e CFG_TUD_VENDOR > 1 //--------------------------------------------------------------------+ @@ -107,6 +169,43 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_n_write_str(uint8_t idx, return tud_vendor_n_write(idx, str, strlen(str)); } +//------------- Interrupt endpoints -------------// +#if CFG_TUD_VENDOR_EP_INT_OUT +// Arm the interrupt OUT endpoint for one packet, return false if a transfer is still ongoing. +// Received data is delivered via tud_vendor_int_rx_cb(); re-arm from the callback or by polling. +bool tud_vendor_n_int_read_xfer(uint8_t idx); +#endif + +#if CFG_TUD_VENDOR_EP_INT_IN +// Send on the interrupt IN endpoint (direct transfer, up to CFG_TUD_VENDOR_EP_INT_IN_BUFSIZE +// bytes). Returns number of bytes queued, 0 if the endpoint is busy or not opened. +uint32_t tud_vendor_n_int_write(uint8_t idx, const void *buffer, uint32_t bufsize); + +// Return available bytes for interrupt IN write: 0 while busy, else the buffer size +uint32_t tud_vendor_n_int_write_available(uint8_t idx); +#endif + +//------------- Isochronous endpoints -------------// +#if CFG_TUD_VENDOR_EP_ISO_OUT +// Arm the isochronous OUT endpoint for one packet, return false if a transfer is still ongoing. +// Received data is delivered via tud_vendor_iso_rx_cb(); re-arm from the callback or by polling. +bool tud_vendor_n_iso_read_xfer(uint8_t idx); +#endif + +#if CFG_TUD_VENDOR_EP_ISO_IN +// Send on the isochronous IN endpoint (direct transfer, up to CFG_TUD_VENDOR_EP_ISO_IN_BUFSIZE +// bytes). Returns number of bytes queued, 0 if the endpoint is busy or not opened. +uint32_t tud_vendor_n_iso_write(uint8_t idx, const void *buffer, uint32_t bufsize); + +// Return available bytes for isochronous IN write: 0 while busy, else the buffer size +uint32_t tud_vendor_n_iso_write_available(uint8_t idx); +#endif + +#if CFG_TUD_VENDOR_ALT_SETTINGS +// Return the currently selected alternate setting +uint8_t tud_vendor_n_alt(uint8_t idx); +#endif + // backward compatible #define tud_vendor_n_flush(idx) tud_vendor_n_write_flush(idx) @@ -161,6 +260,44 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_write_available(void) { return tud_vendor_n_write_available(0); } +#if CFG_TUD_VENDOR_EP_INT_OUT +TU_ATTR_ALWAYS_INLINE static inline bool tud_vendor_int_read_xfer(void) { + return tud_vendor_n_int_read_xfer(0); +} +#endif + +#if CFG_TUD_VENDOR_EP_INT_IN +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_int_write(const void *buffer, uint32_t bufsize) { + return tud_vendor_n_int_write(0, buffer, bufsize); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_int_write_available(void) { + return tud_vendor_n_int_write_available(0); +} +#endif + +#if CFG_TUD_VENDOR_EP_ISO_OUT +TU_ATTR_ALWAYS_INLINE static inline bool tud_vendor_iso_read_xfer(void) { + return tud_vendor_n_iso_read_xfer(0); +} +#endif + +#if CFG_TUD_VENDOR_EP_ISO_IN +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_iso_write(const void *buffer, uint32_t bufsize) { + return tud_vendor_n_iso_write(0, buffer, bufsize); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tud_vendor_iso_write_available(void) { + return tud_vendor_n_iso_write_available(0); +} +#endif + +#if CFG_TUD_VENDOR_ALT_SETTINGS +TU_ATTR_ALWAYS_INLINE static inline uint8_t tud_vendor_alt(void) { + return tud_vendor_n_alt(0); +} +#endif + // backward compatible #define tud_vendor_flush() tud_vendor_write_flush() @@ -176,6 +313,29 @@ void tud_vendor_rx_cb(uint8_t idx, const uint8_t *buffer, uint32_t bufsize); // Invoked when tx transfer is finished void tud_vendor_tx_cb(uint8_t idx, uint32_t sent_bytes); +#if CFG_TUD_VENDOR_EP_INT_OUT +// Invoked when data is received on the interrupt OUT endpoint. The endpoint is not +// re-armed automatically: call tud_vendor_n_int_read_xfer() to receive more. +void tud_vendor_int_rx_cb(uint8_t idx, const uint8_t *buffer, uint32_t bufsize); +#endif + +#if CFG_TUD_VENDOR_EP_INT_IN +// Invoked when an interrupt IN transfer is finished +void tud_vendor_int_tx_cb(uint8_t idx, uint32_t sent_bytes); +#endif + +#if CFG_TUD_VENDOR_EP_ISO_OUT +// Invoked when data is received on the isochronous OUT endpoint. The endpoint is not +// re-armed automatically: call tud_vendor_n_iso_read_xfer() to receive more. +void tud_vendor_iso_rx_cb(uint8_t idx, const uint8_t *buffer, uint32_t bufsize); +#endif + +#if CFG_TUD_VENDOR_EP_ISO_IN +// Invoked when an isochronous IN transfer is finished (result may be a missed frame: +// the data was not necessarily taken by the host, re-arm regardless) +void tud_vendor_iso_tx_cb(uint8_t idx, uint32_t sent_bytes); +#endif + //--------------------------------------------------------------------+ // Internal Class Driver API //--------------------------------------------------------------------+ @@ -183,6 +343,7 @@ void vendord_init(void); bool vendord_deinit(void); void vendord_reset(uint8_t rhport); uint16_t vendord_open(uint8_t rhport, const tusb_desc_interface_t *idx_desc, uint16_t max_len); +bool vendord_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request); bool vendord_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); #ifdef __cplusplus |
