summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/class/vendor/vendor_device.c597
-rw-r--r--src/class/vendor/vendor_device.h161
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